Lab 04 — Exploit Mitigation & Allowlisting¶
Hands-on lab · ← Back to the module concept
Setup¶
This is a reference lab — it ships a one-command environment in the companion
plaintext-labs repo:
git clone https://github.com/plaintext-security/plaintext-labs
cd plaintext-labs/endpoint-hardening/04-exploit-mitigations
make up # build the container with AppArmor userspace tools
make demo # load the profile, show the confined process being denied
make shell # drop into the container to work
make down # stop when done
The container requires
--privilegedand--security-opt apparmor=unconfinedto load AppArmor profiles. This is intentional — you are the one loading the profile. Everything runs locally.
Scenario¶
The organization's Linux application servers run a Python-based payroll API. A recent pentest
found that if the API were compromised, the attacker could read /etc/shadow and the private
TLS key. Your task: write and enforce an AppArmor profile for the API process that denies access
to those paths and logs every denial — so a compromised API cannot escalate without leaving a
loud audit trail.
Do¶
-
[ ]
make demo— observe the confined Python process trying to read/etc/shadowand being denied. Read the denial log output. Note the profile name, the denied path, and the log format. -
[ ]
Identify: the binary it confines, the paths it allows (read/write), and the paths it explicitly denies. Note themake shelland inspect the AppArmor profile indata/apparmor-profile:denyrules. -
[ ] Load the profile in enforce mode and run the confined process manually:
Observe the denied path access in the output and inapparmor_parser -r /lab/data/apparmor-profile aa-status | grep webapp # Run the confined process: sudo -u appuser python3 /lab/data/confined-app.py/var/log/syslogordmesg. -
[ ] Modify the profile in
data/apparmor-profileto also deny read access to/etc/ssl/private/. Reload and re-test. Confirm the denial appears in logs. -
[ ] Switch the profile to complain mode (
aa-complain /lab/data/apparmor-profile) and re-run the process. Does it now succeed in reading the denied path? What changes in the log? Switch back to enforce mode. -
[ ] Read
dmesg | grep apparmororjournalctl | grep apparmorand decode one denial line: profile name, operation, requested mask, denied mask, and the calling process. Write a one-line explanation of what the process was trying to do and why it was denied.
Success criteria — you're done when¶
- [ ] The AppArmor profile is loaded in enforce mode and the confined process is denied access
to
/etc/shadowand/etc/ssl/private/. - [ ] You added a deny rule and confirmed it with a test run.
- [ ] You can read an AppArmor denial log line and explain each field.
- [ ] You understand the difference between enforce and complain mode from direct observation.
Deliverables¶
apparmor-profile (your modified profile) + denial-analysis.md (your log line decode + a
two-paragraph explanation of how AppArmor enforcement would have limited the impact of the
payroll API compromise). Commit both.
Automate & own it¶
Required. Write a shell script check-confinement.sh that uses aa-status to list all
running processes and identify which ones are running unconfined. Output a one-line summary
("N processes confined, M unconfined"). Have an AI draft the aa-status parsing logic; you
test it against real aa-status output and verify the count is correct.
AI acceleration¶
Paste the payroll API's intended filesystem access pattern into an AI assistant and
ask it to draft an AppArmor profile. Use the generated profile as the starting point — then:
syntax-check with apparmor_parser -p, test in complain mode, and use aa-logprof to
incorporate any denials from normal operation. The model drafts the skeleton; the complain-mode
refinement shows you what the application actually does at runtime.
Connects forward¶
The AppArmor confinement you set up here is directly related to module 09 (privilege-escalation defense), where the goal is preventing a confined process from abusing a SUID binary. Module 10 (detecting host compromise) adds the detection side: AppArmor denials become the signals you hunt in the alert stream.
Marketable proof¶
"I wrote and enforced an AppArmor mandatory access control profile for a production application, confirmed denial of sensitive path access, and decoded the audit log to verify enforcement."
Stretch¶
- Use
aa-genprofto auto-generate a profile for a different binary (e.g./usr/bin/curl), exercise it with a few curl commands in complain mode, then review and enforce the generated profile. This is the production workflow for a binary you've never written a profile for. - Research how AppArmor and seccomp complement each other: AppArmor controls what files and capabilities, seccomp controls which syscalls. Find the Docker default seccomp profile and identify one syscall it blocks that AppArmor cannot.
Comments
Sign in with GitHub to comment. Choose the type: Feedback (errors or suggestions on this page) · Hints (help for fellow learners — no spoilers) · General (anything else).