1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-11 12:46:49 +00:00

fix(console): prevent DOM-based XSS in console views and escape JS strings

- Validate and coerce console query parameters (view_only, scale, resize_session, clip_viewport) to strict booleans via to_bool in console/views.py
- Sanitize view parameter to strictly 'lite' or 'full'
- Apply escapejs filter to all JS string interpolations in console-vnc-full.html, console-vnc-lite.html, console-spice-lite.html, and console-xterm.html
- Replace unsafe '| safe' filter in console-spice-lite.html with escapejs
- Add unit tests for XSS query param mitigation and escapejs character encoding
This commit is contained in:
catborise 2026-09-26 19:28:22 +03:00
parent 3d4accf1b4
commit f19c30840e
6 changed files with 102 additions and 35 deletions

View file

@ -65,7 +65,7 @@
// By default, use the host and port of server that served this file
// host = spice_query_var('host', window.location.hostname);
host = '{{ ws_host| safe }}';
host = '{{ ws_host|escapejs }}';
// Note that using the web server port only makes sense
// if your web server has a reverse proxy to relay the WebSocket
@ -80,7 +80,7 @@
}
}
//port = spice_query_var('port', default_port);
port = '{{ ws_port| safe }}';
port = '{{ ws_port|escapejs }}';
if (window.location.protocol == 'https:') {
scheme = "wss://";
}
@ -93,17 +93,16 @@
}
if (password === undefined) {
{% if perms.instances.passwordless_console %}
password = '{{ console_passwd }}';
{% if perms.instances.passwordless_console and console_passwd %}
password = '{{ console_passwd|escapejs }}';
{% else %}
password = prompt('{% trans "Password" %}');
{% endif %}
//password = '{{ console_passwd | safe }}';
}
if (password === 'None') password = '';
//var path = spice_query_var('path', 'websockify');
var path = spice_query_var('path', '{{ ws_path }}');
var path = spice_query_var('path', '{{ ws_path|escapejs }}');
if ((!host) || (!port)) {
console.log('{% trans "must specify host and port in URL" %}');

View file

@ -176,24 +176,24 @@
bindKeyAction('ctrlaltf' + (i + 1), () => sendCtrlAltFN(i));
}
let fullPath = '{{ ws_path }}';
const token = '{{ token }}';
let fullPath = '{{ ws_path|escapejs }}';
const token = '{{ token|escapejs }}';
if (token) {
fullPath += (fullPath.includes('?') ? '&' : '?') + 'token=' + encodeURIComponent(token);
}
const defaults = {
autoconnect: true,
resize: '{{ scale }}' === 'true' ? 'scale' : 'off',
view_only: {{ view_only }},
view_clip: {{ clip_viewport }},
resize: {{ scale|yesno:"'scale','off'" }},
view_only: {{ view_only|yesno:"true,false" }},
view_clip: {{ clip_viewport|yesno:"true,false" }},
{% if perms.instances.passwordless_console and console_passwd %}
password: '{{ console_passwd }}',
password: '{{ console_passwd|escapejs }}',
{% endif %}
};
const mandatory = {
host: '{{ ws_host }}',
host: '{{ ws_host|escapejs }}',
port: {{ ws_port }},
path: fullPath,
};

View file

@ -260,17 +260,17 @@
// Read parameters specified in the URL query string
// By default, use the host and port of server that served this file
const host = readQueryVariable('host', '{{ ws_host }}');
let port = readQueryVariable('port', '{{ ws_port }}');
const host = readQueryVariable('host', '{{ ws_host|escapejs }}');
let port = readQueryVariable('port', '{{ ws_port|escapejs }}');
{% if perms.instances.passwordless_console %}
const password = '{{ console_passwd }}';
{% if perms.instances.passwordless_console and console_passwd %}
const password = '{{ console_passwd|escapejs }}';
{% else %}
const password = readQueryVariable('password');
{% endif %}
//const path = readQueryVariable('path', 'websockify');
const path = readQueryVariable('path', '{{ ws_path }}');
const path = readQueryVariable('path', '{{ ws_path|escapejs }}');
// | | | | | |
// | | | Connect | | |
@ -294,7 +294,7 @@
} else {
url += '/';
}
const token = readQueryVariable('token', '{{ token }}');
const token = readQueryVariable('token', '{{ token|escapejs }}');
if (token) {
url += (url.includes('?') ? '&' : '?') + 'token=' + encodeURIComponent(token);
}
@ -310,10 +310,10 @@
rfb.addEventListener("desktopname", updateDesktopName);
// Set parameters that can be changed on an active connection
rfb.scaleViewport = {{ scale }};
rfb.viewOnly = {{ view_only }};
rfb.resizeSession = {{ resize_session }};
rfb.clipViewport = {{ clip_viewport }};
rfb.scaleViewport = {{ scale|yesno:"true,false" }};
rfb.viewOnly = {{ view_only|yesno:"true,false" }};
rfb.resizeSession = {{ resize_session|yesno:"true,false" }};
rfb.clipViewport = {{ clip_viewport|yesno:"true,false" }};
</script>
{% endblock %}

View file

@ -22,9 +22,9 @@
<script crossorigin="anonymous">
Terminal.applyAddon(fit);
let host = readQueryVariable('host', '{{ socketio_host }}');
let port = readQueryVariable('port', '{{ socketio_port }}');
let path = readQueryVariable('path', '{{ socketio_path }}');
let host = readQueryVariable('host', '{{ socketio_host|escapejs }}');
let port = readQueryVariable('port', '{{ socketio_port|escapejs }}');
let path = readQueryVariable('path', '{{ socketio_path|escapejs }}');
// Build the websocket URL used to connect