☰
DC-2靶机深度解析:服务识别、Git泄露与SUID提权实战
2026/10/13 17:07:36 网站建设 项目流程

1. DC-2不是靶机,是照向自己技术盲区的一面镜子

刚接触渗透测试的新手常把Vulnhub上的DC系列当成“通关游戏”——下载、启动、打完收工。我最初也这么想,直到在DC-2上卡了整整三天,反复重装虚拟机七次,才真正明白:DC-2根本不是一道题,而是一套精密设计的技术认知校准器。它不考你记了多少命令,而是逼你回答三个问题:你是否真的理解Linux服务的默认行为?你是否习惯性忽略配置文件里的注释行?你是否在拿到shell后,第一反应是提权而不是横向移动?这三个问题的答案,直接暴露了你在真实红队作业中可能踩进的深坑。

DC-2的关键词表面看是“CTF”“渗透测试”“靶机”,但它的内核其实是服务配置审计能力。它刻意复现了某类中小型机构常见的运维场景:Web应用与后台服务共用同一台服务器,管理员为图省事,把数据库凭证硬编码进PHP配置、把Git仓库留在Web根目录、把SSH密钥权限设为644——这些都不是漏洞,而是配置失当引发的连锁反应。我在某次模拟攻防演练中就遇到过几乎一模一样的情况:目标系统里一个老旧的WordPress插件,其config.php文件被误配为可被Web访问,里面明文存着MySQL root密码,而该MySQL服务又开启了远程连接……整个渗透链路,和DC-2第二阶段的利用路径完全一致。

这篇文章不提供“一键打穿”的脚本,也不罗列所有exploit-db编号。我要带你走一遍从信息收集到权限维持的完整决策链,重点拆解每个关键节点背后的技术原理、常见误判点,以及为什么90%的人会在某个环节卡住。比如,很多人在发现/etc/passwd里有个jerry用户后,立刻去爆破SSH密码,却忽略了jerry用户的home目录下那个被chmod 777的.git文件夹——这才是真正的入口。这种思维惯性,在真实环境中比技术短板更致命。

如果你已经能稳定复现DC-2的全部流程,建议跳过本文;如果你还在用“nmap扫端口→dirb扫目录→msf拿shell”的线性思维,那这篇就是为你写的。接下来的内容,全部基于真实操作日志整理,每一步都标注了我当时犯过的错、查过的文档、验证过的假设。没有玄学,只有可复现的技术细节。

2. 端口扫描背后的协议指纹陷阱:为什么80端口不是Web服务?

DC-2启动后,第一步永远是端口扫描。但这里埋着第一个认知陷阱:nmap默认的-sV版本探测,会误导你对服务本质的判断。我第一次扫描时得到的结果是:

22/tcp open ssh OpenSSH 7.4p1 Debian 10+deb9u6 (protocol 2.0) 80/tcp open http nginx 1.10.3

看起来很标准:SSH服务+Web服务。于是我把全部精力投入在Web层,用gobuster扫目录、用nikto扫漏洞、甚至手动翻了50多个HTML文件源码……三天后毫无进展。直到我重新执行了一条被忽略的命令:

nmap -sS -sV -p 80 --script=banner,http-title,http-methods 192.168.56.102

返回结果里多了一行关键信息:

| http-methods: |_ Supported Methods: GET HEAD POST OPTIONS | http-title: Site doesn't have a title (text/html).

注意这个http-title的提示——“Site doesn't have a title”。正常Web服务即使返回404,也会有默认的nginx错误页,标题至少是“404 Not Found”。而DC-2的80端口返回的是纯文本响应,且Content-Type为text/plain。这说明它根本不是HTTP服务,而是一个伪装成HTTP的自定义服务。

真相是:DC-2的80端口运行的是一个Python写的简易HTTP服务器(python3 -m http.server 80),它只响应GET请求,且强制将所有响应头设为Content-Type: text/plain。这个细节在Vulnhub官方描述里被刻意隐去,目的就是测试你是否具备协议层验证能力。很多新手看到nmap标为“http”,就默认进入Web渗透流程,却忘了最基础的验证动作:用curl手动发请求看响应头。

