how to hack the uk tax system, i guess

a 3 step guide to a 57 day journey

As I am now an adult, I sometimes need to look at taxes. The longstanding tradition of adults dictates that I must look at my taxes and say to my fellow adults “wow, I wish I had that money which is spent on single payer universal healthcare, infrastructure and education so I could spend it on video games, hardware I never use and thousands of tiny 3D printed statues of myself.”.

Regardless, I didn’t expect my micro-sojourn into responsibility to result in a somewhat bad security issue followed by the ability to arbitrarily modify people’s tax details after making them click a link, followed by a 2 month journey to getting confirmed fixes. Welcome to my 3 step guide to hacking the uk tax system, I guess.

The UK tax system login process is neat, and well thought out. One goes through an interstitial login web form which requests an identification number, a password, and a code texted to your cellular mobile device.

On the technical side of things, this is achieved by the common redirect forwarding pattern in which the page that required login hands off to the login page with a note in parameter form saying where to send the user back to when the login process is successfully completed and you’re ready to dive into taxes and such.

Those hackers with a bug bounty spidey sense may feel a tingling, as I did – and it’s not your leg dying from sitting in a dark room alone too long watching numbers fly by faster than the eye can see because that’s a negative stereotype perpetuated by lazy reporting that only hurts public perception of personal security by creating mystery around what being secure means and what hackers do possibly spurred by the efforts of big corporations to make the abuse of extremely simple security flaws seem pervasive, highly intelligent and advanced when really it’s probably some 17 year old with a l33tspeak username from hackforums who read a tutorial on sql injection.

Imagine you’re a normal person right now, however hard that is. You get an email from Her Majesty’s Revenue and Customs that threatens to send the internet police if you don’t file an emergency tax form and there’s a link that goes to the tax service — you fill in the form with your ID and such, go through to the tax form, fill that in. Now I have your tax details.

If we can specify the continue parameter of the sign in page, we can do exactly this – we can construct a link that goes to HMRC, passes login and then sends the victim to our own carefully crafted site that looks like HMRC but is instead us.

proof of concept™

There are some classic ways to test for these open redirect vulnerabilities. We need to guess a value, which, when put in the redirect parameter the browser and the backend server understand differently: differently enough that the backend server thinks it’s a safe redirect that will go back to the tax system but the browser thinks goes to the outside interweb.

Putting in as the redirect url probably wouldn’t work, so I didn’t test it. Instead, I tried a few standard test-cases: — HTTP simple authentication URI syntax. This is not often expected by developers and misunderstood by generic URI parsers such as Ruby’s. The site rejected this.

// — protocol relative URL syntax, which is, as HTTP simple syntax, specific to HTTP URLs. Because this form begins with a forward slash (‘/’), it often gets through confused software, which is fooled into thinking the slash represents a relative url like /mypage.html. This succeeded, and thusly we can do bad things.

Alright, so we can do this pretty bad redirect thing and steal people’s tax information, what now? Generally, I try to get in contact with someone who can fix it, and if that works look deeper. It’s ✨responsible disclosure time✨✨✨✨✨✨

2: The Unsafety Dance

First comes finding something, then comes the strange and perilous ritualistic incantations of attempting to get something fixed. In act one, we attempt and fail the standard secret security handshake by emailing security@ wherever we found the issue.

Well that doesn’t work. Luckily 50% of my body is made of persistence so let’s invoke the power of the collective knowledge contained within Google together to see if I’m missing something.

The top one is the National Cyber Security Centre’s pentest service for UK businesses, then an article about how the UKcares about cyber security, then an article about the NCSC’s government vulnerability disclosure policy. Seems promising.

Apparently you have to be specially invited, and I wasn’t invited. So, uh let’s just just try to get attention through Twitter?

