Improving website security

Posted by Steve on Wed 22 Nov 2006 at 08:31

Recently this site was updated to avoid a potential security weakness. This article briefly describes the problem which was fixed, and explains some of the most common online security problems.


This article was inspired by recent comments from dkg about a potential security hole present in the code behind this website.

The hole described was new to me and I think it is worth sharing in the interests of full-disclosure, credit to Daniel Gillmor, and hopefully the securing of more sites.


Many web-developers (although not all!) are familiar with Cross Site Scripting attacks, often known as XSS, which are usually the result of not filtering input to applications.

A standard example would be a message-board, or forum, which allowed users to login and post messages which would be displayed literally - so a malicious user could create a message reading:


This would then result in an alert being displayed by subsequent visitors who viewed the message if they had javascript enabled.

The root cause of these security issues is trust. The implementor of the forum or message board trusted the users input and didn't sanitize it appropriately.

The XSS attacks exploit that weakness to steal cookies, etc.

(Once upon a time I wrote a simple online XSS demonstration/explaination, which might make this abstract discussion more interesting or obvious.)

SQL Injection

SQL injection is a problem specific to database-driven websites, especially those written in a hurry without the use of the appropriate language features.

Essentially SQL injections arise because input is passed directly to a database query without being escaped, or processed, correctly.

This is virtually identical to the XSS attack described above, just a different level of attack. (The remote-database rather than the local-client browser.)

Thankfully most programming languages and libraries which have database support make use of "binding" and other mechanisms which should make these issues trivial to prevent. The sad fact is that many programmers don't use them.


"Cross Site Request Forging" is the new name for a new problem, or one new to me at least.

The root cause of the CSRF attacks is also trust. The trust of a website by a user of that site. This trust is exhibited by the fact that the user never, or only rarely, logs out.

In a typical scenario the communications between a webserver and client browser looks like this:

  • Client makes a request to the server.
  • The server responds.
  • The client displays/uses the response.

For example to create a new weblog entry the user would browse to this site, click upon the "add entry" link in the sidebar and submit the resulting form.

Now consider what happens if a malicious external site were to copy the "add weblog entry" form and host it upon their site.

They could change the wording, hide the fields, etc. But ultimately there is nothing stopping them from hosting a modified version of the form - and serving it to users.

This is where the cross-site issue comes into play. If that remote site can coerce, persuade, or trick a user logged into this site to submit the form then the request will be processed by this site using their cached credentials.

Because they trust this site, and have remained logged in, the submission would be processed and the unsuspecting user would have a new weblog entry posted which they didn't explicitly write, or expect. For example "I like cheese."

Step by step the attack works like this:

  • External site copies a form from this one.
  • External site persuades/cajoles/tricks a user who remains logged in here to visit their site and submit the form.
    • Perhaps via javascript magic.
  • The server here receives the form submission and proceses it
    • Because it doesn't realise the form came from a malicious remote source.
    • And because the user was logged in any authentication tests succeed.
  • The user now arrives at this site with new content posted in their name.

The solution to this problem is potentially invasive but conceptually simple. Rather than serving forms to clients and then processing them without regard to the submission source the forms each include a "session token".

When a form is submitted this token is examined, and if it matches the token which was sent with the form the processing occurs, if it doesnt an alert is raised.

This utterly prevents the attack because it means that the malicious remote server cannot serve a valid form - it can't predict the secret form session token, so the submission will always fail.

(There are simpler solutions involving the use of the HTTP Referer (sic) header, but these are unreliable as this information might be forged or not present.)

It is worth noting that many sites fail to verify the submission of forms in this manner, so they are potentially at risk of malicious (ab)use of their forms.

Site Changes

With this discussion in mind I will now describe three changes I've made to this site:

Cookie Safety

Microsoft's Internet Explorer supports an extension to cookies called "httponly". The intention of this support is that the cookies served by a site will be marked as unavailable for scripting use.

This means that if there were an XSS attack available in this site then users of that browser wouldn't be vulnerable to cookie/session theft.

