PHPCMS 問答模組編輯器開放原始碼編輯功能,導致存儲型 XSS。本文複現漏洞成因,分析黑名單過濾繞過技術 (HTML5 新標籤/屬性/事件)、構建無需用戶交互的自動觸發 Payload,並給出白名單過濾、CSP、編輯器配置等多層次修復方案。
<script>alert(1)</script>
<img src=x onerror=alert(1)>
結果:均被後端過濾器移除/轉義,無法執行。
參考 HTML5 Security Cheatsheet,測試多種繞過向量:
| Payload | 觸發條件 | 瀏覽器支援 | 結果 |
|---|---|---|---|
<form id="test"></form><button form="test" formaction="javascript:alert(1)">X</button> | 需用戶點擊按鈕 | 現代瀏覽器 | ✅ 繞過 (未過濾 formaction) |
<input onfocus=alert(1) autofocus> | 頁面載入自動聚焦觸發 | IE10+, Chrome, Firefox 4.5+ | ✅ 繞過 (未過濾 onfocus/autofocus) |
<video><source onerror="alert(1)"></video> | 載入失敗觸發 | 支援 video 的瀏覽器 | ❌ 被過濾 |
<details open ontoggle=alert(1)> | 展開/折疊觸發 | Chrome, Firefox, Edge | ❌ 被過濾 |
<svg onload=alert(1)> | SVG 載入觸發 | 現代瀏覽器 | ❌ 被過濾 |
<input onfocus=... autofocus> 無需交互自動觸發<input onfocus=alert(1) autofocus>
頁面載入時,瀏覽器自動聚焦該輸入框 → 觸發 onfocus → 執行 JS。無需用戶點擊,隱蔽性極強。
eval() 執行任意代碼<input onfocus=eval(atob("YWxlcnQoZG9jdW1lbnQuY29va2llKQ==")) autofocus>
進階:構造鍵盤記錄、表單劫持、BeEF 掛馬、內網掃描等完整攻擊鏈。
<?php
// 簡化版黑名單過濾邏輯
function htmlFilter($html) {
$blacklist = [
'/<script/i', '/on\w+\s*=/i', '/javascript:/i', '/vbscript:/i',
'/<iframe/i', '/<object/i', '/<embed/i', '/<applet/i',
'/expression\s*\(/i', '/url\s*\(/i', '/@import/i'
];
foreach ($blacklist as $pattern) {
$html = preg_replace($pattern, '', $html);
}
return $html;
}
?>
video, audio, canvas, svg, details, dialog, slot, template)、屬性 (formaction, autofocus, onfocus, ontoggle, onscroll)、事件,黑名單永遠追不上<?php
/**
* 基於 HTMLPurifier 的白名單過濾 (業界標準)
* composer require ezyang/htmlpurifier
*/
require_once 'vendor/autoload.php';
function purifyHtml(string $dirtyHtml): string
{
$config = HTMLPurifier_Config::createDefault();
// 1. 定義允許的標籤與屬性 (極小集合)
$config->set('HTML.AllowedElements', [
'p', 'br', 'strong', 'em', 'u', 's', 'span', 'div',
'ul', 'ol', 'li', 'blockquote', 'pre', 'code',
'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
'a', 'img', 'table', 'thead', 'tbody', 'tr', 'th', 'td',
]);
$config->set('HTML.AllowedAttributes', [
'a.href', 'a.title', 'a.target', 'a.rel',
'img.src', 'img.alt', 'img.width', 'img.height', 'img.title',
'span.style', 'div.style', 'p.style', 'td.style', 'th.style',
'td.colspan', 'td.rowspan', 'th.colspan', 'th.rowspan',
]);
// 2. 強制安全屬性
$config->set('Attr.EnableID', true);
$config->set('HTML.TargetBlank', '_blank'); // 強制 target=_blank 加 rel=noopener
$config->set('HTML.SafeIframe', false); // 禁用 iframe
$config->set('HTML.SafeObject', false); // 禁用 object/embed
$config->set('URI.AllowedSchemes', ['http' => true, 'https' => true, 'mailto' => true]);
$config->set('URI.DisableExternalResources', true); // 禁用外部圖片/資源 (可選)
// 3. CSS 白名單 (防 CSS XSS)
$config->set('CSS.AllowedProperties', [
'color', 'background-color', 'font-size', 'font-weight', 'font-family',
'text-align', 'text-decoration', 'margin', 'padding', 'border',
'width', 'height', 'max-width', 'max-height',
]);
$purifier = new HTMLPurifier($config);
return $purifier->purify($dirtyHtml);
}
// 使用
$safeHtml = purifyHtml($_POST['content']);
?>
// CKEditor 5 配置
config.htmlSupport = {
allow: [
{ name: 'p', attributes: { class: true } },
{ name: 'strong', attributes: {} },
{ name: 'a', attributes: { href: true, target: true, rel: true } },
// ... 僅允許必要標籤/屬性
]
};
config.htmlEmbed、config.pasteFilter<?php
// 模板引擎自動逃逸 (Blade, Twig, Smarty, Latte 等默認開啟)
// 原生 PHP 輸出
echo htmlspecialchars($userContent, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
// 若必須輸出「安全 HTML」(經白名單過濾後)
echo $purifiedHtml; // 不再逃逸,但已由 Purifier 保證安全
?>
# Apache / Nginx / PHP header()
Header always set Content-Security-Policy "
default-src 'self';
script-src 'self' 'nonce-{RANDOM}' 'strict-dynamic' https://cdn.example.com;
style-src 'self' 'nonce-{RANDOM}' https://fonts.googleapis.com;
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
object-src 'none';
base-uri 'self';
"
# 關鍵:'strict-dynamic' + nonce 配合,繞過舊版瀏覽器限制
# 現代框架 (Laravel, Symfony) 內建 CSP nonce 支援
<?php
// 設置 Session Cookie
ini_set('session.cookie_httponly', 1); // JS 不可讀
ini_set('session.cookie_secure', 1); // 僅 HTTPS
ini_set('session.cookie_samesite', 'Strict'); // 防 CSRF
session_start();
// 關鍵業務 Cookie
setcookie('auth_token', $token, [
'httponly' => true,
'secure' => true,
'samesite' => 'Strict',
'path' => '/',
'domain' => '.example.com',
]);
?>
文中提及:https://host/index.php?m=ask&c=team&a=team_list&order=team_point&catid=&belong=team&name="><script>alert(1)</script>
修復:控制器輸出前統一 htmlspecialchars、模板引擎自動逃逸、路由參數驗證 (白名單/正則)。
echo $var安全不是功能,是過程。將 XSS 防禦納入 SDLC 每個環節,才能從根本上消除此類漏洞。