Maintaining Access: Persistence and Why Cleanup Isn't Optional
Updated July 28, 2026 · Written by PWNMI — see About.
Getting a shell is the easy part to talk about. What separates a professional engagement from a mess is what happens after — whether you deliberately maintain access on purpose, and whether you can account for, and revert, every single thing you touched. Both halves of that are "maintaining access": the technique of persistence, and the discipline of knowing exactly what you'd need to undo.
Persistence is scoped, not assumed
Initial access on a CTF box or a lab like the ones on this site doesn't need real persistence — you solve it, you move on, the box resets. A real engagement is different. Whether you're authorized to establish durable access at all, and for how long, is a specific line item in the rules of engagement, not something you infer from having gotten a shell. Some engagements explicitly want it tested (can the client's monitoring catch a persistence mechanism being planted); many don't, and expect you to demonstrate the access is possible without leaving anything behind that outlives the test. Read the scope for this specifically — "we got RCE" and "we're authorized to maintain access for the next three days" are different findings with different authorization requirements, the same way OSINT Fundamentals draws the line between "it's public" and "it's in scope."
Common persistence technique categories
These are the categories you'll see across real engagements and write-ups, at the level of what they are and why they work — not a copy-paste toolkit:
- Scheduled tasks and cron jobs. A cron entry or scheduled task that re-establishes a connection or re-executes a payload on a timer. Simple, reliable, and exactly the kind of thing the script privesc checklist teaches you to look for from the defending side — persistence and privesc-hunting are the same skill pointed in opposite directions.
- Service or daemon creation. Registering a new system service (or modifying an existing one) so a payload survives a reboot without needing a scheduled trigger at all.
- SSH keys. Dropping a public key into
~/.ssh/authorized_keysfor an existing account. Quiet, doesn't require creating anything a casualpsor process list would show, and easy to miss unless someone's specifically diffing that file against a known-good baseline. - Backdoor accounts. A new user with elevated privileges — exactly what CVE-2026-63030's exploit chain does on a real target: the pre-authentication chain there doesn't stop at "we found a bug," it ends with a genuine new administrator account, which is itself a persistence mechanism, not just proof of the vulnerability.
- Web shells. A small script dropped into a web-accessible directory, giving command execution through ordinary HTTP requests rather than a maintained network connection. Effective specifically because it blends into normal web server traffic if named and placed carefully.
- Startup items and Run keys (Windows). Registry
Run/RunOncekeys, Scheduled Tasks, or items in a startup folder — the Windows equivalents of cron, achieving the same "survive a reboot" goal through the mechanisms Windows actually provides.
The common thread: every one of these is a real, persistent change to the target's configuration or state. None of them are free.
The other half: knowing exactly what you did
This is the part that's easy to skip and the part that actually matters most. Every one of the CVE labs on this site now includes a Cleanup section for exactly this reason — not because the disposable lab container needs it, but because the habit of asking "what did this actually leave behind, and how would I revert it" is the thing worth building. Look at how differently that answer comes out across a handful of them: Apache Syncope's chain creates a role and two accounts you can cleanly delete. FreeScout's account takeover overwrites a real password hash — there's no request that undoes that, only damage control afterward. The WordPress plugin SQLi is pure data extraction and leaves nothing at all. Knowing which of these you're dealing with, before you run the exploit against anything that matters, is the actual skill — not memorizing a checklist of "things to clean up," but developing the instinct to ask the question every time, because the honest answer is different for every bug.
Cleanup as part of the job
A few concrete habits that separate documented, professional engagement discipline from just "getting in":
- Log what you create as you create it, not from memory afterward. A username, a role name, a file path, a timestamp — capture it the moment you make the change, because reconstructing it later from memory is exactly how something gets left behind.
- Prefer the least persistence necessary to demonstrate the finding. If proving code execution doesn't require planting a durable backdoor, don't plant one — every additional mechanism is one more thing that has to be found and removed later, by you or by someone else.
- Some things genuinely can't be undone. FreeScout's account takeover is the clean example: once you overwrite a password hash, the original is gone. Knowing this in advance changes what you're willing to test against a real account versus a disposable one you control — see that lab's own Cleanup section for how this actually plays out.
- The client's audit trail needs your documentation, not just your word. "I cleaned it up" isn't verifiable after the fact. A record of exactly what was created, when, and how it was removed is what lets the client's own team close the loop on their end.
Common mistakes
- Treating "I got a shell" and "I'm authorized to maintain access" as the same finding. They're not, and conflating them is a scope violation even when the underlying technical finding was fully authorized.
- Planting persistence louder than the finding required. A crontab entry with an obvious name, a service that doesn't try to blend in — noisy persistence gets caught, and getting caught mid-engagement for the wrong reason (sloppy cleanup, not the actual test) burns trust that's hard to rebuild.
- Forgetting your own verification artifacts. Every lab on this site writes a proof file like
/tmp/pwnmi-verify-successspecifically to make the exploit's effect checkable — and on a real engagement, a leftover verification file you created is exactly the kind of loose end that undermines "we left everything clean" in your own report.
Next step
For a worked example of the account-creation side of persistence, see CVE-2026-63030 (WordPress core) — read its Cleanup section specifically. For the defending side of the same skill (finding a script or cron job someone else planted, or that's just misconfigured), see the script privesc checklist. For the same "scoped, not assumed" discipline applied to staying undetected rather than staying present, see Defense Evasion Fundamentals and Command and Control Fundamentals. For keeping a long session — and any shells inside it — alive across your own disconnects, which is a different kind of persistence entirely, see Why I Run Every Engagement Through tmux.
Get new write-ups in your inbox
New roadmaps, tool walkthroughs, and lab write-ups. No spam. Unsubscribe anytime.