From c90f8a05bf792e61db250f210834cdabcc0b7906 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Mon, 4 May 2026 16:36:33 +0200 Subject: fix(security): sanitize remote IP headers and escape log viewer output #4135 --- web/html/component/aCustomStatistic.html | 2 +- web/html/component/aPersianDatepicker.html | 2 +- web/html/component/aSidebar.html | 2 +- web/html/component/aTableSortable.html | 2 +- web/html/component/aThemeSwitch.html | 4 +-- web/html/form/inbound.html | 28 ++++++++--------- web/html/form/protocol/dokodemo.html | 2 +- web/html/form/protocol/hysteria.html | 2 +- web/html/form/protocol/shadowsocks.html | 2 +- web/html/form/protocol/trojan.html | 2 +- web/html/form/protocol/vless.html | 2 +- web/html/form/protocol/vmess.html | 2 +- web/html/form/stream/stream_settings.html | 18 +++++------ web/html/form/tls_settings.html | 2 +- web/html/inbounds.html | 16 +++++----- web/html/index.html | 50 ++++++++++++++++++++---------- web/html/login.html | 6 +++- web/html/modals/client_modal.html | 2 +- web/html/modals/inbound_modal.html | 2 +- web/html/modals/xray_outbound_modal.html | 2 +- web/html/settings.html | 2 +- web/html/xray.html | 18 +++++------ 22 files changed, 95 insertions(+), 75 deletions(-) (limited to 'web/html') diff --git a/web/html/component/aCustomStatistic.html b/web/html/component/aCustomStatistic.html index e9bfe83b..be20a39a 100644 --- a/web/html/component/aCustomStatistic.html +++ b/web/html/component/aCustomStatistic.html @@ -38,7 +38,7 @@ required: false } }, - template: `{{template "component/customStatistic"}}`, + template: `{{template "component/customStatistic" .}}`, }); {{end}} \ No newline at end of file diff --git a/web/html/component/aPersianDatepicker.html b/web/html/component/aPersianDatepicker.html index e8b09b92..cb4c2918 100644 --- a/web/html/component/aPersianDatepicker.html +++ b/web/html/component/aPersianDatepicker.html @@ -34,7 +34,7 @@ required: false, }, }, - template: `{{template "component/persianDatepickerTemplate"}}`, + template: `{{template "component/persianDatepickerTemplate" .}}`, data() { return { date: '', diff --git a/web/html/component/aSidebar.html b/web/html/component/aSidebar.html index 9c89a96d..08b39dc3 100644 --- a/web/html/component/aSidebar.html +++ b/web/html/component/aSidebar.html @@ -96,7 +96,7 @@ } } }, - template: `{{template "component/sidebar/content"}}`, + template: `{{template "component/sidebar/content" .}}`, }); {{end}} \ No newline at end of file diff --git a/web/html/component/aTableSortable.html b/web/html/component/aTableSortable.html index b3606527..925adbb5 100644 --- a/web/html/component/aTableSortable.html +++ b/web/html/component/aTableSortable.html @@ -175,7 +175,7 @@ } }); Vue.component('a-table-sort-trigger', { - template: `{{template "component/sortableTableTrigger"}}`, + template: `{{template "component/sortableTableTrigger" .}}`, props: { 'item-index': { type: undefined, diff --git a/web/html/component/aThemeSwitch.html b/web/html/component/aThemeSwitch.html index 2107e5a8..2712b1f7 100644 --- a/web/html/component/aThemeSwitch.html +++ b/web/html/component/aThemeSwitch.html @@ -95,7 +95,7 @@ } const themeSwitcher = createThemeSwitcher(); Vue.component('a-theme-switch', { - template: `{{template "component/themeSwitchTemplate"}}`, + template: `{{template "component/themeSwitchTemplate" .}}`, data: () => ({ themeSwitcher }), @@ -107,7 +107,7 @@ } }); Vue.component('a-theme-switch-login', { - template: `{{template "component/themeSwitchTemplateLogin"}}`, + template: `{{template "component/themeSwitchTemplateLogin" .}}`, data: () => ({ themeSwitcher }), diff --git a/web/html/form/inbound.html b/web/html/form/inbound.html index 736a1fd4..61d7bc57 100644 --- a/web/html/form/inbound.html +++ b/web/html/form/inbound.html @@ -102,69 +102,69 @@ - {{template "form/sniffing"}} + {{template "form/sniffing" .}} diff --git a/web/html/form/protocol/dokodemo.html b/web/html/form/protocol/dokodemo.html index 4437a3e3..1dbace29 100644 --- a/web/html/form/protocol/dokodemo.html +++ b/web/html/form/protocol/dokodemo.html @@ -32,6 +32,6 @@ {{end}} \ No newline at end of file diff --git a/web/html/form/protocol/hysteria.html b/web/html/form/protocol/hysteria.html index 557ebb43..5613dfc5 100644 --- a/web/html/form/protocol/hysteria.html +++ b/web/html/form/protocol/hysteria.html @@ -1,7 +1,7 @@ {{define "form/hysteria"}} - {{template "form/client"}} + {{template "form/client" .}} diff --git a/web/html/form/protocol/shadowsocks.html b/web/html/form/protocol/shadowsocks.html index 8112222c..12371399 100644 --- a/web/html/form/protocol/shadowsocks.html +++ b/web/html/form/protocol/shadowsocks.html @@ -2,7 +2,7 @@ @@ -668,13 +668,13 @@ {{template "component/aThemeSwitch" .}} {{template "component/aCustomStatistic" .}} {{template "component/aPersianDatepicker" .}} -{{template "modals/inboundModal"}} -{{template "modals/promptModal"}} -{{template "modals/qrcodeModal"}} -{{template "modals/textModal"}} -{{template "modals/inboundInfoModal"}} -{{template "modals/clientsModal"}} -{{template "modals/clientsBulkModal"}} +{{template "modals/inboundModal" .}} +{{template "modals/promptModal" .}} +{{template "modals/qrcodeModal" .}} +{{template "modals/textModal" .}} +{{template "modals/inboundInfoModal" .}} +{{template "modals/clientsModal" .}} +{{template "modals/clientsBulkModal" .}} // Tiny Sparkline component using an inline SVG polyline Vue.component('sparkline', { @@ -963,6 +963,18 @@ }, }; + const escapeHtml = (value) => { + if (value === null || value === undefined) { + return ''; + } + return String(value) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + }; + const logModal = { visible: false, logs: [], @@ -986,24 +998,28 @@ if (index > 0) formattedLogs += '
'; if (parts.length === 3) { - const d = parts[0]; - const t = parts[1]; - const level = parts[2]; - const levelIndex = levels.indexOf(level, levels) || 5; + const d = escapeHtml(parts[0]); + const t = escapeHtml(parts[1]); + const levelRaw = parts[2]; + const level = escapeHtml(levelRaw); + const idx = levels.indexOf(levelRaw); + const levelIndex = idx >= 0 ? idx : 5; //formattedLogs += `${index + 1}.`; formattedLogs += `${d} ${t} `; formattedLogs += `${level}`; } else { - const levelIndex = levels.indexOf(data, levels) || 5; - formattedLogs += `${data}`; + const idx = levels.indexOf(data); + const levelIndex = idx >= 0 ? idx : 5; + formattedLogs += `${escapeHtml(data)}`; } if (message) { - if (message.startsWith("XRAY:")) - message = "XRAY: " + message.substring(5); - else - message = "X-UI: " + message; + if (message.startsWith("XRAY:")) { + message = "XRAY: " + escapeHtml(message.substring(5)); + } else { + message = "X-UI: " + escapeHtml(message); + } } formattedLogs += message ? ' - ' + message : ''; @@ -1063,16 +1079,16 @@ let text = ``; if (log.Email !== "") { - text = `${log.Email}`; + text = `${escapeHtml(log.Email)}`; } formattedLogs += ` - ${IntlUtil.formatDate(log.DateTime)} - ${log.FromAddress} - ${log.ToAddress} - ${log.Inbound} - ${log.Outbound} + ${escapeHtml(IntlUtil.formatDate(log.DateTime))} + ${escapeHtml(log.FromAddress)} + ${escapeHtml(log.ToAddress)} + ${escapeHtml(log.Inbound)} + ${escapeHtml(log.Outbound)} ${text} `; diff --git a/web/html/login.html b/web/html/login.html index 78bffd30..2e03a4c5 100644 --- a/web/html/login.html +++ b/web/html/login.html @@ -150,7 +150,11 @@ }, initHeadline() { const animationDelay = 2000; - const headlines = this.$el.querySelectorAll('.headline'); + const rootEl = this.$el instanceof Element ? this.$el : document.getElementById('app'); + if (!rootEl || typeof rootEl.querySelectorAll !== 'function') { + return; + } + const headlines = rootEl.querySelectorAll('.headline'); headlines.forEach((headline) => { const first = headline.querySelector('.is-visible'); if (!first) return; diff --git a/web/html/modals/client_modal.html b/web/html/modals/client_modal.html index f66c01e6..65a481f6 100644 --- a/web/html/modals/client_modal.html +++ b/web/html/modals/client_modal.html @@ -7,7 +7,7 @@ :style="{ marginBottom: '10px', display: 'block', textAlign: 'center' }">Account is (Expired|Traffic Ended) And Disabled - {{template "form/client"}} + {{template "form/client" .}}