sql注入小小结

一些注入方法

报错注入

基于floor的报错注入:

count(*), rand(), group by

缺一不可

报错公式:

?id=2' and (select 1 from (select count(*), concat( floor(rand(0)*2),(select group_concat(table_name) from information_schema.tables where table_schema=database())x from information_schema.tables group by x)a)--+

原理解释:

count(): 计算选择结果的数目
rand(): 产生0-1之间的随机数,可以设置参数种子,设置参数种子时产生的随机数是有规律的,使用rand(0)是因为此时产生的随机数得到的0,1序列是固定的,并且恰好可以产生报错
group by: 根据某个列值进行分组输出,group by的理解需要理解它的具体执行过程

下面来看一下 floor(rand(0)*2) 产生报错的具体原理。

  • floor(rand(0)*2) 产生的序列:0 1 1 0 1 1 0...

  • select count(*) from test group by floor(rand(0)*2) 的具体执行过程

    数据库在执行 count(*) 的语句时,首先会生成一个虚表,按照实表中的记录的顺序对从上往下执行

    • 第一条记录:首先计算 floor(rand(0)*2) ,结果为 0 ,在虚拟表中查询,发现没有键值 0 ,于是再执行一次 floor(rand(0)*2),这是第二次计算,计算结果为 1, 此时执行插入操作,插入键值为 1, count(*)1,此时针对实表的第一条记录的操作完成
    • 第二条记录:计算 floor(rand(0)*2) ,得到的结果为 1,在虚表中查询,发现键值为 1 的记录已存在,于是对虚表进行更新,键值为 1 的记录对应的 count(*) 的值变为 2
    • 第三条记录:计算 floor(rand(0)*2) , 这是第四次计算,结果为 0,在虚表中查询,发现没有键值为 0 的记录,于是要执行插入操作。再次计算 floor(rand(0)*2) ,得到的结果为 1,此时执行插入操作就会产生报错,因为键值为 1 的记录早已存在。

    可以看到,只要表中的记录超过三条,使用 floor(rand(0)*2) 必将报错。

    对于表中记录低于三条的表,也可以尝试用 floor(rand()*2) ,使用这种方法具有一定的概率性,多试几次可能会注入成功。

还有一种报错注入是利用 updatexml()executexml()
这两个函数都有三个参数,第一个参数表示xml文件中目标替换文字,第二个参数表示目标文档的绝对路径,第三个参数表示用来替换的文字。如果第二个参数不是文件路径的格式,就会产生报错。

常见的形式为^updatexml(1, concat('~',(select database()),'~'), 1)

基于布尔的盲注

基于布尔的SQL盲注

关键就是构造逻辑表达式,一般可以利用的函数有:

left(s, 1)	# 从左往右取字符串的前 1 位
right(s, 1) # 从右往左取字符串的前 1 位
substr(s, 0, 1) # 从字符串s的第 0 位开始取,取 1 位
ascii('a') # 返回字符的ascii码值

注意逻辑符号的使用(|| & ^

常见的形式:

ascii(substr((seletc schema_name from information_schema.schemata),0,1))=101 
或
substr((select schema_name from information_schema.schemata),0,1)=chr(101)

基于时间的盲注

一般与 IF 语句一起使用,一般形式:' and IF(ascii(substr((select database()),0,1))=101, sleep(), 0)

关于利用:个人觉得只要用逻辑符把语句连在一起就行了。

union注入

union注入适用于 sql 语句中不含 order by 且是有回显的情况

要注意的是保证 union 前后的 select 语句的字段数是相同的

select uName,email,score from user union select 1,2,password from user

宽字节注入

这种情况比较适用于过滤了'的情况,利用宽字符注入,我们可以使得'逃逸出来

原理如下:

' -> \' -> %5c%27
%df' -> %df\' -> %df%5c%27

utf编码中,%df%5c,所以当我们输入%df'时,解析结果%df%5c%27,使用utf编码,解码结果为縗',这样'就被逃逸出来了

PS: utf解码的时候如果第一个字节大于128的话,就会连读两个字节,解码成一个汉字

防御宽字节注入:

  1. 调用 mysql_set_charset 将所有连接的字符集设置为gbkmysql_set_charset("gbk", $conn)),并且对所有的输入都进行mysql_real_escape_string,但是对于一些老的cms,这样做还是不行的,因为很多老的cms使用的addslashes进行过滤,mysql_real_escape_string的转义会考虑当前的字符集,不会出现将5c27拼接在一起的情况

  2. 在所有sql语句前指定连接的形式是二进制。产生宽字节注入的原因就是mysql在接受到客户端的信息后,直接进行gbk解码,导致引号逃逸了出来。所以解决这个问题最根本的手段还是从编码上入手。做以下设置:

    SET character_set_connection=gbk, character_set_results=gbk, character_set_client=binary
    

    mysql从客户端接收到数据时,会把数据当成character_set_clientbinary编码,然后再把数据转化character_set_connectiongbk 编码,之后再进到响应的表中,把数据转化成数据表的具体编码,再进行数据操作。产生执行结果后,把结果转化成character_set_resultsgbk编码。在上面的过程中,' 无法利用编码逃逸出来,自然就避免了宽字符注入。(但是为啥这样不会使结果错误呢?)

    1. 使用utf-8编码

堆叠注入

