1local proxy = require "proxy"
2local shortport = require "shortport"
3local stdnse = require "stdnse"
4local string = require "string"
5local table = require "table"
6local url = require "url"
7
8description=[[
9Checks if an HTTP proxy is open.
10
11The script attempts to connect to www.google.com through the proxy and
12checks for a valid HTTP response code. Valid HTTP response codes are
13200, 301, and 302. If the target is an open proxy, this script causes
14the target to retrieve a web page from www.google.com.
15]]
16
17---
18-- @usage
19-- nmap --script http-open-proxy.nse \
20--      --script-args proxy.url=<url>,proxy.pattern=<pattern>
21-- @output
22-- Interesting ports on scanme.nmap.org (64.13.134.52):
23-- PORT     STATE SERVICE
24-- 8080/tcp open  http-proxy
25-- |  proxy-open-http: Potentially OPEN proxy.
26-- |_ Methods successfully tested: GET HEAD CONNECT
27
28-- Arturo 'Buanzo' Busleiman <buanzo@buanzo.com.ar> / www.buanzo.com.ar / linux-consulting.buanzo.com.ar
29-- Changelog: Added explode() function. Header-only matching now works.
30--   * Fixed set_timeout
31--   * Fixed some \r\n's
32-- 2008-10-02 Vlatko Kosturjak <kost@linux.hr>
33--   * Match case-insensitively against "^Server: gws" rather than
34--     case-sensitively against "^Server: GWS/".
35-- 2009-05-14 Joao Correa <joao@livewire.com.br>
36--   * Included tests for HEAD and CONNECT methods
37--   * Included url and pattern arguments
38--   * Script now checks for http response status code, when url is used
39--   * If google is used, script checks for Server: gws
40
41author = "Arturo 'Buanzo' Busleiman"
42license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
43categories = {"default", "discovery", "external", "safe"}
44
45--- Performs the custom test, with user's arguments
46-- @param host The host table
47-- @param port The port table
48-- @param test_url The url te send the request
49-- @param pattern The pattern to check for valid result
50-- @return status if any request succeeded
51-- @return response String with supported methods
52function custom_test(host, port, test_url, pattern)
53  local lstatus = false
54  local response = {}
55  -- if pattern is not used, result for test is code check result.
56  -- otherwise it is pattern check result.
57
58  -- strip hostname
59  if not string.match(test_url, "^http://.*") then
60    test_url = "http://" .. test_url
61    stdnse.debug1("URL missing scheme. URL concatenated to http://")
62  end
63  local url_table = url.parse(test_url)
64  local hostname = url_table.host
65
66  local get_status = proxy.test_get(host, port, "http", test_url, hostname, pattern)
67  local head_status = proxy.test_head(host, port, "http", test_url, hostname, pattern)
68  local conn_status = proxy.test_connect(host, port, "http", hostname)
69  if get_status then
70    lstatus = true
71    response[#response+1] = "GET"
72  end
73  if head_status then
74    lstatus = true
75    response[#response+1] = "HEAD"
76  end
77  if conn_status then
78    lstatus = true
79    response[#response+1] = "CONNECTION"
80  end
81  if lstatus then response = "Methods supported: " .. table.concat(response, " ") end
82  return lstatus, response
83end
84
85--- Performs the default test
86-- First: Default google request and checks for Server: gws
87-- Seconde: Request to wikipedia.org and checks for wikimedia pattern
88-- Third: Request to computerhistory.org and checks for museum pattern
89--
90-- If any of the requests is successful, the proxy is considered open
91-- If all get requests return the same result, the user is alerted that
92-- the proxy might be redirecting his requests (very common on wi-fi
93-- connections at airports, cafes, etc.)
94--
95-- @param host The host table
96-- @param port The port table
97-- @return status if any request succeeded
98-- @return response String with supported methods
99function default_test(host, port)
100  local fstatus = false
101  local cstatus = false
102  local get_status, head_status, conn_status
103  local get_r1, get_r2, get_r3
104  local get_cstatus, head_cstatus
105
106  -- Start test n1 -> google.com
107  -- making requests
108  local test_url = "http://www.google.com"
109  local hostname = "www.google.com"
110  local pattern  = "^server: gws"
111  get_status, get_r1, get_cstatus = proxy.test_get(host, port, "http", test_url, hostname, pattern)
112  local _
113  head_status, _, head_cstatus = proxy.test_head(host, port, "http", test_url, hostname, pattern)
114  conn_status = proxy.test_connect(host, port, "http", hostname)
115
116  -- checking results
117  -- conn_status use a different flag (cstatus)
118  -- because test_connection does not use patterns, so it is unable to detect
119  -- cases where you receive a valid code, but the response does not match the
120  -- pattern.
121  -- if it was using the same flag, program could return without testing GET/HEAD
122  -- once more before returning
123  local response = {}
124  if get_status then fstatus = true; response[#response+1] = "GET" end
125  if head_status then fstatus = true; response[#response+1] = "HEAD" end
126  if conn_status then cstatus = true; response[#response+1] = "CONNECTION" end
127
128  -- if proxy is open, return it!
129  if fstatus then return fstatus, "Methods supported: " .. table.concat(response, " ") end
130
131  -- if we receive a invalid response, but with a valid
132  -- response code, we should make a next attempt.
133  -- if we do not receive any valid status code,
134  -- there is no reason to keep testing... the proxy is probably not open
135  if not (get_cstatus or head_cstatus or conn_status) then return false, nil end
136  stdnse.debug1("Test 1 - Google Web Server\nReceived valid status codes, but pattern does not match")
137
138  test_url = "http://www.wikipedia.org"
139  hostname = "www.wikipedia.org"
140  pattern  = "wikimedia"
141  get_status, get_r2, get_cstatus = proxy.test_get(host, port, "http", test_url, hostname, pattern)
142  head_status, _, head_cstatus = proxy.test_head(host, port, "http", test_url, hostname, pattern)
143  conn_status = proxy.test_connect(host, port, "http", hostname)
144
145  if get_status then fstatus = true; response[#response+1] = "GET" end
146  if head_status then fstatus = true; response[#response+1] = "HEAD" end
147  if conn_status then
148    if not cstatus then response[#response+1] = "CONNECTION" end
149    cstatus = true
150  end
151
152  if fstatus then return fstatus, "Methods supported: "  .. table.concat(response, " ") end
153
154  -- same valid code checking as above
155  if not (get_cstatus or head_cstatus or conn_status) then return false, nil end
156  stdnse.debug1("Test 2 - Wikipedia.org\nReceived valid status codes, but pattern does not match")
157
158  test_url = "http://www.computerhistory.org"
159  hostname = "www.computerhistory.org"
160  pattern  = "museum"
161  get_status, get_r3, get_cstatus = proxy.test_get(host, port, "http", test_url, hostname, pattern)
162  conn_status = proxy.test_connect(host, port, "http", hostname)
163
164  if get_status then fstatus = true; response[#response+1] = "GET" end
165  if conn_status then
166    if not cstatus then response[#response+1] = "CONNECTION" end
167    cstatus = true
168  end
169
170  if fstatus then return fstatus, "Methods supported:" .. table.concat(response, " ") end
171  if not get_cstatus then
172    stdnse.debug1("Test 3 - Computer History\nReceived valid status codes, but pattern does not match")
173  end
174
175  -- Check if GET is being redirected
176  if proxy.redirectCheck(get_r1, get_r2) and proxy.redirectCheck(get_r2, get_r3) then
177    return false, "Proxy might be redirecting requests"
178  end
179
180  -- Check if at least CONNECTION worked
181  if cstatus then return true, "Methods supported:" .. table.concat(response, " ") end
182
183  -- Nothing works...
184  return false, nil
185end
186
187portrule = shortport.port_or_service({8123,3128,8000,8080},{'polipo','squid-http','http-proxy'})
188
189action = function(host, port)
190  local supported_methods = "\nMethods successfully tested: "
191  local fstatus = false
192  local def_test = true
193  local test_url, pattern
194
195  test_url, pattern = proxy.return_args()
196
197  if(test_url) then def_test = false end
198  if(pattern) then pattern = ".*" .. pattern .. ".*" end
199
200  if def_test
201    then fstatus, supported_methods = default_test(host, port)
202    else fstatus, supported_methods = custom_test(host, port, test_url, pattern);
203  end
204
205  -- If any of the tests were OK, then the proxy is potentially open
206  if fstatus then
207    return "Potentially OPEN proxy.\n" .. supported_methods
208  elseif not fstatus and supported_methods then
209    return supported_methods
210  end
211  return
212
213end
214