Update, 14 Feb 2014: A year and a half on from writing this, Tesco has indeed suffered a serious security incident almost certainly as a result of some of the risks originally detailed here. Read more about it in The Tesco hack – here’s how it (probably) happened.

Let me set the scene for this post by sharing a simple tweet from last night:

Ok then, that’s about as many security misdemeanours as I reckon you can fit in 140 chars! For those wondering, yes, this is actually a verified account and it really is Tesco responding to me. I’ll come back to Tesco’s many interesting views on security a little later, but first, some background:

I keep a watch on mentions of my blog over on Twitter and get a lot of tweets along these lines:

Curious, as always, I headed over to tesco.com to take a look. A few cursory glances around showed perhaps there was a bit of an opportunity here – an education opportunity for developers who like to learn from anti-patterns, i.e. seeing how those who have gone before them have done it wrong. So let’s take a look at the many simple security errors Tesco have delivered and see how we would approach this differently when applying basic security principles.

Oh – and for audiences outside the UK, Tesco is a major supermarket chain the likes of Coles in Australia or Costco Safeway in the US. You know, the kind of multi-billion dollar brand that should know how to get web security basics right, particularly when they’re providing online shopping services and handling your payment info. They also provide banking and insurance services, although that’s not an area I’ll look at in this post.

Password storage

This was obviously the first place to start given Dan’s comment. As it turns out, although I live in Sydney I actually have a Tesco account from my time back in the UK around the turn of the millennium. I wonder what my password was back then…

Oh dear, well Dan certainly nailed it when he mentioned no salt, clearly the passwords aren’t hashed at all let alone salted. At best they’re encrypted but chances are they’re stored in plain text, unfortunately there’s no evidence to the contrary.

Encryption is not selective – data is either important or it’s not

When you decide data is worth protecting, you need to be consistent in your approach. There’s no point tightly securing it in one location then having it flapping around in the breeze at another.

Apparently, encryption is important to keep your personal details private:

Righto, so how exactly was that password protected in email? Well of course it wasn’t protected at all, it was just sent off willy nilly.

But hang on – Tesco put a big yellow padlock on the page – it must be secure! This is exactly the sort of thing I was talking about a year ago when I said the padlock icon must die. It has become nothing more than a token gesture which in no way guarantees any of your content is actually secure. It’s like those people who put up signs saying “beware of the dog” when all they’ve got is a geriatric hamster.

Mixed content and browser warnings

Everyone remember what mixed content is? That’s when you load up a page over HTTPS – which implies a degree of security – but then it embeds resources loaded over HTTP which gives you no assurances whatsoever.

This is bad. In fact it’s so bad that the browsers of today give you a very blatant warning when this is happening. Cast your mind back to the image above with the text which says “Why it’s safe to shop at Tesco.com”. Know what that link does? This:

Ok, so the browser is being very clear here: do not trust this page (the one about trust), in fact don’t load it at all. What the heck, I like living dangerously:

When you start getting big red crosses in your browser, be afraid, be very afraid. But you probably should be even more afraid of Tesco’s simple opening paragraph:

It is safe to shop at Tesco.com

Well at least they’re direct. Completely wrong (at least based on what we’ve seen thus far), but direct. They have a secure server (we know this because we saw their padlock) so we should all be confident that “transaction with us are private”. Nice.

Browser version craziness

But Tesco won’t just let any old browser in, oh no, you must be using something modern like version 3.0 of “explorer” or 3.02 of Netscape (this is from further down the screen in the previous grab):

Remember what these guys looked like? Do you even remember Netscape? Quite possibly not because there’s a sizable audience out there browsing the web today who were still breastfeeding when it launched in mid 1996. Here’s a recap:

Fancy.

Why Tesco feels the need to direct users to ensure they meet a 16 year old browser version (or above, thank you) is rather odd. In fact it gives the impression that nobody is really paying much attention to the site. Heck, it was odd when I originally created my account in 1999!

Persistent insecurity through HTTP cookies

Remember how Tesco is secure? It must be because they had that padlock icon, but to their credit, they did actually provide login forms over HTTPS. But then they go and do this:

See the problem? No more HTTPS – but we’re still logged in! Ah, but the password was sent over HTTPS at login so it must be secure, they’ll say. Except, of course, SSL is not about encryption, or at least it’s not just about encrypting login credentials.

You see, HTTP is stateless so the only (practical) way a state such as being logged in can be persisted is by passing cookies backwards and forwards between the browser and the website. Each time the user makes a request, the browser says “Hey, I’m meant to be logged in as Troy, here’s my cookie to prove it”. You can see those cookies right here courtesy of Edit This Cookie:

And because they’re being sent over an HTTP connection, anyone who can watch the traffic can see those same cookies. And copy them. And hijack your session. Sound tricky? It’s not, in fact I demonstrated how easy it was in part 9 of my OWASP for .NET devs series on insufficient transport layer protection when I did precisely this.

Password rules

I’ve got to come clean – I’m a sinner. I haven’t always created strong passwords. I reused them. I sometimes used the bloody dog’s name, dammit! But I’m reformed and am now a fervent advocate of the mantra that The only secure password is the one you can’t remember.

So let’s jump on over to the “change password” page and generate me a strong one with 1Password:

Oh dear, 10 characters. That’s it. Conventional wisdom – any wisdom – states that passwords should be long, random and unique, the more of each, the better (and please, don’t post that f****** XKCD horse battery comic as a counter-argument). So what’s going on at Tesco? Only 10? I’ll tell you what this says to me and it goes back to the password storage point earlier on: someone’s got themselves a varchar(10) under there somewhere and it’s all sitting in plain text. Of course I can only speculate, but the evidence does seem to suggest this on numerous fronts.

But you know the odd thing about this? There are 10 characters in both those password fields, in fact the value is “g'Szq\5tMk”. So why the problem? I’ve complied with the rule, haven’t I?

It takes a bit of sleuthing around the site to understand what has gone wrong:

Ah, letters and numbers only. Oh – and don’t bother about case, that just confuses things. It’s about now I’d usually get out the old soapbox and wax lyrical about the ease of brute forcing a password with these rules, but, well, you only tend to brute force a password when it’s protected to begin with.

Lack of case sensitivity is also another pointer to how passwords might be stored; what are the chances that the password column in the database is simply a non-case sensitive collation and there’s a query that does a direct comparison with the provided username and password? Certainly the password provided at logon is not being hashed and compared to the one in the database or that would fail the case sensitivity test (and we know this anyway because they’re emailing passwords), and we also know the provided password is not being encrypted then compared or that would also fail. In fact the only real possibility that leaves any credibility whatsoever is that the stored password is being decrypted then compared to the password provided at logon using a non-case sensitive comparer.

The other odd thing on the case front is that the password that was originally emailed to me was all uppercase. Now I know that’s not how I created it, so perhaps they’re just converting it to upper before storage? Or in other words, the fidelity you create in your original password entropy – even within the restrictive length and character type constraints – is lost as soon as the account is created.

But here’s what I really like about that page (incidentally, it’s shown during the registration process):

You can be 100% confident shopping with Tesco.com. To ensure your security online we will ask you for your password when you access your personal details or go to pay. This information will always be encrypted so that it is not possible for anyone else to access it.

Wow – 100% confident! Always encrypted! Liars.

Security misconfiguration

One of the things I spend a lot of time looking at through my work on ASafaWeb is security misconfiguration. This refers to those little settings available in modern day web stacks that can so easily go awry and start disclosing internal implementations which then leak information an attacker could potentially exploit.

An easy check for security misconfiguration is to see if a trace.axd handler is present. One of three things usually happens:

It’s present and it’s unsecured thus exposing all sorts of internal nasties. A branded, custom error page is returned politely apologising for the inconvenience (obviously the assumption is that you’ve landed there by accident) An internal server error is returned because although the trace handler can’t be displayed, the site is not configured so show friendly error messages. It looks exactly like this:

So in short, Tesco has a case of security misconfiguration which could well leak internal implementations of the code. Nice. In fact that infamous screen above is colloquially known as a YSOD, or Yellow Screen Of Death.

Very old web server and framework

You know what the problem with today’s web apps is? They talk too damn much. In fact Tesco’s talks so damn much it’s happy to tell you a whole lot about what’s running under the covers:

What you’re seeing here are the response headers returned along with the earlier YSOD. I’ve used Fiddler to inspect the headers and they reveal some telling things about the choice of technology:

They’re still running on IIS 6, now a 7 year old web server and twice superseded (actually, it will be thrice superseded in the very near future when Windows Server 2012 with IIS 8 launches) They’re still running ASP.NET 1.1. This one is very surprising – it’s now 9 years old and superseded by .NET 2, .NET 3, .NET 3.5, .NET 4 and almost .NET 4.5.

Now none of this is to say that these were bad technologies in the day, they weren’t, but it’s like saying that your 5.25 inch floppy disk is a good thing. It had a time and a place and both of those are now gone. The security landscape has changed significantly since these technologies where launched and ongoing improvements in newer generations of the breed make continued progress in ensuring a more secure app by default.

Don’t underestimate the value of keeping software components up to date, in fact OWASP specifically call this out in part 6 of their Top 10 Web Application Security Risks:

Do you have a process for keeping all your software up to date? This includes the OS, Web/App Server, DBMS, applications, and all code libraries.