(no reply from

After trying to reach out to, the new-ish (and so much better) platform built by the government digital service, I tried to reach out to the National Cyber Security Centre, whose explicit reason for being is to improve the security of the UK and UK government systems.

Sadly, what this tweet was actually directing me to was not a vulnerability reporting tool, but an online fraud reporting tool. Next, HMRC customer help sent me a tweet:

I messaged them saying I couldn’t DM them because they didn’t follow me! … and then they never followed me back.

I called the press office and passed the vulnerability report on to my newfound contact, who they thanked me and passed it onto the security team. All seemed well, and I’d only had to contact 3 separate government agencies!

3: time to find out how much tax someone pays in the uk, I guess

One Saturday night I decided to revisit the UK tax office website and see if there was anything juicer I could find, something I like to do as thanks for a positive response to my work. What I found was an issue that allowed me to effectively hijack the tax accounts of HMRC online users. Yes — the wunderkind of the modern web, XSS.

HMRC uses some kind of device signature service called It sends HMRC a pretty scary amount of information that it can glean about your computer, likely as a heuristic for an anti-fraud measure. With this information, I discovered the spookily named ‘_.htm’, which loads every time the login page does.

a request to _.htm by the HMRC login page

More quizzical are the actual contents of the file:

<!doctype html><html><head><!--2.1.0--><meta name="referrer" content="no-referrer"></head><body><script>var _=function(n){if(typeof(_.list[n])=="string")return _.list[n].split("").reverse().join("");return _.list[n]};_.list=["a(||)????(^)4erideRW;ba;))]h^i&3[&&<<) E(Te(^<<e^3>>>f(+)2a. m}{)a(eikooegasse<<f^5>>>e(=g,],)[b= m})(ka{){)b,a()(ja.(A.C(x.f",//g,/^/,"~`@\'","9876543210_ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba",/\b\w\w?\b/g,"0202?htaM?atad?esle?di?eman?htap?tset?00?naJ?rugua?roolf?hctam?yrt?gnirtS?niamod?hctiws?552?fOxedni?van?ecalper?noitacol?ruguA?DIU??edocnE46b?hctac?esrap?eulav?7251350461?7876661561?1536603591?fi?nioj?diov?tnevEhcatta?noitacoLteg?TLSredlOteg?Mtsop?edoCrahCmorf?irafaSsIsiht?kaerb?gnirtStpyrcne???seripxe``TLS?roFkcehc?NOSJ?renetsiLtnevEdda?esac?c?edoceD46b?rof?Mno?yfignirts?5927694924?TLSdilaVsi?DELBASID_
[... 4202 characters ...]
charAt((j-63)/63)+c.charAt((j-63)%63))+'\b','\x67'),r[j]):0}}return o.replace(_(1),"\"")})([[_(6),_(0),""]])))()</script></body></html>

The page is almost 100% javascript with some very bizarre attempt at either compression or obfuscation. The output of the compressor / obfuscator reminds me of JSSFX, a really technically interesting tool which was used in the Javasript demoscene to make very small Javascript files — before the author vanished from the internet. Considering the necessary throughput of whatever they’re serving this file from, I’d find it very odd if they were actually attempting to make this smaller and glean some tiny saving.

Addendum: I explored augur’s code more, and it seems this scheme is used on all their code, likely as an obfuscation measure.

I’ve spent a considerable amount of time reversing obfuscated javascript software or writing it, and I would like to emphasise what should follow from the difference between ‘obfuscation’ and ‘encryption’ — a secret contained in an obfuscated system is not protected. It’s right there, in the software, someone just has to know how to find it.

I’m going to stop myself from getting lost in a segue on Javascript obfuscation, and how it doesn’t work. You may be able to get the gist of that topic from the simplicity my next step: remove the last ‘()’ from the code, and run it. With this simple scheme, the language’s interpreter rather helpfully returns a value representing the function after the operation to unpack the obfuscated value was been performed.

We can then call toString on that blob of text to get the full raw code which the obfuscation / compression operation was performed on. That code, while not totally unintelligible, is still pretty frustrating to read, so I usually just throw it in closure compiler to pretty print, resulting in this:

🌶🌶HOT🌶🌶 TIP🌶🌶 closure compiler’s ‘advanced optimizations’ feature will optimise away many typical kinds of non-compressing obfuscation

Once that’s done, toss the resulting code in your text editor of choice, season appropriately and start running through it for issues, a process I will here mostly elide. Here’s the fun part. There’s a function called ifRedirectThenRedirect, which looks like this:

You’ll also need the definition of rt from the main entry point of the program, which looks like this:

Rt comes from the search string, which is the part of a url after the ‘?’, but before the ‘#’, like this:

I hope you got that. I have way to much familiarity with reading total garbage-nonsense code, so I’ll try to interpret it for you, thus:

