summaryrefslogtreecommitdiffstats
path: root/scripts/http-open-proxy.nse
blob: 3a3f5f7490c8cfff3fd298ad8a0343ecea3a2dfa (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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
local proxy = require "proxy"
local shortport = require "shortport"
local stdnse = require "stdnse"
local string = require "string"
local table = require "table"
local url = require "url"

description=[[
Checks if an HTTP proxy is open.

The script attempts to connect to www.google.com through the proxy and
checks for a valid HTTP response code. Valid HTTP response codes are
200, 301, and 302. If the target is an open proxy, this script causes
the target to retrieve a web page from www.google.com.
]]

---
-- @usage
-- nmap --script http-open-proxy.nse \
--      --script-args proxy.url=<url>,proxy.pattern=<pattern>
-- @output
-- Interesting ports on scanme.nmap.org (64.13.134.52):
-- PORT     STATE SERVICE
-- 8080/tcp open  http-proxy
-- |  proxy-open-http: Potentially OPEN proxy.
-- |_ Methods successfully tested: GET HEAD CONNECT

-- Arturo 'Buanzo' Busleiman <buanzo@buanzo.com.ar> / www.buanzo.com.ar / linux-consulting.buanzo.com.ar
-- Changelog: Added explode() function. Header-only matching now works.
--   * Fixed set_timeout
--   * Fixed some \r\n's
-- 2008-10-02 Vlatko Kosturjak <kost@linux.hr>
--   * Match case-insensitively against "^Server: gws" rather than
--     case-sensitively against "^Server: GWS/".
-- 2009-05-14 Joao Correa <joao@livewire.com.br>
--   * Included tests for HEAD and CONNECT methods
--   * Included url and pattern arguments
--   * Script now checks for http response status code, when url is used
--   * If google is used, script checks for Server: gws

author = "Arturo 'Buanzo' Busleiman"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default", "discovery", "external", "safe"}

--- Performs the custom test, with user's arguments
-- @param host The host table
-- @param port The port table
-- @param test_url The url te send the request
-- @param pattern The pattern to check for valid result
-- @return status if any request succeeded
-- @return response String with supported methods
function custom_test(host, port, test_url, pattern)
  local lstatus = false
  local response = {}
  -- if pattern is not used, result for test is code check result.
  -- otherwise it is pattern check result.

  -- strip hostname
  if not string.match(test_url, "^http://.*") then
    test_url = "http://" .. test_url
    stdnse.debug1("URL missing scheme. URL concatenated to http://")
  end
  local url_table = url.parse(test_url)
  local hostname = url_table.host

  local get_status = proxy.test_get(host, port, "http", test_url, hostname, pattern)
  local head_status = proxy.test_head(host, port, "http", test_url, hostname, pattern)
  local conn_status = proxy.test_connect(host, port, "http", hostname)
  if get_status then
    lstatus = true
    response[#response+1] = "GET"
  end
  if head_status then
    lstatus = true
    response[#response+1] = "HEAD"
  end
  if conn_status then
    lstatus = true
    response[#response+1] = "CONNECTION"
  end
  if lstatus then response = "Methods supported: " .. table.concat(response, " ") end
  return lstatus, response
end

--- Performs the default test
-- First: Default google request and checks for Server: gws
-- Seconde: Request to wikipedia.org and checks for wikimedia pattern
-- Third: Request to computerhistory.org and checks for museum pattern
--
-- If any of the requests is successful, the proxy is considered open
-- If all get requests return the same result, the user is alerted that
-- the proxy might be redirecting his requests (very common on wi-fi
-- connections at airports, cafes, etc.)
--
-- @param host The host table
-- @param port The port table
-- @return status if any request succeeded
-- @return response String with supported methods
function default_test(host, port)
  local fstatus = false
  local cstatus = false
  local get_status, head_status, conn_status
  local get_r1, get_r2, get_r3
  local get_cstatus, head_cstatus

  -- Start test n1 -> google.com
  -- making requests
  local test_url = "http://www.google.com"
  local hostname = "www.google.com"
  local pattern  = "^server: gws"
  get_status, get_r1, get_cstatus = proxy.test_get(host, port, "http", test_url, hostname, pattern)
  local _
  head_status, _, head_cstatus = proxy.test_head(host, port, "http", test_url, hostname, pattern)
  conn_status = proxy.test_connect(host, port, "http", hostname)

  -- checking results
  -- conn_status use a different flag (cstatus)
  -- because test_connection does not use patterns, so it is unable to detect
  -- cases where you receive a valid code, but the response does not match the
  -- pattern.
  -- if it was using the same flag, program could return without testing GET/HEAD
  -- once more before returning
  local response = {}
  if get_status then fstatus = true; response[#response+1] = "GET" end
  if head_status then fstatus = true; response[#response+1] = "HEAD" end
  if conn_status then cstatus = true; response[#response+1] = "CONNECTION" end

  -- if proxy is open, return it!
  if fstatus then return fstatus, "Methods supported: " .. table.concat(response, " ") end

  -- if we receive a invalid response, but with a valid
  -- response code, we should make a next attempt.
  -- if we do not receive any valid status code,
  -- there is no reason to keep testing... the proxy is probably not open
  if not (get_cstatus or head_cstatus or conn_status) then return false, nil end
  stdnse.debug1("Test 1 - Google Web Server\nReceived valid status codes, but pattern does not match")

  test_url = "http://www.wikipedia.org"
  hostname = "www.wikipedia.org"
  pattern  = "wikimedia"
  get_status, get_r2, get_cstatus = proxy.test_get(host, port, "http", test_url, hostname, pattern)
  head_status, _, head_cstatus = proxy.test_head(host, port, "http", test_url, hostname, pattern)
  conn_status = proxy.test_connect(host, port, "http", hostname)

  if get_status then fstatus = true; response[#response+1] = "GET" end
  if head_status then fstatus = true; response[#response+1] = "HEAD" end
  if conn_status then
    if not cstatus then response[#response+1] = "CONNECTION" end
    cstatus = true
  end

  if fstatus then return fstatus, "Methods supported: "  .. table.concat(response, " ") end

  -- same valid code checking as above
  if not (get_cstatus or head_cstatus or conn_status) then return false, nil end
  stdnse.debug1("Test 2 - Wikipedia.org\nReceived valid status codes, but pattern does not match")

  test_url = "http://www.computerhistory.org"
  hostname = "www.computerhistory.org"
  pattern  = "museum"
  get_status, get_r3, get_cstatus = proxy.test_get(host, port, "http", test_url, hostname, pattern)
  conn_status = proxy.test_connect(host, port, "http", hostname)

  if get_status then fstatus = true; response[#response+1] = "GET" end
  if conn_status then
    if not cstatus then response[#response+1] = "CONNECTION" end
    cstatus = true
  end

  if fstatus then return fstatus, "Methods supported:" .. table.concat(response, " ") end
  if not get_cstatus then
    stdnse.debug1("Test 3 - Computer History\nReceived valid status codes, but pattern does not match")
  end

  -- Check if GET is being redirected
  if proxy.redirectCheck(get_r1, get_r2) and proxy.redirectCheck(get_r2, get_r3) then
    return false, "Proxy might be redirecting requests"
  end

  -- Check if at least CONNECTION worked
  if cstatus then return true, "Methods supported:" .. table.concat(response, " ") end

  -- Nothing works...
  return false, nil
end

portrule = shortport.port_or_service({8123,3128,8000,8080},{'polipo','squid-http','http-proxy'})

action = function(host, port)
  local supported_methods = "\nMethods successfully tested: "
  local fstatus = false
  local def_test = true
  local test_url, pattern

  test_url, pattern = proxy.return_args()

  if(test_url) then def_test = false end
  if(pattern) then pattern = ".*" .. pattern .. ".*" end

  if def_test
    then fstatus, supported_methods = default_test(host, port)
    else fstatus, supported_methods = custom_test(host, port, test_url, pattern);
  end

  -- If any of the tests were OK, then the proxy is potentially open
  if fstatus then
    return "Potentially OPEN proxy.\n" .. supported_methods
  elseif not fstatus and supported_methods then
    return supported_methods
  end
  return

end