Paulo Hennig

Cyber Security Engineer

Cloud Security - AWS / Azure

Automation - Python / Shell Script

Code Review - Java / Javascript / PHP / Python

IaC - Cloudformation / Terraform

Red Team

Pentester

Paulo Hennig

Cyber Security Engineer

Cloud Security - AWS / Azure

Automation - Python / Shell Script

Code Review - Java / Javascript / PHP / Python

IaC - Cloudformation / Terraform

Red Team

Pentester

Blog Post

XSS through PHP_SELF

June 24, 2022 Uncategorized

As per https://www.php.net/manual/en/reserved.variables.server.php, we can interpret PHP_SELF as:

The filename of the currently executing script, relative to the document root. For instance, $_SERVER[‘PHP_SELF’] in a script at the address http://example.com/foo/bar.php would be /foo/bar.php. The __FILE__ constant contains the full path and filename of the current (i.e. included) file. If PHP is running as a command-line processor this variable contains the script name.

With the information above, it’s common to have such variable across different usages such as searches and web forms. However, depending on how you’re using it, it might be vulnerable to different cross-site scripting (XSS) vectors over a given app.

Scenario:

Please refer to the full function below, corresponding a PHP application (i.e., IPPlan v4.92b):

function Search() {

        unset($this->vars[$this->frmvar]);
        unset($this->vars["block"]);
        unset($this->vars["expr"]);
        //    $url=my_http_build_query($vars);

        // start form
        insert($this->w, $f = form(array("name"=>"SEARCH",
                        "method"=>$this->method,
                        "action"=>$_SERVER["PHP_SELF"])));

        insert($f, $con=container("fieldset",array("class"=>"fieldset")));
        insert($con, $legend=container("legend",array("class"=>"legend")));
        insert($legend, text($this->legend));
        if ($this->expr_disp) {
            $lst=array("START"=>my_("Starts with"),
                    "END"=>my_("Ends with"),
                    "LIKE"=>my_("Contains"),
                    "NLIKE"=>my_("Does not contain"),
                    "EXACT"=>my_("Equal to"));
            if (DBF_TYPE=="mysql" or DBF_TYPE=="maxsql" or DBF_TYPE=="postgres7") {
                $lst["RLIKE"]=my_("Regex contains");
            }
            // only supported by mysql
            if (DBF_TYPE=="mysql" or DBF_TYPE=="maxsql") {
                $lst["NRLIKE"]=my_("Does not regex contain");
            }
            insert($con,selectbox($lst, array("name"=>"expr"), $this->expr));
        }

        insert($con,input_text(array("name"=>$this->frmvar,
                        "value"=>$this->search,
                        "size"=>"20",
                        "maxlength"=>"80")));

        foreach ($this->vars as $key=>$value) {
            insert($con,hidden(array("name"=>"$key",
                            "value"=>"$value")));
        }

        insert($con,submit(array("value"=>my_("Submit"))));
        insert($con,block(" <a href='#' onclick='SEARCH.".$this->frmvar.".value=\"\"; SEARCH.submit();'>".my_("Reset Search")."</a>"));


    }

Due to the PHP action (i.e., “action”=>$_SERVER[“PHP_SELF”]), notice that we have no sanitization or validation and therefore vulnerable to XSS. That is, given the default behavior where PHP_SELF is copied into the value of an HTML tag, we can easily append any sort of HTML or Javascript injection within the query string.

For evidence, refer to the prints below where I’m accessing the mentioned application with and without our manipulation:

Regular
Triggering XSS

HTTP Method

Also notice that according to the code’s logic, we can also specify our HTTP method (e.g., GET, POST). And although it changes nothing for the XSS flow above, certain user input sanitizations and validations might be set for specific HTTP verbs only, so controlling such parameters can also have different consequences (e.g., The app may filter any user controlled parameter via GET where we can proceed with the same task over POST where we have no filters in place),

How to avoid it?

In order to avoid HTML or Javascript injections over PHP_SELF, we can simply use htlmentities() as it converts special characters to HTML entities and therefore it’s enough to mitigate such issue.

Finding it – Regex

Even though this can be easily spotted with a SAST solution, you can also run grep/rg recursively in order to spot any low-hanging fruit as the example below:

rg '\$_SERVER+.*PHP_SELF' | rg -v htmlspecialchars

References