PageRenderTime 38ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/nselib/proxy.lua

https://gitlab.com/g10h4ck/nmap-gsoc2015
Lua | 334 lines | 230 code | 16 blank | 88 comment | 58 complexity | e305063eb59adbb60b7ace412e471865 MD5 | raw file
Possible License(s): BSD-3-Clause, GPL-2.0, Apache-2.0, LGPL-2.0, LGPL-2.1, MIT
  1. ---
  2. -- Functions for proxy testing.
  3. --
  4. -- @author Joao Correa <joao@livewire.com.br>
  5. -- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
  6. local bin = require "bin"
  7. local dns = require "dns"
  8. local ipOps = require "ipOps"
  9. local nmap = require "nmap"
  10. local stdnse = require "stdnse"
  11. local string = require "string"
  12. local table = require "table"
  13. _ENV = stdnse.module("proxy", stdnse.seeall)
  14. -- Start of local functions
  15. --- check function, checks for all valid returned status
  16. --- If any of the HTTP status below is found, the proxy is potentially open
  17. --- The script tries to split header from body before checking for status
  18. --@param result connection result
  19. --@return true if any of the status is found, otherwise false
  20. local function check_code(result)
  21. if result then
  22. local header
  23. if result:match( "\r?\n\r?\n" ) then
  24. result = result:match( "^(.-)\r?\n\r?\n(.*)$" )
  25. end
  26. if result:lower():match("^http/%d%.%d%s*200") then return true end
  27. if result:lower():match("^http/%d%.%d%s*30[12]") then return true end
  28. end
  29. return false
  30. end
  31. --- check pattern, searches a pattern inside a response with multiple lines
  32. --@param result Connection result
  33. --@param pattern The pattern to be searched
  34. --@return true if pattern is found, otherwise false
  35. local function check_pattern(result, pattern)
  36. local lines = stdnse.strsplit("\n", result)
  37. for i, line in ipairs(lines) do
  38. if line:lower():match(pattern:lower()) then return true end
  39. end
  40. return false
  41. end
  42. --- check, decides what kind of check should be done on the response,
  43. --- depending if a specific pattern is being used
  44. --@param result Connection result
  45. --@param pattern The pattern that should be checked (must be false, in case of
  46. --code check)
  47. --@return true, if the performed check returns true, otherwise false
  48. local function check(result, pattern)
  49. local s_pattern = false
  50. local s_code = check_code(result)
  51. if s_code and pattern then
  52. s_pattern = check_pattern(result, pattern)
  53. end
  54. return s_code, s_pattern
  55. end
  56. --- Performs a request to the web server and calls check to check if
  57. -- the response is a valid result
  58. --
  59. --@param socket The socket to send the request through
  60. --@param req The request to be sent
  61. --@param pattern The pattern to check for valid result
  62. --@return check_status True or false. If pattern was used, depends on pattern check result. If not, depends on code check result.
  63. --@return result The result of the request
  64. --@return code_status True or false. If pattern was used, returns the result of code checking for the same result. If pattern was not used, is nil.
  65. local function test(socket, req, pattern)
  66. local status, result = socket:send(req)
  67. if not status then
  68. socket:close()
  69. return false, result
  70. end
  71. status, result = socket:receive()
  72. if not status then
  73. socket:close()
  74. return false, result
  75. end
  76. socket:close()
  77. local s_code, s_pattern = check(result, pattern)
  78. if result and pattern then return s_pattern, result, s_code end
  79. if result then return s_code, result, nil end
  80. return false, nil, nil
  81. end
  82. --- Builds the GET request and calls test
  83. -- @param host The host table
  84. -- @param port The port table
  85. -- @param proxyType The proxy type to be tested, might be 'socks4', 'socks5' or 'http'
  86. -- @param test_url The url to send the request
  87. -- @param hostname The hostname of the server to send the request
  88. -- @param pattern The pattern to check for valid result
  89. -- @return the result of the function test (status and the request result)
  90. function test_get(host, port, proxyType, test_url, hostname, pattern)
  91. local status, socket = connectProxy(host, port, proxyType, hostname)
  92. if not status then
  93. return false, socket
  94. end
  95. local req = "GET " .. test_url .. " HTTP/1.0\r\nHost: " .. hostname .. "\r\n\r\n"
  96. stdnse.debug1("GET Request: " .. req)
  97. return test(socket, req, pattern)
  98. end
  99. --- Builds the HEAD request and calls test
  100. -- @param host The host table
  101. -- @param port The port table
  102. -- @param proxyType The proxy type to be tested, might be 'socks4', 'socks5' or 'http'
  103. -- @param test_url The url te send the request
  104. -- @param hostname The hostname of the server to send the request
  105. -- @param pattern The pattern to check for valid result
  106. -- @return the result of the function test (status and the request result)
  107. function test_head(host, port, proxyType, test_url, hostname, pattern)
  108. local status, socket = connectProxy(host, port, proxyType, hostname)
  109. if not status then
  110. return false, socket
  111. end
  112. local req = "HEAD " .. test_url .. " HTTP/1.0\r\nHost: " .. hostname .. "\r\n\r\n"
  113. stdnse.debug1("HEAD Request: " .. req)
  114. return test(socket, req, pattern)
  115. end
  116. --- Builds the CONNECT request and calls test
  117. -- @param host The host table
  118. -- @param port The port table
  119. -- @param proxyType The proxy type to be tested, might be 'socks4', 'socks5' or 'http'
  120. -- @param hostname The hostname of the server to send the request
  121. -- @return the result of the function test (status and the request result)
  122. function test_connect(host, port, proxyType, hostname)
  123. local status, socket = connectProxy(host, port, proxyType, hostname)
  124. if not status then
  125. return false, socket
  126. end
  127. local req = "CONNECT " .. hostname .. ":80 HTTP/1.0\r\n\r\n"
  128. stdnse.debug1("CONNECT Request: " .. req)
  129. return test(socket, req, false)
  130. end
  131. --- Function that resolves IP address for hostname and
  132. --- returns it as hex values
  133. --@param hostname Hostname to resolve
  134. --@return Ip address of hostname in hex
  135. function hex_resolve(hostname)
  136. local a, b, c, d;
  137. local dns_status, ip = dns.query(hostname)
  138. if not dns_status then
  139. return false
  140. end
  141. local t, err = ipOps.get_parts_as_number(ip)
  142. if t and not err
  143. then a, b, c, d = table.unpack(t)
  144. else return false
  145. end
  146. local sip = string.format("%.2x ", a) .. string.format("%.2x ", b) .. string.format("%.2x ", c) .. string.format("%.2x ",d)
  147. return true, sip
  148. end
  149. --- Checks if any parameter was used in old or new syntax
  150. -- and return the parameters
  151. -- @return url the proxy.url parameter
  152. -- @return pattern the proxy.pattern parameter
  153. function return_args()
  154. local url = false
  155. local pattern = false
  156. if nmap.registry.args['proxy.url']
  157. then url = nmap.registry.args['proxy.url']
  158. elseif nmap.registry.args.proxy and nmap.registry.args.proxy.url
  159. then url = nmap.registry.args.proxy.url
  160. end
  161. if nmap.registry.args['proxy.pattern']
  162. then pattern = nmap.registry.args['proxy.pattern']
  163. elseif nmap.registry.args.proxy and nmap.registry.args.proxy.url
  164. then pattern = nmap.registry.args.proxy.pattern
  165. end
  166. return url, pattern
  167. end
  168. --- Creates a socket, performs proxy handshake if necessary
  169. --- and returns it
  170. -- @param host The host table
  171. -- @param port The port table
  172. -- @param proxyType A string with the proxy type. Might be "http","socks4" or "socks5"
  173. -- @param hostname The proxy destination hostname
  174. -- @return status True if handshake succeeded, false otherwise
  175. -- @return socket A socket with the handshake already done, or an error if
  176. function connectProxy(host, port, proxyType, hostname)
  177. local socket = nmap.new_socket()
  178. socket:set_timeout(10000)
  179. local status, err = socket:connect(host, port)
  180. if not status then
  181. socket:close()
  182. return false, err
  183. end
  184. if proxyType == "http" then return true, socket end
  185. if proxyType == "socks4" then return socksHandshake(socket, 4, hostname) end
  186. if proxyType == "socks5" then return socksHandshake(socket, 5, hostname) end
  187. socket:close()
  188. return false, "Invalid proxyType"
  189. end
  190. --- Performs a socks handshake on a socket and returns it
  191. -- @param socket The socket where the handshake will be performed
  192. -- @param version The socks version (might be 4 or 5)
  193. -- @param hostname The proxy destination hostname
  194. -- @return status True if handshake succeeded, false otherwise
  195. -- @return socket A socket with the handshake already done, or an error if
  196. -- status is false
  197. function socksHandshake(socket, version, hostname)
  198. local resolve, sip, paystring, payload
  199. resolve, sip = hex_resolve(hostname)
  200. if not resolve then
  201. return false, "Unable to resolve hostname"
  202. end
  203. if version == 4 then
  204. paystring = '04 01 00 50 ' .. sip .. ' 6e 6d 61 70 00'
  205. payload = bin.pack("H",paystring)
  206. local status, response = socket:send(payload)
  207. if not status then
  208. socket:close()
  209. return false, response
  210. end
  211. status, response = socket:receive()
  212. if not status then
  213. socket:close()
  214. return false, response
  215. end
  216. if #response < 2 then
  217. socket:close()
  218. return false, "Invalid or unknown SOCKS response"
  219. end
  220. local request_status = string.byte(response, 2)
  221. local err = string.format("Unknown response (0x%02x)", request_status)
  222. if(request_status == 0x5a) then
  223. stdnse.debug1('Socks4: Received "Request Granted" from proxy server')
  224. return true, socket
  225. end
  226. if(request_status == 0x5b) then
  227. err = "Request rejected or failed"
  228. elseif (request_status == 0x5c) then
  229. err = "request failed because client is not running identd"
  230. elseif (request_status == 0x5d) then
  231. err = "request failed because client program and identd report different user-ids"
  232. end
  233. stdnse.debug1('Socks4: Received "%s" from proxy server', err)
  234. return false, err
  235. end
  236. if version == 5 then
  237. local payload = bin.pack("H",'05 01 00')
  238. local status, err = socket:send(payload)
  239. if not status then
  240. socket:close()
  241. return false, err
  242. end
  243. local auth
  244. status, auth = socket:receive()
  245. local r2 = string.byte(auth,2)
  246. -- If Auth is required, proxy is closed, skip next test
  247. if(r2 ~= 0x00) then
  248. err = "Authentication Required"
  249. else
  250. -- If no Auth is required, try to establish connection
  251. stdnse.debug1("Socks5: No authentication required")
  252. -- Socks5 second payload: Version, Command, Null, Address type, Ip-Address, Port number
  253. paystring = '05 01 00 01 ' .. sip .. '00 50'
  254. payload = bin.pack("H",paystring)
  255. status, err = socket:send(payload)
  256. if not status then
  257. socket:close()
  258. return false, err
  259. end
  260. local z
  261. status, z = socket:receive()
  262. if not status then
  263. socket:close()
  264. return false, z
  265. end
  266. local request_status = string.byte(z, 2)
  267. err = string.format("Unknown response (0x%02x)", request_status)
  268. if (request_status == 0x00) then
  269. stdnse.debug1('Socks5: Received "Request Granted" from proxy server')
  270. return true, socket
  271. elseif(request_status == 0x01) then
  272. err = "General Failure"
  273. elseif (request_status == 0x02) then
  274. err = "Connection not allowed by ruleset"
  275. elseif (request_status == 0x03) then
  276. err = "Network unreachable"
  277. elseif (request_status == 0x04) then
  278. err = "Host unreachable"
  279. elseif (request_status == 0x05) then
  280. err = "Connection refused by destination host"
  281. elseif (request_status == 0x06) then
  282. err = "TTL Expired"
  283. elseif (request_status == 0x07) then
  284. err = "command not supported / protocol error"
  285. elseif (request_status == 0x08) then
  286. err = "Address type not supported"
  287. end
  288. end
  289. stdnse.debug1('Socks5: Received "%s" from proxy server', err)
  290. return false, err
  291. end
  292. return false, "Invalid SOCKS version"
  293. end
  294. --- Checks if two different responses are equal,
  295. -- if true, the proxy server might be redirecting the requests
  296. -- to a default page
  297. --
  298. -- Functions splits body from head before comparing, to avoid session
  299. -- variables, cookies...
  300. --
  301. -- @param resp1 A string with the response for the first request
  302. -- @param resp2 A string with the response for the second request
  303. -- @return bool true if both responses are equal, otherwise false
  304. function redirectCheck(resp1, resp2)
  305. local body1, body2, _
  306. if resp1:match( "\r?\n\r?\n" ) then
  307. local body1
  308. _, body1 = resp1:match( "^(.-)\r?\n\r?\n(.*)$" )
  309. if resp2:match( "\r?\n\r?\n" ) then
  310. _, body2 = resp2:match( "^(.-)\r?\n\r?\n(.*)$" )
  311. if body1 == body2 then
  312. return true
  313. end
  314. end
  315. end
  316. return false
  317. end
  318. return _ENV;