Two incidents can look identical in the alert queue and be entirely different animals underneath, because one of them means somebody read data they shouldn't have and the other means somebody can run commands on your server whenever they like, and confusing the two is how teams end up rebuilding a machine that only needed a patch, or patching an application while an implant sits quietly in /tmp waiting for them to finish.
The distinction that matters isn't severity, it's what the attacker actually holds, because an application compromise gives them a position inside your business logic, while a host compromise gives them a position inside your operating system, and those two positions come with completely different capabilities, different evidence, and different containment playbooks.
So here is how to tell them apart, why one is fundamentally a data problem and the other is fundamentally a control problem, where the boundary blurs, and the actual commands that help you decide which one you're dealing with.
Important Disclaimer
This article is intended for educational and defensive purposes only, and it describes general incident response practices rather than guarantees. Containment decisions depend on your environment, your legal obligations, and the specifics of the incident, so involve your incident response lead, legal counsel, and where relevant your regulator before acting.
Do not run destructive commands on a production host you believe is compromised, and preserve volatile evidence before anything is restarted. The author assumes no liability for missed findings, evidence loss, business outcomes, or damages resulting from the use or misuse of this information.
The Core Distinction in One Sentence
An application compromise means the attacker can make your application do things, and a host compromise means the attacker can make your server do things, and the difference shows up in what they can reach, what they can persist, and what evidence they leave behind.
That's the whole idea, and everything below is the practical consequence of it.
App Compromise: The Attacker Is Inside Your Logic
When the compromise is at the application layer, the attacker is constrained to the request and response cycle, which means they are using valid sessions, valid endpoints, and valid credentials to do things the application was already designed to do, just for the wrong person.
The typical entry points are authorisation flaws where one user can read another user's records by changing an identifier, injection flaws where input reaches a query or a template, authentication weaknesses such as session fixation or predictable reset tokens, business logic abuse where a workflow can be repeated or skipped, and misconfigured access on files or APIs that were never meant to be public.
What they can reach is data, which means records, documents, invoices, messages, and anything else the application serves, and what they can do is whatever the application allows, which is usually a great deal more than the user interface suggests. What they cannot easily do is install software, create a system user, open a listening port, or survive a rebuild of the application, because they never had a position at the operating system level to begin with.
This is why the app compromise is a data problem, because the questions that matter afterwards are all about exposure, which means what records were accessed, how many data subjects are affected, whether the access was authorised for that account, and what you are obliged to notify. The remediation is similarly data-shaped, which means fixing the logic, rotating application secrets, invalidating sessions, and where appropriate forcing credential resets.
Host Compromise: The Attacker Is Inside Your Machine
When the compromise reaches the host, the attacker has code execution at the operating system level, which means they can run commands as whatever user the vulnerable service was running as, and from there the constraints are completely different.
What they can reach is the machine and everything it can touch, which includes the application source, the environment files with database passwords in them, the SSH keys in the deploy user's home directory, the cloud instance role credentials from the metadata endpoint, the other containers on the same node, the internal network the host can route to, and the memory of every process running on it.
What they can do is persist, and this is the part that changes the response entirely, because they can add a cron job, drop a systemd unit, append an SSH key, modify a startup script, patch a shared library, install a kernel module, or simply leave a process running that reconnects every few minutes. A host compromise survives a code deploy, survives a password change, and in many cases survives a reboot because that was the whole point of the persistence.
This is why the host compromise is a control problem, because the questions afterwards are all about what the attacker can now control and what they have established as a foothold, and the remediation is not a patch, it's a rebuild, because you cannot prove a machine is clean by removing the thing you found.
Why the Response Diverges So Sharply
The two incidents require different playbooks because they break different assumptions.
For an app compromise, the host is trustworthy, which means you can read its logs and believe them, you can redeploy code to it, and you can keep serving traffic while you fix the logic. Containment is largely about revoking access, which means killing sessions, rotating application secrets, disabling the vulnerable endpoint, and locking down the affected accounts. Recovery is a patch and a deploy, and the evidentiary question is who saw what.
For a host compromise, the host is not trustworthy, which means every log on it might have been edited, every binary might have been replaced, every credential it holds is now suspect, and every secret in its environment is burned. Containment becomes isolation rather than correction, which means taking it out of the load balancer, preserving volatile state, and then rebuilding from a known-good image rather than cleaning the existing one. Recovery is a rebuild plus a credential rotation across everything that host could reach, and the evidentiary question is what it can still do.
The asymmetry in effort is large, and it's the reason misclassifying in the wrong direction is so expensive, because treating a host compromise as a code bug leaves the implant in place while you congratulate yourself on the patch.
Where the Boundary Blurs: The Pivot Cases
In practice, the two categories are joined by a short list of vulnerabilities that start as application issues and end as host issues, and these are the ones that get misclassified most often.
- Remote code execution through deserialisation, template injection, or a command-injection flaw starts as an input handling bug and ends as a shell, and the moment it ends as a shell, the incident has changed category.
- Unrestricted file upload into an executable or web-served directory is the classic path, because a file that lands in a directory the web server will execute is an application feature that behaves like a remote shell.
- Server-side request forgery can reach the cloud metadata endpoint and retrieve the instance role credentials, which is an application bug that grants control-plane access, and from there the attacker is operating at the infrastructure layer rather than the application layer.
- Container escape is the last one, and it's the transition from "inside a container" to "inside the node," which takes the incident from a single workload to every workload sharing that kernel.
The practical rule is that the moment you see a process, a listening port, a new file that wasn't deployed, or a connection you didn't initiate, you stop treating the incident as an application issue.
The Evidence Problem: Volatile Versus Durable
The two categories also differ in where the proof lives, and that difference dictates what you do in the first ten minutes.
Application evidence is durable, because it lives in the web server access log, the application audit log, the database query log, the WAF, and the session store, and most of it is already centralised somewhere outside the affected host, which means an app investigation can proceed even after a restart.
Host evidence is largely volatile, because it lives in process memory, in open file handles, in network connections, in /proc, and in the running kernel state, and a reboot destroys nearly all of it. That's why the single worst thing you can do to a suspected host compromise is restart it out of habit, and why the correct first move is to preserve state before touching anything.
Practical Example:
Running ps auxf, ss -tulpn, and lsof -i on a suspected host takes thirty seconds and captures the process tree, the listening sockets, and the live connections, and none of that survives a restart, which is why the order of operations matters more than the depth of the investigation.
Deciding Which One You're In: The Actual Commands
Here is the triage that answers the question in a few minutes on a Linux host, and every command is read-only.
Start with what is listening and what is running.
ss -tulpn
ps auxf --sort=-%cpu | head -30
lsof -i -n -P | grep ESTABLISHEDThe question you are asking is whether there is a socket bound to a port you did not configure, or a process you cannot account for, or an established connection to an address you do not recognise. Any of those answers moves you toward host compromise.
Then look for files that were not deployed.
find /tmp /var/tmp /dev/shm -type f -mtime -2 -ls
find /var/www -type f -newermt '-48 hours' -lsFiles in shared memory directories, or files modified outside your deploy window, are the signature of an operator rather than a bug.
Then check persistence, which is where a host compromise announces itself.
crontab -l; ls -la /etc/cron.* 2>/dev/null
systemctl list-timers --all
cat /root/.ssh/authorized_keys 2>/dev/null
cat ~/.ssh/authorized_keys 2>/dev/nullAn SSH key you did not add, or a timer you did not create, ends the debate.
Then check package and binary integrity.
rpm -Va 2>/dev/null | head -40 # RHEL-family
debsums -c 2>/dev/null # Debian-familyModified system binaries mean the host is no longer trustworthy at all, because you cannot trust any tool you run on it afterwards, including the ones you would use to investigate it.
Then check audit records if you have them.
ausearch -m EXECVE -ts recent 2>/dev/null | head -50And on the application side, the equivalent triage looks at what the requests actually did.
awk '{print $1, $7, $9, $10}' access.log | sort |
uniq -c | sort -rn | head -20
awk '$7 ~ /invoice|document|export/ {print $1, $7}'
access.log | sort | uniq -c | sort -rn | headThe pattern you are looking for is a single session or IP walking identifiers in sequence, or a small number of accounts pulling disproportionate volumes of data, which is the signature of an authorisation flaw being exploited rather than a server being owned.
The Two Playbooks Side by Side
The difference becomes obvious when you put the first six actions next to each other.
For an app compromise, the first actions are to identify the vulnerable endpoint or logic flaw, block or fix it, invalidate all active sessions, rotate the application's secrets and API keys, enable or tighten logging on the affected routes, and begin scoping the data exposure by querying the audit trail for the affected accounts.
For a host compromise, the first actions are to isolate the host from the network without powering it off, capture volatile state from memory, processes, sockets, and open files, preserve a forensic image if the incident warrants it, identify the initial access path, rebuild from a known-good image rather than cleaning, and rotate every credential the host could reach, which includes the database password, the API keys in the environment, the cloud instance role, and any SSH keys that were present.
The last item is the one that gets under-scoped, because a host compromise means every secret on that machine is compromised, and rotating only the ones you remember leaves the attacker with a working credential and no need for the implant.
Real Two Scenarios
Scenario 1: The Authorisation Flaw That Looked Like Nothing
The Setup
A SaaS platform receives a support ticket claiming that a customer could see another customer's invoice after editing a number in the URL, and the engineering team reproduces it in staging within ten minutes, which confirms an insecure direct object reference in the invoice endpoint.
What Happened
The fix is a one-line ownership check, the deploy goes out the same afternoon, and the team initially treats the ticket as closed, until the security lead asks for the access log query and finds that one IP had been walking invoice identifiers sequentially for eleven days, pulling roughly nine thousand records through a valid session.
The Result
The incident is reclassified from "bug fixed" to "data exposure," the account is suspended, affected customers are identified from the audit trail, and the notification assessment takes two weeks and involves legal, because the questions that mattered were about who saw what and how many records were involved rather than about the code.
The Lesson
The technical fix took ten minutes and the data problem took two weeks, which is the normal shape of an application compromise, and the reason it was caught at all was that someone queried the log instead of closing the ticket.
Scenario 2: The Upload That Became a Shell
The Setup
A different team receives an alert from their endpoint tooling about an unusual process spawned by the web server user, and the on-call engineer resists the instinct to restart the service and instead runs the read-only triage against the host.
What Happened
The triage shows a PHP process listening on an unusual port, a file in the uploads directory with a recent modification time that matches nothing in the deploy history, a cron entry created two days earlier that re-downloads the file every ten minutes, and an SSH key in the deploy user's home directory that nobody recognises.
The Result
The host is isolated rather than restarted, volatile state is captured, the forensic image is taken, and the rebuild proceeds from a clean image, and the credential rotation covers the database, the object storage keys, the instance role, and the deploy keys, which takes a full day but leaves nothing usable behind.
The Lesson
The presence of a cron job and an SSH key changed the incident from a code bug into a control problem, because the attacker had established persistence that a patch would not have removed, and the only correct response was to stop trusting the machine.
How to Spot Which One You're In
- A single HTTP request reproduces the issue. That points to an application flaw rather than a foothold.
- There is a process you did not start. That is a host compromise until proven otherwise.
- There is a file that was not deployed. Modified times outside your deploy window are a strong indicator of an operator.
- There is a listening port you did not configure. Same conclusion, immediately.
- There is a cron job, timer, or SSH key you did not create. Persistence means the attacker intends to stay, which is host-level behaviour.
- A session walked identifiers in sequence. That is an authorisation flaw being exploited, which is application-level.
- A system binary fails an integrity check. The host is no longer trustworthy, so every tool on it is suspect.
- The activity survives a redeploy. Code changes don't remove implants, which is how host compromises get discovered late.
Quick Reference: App Versus Host
|
Dimension |
App Compromise |
Host Compromise |
|
Attacker position |
Inside business logic |
Inside the operating system |
|
What they hold |
Valid sessions and data access |
Code execution and persistence |
|
Primary loss |
Data exposure |
Control of the machine and its credentials |
|
Evidence location |
Access logs, audit logs, session store |
Memory, processes, sockets, filesystem |
|
Evidence durability |
Durable and often centralised |
Volatile, destroyed by a restart |
|
First action |
Identify and block the vulnerable path |
Isolate without powering off, capture state |
|
Remediation |
Patch, rotate app secrets, invalidate sessions |
Rebuild from known-good, rotate every credential |
|
Blast radius |
Records and data subjects |
Everything the host can reach |
|
Success criteria |
Exposure scoped and fixed |
Machine proven clean by replacement |
The Bottom Line
An app compromise is a data problem because the attacker is limited to what your application will do for them, so the work afterwards is exposure scoping, notification assessment, and a logic fix, and a host compromise is a control problem because the attacker can run code, persist, and reach everything the machine touches, so the work afterwards is isolation, forensic preservation, rebuild, and a credential rotation that covers every secret that lived on that box.
The practical rule is to look for the things a bug cannot produce, which means processes, listening ports, undeployed files, cron entries, and SSH keys, because the moment one of those appears the incident has changed category and the playbook has to change with it. And the reverse discipline matters just as much, because rebuilding a server over an authorisation flaw wastes a week, while patching an application over a webshell leaves the attacker exactly where they were.
So when the next alert arrives, ask one question before anything else, which is whether a request did this or a process did this, because that single answer determines whether you're scoping data or rebuilding a machine.
FAQ
What is the difference between an application compromise and a host compromise?
An application compromise means the attacker can make your application do things using valid sessions and endpoints, which limits them to data they can reach through your logic, while a host compromise means they can run commands on the operating system, which gives them persistence, credential access, and reach into everything the machine can touch.
How can I tell which one I'm dealing with?
Look for the things a bug cannot produce, which are an unexpected listening port, a process you cannot account for, a file modified outside your deploy window, or a cron entry or SSH key you did not create, because any of those indicates host-level access rather than an application flaw.
Does an application compromise mean my server is owned?
Not necessarily, because an authorisation flaw or an injection that only touches data keeps the attacker inside your application logic, but you must confirm that by checking for processes, sockets, and files, since a flaw that produced a shell has crossed into host territory.
Should I reboot a server I think is compromised?
No, because a reboot destroys the volatile evidence you need, which lives in process memory, open file handles, network connections, and kernel state, so isolate the host from the network first and capture that state before anything restarts.
Do I need to rebuild the server after a host compromise?
Yes, in most cases, because you cannot prove a machine is clean by removing the component you found, and the attacker may have modified binaries, installed a kernel module, or established persistence you have not discovered, so rebuilding from a known-good image is the only defensible position.
What credentials do I need to rotate after a host compromise?
Every credential the host could reach, which typically includes the database password, API keys in environment files, the cloud instance role or metadata credentials, object storage keys, deploy keys, and any SSH keys or certificates present on the machine, because a single unrotated secret leaves the attacker with access.
What should I check first on an application compromise?
Query the access and audit logs for the affected accounts and endpoints, because the exposure question is about who accessed what and how much, and the pattern of a single session or address walking identifiers in sequence is usually the clearest evidence of exploitation.
Is a webshell an application or host compromise?
It is a host compromise, because a web-served file that executes commands gives the attacker code execution at the operating system level, which means persistence and credential access are now in scope even if the initial flaw was an upload validation bug.