Threat Insight

She Sells Web Shells by the Seashore (Part I) 

During the summer, Truesec helped one of its customers with the compromise of a webserver: the Customer’s SOC had detected the execution of several commands related to reconnaissance – whoami, id, and friends – coming from the PHP-CGI executable. In other words, a PHP script was invoking system commands. This leads to the definition of a web shell: a script that executes under a web server and that can interact directly with the underlying operating system and return the output to the requester.

web shell on seashore

Web Shell in a Nutshell

Web shells are nothing new: they have been around for the best part of the last 15 years [1].

At the heart of a web shell is the execution of a command by a script or program: the threat actor requests a specific page on the webserver – the web shell – with a parameter that indicates the command to execute. The “specific page” in question can be a scripting language for which the webserver has a plugin, for example ASPX or PHP, or an actual executable invoked using the CGI mechanism. This latter method was very common in the early days of the web and is often denoted to as “cgi-bin”, in reference to the path where Apache stores these executables.

For our series, we will consider PHP as this was the language the web shell we will present was written in. Keep in mind that everything can be transposed to ASPX.

A Bit about PHP

The development of PHP1 began in the late 1990s[2] with the efforts of Rasmus Lerdorf. Quickly, it became very popular and was one of the main components of the so-called LAMP stack, Linux-Apache-MySQL-PHP, which contributed majorly to the evolution of the internet and led to the Web as we know it today.

It is an interpreted language, meaning that a bit of code called the “interpreter” takes the commands and processes them. Figure 1 shows the PHP interpreter run in a shell, with the traditional “Hello, World!” displayed in that language.

Figure 1 PHP in the Shell

Let’s look at some of its features, as they will be important when looking at the different scripts.

From String to Command

Many modern interpreted languages can take a string and “run it as if it were a program”. This is usually called “evaluation”: the content of the string is “evaluated” by the interpreter. Here is an example in Figure 2.

Figure 2 Using eval()

First, we assign values to two variables, $a and $b, the dollar sign “$” indicates a variable in PHP. The third variable, $c, is a string that contains the statement “print($a+$b);”. When we evaluate $c, PHP takes that value and runs it as PHP code, displaying the result of the addition.

Also note something very important: the code that runs in the eval() function has access to the variables $a and $b, even though they are not part of the evaluated string. Keep that in mind, we will get back to this later.

Call to the OS

For a web shell to be … well … a shell, it needs to be able to run OS commands. Most interpreted languages have at least one function for that. In PHP, there are a few different options, depending on whether the command is run through a shell or a pipe, and how the command output is returned to the caller. Let’s look at one of them, shell_exec().

Figure 3 Executing a system command

The example in Figure 3 shows how this works: the command “whoami” is passed to shell_exec(), which spawns a shell and runs “whoami”. The output is returned in a string, which can then be processed and displayed.

Shapeshifting Strings

The last element we need to know is how we can transform inputs. There are a few different terms that cover these operations:

  • Decoding: this means transforming the input from one representation to another one that is equivalent. For example, replacing “A” with its ASCII equivalent “\x41”
  • Decompressing/inflating: this uses an algorithm to reverse the compression applied to a string. Compression is used to remove the redundancy of a string so it takes less space.
  • Substitution: this replaces all occurrences of a character with another character. The Caeser Cipher is an example where A is replaced by N, B is replaced by O … and so forth.
  • Concatenation: simply put, this is chaining strings together. For example “Hello” + “, World!” concatenated is “Hello, World!”

Why is this relevant and important? The threat actors don’t want their code to be too visible or easily discoverable. To prevent that, they often use obfuscation, that is replacing their code with a less readable version that will be hard to review but that executes just fine.

Imagine that you want to pass the message “Meet me later!” to a friend but you want to make it less obvious, as your adversary does not like people meeting. To do so, you decide to pass a piece of PHP code to your friend.

The code in Figure 4 does exactly that but it is still obvious what it achieves, and if someone is watching for all messages that contain the word “Meet”, they will easily discover it. So, let’s throw in a few changes.

Figure 5 Obfuscating the message

Now, what is left is to communicate the string “S04tS0zXUIoqKkpXqCpSqMxLL0pVVNK0BgA=”. He, in turn, will execute the commands, see Figure 6, in reverse and evaluate the result.

Figure 6 Getting the message

Of course, when analyzing scripts that may be malicious, running code is not recommended. In these cases, we often use a tool called Cyberchef[3], visible in Figure 7.

Figure 7 Cyberchef in action

PHP Loading PHP

Now that we know a bit more about PHP, let’s dive into the first part, which we will call “the Loader.”

The web shells we found were not stored on the disk. Instead, we discovered that they were requested from the Internet, sometimes deobfuscated, and then passed to eval(). As the web shell is not written to disk, any security tool relying on a scan on access will simply not detect its malicious nature. The downside being, of course, that the code of the web shell is downloaded from the internet for every and each request.

The first loader we will look into is only lightly obfuscated, as shown in Figure 8.

The purpose is immediately visible, but the details are hidden: some content is retrieved, two characters are concatenated before that content, and the whole thing is evaluated. But what are these details? Cyberchef to the rescue!

Figure 9 Cyberchef making short work of the first loader

The deobfuscated code is shown in Figure 9. Now, both purpose and details are visible:

  • Some content – we will look into this in part III – is retrieved from the site pinka[.]b-cdn[.]net.
  • The string “?>” is concatenated before the content.
  • The whole thing is executed.

The string “?>” indicates the end of a PHP script. But why is it added before the string? Because the content starts with “” was not added, the PHP interpreter would try to interpret “<?php”, which is not a valid statement and would throw an error, as demonstrated in Figure 10.