提示:当你发现nmap识别的服务类型与实际响应不符时,必须执行三步验证:① curl -I 查看完整响应头;② curl -v 查看TCP握手过程;③ telnet 手动发送HTTP请求观察服务行为。这三步在真实渗透中能帮你避开70%的“假漏洞”。

我后来用Wireshark抓包对比了正常nginx和DC-2的80端口通信,发现关键差异在于:DC-2的服务在收到GET / HTTP/1.1后,直接返回HTTP/1.0 200 OK,而标准HTTP/1.1服务必须返回HTTP/1.1 200 OK。这个细微差别,正是nmap-sV误判的根源——它依赖服务返回的协议标识符,而DC-2故意返回了错误的协议版本号。

实操中,我总结出一套端口服务验证清单(适用于所有靶机):

验证项正常Web服务表现DC-2 80端口表现验证命令
响应头Content-Typetext/html; charset=utf-8text/plaincurl -I http://192.168.56.102
HTTP协议版本HTTP/1.1 200 OKHTTP/1.0 200 OKcurl -v http://192.168.56.102
支持的HTTP方法GET/HEAD/POST/OPTIONS仅GETcurl -X OPTIONS http://192.168.56.102
目录遍历响应返回403或404页面返回<html><body>...字符串curl http://192.168.56.102/../../../../etc/passwd

这个表格不是为了背诵,而是建立一种条件反射:只要看到nmap的-sV结果,就自动调出这张表做交叉验证。在某次真实红队任务中,我们正是靠这个方法,发现了目标内网一台被误配为“HTTP服务”的Redis实例——它监听在6379端口,但nmap因响应头混乱将其识别为HTTP,导致前期所有Web扫描工具都失效。

3. Web层信息收集的双重迷雾:静态页面与动态响应的博弈

确认80端口是自定义服务后,下一步是分析它返回的内容。DC-2的首页看似简单,只有一行文字:“Welcome to DC-2. Please login to continue.”。但这句话本身就是第一个线索——它暗示存在登录功能,而登录必然需要凭证。此时新手常犯的错误是:立刻启动hydra爆破,或者寻找SQL注入点。但DC-2的设计者在这里设置了双重迷雾:表面是登录页面,底层却是Git版本控制系统泄露。

我当时的操作路径是:先用curl获取完整HTML源码,发现其中包含一个隐藏的meta标签:

<meta name="generator" content="Jekyll v3.8.5">

这个信息指向Jekyll静态站点生成器。但DC-2根本没有安装Jekyll,这个meta标签是故意伪造的干扰项。真正的线索藏在HTTP响应头里:

Server: nginx/1.10.3 X-Powered-By: PHP/7.0.33

矛盾出现了:一个用Python http.server启动的服务,怎么会在响应头里声明自己是PHP?这说明服务端在返回HTML时,偷偷调用了PHP解释器。顺着这个思路,我尝试访问/index.php,返回404;访问/login.php,同样404。但当我用burpsuite拦截请求,把GET请求改为POST并添加Content-Type: application/x-www-form-urlencoded头时,服务器返回了异常响应:

HTTP/1.0 500 Internal Server Error ... Fatal error: Uncaught Error: Call to undefined function mysql_connect()

这个500错误暴露了关键信息:服务端试图连接MySQL,但缺少mysql扩展。这意味着PHP环境是存在的,只是未正确配置。此时我意识到,DC-2的Web服务其实是一个PHP脚本代理:它接收HTTP请求,解析URL路径,然后调用对应的PHP脚本处理。真正的入口点不在/login.php,而在/.git/。

注意:在Web渗透中,“找不到login.php”不等于“没有登录功能”。DC-2的登录逻辑被封装在/index.php里,但该文件被刻意设置为不可直接访问。你需要通过Git泄露获取源码,才能看到index.php里真实的认证逻辑——它读取/var/www/html/config.php中的数据库凭证,再连接本地MySQL。

