summaryrefslogtreecommitdiffstats
path: root/scripts/http-vuln-cve2011-3368.nse
blob: e60f1f5c85d4f5dbe23b23bc602491940e462c3e (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
local http = require "http"
local os = require "os"
local shortport = require "shortport"
local stdnse = require "stdnse"
local string = require "string"
local vulns = require "vulns"
local rand = require "rand"

description = [[
Tests for the CVE-2011-3368 (Reverse Proxy Bypass) vulnerability in Apache HTTP server's reverse proxy mode.
The script will run 3 tests:
* the loopback test, with 3 payloads to handle different rewrite rules
* the internal hosts test. According to Contextis, we expect a delay before a server error.
* The external website test. This does not mean that you can reach a LAN ip, but this is a relevant issue anyway.

References:
* http://www.contextis.com/research/blog/reverseproxybypass/
]]

---
-- @usage
-- nmap --script http-vuln-cve2011-3368 <targets>
--
-- @output
-- PORT   STATE SERVICE
-- 80/tcp open  http
-- | http-vuln-cve2011-3368:
-- |   VULNERABLE:
-- |   Apache mod_proxy Reverse Proxy Security Bypass
-- |     State: VULNERABLE
-- |     IDs:  CVE:CVE-2011-3368  BID:49957
-- |     Description:
-- |       An exposure was reported affecting the use of Apache HTTP Server in
-- |       reverse proxy mode. The exposure could inadvertently expose internal
-- |       servers to remote users who send carefully crafted requests.
-- |     Disclosure date: 2011-10-05
-- |     Extra information:
-- |       Proxy allows requests to external websites
-- |     References:
-- |       https://www.securityfocus.com/bid/49957
-- |_      https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2011-3368
--
-- @args http-vuln-cve2011-3368.prefix sets the path prefix (directory) to check for the vulnerability.
--

author = {"Ange Gutek", "Patrik Karlsson"}
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"intrusive", "vuln"}



portrule = shortport.http

action = function(host, port)

  local vuln = {
    title = 'Apache mod_proxy Reverse Proxy Security Bypass',
    IDS = { CVE='CVE-2011-3368', BID='49957'},
    description = [[
An exposure was reported affecting the use of Apache HTTP Server in
reverse proxy mode. The exposure could inadvertently expose internal
servers to remote users who send carefully crafted requests.]],
    references = { 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2011-3368' },
    dates = {
      disclosure = { year='2011', month='10', day='05'}
    },
  }

  local report = vulns.Report:new(SCRIPT_NAME, host, port)
  local prefix = stdnse.get_script_args("http-vuln-cve2011-3368.prefix") or ""

  -- Take a reference chrono for a 404
  local start = os.time(os.date('*t'))
  local random_page = rand.random_alpha(20)
  local reference = http.get(host,port,("%s/%s.htm"):format(prefix,random_page))
  local chrono_404 = os.time(os.date('*t'))-start

  -- TEST 1: the loopback test, with 3 payloads to handle different rewrite rules
  local all
  all = http.pipeline_add(("%s@localhost"):format(prefix),nil, all)
  all = http.pipeline_add(("%s:@localhost"):format(prefix),nil, all)
  all = http.pipeline_add(("%s:@localhost:80"):format(prefix), nil, all)

  local bypass_request = http.pipeline_go(host,port, all)
  if ( not(bypass_request) ) then
    stdnse.debug1("got no answers from pipelined queries")
    return stdnse.format_output(false, "Got no answers from pipelined queries")
  end


  -- going through the results of TEST 1 we could see
  -- * 200 OK
  --    o This could be the result of the server being vulnerable
  --    o This could also be the result of a generic error page
  -- * 40X Error
  --    o This is most likely the result of the server NOT being vulnerable
  --
  -- We can not determine whether the server is vulnerable or not solely
  -- by relying on the 200 OK. If we have no 200 OK abort, otherwise continue
  local got_200_ok
  for _, response in ipairs(bypass_request) do
    if ( response.status == 200 ) then
      got_200_ok = true
    end
  end

  -- if we didn't get at least one 200 OK, the server is most like NOT vulnerable
  if ( not(got_200_ok) ) then
    vuln.state = vulns.STATE.NOT_VULN
    return report:make_output(vuln)
  end

  for i=1, #bypass_request, 1 do
    stdnse.debug1("test %d returned a %d",i,bypass_request[i].status)

    -- here a 400 should be the evidence for a patched server.
    if ( bypass_request[i].status == 200 and vuln.state ~= vulns.STATE.VULN )  then

      -- TEST 2: the internal hosts test. According to Contextis, we expect a delay before a server error.
      -- According to my (Patrik) tests, internal hosts reachable by the server may return instant responses
      local tests = {
        { prefix = "", suffix = "" },
        { prefix = ":", suffix = ""},
        { prefix = ":", suffix = ":80"}
      }

      -- try a bunch of hosts, and hope we hit one that's
      -- not on the network, this will give us the delay we're expecting
      local hosts = {
        "10.10.10.10",
        "192.168.211.211",
        "172.16.16.16"
      }

      -- perform one request for each host, and stop once we
      -- receive a timeout for one of them
      for _, h in ipairs(hosts) do
        local response = http.get(
        host,
        port,
        ("%s%s@%s%s"):format(prefix, tests[i].prefix, h, tests[i].suffix),
        { timeout = ( chrono_404 + 5 ) * 1000 }
        )
        -- check if the GET timed out
        if ( not(response.status) ) then
          vuln.state = vulns.STATE.VULN
          break
        end
      end
    end
  end

  -- TEST 3: The external website test. This does not mean that you can reach a LAN ip, but this is a relevant issue anyway.
  local external = http.get(host,port, ("%s@scanme.nmap.org"):format(prefix))
  if ( external.status == 200 and string.match(external.body,"Go ahead and ScanMe") ) then
    vuln.extra_info = "Proxy allows requests to external websites"
  end
  return report:make_output(vuln)
end