A bug was filed against Mozilla, but progress appears to have stalled (#178993).

I'm unaware of any XSS attacks lurking in the current codebase, so this is a small paranoia addition rather than a significant change.

(IE comprises about 9% of visitors to this site, certainly not the majority. However the change involved is sufficiently minimal that it is worth doing just to mitigate any future attacks.)

Session Safety

On a similar front it is now possible to choose a "secure" login when you visit this site. This actually ties your login session to your IP address.

Again this offers no increased security in the normal course of events, but if there were ever to be a security issue which resulted in a cookie or session theft due to XSS, or similar, then you would be protected if you chose this option.

There is a brief guide to this facility linked to from the login form.

Cross-Site Request Forgery Protection

This change is the most significant and is the one which caused me the most pain - as discussed above there is a potential weakness in the handling of many form submissions in online applications.

In the case of this current codebase I now insert a "session token" in each significant form, which will prevent the processing of rogue submissions rather than end-user page views.

This is a significant change, which appears to be working nicely, but as I'm all too aware there may be bugs. If you find them please do report them, either via mail or a site message.

I hope that was interesting reading both for regular visitors to this site, and for any potential web-application programmers.

If you'd like to disclose any security issues relating to this site I'm always prepared to listen and work with you. If you'd like to donate an SSL certificate for real secure logins that'd be a nice suprise too ;)



Posted by Anonymous (85.22.xx.xx) on Wed 22 Nov 2006 at 18:27
Just posted a comment on a Weblog and got trapped by a missing session token. This happened because I have cookies disabled by default. Thanks to Opera this is easily enabled for a specific site.
You should add a note that cookies have to be enabled to post a form.

[ Parent | Reply to this comment ]

Posted by Steve (62.30.xx.xx) on Wed 22 Nov 2006 at 18:35
[ View Steve's Scratchpad | View Weblogs ]

Thanks for the bug report. I'll have to see if I can make it work with cookies disabled as soon as possible.

Although to be honest I've been using session cookies since the site was created and I guess this is the first change which really makes them mandatory.


[ Parent | Reply to this comment ]

Posted by Anonymous (85.22.xx.xx) on Thu 23 Nov 2006 at 17:51
The cookie usage is OK. I just missed a hint to enable them.

[ Parent | Reply to this comment ]

Posted by Steve (62.30.xx.xx) on Thu 23 Nov 2006 at 22:11
[ View Steve's Scratchpad | View Weblogs ]

OK well I've addressed that part at least, by explaining that cookies are required on the error page.


[ Parent | Reply to this comment ]

Posted by Anonymous (137.222.xx.xx) on Thu 23 Nov 2006 at 17:26
Any chance of seeing some pseudocode example of how you actually implemented the magic numbers in the forms? It sounds like you store it in the session data, but a bit more of a description or some code would be nice.


[ Parent | Reply to this comment ]

Posted by Steve (80.68.xx.xx) on Thu 23 Nov 2006 at 17:37
[ View Steve's Scratchpad | View Weblogs ]

Well the whole code behind this site is available online, so if you're really curious you're welcome to take a look! Still to keep things simple I guess an explanation wouldn't go amiss.

The actual value which is added to the form doesn't really matter too much, so long as it is:

  • Simple to generate - so that at submission time the "real" value can be compared to the submitted one.
  • Isn't known to other users, or predictable by them.

With that in mind I'm just using the actual session ID each user gets when they visit the site, logged in or not each user gets a session when they visit the site, the ID of which is served as a cookie to browsers who accept them. (So to be explicit: No I'm not storing anything in the session for this - although that would be a good approach if the token was something else.)

Since the session identifier is known to the server for each user, and also something which isn't predictable, sequential, or known to anybody else (such as the outside server) I just use that.

The code actually uses an MD5 hash, but that isn't required.

So the process goes:

  • Generate the hash for a session for a new user.
  • When sending forms to them insert MD5(session ID)
  • When the form is accepted for processing check that there
    1. Is a token with the form - if it is missing we reject it.
    2. Test that MD5(sessionID) is equal to the token in the form submission
  • All done.

There are some things you can predict from this explanation - most obviously that every form submission from the same user will use the same token ID, since the session is identical. Whether this is a weakness or not remains to be seen, but generating a timeout, or salt for each form would be pretty trivial at this point.

The initial code change was included in this commit, although many more updates were required to fix bugs, protect additional areas of the site, and get things working neatly, cleanly and consistantly.


[ Parent | Reply to this comment ]

Posted by dkg (216.254.xx.xx) on Sat 25 Nov 2006 at 01:07
[ View dkg's Scratchpad | View Weblogs ]
Thanks for making these changes so promptly, Steve, and for this clear and concise writeup. I see you've even added the "form token" idea to the logout links, so malicious web servers can't automatically log a user out with a 302 redirect. This is a nice touch.

As for an SSL certificate, i'd imagine that enough of the audience of d-a would be fine with a self-signed cert (or a cert: It's simple enough these days to run your own mini-certificate-authority with tools like tinyca). I certainly would prefer to only use https for this site, even if i need to tell my browser to accept a cert once to do so.

Or, if you prefer an external certificate authority, you could use, which offers free TLS certificates, and has been included in the debian ca-certificates package, for what that's worth. (it's not automatically trusted by firefox/iceweasel yet, though, as far as i know).

Or if you want a TLS cert for signed by my CA, Steve, i'm happy to provide one: just send me a certreq gpg-signed with your debian key.

Note: this does not mean i think everyone should trust my personal CA. To someone who doesn't know me already, you have no more reason to trust me than you do to trust Verisign or CAcert. Writing this response actually prompted a longer rant about TLS, if anyone is interested.

[ Parent | Reply to this comment ]

Posted by Steve (62.30.xx.xx) on Sat 25 Nov 2006 at 15:45
[ View Steve's Scratchpad | View Weblogs ]

SSL certificates are things which I'm in two minds about, I've not seriously considered them previously, it was only the other day when I was informed that the "secure login" didn't work - because it didn't enforce HTTPS - that I started really thinking about it again.

I could probably use a self-signed, or local certificate agency to produce one for this site, but it always feels like that is just a cheap option. I'm incredibly disappointed that the Debian project as a whole seems to do that properly. (e.g. has a good certificate but alioth doesn't.)

I figure if it is worth doing it is worth doing properly, so I've previously ignored CACert as they are not trusted by most browsers by default and don't seem to do a lot of checking. I figure there is little gain in using them over using one of my own - although that does require that I be very careful in setting up the infrastructure and keeping things secure.

Still I guess there is nothing stopping me from making a certificate and running the login section over SSL for people who care enough. I can easily GPG-sign a certificate so that people who know about trust-paths, etc, can validate it. I just wonder if I'd be wasting my time.

One issue is that my feelings on the security of this site/content have changed a little since it started - now there are enough users and sufficient content that I feel much more strongly about protecting it. Initially it mattered less, (even though generally I care about security), so I was happy to leave some weaknesses if they weren't being obviously exploited.

Your rant was very interesting, (and remarkably similar to a pub conversation I had locally only last week!), I will almost certainly comment later in the day.

Thanks again for your report :)


