home *** CD-ROM | disk | FTP | other *** search
/ Hackers Magazine 57 / CdHackersMagazineNr57.iso / Software / Networking / nmap-5.00-setup.exe / scripts / sql-injection.nse < prev    next >
Text File  |  2009-07-06  |  6KB  |  245 lines

  1. description = [[
  2. Spiders an HTTP server looking for URLs containing queries vulnerable to an SQL
  3. injection attack.
  4.  
  5. The script spiders an HTTP server looking for URLs containing queries. It then
  6. proceeds to combine crafted SQL commands with susceptible URLs in order to
  7. obtain errors. The errors are analysed to see if the URL is vulnerable to
  8. attack. This uses the most basic form of SQL injection but anything more
  9. complicated is better suited to a standalone tool. Both meta-style and HTTP redirects
  10. are supported.
  11.  
  12. We may not have access to the target web server's true hostname, which can prevent access to
  13. virtually hosted sites.  This script only follows absolute links when the host name component is the same as the target server's reverse-DNS name.
  14. ]]
  15.  
  16. require('url')
  17. require('shortport')
  18. require('stdnse')
  19. require('strbuf')
  20. require('listop')
  21.  
  22. author = "Eddie Bell <ejlbell@gmail.com>"
  23. license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
  24. categories = {"intrusive", "vuln"}
  25. runlevel = 1.0
  26.  
  27. -- Change this to increase depth of crawl
  28. local maxdepth = 10
  29. local get_page_from_host
  30.  
  31. local soc
  32. local catch = function() soc:close() end
  33. local try = nmap.new_try(catch)
  34.  
  35. portrule = shortport.service("http")
  36.  
  37. --[[
  38. Download a page from host:port http server. The url is passed 
  39. straight to the get request, so shouldn't include the domain name
  40. --]]
  41.  
  42. local function get_page(host, port, httpurl) 
  43.     local lines = ""
  44.     local status = true
  45.     local response = ""
  46.  
  47.     -- connect to webserver 
  48.     soc = nmap.new_socket()
  49.     soc:set_timeout(4000)
  50.     try(soc:connect(host.ip, port.number))
  51.  
  52.     httpurl = string.gsub(httpurl, "&", "&")
  53.     --print(filename .. ": " .. httpurl) 
  54.  
  55.     -- request page
  56.     local query = strbuf.new()
  57.     query = query .. "GET " .. httpurl .. " HTTP/1.1"
  58.     query = query .. "Accept: */*"
  59.     query = query .. "Accept-Language: en"
  60.     query = query .. "User-Agent: Mozilla/5.0 (compatible; Nmap Scripting Engine; http://nmap.org/book/nse.html)"
  61.     query = query .. "Host: " .. host.ip .. ":" .. port.number 
  62.     try(soc:send(strbuf.dump(query, '\r\n') .. '\r\n\r\n'))
  63.  
  64.     while true do
  65.         status, lines = soc:receive_lines(1)
  66.         if not status then break end
  67.         response = response .. lines
  68.     end
  69.  
  70.     soc:close()
  71.     return response
  72. end
  73.  
  74. -- Curried function: so we don't have to pass port and host around
  75. local function get_page_curried(host, port)
  76.     return function(url)
  77.         return get_page(host, port, url)
  78.     end
  79. end
  80.  
  81. --[[
  82. Pattern match response from a submitted injection query to see
  83. if it is vulnerable
  84. --]]
  85.  
  86. local function check_injection_response(response)
  87.  
  88.     if not (string.find(response, 'HTTP/1.1 200 OK')) then
  89.         return false 
  90.     end
  91.  
  92.     response = string.lower(response)
  93.  
  94.     return (string.find(response, "invalid query") or
  95.         string.find(response, "sql syntax") or
  96.         string.find(response, "odbc drivers error"))
  97. end
  98.  
  99. --[[
  100. Parse urls with queries and transform them into potentially 
  101. injectable urls. 
  102. --]]
  103.  
  104. local function enumerate_inject_codes(injectable) 
  105.     local utab, k, v, urlstr, response
  106.     local qtab, old_qtab, results
  107.  
  108.     results = {}
  109.     utab = url.parse(injectable)
  110.     qtab = url.parse_query(utab.query)
  111.  
  112.     for k, v in pairs(qtab) do
  113.         old_qtab = qtab[k];
  114.         qtab[k] = qtab[k] .. "'%20OR%20sqlspider"
  115.  
  116.         utab.query = url.build_query(qtab)
  117.         urlstr = url.build(utab)
  118.         response = get_page_from_host(urlstr)
  119.  
  120.         if (check_injection_response(response)) then
  121.             table.insert(results, urlstr)
  122.         end 
  123.  
  124.         qtab[k] = old_qtab
  125.         utab.query = url.build_query(qtab)
  126.     end
  127.     return results
  128. end
  129.  
  130. --[[
  131. Follow redirects, Instead of adding redirects to the url list
  132. we just modify it's format so the parser logic can be applied to
  133. it in find_links()
  134. --]]
  135.  
  136. local function check_redirects(page) 
  137.     local lpage = string.lower(page)
  138.     local _, httpurl = nil
  139.  
  140.     -- meta redirects
  141.     if(string.find(lpage, '<%s*meta%s*http%-equiv%s*=%s*"%s*refresh%s*"')) then
  142.         _, _, httpurl = string.find(lpage, 'content%s*=%s*"%s*%d+%s*;%s*url%s*=%s*([^"]+)"')
  143.         if httpurl then
  144.             page = page .. 'href="' .. httpurl .. '"'
  145.         end
  146.     end
  147.  
  148.     -- http redirect
  149.     if(string.find(lpage, 'HTTP/1.1 301 moved permanently')) then
  150.         _, _, httpurl = string.find(lpage, 'location:%s*([^\n]+)')    
  151.         if httpurl then
  152.             page = page .. 'href="' .. httpurl .. '"'
  153.         end
  154.     end
  155.  
  156.     return page
  157. end
  158.  
  159. --[[
  160. True if url is local to the site we're scanning. We never should spider 
  161. away from current site!
  162. --]]
  163.  
  164. local function is_local_link(url_parts, host) 
  165.     if url_parts.authority and 
  166.        not(url_parts.authority == host.name) then
  167.         return false
  168.     end
  169.     return true
  170. end
  171.  
  172. --[[
  173. Parse a html document looking for href links. If a local link is found
  174. it is added to the spider list If a link with a query is found it is 
  175. added to the inject list, which is returned.
  176. --]]
  177.  
  178. local function find_links(list, base_path, page, host) 
  179.     local httpurl,injectable, url_parts
  180.     local i, s, e
  181.  
  182.     injectable = {}
  183.     url_parts = {}
  184.     
  185.     for w in string.gmatch(page, 'href%s*=%s*"%s*[^"]+%s*"') do
  186.         s, e = string.find(w, '"')
  187.         httpurl = string.sub(w, s+1, string.len(w)-1)
  188.         i = 1
  189.  
  190.         -- parse out duplicates, otherwise we'll be here all day 
  191.         while list[i] and not(list[i] == httpurl) do
  192.             i = i + 1
  193.         end
  194.  
  195.         url_parts = url.parse(httpurl)
  196.  
  197.         if list[i] == nil and is_local_link(url_parts, host) and 
  198.            (not url_parts.scheme or url_parts.scheme == "http") then
  199.             httpurl = url.absolute(base_path, httpurl)
  200.             table.insert(list, httpurl)
  201.             if url_parts.query then 
  202.                 table.insert(injectable, httpurl) 
  203.             end
  204.         end
  205.     end
  206.     return injectable
  207. end
  208.  
  209. action = function(host, port)
  210.     local urllist, results, injectable 
  211.     local links, i, page
  212.     
  213.     i = 1 
  214.     urllist = {}
  215.     injectable = {}
  216.     get_page_from_host = get_page_curried(host, port)
  217.  
  218.     -- start at the root
  219.     table.insert(urllist, "/")
  220.  
  221.     while not(urllist[i] == nil) and i <= maxdepth do
  222.         page = get_page_from_host(urllist[i])
  223.         page = check_redirects(page)
  224.         links = find_links(urllist, urllist[i], page, host)
  225.         -- store all urls with queries for later analysis
  226.         injectable = listop.append(injectable, links)
  227.         i = i + 1
  228.     end
  229.  
  230.     if #injectable > 0 then
  231.         stdnse.print_debug(1, "%s: Testing %d suspicious URLs", filename, #injectable )
  232.     end
  233.  
  234.     -- test all potentially vulnerable queries
  235.     results = listop.map(enumerate_inject_codes, injectable)
  236.     -- we can get multiple vulnerable URLS from a single query
  237.     results = listop.flatten(results);
  238.  
  239.     if not listop.is_empty(results) then
  240.         return "Host might be vulnerable\n" .. table.concat(results, '\n')
  241.     end
  242.  
  243.     return nil
  244. end
  245.