MediCare Pvt. Ltd. runs its public website on a single internet-facing server. On the night of 30 July 2026 their monitoring flagged a burst of requests from an unknown address, followed by a long interactive session. Nobody looked at the alert until the next morning. By then the server had already been rebuilt. No logs, no disk image, nothing except a packet capture from the perimeter.
Work out how the attacker got in and what they left behind
One external host scanned a WordPress site with a WPScan-style tool, then switched to a purpose-built exploit that abused the REST batch route to bypass authentication and spawn a temporary administrator. That account uploaded a plugin that gave command execution, followed by a second plugin whose only job was to create a hidden persistent admin and which fired minutes later. With a shell, the attacker planted a deliberately vulnerable plugin so they could upload files without credentials, published a page to reach it, and used it to drop a loader into the must-use plugin directory where other users would not see it. They finished by wiping the site’s backup directory. The whole intrusion is recoverable from the HTTP traffic in a single perimeter capture.
tshark for filtering and field extraction, --export-objects http and Wireshark follow-stream for carving uploadsTimestamp note: The capture’s local timezone is +1000. Every answer uses UTC, which is what HTB accepted, so convert before submitting.
Start with the conversation and endpoint statistics. One external address owns all of the HTTP attack traffic against the web server, so it is the attacker. Every later pivot in this case is a filter on that source.
tshark -r capture.pcap -q -z endpoints,ip
Dump every HTTP request from the attacker IP to a TSV with time, URI, method and User-Agent, then count requests per UA. One legacy Firefox 11 string on Linux i686 sends 178 requests in about six seconds, hitting plugin and theme paths in the pattern of a WPScan-style scanner. A real browser never produces that rate or that path list.
tshark -r capture.pcap -Y 'ip.src==176.107.185.250 && http.request' -T fields -e frame.time_utc -e http.request.method -e http.request.uri -e http.user_agent > work/http.tsv
cut -f4 work/http.tsv | sort | uniq -c | sort -rn
Tip: The format mask in the task is the shape of the string itself, which is a quick sanity check against the top UA in the count.
Take the first request carrying the Q2 User-Agent. Use UTC output (frame.time_utc) rather than local time, since the capture sensor ran at +1000.
grep 'Firefox/11.0' work/http.tsv | head -1
The scanner goes quiet at 19:09:50 and a new UA appears from the same IP at 19:11:01. Its name is the tool’s own, short and not browser-shaped, which marks it as the exploit client.
awk -F'\t' '{print $4}' work/http.tsv | uniq -c
Filter requests from the Q4 UA. The tool sends repeated POSTs to /?rest_route=/batch/v1, the REST batch route, which lets it bundle sub-requests into one unauthenticated call.
tshark -r capture.pcap -Y 'http.user_agent=="wp2shell" && http.request.method=="POST"' -T fields -e frame.time_utc -e http.request.uri
The pcap carries no CVE identifiers, so this one comes from advisories. The behaviour is an unauthenticated SQL injection in WordPress core reachable through the batch route, and a Patchstack write-up on the core fix (https://www.patchstack.com/articles/unauthenticated-sql-injection-in-wordpress-core-fixed-in-7-0-2/) lists the two CVE IDs. This was matched from reference material, not read from an artifact.
Gotcha: The platform accepts the two IDs only in one order, lowest number first. The reverse order is rejected.
Right after the batch exploit, the tool logs in over wp-login.php. Decode the POST body: the log and URL-encoded pwd fields hold the generated admin account. Run the password through a URL decode so %21 and similar become literal characters.
tshark -r capture.pcap -Y 'http.request.method=="POST" && http.request.uri contains "wp-login.php"' -T fields -e urlencoded-form.key -e urlencoded-form.value
Plugin installs go through update.php?action=upload-plugin as multipart POSTs. Export the HTTP objects and pull the multipart filename from the first upload after the login. It is a zip containing a PHP file of the same stem, which is the webshell wrapper.
tshark -r capture.pcap --export-objects http,work/obj -q
unzip -l work/up/*.zip
Gotcha: The question asks for the uploaded file, which is the
.zipnamed in the multipart header, not the.phpfile inside it.
A second plugin upload follows the webshell, carried in frame 18285. Carve the multipart body out of that frame, unzip it, and hash the PHP file wp-insights-collector.php. The hash is of the PHP file, not the zip.
sha256sum work/up/b/wp-insights-collector.php
Read the carved plugin source from Q9. It calls wp_create_user with a hard-coded username and password, gated behind a trigger condition.
grep -n 'wp_create_user' -B3 -A3 work/up/b/wp-insights-collector.php
The plugin source from Q9 fires on a request to /?collect=go. Find the GET carrying that parameter; it sits in frame 19072. Read its timestamp in UTC.
tshark -r capture.pcap -Y 'http.request.uri contains "collect=go"' -T fields -e frame.number -e frame.time_utc
A third upload-plugin POST, frame 20447, installs career-section.1.7.zip. That plugin version carries an unauthenticated arbitrary file upload, documented in Wordfence’s entry for Career Section 1.7 (https://www.wordfence.com/threat-intel/vulnerabilities/wordpress-plugins/career-section/career-section-17-unauthenticated-arbitrary-file-upload). The CVE ID comes from that advisory, matched to the plugin name and version seen in the capture.
tshark -r capture.pcap -Y 'frame.number==20447' -T fields -e http.file_data | head -c 300
The Career Section upload form is rendered through a shortcode on a public page, so the attacker had to create one. Look at the post creation in the capture for the title of the new post (post 27) carrying the csection shortcode.
tshark -r capture.pcap -Y 'http.request.method=="POST" && http.request.uri contains "post.php"' -T fields -e urlencoded-form.key -e urlencoded-form.value
Follow the webshell traffic after the page goes live. The command channel shows the upload landing as 1785439610_hider.php, then a mv that relocates it to ../../mu-plugins/hider.php. Must-use plugins do not appear in the normal plugin list, which is the hiding. Resolve the relative path against the web root of the site.
tshark -r capture.pcap -Y 'http.request.uri contains "mu-plugins" || http.file_data contains "hider"' -T fields -e frame.number -e http.request.uri
The last meaningful webshell command removes the backup archives: rm -rf backup* inside wp-content/updraft, the UpdraftPlus backup directory. Take the timestamp of that request in UTC.
tshark -r capture.pcap -Y 'http.request.uri contains "updraft" || http.file_data contains "rm -rf"' -T fields -e frame.time_utc -e http.request.uri
| Phase | Action |
|---|---|
| Reconnaissance | WPScan-style enumeration with a spoofed legacy Firefox User-Agent |
| Initial Access | wp2shell exploit abuses the REST batch route for unauthenticated access |
| Initial Access | Temporary administrator account created and used to log in |
| Execution | Plugin zip uploaded that gives command execution |
| Persistence | Second plugin uploaded that creates a hidden backdoor admin when triggered |
| Persistence | Backdoor triggered with a collect=go request |
| Persistence | Vulnerable Career Section plugin installed and a public page published to expose its upload form |
| Defense Evasion | Uploaded loader moved into mu-plugins to hide it from the plugin list |
| Impact | Backup directory wiped with rm -rf |
| Timestamp (UTC) | Event |
|---|---|
| 2026-07-30 19:09:44 | Enumeration begins with the Firefox 11 User-Agent |
| 2026-07-30 19:09:50 | Enumeration ends after 178 requests |
| 2026-07-30 19:11:01 | wp2shell User-Agent first appears |
| 2026-07-30 19:18:29 | Backdoor plugin triggered via /?collect=go |
| 2026-07-30 19:31:37 | Backups deleted from wp-content/updraft |
| Type | Value |
|---|---|
| IP | 176.107.185[.]250 |
| User-Agent (scanner) | Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.16) Gecko/20120421 Gecko Firefox/11.0 |
| User-Agent (exploit) | wp2shell |
| Account (temporary admin) | wp2_bb9ed300485b |
| Account (backdoor) | wp_insights_user |
| File | wp2shell_602a13ff.zip |
| File | wp-insights-collector.php |
| SHA-256 | ae29e0acf2071277f3dbca178895f2602c4d06e42727225aa6a610803393abe4 |
| Path | /var/www/html/wp-content/mu-plugins/hider.php |
| Plugin | career-section 1.7 |
| Technique | ID | Description |
|---|---|---|
| Active Scanning: Wordlist Scanning | T1595.002 | WPScan-style plugin and theme enumeration |
| Exploit Public-Facing Application | T1190 | Batch route authentication bypass in WordPress core |
| Valid Accounts: Local Accounts | T1078.003 | Login with the account the exploit generated |
| Server Software Component: Web Shell | T1505.003 | Uploaded plugin giving command execution |
| Create Account: Local Account | T1136.001 | wp_insights_user backdoor admin |
| Event Triggered Execution | T1546 | Backdoor plugin fires on a crafted request |
| Indicator Removal: File Deletion | T1070.004 | Loader tucked into mu-plugins and backups removed |
| Data Destruction | T1485 | Backup directory wiped |
An unauthenticated core route needs to be patched fast and fronted by a WAF. The batch endpoint gave the attacker admin without any credential. Patch cadence for WordPress core and a rule limiting rest_route=/batch/v1 from the internet would have closed the door.
Scanner traffic is the early warning. 178 requests in six seconds under a fake legacy browser string is a trivially alertable burst. The exploit followed about a minute later, so a block at the scan stage would have cost the attacker the foothold.
Plugin uploads by new administrators should page someone. Three upload-plugin calls in one session, two shortly after an admin appeared, is an unmistakable pattern. Disable file modification in the dashboard (DISALLOW_FILE_MODS) so plugin installs through the web UI fail outright.
Watch mu-plugins and the user table. Must-use plugins load silently and never show in the plugin list. Monitor that directory for new files, and alert on new administrator accounts created outside a change window.
Keep backups off the web host. The attacker only had to run rm -rf on a local directory to remove recovery. Offsite, immutable backups would have survived, and a perimeter capture should not be the only evidence left, so ship web logs off the box.
The attacker went from a scanner to a hidden persistent administrator and deleted the backups in about twenty minutes, all over plain HTTP. The packet capture was the only record left because the server was rebuilt, which is the argument for shipping web logs and backups off the host.