[ Parent | Reply to this comment ]

Posted by dkg (216.254.xx.xx) on Sat 25 Nov 2006 at 18:08
[ View dkg's Scratchpad | View Weblogs ]
Steve wrote:
(e.g. has a good certificate but alioth doesn't.)
I was puzzled by this, and just went and checked it out. alioth's certificate is good, but it is signed by SPI's new Authority Certificate. The old one (which you've probably already configured your browser to trust) expires 2007-01-14, so SPI has done a very reasonable thing: they've issued a new CA certificate (months ahead of deadline!), and signed it by a well-known member (Joerg Jaspert). The chain of trust is kind of a convoluted one, but if you don't already have Joerg's key, you can fetch it from db.d.o, or install the debian-keyring package for a keyring signed by the release team. debian-keyring contains Joerg's key in /usr/share/keyrings/debian-keyring.gpg, i think. They should really get it signed by many more people than Joerg, though, and publish all the signatures.

If you then tell your browser to trust the new SPI CA cert, the new alioth cert should give you no warnings.

Thinking about how to properly update root certificates with the X.509 model makes my head hurt, though... That's another advantage of multi-sig certificates, i guess: it would would make this kind of transition easier.

[ Parent | Reply to this comment ]

Posted by dkg (216.254.xx.xx) on Sat 25 Nov 2006 at 19:41
[ View dkg's Scratchpad | View Weblogs ]
a note to folks who are into checking these sorts of things, or who want to do this themselves -- here's how i did it:

First, fetch the certificate and signature:

[0 dkg@squeak ~]$ wget -q ''
[0 dkg@squeak ~]$ wget -q ''
[0 dkg@squeak ~]$ 

make sure that the signed fingerprint matches the certificate itself:

[0 dkg@squeak ~]$ openssl x509 -fingerprint -sha1 -in spi-ca.crt -noout
SHA1 Fingerprint=D4:CB:C2:DE:8A:CE:1C:4E:4C:96:17:AA:DC:BD:9E:BA:FB:66 :2C:94
[0 dkg@squeak ~]$ grep ^SHA1 spi-ca.crt.fingerprint.txt
SHA1 Fingerprint=D4:CB:C2:DE:8A:CE:1C:4E:4C:96:17:AA:DC:BD:9E:BA:FB:66 :2C:94
[0 dkg@squeak ~]$ openssl x509 -fingerprint -md5 -in spi-ca.crt -noout
MD5 Fingerprint=3B:30:4A:04:E8:8D:AC:48:B4:5F:EF:D5:A8:07:9E:91
[0 dkg@squeak ~]$ grep ^MD5 spi-ca.crt.fingerprint.txt
MD5 Fingerprint=3B:30:4A:04:E8:8D:AC:48:B4:5F:EF:D5:A8:07:9E:91
[0 dkg@squeak ~]$ 
If you don't already have Joerg's key, you can fetch it and import it into your public keyring like this:
[0 dkg@squeak ~]$ wget -q -O- ' FBFA6D715ED6A07E7B8AC9' | gpg --import -a 
gpg: key 7E7B8AC9: public key "Joerg Jaspert <>" imported
gpg: Total number processed: 1
gpg:               imported: 1
gpg: 3 marginal(s) needed, 1 complete(s) needed, classic trust model
gpg: depth: 0  valid:   1  signed:  16  trust: 0-, 0q, 0n, 0m, 0f, 1u
gpg: depth: 1  valid:  16  signed:   9  trust: 10-, 0q, 0n, 5m, 1f, 0u
gpg: next trustdb check due at 2006-12-07
[0 dkg@squeak ~]$ 
and now make sure that the fingerprint itself was validly signed by Joerg:
[0 dkg@squeak cdtemp.s13367]$ gpg --verify spi-ca.crt.fingerprint.txt
gpg: Signature made Tue 10 Oct 2006 04:03:56 PM EDT using DSA key ID 7E7B8AC9
gpg: Good signature from "Joerg Jaspert <>"
gpg:                 aka "Joerg Jaspert <>"
gpg:                 aka "Joerg Jaspert <>"
gpg:                 aka "Joerg Jaspert <>"
Primary key fingerprint: DF7D EB2F DB28 FD2B A9FB  FA6D 715E D6A0 7E7B 8AC9
[0 dkg@squeak cdtemp.s13367]$ 
If you don't already have Joerg Jaspert in your web of trust, you might get a couple lines in the output of GPG like:
gpg: WARNING: This key is not certified with a trusted signature!
gpg:          There is no indication that the signature belongs to the owner.
If you see the same fingerprint as i've got above (and shown here), you know you've got the same key as i got. So now you only need to evaluate how likely it is that this key is really bound to the real Joerg Jaspert. If you think it is, and you trust Joerg to be responsible in signing the CA Certificate for Software in the Public Interest, then you are pretty safe in assuming that this is actually SPI's certificate.

Note: Joerg probably should have put more than just the fingerprints in the signature that he posted. As it currently stands, it doesn't really say anything, certainly not this is the SPI CA Cert, which is what i think it is supposed to say. I've written him about this.

[ Parent | Reply to this comment ]

Posted by Steve (62.30.xx.xx) on Sat 25 Nov 2006 at 21:19
[ View Steve's Scratchpad | View Weblogs ]

SSL certificate installed now.

I'll need to check that all our links are correct - and that I don't meta-refresh to a non-ssl version of any of the pages.

Still looking good so far!


[ Parent | Reply to this comment ]

Posted by dkg (216.254.xx.xx) on Sat 25 Nov 2006 at 21:50
[ View dkg's Scratchpad | View Weblogs ]
Thanks, Steve! can you post a GPG-signed statement about the certificate itself, too, so those of us who care to check these things can do so?

FWIW, the link rel="search" tag still points to a non-SSL URL, as does the "Debian Administration" link in the footer.

Also, when the javascript image-based ads are visible (on the articles pages), they aren't loaded from a secure server, which i think is what's causing those pages to give me the "broken lock". I don't know much about adsense, but can you change the javascript to request secure images instead?

[ Parent | Reply to this comment ]

Posted by Steve (62.30.xx.xx) on Sat 25 Nov 2006 at 21:58
[ View Steve's Scratchpad | View Weblogs ]

Thanks for the comments - I've fixed the footer and the search links to be relative.

As for a signature is this sufficient?.

I'm not sure about the javascript for the site adverts, but I will investigate. I think everything else looks relative.


[ Parent | Reply to this comment ]

Posted by dkg (216.254.xx.xx) on Sat 25 Nov 2006 at 22:29
[ View dkg's Scratchpad | View Weblogs ]
The signature checks out for me, thanks! i note that you set a 30 day expiration on the certificate, though -- is that really what you want? since no CA is being used, upgrading the cert is going to cause warnings for everyone using https. Yearly warnings (and rechecking of the signed certificate page) might be less hassle for everyone involved...

Here's how i verified the key:

[0 dkg@squeak ~]$ wget -q -O- '' | gpg --decrypt | openssl x509 -fingerprint -subject -out
gpg: Signature made Sat 25 Nov 2006 04:53:52 PM EST using DSA key ID CD4C0D9D
gpg: Good signature from "Steve Kemp <>"
gpg:                 aka "Steve Kemp <>"
gpg:                 aka "Steve Kemp <>"
SHA1 Fingerprint=5C:9A:5E:2C:84:30:7F:B9:A2:1D:0C:2B:8F:D8:E4:BE:B2:94 :C3:6F
subject= /C=GB/ST=Scotland/L=Edinburgh/O=Debian Administration/OU=Website/
[0 dkg@squeak ~]$ 
Since the subject had a Common Name (CN) of the hostname of the server, and Steve's signature was validated, i know that Steve is claiming this is the proper certificate.

I then i compared the fingerprint against the one reported by my browser when i visited the https: page.

[ Parent | Reply to this comment ]

Posted by Steve (62.30.xx.xx) on Sat 25 Nov 2006 at 22:38
[ View Steve's Scratchpad | View Weblogs ]


I thought I'd set it to expire in a years time. I've regenerated it, and resigned the certificate at the same link.

Sorry for the inconvienance.

Thanks for the verification recipe. I'll add a copy on the page in the morning. (Going out for the night now!)


[ Parent | Reply to this comment ]

Posted by dkg (216.254.xx.xx) on Sun 26 Nov 2006 at 01:31
[ View dkg's Scratchpad | View Weblogs ]
Yep, that looks like it's set for a year to me.

While i'm asking favors for the site, can we have an "i prefer SSL" user option? I'm thinking it would mean two things:

  • only accept authentication from this user over https (both cookies and passwords would be summarily rejected in the clear), and
  • when sending out e-mail notifications, specify https: in the URLs for easy clicking
Thanks for making all these updates, Steve.

[ Parent | Reply to this comment ]

Posted by Steve (62.30.xx.xx) on Sun 26 Nov 2006 at 13:04
[ View Steve's Scratchpad | View Weblogs ]

No worries Daniel, I've got a couple of changes planned along those lines already.

There is a problem though that you can't force SSL in an intuitive fashion - since you can't lookup a users preferences before they've logged in - by which point it is kinda too late.

At the least I'm going to rework the login form to link to the SSL page if not already present.


[ Parent | Reply to this comment ]

Posted by Steve (62.30.xx.xx) on Sun 26 Nov 2006 at 13:42
[ View Steve's Scratchpad | View Weblogs ]

OK the initial implementation is live now. It will give you two additional login choices:

  • Bind session to IP.
  • Mandate SSL

If you subsequantly access a page over non-SSL it will redirect to the secure version once the SSL option has been ticked.

I still need a "switch to ssl login" option somewhere for when that page is accessed over an insecure link, but I'll leave that until later.


[ Parent | Reply to this comment ]

Posted by dkg (216.254.xx.xx) on Sun 26 Nov 2006 at 15:29
[ View dkg's Scratchpad | View Weblogs ]
Thanks, i'm using Mandate SSL now!

One more thing i just noticed (sorry i'm not organized enough to note all of these things in one go) is that the cookies for aren't emitted with the secure flag. This means that if i log in over HTTPS, get a cookie, and then somehow click a link (or am redirected by another page) to the HTTP version of the site, my authentication token is sent in the clear.

With the secure flag (which modern browsers all support), the cookie will only be sent back if the connection is an HTTPS connection.

[ Parent | Reply to this comment ]

Posted by Steve (62.30.xx.xx) on Sun 26 Nov 2006 at 15:59
[ View Steve's Scratchpad | View Weblogs ]

Is the "secure" flag related to the HTTPOnly one that I discussed in this article? My google skills are failing me and I can't find a decent reference.

If it is just a matter of adding an attribute to the cookie when it is emitted that is a trivial change and I'd be happy to make it ... but a guide or documentation would be nice!


[ Parent | Reply to this comment ]

Posted by dkg (216.254.xx.xx) on Sun 26 Nov 2006 at 16:26
[ View dkg's Scratchpad | View Weblogs ]
secure is way older (and more widely implemented) than httponly (i hadn't heard about httponly until your article).

here's an old reference, and here's a reference to the PHP setcookie() function, which includes it as an option. And (more relevantly to yawns) here's a link to the CGI::Cookie perl module, which appears to have a -secure option.

btw, my e-mail alerts are still coming in with http:// protocol indicators, even though i have enabled Mandate SSL.

[ Parent | Reply to this comment ]

Posted by Steve (62.30.xx.xx) on Sun 26 Nov 2006 at 16:29
[ View Steve's Scratchpad | View Weblogs ]

Thanks, will implement later.

As for the mail changes they are still pending - since they tie in with a lot of the messaging-system updates I'm making, will take a week or so.


[ Parent | Reply to this comment ]

Posted by Eirik (129.177.xx.xx) on Wed 13 Dec 2006 at 12:10
Nice to see a casestudy in XSS/site-security like this. They are all too rare :-)

As for the certificate issue, I for one think self-signed sertificates are fine. I really don't trust Verisign or Tawte etc much at all, even if Microsoft or Mozilla does. As far as I'm concerned, CAs are good at binding certs to credit card numbers, but they're way too light on actually verifying who/what of a cert to actually add any value, at least the kind of value that they are charging.

As I see it, the pgp/gpg web-of-trust model is much better, an alternative would be government-issued certs (like passports, and most other proofs of id), or for corporatins, mabye banks and other institutions could issue signed certificates, linked in some way back to personal id (eg CEO, or Chief of Security, or some such person acutally physically goes to the Bank branch office, shows proof of ID, and proof of key ownership, and a statement showing right to ask for more certicates).

I don't see anything like that likely to happen in the near future though. Note that I can see the value of CAs for non-technical users, that don't know and care about how security actually works -- but like most technolgies that try to "give away" security without a learning curve, or real user involvement, it's mostly just an empty promise. In the end I'm not sure it really buys most users anything.

Finally a word of warning; I'm sure you're aware that SSL will put a higher load on your server, but you might not be aware of how much of a load it can be -- in case of a Slashdotting or other high-bandwidth event. Due to a mis-configuration at work, I inadvertedly tested this a couple of years back, and a load of around 100 hits/minute killed the https-part of a site, while the same hardware/software combination had no problem once links where reconfigured to use http (as they should have all along).

Also, I think some thought should go into dviding the site between http and https -- I see no real need for reading the site via https, only for posting, logging in etc, should ssl be needed. That's just my personal opinion, though.

[ Parent | Reply to this comment ]

Posted by Steve (62.30.xx.xx) on Mon 18 Dec 2006 at 17:03
[ View Steve's Scratchpad | View Weblogs ]

Thanks for the comments.. I'd agree that we don't need https for all operations, but after setting it up and making sure most things worked the extra effort involved in making the whole site work was fairly minimal.

It seems best to leave things working under both unless the load issue becomes too much of a concern, and right now thats not something that is causing me too much worry.

As for a web of trust for certificates I would like to see that working, but I can't see it. 99% of users don't care about email security, allowing them to choose to trust certificates seems like a recipe for diastaer - especially when you take phishing scams into account ..


[ Parent | Reply to this comment ]

Sign In







Current Poll

Will you stick to systemd as the default in Debian?

( 892 votes ~ 35 comments )