This is not necessarily a high-intensity exercise, once every few years you simply make sure you haven’t fallen too far behind the eight ball. Certainly you don’t let key software components get 9 years old and nearly 5 versions out of date.

Unconscious incompetence

Ever heard of the four stages of competence? It starts off with unconscious incompetence:

The individual does not understand or know how to do something and does not necessarily recognise the deficit.

After tweeting about security deficiencies, Tesco amply demonstrated that in terms of web security, they’re firmly stuck in that first stage of competence:

I like the authoritative nature of this response and the software security prowess of the Customer Care account! The other statement that is always a red flag to me is “industry standard”; in my experience, this is the canonical response given by people who actually don’t know the mechanics of what they’re talking about (which of course is what you’d expect from a Customer Care department). It’s like “best practice” and whilst it looks good on a PowerPoint deck, once again, it is by no means evidence that you actually understand the execution of what you’re talking about.

Clearly this was not one to let go without a response:

And that’s when we got that zinger from earlier on:

In fact it’s such a zinger that it has become rather popular:

There’s probably a lesson in there somewhere about not letting your Customer Care folks make technical statements via social media…

But this has really been the theme the whole way through; Tesco continually overstate their security prowess whilst clearly under-delivering in their execution. Yes, this is a Customer Care account but as condescending as it may sound, this really is a case of unconscious incompetence not just by a semi-automated Twitter account pulling standard lines from the book, but by the people creating the web assets. And that’s inexcusable.

Lessons for developers everywhere

As I said right at the beginning, let’s take a constructive view of Tesco’s approach to web security and learn a few lessons along the way. I’m under no illusion that Tesco themselves will turn around and fix things in response to this post, they’ve known about these problems for long enough and obviously they’ve elected not to do anything about them.

So the lessons for developers:

Password storage should always be done using a strong hashing algorithm. IT should be one designed for password storage and also use a cryptographically random salt. It also must be a slow hashing algorithm – read Our password hashing has no clothes if this is a foreign concept. Password retrieval should never happen. Indeed it can’t if you’ve implemented the previous step correctly. Always implement a secure password reset process. Read Everything you ever wanted to know about building a secure password reset feature for some tips on this. Never mix HTTP content into your HTTPS pages. If HTTPS is important to you – and it should be – either explicitly refer to the HTTPS protocol in your references or even easier, use protocol relative URLs. There’s plenty of info in OWASP Top 10 for .NET developers part 9: Insufficient Transport Layer Protection. Always send authentication cookies over HTTPS. These are almost as valuable as the password itself; it gives anyone who holds them the rights to perform any tasks the user who originally authenticated to the system can. See the link in the previous point for more information. There should never be restrictions on password entropy. Don’t exclude special characters, don’t chop the length at a short, arbitrary limit (if you have to, make it 100 chars or so) and definitely don’t implement a system which is case-insensitive. See Who’s who of bad password practices – banks, airlines and more for more common mistakes. Ensure basic security configurations are correct. Tracing is off, custom errors are on, a default redirect page exists, debug mode is off, etc. This is obviously for ASP.NET, but there are parallels in other web stacks. Check your .NET apps with ASafaWeb.

And lastly, don’t let your ego write checks your body can’t cash. Padlock GIFs and statements on web pages mean absolutely nothing if what’s underneath has got holes all through it. In fact, just like that classic Twitter comment in the opening paragraph has shown, it makes things much, much worse as it demonstrates unconscious incompetence.

You know what the Tesco situation reminds me of? What I’ve written about above is very, very similar to what I wrote about Billabong a couple of weeks ago. Not necessarily the same vulnerabilities (although some of them are pretty close), but the rampant and readily observable failures in basic security practices. I wrote about Billabong after they’d been hacked and 21,000 account details published. I concluded the post by saying:

Now I’m not saying that any of the specific vulnerabilities identified above were at the root of this breach, but what I am saying is that they indicate Billabong clearly never took security seriously. It’s obvious that there’s a fundamental security process missing and if such glaringly obvious vulnerabilities are present – ones that can be observed from the browser alone – what else lies within? The site was a red flag – it made it crystal clear that a bit of probing would very, very likely turn up more serious flaws.

Now think about Tesco – what can we conclude about their risk profile? Let’s just say if you have a Tesco account, I’d make damn sure you haven’t reused that password anywhere else.

Update, 30 July: A reader directed me to this response from Tesco’s Customer Service Manager a couple of years back when the issue of website security was raised with them. Here’s the interesting bit:

I've had a word with my support team and asked them if they're stored with ‘one way encryption’ or any encryption and they say that although the information is not encrypted the level of security surrounding the password means that only the senior technical positions could access the information.

This is on Pastebin so take it with a grain of salt, but certainly the message is consistent with the level of understanding Tesco appears to have and passwords stored with no cryptographic means whatsoever would be consistent with what I’ve observed above.