Blog
So melden Sie sich bei einem Google Cloud Run-Container an

Mit Googles Cloud Run ist es sehr einfach geworden, Ihren Container in der Cloud einzusetzen und einen öffentlichen HTTPS-Endpunkt zurückzubekommen. Aber haben Sie sich jemals gefragt, wie die Cloud Run-Umgebung von innen aussieht?
In diesem Blogbeitrag zeigen wir Ihnen, wie Sie sich bei einem laufenden Google Cloud Run-Container anmelden.
Nachdem Wietse mein Interesse an Googles neuer serverloser Plattform geweckt hatte und ich ein wenig damit herumgespielt hatte, stellte ich mir Fragen wie:
- Wie sieht das Netzwerk aus?
- Enthält die Umgebung irgendwelche magischen Werte (z.B. interessante Pfade, Debug-Flags, versteckte Metadaten oder geheime Zugangsschlüssel)?
- Gibt es auf
/sys,/proc, etc. etwas Bemerkenswertes zu sehen? - Wie werden Dateisysteme gehandhabt?
Während ein Teil dieser Fragen durch das Lesen der Dokumentation beantwortet werden kann, macht es mehr Spaß, dies zu erforschen, indem man einen Container ausführt und von innerhalb des Containers mit einer Shell erkundet. Wenn wir
oder in den Container einbinden könnten, wäre das trivial, da wir die Tools innerhalb des Images bereits kontrollieren. Schließlich haben wir das Docker-Image, das der Container ausführt, selbst erstellt. Allerdings läuft der Container nur für eine kurze Zeit - derzeit sind es maximal 15 Minuten . Außerdem wird er keine öffentliche IP-Adresse haben, auf der wir beliebige Ports öffnen können, um eine Verbindung herzustellen.Reverse Shell
Anstatt sich mit dem laufenden Container zu verbinden, machen wir das Gegenteil: Wir lassen den Cloud Run-Container sich mit einem Rechner verbinden, den wir kontrollieren. Damit dies funktioniert, benötigen wir einen Rechner mit einer öffentlich zugänglichen IP-Adresse und einem offenen TCP-Port, auf dem wir auf eine eingehende Verbindung warten. Sobald dies der Fall ist, können wir unseren speziellen Container bei Cloud Run einsetzen und ihn mit ihm verbinden lassen. So sieht das aus:
Jetzt, da das große Bild klar ist, ist es an der Zeit, sich mit den Details zu befassen. Das werden wir tun:
- Erstellen Sie eine neue VM und öffnen Sie die Firewall an einem Port unserer Wahl.
- Auf dem geöffneten Port warten wir auf eingehende Verbindungen.
- Erstellen und verteilen Sie ein benutzerdefiniertes Docker-Image für den Cloud-Betrieb.
1. Erstellen Sie eine neue VM und öffnen Sie die Firewall an einem Port unserer Wahl
Wir müssen einen Endpunkt erstellen, mit dem sich Cloud Run verbinden kann und von dem aus wir schließlich die Shell steuern können.
Dazu können wir eine Compute-Instanz in GCP starten und einen Port in der Firewall öffnen. Dies setzt voraus, dass das Standardnetzwerk vorhanden ist und Sie über ausreichende Berechtigungen verfügen:
gcloud config set compute/zone europe-west4-a
gcloud beta compute instances create mylistener --machine-type=f1-micro
--metadata=startup-script="apt-get update && apt-get install -y netcat"
--image-family=debian-10 --image-project=debian-cloud
--tags=listener
PUBLIC_IP=$(gcloud compute instances describe --zone europe-west4-a
mylistener
--format 'value(networkInterfaces[0].accessConfigs[0].natIP)')
gcloud compute firewall-rules create "default-allow-listener"
--allow tcp:1234
--target-tags="listener"
--description="Allow reverse shell"
--direction INGRESS
Der erste Befehl setzt die Zone auf europe-west4-a. Der zweite Befehl erstellt eine winzige, auf Debian-10 basierende virtuelle Maschine namens mylistener im Standardnetzwerk und -subnetz und installiert netcat auf ihr. Wenn er erfolgreich ist, gibt er eine Tabelle aus, die auch die externe IP-Adresse enthält, die wir für die Verbindung verwenden werden. Der dritte Befehl ruft die öffentliche IP-Adresse des Rechners ab und speichert sie als PUBLIC_IP.
Der vierte Befehl erstellt eine Firewall-Regel, die eine eingehende TCP-Verbindung auf Port 1234 zu allen Rechnern mit dem Tag listener durchlässt, den unsere VM mylistener hat.
2. Auf dem geöffneten Port lauschen wir auf eingehende Verbindungen.
Jetzt, da der Rechner läuft und die Firewall eingehenden Datenverkehr zu unserem Port zulässt, müssen wir noch etwas auf diesem Port lauschen lassen, das auf eingehende Verbindungen antwortet.
Wir verwenden dafür die zuvor installierte netcat.
Öffnen Sie ein neues Terminal, starten Sie netcat auf dem Listener:
$ gcloud compute ssh --zone europe-west4-a mylistener -- nc -v -l -p 1234
listening on [any] 1234 ...
Jetzt, wo Ihre Compute-Instanz netcat auf eingehende Verbindungen wartet, können Sie es auf einem beliebigen Rechner ausprobieren, z.B. auf der integrierten Shell von GCP:
$ sudo apt-get -y install netcat
$ PUBLIC_IP=$(gcloud compute instances
describe --zone europe-west4-a
mylistener
--format 'value(networkInterfaces[0].accessConfigs[0].natIP)')
$ nc -v $PUBLIC_IP 1234 -e /bin/bash
224.217.90.34.bc.googleusercontent.com [34.90.217.224] 1234 (?) open
Der obige netcat-Befehl stellt eine Verbindung zur öffentlichen IP-Adresse an Port 1234 her und - jetzt kommt der magische Teil - bindet die Ein- und Ausgaben des verbundenen tcp/ip-Sockets an eine Bash-Shell! Auf Ihrer abhörenden Compute-Instanz sollten Sie nun eine eingehende Verbindung sehen, woraufhin Sie Befehle wie in einer normalen Bash-Shell eingeben können (allerdings mit einigen Einschränkungen):
listening on [any] 1234 ...
connect to [10.164.0.2] from 228.175.204.35.bc.googleusercontent.com [35.204.175.228] 44658
echo $HOSTNAME
cs-6000-devshell-vm-23e82b8b-fb92-4bc3-b093-33b16ac9cf57
echo $RANDOM
28595
Vorbehalte:
CTRL-C nicht das entfernte Programm stoppen, sondern stattdessen Ihren netcat-Befehl.3. Erstellen und verteilen Sie ein benutzerdefiniertes Docker-Image in der Cloud.
Der letzte Schritt besteht darin, dies in einen Docker-Container zu packen und ihn bei Cloud Run einzusetzen. Hier ist eine kleine Dockerdatei, mit der Sie beginnen können:
FROM nginx
Fügen Sie Ihre Inspektionswerkzeuge in die nächste Zeile ein
RUN apt-get update && apt-get install -y netcat rlwrap python procps net-tools tcpdump dnsutils iproute2 nmap traceroute curl findutils &&
sed -e 's/listen.*/listen ${PORT};/' /etc/nginx/conf.d/default.conf > default.conf.tpl &&
echo '<h1>Cloud Run Reverse Shell Demo!</h1>' >
/usr/share/nginx/html/index.html
CMD /bin/bash -c "envsubst < default.conf.tpl > /etc/nginx/conf.d/default.conf && (while : ; do nc $PUBLIC_IP $PUBLIC_PORT -e /bin/bash ; sleep 1 ; done) & exec nginx -g 'daemon off;'"
Erstellen und verteilen Sie es in Cloud Run:
PUBLIC_IP=$(gcloud compute instances describe --zone europe-west4-a mylistener --format 'value(networkInterfaces[0].accessConfigs[0].natIP)')
PROJECT=$(gcloud config get-value project)
gcloud builds submit -t gcr.io/$PROJECT/cloudshell:v1 .
# Wait a little while Google Build does its thing...
gcloud run deploy cloudshell --set-env-vars=PUBLIC_IP=${PUBLIC_IP},PUBLIC_PORT=1234 --image gcr.io/$PROJECT/cloudshell:v1 --allow-unauthenticated --platform managed --region europe-west1 --max-instances=1 --timeout=900
Nach ein paar Sekunden sollten Sie eine neue Verbindung sehen, dieses Mal vom Cloud Run-Container!
Erforschen Sie
Zu Beginn werden Sie wahrscheinlich feststellen, dass die Shell nur sehr langsam reagiert. Cloud Run stellt während des Starts der Container-Instanz und der Verarbeitung von Anfragen CPU-Zeit zur Verfügung, drosselt aber die CPU, wenn dies nicht der Fall ist. Wenn Sie möchten, dass die Shell schneller reagiert, können Sie im Hintergrund eine Curl-Schleife zum Dienst starten:
CLOUD_RUN_URL=$(gcloud --format 'value(status.address.url)' run services describe cloudshell)
while : ; do curl -s $CLOUD_RUN_URL ; sleep 1 ; done
Außerdem wird Ihre Verbindung wahrscheinlich irgendwann abbrechen - entweder durch eine Sperre, die von Programmen verursacht wird, die ein Terminal erwarten, oder durch versehentliches Drücken von STRG-C. Starten Sie den netcat-Befehl auf der VM neu und der Cloud Run-Container sollte sich aufgrund der while-Schleife im Startbefehl des Containers wieder mit Ihnen verbinden - solange er noch Anfragen erhält, versteht sich. Beachten Sie, dass Ihre nicht mehr funktionsfähige, aber immer noch laufende Shell zuerst beendet werden muss. Meiner Erfahrung nach geht die erneute Bereitstellung daher viel schneller. Möglicherweise bemerken Sie auch, dass STDERR aufgrund der Art und Weise, wie die Dateideskriptoren zugeordnet werden, verschwindet. Eine Möglichkeit, dies zu verbessern, besteht darin, etwas wie dies auszuführen:
python -c 'import pty; pty.spawn("/bin/bash")'
Das obige Snippet verwendet das pty-Modul von Python, um eine weitere Shell zu erzeugen. Diesmal gibt es der Bash die Vorstellung eines kontrollierenden Terminals, so dass sie sich mehr wie ein Terminal verhält und Ihnen sogar einen PS1-Prompt zur Verfügung stellt.
Bemerkenswerte Befunde
Die Erkundung der Cloud Run-Umgebung selbst weist einige interessante Eigenheiten auf:
* Es gibt zwei primäre Netzwerkschnittstellen, eth0 und eth1, von denen eth1 die Standardroute und eine lokale IPv6-Adresse hat. Die Schnittstelle eth0 hat eine riesige MTU von 65000, während eth1 nur 1280 ist.
* Routing ist versteckte Magie. Wenn Sie ip ro get 8.8.8.8 fragen, erhalten Sie ein hübsches RTNETLINK answers: Operation not supported (im Gegensatz zu etwas wie 8.8.8.8 via 172.17.0.1 dev eth1 src 172.17.0.2)
* Die Einhängung /var/log ist etwas Besonderes - df kann Ihnen nichts über /var/log sagen, aber über die anderen Einhängungen schon. Das liegt an der Magie des Stackdrivers.
* dmesg macht dank der gVisor Umgebung immer noch Spaß.
Abgesehen von den oben genannten Punkten sieht der Rest der Umgebung so aus, wie man es erwartet, aber nehmen Sie mich nicht beim Wort - sehen Sie es sich selbst an!
Viel Spaß beim Erkunden!
Verfasst von

Wouter de Geus
Unsere Ideen
Weitere Blogs
Contact