The second loader is a lot more obfuscated, just take a look at Figure 11!

Before we dive into the deobfuscation, let’s analyze the top portion, reproduced in Figure 12. It disables the limit on the time the script is allowed to run and it disables the error reporting, that is the logging of potential error messages. Lastly, it sets the HTTP code to 404, which usually indicates that the page was not found. To further that impression, the page title is set to “404 Not Found”.

Figure 12 Now you can’t find me

This is an interesting decoy: when the page is requested, the webserver will log that the page does not exist. Even though it was fetched.

On to the obfuscated part now. The statements give us the order to deobfuscate the code: base64 decode, ZLIB inflate, rotate by 13 letters2. First step, we apply this recipe. This gives the output shown in Figure 13.

That is strange! This looks like the same code. Except that the value is not the same. Threat actors use several rounds of obfuscation layers as a common trick to slow down the analysis. So, let’s follow the flow and go to Figure 14.

Only base64_decode()? Is it over already? Not quite so! Figure 15 shows the bottom of that code: the same gzuncompress() and str_rot13() functions appear. The threat actor simply moved the evaluations to after the assignment, again in the hope of making the analysis more difficult.

The third layer has been peeled, revealing the code shown in Figure 16.

The deobfuscation happens, again, at the bottom of the function, shown in Figure 17.

We see another variable, $rand, which is passed to base64_decode() in addition to the initial base64_decode(), and then evaluated. That content is shown in Figure 18: this decodes the variable shown above, inflates it and proceeds with a few substitutions: “ý” becomes “a”, “ê” becomes “i”, and so forth. Note that that code uses the variable $SISTEMIT_COM_ENC, defined outside of the evaluated string: as we saw under “From String to Command”, eval() has access to all the existing variables.

Let’s do the same and remove the fourth layer of obfuscation. This gives the code shown in Figure 19.

From here on, this is just peeling the layers one after the other. After the last layer, we see Figure 20.

Figure 20 Passed the final layer

Et voilà! No more obfuscation layers for this loader! We did it! Our journey has reached its end successfully and the purpose of this script is now visible: to retrieve some content from user-images[.]githubusercontent[.]com and eval() it. Noteworthily, the content retrieved pretends to be an image (“.jpg”) but is actually a text content. Another technique the threat actors use to hide their contents.

Now, we can see that both loaders do the same thing: get content from a website and pass it to eval(). The difference being the number and order of the layers of obfuscations. We will look at this content in part II, the Shin Web Shell, and part III, an unknown web shell.

How did it get there?

Before we conclude the first part, let’s ponder for a moment: how did these files appear on the webserver? As usual, there are several answers.

In the incident we responded to, we suspect that a misconfigured PHP file manager was abused to write a PHP file on the webserver, which was then used to deploy the loader. In 2022, two Microsoft Exchange vulnerabilities were exploited to deploy web shells on affected servers. This was called “ProxyShell” [4]. The year before, Microsoft Exchange server was susceptible to vulnerabilities, which HAFNIUM exploited [5]. More recently, a vulnerability in Microsoft SharePoint was exploited to deploy web shells on vulnerable installations [6].

A third way in is through a “remote file inclusion” [7]: this is a specific type of vulnerability in which a PHP script can be tricked into including a file under the control of the threat actor. This included file may contain any code, for example to download and write a loader on the vulnerable server.

In Summary

So far, we introduced the PHP language and a few of its features, including the possibility to evaluate strings as actual code and the execution of OS commands.

Armed with that information, we had a look at two PHP loaders, whose purpose is to retrieve additional code from the internet and to execute it on the web server. What this additional code does will be looked into in parts II and III. As that additional code is never written to disk, it may escape detection.

Threat actors use obfuscation to mask the true purpose of their scripts. Knowing how to peel these layers is an important part of the analysis of these scripts. In addition, threat actors use extension masquerading and fake return codes to hide the purposes and actions of their scripts.

Lastly, how scripts and files are transferred to the web server in the first place does not have a single answer, with the path ranging from misconfiguration to vulnerabilities.

The Shin Web Shell

In the next part of this series, we will have a look at the Shin Web Shell, which is loaded through the second loader we looked at above.

Continue reading “She Sells Web Shells by the Seashore (Part II)”

References

[1] Article on Web Shell, by Invicti.
[2] “History of PHP”, PHP: History of PHP – Manual, by the PHP Documentation Group.
[3] Cyberchef on Github, gchq/CyberChef: The Cyber Swiss Army Knife – a web app for encryption, encoding, compression and data analysis
[4] “Analyzing attacks using the Exchange vulnerabilities CVE-2022-41040 and CVE-2022-41082 “, Analyzing attacks using the Exchange vulnerabilities CVE-2022-41040 and CVE-2022-41082 | Microsoft Security Blog, by Microsoft
[5] “Tracking Microsoft Exchange Zero-Day ProxyLogon and HAFNIUM”, Tracking Microsoft Exchange Zero-Day ProxyLogon and HAFNIUM – Truesec, by Truesec
[6] “Detection of CVE-2025-53770 / Toolshell”, Detection-of-cve-2025-53770-toolshell, by Truesec
[7] “What is remote file inclusion?”, Remote File Inclusion (RFI), by Invicti

Stay ahead with cyber insights

Newsletter

Stay ahead in cybersecurity! Sign up for Truesec’s newsletter to receive the latest insights, expert tips, and industry news directly to your inbox. Join our community of professionals and stay informed about emerging threats, best practices, and exclusive updates from Truesec.