Einführung
In meinem letzten Blogeintrag "Cobalt Strike - DNS Listener" haben wir über die Einrichtung eines DNS Listeners unter Cobalt Strike im Kontext von Microsoft Azure und dem Domain Registrar GoDaddy gesprochen. Ein DNS Listener hat durchaus seine Berechtigung, z.B. im Kontext von Long-Haul Servern um z.B. Persistenzen zu pflegen, bringt aber z.B. den Nachteil mit sich, dass die Übertragung über DNS sehr langsam sein kann und auch nicht für die Übertragung von großen Datenmengen gedacht ist.
In diesem Artikel möchten wir daher untersuchen, wie High Reputation Domains im Kontext von Content Delivery Networks (CDNs) unter Microsoft Azure in Verbindung mit einer C2 Domain und Nginx als Reverse Proxy für unsere Red Team Infrastruktur genutzt werden können. Im Folgenden gebe ich einen Überblick über die benötigten Ressourcen und die wichtigsten Themen, die wir im Detail behandeln werden.
- Auswahl einer C2-Domain
- Erstellung und Konfiguration der erforderlichen Komponenten in Microsoft Azure
- VM für Nginx Reverse Proxy
- VM für Cobalt Strike Team Server
- Content Delivery Network (CDN)
- Konfiguieren des DNS der C2-Domain
- Konfiguration von Cobalt Strike
- Mallable Profile
- Listener
- Konfiguration des Nginx Reverse Proxy
Am Ende dieses Artikels soll eine C2-Infrastruktur stehen, die die Kommunikation vom Implant (Beacon) auf dem Zielhost zum Cobalt Strike Team Server über den Pfad Azure CDN -> C2-Domain -> Nginx Reverse Proxy ermöglicht. Die folgende Abbildung zeigt das Konzept in vereinfachter Form.
Auswahl C2-Domain
In der Praxis ist es für unser geplantes C2-Setup aus CDN, Nginx und Cobalt Strike nicht unbedingt erforderlich, eine Domain als C2-Domain auszuwählen. Wie wir später noch sehen werden, möchten wir es jedoch vermeiden, die IP-Adresse unseres Nginx Reverse-Proxys als Origin-Hostname in der CDN-Konfiguration anzugeben. Stattdessen bevorzugen wir die Angabe des FQDN einer legitimen Subdomain im Kontext unserer C2-Domain, die über einen A-Record auf die öffentliche IP-Adresse unseres Nginx-Reverse-Proxys verweist. Dies mag auf den ersten Blick komplex erscheinen, wird aber im weiteren Verlauf des Artikels klarer und verständlicher erklärt.
Wie auch immer, für die Auswahl einer Domain welcher wir im Kontext unserer C2-Infrastruktur verwenden möchten gibt es mehrere Möglichkeiten. Einerseits kann man eine neue Domain registrieren und diese schrittweise aufbauen, indem man sie altern lässt und ihre Reputation aufbaut. Zum anderen besteht die Möglichkeit, eine bereits abgelaufene Domain über Dienste wie expireddomains.net zu erwerben. In der Praxis ist es im Rahmen eines Red Team Engagements aus Zeitgründen meist notwendig, eine expired Domain zu kaufen, die im Kontext des jeweiligen Szenarios verwendet werden kann.
Vor dem Kauf oder der Registrierung einer Domain sollte jedoch sichergestellt werden, dass diese ausreichend alt ist, einen guten Ruf hat und nicht auf den Blacklists von Sicherheitsanbietern steht. Die Konfiguration des DNS für unsere Domäne werden wir zu einem späteren Zeitpunkt in Angriff nehmen, wenn wir die Nginx VM in Azure erstellt haben und die öffentliche IP der VM kennen.
Microsoft Azure - Erforderliche Komponenten
Für die Implementierung unserer geplanten C2 Infrastruktur benötigen wir folgende Komponenten in Microsoft Azure:
- Linux VM für Nginx Reverse Proxy
- Linux VM für Cobalt Strike Team Server
- Edgio CDN Endpoint
Azure - Nginx VM
Im ersten Schritt wird die Linux-VM erstellt, die später als Nginx Reverse Proxy fungiert. Der Reverse Proxy spielt eine zentrale Rolle in der Command-and-Control (C2)-Infrastruktur, da er den eingehenden Datenverkehr verarbeitet und an den eigentlichen C2-Server weiterleitet. Was genau ein Reverse Proxy macht, wie er im C2-Kontext eingesetzt wird und worin der Unterschied zu einem einfachen Redirector (wie socat oder iptables) besteht, werden wir später bei der Konfiguration von Nginx im Detail erläutern.
Zunächst konzentrieren wir uns jedoch darauf, die Linux-VM in Microsoft Azure zu erstellen und zu konfigurieren.
Im Folgenden wird auf die Konfiguration der VM eingegangen, wobei zu beachten ist, dass nur auf die Punkte eingegangen wird, die konfiguriert bzw. geändert werden.
Basic Settings
Unter der Voraussetzung, dass wir über ein aktives Azure-Abonnement (Subscription) verfügen, beginnen wir mit der Grundkonfiguration der virtuellen Maschine (VM). Wir ordnen die VM einer Resource Group zu, vergeben einen Namen, wählen eine geeignete Region sowie ein geeignetes Image aus und nehmen die weiteren Grundeinstellungen vor. Die folgende Abbildung zeigt die konfigurierten Grundeinstellungen unserer Nginx VM.
In meinem letzten Artikel "Cobalt Strike - DNS Listener" haben wir darüber gesprochen, dass die sorgfältige Auswahl der Region ein entscheidender Faktor bei der Konfiguration ist. Diese Region sollte idealerweise mit dem Standort des Ziels übereinstimmen, um Anomalien bei ausgehenden DNS-Anfragen im Zielnetzwerk zu vermeiden. Eine nicht übereinstimmende Region könnte verdächtig erscheinen und unerwünschte Aufmerksamkeit auf den Datenverkehr lenken.
Auch in dieser C2-Konfiguration können wir die Region nach diesem Kriterium auswählen. In diesem speziellen Fall ist es jedoch aus OPSEC-Sicht weniger kritisch, wenn die Region nicht genau mit dem Standort des Ziels übereinstimmt. Dies liegt daran, dass der Nginx Reverse Proxy in dieser Architektur "nur" als Vermittler zwischen dem CDN in Azure und dem Cobalt Strike Team Server fungiert und nicht direkt zwischen dem Beacon und dem Cobalt Strike Team Server. Mit anderen Worten: Der Beacon auf dem Zielhost stellt zunächst eine Verbindung zum CDN her. Erst danach wird der Datenverkehr über den Origin-Hostname an den DNS-Server von GoDaddy und schließlich an den Nginx-Reverse-Proxy in Azure weitergeleitet.
Disks Settings
Wie in der folgenden Abbildung gezeigt, setzen wir die Einstellung für "OS disk type" auf "Standard SSD (locally-redundant storage)". Der Rest der Konfiguration bleibt unverändert.
Review & Create VM
Damit ist die Konfiguration der Linux-VM für unseren Redirector abgeschlossen. Nicht aufgeführte Einstellungen wurden nicht verändert und sind daher nicht explizit erwähnt. Im letzten Schritt führen wir eine Überprüfung der VM-Konfiguration durch, bevor wir die VM erstellen.
Nginx - Konfiguration Network Settings
Nachdem die VM erstellt wurde, können wir mit der Konfiguration der Netzwerkeinstellungen fortfahren. In diesem Abschnitt konzentrieren wir uns nur auf die Konfigurationselemente, die angepasst oder hinzugefügt wurden. Da wir bei der Erstellung der VM alle eingehenden Ports deaktiviert haben, beginnen wir mit der Anpassung dieser Einstellungen an unsere Bedürfnisse.
Wie in der Abbildung unten zu sehen ist, erlauben wir den SSH-Zugriff auf unsere Nginx VM, beschränken ihn aber auf eine Auswahl bestimmter IP-Adressen. Das bedeutet, dass der Zugriff auf die VM über SSH nur von diesen autorisierten IP-Adressen möglich ist, um unautorisierten Zugriff zu verhindern.
Zusätzlich öffnen wir den Port für HTTPS-Verbindungen, beschränken diesen aber im Gegensatz zu SSH nicht auf bestimmte IP-Adressen. Dies ist besonders wichtig, da wir in einem Red-Teaming-Szenario die öffentlichen IP-Adressen unseres Ziels normalerweise nicht im Voraus kennen. Eine Beschränkung des HTTPS-Zugriffs auf bestimmte IP-Adressen oder Bereiche könnte dazu führen, dass unser Redirector eingehende HTTPS-Anfragen des Ziels blockiert und somit die korrekte Weiterleitung zum Cobalt Strike Team-Server verhindert. Daher lassen wir HTTPS-Anfragen uneingeschränkt zu.
Nachdem wir die Konfiguration der inbound rules abgeschlossen haben, wollen wir der Nginx VM noch eine interne statische IPv4 Adresse zuweisen (in diesem Fall 10.3.0.4
). Dies ist notwendig, damit wir später die inbound rules der Cobalt Strike VM (die als nächstes erstellt wird) so konfigurieren können, dass eingehende Anfragen im Kontext von HTTPS nur von der internen IPv4 der Nginx VM akzeptiert werden. Wichtig: Voraussetzung für dieses Konzept ist, dass sich die Nginx VM und die Cobalt Strike VM im selben virtuellen Netzwerk befinden.
Die Konfiguration der Nginx VM in Azure ist soweit abgeschlossen, aber bevor wir uns um die Installation und Konfiguration von Nginx auf dieser VM kümmern, erstellen wir zunächst die restlichen Komponenten, die wir in Azure benötigen, nämlich eine VM für den Cobalt Strike Team Server und das Edgio CDN.
Azure - Cobalt Strike VM
Die VM für den Cobalt Strike Teamserver kann auf die gleiche Weise wie die Nginx VM erstellt werden. Wir verwenden das gleiche Linux Image "Debian 12 Bookworm", auch die Konfiguration der Basic Settings unterscheidet sich nicht, lediglich die Konfiguration der Inbound Rules ist etwas anders.
Cobalt Strike - Konfiguration Network Settings
Auch für die Cobalt Strike VM nehmen wir die notwendige Netzwerkkonfiguration für die Inbound Rules vor. Wie bei der Nginx VM wird auch die Cobalt Strike VM nur von bestimmten IP-Adressen über SSH erreichbar sein. Damit wird sichergestellt, dass nur autorisierte Benutzer auf die VM zugreifen können.
Zusätzlich öffnen wir den Port 50050, der für die Verbindung zwischen dem Cobalt Strike Client und dem Cobalt Strike Team Server benötigt wird. Auch hier beschränken wir den Zugriff auf diesen Port auf bestimmte IP-Adressen, um sicherzustellen, dass nur autorisierte IPs über Port 50050 mit dem Team Server kommunizieren können.
Wir lassen auch HTTPS-Verkehr zur Cobalt Strike VM zu, aber im Gegensatz zur Nginx VM beschränken wir die eingehenden Verbindungen auf bestimmte IP-Adressen. In diesem Fall erlauben wir im HTTPS-Kontext nur eingehende Anfragen von der internen IPv4-Adresse unserer Nginx VM, in diesem Beispiel 10.3.0.4
. Diese Konfiguration stellt sicher, dass unser Cobalt Strike Team Server generell nur eingehenden HTTP-Verkehr vom Nginx Reverse Proxy akzeptiert.
Diese Maßnahme erhöht die Sicherheit der Cobalt Strike Infrastruktur, indem der Zugriff strikt auf autorisierte Kommunikationswege und IP-Adressen beschränkt wird, was die Angriffsfläche minimiert und die Integrität unseres Cobalt Strike Team Servers schützt.
Wir weisen der Cobalt Strike VM auch eine statische IP-Adresse (10.3.0.5
) für die interne IPv4 zu, die später in der Nginx-Konfiguration verwendet wird. Dies stellt sicher, dass die Kommunikation zwischen der Nginx VM und der Cobalt Strike VM stabil und vorhersehbar bleibt, was insbesondere für die Konfiguration des Reverse-Proxys wichtig ist.
Damit ist die Grundkonfiguration der Cobalt Strike VM abgeschlossen. Um das Setup abzuschließen, werden wir uns später mit der Konfiguration des Malleable Profiles und des Cobalt Strike Listeners beschäftigen.
C2-Domain - DNS
Bevor wir mit der Einrichtung des CDN in Azure beginnen, müssen wir einen DNS-Eintrag in der C2-Domäne vornehmen, die später in der CDN-Konfiguration als Origin-Hostname verwendet wird, auf den unser CDN-Endpunkt verweist.
Wie in der folgenden Abbildung dargestellt, wählen wir zunächst einen Namen für die Subdomain (in diesem Fall "docs"), die in Verbindung mit unserer C2-Domain verwendet wird. Dieser Name wird dann über einen A-Record auf die öffentliche IP-Adresse unserer Nginx-VM verweisen. Auf diese Weise wird sichergestellt, dass das Implant (Beacon) am Ziel mit der Nginx-VM über das CDN und die C2-Domain unter Verwendung des Origin-Hostname kommuniziert.
Azure - CDN Endpoint
Der nächste Schritt ist die Erstellung des CDN-Endpunkts in Microsoft Azure, den wir für die Kommunikation unseres Command-and-Control-Verkehrs verwenden wollen. Ohne an dieser Stelle zu sehr ins Detail zu gehen, wie Content Delivery Networks (CDNs) funktionieren, sei erwähnt, dass sie in unserem Kontext eine entscheidende Rolle spielen. Sie bieten uns die Möglichkeit, Microsoft Azure-Domains (z.B. ajax.microsoft.com
) mit sehr hoher Reputation zu nutzen, um unseren Command-and-Control-Verkehr effektiv zu verschleiern.
Durch die Nutzung eines CDN können wir unseren Datenverkehr über vertrauenswürdige und häufig genutzte Infrastrukturen leiten, wodurch es viel schwieriger wird, unsere Aktivitäten zu erkennen und zu blockieren. Dies ist ein großer Vorteil in Red-Teaming-Szenarien, in denen es darum geht, unter dem Radar zu bleiben und verdächtigen Internet Traffic zu vermeiden. Der erste Schritt ist die Einrichtung des CDN in Azure, wie in der folgenden Abbildung dargestellt.
Als nächstes müssen wir uns für ein Angebot entscheiden und wählen die Variante "Standard Edgio".
Im nächsten Schritt kümmern wir uns um die grundlegende Konfiguration unseres CDN-Profils. Wie bei den VMs benötigen wir hierfür eine aktive Azure-Subscription und wählen als Resource Group diejenige aus, in der sich sowohl die Nginx- als auch die Cobalt Strike-VMs befinden. Der Name für das CDN kann frei gewählt werden, ebenso der CDN Endpoint Name, sofern dieser noch nicht vergeben ist. In diesem Beispiel verwenden wir z.B. xyz-cache.azuredge.net
.
Den "Origin Type" setzen wir auf "Custom Origin". Unter "Origin Hostname" geben wir den FQDN der Subdomain unserer C2-Domain an, den wir zuvor im DNS unserer C2-Domain angelegt haben und der über einen A-Record auf die öffentliche IP der Nginx VM in Azure verweist. Mit anderen Worten, wenn der Name unserer C2-Domain domain.com
lautet und wir im DNS eine Subdomain, z. B. "docs", definiert haben, die auf die öffentliche IP der Nginx VM zeigt, geben wir als "Origin Hostname" in diesem Fall docs.domain.com
ein.
Diese Konfiguration stellt sicher, dass alle Anfragen an den CDN-Endpunkt an die entsprechende Subdomain unserer C2-Domain weitergeleitet werden, die wiederum direkt auf die öffentliche IP der Nginx VM in Azure zeigt. Auf diese Weise können wir den Command-and-Control-Verkehr über das CDN effektiv verschleiern und sicherstellen, dass er letztendlich korrekt bei unserer Nginx VM ankommt.
Anschließend kann unsere CDN-Endpunkt erstellt werden, das Deployment kann etwas dauern, sollte aber nach wenigen Minuten abgeschlossen sein.
CDN - Konfiguration
Nachdem wir unser CDN erstellt haben, müssen wir noch einige Einstellungen in der Konfiguration vornehmen. Im ersten Schritt wählen wir in der Übersicht unseren Endpunkt aus, in diesem Fall xyz-cache.azureedge.net
, wie unten abgebildet.
Abschließend deaktivieren wir die Komprimierung und bestätigen mit "save".
Zusätzlich wollen wir noch die Konfiguration für die "Caching Rules" wie folgt anpassen, das "Caching behaviour" auf "Bypass cache" stellen und die angepasste Konfiguration wieder mit dem "Save"-Button bestätigen.
Damit ist die Konfiguration unserer CDN-Endpunkt abgeschlossen.
Zusammenfassung Azure
Unsere Vorbereitungen in Microsoft Azure sind nun abgeschlossen. Wir haben erfolgreich eine VM für unseren Nginx Reverse Proxy und eine VM für unseren Cobalt Strike Team Server installiert und konfiguriert. Beide VMs haben eine interne statische IPv4 Adresse, befinden sich im selben virtuellen Netzwerk und können somit intern miteinander kommunizieren.
Aus Sicherheitsgründen haben wir beide VMs so konfiguriert, dass eingehende SSH-Verbindungen nur von bestimmten IP-Adressen erlaubt sind, so dass wir kontrollieren können, wer sich über SSH verbinden darf. Zusätzlich erlauben beide VMs eingehende HTTPS-Verbindungen, wobei unsere Nginx VM HTTPS-Anfragen uneingeschränkt akzeptiert, während die Cobalt Strike VM eingehende HTTPS-Verbindungen nur von der internen IPv4-Adresse der Nginx VM zulässt.
Zusätzlich haben wir den Port 50050 auf der Cobalt Strike VM geöffnet, der für die Kommunikation zwischen dem Cobalt Strike Team Server und dem Client benötigt wird. Ähnlich wie bei den SSH-Verbindungen haben wir auch hier den Zugriff auf bestimmte IP-Adressen beschränkt, um genau kontrollieren zu können, wer sich von welchen IP-Adressen aus über den Cobalt Strike Client mit dem Team Server verbinden kann. Mit dieser Konfiguration haben wir eine solide Basis für ein sicheres und kontrolliertes Management unserer C2-Infrastruktur geschaffen.
Nginx - Reverse Proxy
Im nächsten Schritt verbinden wir uns über SSH mit unserer Nginx VM und beginnen mit der Installation und Konfiguration des Nginx Reverse Proxy. Bevor wir jedoch in die Details der Konfiguration eintauchen, ist es wichtig zu verstehen, was ein Reverse Proxy ist, wie er im Kontext von Command and Control (C2) Traffic verwendet werden kann und wie er sich von einem einfachen Redirector, der beispielsweise socat oder iptables verwendet, unterscheidet.
Was ist ein Reverse Proxy?
Ein Reverse Proxy (in unserem Fall Nginx) ist ein Server, der Anfragen von Clients (Implant am Ziel - Beacon) entgegennimmt und diese an einen Backend-Server (in unserem Fall unseren Cobalt Strike Team Server) weiterleitet. Vereinfacht ausgedrückt fungiert der Reverse Proxy in unserem C2-Setup als Vermittler zwischen dem Client (Beacon) und dem Backend-Server (Team Server), wobei der Client den eigentlichen Backend-Server nicht direkt kontaktiert und vereinfacht ausgedrückt nicht einmal weiß, welcher Server sich hinter dem Nginx Reverse Proxy befindet. Der Reverse Proxy verbirgt also die Identität und den Standort des Backend-Servers und schützt ihn so vor direkten Angriffen.
Darüber hinaus bietet ein Reverse Proxy zahlreiche Vorteile, wie z.B. Lastverteilung, indem der eingehende Datenverkehr auf mehrere Backend-Server verteilt wird, um Überlastungen zu vermeiden. Er kann auch als Sicherheitsbarriere dienen, indem er Anfragen filtert, bevor sie den Backend-Server erreichen, und indem er die SSL/TLS-Terminierung übernimmt, was die Verwaltung von Verschlüsselungszertifikaten vereinfacht. Darüber hinaus kann ein Reverse Proxy Inhalte zwischenspeichern, um die Antwortzeiten zu verbessern und die Last auf den Backend-Servern zu verringern.
Im Vergleich dazu arbeitet ein klassischer Proxy-Server, auch Forward Proxy genannt, auf der Client-Seite. Während der Forward Proxy vom Client verwendet wird, um Anfragen an das Internet zu senden und dabei eventuell Inhalte zu filtern oder den Zugriff zu kontrollieren, befindet sich der Reverse Proxy auf der Serverseite und verwaltet die eingehenden Anfragen der Clients. Der Forward Proxy agiert also als Stellvertreter für den Client, während der Reverse Proxy die Rolle des Stellvertreters für den Server übernimmt.
Diese Unterscheidung ist entscheidend für das Verständnis der jeweiligen Aufgaben der beiden Proxy-Typen innerhalb einer Netzwerkinfrastruktur. Während der Forward Proxy die Kontrolle auf der Client-Seite ausübt, ermöglicht der Reverse Proxy eine effiziente und sichere Verwaltung des Datenverkehrs auf der Server-Seite.
Reverse Proxy im Kontext von C2
Im Rahmen unserer C2-Infrastruktur setzen wir den Nginx Reverse Proxy als intelligenten Vermittler ein, der HTTPS-Anfragen von unserem Beacon gezielt an den Cobalt Strike Team Server weiterleitet. Im Gegensatz zu einem einfachen Redirector, der beispielsweise mit socat oder iptables arbeitet und alle eingehenden HTTPS-Anfragen pauschal und undifferenziert an den Cobalt Strike Team Server weiterleitet, bietet der Nginx Reverse Proxy die Möglichkeit, in der Konfiguration spezifische Kriterien festzulegen.
Mit Nginx können wir beispielsweise festlegen, dass nur HTTPS-Anfragen, die bestimmte URIs oder Host-Header enthalten (die im Mallable-Profil von Cobalt Strike definiert sind), an den Cobalt Strike Team-Server weitergeleitet werden. Das bedeutet, dass der Nginx Reverse Proxy in der Lage ist, eingehende HTTPS-Anfragen intelligent zu filtern. Nur Anfragen, die den von uns definierten Kriterien entsprechen und für den Cobalt Strike Team Server bestimmt sind, werden tatsächlich weitergeleitet.
Dieser Ansatz erhöht die Sicherheit unserer C2-Infrastruktur erheblich, da er den Command and Control Server vor unerwünschten Zugriffen durch Sicherheitsmaßnahmen des Blue Teams, automatisierte Scanner, Sandboxes und andere potenzielle Bedrohungen schützt. Durch den gezielten Einsatz von Nginx als Filter für legitimen Datenverkehr tragen wir wesentlich zur Sicherheit und Effizienz unserer C2-Infrastruktur bei.
Nginx - Setup
Nachdem der theoretische Teil zum Thema Reverse Proxy im Kontext von Command and Control soweit geklärt ist, widmen wir uns nun der praktischen Installation und Konfiguration.
SSL Zertifikat
Bevor wir mit der eigentlichen Konfiguration von Nginx beginnen, verwenden wir Certbot, um ein SSL-Zertifikat für unsere C2-Domain zu erstellen, das wir auf der Nginx VM verwenden möchten. Dies ist notwendig, da unser Cobalt Strike Server später kein dediziertes SSL-Zertifikat verwenden wird.
Da unser CDN jedoch auf Layer 7 (Application Layer) arbeitet, ist es notwendig, dass die Gegenstelle, in diesem Fall unser Nginx Reverse Proxy, über ein gültiges SSL-Zertifikat verfügt. Dadurch wird sichergestellt, dass der CDN-Dienst ordnungsgemäß mit unserem Proxy kommunizieren kann, ohne Sicherheitswarnungen auszulösen.
Der folgende certbot-Befehl kann verwendet werden, um das SSL-Zertifikat zu erstellen, wobei domain.com durch den Namen der eigenen C2-Domain ersetzt werden muss.
certbot certonly --manual --preferred-challenges=dns --email admin@domain.com --server https://acme-v02.api.letsencrypt.org/directory --agree-tos -d '*.domain.com' -d 'domain.com'
Nachdem wir den Befehl certbot
ausgeführt haben, erhalten wir den Wert für die erste _acme_challenge
, den wir später in die DNS-Einstellungen unserer C2-Domain eintragen müssen. Nachdem wir diesen Wert notiert haben, bestätigen wir mit Enter, um zum nächsten Schritt zu gelangen. Nun wird der Wert für den zweiten _acme_challenge
angezeigt, der ebenfalls als TXT-Record im DNS der C2-Domain hinterlegt werden muss.
Diese TXT-Records sind ein integraler Bestandteil des Verifizierungsprozesses, den Let's Encrypt (via Certbot) verwendet, um sicherzustellen, dass Sie die Kontrolle über die Domain haben. Sobald beide _acme_challenge
Records korrekt im DNS Ihrer C2-Domain eingerichtet sind, kann der Verifizierungsprozess abgeschlossen und das SSL-Zertifikat ausgestellt werden.
Die folgende Abbildung zeigt beispielhaft den `certbot`-Prozess für domain.com
. Dieser Name muss natürlich durch die tatsächliche Domain Ihrer C2-Infrastruktur ersetzt werden, die Sie im Rahmen Ihrer C2-Operationen verwenden möchten.
Dies ist ein wichtiger Schritt, um sicherzustellen, dass der CDN-Dienst erfolgreich auf die Nginx-Instanz zugreifen kann, da für die Kommunikation auf Layer 7 ein gültiges SSL-Zertifikat erforderlich ist.
Nachdem die beiden Einträge im DNS der C2-Domain vorgenommen wurden, sollte man ca. 5-10 Minuten warten, bevor man mit Enter den Vorgang zur Ausstellung der Certbot-Zertifikate abschließt. Diese Wartezeit ist wichtig, da es einige Zeit dauern kann, bis die DNS-Änderungen weltweit bekannt gegeben werden und die neuen TXT-Records vollständig verarbeitet sind.
Die Wartezeit stellt sicher, dass Let's Encrypt die TXT-Records korrekt erkennen und verifizieren kann, so dass der Zertifikatsausstellungsprozess erfolgreich abgeschlossen werden kann. Sobald diese Zeit abgelaufen ist, kann der Certbot-Prozess durch Drücken der Eingabetaste fortgesetzt und das SSL-Zertifikat für die C2-Domain ausgestellt werden.
Damit ist die Erstellung des SSL-Zertifikats abgeschlossen und wir können mit der eigentlichen Installation und Konfiguration des Nginx Reverse Proxy beginnen.
Nginx - Konfiguration
Falls Nginx noch nicht auf der Linux VM installiert ist, verwenden wir den folgenden Befehl für die Installation unter Ubuntu.
apt install nginx
Im nächsten Schritt löschen wir die Datei, die die Standardkonfiguration von Nginx enthält, mit folgendem Befehl.
rm /etc/nginx/sites-enabled/default
Nun erstellen wir für Nginx, wo wir unsere Reverse-Proxy-Konfiguration konfigurieren, eine neue Datei für die Standardkonfiguration.
nano /etc/nginx/sites-enabled/default
Für die neue Standardkonfiguration verwenden wir die folgenden Parameter, wobei domain.com
natürlich durch die eigene C2-Domain ersetzt werden muss. In dieser Konfiguration legen wir zum einen den Pfad zum SSL-Zertifikat fest, das wir mit Certbot erstellt haben. Zum anderen legen wir über den Parameter proxy_pass
fest, an welche IP-Adresse gültige HTTPS-Anfragen weitergeleitet werden. Hier wird die interne IPv4-Adresse des Cobalt Strike Team Servers eingetragen, und da es sich um HTTPS-Verkehr handelt, verwenden wir Port 443.
Ein wichtiger Aspekt dieser Konfiguration ist die Frage, nach welchen Kriterien der Nginx Reverse Proxy entscheidet, ob eine eingehende HTTPS-Anfrage legitim ist und an den Cobalt Strike Team Server weitergeleitet werden soll. Diese Entscheidung wird durch die Definition der URIs für GET- und POST-Requests getroffen, die später in das Cobalt Strike Malleable Profile eingetragen werden. In diesem Fall verwenden wir lib-8f74c3d1.min.js
für GET-Anfragen und app.7e5b9f2a.bundle.js
für POST-Anfragen.
Vereinfacht ausgedrückt bedeutet dies, dass, wenn wir später die Konfiguration des Malleable Profiles von Cobalt Strike abgeschlossen haben und beispielsweise eine Test-Payload mit den konfigurierten Listenern erstellen, die definierten URIs auch in dieser Payload enthalten sind. Wenn der Beacon schließlich versucht, sich über die Route CDN -> DNS -> Nginx mit dem Team Server zu verbinden, erkennt der Nginx Reverse Proxy diese URIs als legitime Teile der eingehenden HTTPS-Anfrage. Dadurch kann der Proxy die Anfrage als legitimen Traffic unseres Cobalt Strike Beacons identifizieren und den HTTPS-Traffic entsprechend an den Team Server weiterleiten.
Diese Methode stellt sicher, dass nur speziell konfigurierte Anfragen durch den Nginx Reverse Proxy geleitet werden und schafft so eine zusätzliche Sicherheits- und Filterebene zwischen dem externen Netzwerk und dem Cobalt Strike Team Server.
server {
listen 443 ssl default_server; # Listen on port 443 for HTTPS as the default server for this IP.
listen [::]:443 ssl default_server; # Listen on port 443 for HTTPS on all IPv6 addresses as the default server.
ssl_certificate /etc/letsencrypt/live/domain.com/fullchain.pem; # Path to the SSL certificate file.
ssl_certificate_key /etc/letsencrypt/live/domain.com/privkey.pem; # Path to the SSL certificate private key file.
client_max_body_size 200M; # Set the maximum allowed size of client request body to 200MB.
root /var/www/html; # Set the document root to serve files from /var/www/html.
server_name _; # Catch-all server block (matches any server name).
location ~ ^/(lib-8f74c3d1.min.js|app.7e5b9f2a.bundle.js) {
client_max_body_size 200M; # Override the client body size limit to 200MB for these specific files.
proxy_pass https://10.3.0.5:443; # Proxy requests for these files to another server at 10.3.0.5 over HTTPS.
}
}
Damit ist die Konfiguration des Nginx Reverse Proxy abgeschlossen. Nachdem wir die Standardkonfigurationsdatei gespeichert und geschlossen haben, verwenden wir den folgenden Befehl, um sicherzustellen, dass Nginx nach einem Neustart automatisch startet.
sudo systemctl enable nginx
Der nächste Schritt besteht darin, den Nginx-Dienst neustarten zu lassen, um sicherzustellen, dass die neue Standardkonfiguration installiert und verwendet wird.
sudo systemctl restart nginx
Abschließend werden wir mit Hilfe des folgenden Befehls überprüfen, ob der Nginx-Dienst korrekt ausgeführt wird.
sudo systemctl status nginx
Wenn der Nginx-Dienst korrekt läuft und die Konfiguration korrekt durchgeführt wurde, sind wir mit der Konfiguration des Nginx Reverse Proxy fertig und können uns der Konfiguration von Cobalt Strike widmen.
Cobalt Strike - Setup
Im Fall von Cobalt Strike müssen wir zwei Dinge konfigurieren, zum einen müssen wir auf der Serverseite (Team Server) das Mallable Profil korrekt konfigurieren und zum anderen muss auf der Clientseite der Cobalt Strike Listener korrekt erstellt werden. Wir beginnen mit dem mallable-Profil.
Mallable Profile - Konfiguration
Das Malleable Profile ist eine zentrale Komponente von Cobalt Strike, die es ermöglicht, das Netzwerkverhalten des Beacons so anzupassen und zu verschleiern, dass es wie legitimer Webverkehr wirkt und somit schwerer zu erkennen ist. Eine sorgfältige Konfiguration des Malleable Profiles ist entscheidend, um sicherzustellen, dass die Kommunikation zwischen dem Beacon und dem Team Server unauffällig bleibt und von Sicherheitslösungen nicht entdeckt wird. Das Malleable-Profil definiert verschiedene Aspekte der Netzwerkkommunikation wie HTTP-Header, URIs, POST-Daten und andere Parameter, die der Beacon für die Kommunikation mit dem Team Server verwendet. Diese Anpassungen sind notwendig, um den C2-Verkehr als legitimen Webverkehr erscheinen zu lassen und so die Wahrscheinlichkeit zu minimieren, dass er von Sicherheitslösungen erkannt und blockiert wird.
URIs
Die URIs für GET- und POST-Anfragen müssen so konfiguriert werden, dass sie mit den in der Nginx-Konfiguration definierten URIs übereinstimmen. In unserem Fall sind dies lib-8f74c3d1.min.js
für GET-Anfragen und app.7e5b9f2a.bundle.js
für POST-Anfragen. Diese URIs dienen nicht nur zur Tarnung des Beacon-Traffics, indem sie legitime Webanfragen imitieren, sondern spielen auch eine zentrale Rolle in der Funktionalität des Nginx Reverse Proxy. Der Proxy verwendet diese URIs, um festzustellen, ob es sich um eine legitime HTTPS-Anfrage des Beacons handelt. Mit anderen Worten: Nur wenn die URIs korrekt übereinstimmen, wird der HTTPS-Verkehr vom Nginx Reverse Proxy an den Cobalt Strike Team Server weitergeleitet. Andernfalls wird die Anfrage nicht weitergeleitet, was zusätzliche Sicherheit bietet und unerwünschte Zugriffe von Scannern, Bots, Blue Teams etc. blockiert.
Um die Konfiguration vorzunehmen, öffnen Sie das entsprechende Malleable-Profil, z.B. webbug.profile
, und fügen Sie die entsprechenden Einträge in den Abschnitten für GET und POST hinzu. Das Verb für GET und POST sollte bereits vorhanden sein, Sie müssen nur die URIs für GET und POST hinzufügen. Es ist wichtig, dass die URIs für GET und POST unterschiedlich sind, damit Cobalt Strike und Nginx zwischen diesen beiden HTTP-Methoden unterscheiden können. In unserem Beispiel verwenden wir die URI lib-8f74c3d1.min.js
für GET und die URI app.7e5b9f2a.bundle.js
für POST. Diese URIs müssen exakt mit denen übereinstimmen, die in der Nginx-Konfiguration festgelegt wurden, um sicherzustellen, dass der Nginx Reverse-Proxy den Datenverkehr korrekt verarbeitet. Wenn die URIs übereinstimmen, wird der HTTPS-Traffic an den Cobalt Strike Team-Server weitergeleitet, andernfalls wird die Anfrage blockiert.
Host Header
Der Host-Header muss ebenfalls korrekt definiert werden, um sicherzustellen, dass die Kommunikation über das CDN reibungslos funktioniert. Dieser Header informiert das CDN über die adressierte Domain bzw. Subdomain und ist entscheidend für die korrekte Weiterleitung der Anfrage an den Nginx Reverse Proxy und schließlich an den Cobalt Strike Team Server. Daher ist es notwendig, im Malleable-Profil sowohl für GET- als auch für POST-Anfragen den Parameter Host
hinzuzufügen und als Wert den Endpunktnamen anzugeben, den wir für unser CDN ausgewählt haben. In diesem Fall wäre das xyz-cache.azureedge.net
. Der Wert für den Header muss natürlich entsprechend an den eigenen CDN-Endpunktnamen angepasst werden.
Damit ist die Konfiguration des Mallable-Profils abgeschlossen und die gezeigte Konfiguration stellt sicher, dass der Beacon den gewünschten Traffic generiert, der sowohl vom CDN als auch vom Nginx Reverse Proxy akzeptiert und korrekt weitergeleitet wird.
Cobalt Strike - Listener
Zum Schluss müssen wir noch unseren Cobalt Strike Listener konfigurieren. Dazu erstellen wir einen HTTPS Listener. Im Feld Hosts
tragen wir die CDN-Domains ein, die für die Verschleierung unseres C2-Traffics verwendet werden sollen. Diese Domains sind die Endpunkte, die wir zuvor für unser CDN konfiguriert haben und die eine zentrale Rolle in der Kommunikation zwischen dem Beacon und dem Cobalt Strike Team Server spielen.
Damit ist die Konfiguration von Cobalt Strike und unserer C2-Infrastruktur, bestehend aus CDN, DNS, Nginx, Cobalt Strike Team Server und Client, abgeschlossen.
C2-Setup - Test
Abschließend möchten wir die Funktionalität unserer C2-Konfiguration testen. Dazu gibt es mehrere Möglichkeiten. Zum einen können wir einen Test mit curl
durchführen, zum anderen können wir eine Test-Payload mit Cobalt Strike erstellen und überprüfen, ob sich im Cobalt Strike Client eine Command-and-Control-Verbindung öffnet.
Im ersten Schritt testen wir mit dem folgenden curl
Befehl, ob Nginx unsere Anfrage an den Cobalt Strike Team-Server weiterleitet. Der Befehl simuliert eine POST-Anfrage über das CDN. Wenn wir uns den Befehl genauer ansehen, stellen wir fest, dass die CDN-Domain korrekt angegeben ist, aber die POST-URI app.7e5b9f2a.bundle.js
nicht mit der URI übereinstimmt, die wir in Nginx und im Malleable-Profil von Cobalt Strike definiert haben. Wir führen den Befehl trotzdem aus, um zu sehen, was passiert.
curl -X POST https://ajax.microsoft.com/false_uri.js -d test -H "host: xyz-cache.azureedge.net"
Da die URI nicht mit der im Malleable-Profil und in der Nginx-Konfiguration definierten URI übereinstimmt, könnte Nginx die Anfrage blockieren und nicht an den Cobalt Strike Team-Server weiterleiten. Dies wäre ein Hinweis darauf, dass die Sicherheitsmechanismen korrekt funktionieren. Bitte lassen Sie sich nicht davon irritieren, dass ich den Hostnamen in diesem Fall verpixelt habe, da ich für diesen Test ein fertiges Setup verwendet habe und den Endpunkt-Namen des CDNs nicht preisgeben möchte.
Im nächsten Schritt verwenden wir erneut den Befehl curl
, diesmal jedoch mit der korrekten URI, die in unserer Nginx- und Malleable-Profilkonfiguration definiert ist. Auch an dieser Stelle verwende ich eine bereits fertige Konfiguration, daher habe ich die korrekte URI aus Sicherheitsgründen verpixelt, dieser Test zeigt, dass die Verbindung über den Pfad CDN -> DNS C2-Domain -> Nginx Reverse Proxy -> Cobalt Strike Team Server erfolgreich aufgebaut wird.
Im Vergleich zum vorherigen Test, bei dem die falsche URI verwendet wurde, sehen wir nun, dass die Anfrage korrekt verarbeitet und an den Cobalt Strike Team Server weitergeleitet wird. Dies bestätigt, dass unsere C2-Infrastruktur korrekt konfiguriert ist und wie geplant funktioniert.
Mit diesem Test stellen wir sicher, dass alle Komponenten unserer C2-Infrastruktur korrekt miteinander kommunizieren und dass der Nginx Reverse Proxy in der Lage ist, legitime Anfragen zuverlässig an den Cobalt Strike Team Server weiterzuleiten. Als zusätzlichen Test können wir eine Payload mit Cobalt Strike im Kontext unseres HTTPS CDN Listeners ausführen, um sicherzustellen, dass der C2-Kanal vom Beacon zum Team Server korrekt geöffnet wird.
In diesem Szenario haben wir Nginx und den Cobalt Strike Listener so konfiguriert, dass die gesamte Kommunikation über HTTPS erfolgt, was die Analyse des Datenverkehrs mit Tools wie Wireshark erschwert, da der Datenverkehr verschlüsselt ist. Wer jedoch den C2-Verkehr mit Wireshark genauer untersuchen möchte, kann die Nginx-Konfiguration so erweitern, dass auch unverschlüsselter Verkehr über Port 80 zugelassen wird. Dazu müsste unter Cobalt Strike ein HTTP-Listener eingerichtet werden und der Test könnte dann mit HTTP statt HTTPS wiederholt werden. Diese Vorgehensweise ermöglicht es, den unverschlüsselten C2-Verkehr im Detail zu analysieren und zu verstehen.
Die Einrichtung einer HTTP-Konfiguration und die Analyse des unverschlüsselten Datenverkehrs überlasse ich dem Leser. Dies ist eine gute Gelegenheit, sich mit der Funktionsweise der C2-Konfiguration dieses Artikels vertraut zu machen.
Zusammenfassung
In diesem Artikel haben wir ein mögliches C2-Setup für Cobalt Strike vorgestellt, das Microsoft Azure CDNs, eine C2-Domain und einen Nginx Reverse Proxy für die C2-Kommunikation zwischen Beacon und Team Server verwendet. Bei der Einrichtung des CDNs war es notwendig, einen Namen für den Endpunkt zu definieren, z.B. xyz-cache.azureedge.net
. Als Origin Host haben wir den FQDN der Subdomain unserer C2-Domain definiert, der über einen A-Record auf die öffentliche IP des Nginx Reverse Proxy verweist z.B. docs.domain.com
.
Der Nginx Reverse Proxy wurde so konfiguriert, dass er über HTTPS mit dem CDN kommuniziert und eingehende HTTPS-Anfragen nur dann an den Cobalt Strike Team-Server weiterleitet, wenn die URIs für GET und POST korrekt sind. Anfragen, die nicht den definierten URIs entsprechen, werden abgelehnt und nicht an den Team Server weitergeleitet.
Dieses C2-Setup bietet eine solide Grundlage für eine fortgeschrittene C2-Infrastruktur, die über die Möglichkeiten eines einfachen Redirectors hinausgeht, der beispielsweise socat
oder iptables
verwendet, um eingehende HTTPS-Anfragen ungefiltert an den Team Server weiterzuleiten. Mit Nginx können wir unseren Team Server besser vor unerwünschten Zugriffen durch Scanner, Bots oder das Blue Team schützen. Daher ist es ratsam, die URIs für GET und POST so zu wählen, dass sie schwer zu erraten sind. Die Verwendung von CDNs ermöglicht es uns außerdem, unseren C2-Traffic unter legitimem CDN-Traffic zu verstecken und Domains mit sehr hoher Reputation für unseren Command-and-Control-Traffic zu verwenden.
Darüber hinaus haben wir untersucht, wie das Malleable-Profil konfiguriert werden muss, damit die Kommunikation über CDN und Nginx reibungslos funktioniert. Um sicherzustellen, dass die Kommunikation über das CDN korrekt abläuft, haben wir den Host Header für GET und POST hinzugefügt und unseren CDN Endpoint Name (xyz-edge.azureedge.net
) eingetragen. Damit der Nginx Reverse Proxy die eingehenden Verbindungen unseres Beacons korrekt an den Team Server weiterleiten kann, haben wir im Malleable Profile die gleichen URIs für GET (lib-8f74c3d1.min.js
) und POST (app.7e5b9f2a.bundle.js
) definiert, die auch in der Nginx-Konfiguration verwendet werden. Die Malleable Profile-Konfiguration wird bei der Erstellung jeder Cobalt Strike-Payload berücksichtigt, so dass der Reverse Proxy die eingehende HTTPS-Verbindung des Beacons identifizieren und korrekt an den Team Server weiterleiten kann.
Insgesamt bietet das gezeigte C2-Setup von Cobalt Strike, bestehend aus Edgio CDN, C2-Domain und Nginx Reverse Proxy, eine solide Basis für eine gute und sichere C2-Kommunikationsumgebung, indem verschiedene Sicherheitsebenen integriert werden und die Erkennung des Command and Control Traffics sowie des Cobalt Strike Team Servers durch Scanner, Bots, Blue Team etc. erschwert wird.
Happy Hacking!
Daniel Feichter @VirtualAllocEx