Thursday, 28 August 2014

Resizing ASP.Net images with ImageResizer on the server-side

Very common scenario, you want to upload images and then resize them. I use ImageResizer from http://imageresizing.net, which is dead easy to use and has most options you would probably want (like setting max size or image format).

I want to describe, however, the entire journey from browser to server, because it's easy just to add the functionality and not to add security and checking to ensure that an attacker is not uploading an executable instead of an image. In some cases, this wouldn't matter, in others, you risk a malware attack on your servers.

1. Use a FileUpload control to upload your images. In order to change its appearance to have to resort to various tricks (basically make it invisible and use other controls to populate it and fire the event!). You can, and should, use a regularexpressionvalidator control to ensure that only files of the correct type are uploaded. This is only client side but provides quick feedback to the user if they try and upload something stupid. The regex I use is "^.+\.((jpg)|(JPG)|(jpeg)|(JPEG)|(png)|(PNG)|(BMP)|(bmp))$"
2. Because client validation can be bypassed and files can be renamed, you should provide additional controls on the server-side to ensure the contents of the file are correct, as far as you can. Firstly, I check that fileUpload.HasFile, something which can easily be missed and cause a crash! I then check the fileUpload.PostedFile.ContentType and ensure it starts with "image" or it is "application/octet-stream". If it is octet-stream, I also re-check the file extension is valid.
3. I then check that fileUpload.PostedFile.ContentLength is not greater than a certain amount (in bytes) to stop people uploading very large images to crash my server.
4. I then go one step further and check the uploaded file header to make sure it is what matches a bitmap, jpeg or png. This is a utility function that checks the first few bytes of the file to check for a relevant header. These look like this in my code:

var jpeg = new byte[] { Convert.ToByte("0xFF", 16), Convert.ToByte("0xD8", 16), Convert.ToByte("0xFF", 16) };
var png = new byte[] { Convert.ToByte("0x89", 16), Convert.ToByte("0x50", 16), Convert.ToByte("0x4E", 16), Convert.ToByte("0x47", 16), Convert.ToByte("0x0D", 16), Convert.ToByte("0x0A", 16), Convert.ToByte("0x1A", 16), Convert.ToByte("0x0A", 16) };
var bmp = new byte[] { Convert.ToByte("0x42", 16), Convert.ToByte("0x4D", 16) };

Using these variables and the uploaded file extension, I decide whether the file really is what it claims to be using something like this:

if ((inputFile.Take(png.Length).SequenceEqual(png) && fileName.EndsWith(".png", StringComparison.OrdinalIgnoreCase))
|| // jpeg etc...

5. I now need to obtain an ImageFormat value for the relevant file so I created a simple if/else function to map the file extension onto the ImageFormat enumeration.
6. Create an output memory stream with using() to make sure it is disposed afterwards.
7. I then avoid resizing images that are already a reasonable size (in my case 100K or less) just to reduce burden on the server. Otherwise I call a helper function to resize the image with a maximum width of 1280 and max height of 720.
8. In my case, I do some trickery to see whether somebody has renamed a png to a jpeg or vice-versa by resizing it to both formats and seeing which is smaller. This also means bitmaps will end up compressed in one format or another. Ignoring that little trick, the resizing is simply:

ImageResizer.ImageBuilder.Current.Build(fileUpload.FileContent, outputMemoryStream, new ImageResizer.ResizeSettings("maxwidth=1280&maxheight=720&format=png"), false);

The false is so that the operation does NOT dispose the original bitmap, because I still need to use it. There are plenty of other resize options here.

9. Make sure you finish using your image before leaving the using statement for the memory stream, otherwise something will go bang.

Simples!

Wednesday, 20 August 2014

Chrome not displaying date in html 5 "date" input

While stumbling through another MVC 4 app with Visual Studio, I noticed that despite all things appearing correct with my data binding, when clicking edit on a model with date fields, the dates were not displayed by Chrome although they were displayed by FireFox and the values were correctly set in the input control.

It turns out that Chrome only likes ISO style dates (e.g. 2014-08-19) and if they are not in that format, they are both not displayed and the original values are not even posted back so you get validation errors!

By adding the DisplayFormat attribute to the date fields and setting it in ISO format, it all worked again.

Note that the DisplayFormat is a generic attribute, it is not just for dates. This means the format has to match String.Format and include curly brackets like this:

[DisplayFormat(DataFormatString="{0:yyyy-MM-dd}", ApplyFormatInEditMode=true)]

Tuesday, 19 August 2014

How to spot a phishing email

Not a day seems to go by without reading about another hack and most of these are either initiated by some kind of injection of bad data into a web application and the rest are mostly originated by phishing emails. The most serious hacks are usually initiated by phishing since phishing will often get you inside a company in a way that web applications usually can't - this means the attacker potentially has access to everything that an employee has access to. My own experience is that this is usually far more than they should but anyway....

A phishing email is an email that intends to create a means to steal information. It does this by producing some trust with the person reading the email and then asking them to do something, either typing in some login credentials, which the attacker can then steal or perhaps run a program which will install a virus onto the local machine which allows the attacker remote access.

In order to create trust, good phishing emails need to look legitimate. This might include one or more of the following:

  1. A from address that looks believable (but possibly a close copy of a real address) like info@micosoft.com. It could also appear to come from one of your friends or contacts due to a virus on someone else's machine. The from address can be manually set by an attacker to anything they want, although most modern email scanners will detect this.
  2. Some official looking logos. Remember, any image that appears on a web site can be copied and used by an attacker. Just because it has a Microsoft logo, doesn't mean it is from microsoft.
  3. Some kind of urgency or warning to make you do something. Phrases like, "Account validation required", "Illegal access detected, please confirm your details" or "You account will be locked if you fail to take action". Don't be scared by these phrases. I have never seen a proper system that uses only emails to warn of such drastic actions. Any good site, like your bank, will tell you to visit the bank website and carry out any actions that are required.
  4. Hyperlinks that look correct but not be. A hyperlink, like the following example, can say one thing but point to a different site. Hover over this link and notice that the actual destination does not match the text: http://www.microsoft.com (your browser might block the link!)
  5. Some kind of attachment to open. Note that what it appears to be might not be what it is. A classic example is a program with an icon that looks like a pdf. When you open it, the program runs, installs a virus and then opens a pdf to make it look like it is just a simple attachment. Some attachments will be blocked by email readers by default and some are more dangerous than others. In general office documents and PDFs are most dangerous because they can contain code that is run when they are opened.
  6. Suitably abstract language that is needed when sending emails to lots of people. Rather than Dear Luke, it might say, Dear Colleague. "Your IT department has requested that..", "Your manager requires you to...." when these are translated from other languages, these might sound quite funny, "Your account is will locked you not obey instructions".
  7. Some kind of invitation to be curious. How many of you have clicked on a Facebook link because, "You will never believe what happens..." or "This is the funniest thing I've seen all week..."?
It can be very hard to spot the best types of phishing emails - they can look very convincing. But the following suggestions should make it all but impossible for the phisher to succeed.
  1. IT departments should plan to keep operating systems up to date with the latest updates. Many of these are created to block holes in the operating system, some of which can be exploited by phishing. Do not continue to use very old operating systems, again, plan long term update policies even if it means new PCs come with new operating systems so replacements happen automatically over time. Older Windows operating systems allow programs to run, sometimes without you even knowing.
  2. Use a modern email client or a web-based email client. Modern clients have better security features in general.
  3. Ensure you have a modern virus checker on your email system (either on your mail server if you have one, or on your local email client). Keep it up to date! Online mail tends to include this built-in but check that you are not just assuming that.
  4. Try and use SPAM filters and any other kind of automatic detection for bad emails. Many new systems have a range of measures to detect suspicious emails, although these are not perfect - sometimes they create false negatives and false positives.
  5. Set up a culture and training in your organisations, both to spot bad emails but also to avoid sending out emails that look like phishing. If you want your employees to do something involving clicking links or updating details, communicate it verbally or instruct the user in the email to visit a site they should already have access to rather than sending a link.
  6. Be suspicious of any emails with strange requests and check them out before obeying the instructions. As far as I know, NO proper company would send out an email with embedded content or hyperlinks to ask you for personal details, including login details. They would also not send out attachments for you to open.
  7. Do not open any "fun" stuff at work, whoever it appears to come from.
  8. Never, ever, ever allow a program to run from an email attachment, if it asks you for permission, unless you are 100% that the email is expected and was sent from a person you were expecting it from.

Monday, 18 August 2014

The name does not exist in the current context - Master Pages

I was having a really annoying problem trying to compile my Visual Studio solution with random errors about two controls that were definitely on the master page. They were referenced in the .cs file but every now and then, I would get, "The name .... does not exist in the current context". I would comment out the affected lines and it would build fine and then at a later date, uncomment them and they were still fine.

It turns out, I had made a very sloppy copy-and-paste error when creating a second master page and instead of referencing the correct CodeBehind file, I was referencing the code behind for the original master page in both .Master files.

The effect was that the second file had no Designer.cs file created for it and therefore the compilation was failing. Apparently, in some scenarios, it did find what it was looking for, I'm guessing in the original designer file and then it would build OK. Every change to the master page made it all fail again.

Anyway, I had to change the CodeBehind attribute to be correct, right-click the second master page and choose "Convert to web application" so that it would generate a designer.cs file and then all the problems went away!

Wednesday, 13 August 2014

Do NOT use guids for secure object identifiers

A common problem for web application developers is how to refer to things securely. If I want a user to access, for instance, a holiday booking, I usually need to pass an argument in the url, something like booking=123456.

The basic problem with direct object reference is that the user can easily change the number to something else and then what happens? Hotel Hippo should have known that this is security 101 and the usual answer is to make sure the user is logged in and then check that the user has access to the booking they are trying to access. This basic check makes sure that the permissions are correct but there are places where the user is not and cannot be logged in to perform this check.

Imagine a password reset email. You send a link and it contains something like http://mysite.com/passwordreset/userid=123456, which will be clicked on to reset this user. The same problem occurs here. What happens if I change the number? I can access the reset page for another user which might expose private data like a security question to me. In this case, the user is resetting their password and cannot login yet so you can't actually check they have permission to do this.

What do we do? We use a nonce - a Number used ONCE - to add to the URL the combination of this and the userid means that we cannot just change the userid to another id - it won't match the nonce that we stored in the database and therefore won't validate. All good so far but where do we get the number from?

We need a 'number' or code that is statistically hard to guess, although it does not have to be cryptographically secure. The number is valid for a relatively short period after which it is deleted. There are never more than a handful of these at any point in time so being able to guess a userid/nonce combination is as good as impossible.

When I first did this, I thought, "guid". globally unique, relatively long and easy to generate in SQL or code. I would remove the dashes so it is just a hex number and we're all good right? Nope. The problem with a guid is that although it looks random, it is not actually random. It is a combination of data, time and the mac address of the system it is generated on, which is why it is globally unique but also why it is definitely not secure or statistically hard to guess. Generate one on a system and you have a very small subset of values that you know will be used in the future. In other words, your attack entropy is very small, MUCH smaller than a genuinely randomly created nonce.

There are various ways to create secure numbers for this purpose (rand or mt_rand in php, SecureRandom in .Net etc.) and these should be used, NOT guids. In some cases, there are not even two parameters, just one object reference, one guid - do not assume they cannot be guessed. You have been warned.