Why Is $_SERVER['PHP_SELF'] Unsafe For Use

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    Why Is $_SERVER['PHP_SELF'] Unsafe For Use

    Quote:
    "A predominant PHP developer (whose name I didn't get permission to drop, so I won't, but many of you know who I mean) has been doing a bunch of research related to Cross Site Scripting (XSS), lately. It's really opened opened my eyes to how much I take user input for granted.

    Don't get me wrong. I write by the "never trust users" mantra. The issue, in this case, is something abusable that completely slipped under my radar.

    Most developers worth their paycheque, I'm sure, know the common rules of "never trust the user", such as "escape all user-supplied data on output," "always validate user input," and "don't rely on something not in your control to do so (ie. Javascript cannot be trusted)." "Don't output unescaped input" goes without saying, in most cases. Only a fool would "echo $_GET['param'];" (and we're all foolish sometimes, aren't we?).

    The problem that was demonstrated to me exploited something I considered to be safe. The filename portion of request URI. Now I know just how wrong I was.

    Consider this: you build a simple script; let's call it simple.php but that doesn't really matter. simple.php looks something like this:
    PHP Code:
    <html>
     <body>
      <?php
      
    if (isset($_REQUEST['submitted']) && $_REQUEST['submitted'] == '1') {
            echo 
    "Form submitted!";
      }
      
    ?>
      <form action="<?php echo $_SERVER['PHP_SELF']; ?>">
       <input type="hidden" name="submitted" value="1" />
       <input type="submit" value="Submit!" />
      </form>
     </body>
    </html>
    Alright. Let's put this script at:
    Code:
    http://example.com/tests/simple.php
    . On a properly-configured web server, you would expect the script to always render to this, on request:
    PHP Code:
    <html>
     <
    body>
      <
    form action="/tests/simple.php">
       <
    input type="hidden" name="submitted" value="1" />
       <
    input type="submit" value="Submit!" />
      </
    form>
     </
    body>
    </
    html
    Right? No.

    What I forgot about, as I suspect some of you have, too (or maybe I'm the only loser who didn't think of this (-; ), is that $_SERVER['PHP_SELF'] can be manipulated by the user.

    How's that? If I put a script at /simple/test.php, $_SERVER['PHP_SELF'] should always be "/simple/test.php", right?

    Wrong, again.

    See, there's a feature of Apache (I think it's Apache, anyway) that you may have used for things like short URLs, or to optimize your query-string-heavy website to make it search-engine friendly. $_SERVER['PATH_INFO']-based URLs.

    Quickly, this is when scripts are able to receive data in the GET string, but before the question mark that separates the file name from the parameters. In a URL like
    Code:
    http://www.example.com/download.php/path/to/file
    , download.php would be executed, and /path/to/file would (usually, depending on config) be available to the script via $_SERVER['PATH_INFO'].

    The quirk is that $_SERVER['PHP_SELF'] contains this extra data, opening up the door to potential attack. Even something as simple the code above is vulnerable to such exploits.

    Let's look at our simple.php script, again, but requested in a slightly different manner:
    Code:
    http://example.com/tests/simple.php/extra_data_here
    It would still "work"--the output, in this case, would be:
    PHP Code:
    <html>
     <
    body>
      <
    form action="/tests/simple.php/extra_data_here">
       <
    input type="hidden" name="submitted" value="1" />
       <
    input type="submit" value="Submit!" />
      </
    form>
     </
    body>
    </
    html
    I hope that the problem is now obvious. Consider:
    Code:
    http://example.com/tests/simple.php/%22%3E%3Cscript%3Ealert('xss')%3C/script%3E%3Cfoo
    The output suddenly becomes very alarming:
    PHP Code:
    <html>
     <
    body>
      <
    form action="/tests/simple.php/"><script>alert('xss')</script><foo">
       <input type="
    hidden" name="submitted" value="1" />
       <input type="
    submit" value="Submit!" />
      </form>
     </body>
    </html> 
    If you ignore the obviously-incorrect <foo"> tag, you'll see what's happening. The would-be attacker has successfully exploited a critical (if you consider XSS critical) flaw in your logic, and, by getting a user to click the link (even through a redirect script), he has executed the Javascript of his choice on your user's client (obviously, this requires the user to have Javascript enabled). My alert() example is non-malicious, but it's trivial to write similarly-invoked Javascript that changes the action of a form, or usurps cookies (and submits them in a hidden iframe, or through an image tag's URL, to a server that records this personal data).

    The solution should also be obvious. Convert the user-supplied data to entities. The code becomes:
    PHP Code:
    <html>
     <body>
      <?php
      
    if (isset($_REQUEST['submitted']) && $_REQUEST['submitted'] == '1') {
            echo 
    "Form submitted!";
      }
      
    ?>
      <form action="<?php echo htmlentities($_SERVER['PHP_SELF']); ?>">
       <input type="hidden" name="submitted" value="1" />
       <input type="submit" value="Submit!" />
      </form>
     </body>
    </html>
    And an attack, as above, would be rendered:
    PHP Code:
    <html>
     <
    body>
      <
    form action="/tests/simple.php/&quot;&gt;&lt;script&gt;alert('xss')&lt;/script&gt;&lt;foo">
       <
    input type="hidden" name="submitted" value="1" />
       <
    input type="submit" value="Submit!" />
      </
    form>
     </
    body>
    </
    html
    This still violates the assumption that the script name and path are the only data in $_SERVER['PHP_SELF'], but the payload has been neutralized.

    Needless to say, I felt silly for not thinking of such a simple exploit, earlier. As the aforementioned PHP developer said, at the time (to paraphrase): if guys who consider themselves experts in PHP development don't notice these things, there's little hope for the unwashed masses who have just written their first 'echo "hello world!\n";'. He's working on a generic user-input filtering mechanism that can be applied globally to all user input. Hopefully we'll see it in PECL, soon. Don't forget about the other data in $_SERVER, either.."
    Last edited by arnage; 19.10.11, 18:19.
    <!DOCTYPE html PUBLIC "-//WAPFORUM.RS

    #2
    Good Tutorials,But one important thing is the PECL Regular Expression,it helps alot

    Comment


      #3
      There are many important things depending on the case.
      In this one the point is that $_SERVER['PHP_SELF'] shouldn't be echoed without converting and the best is not to echo it at all.
      <!DOCTYPE html PUBLIC "-//WAPFORUM.RS

      Comment


        #4
        Originally posted by arnage View Post
        There are many important things depending on the case.
        In this one the point is that $_SERVER['PHP_SELF'] shouldn't be echoed without converting and the best is not to echo it at all.
        yeah .. you are right ;) thanx for explanation , manny of them are still using $_SERVER['PHP_SELF']; ,i'd rather think that this is an old style , of wroting php codes using $_SERVER['PHP_SELF']; i never used , PHP_SELF is also used on wapbuilders ... so read this and take care of your code / projects .. again thanx arnage
        This is ten percent luck, twenty percent skill
        Fifteen percent concentrated power of will
        Five percent pleasure, fifty percent pain

        And a hundred percent reason to remember the name!

        Comment


          #5
          You are welcome bro.
          <!DOCTYPE html PUBLIC "-//WAPFORUM.RS

          Comment


            #6
            kinda old thread.

            i know this is kinda an old thread, but was pointed to it by arnage. after some googling to see if there was other solutions
            i came across this post on php.about.com and thought it may be useful in "some" way to add here also.

            Quote From Original Poster
            By Angela Bradley, About.com Guide

            $_SERVER[’PHP_SELF’], $_SERVER['REQUEST_URI'], and $_SERVER[’SCRIPT_NAME’] all behave in similar ways, they return information about what file is being used. When exposed to some differnt scenarios, you can see in some cases they do behave differently. This can help you decide which is best for what you need in your script.

            $_SERVER[’PHP_SELF’]
            http://www.yoursite.com/example/ -- -- -- /example/index.php
            http://www.yoursite.com/example/index.php -- -- -- /example/index.php
            http://www.yoursite.com/example/index.php?a=test -- -- -- /example/index.php
            http://www.yoursite.com/example/index.php/dir/test -- -- -- /dir/test

            When we use $_SERVER[’PHP_SELF’] we have the file name /example/index.php returned to us both when we did and did not actully type it in the URL. When we appended variables to the end of it, they where truncated and again /example/index.php was returned. The only one that produced a different result was when we appended directories after the file name. In that case, it returned those directories.

            $_SERVER['REQUEST_URI']
            http://www.yoursite.com/example/ -- -- -- /
            http://www.yoursite.com/example/index.php -- -- -- /example/index.php
            http://www.yoursite.com/example/index.php?a=test -- -- -- /example/index.php?a=test
            http://www.yoursite.com/example/index.php/dir/test -- -- -- /example/index.php/dir/test

            In all of our examples, this returned exactly what we entered for the URL. It returned a plain /, the file name, the variables, and the appended directories, all just as they had been entered.

            $_SERVER[’SCRIPT_NAME’]
            http://www.yoursite.com/example/ -- -- -- /example/index.php
            http://www.yoursite.com/example/index.php -- -- -- /example/index.php
            http://www.yoursite.com/example/index.php?a=test -- -- -- /example/index.php
            http://www.yoursite.com/example/index.php/dir/test -- -- -- /example/index.php

            In all cases here we were returned only the file name /example/index.php regardless of if it was typed, not typed, or anything was appended to it.


            hope this was useful in some way. - (Ghost)

            ///////////////////////////////////////////////////////////////////////////////////////////
            //////////////////////////////////////////////////////////////////////////////////////////
            ok noticed some suttle differences after posting this.

            ok how do i explain this. in arnages explaination you see $_SERVER['PHP_SELF'] < here it uses the normal single quote > ' <
            but in this quoted post you see $_SERVER[’PHP_SELF’] < here it uses a kinda different single quote > ’ <
            youll notice this is the same for $_SERVER[’SCRIPT_NAME’] but "not" for $_SERVER['REQUEST_URI']

            now when trying to use the normal single quote in my link like. > ' <
            PHP Code:
            if ($npage <= $npages AND $npage>1$gline_rew "<a href=\"$_SERVER['PHP_SELF']?npage=".($npage-1)."\">Prev</a>"
            it returns an error that the php self isnt valid.

            but when using the quoted post version of it. > ’ <
            PHP Code:
            if ($npage <= $npages AND $npage>1$gline_rew "<a href=\"$_SERVER[’PHP_SELF’]?npage=".($npage-1)."\">Prev</a>"
            the link is validated php and works as planned.

            i do prefere to want to use
            PHP Code:
            if ($npage <= $npages AND $npage>1$gline_rew "<a href=\"$_SERVER[’SCRIPT_NAME’]?npage=".($npage-1)."\">Prev</a>"
            but cant understand the difference in the single quotes, and why the normal single quote wont work,
            while the abnormal one does work in my demonstrated links.

            anyone clear this up a little for me?

            P.S i do know that getting the original single quote > ' < to work you'd do.. ".$_SERVER['PHP_SELF']." instead.
            but im curious why the abnormal quote would work without undergoing this process.
            Last edited by Ghost; 30.05.12, 07:13. Reason: notcied some slight changes.
            <?php
            include ('Ghost');
            if ($Post == true) {
            echo '

            sigpic
            alt='coding-talk.com!!' />';
            echo 'Sharing Is Caring!';
            } else {
            echo '

            alt='the username GHOST has been comprimised!' />';
            echo 'OMG SOMEBODY HELP ME!!';
            }
            ?>

            Comment


              #7
              Most probably because its double quoted at beginning and without dots. It should be:

              PHP Code:
              if ($npage <= $npages AND $npage>1$gline_rew '<a href="'.$_SERVER['PHP_SELF'].'?npage='.($npage-1).'">Prev</a>'
              So, quotes problem.
              <!DOCTYPE html PUBLIC "-//WAPFORUM.RS

              Comment


                #8
                felt this needed editing lol.

                Originally posted by arnage View Post
                Most probably because its double quoted at beginning and without dots. It should be:

                PHP Code:
                if ($npage <= $npages AND $npage>1$gline_rew '<a href="'.$_SERVER['PHP_SELF'].'?npage='.($npage-1).'">Prev</a>'
                So, quotes problem.
                not quite what i ment lol but thanks for pointing out i accidently showed double quote instead of single.

                what i was actually getting at is there seems to be 2 different php_self

                one looks like 'PHP_SELF' <<< this shows "proper" single quotes
                and other looks like ’PHP_SELF’ <<< this shows an abnormal "slanted to one side" single quotes.

                while $_SERVER[’PHP_SELF’] (abnormal) works "fine" in url addy. (without '. .' )
                this $_SERVER['PHP_SELF'] (proper) will not work unless '. .' is added,

                i was just curious why this was. i have tested this and the abnormal ’ seems to do something
                the proper ' cant. :s i just couldnt seem to find any reference to this ’ abnormal quote
                its defo a different single quote its not just my eyes playing games as i have tested both results out.

                the only way to discribe this abnormal ’ is to say it looks like a , that floats..
                kinda looks like the ` you would see around some mysql quotes like.
                INSERT INTO `smilies` VALUES (1, '-aggressive-', 'smilies/aggressive.gif');
                but in the php_self it points forwards instead

                anyway its defo not right. but sorry for rambling on like a mad man..

                /////////////////////////////////////////////
                ok may of found my answer, but not sure, supposidly these different looking quotes are used in X++ (whatever)

                but its strange how yet again today i found another piece of code using these symbols

                str s = ‘<MyTag attr="hello">’; ‘ and ’ are not regular quotes but seems to be being used by blog
                writers in their examples. was just weird i found these symbols in a server php_self / server script name tags.

                doesnt really matter was just curious, i know curiousity killed the cat an all, but its never hurt me yet lol.
                Last edited by Ghost; 30.05.12, 21:41.
                <?php
                include ('Ghost');
                if ($Post == true) {
                echo '

                sigpic
                alt='coding-talk.com!!' />';
                echo 'Sharing Is Caring!';
                } else {
                echo '

                alt='the username GHOST has been comprimised!' />';
                echo 'OMG SOMEBODY HELP ME!!';
                }
                ?>

                Comment


                  #9
                  As it turns out, '.$_SERVER[’PHP_SELF’].' looks like php skips it like its not there, i have disabled warnings so don't know for that, and '$_SERVER[’PHP_SELF’]' coses the error: PHP Parse error: syntax error, unexpected T_VARIABLE, expecting ',' or ';' echoed with single quotes.

                  If ’ is echoed into browser in some snippet or code it is most likely just for particular presented example and its not for copy-paste use.
                  <!DOCTYPE html PUBLIC "-//WAPFORUM.RS

                  Comment


                    #10
                    Originally posted by arnage View Post
                    As it turns out, '.$_SERVER[’PHP_SELF’].' looks like php skips it like its not there, i have disabled warnings so don't know for that, and '$_SERVER[’PHP_SELF’]' coses the error: PHP Parse error: syntax error, unexpected T_VARIABLE, expecting ',' or ';' echoed with single quotes.

                    If ’ is echoed into browser in some snippet or code it is most likely just for particular presented example and its not for copy-paste use.
                    i thought that may be it. but i had to make sure. strange though seeing it in a few posts today lol.

                    anyway i decided to go a different way from php self.

                    and used
                    PHP Code:
                      /** here we return only the file name users.php using $_SERVER['SCRIPT_NAME'] regardless
                          of if it was typed, not typed, or anything was appended to it. And Protect it Using htmlentites */
                    if ($npage <= $npages AND $npage>1$gline_rew '<a href="'.htmlentities($_SERVER['SCRIPT_NAME']).'?npage='.($npage-1).'">Prev</a>';
                    if (
                    $npages AND $npage<$npages$gline_next '<a href="'.htmlentities($_SERVER['SCRIPT_NAME']).'?npage='.($npage+1).'">Next</a>'
                    does same as php self. but will only give the file name back no matter what you do with url.

                    so could put users.php/folder/file.php and still get users.php shown. and of course htmlentities to help things.
                    <?php
                    include ('Ghost');
                    if ($Post == true) {
                    echo '

                    sigpic
                    alt='coding-talk.com!!' />';
                    echo 'Sharing Is Caring!';
                    } else {
                    echo '

                    alt='the username GHOST has been comprimised!' />';
                    echo 'OMG SOMEBODY HELP ME!!';
                    }
                    ?>

                    Comment


                      #11
                      Why not just use dynamic paths in href? Maybe you can use ./ ../ / instead.
                      <!DOCTYPE html PUBLIC "-//WAPFORUM.RS

                      Comment


                        #12
                        Originally posted by arnage View Post
                        Why not just use dynamic paths in href? Maybe you can use ./ ../ / instead.
                        its basically only for my smileys / avatars / shout history / guestbook.

                        example
                        PHP Code:
                        <?PHP
                        session_start
                        ();
                        header("Content-type: text/html; charset=UTF-8");

                           echo 
                        "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>";
                                  echo 
                        "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" 
                             \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"
                        ;

                                 
                        /**
                                   * @Author Ghost (Xhtml Creator, Wml Major Updater) - (Fully Taken Over)
                                   * @PreviousAuthor Deviance Aka Wappy - (Retired Wml Creator/Updater) - (Gone)
                                   * @Copyright 2012 - Mobile Chat Xhtml
                                   * @Version 1.0
                                   * @Description Flat File Xhtml Chat With Sessions And Encrypted Passwords
                                   * @Description (Should Be Working On All Servers)
                                   * @HTMLVersion Xhtml 1.0 Transitional EN
                                   */

                        echo "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\">\n";

                        include(
                        "../config/config.php");

                        if (!
                        $_SESSION["valid_user"])
                        {
                                
                        /** If User Sessions Not Successful, Redirect to index.php */
                          
                        echo "<head>";
                            echo 
                        "<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\" />";
                             echo 
                        "<meta http-equiv=\"Refresh\" content=\"0; url=../index.php\" />";
                           echo 
                        "<title>Redirecting</title>";
                         echo 
                        "<link rel=\"stylesheet\" type=\"text/css\" href=\"../stylesheets/stylesheet.css\" media=\"handheld,screen,projection\" />";
                        echo 
                        "</head>";
                                  echo 
                        "<body class=\"body\">";

                        echo 
                        "<div>";
                        //echo "System:<br/>---<br/><b>*</b>You are NOT logged in! please hit the login button<br/>";
                        //echo "if this matter persist please email one of the staff<br/>";
                        //echo "and we will try to sort the problem asap thanx :-)<br/>";
                        //echo "<a href=\"../index.php\">« Home page</a>";
                        //echo "<br/>---<br/>$copyright";
                        echo "</div>";
                              echo 
                        "</body>\n";
                           echo 
                        "</html>\n";
                        }
                        else
                        {      
                        /** If Users Sessions Successful, Show page */
                            
                        $nimi $_SESSION["valid_user"];

                          echo 
                        "<head>";
                            echo 
                        "<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\" />";
                             echo 
                        "<meta http-equiv=\"Cache-Control\" content=\"no-cache\"/>";
                           echo 
                        "<title>Smileys</title>";
                         echo 
                        "<link rel=\"stylesheet\" type=\"text/css\" href=\"../stylesheets/stylesheet.css\" media=\"handheld,screen,projection\" />";
                        echo 
                        "</head>";
                            echo 
                        "<body class=\"body\">";
                        echo 
                        "<div>";

                        $npage $_GET['npage'];
                        $p 5;
                        $xfile=file('../text/smileys.txt');
                        $countfile=count($xfile);
                        if (
                        $npage == "")$npage "1";
                        echo 
                        "Smileys: ".$countfile."<br/>---<br/>\n";

                        $second = ($p $npage);
                        $first = ($p * ($npage-1));
                        $npages =(int) ceil($countfile $p);

                          
                        /** here we return only the file name chatsmileys2.php using $_SERVER['SCRIPT_NAME'] regardless
                              of if it was typed, not typed, or anything was appended to it. And Protect it Using htmlentites */
                        if ($npage <= $npages AND $npage>1$gline_rew '<a href="'.htmlentities($_SERVER['SCRIPT_NAME']).'?npage='.($npage-1).'">Prev</a>';
                        if (
                        $npages AND $npage<$npages$gline_next '<a href="'.htmlentities($_SERVER['SCRIPT_NAME']).'?npage='.($npage+1).'">Next</a>';

                        for (
                        $i $first$i <= $second-1$i++)

                        $a=explode("",$xfile[$i]);
                        $smiley rtrim($a[0]);
                        echo 
                        "<img src=\"../smileys/$smiley.gif\" alt=\"-$smiley-\"/>";
                        echo 
                        "-$smiley-<br/>";
                        }
                        print 
                        " ".$gline_rew." | ".$gline_next."<br/>";
                        print 
                        "Page $npage of $npages";
                        {
                        echo 
                        "<br/><a href=\"chat.php\">Chatroom</a><br/>";
                        }
                        echo 
                        "---<br/>$copyright";
                        echo 
                        "</div>";
                            echo 
                        "</body>\n";
                        echo 
                        "</html>\n";
                        }
                        ?>
                        which inside uses arrays to display all the stuff. pretty sure npages would be best route.
                        but been away for a long time things have changed.
                        <?php
                        include ('Ghost');
                        if ($Post == true) {
                        echo '

                        sigpic
                        alt='coding-talk.com!!' />';
                        echo 'Sharing Is Caring!';
                        } else {
                        echo '

                        alt='the username GHOST has been comprimised!' />';
                        echo 'OMG SOMEBODY HELP ME!!';
                        }
                        ?>

                        Comment

                        Working...
                        X