堆叠注入就是以;为界,同时执行多个语句,与union相比之下,优点主要是可以使用除select之外的其他语句(insert delete create update等)

但是缺点是不是所有的环境下都可以使用堆叠注入,受限于所使用的数据库以及APIOracle在任何情况下不支持堆叠注入,会直接报错,mysql和Postgresql在本身是支持堆叠注入的,所以就受限于API,并且一般web服务只返回一个信息,所以我们不能使用堆叠注入执行获取信息的操作,并且也不能知道注入语句是否执行成功。可以看到,堆叠注入的局限性比较大,使用union进行注入的频率大于堆叠注入。

二次注入

第一次将值插入到表中,插入的时候进行转义,但是插入到表中的是原本的数据。于是在第二次引出数据的时候,如果没有做转义的话,就会直接将数据拼接在语句中产生执行

其他注入点

limit注入

注入点在 limit 后面,一般的形式有:

select * from xxx limit [注入点]
select * from xxx order by xxx limit [注入点]

对于第一种来说,我们通过 union 注入即可

但是对第二种来说,由于 order by 的存在,我们不可以使用 union 注入,查看mysql 5.x 的文档中 select 的语法:

SELECT
    [ALL | DISTINCT | DISTINCTROW ]
      [HIGH_PRIORITY]
      [STRAIGHT_JOIN]
      [SQL_SMALL_RESULT] [SQL_BIG_RESULT] [SQL_BUFFER_RESULT]
      [SQL_CACHE | SQL_NO_CACHE] [SQL_CALC_FOUND_ROWS]
    select_expr [, select_expr ...]
    [FROM table_references
    [WHERE where_condition]
    [GROUP BY {col_name | expr | position}
      [ASC | DESC], ... [WITH ROLLUP]]
    [HAVING where_condition]
    [ORDER BY {col_name | expr | position}
      [ASC | DESC], ...]
    [LIMIT {[offset,] row_count | row_count OFFSET offset}]
    [PROCEDURE procedure_name(argument_list)]
    [INTO OUTFILE 'file_name' export_options
      | INTO DUMPFILE 'file_name'
      | INTO var_name [, var_name]]
    [FOR UPDATE | LOCK IN SHARE MODE]]

可以看到 LIMIT 后面还可以有 PROCEDRUEINTOINTO 的话暂时不考虑

考虑PROCEDUREMYSQL中默认有的存储过程有 ANALYSE,可以利用这个存储过程。

ANALYSE 存在两个参数(ANALYSE(1,1)),考虑在参数中加入我们想要执行的注入语句

比如:

select * from xxx order by xxx limit 1,1 procedure analyse((updatexml(rand(),concat(0x3a,IF(substr(version(),0,1) LIKE 5), BENCHMARK(10000000,sha(1)),1),rand())),1)

需要注意的是,这里延迟函数不能用 sleep(),只能使用 BENCHMARK(),当然上面这种形式用于无回显的情况,如果有回显显示报错信息的话,那么就不必要使用时间盲注了

order by注入

注入点形式:

select * from xxx order by [注入点]

最基本的利用是,可以通过order by来测试表中列的数目,order by + num,如果这个num数值大于列的数目,就会产生报错。

比较常用的是在order by后面使用报错注入

select * from xxx order by (updatexml('1',concat('~',version()),'1'));

sql注入的防御手段

  1. 使用mysql_real_escape_string()addslashes()

    mysql_real_escape_string()会转义' " \r \n NULL Control-Z

    addslashes()会转义' " \ NULL

    这对预防万能密码比较有效,同时对字符型注入也有一定的效果,但是要注意使用addslashes()函数时要注意宽字符注入。同时也要注意二次注入。

  2. 使用预编译

    sql语句进行预编译,预编译语句接受参数,不直接将参数拼接在语句中,也就试输无法通过参数来改变原先的sql语句,参数中的敏感符号也会被当作普通字符。JAVA中常使用预编译,php可以使用PDO预编译

    另外要注意,使用PDO编译虽然可以提高整个的安全性,但是也存在一些局限性:

    1. PDO预编译对于很多次的查询来说可以提高效率,但是如果是一次查询使用PDO查询的话,PDO就显得不太高效了
    2. 如果忘记销毁预处理语句,就可能导致资源泄露
    3. 预处理语句不能使用mysql5.0以前版本的缓存
    4. 预处理语句只针对一个连接,所以另外的连接不能使用同样的句柄。出于这个原因,一个先断开再重新连接的客户端会丢失句柄。(我的理解是在用户断开重连后PDO会重新预编译一次,也就是句柄变了,不知道这样理解对不对)
  3. 使用存储过程

    存储过程可以一定程度预防注入,但是要注意不要在存储过程中使用动态语句,也就是说不要出现变量拼接,一个例子:

    exec("select * from Student where Sno"+@id)
    select * from Student where Sno=@id
    

    上面两者的区别就是,上面直接拼接,该怎么注入还是怎么注入,但是下面的在执行时会检查@id的类型,如果不是int型的话,就会报错。

  4. 检查数据类型

    对于电话号码,年龄等等这种int型的数据,在操作前应该要对接收数据的类型进行检测

    例如:is_numeric(),ctype_digit(),但是这种方法也不是完全安全的,像is_numeric()就可以通过传入数组进行绕过

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值