You’ll grapple with an anomaly in Forela’s AWS infrastructure, managed by a technical partner. After deploying an EC2 instance to host the Grafana application, the CPU usage unexpectedly soared to a constant 98%+ due to a process named “xmrig”. This task calls on the you to probe into this anomalous behavior, leveraging your understanding of cloud infrastructure, EC2 instances, and system processes. You’ll need to identify what “xmrig” is, why it’s consuming so much CPU power, and if it poses a threat to the organization’s operations.
An internet-facing Grafana instance on an EC2 host was exploited through an unauthenticated plugin path traversal, giving the actor arbitrary file read, including a Grafana configuration file with an administrator password in clear text. The actor then logged on over SSH as the Grafana service account, pulled a loader script from their own server with one download tool and the miner and its config with another. A script that an administrator cron job already ran as root was edited to start the miner with root rights, which then mined Monero at near-constant 98% CPU. The binaries were moved into a hidden directory and the loader was shredded to defeat recovery. The SOC’s own CatScale collection later left a footprint on the same host.
catscale tarball → extract, then chmod -R u+rwX because the archive unpacks without read permissions. Holds /var/log, the cron listings, the Grafana tree under usr/share/grafana (including .bash_history and data/log) and a Misc folder with a full file-system timeline CSVvar/log/syslog → Sysmon for Linux is installed, so process-creation events (EID 1) land here as XML. Parse them with Python into one line per event, then grepvar/log/auth.log → SSH logonsusr/share/grafana/data/log/ → HTTP requests with remote_addrTimestamp note: The attacker’s commands all fall on 2022-11-24 between 09:55 and 09:59 in host log time, after an SSH logon the day before. Q8 needs the full
YYYY-MM-DD hh:mm:ssform.
Grafana logs every HTTP request, so start in usr/share/grafana/data/log. Requests to /public/plugins/alertlist/../../..//etc/passwd and to the Grafana defaults.ini are an unauthenticated traversal in the plugin static-file handler, an arbitrary file read in Grafana 8.x. That behaviour maps to the 2021 Grafana plugin path traversal CVE.
grep -a remote_addr usr/share/grafana/data/log/*
Tip: The format mask fits a 2021 CVE number ending in 8. The traversal string in the log is enough to confirm it.
Two sources feed this. The Grafana request log shows two addresses sending the etc/passwd and defaults.ini traversal requests. The parsed Sysmon events (built in Q5) show wget and curl pulling the loader and miner from a third address, which is the payload server. List all three in ascending order.
grep -a remote_addr usr/share/grafana/data/log/* | grep -a 'etc/passwd\|defaults.ini'
grep -aE 'wget|curl' sysmon1.txt
From Q2, take the traversal address that also appears as an SSH source and read its accepted logons in auth.log. It authenticated with a password on Nov 23 at 11:17:18, about a day before the mining commands, using the account that runs the Grafana service.
grep 'Accepted' var/log/auth.log | grep 195.80.150.137
The file the traversal went after (Q1) is the one that matters. The CatScale copy of the Grafana tree has the same conf/defaults.ini, and the admin_password line under [security] holds a real value rather than the stock default.
grep -a admin_password usr/share/grafana/conf/defaults.ini
Plain grep handles Sysmon’s XML in syslog badly, so parse the EID 1 events into one line per process with Python and search that. The loader request goes to the payload server from Q2 on port 80, and the first word of the command line is the answer.
python3 -I parse_sysmon.py var/log/syslog > sysmon1.txt
grep -a injector.sh sysmon1.txt
A different tool from Q5. A single command with two -O flags fetches both the miner and its config from the same server.
grep -a 'xmrig' sysmon1.txt | grep -a -- '-O'
-O writes into the working directory, so the answer is the CurrentDirectory field of the same Sysmon event as Q7, not anything in the command line.
grep -a 'CurrentDirectory' sysmon1.txt | grep -a xmrig
The Grafana account’s .bash_history shows the actor opening a script in nano, in the same directory as the Q6 download. That script also appears in root’s cron listing, so anything added to it runs as root.
cat usr/share/grafana/.bash_history
grep -r updater.sh cron/
Read the schedule on root’s crontab entry for the Q4 script. Cron fields run minute, then hour, then day-of-month, month and weekday, so the first two fields give the time of day and the wildcards give the frequency.
grep -r updater.sh cron/
Gotcha: Cron lists minute before hour, so the time in
hh:mmorder is the reverse of the first two fields.
Filter the parsed Sysmon events to the xmrig launch after the 08:30 cron run. The command line passes the config with -c and ends with a threads argument after --.
grep -a 'xmrig' sysmon1.txt
xmrig prints its own banner to syslog on start, and the POOL #1 line names the pool and port, so no network telemetry is needed.
grep -a 'POOL #1' var/log/syslog
A mv event in the attacker window moves the miner and config out of the Q6 directory into a dot-prefixed directory under /usr/share, which hides it from a default listing and reads as a log folder at a glance.
grep -a ' mv ' sysmon1.txt
Search the parsed events for the loader name after the Q5 download. A secure-delete call at 09:59:49 overwrites the file before unlinking it, so there is nothing for carving to find. The format mask gives the shape of the command.
grep -a 'injector.sh' sysmon1.txt
This one was reasoned rather than read from a single artifact. The collector leaves its own script, Cat-Scale.sh, in /tmp, uploaded from the Lincoln admin address. The earliest sign that collection started is that file’s creation (birth) time in the CatScale file-system timeline, so that is the value that fits the question.
grep -a Cat-Scale catscale_out/Misc/*full-timeline.csv
Gotcha: Logon and first-command times from the Lincoln IP look like the obvious answer but are not what the question wants. It asks when collection began, and the script’s file creation time is the match.
| Phase | Action |
|---|---|
| Initial Access | Path traversal against the Grafana plugin handler reads /etc/passwd and defaults.ini |
| Credential Access | Administrator password recovered from the Grafana configuration |
| Initial Access | SSH logon as the grafana account from 195.80.150[.]137 |
| Command and Control | wget pulls injector.sh from 44.204.18[.]94 |
| Command and Control | curl -O pulls xmrig and config.json into /opt/automation |
| Privilege Escalation | /opt/automation/updater.sh, run by root’s cron, edited to launch the miner |
| Impact | xmrig mines to monero.herominers[.]com |
| Defense Evasion | Miner and config moved to /usr/share/.logstxt/ |
| Defense Evasion | shred -u ./injector.sh removes the loader |
| Response | SOC uploads Cat-Scale.sh to /tmp from the admin IP and starts collection |
| Type | Value |
|---|---|
| IP (payload server) | 44.204.18[.]94 |
| IP (traversal) | 95.181.232[.]32 |
| IP (traversal + SSH) | 195.80.150[.]137 |
| Account | grafana |
| CVE | CVE-2021-43798 |
| File | /opt/automation/updater.sh (modified) |
| Directory | /usr/share/.logstxt/ |
| Domain (pool) | monero[.]herominers[.]com (port 10191) |
| Command | shred -u ./injector.sh |
| Technique | ID | Description |
|---|---|---|
| Exploit Public-Facing Application | T1190 | CVE-2021-43798 file read against Grafana |
| Valid Accounts | T1078 | SSH logon as grafana |
| Unsecured Credentials: Credentials In Files | T1552.001 | Admin password in the Grafana config |
| Ingress Tool Transfer | T1105 | wget for the loader, curl for the miner and config |
| Scheduled Task/Job: Cron | T1053.003 | Root cron job’s script edited to run the miner as root |
| Resource Hijacking | T1496 | xmrig mining to a Monero pool at 98%+ CPU |
| Hide Artifacts: Hidden Files and Directories | T1564.001 | Miner moved to the dot directory .logstxt |
| Indicator Removal: File Deletion | T1070.004 | shred -u on the loader |
Patch or front internet-facing Grafana. A single unauthenticated request path gave the actor arbitrary file read. Updating past the fixed release, or putting the service behind a proxy that blocks .. in paths, would have removed the entry point.
Stop leaving real passwords in defaults.ini. The file leaked through the traversal and held an administrator password in clear text. Pass secrets through environment variables or a secrets store, and rotate anything that could have been read.
Root cron jobs must not run scripts that a service account can write. The Grafana account could edit /opt/automation/updater.sh, which root ran every day at 08:30. Make such scripts root-owned and not group- or world-writable, and alert on changes to files under cron-referenced paths.
Sysmon for Linux in syslog was the best evidence on the host. It gave full command lines and working directories for wget, curl, mv and shred. Forward those events off the box, because the shredded loader was unrecoverable from disk while its command line was still in the logs.
Alert on sustained CPU and known miner strings. A constant 98% CPU alert plus pool names and the xmrig banner in logs would have surfaced this earlier than a billing or performance complaint.
One unpatched Grafana plugin path exposed a password, and a world-reachable service account plus a root cron script turned that into a root-run Monero miner. The loader was shredded, but Sysmon’s process events recorded every command, so the logging, not the disk, told the story.