Remote Code Execution — Bypassing WAF and Filters

Jeremyah Joel
6 min readNov 8, 2020

The injection is always a thing. Existing and settling well in OWASP Top 10 Security vulnerabilities 2007, 2010, 2013, and 2017 version - in which case made it to the first place for the last three versions, make this vulnerability is tempting to play with.

Injection flaws allow attackers to relay malicious code through an application to another system. These attacks include calls to the operating system via system calls, the use of external programs via shell commands, as well as calls to backend databases via SQL (i.e., SQL injection). Whole scripts written in Perl, Python, and other languages can be injected into poorly designed applications and executed. Any time an application uses an interpreter of any type there is a danger of introducing an injection vulnerability.

https://owasp.org/www-community/Injection_Flaws

There are many scenarios on how one's can take advantage of these Injection flaws, but in this post, we will be talking about Remote Code Execution. Nowadays, most websites are equipped with a Web Application Firewall as it is usually preinstalled in most hosting providers. These Web Application Firewalls usually have fixed rules to prevent and detect "Arbitrary code execution." But there are tons of ways to play with it, and it's where all the fun begins!

Welcome to the series

In this first series, I'll talk about bypassing simple rules. I'll be using a simple PHP script to mimic the vulnerabilities found in real life. This vulnerability is very easy to identify because this vulnerability is very limited to functions that pass input to the Operating System, i.e., pass-thru/system/shell in PHP or os. System and subprocess in python. In the real case, the script will be similar to the one below.

PHP Script to pass a variable into OS

If you're new, you might wonder if this script will harm us as we will only supply the host target as an input. We can use || and && to execute command chaining or two or more different commands. The elegant way is to split the command using ;

Red for the first command and green for the second

This is the point of RCE injection; it allows you to execute all commands that exist in the operating system. But the problem is most of the websites are protected by a Web Application Firewall. This attack requires an exact payload because the payload is exactly an operating system command with a supplied argument. This makes the attack is too easy to detect. Don't worry, and there are many ways to flip this around!

Are you bypassing if "Space" is forbidden?

This might be the case if you're here because of CTF competition. In bash, you can use $IFS to replace space. IFS stands for Internal Field Separator, not only space, and this can help you if you're trying to substitute a new line, line separator, etc.

$IFS to replace space

$IFS work just fine, but if it fails, you could try to use {command, var} to get your file. Well, it's a bit tricky because cURL usually takes the payload as "do this cURL request for each value inside{}."

{command,var} works fine

Bypassing WAF template matching or rules

As I mentioned before, Web Application Firewall and Filter have specific rules, and they will block you right away if you're trying to go with an exact payload like "bin/cat" or "etc/passwd." There are many ways we can play with this.

I've been learning a lot and getting ideas from the sec juice post about evasion techniques, and he provides a deep and clear explanation about the payload. You can check his post for a more detailed explanation about the first three-point below :)

Use 'string' to wrap your payload.

You can split your payload, and it will work just fine!

c’at’ /’e’’t’c/passwd == cat /etc/passwd

Splitting payload

Use Uninitialized Variable

In bash, uninitialized variables count as null or no values, which might help us bypass the "template matching."

Using uninitialized variable

Not only in your payload, uninitialized variable can also help you if || && is forbidden, you can simply do

x=google.com$x|$y|ls$z -la for ls -la

or

x=google.com$x&$y&$x/bin$y/cat $a/etc$b/passwd$h for /bin/cat /etc/passwd

Use Regex Wildcard

This is the most interesting part, you can replace any character with the wild card, either (? . *) will work like a charm! To help you craft this payload, you can use the command -v till you get the command you are looking for. If the wildcard still returns two or more possibilities, it will execute each with the values given. It's okay, but it might result in an error in the curling process. For example, if you're trying to get a file's content, you might need to call /bin/cat. You can start trying with

command -v /b??/c??

It will list all possible results.

After you get a good payload, you can shoot it right away!

Wildcard works just fine

Maximizing Bash Features to your benefit

Well, there are too many ways you can bypass this template matching, but if by any chance you can use the "which" command, you can abuse its features to your benefit. I'll do two examples; base64 and xxd.

Base64

You can check if the base64 command exist by using which base64

Which command to see if its exist

This is our golden ticket, and you can do anything you want if WAF / Filter does not filter base64. Pipe or | will be our best friend in this method.

First, we need to base64 encode our string

cat /etc/passwd == Y2F0IC9ldGMvcGFzc3dk

echo our sting -> base 64 decode it -> bash it!

I use one wildcard “?”

Well, if “base64” is filtered, you can try to combine your payload with splitting, uninitialized variable, or even wild card to get it works!

echo+’Y2F0IC9ldGMvcGFzc3dk’|/usr/bin/base64+-d|bash

is equal to

echo+’Y2F0IC9ldGMvcGFzc3dk’$a|$x/’u’sr/b?n%y/bas’e’64+-d$z|bash$c

XXD

Same with base64, first, we need to see if xxd exists in the target machine.

XXD is Exist

The only different with base64 is, we’re translating from hex here.

cat /etc/passwd == 636174202f6574632f706173737764

echo our sting -> xxd -r -p -> bash it!

It works!

Based on my experience, usually, base64 is filtered while XXD is not. Well, it doesn't matter as you can almost bypass this template matching by combining two or more methods :D

Lastly

It's almost impossible for Web Application Firewall to detect more evolving payload with just "Template Matching". Maybe it's time for you to move to Machine Learning Based- Web Application Firewall.

--

--

Jeremyah Joel

Product Security at Ministry of Education, Culture, Research, and Technology of Indonesia