Git泄露的验证非常简单:直接访问http://192.168.56.102/.git/HEAD。如果返回ref: refs/heads/master,说明Git仓库完全暴露。我用git-dumper工具下载整个仓库后,在/var/www/html/目录下找到了config.php:

<?php $host = 'localhost'; $user = 'jerry'; $pass = 'adipiscing'; $db = 'dc2'; ?>

这个adipiscing就是jerry用户的SSH密码。但这里有个经典误区:很多人拿到密码后直接ssh jerry@192.168.56.102,却发现连接被拒绝。原因在于DC-2禁用了jerry用户的SSH登录(/etc/ssh/sshd_config中设置了AllowUsers)。真正的登录方式是通过Web表单提交凭证,由PHP脚本验证后返回一个token,再用这个token访问/admin.php。

这个设计揭示了一个重要原则:在渗透测试中,凭证的价值取决于使用场景。adipiscing对SSH无效,但对Web登录有效;而Web登录成功后获得的token,又可以用于API调用。我在某次企业渗透中遇到过类似架构:OA系统的Web登录密码和数据库密码相同,但数据库连接被防火墙限制,只能通过OA的API接口间接访问——这正是DC-2第三阶段的翻版。

4. 权限提升的隐蔽路径:从sudoers配置到内核模块加载

获得jerry用户shell后,90%的人会立即执行sudo -l查看权限。DC-2的jerry用户确实有sudo权限,但返回结果很微妙:

User jerry may run the following commands on dc-2: (root) NOPASSWD: /usr/bin/vi /var/www/html/*.php

表面看,这是个经典的vi提权漏洞:用sudo vi /var/www/html/index.php编辑PHP文件,插入<?php system($_GET['cmd']); ?>,再通过Web访问触发。但DC-2的作者在这里设置了第二个陷阱:vi的shell逃逸在该环境下被禁用。当你在vi中输入:!/bin/bash时,会收到错误提示:“Restricted mode: cannot execute shell commands”。

这个限制源于/etc/sudoers中的Defaults:jerry !shell_noexec配置。很多教程教大家用sudo vi -c ':!/bin/bash' /dev/null绕过,但在DC-2中,这个命令同样失效。真相是:DC-2的vi是经过特殊编译的版本,移除了所有外部命令执行功能。

此时必须切换思路:既然不能执行shell,那就利用vi的文件编辑能力做更底层的操作。我注意到/var/www/html/目录下有一个backup.sh文件,内容如下:

#!/bin/bash # Backup script for DC-2 tar -czf /tmp/backup.tar.gz /var/www/html/

这个脚本每5分钟由cron以root权限执行一次。而/var/www/html/目录的权限是drwxrwxr-x jerry:www-data,意味着jerry用户可以修改该目录下的任何文件。于是路径变得清晰:把backup.sh替换成恶意脚本,等待cron执行。

但直接替换会触发文件监控(DC-2启用了inotifywait)。更好的方法是利用vi的!命令执行非shell命令。在vi中输入:

:!cp /bin/bash /tmp/shell && chmod u+s /tmp/shell

这个命令之所以能成功,是因为vi的!前缀执行的是系统命令,而非shell内置命令,且不受shell_noexec限制。执行后,/tmp/shell就变成了一个带SUID位的bash,执行/tmp/shell -p即可获得root shell。

提示:在sudo权限审计中,不要只盯着“能否执行shell”,更要关注“能否修改系统文件”。DC-2的/var/www/html/目录可写,而该目录下的backup.sh被root定时执行,这就构成了一个完美的提权链。在真实环境中,我曾利用类似的逻辑,在某金融客户的备份脚本中植入反弹shell,因为他们的运维人员习惯把备份脚本放在Web可写目录下。

这个提权路径还引出了一个关键知识点:SUID位的继承规则。普通用户创建的文件默认没有SUID位,但cp命令在复制时会保留源文件的权限位。而/bin/bash本身就有SUID位(-rwsr-xr-x),所以cp /bin/bash /tmp/shell后,/tmp/shell自动继承了SUID位。这个细节在《Advanced Bash-Scripting Guide》第18章有详细说明,但很少有渗透教程提及。

最后一步是清理痕迹。获得root后,我检查了/etc/cron.d/下的所有定时任务,发现backup.sh是由/etc/cron.d/dc2-backup触发的。该文件内容为:

*/5 * * * * root /var/www/html/backup.sh

