{"id":693,"date":"2022-06-24T14:51:34","date_gmt":"2022-06-24T14:51:34","guid":{"rendered":"https:\/\/devbrain.com.br\/?p=693"},"modified":"2022-06-24T14:51:34","modified_gmt":"2022-06-24T14:51:34","slug":"xss-through-php_self","status":"publish","type":"post","link":"https:\/\/devbrain.com.br\/index.php\/2022\/06\/24\/xss-through-php_self\/","title":{"rendered":"XSS through PHP_SELF"},"content":{"rendered":"\n<p>As per https:\/\/www.php.net\/manual\/en\/reserved.variables.server.php, we can interpret PHP_SELF as:<\/p>\n\n\n\n<p><em>The filename of the currently executing script, relative to the document root. For instance, <var>$_SERVER[&#8216;PHP_SELF&#8217;]<\/var> in a script at the address <var>http:\/\/example.com\/foo\/bar.php<\/var> would be <var>\/foo\/bar.php<\/var>. The <a href=\"https:\/\/www.php.net\/manual\/en\/language.constants.predefined.php\">__FILE__<\/a> 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.<\/em><\/p>\n\n\n\n<p>With the information above, it&#8217;s common to have such variable across different usages such as searches and web forms. However, depending on how you&#8217;re using it, it might be vulnerable to different cross-site scripting (XSS) vectors over a given app.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Scenario:<\/h2>\n\n\n\n<p>Please refer to the full function below, corresponding a PHP application (i.e., IPPlan v4.92b):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>function Search() {\n\n        unset($this-&gt;vars&#91;$this-&gt;frmvar]);\n        unset($this-&gt;vars&#91;\"block\"]);\n        unset($this-&gt;vars&#91;\"expr\"]);\n        \/\/    $url=my_http_build_query($vars);\n\n        \/\/ start form\n        insert($this-&gt;w, $f = form(array(\"name\"=&gt;\"SEARCH\",\n                        \"method\"=&gt;$this-&gt;method,\n                        \"action\"=&gt;$_SERVER&#91;\"PHP_SELF\"])));\n\n        insert($f, $con=container(\"fieldset\",array(\"class\"=&gt;\"fieldset\")));\n        insert($con, $legend=container(\"legend\",array(\"class\"=&gt;\"legend\")));\n        insert($legend, text($this-&gt;legend));\n        if ($this-&gt;expr_disp) {\n            $lst=array(\"START\"=&gt;my_(\"Starts with\"),\n                    \"END\"=&gt;my_(\"Ends with\"),\n                    \"LIKE\"=&gt;my_(\"Contains\"),\n                    \"NLIKE\"=&gt;my_(\"Does not contain\"),\n                    \"EXACT\"=&gt;my_(\"Equal to\"));\n            if (DBF_TYPE==\"mysql\" or DBF_TYPE==\"maxsql\" or DBF_TYPE==\"postgres7\") {\n                $lst&#91;\"RLIKE\"]=my_(\"Regex contains\");\n            }\n            \/\/ only supported by mysql\n            if (DBF_TYPE==\"mysql\" or DBF_TYPE==\"maxsql\") {\n                $lst&#91;\"NRLIKE\"]=my_(\"Does not regex contain\");\n            }\n            insert($con,selectbox($lst, array(\"name\"=&gt;\"expr\"), $this-&gt;expr));\n        }\n\n        insert($con,input_text(array(\"name\"=&gt;$this-&gt;frmvar,\n                        \"value\"=&gt;$this-&gt;search,\n                        \"size\"=&gt;\"20\",\n                        \"maxlength\"=&gt;\"80\")));\n\n        foreach ($this-&gt;vars as $key=&gt;$value) {\n            insert($con,hidden(array(\"name\"=&gt;\"$key\",\n                            \"value\"=&gt;\"$value\")));\n        }\n\n        insert($con,submit(array(\"value\"=&gt;my_(\"Submit\"))));\n        insert($con,block(\" &lt;a href='#' onclick='SEARCH.\".$this-&gt;frmvar.\".value=\\\"\\\"; SEARCH.submit();'&gt;\".my_(\"Reset Search\").\"&lt;\/a&gt;\"));\n\n\n    }<\/code><\/pre>\n\n\n\n<p>Due to the PHP action (i.e., &#8220;action&#8221;=&gt;$_SERVER[&#8220;PHP_SELF&#8221;]), notice that we have no sanitization or validation and therefore vulnerable to XSS. That is, given the default behavior where <strong>PHP_SELF<\/strong> is copied into the value of an HTML tag, we can easily append any sort of HTML or Javascript injection within the query string. <\/p>\n\n\n\n<p>For evidence, refer to the prints below where I&#8217;m accessing the mentioned application with and without  our manipulation:<\/p>\n\n\n\n<h5 class=\"wp-block-heading\">Regular<\/h5>\n\n\n\n<figure class=\"wp-block-image size-large\"><img fetchpriority=\"high\" decoding=\"async\" width=\"1024\" height=\"210\" src=\"https:\/\/devbrain.com.br\/wp-content\/uploads\/2022\/06\/Screen-Shot-2022-06-17-at-16.14.26-1024x210.png\" alt=\"\" class=\"wp-image-720\" srcset=\"https:\/\/devbrain.com.br\/wp-content\/uploads\/2022\/06\/Screen-Shot-2022-06-17-at-16.14.26-1024x210.png 1024w, https:\/\/devbrain.com.br\/wp-content\/uploads\/2022\/06\/Screen-Shot-2022-06-17-at-16.14.26-300x61.png 300w, https:\/\/devbrain.com.br\/wp-content\/uploads\/2022\/06\/Screen-Shot-2022-06-17-at-16.14.26-768x157.png 768w, https:\/\/devbrain.com.br\/wp-content\/uploads\/2022\/06\/Screen-Shot-2022-06-17-at-16.14.26-600x123.png 600w, https:\/\/devbrain.com.br\/wp-content\/uploads\/2022\/06\/Screen-Shot-2022-06-17-at-16.14.26.png 1324w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" width=\"995\" height=\"297\" src=\"https:\/\/devbrain.com.br\/wp-content\/uploads\/2022\/06\/Screen-Shot-2022-06-17-at-16.19.36.png\" alt=\"\" class=\"wp-image-722\" srcset=\"https:\/\/devbrain.com.br\/wp-content\/uploads\/2022\/06\/Screen-Shot-2022-06-17-at-16.19.36.png 995w, https:\/\/devbrain.com.br\/wp-content\/uploads\/2022\/06\/Screen-Shot-2022-06-17-at-16.19.36-300x90.png 300w, https:\/\/devbrain.com.br\/wp-content\/uploads\/2022\/06\/Screen-Shot-2022-06-17-at-16.19.36-768x229.png 768w, https:\/\/devbrain.com.br\/wp-content\/uploads\/2022\/06\/Screen-Shot-2022-06-17-at-16.19.36-600x179.png 600w\" sizes=\"(max-width: 995px) 100vw, 995px\" \/><\/figure>\n\n\n\n<h5 class=\"wp-block-heading\">Triggering XSS<\/h5>\n\n\n\n<figure class=\"wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-1 is-layout-flex wp-block-gallery-is-layout-flex\">\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" width=\"1024\" height=\"369\" data-id=\"726\" src=\"https:\/\/devbrain.com.br\/wp-content\/uploads\/2022\/06\/2022-06-17_16-22-1024x369.png\" alt=\"\" class=\"wp-image-726\" srcset=\"https:\/\/devbrain.com.br\/wp-content\/uploads\/2022\/06\/2022-06-17_16-22-1024x369.png 1024w, https:\/\/devbrain.com.br\/wp-content\/uploads\/2022\/06\/2022-06-17_16-22-300x108.png 300w, https:\/\/devbrain.com.br\/wp-content\/uploads\/2022\/06\/2022-06-17_16-22-768x277.png 768w, https:\/\/devbrain.com.br\/wp-content\/uploads\/2022\/06\/2022-06-17_16-22-600x216.png 600w, https:\/\/devbrain.com.br\/wp-content\/uploads\/2022\/06\/2022-06-17_16-22.png 1292w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n<\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"311\" src=\"https:\/\/devbrain.com.br\/wp-content\/uploads\/2022\/06\/2022-06-17_16-23-1024x311.png\" alt=\"\" class=\"wp-image-727\" srcset=\"https:\/\/devbrain.com.br\/wp-content\/uploads\/2022\/06\/2022-06-17_16-23-1024x311.png 1024w, https:\/\/devbrain.com.br\/wp-content\/uploads\/2022\/06\/2022-06-17_16-23-300x91.png 300w, https:\/\/devbrain.com.br\/wp-content\/uploads\/2022\/06\/2022-06-17_16-23-768x234.png 768w, https:\/\/devbrain.com.br\/wp-content\/uploads\/2022\/06\/2022-06-17_16-23-600x182.png 600w, https:\/\/devbrain.com.br\/wp-content\/uploads\/2022\/06\/2022-06-17_16-23.png 1026w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">HTTP Method<\/h3>\n\n\n\n<p>Also notice that according to the code&#8217;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),<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">How to avoid it?<\/h3>\n\n\n\n<p>In order to avoid HTML or Javascript injections over PHP_SELF, we can simply use <strong>htlmentities()<\/strong> as it converts special characters to HTML entities and therefore it&#8217;s enough to mitigate such issue.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Finding it &#8211; Regex<\/h3>\n\n\n\n<p>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:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>rg '\\$_SERVER+.*PHP_SELF' | rg -v htmlspecialchars<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">References<\/h3>\n\n\n\n<ul class=\"wp-block-list\"><li><a href=\"https:\/\/www.php.net\/manual\/en\/function.htmlspecialchars.php\">https:\/\/www.php.net\/manual\/en\/function.htmlspecialchars.php<\/a><\/li><li><a href=\"https:\/\/www.php.net\/manual\/en\/reserved.variables.server.ph\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/www.php.net\/manual\/en\/reserved.variables.server.ph<\/a><\/li><\/ul>\n","protected":false},"excerpt":{"rendered":"<p>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&#8230;.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-693","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/devbrain.com.br\/index.php\/wp-json\/wp\/v2\/posts\/693","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devbrain.com.br\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devbrain.com.br\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devbrain.com.br\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/devbrain.com.br\/index.php\/wp-json\/wp\/v2\/comments?post=693"}],"version-history":[{"count":52,"href":"https:\/\/devbrain.com.br\/index.php\/wp-json\/wp\/v2\/posts\/693\/revisions"}],"predecessor-version":[{"id":749,"href":"https:\/\/devbrain.com.br\/index.php\/wp-json\/wp\/v2\/posts\/693\/revisions\/749"}],"wp:attachment":[{"href":"https:\/\/devbrain.com.br\/index.php\/wp-json\/wp\/v2\/media?parent=693"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devbrain.com.br\/index.php\/wp-json\/wp\/v2\/categories?post=693"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devbrain.com.br\/index.php\/wp-json\/wp\/v2\/tags?post=693"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}