When the hash component exists, and the hash component begins with ‘#!’, some weird stuff involving caching in memory which we can probably ignore happens, and then the browser is directed to whatever rt?rt+"?url=”+hash:hash means.

Here’s the game plan: if we can get the browser to be directed to a url starting with ‘javascript:’, the browser will instead run our redirect as though it were HMRC’s own code in the user’s browser – allowing us to access and edit all their taxes.

The first part of the code checks to see if rt exists, and combines it with the hash component if it does; otherwise, it just redirects to the hash component. The getLocation function that is used to retrieve the hash component has some basic validation that makes it tough for us to simply start the hash with javascript: so we must provide rt via the search component to make sure we trigger the rt+”?url="+hash case. Since rt =, that is– every character from the 4th character of the search component, we need to put a buffer of 3 characters between the javascript: url containing the code we want to execute and the ?. Here’s an injection URL I made earlier:!

Amusingly, this trips a WAF (‘web application firewall’) of some kind.

WAFs are pretty dumb, and useless, so we can bypass the firewall simply by changing our () to ``:

Okay, that’s cool and all, but we’re not exactly reading someone’s taxes yet. Once we have XSS on (which we now do), the browser believes we are HMRC, so we just need to request that information, which is located on the page

I’ve edited out my actual income for fear of cultural faux pas

This information isn’t taken from such a place a well-intentioned but sadly manipulated computer might just grab on its own, but luckily the X-Frame-Options header, which specifies what pages can embed the tax page is set to SAMEORIGIN . The browser believes our code is directly from, so we’re on the same origin: we can embed the tax information page in a cute little child window some would call an iframe. Once that is done, we can request the data nicely, and directly from the child tax page like the gentlefolk we are. It’s like uh, taking tax information from uh… a child.

Noting that the name of the element with some scary tax details in it is called ‘employmentName0’, we can work on the payload of our exploit in the console of the vulnerable page until we get something we like.

experimenting with payload code for creating and stealing data out of iframes in the Chrome console

Once we’re happy with our payload – and we are – we can combine it with the obfuscated injection point we found earlier to produce this lovely little thing:!!%20I%20have%20your%20tax%20details%3A%5Cn%22%2B%20el.contentDocument.getElementById(%22employmentName0%22).innerText)!

Sadly, this again triggers the WAF, so we’ll have to do some obfuscation to make it look ‘less suspicious’. A WAF’s job is to be scared of anything that looks like a vulnerability it’s seen before, so we just need to exercise our innate, childlike creativity and produce something relatively original.

My method here is simply to use eval (which runs Javascript we give it as text) to get a reference to String.fromCharCode which, in turn, allows us to turn numbers into text. Entering the right numbers into String.fromCharCode gives us our code back, which we can pass to a second eval() to execute.


Ah, not suspicious at all. The WAF is totally happy with that. So pure. So innocent. The resulting URL for our vulnerability is now:!

Not even slightly suspicious. Let’s try it!

Okay, turns out it didn’t work because there’s some kind of timer that logs you out after a while. If only there was a way of tricking the user into logging in and then going somewhere else ;)

Combining our first vuln, which can be used to trick the user into logging in and going somewhere else and this one gets us:!

Once loaded (perhaps from a click on a link in an email), this will ask the user to login to their UK tax account, and then redirect them to the vulnerable page and tell them how much they earn. Which hopefully comes as no surprise – but the point here is to demonstrate power, not make code that might actually cause a financial crisis when released into the wild. That would be so-to-say, immoral.

One important note is that this access is not limited to reading! We can push buttons, fill in forms and make other changes to the user’s tax status. This bypasses all the heuristic checks I’ve seen HMRC employ as we’re operating on the victim’s device. We could even file their tax forms for them! But we won’t.

4: impact

There’s a notable amount of rabble-rousing around the impact of vulnerabilities of this kind, specifically that XSS vulnerabilities ‘require user input’. Perhaps in contrast to vulnerabilities like heartbleed, shellshock, ETERNALBLUE or a denial of service attack that require only network access (but also don’t necessarily connect you with financial systems, either).

