GitHub Security Advisory: GHSA-rgv9-w7jp-m23g
Package: label-studio-sdk (PIP)
Severity: HIGH (CVSS 0.0)
CWE: CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal'), CWE-26: Path Traversal: '/dir/../filename'
Vulnerable versions: < 1.0.10
Patched in: 1.0.10

Summary: Label Studio has a Path Traversal Vulnerability via image Field

Details:
## Description
A path traversal vulnerability in Label Studio SDK versions prior to 1.0.10 allows unauthorized file access outside the intended directory structure. Label Studio versions before 1.16.0 specified SDK versions prior to 1.0.10 as dependencies, and the issue was confirmed in Label Studio version 1.13.2.dev0; therefore, Label Studio users should upgrade to 1.16.0 or newer to mitigate it. The flaw exists in the VOC, COCO and YOLO export functionalites. These functions invoke a `download` function on the `label-studio-sdk` python package, which fails to validate file paths when processing image references during task exports:

```python
def download(
    url,
    output_dir,
    filename=None,
    project_dir=None,
    return_relative_path=False,
    upload_dir=None,
    download_resources=True,
):
    is_local_file = url.startswith("/data/") and "?d=" in url
    is_uploaded_file = url.startswith("/data/upload")

    if is_uploaded_file:
        upload_dir = _get_upload_dir(project_dir, upload_dir)
        filename = urllib.parse.unquote(url.replace("/data/upload/", ""))
        filepath = os.path.join(upload_dir, filename)
        logger.debug(
            f"Copy {filepath} to {output_dir}".format(
                filepath=filepath, output_dir=output_dir
            )
        )
        if download_resources:
            shutil.copy(filepath, output_dir)
        if return_relative_path:
            return os.path.join(
                os.path.basename(output_dir), os.path.basename(filename)
            )
        return filepath

    if is_local_file:
        filename, dir_path = url.split("/data/", 1)[-1].split("?d=")
        dir_path = str(urllib.parse.unquote(dir_path))
        filepath = os.path.join(LOCAL_FILES_DOCUMENT_ROOT, dir_path)
        if not os.path.exists(filepath):
            raise FileNotFoundError(filepath)
        if download_resources:
            shutil.copy(filepath, output_dir)
        return filepath
```

By creating tasks with path traversal sequences in the image field, an attacker can force the application to read files from arbitrary server filesystem locations when exporting projects in any of the mentioned formats.

Note that there are two different possible code paths leading to this result, one for the `is_uploaded_file` and another one for the `is_local_file`.

## Steps to Reproduce
1. Login to Label Studio
2. Create project with image labeling configuration
3. If the `data/media/upload` directory doesn't exists yet, upload an image to force the server to create it
4. Create task with path traversal in image field
   
    4.1. To trigger the `is_uploaded_file` code path:
   ```json
   {
     "data": {
       "text": "test",
       "image": "/data/upload/../../../../../etc/passwd"
     }
   }
   ```
    4.2. To trigger the `is_local_file` code path:
   ```json
   {
     "data": {
       "text": "test",
       "image": "/data/local-files/?d=../../../etc/passwd"
     }
   }
   ```
6. Export project using VOC