Analyze the Atlassian Confluence remote code execution vulnerability from the logs.
An external host sent a series of scripted POST requests to a vulnerable Confluence template endpoint and got command execution as the Confluence service account. The first commands were an identity check and a directory listing. The attacker then tried several times to pull a shell script through the application, which only left error entries in the Confluence log, before switching to a different download that worked. That script produced a reverse shell back to the same host, and the attacker created a privileged backdoor account to keep access. A cryptominer archive was dropped into the Confluence directory before the intrusion was caught. The web logs, Sysmon for Linux events in syslog and the triage listings together cover the whole chain.
grep and awk. Source IP, user agent and request path for every requestgrep. Records OGNL evaluation errors with the attempted commanduseradd)grep and awkTimestamp note: Time answers use
YYYY-MM-DD hh:mm:ss. The first POST and the first Sysmon process event share the same second, so the web log and Sysmon clocks line up.
Start with the web access log and list the POST requests only; a scanner or browser would be mostly GET. A single template path under /template/aui/ receives a stream of POSTs and nothing else gets that treatment. The path is a Velocity template that accepts a parameter into an OGNL expression, the injection point of CVE-2023-22527.
grep 'POST' conf_access_log | awk '{print $7}' | sort | uniq -c | sort -rn
Take the path from Q1 and read the earliest POST to it in the access log. That request is the first exploitation attempt, and it lines up to the second with the first process-creation event in Sysmon (Q4).
grep 'POST /template/aui/text-inline.vm' conf_access_log | head -1
Count client IPs across the POSTs to the Q1 path. One external address sends all of them.
grep 'POST' conf_access_log | awk '{print $1}' | sort | uniq -c | sort -rn
The user agent sits in the same access-log lines as the exploit POSTs from Q3. It is a scripting library default, not a browser string, which fits an exploit script rather than a person clicking around.
grep '3.110.176.89' conf_access_log | grep -o '"[^"]*"$' | sort | uniq -c
Sysmon for Linux writes its XML events into syslog. Parse Event ID 1 (process creation) for the confluence user and sort by time. The first entry is at the same second as the first POST from Q2, so it is the payload of that request: a one-word identity check, the standard “did the exploit work” probe.
python3 -I parse_sysmon.py syslog --eid 1 --user confluence
Continue down the same Event ID 1 list from Q4. After the identity check, the first directory listing appears as an ls -la spawned by the Confluence process. Its timestamp is the answer.
python3 -I parse_sysmon.py syslog --eid 1 --user confluence | grep ' ls '
Failed OGNL evaluations never make it into Sysmon, but the application log keeps them. Search atlassian-confluence.log around the given time for the OGNL warning; it contains the attempted command: a download of a script from the attacker host, a chmod, then a bash of the file.
grep '11:50:50' atlassian-confluence.log
Gotcha: The log URL-decodes the injected
+characters into spaces, and thechmodline shows two spaces before the argument. The accepted form has a single space, matching the answer mask.
The working download shows up in Sysmon, not in the failed attempts from Q6. Look for curl process creations from the Confluence user: one writes to /tmp and the next event runs it with bash.
python3 -I parse_sysmon.py syslog --eid 1 --user confluence | grep -E 'curl|bash'
Find the file from Q8 in the triage bodyfile. Bodyfile fields are MD5|name|inode|mode|UID|GID|size|atime|mtime|ctime|crtime, so the size is the seventh field. The line for the script reads 0|/tmp/xxx.sh|68855|-rwxrwxrwx|998|998|46|....
grep '/tmp/xxx.sh' bodyfile
Gotcha: The real size field holds a much smaller number than the value HTB accepted. The accepted value is the third field, the inode. The five-character answer mask fits the inode, not the byte size, so this answer is the inode column; the question wording and the accepted value disagree.
The ss -tanp output lists established sockets with their owning process. An established connection owned by bash/su goes to the attacker address from Q3 on a non-standard port. A shell holding an outbound TCP session to the attacker host is the reverse shell started by the Q8 script.
grep ESTAB ss_-tanp.txt
hash_executables.sha1 from the triage collection already has an entry for the script. Hashing the file directly gives the same value.
grep 'xxx.sh' hash_executables.sha1
auth.log records account management. A useradd entry carrying the shell session of the attacker creates a new account, and the spelling in the log is the answer as written.
grep -E 'useradd|usermod' auth.log
Gotcha: The name is misspelled in the log (
servcie). Copy it exactly rather than correcting it.
The timestamp of the same useradd line from Q12.
grep useradd auth.log
Search the bodyfile for archives in the Confluence install directory. A tarball named after a public Monero miner release sits next to the application files, which is not something Confluence ships.
grep -iE 'xmrig|\.tar\.gz' bodyfile
| Phase | Action |
|---|---|
| Initial Access | Scripted POSTs to the vulnerable Confluence template path from one external IP |
| Execution | First payload is an identity check run as the Confluence user |
| Discovery | Directory listing of the host |
| Command and Control | Several download-and-run attempts through OGNL fail and are logged as warnings |
| Command and Control | A script is fetched to /tmp with curl and run with bash |
| Command and Control | Reverse shell held open to the attacker host |
| Persistence | Privileged backdoor account created |
| Impact | XMRig miner archive dropped in the Confluence directory |
Skipped: fewer than five distinct timestamps were established.
| Type | Value |
|---|---|
| IP (attacker, C2) | 3.110.176[.]89 |
| C2 endpoint | 3.110.176[.]89:1200 |
| User agent | python-requests/2.28.2 |
| Failed stager URL | hxxp[://]3[.]110[.]176[.]89/lin/6730789213.sh |
| Dropped script | /tmp/xxx.sh (SHA1 9ac4d3459093aac9ee4bb3b87fdeaaaa3c5bd551) |
| Backdoor account | confluence_servcie |
| Miner archive | /opt/confluence/xmrig-6.16.4-linux-static-x64.tar.gz |
| Vulnerable path | /template/aui/text-inline.vm |
| Technique | ID | Description |
|---|---|---|
| Exploit Public-Facing Application | T1190 | Template injection POSTs against Confluence |
| Command and Scripting Interpreter: Unix Shell | T1059.004 | id, ls, wget, chmod and bash run through OGNL and a reverse shell |
| File and Directory Discovery | T1083 | ls -la right after the first command |
| Ingress Tool Transfer | T1105 | Shell script fetched with curl, miner archive staged on disk |
| Create Account: Local Account | T1136.001 | Backdoor user added with useradd |
| Resource Hijacking | T1496 | XMRig archive placed on the server |
An unauthenticated template endpoint should never be reachable from the internet. The whole chain began with POSTs to one .vm path. Patching the Confluence version, or blocking /template/aui/*.vm at the proxy, would have removed the entry point. A burst of POSTs from a scripting-library user agent to that path is a cheap alert.
The failed attempts are the best early warning. The OGNL warnings in atlassian-confluence.log carry the full attempted command, minutes before the successful download. Alerting on wget/curl inside Confluence log errors catches the attacker while they are still fumbling.
Sysmon for Linux gave the clean process tree. An identity check, then a directory listing, then curl to /tmp and bash, all with the Confluence service account as the parent user, is a short high-signal chain. A web service account spawning shells and download tools should be its own detection.
Triage listings close the gaps in logs. Socket output tied the shell to an outbound connection, and the bodyfile exposed both the dropped script and the miner archive. Collect both. Outbound connections from a web server to an arbitrary high port on an external host are worth blocking by default.
A single unauthenticated template endpoint gave an external attacker code execution, a reverse shell, a backdoor account and a miner on the Confluence host within about half an hour. Every step was logged somewhere; the logs just had to be read together.