I think part of this line of thought in the security scene comes from the reassuring hypothetical that unlike the big-name vulnerabilities you hear about coming down the intertubes, there’s a small chance that when someone emails you, yourself (a Smart and well-versed security practitioner), a pixel-perfect replica of a plausible email from Her Majesty’s Revenue and Customs asking you simply to click a link that objectively goes to the UK tax service website, you might somehow x-ray it deeply enough to turn tail and refuse to click a single link in it.

While I do appreciate this, the set of people on whom you might use a sharp, well-honed spearphishing attack armed with a vulnerability that extracts and manipulates financial information directly doesn’t usually include industry professionals and the excessively paranoid. Indeed, we live in a world where the highest-earning mode of internet fraud (to the tune of $2.3B just in the US) is simply emailing financial officers asking them to make a wire transfer of big $$$ to our bank account which is registered conveniently to a bank in the middle of a volcano dimension that has no extradition treaty to NATO states.

time, and space

My words so far cover only the first three days of the 57 days it took to get a confirmed fix on these issues. It’s been really hard to write something about the rest:- being in real life, as in prose the boring and tedious bit.

Not writing about this step would be misrepresenting the trials I had to go through, and I think that’s similar to denying these things happened. Those 57 days represent the largest river I had to ford, a hurdle I had to personally push myself to jump in order to achieve the simple state of knowing the serious issues I identified were known and in good hands.

There’s a weight to finding issues and caring about them, a moral load that, as a security researcher you have to carry until the issues are fixed. Sometimes you have to fight people to care, sometimes you have to fight to find people to fight to care. It sucks.

I attached a raw timeline to the end of this article, but I’m also going to try my best to best represent the details here.

So far the hero of our story (that’s me 😉) found the first issue, reached out to multiple government agencies and contact addresses that could all have reasonably given a good contact. I reached someone in the HMRC press office, who responded positively. I sent them a second email with details of a much more serious issue. It’s the 21st of April. It’s taken me two days to get hold of a contact.

22nd April: By the next day, the first issue I found appears fixed, but I don’t delve far enough to know for sure. Three days pass with no reply.

25th April: I call my contact at his work phone. They avoid discussion of the issues, and on the topic of my emails say it’s not their place to comment on security issues. I’m kind of at a loss of what to do now. Four days pass.

29th April: I try to reach out to the consultant who runs the UK government responsible disclosure program. They, oddly follow me back on twitter but do not reply to my DMs. Five days pass.

3rd May: Frustrated, I start writing this article, hoping to at the very least talk about my experiences. I contact, the vendor whose code I leverage in my vulnerability. They reply and thank me for the disclosure the same day. They notify me the issue is fixed the next day. Six days.

9th May: I call the HMRC press office to notify them of my intent to publish a predecessor to this article. The person at the other end is courteous and appears to understand my intentions. I email them an article URL.

At dinner that evening, I get a phone call from my original contact at the HMRC press office. I take the call out on the street, leaving my date inside. The caller implores me not to publish this article, threatening me with a plethora of criminal calamities I might cause if I publish.

Surprised, I try to muster a few common arguments on responsible disclosure. I decide, and state the decision that unless I have someone who can talk for the security policy for the HMRC tell me I shouldn’t publish this article, I’ll have to publish it. I mention the NCSC. They say they’ll try to find something.

10th May: HMRC original contact comes back to me with a mysterious NCSC email address for which googling has only 2 hits in the same PDF for a presentation made at a business conference. I compose a summation of all that has happened so far and send it to this email. Ten days pass.

20th May: I give up on this approach. I reach out to a special friend, who, it turns out has spooks on speed-dial and emails the Communications-Electronics Security Group (CESG), (as of now merged into the NCSC)with my email summation. He warns me these things take a little time, but move fast after that. 4 days later, we have a reply.

24th May: friend gets back an email from the CESG mailing list. They try to establish PGP encrypted contact with HMRC’s security team. 7 days pass.

31st May: Initial attempt at PGP key exchange between me and the NCSC. PGP sucks so this takes 2 days.

11th Jun: Senior NCSC contact confirms fixes, thanks me and offers to buy a drink.


We live in an age where the great engines of our time are spun simply out of ideas, tethered to the universe only by the flickering of charges in a silicon die.

Sadly, I don’t think I’m ever going to fly through the sprawling dark cities and networks pictured in film. It hurts especially that I’ll never be able to pull off sick grinds with my friends in cyberspace on the internet superhighway or probably learn how to rollerblade.

