summaryrefslogtreecommitdiffstats
path: root/scripts/http-phpself-xss.nse
blob: b59d892f345b13b6ce5353ccbc3695dc17332422 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
description=[[
Crawls a web server and attempts to find PHP files vulnerable to reflected
cross site scripting via the variable <code>$_SERVER["PHP_SELF"]</code>.

This script crawls the webserver to create a list of PHP files and then sends
an attack vector/probe to identify PHP_SELF cross site scripting
vulnerabilities.  PHP_SELF XSS refers to reflected cross site scripting
vulnerabilities caused by the lack of sanitation of the variable
<code>$_SERVER["PHP_SELF"]</code> in PHP scripts. This variable is commonly
used in PHP scripts that display forms and when the script file name  is
needed.

Examples of Cross Site Scripting vulnerabilities in the variable $_SERVER[PHP_SELF]:
* http://www.securityfocus.com/bid/37351
* http://software-security.sans.org/blog/2011/05/02/spot-vuln-percentage
* http://websec.ca/advisories/view/xss-vulnerabilities-mantisbt-1.2.x

The attack vector/probe used is: <code>/'"/><script>alert(1)</script></code>
]]

---
-- @usage
-- nmap --script=http-phpself-xss -p80 <target>
-- nmap -sV --script http-self-xss <target>
--
-- @output
-- PORT   STATE SERVICE REASON
-- 80/tcp open  http    syn-ack
-- | http-phpself-xss:
-- |   VULNERABLE:
-- |   Unsafe use of $_SERVER["PHP_SELF"] in PHP files
-- |     State: VULNERABLE (Exploitable)
-- |     Description:
-- |       PHP files are not handling safely the variable $_SERVER["PHP_SELF"] causing Reflected Cross Site Scripting vulnerabilities.
-- |
-- |     Extra information:
-- |
-- |   Vulnerable files with proof of concept:
-- |     http://calder0n.com/sillyapp/three.php/%27%22/%3E%3Cscript%3Ealert(1)%3C/script%3E
-- |     http://calder0n.com/sillyapp/secret/2.php/%27%22/%3E%3Cscript%3Ealert(1)%3C/script%3E
-- |     http://calder0n.com/sillyapp/1.php/%27%22/%3E%3Cscript%3Ealert(1)%3C/script%3E
-- |     http://calder0n.com/sillyapp/secret/1.php/%27%22/%3E%3Cscript%3Ealert(1)%3C/script%3E
-- |   Spidering limited to: maxdepth=3; maxpagecount=20; withinhost=calder0n.com
-- |     References:
-- |       https://www.owasp.org/index.php/Cross-site_Scripting_(XSS)
-- |_      http://php.net/manual/en/reserved.variables.server.php
--
-- @args http-phpself-xss.uri URI. Default: /
-- @args http-phpself-xss.timeout Spidering timeout. (default 10s)
--
-- @see http-stored-xss.nse
-- @see http-dombased-xss.nse
-- @see http-xssed.nse
author = "Paulino Calderon <calderon@websec.mx>"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"fuzzer", "intrusive", "vuln"}

local http = require 'http'
local httpspider = require 'httpspider'
local shortport = require 'shortport'
local url = require 'url'
local stdnse = require 'stdnse'
local vulns = require 'vulns'
local string = require 'string'
local table = require 'table'

portrule = shortport.http

-- PHP_SELF Attack vector
local PHP_SELF_PROBE = '/%27%22/%3E%3Cscript%3Ealert(1)%3C/script%3E'
local probes = {}

--Checks if attack vector is in the response's body
--@param response Response table
--@return True if attack vector is found in response's body
local function check_probe_response(response)
  stdnse.debug3("Probe response:\n%s", response.body)
  if string.find(response.body, "'\"/><script>alert(1)</script>", 1, true) ~= nil then
    return true
  end
  return false
end

--Launches probe request
--@param host Hostname
--@param port Port number
--@param uri URL String
--@return True if page is vulnerable/attack vector was found in body
local function launch_probe(host, port, uri)
  local probe_response

  --We avoid repeating probes.
  --This is a temp fix since httpspider do not keep track of previously parsed links at the moment.
  if probes[uri] then
    return false
  end

  stdnse.debug1("HTTP GET %s%s", uri, PHP_SELF_PROBE)
  probe_response = http.get(host, port, uri .. PHP_SELF_PROBE)

  --save probe in list to avoid repeating it
  probes[uri] = true

  if check_probe_response(probe_response) then
    return true
  end
  return false
end

---
--main
---
action = function(host, port)
  local uri = stdnse.get_script_args(SCRIPT_NAME..".uri") or "/"
  local timeout = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME..'.timeout'))
  timeout = (timeout or 10) * 1000
  local crawler = httpspider.Crawler:new(host, port, uri, { scriptname = SCRIPT_NAME } )
  crawler:set_timeout(timeout)

  local vuln = {
       title = 'Unsafe use of $_SERVER["PHP_SELF"] in PHP files',
       state = vulns.STATE.NOT_VULN,
       description = [[
PHP files are not handling safely the variable $_SERVER["PHP_SELF"] causing Reflected Cross Site Scripting vulnerabilities.
       ]],
       references = {
           'http://php.net/manual/en/reserved.variables.server.php',
           'https://www.owasp.org/index.php/Cross-site_Scripting_(XSS)'
       }
     }
  local vuln_report = vulns.Report:new(SCRIPT_NAME, host, port)

  local vulnpages = {}
  local probed_pages= {}

  while(true) do
    local status, r = crawler:crawl()
    if ( not(status) ) then
      if ( r.err ) then
        return stdnse.format_output(false, r.reason)
      else
        break
      end
    end

    local parsed = url.parse(tostring(r.url))

    --Only work with .php files
    if ( parsed.path and parsed.path:match(".*.php") ) then
        local host = parsed.host
        local port = parsed.port or url.get_default_port(parsed.scheme)
        local escaped_link = parsed.path:gsub(" ", "%%20")
        if launch_probe(host,port,escaped_link) then
          table.insert(vulnpages, parsed.scheme..'://'..host..escaped_link..PHP_SELF_PROBE)
        end
      end
  end

  if ( #vulnpages > 0 ) then
    vuln.state = vulns.STATE.EXPLOIT
    vulnpages.name = "Vulnerable files with proof of concept:"
    vuln.extra_info = stdnse.format_output(true, vulnpages)..crawler:getLimitations()
  end

  return vuln_report:make_output(vuln)

end