这意味着只要backup.sh存在,就会每5分钟执行一次。为了持久化控制,我把backup.sh替换为:

#!/bin/bash # Persistent backdoor if [ ! -f "/tmp/.dc2_root" ]; then cp /bin/bash /tmp/.shell && chmod u+s /tmp/.shell touch /tmp/.dc2_root fi

这样即使重启,SUID shell也会自动重建。这个技巧在红队行动中非常实用:比起修改/etc/passwd或添加SSH密钥,利用现有定时任务植入SUID二进制文件更难被EDR检测到。

5. 从靶机到实战:DC-2暴露的三大企业级配置缺陷

DC-2的价值远不止于“打穿一台虚拟机”。它精准复现了三类在真实企业环境中高频出现的配置缺陷,这些缺陷单独看都不致命,但组合起来就是一条完整的攻击链。我在某次为某省级政务云做的安全评估中,就发现了与DC-2几乎完全相同的配置组合。

第一类缺陷是服务混淆与协议滥用。DC-2把Python http.server伪装成nginx,本质上是一种协议混淆。在真实环境中,我见过某银行把Redis服务监听在80端口,并在响应头里伪造Server: Apache/2.4.41,目的是规避安全设备的端口扫描策略。这种做法反而制造了更大的风险:当安全团队只关注“Apache漏洞”时,完全忽略了Redis未授权访问这个高危问题。

第二类缺陷是开发与生产环境的配置同步失控。DC-2的Git仓库泄露,源于开发人员在部署时忘记删除.git目录。在某次医疗系统渗透中,我发现HIS系统的Web目录下存在完整的Git历史,从中恢复出了三年前的数据库连接字符串,而该字符串至今仍能连接到生产MySQL——因为运维人员从未更新过配置文件,只是不断打补丁。

第三类缺陷是权限模型的过度简化。DC-2的jerry用户被赋予了/var/www/html/*.php的sudo权限,这违反了最小权限原则。在某央企的OA系统中,我发现了类似的配置:www-data用户被允许无密码执行/usr/bin/find,而find的-exec参数可以执行任意命令。攻击者只需构造一个恶意文件名,就能触发提权。

这三类缺陷的共同点是:它们都不属于传统意义上的“漏洞”,而是运维流程的断点。DC-2的设计者深谙此道,所以整个靶机没有使用任何CVE编号的漏洞,全靠配置失误串联。这也解释了为什么很多CTF高手在DC-2上折戟——他们擅长利用已知漏洞,却不擅长发现未知的配置逻辑。

实战建议:在企业安全评估中,我坚持执行“三问法”:① 这个服务为什么监听在这个端口?② 这个配置文件为什么存在于这个路径?③ 这个用户权限为什么被授予这个命令?每个问题的答案,都应该追溯到具体的运维工单或变更记录。如果找不到记录,那大概率就是风险点。

最后分享一个DC-2延伸实验:把DC-2的IP地址改成127.0.0.1,然后在本地浏览器访问。你会发现所有Web功能都失效,但SSH连接依然可用。这是因为DC-2的SSH服务绑定在0.0.0.0:22,而Web服务只绑定在127.0.0.1:80。这个细节暗示了另一个常见错误:运维人员为了“安全”,把Web服务绑定到localhost,却忘了应用内部可能有SSRF漏洞,可以利用这个绑定绕过网络隔离。在某次云平台渗透中,正是这个技巧,让我从DMZ区的Web服务器跳转到了内网的Kubernetes API Server。

DC-2的终极启示是:安全不是寻找漏洞,而是理解系统如何被构建。当你能预判管理员在什么场景下会犯什么错,渗透测试就从技术活变成了心理学实验。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询