I do think that, however the things we put into our computers live in their own little universe, so drastically different to our own. There’s no concept of space here, but there are universal laws: rules upon rules upon which a software application, an app, a program, a video game all live and die. We came up with these, of course — we call them protocols or APIs sometimes.

As someone who finds vulnerabilities in software, many parts of this foreign universe are more intricately familiar to me than the places I’ve lived. Someone with the right knowledge can send a little idea of their own into a computer, one that interacts, competes with and manipulates others to the author’s own ends.

All of us with these abilities have a moral compass they must construct for themselves. I don’t think anything has given me more respect for an individual’s right to their own boundaries and privacy than living and working every day with the knowledge of how to strip those boundaries away. Not all end up seeing the same way.

I try to do my best to do the right thing, but sometimes you can’t help trying to avoid putting yourself in that place where you should be doing the right thing. You ask yourself — if I invest a little time trying to do the right thing, am I going to be sucked into a 57 day trek trying to see it through? There comes a point at which even doing the right thing seems to have been the wrong choice.

If you choose to walk down the moral high road with security issues sometimes you will find people who care as much as you do. Other times you’ll find people whose job you’re just making more difficult, people who think you’re trying to harm them or their company and people who just don’t understand. Those are fights you have to fight yourself.

I’m happy to be working security in a time where we have bug bounties — where sometimes, if the planets align I can feel like I didn’t just do the right thing because I had to.

But the places where security help is needed most are the places that don’t have these security investments; the places that don’t know, can’t afford, or can’t understand the value of security. The places with no security email address or responsible disclosure procedure.

The security issues I found were complex. The issues that made fixing them take 57 days are simple, and common.

Good security is an invisible luxury most places can’t afford. Security teams are expensive and hard to measure success for.

Security is young. I hope if I have children, they get to live in a world that better understands the risks and rewards of putting their data in that little silicon universe.


19 Apr: first issue found, tweeted @govuk @ncsc, email security@
19 Apr: contact from NCSC via twitter, link to cyber fraud page
19 Apr: inform NCSC link is wrong
20 Apr: HMRC customer help asks me to DM them (I'm unable to)
21 Apr: HMRC Press Office asks me to call them
21 Apr: Call HMRC Press Office
21 Apr: Email first issue to contact, contact thanks & will get back
22 Apr: Email second issue to contact (no reply)
(sometime before) vuln tentatively fixed
25 Apr: Called contact, they say it's not their place to comment on security issues
29 Apr: Try to contact consultant who runs UK gov responsible disclosure, get followed but doesn't dm back
03 May: begin writeup, contact via email
03 May: replies, thanks for disclosure
04 May: fix deployed to (not replicated to UK tax systems)
09 May: call HRMC press office with intent to publish this article
09 May: original contact calls me & implores me not to publish, says I'll be encouraging criminals. I ask them to get in contact with NCSC
10 May: get some kind of ncsc email, attempt to contact w/ details of additional issue (the email has literally 2 hits in PDFs on Google)
20 May: reach out to special friend for contacts, friend emails the Communications-Electronics Security Group (CESG) -- now merged into the NCSC
24 May: NCSC contact responds to friend
31 May: NCSC contact is attempting to establish PGP encrypted communication with HMRC
31 May: attempt PGP key exchange with NCSC contact
1 Jun: actually complete PGP key exchange (lol, pgp)
2 Jun: send PGP encrypted details to ncsc contact
11 Jun: reply from senior NCSC contact, thanks & apologies, offers to buy me a drink
15 Jun: vulns confirmed fixed; OK to publish with corrections from NCSC
22 Aug: finish much better rewrite of the article


Take care of yourselves. Stay healthy, stay cosy. Get regular exercise. Take breaks. Go outside. Look at a plant. Be as real with people as your feelings are to you.

Thanks to:
my special friend for getting me in touch with people who could help, though they did not wish to be named ❤
Erin Browning for tirelessly helping to edit my article.
Contacts at the NCSC for being courteous and accommodating.
Thomas Fox-Brewster for taking, & helping me through some of my frustrations through Twitter DM.

Greetz to:

XMPPWocky nyx secwrks

all my co-workers and bffs at twitch

id also like to thank andrew loyd weber, inventer of the world wide web for making the internet