CVE-2021-4080 (published 2022-01-12T14:15Z)

crater is vulnerable to Unrestricted Upload of File with Dangerous Type

Severity: MEDIUM
CVSS v2 base score: 6.5 (AV:N/AC:L/Au:S/C:P/I:P/A:P)
CVSS v3 base score: 8.8, HIGH (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-434
cwe_name: Unrestricted Upload of File with Dangerous Type
description: The product allows the upload or transfer of dangerous file types that are automatically processed within its environment.
extended_description: 
            
            
            
            
            
         
         

Fix information:
repo_url: https://github.com/crater-invoice/crater
commit_details: repo_url: https://github.com/crater-invoice/crater
author: theWorstComrade
author_date: 2021-12-29 13:33:20+01:00
msg: Unrestricted php file upload fix (#681)

https://huntr.dev/bounties/d7453360-baca-4e56-985f-481275fa38db/
repository: repo_url: https://github.com/crater-invoice/crater
repo_name: crater-invoice/crater
description: Open Source Invoicing Solution for Individuals & Businesses
repo_language: PHP
owner: crater-invoice
file_changes: filename: ExpensesController.php
change_type: ModificationType.MODIFY
diff: @@ -39,7 +39,7 @@ class ExpensesController extends Controller
     /**
      * Store a newly created resource in storage.
      *
-     * @param  \Illuminate\Http\Request $request
+     * @param  \Crater\Http\Requests\ExpenseRequest $request
      * @return \Illuminate\Http\JsonResponse
      */
     public function store(ExpenseRequest $request)
@@ -67,7 +67,7 @@ class ExpensesController extends Controller
     /**
      * Update the specified resource in storage.
      *
-     * @param  \Illuminate\Http\Request $request
+     * @param  \Crater\Http\Requests\ExpenseRequest $request
      * @param  \Crater\Models\Expense $expense
      * @return \Illuminate\Http\JsonResponse
      */

programming_language: PHP
filename: UploadReceiptController.php
change_type: ModificationType.MODIFY
diff: @@ -5,17 +5,18 @@ namespace Crater\Http\Controllers\V1\Admin\Expense;
 use Crater\Http\Controllers\Controller;
 use Crater\Models\Expense;
 use Illuminate\Http\Request;
+use Crater\Http\Requests\ExpenseRequest;
 
 class UploadReceiptController extends Controller
 {
     /**
      * Upload the expense receipts to storage.
      *
-     * @param  \Illuminate\Http\Request $request
+     * @param  \Crater\Http\Requests\ExpenseRequest $request
      * @param  Expense $expense
      * @return \Illuminate\Http\JsonResponse
      */
-    public function __invoke(Request $request, Expense $expense)
+    public function __invoke(ExpenseRequest $request, Expense $expense)
     {
         $this->authorize('update', $expense);
 

programming_language: PHP
filename: CompanyController.php
change_type: ModificationType.MODIFY
diff: @@ -9,6 +9,8 @@ use Crater\Http\Resources\CompanyResource;
 use Crater\Http\Resources\UserResource;
 use Crater\Models\Company;
 use Illuminate\Http\Request;
+use Crater\Http\Requests\AvatarRequest;
+use Crater\Http\Requests\CompanyLogoRequest;
 
 class CompanyController extends Controller
 {
@@ -58,10 +60,10 @@ class CompanyController extends Controller
     /**
      * Upload the company logo to storage.
      *
-     * @param  \Illuminate\Http\Request $request
+     * @param  \Crater\Http\Requests\CompanyLogoRequest $request
      * @return \Illuminate\Http\JsonResponse
      */
-    public function uploadCompanyLogo(Request $request)
+    public function uploadCompanyLogo(CompanyLogoRequest $request)
     {
         $company = Company::find($request->header('company'));
 
@@ -89,10 +91,10 @@ class CompanyController extends Controller
     /**
      * Upload the Admin Avatar to public storage.
      *
-     * @param  \Illuminate\Http\Request $request
+     * @param  \Crater\Http\Requests\AvatarRequest $request
      * @return \Illuminate\Http\JsonResponse
      */
-    public function uploadAvatar(Request $request)
+    public function uploadAvatar(AvatarRequest $request)
     {
         $user = auth()->user();
 

programming_language: PHP
filename: AvatarRequest.php
change_type: ModificationType.ADD
diff: @@ -0,0 +1,40 @@
+<?php
+
+namespace Crater\Http\Requests;
+
+use Crater\Rules\Base64Mime;
+use Illuminate\Foundation\Http\FormRequest;
+
+class AvatarRequest extends FormRequest
+{
+    /**
+     * Determine if the user is authorized to make this request.
+     *
+     * @return bool
+     */
+    public function authorize()
+    {
+        return true;
+    }
+
+    /**
+     * Get the validation rules that apply to the request.
+     *
+     * @return array
+     */
+    public function rules()
+    {
+        return [
+            'admin_avatar' => [
+                'nullable',
+                'file',
+                'mimes:gif,jpg,png',
+                'max:20000'
+            ],
+            'avatar' => [
+                'nullable',
+                new Base64Mime(['gif', 'jpg', 'png'])
+            ]
+        ];
+    }
+}

programming_language: PHP
filename: CompanyLogoRequest.php
change_type: ModificationType.ADD
diff: @@ -0,0 +1,34 @@
+<?php
+
+namespace Crater\Http\Requests;
+
+use Crater\Rules\Base64Mime;
+use Illuminate\Foundation\Http\FormRequest;
+
+class CompanyLogoRequest extends FormRequest
+{
+    /**
+     * Determine if the user is authorized to make this request.
+     *
+     * @return bool
+     */
+    public function authorize()
+    {
+        return true;
+    }
+
+    /**
+     * Get the validation rules that apply to the request.
+     *
+     * @return array
+     */
+    public function rules()
+    {
+        return [
+            'company_logo' => [
+                'nullable',
+                new Base64Mime(['gif', 'jpg', 'png'])
+            ]
+        ];
+    }
+}

programming_language: PHP
filename: ExpenseRequest.php
change_type: ModificationType.MODIFY
diff: @@ -51,6 +51,12 @@ class ExpenseRequest extends FormRequest
             'currency_id' => [
                 'required'
             ],
+            'attachment_receipt' => [
+                'nullable',
+                'file',
+                'mimes:jpg,png,pdf,doc,docx,xls,xlsx,ppt,pptx',
+                'max:20000'
+            ]
         ];
 
         if ($companyCurrency && $this->currency_id) {

programming_language: PHP
filename: Base64Mime.php
change_type: ModificationType.ADD
diff: @@ -0,0 +1,85 @@
+<?php
+
+namespace Crater\Rules;
+
+use Illuminate\Contracts\Validation\Rule;
+
+class Base64Mime implements Rule
+{
+    private $attribute;
+    private $extensions;
+
+    /**
+     * Create a new rule instance.
+     *
+     * @return void
+     */
+    public function __construct(array $extensions)
+    {
+        $this->extensions = $extensions;
+    }
+
+    /**
+     * Determine if the validation rule passes.
+     *
+     * @param  string  $attribute
+     * @param  mixed  $value
+     * @return bool
+     */
+    public function passes($attribute, $value)
+    {
+        $this->attribute = $attribute;
+
+        try {
+            $data = json_decode($value)->data;
+        } catch (\Exception $e) {
+            return False;
+        }
+
+        $pattern = '/^data:\w+\/[\w\+]+;base64,[\w\+\=\/]+$/';
+
+        if(!preg_match($pattern, $data)) {
+            return False;
+        }
+
+        $data = explode(',', $data);
+
+        if(!isset($data[1]) || empty($data[1])) {
+            return False;
+        }
+
+        try {
+            $data = base64_decode($data[1]);
+            $f = finfo_open();
+            $result = finfo_buffer($f, $data, FILEINFO_EXTENSION);
+
+            if($result === '???')
+                return False;
+
+            if(strpos($result, '/')) {
+                foreach(explode('/', $result) as $ext) {
+                    if(in_array($ext, $this->extensions))
+                        return True;
+                }
+            } else {
+                if(in_array($result, $this->extensions))
+                    return True;
+            }
+        } catch (\Exception $e) {
+            return False;
+        }
+        
+        return False;
+
+    }
+
+    /**
+     * Get the validation error message.
+     *
+     * @return string
+     */
+    public function message()
+    {
+        return 'The ' . $this->attribute . ' must be a json with file of type: ' . implode(', ', $this->extensions) . ' encoded in base64.';
+    }
+}

programming_language: PHP