VYPR
Moderate severityNVD Advisory· Published Jan 27, 2022· Updated Aug 2, 2024

Cross-site Scripting (XSS) - Stored in crater-invoice/crater

CVE-2022-0372

Description

Cross-site Scripting (XSS) - Stored in Packagist bytefury/crater prior to 6.0.2.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Stored XSS in Crater invoice app prior to 6.0.2 via unrestricted file upload allows attackers to execute arbitrary JavaScript in victim browsers.

Vulnerability

Stored cross-site scripting (XSS) vulnerability in Crater, an open-source invoicing application, prior to version 6.0.2. The vulnerability exists in the file upload functionality, specifically in the UploadReceiptController and CompanyController which did not validate uploaded file types. An attacker could upload a malicious file (e.g., an SVG with embedded JavaScript) that would be stored and later served to other users. The fix introduced custom request classes (ExpenseRequest, AvatarRequest, CompanyLogoRequest) to enforce file type validation [1][2].

Exploitation

An attacker with the ability to upload files (e.g., as an authenticated user with permission to create expenses or update company settings) can upload a crafted file containing JavaScript. The file is stored on the server and when other users (including administrators) view the uploaded file (e.g., viewing an expense receipt or company logo), the malicious script executes in their browser context. No additional user interaction beyond viewing the page is required [1][2].

Impact

Successful exploitation allows an attacker to execute arbitrary JavaScript in the context of the victim's session. This can lead to theft of session cookies, account takeover, defacement, or other actions performed on behalf of the victim. The vulnerability is classified as stored XSS with a CVSS score of 6.1 (Medium) [3].

Mitigation

The vulnerability is fixed in Crater version 6.0.2, released on 2022-01-27. Users should upgrade to version 6.0.2 or later. The fix enforces file type validation using dedicated request classes. No workaround is available for earlier versions. The CVE is not listed in CISA's Known Exploited Vulnerabilities catalog [1][2][3].

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
bytefury/craterPackagist
< 6.0.06.0.0

Affected products

2

Patches

1
cdc913d16cf6

Unrestricted php file upload fix (#681)

https://github.com/crater-invoice/cratertheWorstComradeDec 29, 2021via ghsa
7 files changed · +176 8
  • app/Http/Controllers/V1/Admin/Expense/ExpensesController.php+2 2 modified
    @@ -39,7 +39,7 @@ public function index(Request $request)
         /**
          * 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 @@ public function show(Expense $expense)
         /**
          * 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
          */
    
  • app/Http/Controllers/V1/Admin/Expense/UploadReceiptController.php+3 2 modified
    @@ -5,17 +5,18 @@
     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);
     
    
  • app/Http/Controllers/V1/Admin/Settings/CompanyController.php+6 4 modified
    @@ -9,6 +9,8 @@
     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 @@ public function updateCompany(CompanyRequest $request)
         /**
          * 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 @@ public function uploadCompanyLogo(Request $request)
         /**
          * 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();
     
    
  • app/Http/Requests/AvatarRequest.php+40 0 added
    @@ -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'])
    +            ]
    +        ];
    +    }
    +}
    
  • app/Http/Requests/CompanyLogoRequest.php+34 0 added
    @@ -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'])
    +            ]
    +        ];
    +    }
    +}
    
  • app/Http/Requests/ExpenseRequest.php+6 0 modified
    @@ -51,6 +51,12 @@ public function rules()
                 'currency_id' => [
                     'required'
                 ],
    +            'attachment_receipt' => [
    +                'nullable',
    +                'file',
    +                'mimes:jpg,png,pdf,doc,docx,xls,xlsx,ppt,pptx',
    +                'max:20000'
    +            ]
             ];
     
             if ($companyCurrency && $this->currency_id) {
    
  • app/Rules/Base64Mime.php+85 0 added
    @@ -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.';
    +    }
    +}
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.