模拟CSRF攻击

news2025/1/10 20:30:10

今天给大家表演一个拙劣的CSRF攻击。

我会编写两个应用:一个是正经应用,一个是钓鱼的应用。然后让后者攻击前者,让它打钱!

一、绪论

1.1 先聊聊Cookie

参考:常用的本地存储——cookie篇

Cookie在八股文里面好像已经讲烂了, 就是一个服务端给客户端返回的【键值对】,这个键值对用来存储一些用户的信息(不包括密码,要不然就太不安全了),下次客户端再访问服务端的时候,带上Cookie。Cookie 是常用的本地存储(Local Stroage 、 Session Stroage 、 IndexedDB 、Cookies)的一种,用户每次访问站点时,Web应用程序都可以读取 Cookie 包含的信息。当用户再次访问这个站点时,浏览器就会在本地硬盘上查找与该 URL 相关联的 Cookie。如果该 Cookie 存在,浏览器就将它添加到request headerCookie字段中,与http请求一起发送到该站点。

Cookie 将 document 对象的cookie 属性提供给 JavaScript,可以使用JavaScript来创建和取回 cookie 的值,因此我们可以通过document.cookie访问它。

cookie是存于用户硬盘的一个文件,这个文件通常对应于一个域名,也就是说,cookie可以跨越一个域名下的多个网页,但不能跨越多个域名使用

作用包括但不限于:

  • 保存用户登录信息
  • 保存购物车数据,同一个域名下可以Cookie共享,这可以保证不同页面之间的数据同步
  • 跟踪用户行为,记录用户偏好

总之,有什么作用全看你往cookie里存什么了。

设置Cookie的方式很多,可以在前端设置,也可以在后端设置。甚至用户可以自己打开浏览器控制台通过命令来设置(除了key-value以外都不是必须设置的):

document.cookie = "myCookieKey=myCookieValue;domain=.google.com.hk;path=/webhp;expires=Sat, 04 Nov 2017 16:00:00 GMT;max-age=10800;"

通过了解Cookie的属性,我们也更能把握Cookie的工作原理,现在把每个设置项罗列一下: 

属性名释义
path默认值为"/"表示能访问的路径
domian默认值为"/",指定域下的所有路径都能访问。.google.com.hk表示不包括子域。www.google.com.hk表示包括子域。
expiresCookie 什么时候失效(被删除)
max-ageCookie 有效期到什么时候(被删除)
secure默认情况为空,不指定 secure 选项,即不论是 http 请求还是 https 请求,均会发送cookie。指定后,cookie只有在使用SSL连接(如HTTPS请求或其他安全协议请求的)时才会发送到服务器。
httponly默认情况是不指定 httponly,即可以通过 js 去访问。

1.2 再聊聊CSRF

CSRF(Cross Site Request Forgery,跨站点请求伪造),要素:

  1. 用户登录正规网站
  2. 用户访问攻击页面
  3. 攻击网站利用正规网站对用户的信用凭证伪装成用户向目标网站发起请求

关于第 2 点,用户如何访问攻击页面,那肯定是点击链接。第 3 点攻击页面要使用(注意,只是使用,因为他只是运用浏览器的特性可以使用这个Cookie而已)信用凭证(在本文中特指 Cookie),那就是用户要从正规网站上点击非法链接,这个链接可能藏在正规网站的帖子,博文中。这样,对方得到了Cookie再拿着Cookie去伪造请求,就可以达到不可告人的目的。

攻击能成功的关键:在同一个浏览器下,无论哪个页面或者网站向正规网站发送请求,请求头都会带上该正规网站域名下的 Cookie

1.3 再聊聊CSRF怎么预防

1.4 再聊聊我的模拟

有一个“银行应用”,有登录接口,登录完之后会给客户端下发Cookie,还有一个过滤器,每次调用应用的接口都会校验一下Cookie。此外,该应用还有一个转账接口。

再准备一个“钓鱼应用”,应用链接内嵌到“银行应用”的博文列表中,当用户点击钓鱼链接时,就会跳转到钓鱼应用页面。之后页面会调用“银行应用”的转账接口。

依赖:

Springboot 3.1.0 + vue3.0 + axios + jackson + mybatis+ + lombok插件

二、步骤

2.0 准备持久层相关

 

(1)数据库里两张表

一张用户表

 

一张文章列表

 (2)创建实体类

Essay.java和User.java并准备其增删改查接口(这里只用到了查询,所以直接创建就可以,非常简单)

Essay.java

@NoArgsConstructor
@AllArgsConstructor
@ToString
@Data
public class Essay {
    private Integer id;
    private String title;
    private String content;
}

User.java

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class User {
    private Integer id;
    private String username;
    private String password;
}

Servie接口UserService.java:

public interface UserService {
     User findUserByUsername(String username);
     List<Essay> getAllEssay();
}

实现UserService接口:

@Service
public class UserServiceImpl implements UserService {
    @Resource
    private UserMapper userMapper;
    @Override
    public User findUserByUsername(String username) {
        return userMapper.getUserByUsername(username);
    }
    @Override
    public List<Essay> getAllEssay() {
        return userMapper.getAllEssay();
    }
}

UserMapper.java

@Mapper
public interface UserMapper {
    List<User> getAllUsers();

    int updateCookieByUserName(@Param("cookie") String cookie);

    User getUserByUsername(@Param("username") String username);

    List<Essay> getAllEssay();
}

UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.example.imitatecsrf.mapper.UserMapper">
    <select id="getUserByUsername" resultType="com.example.imitatecsrf.model.User">
        select * from t_user where username = #{username}
    </select>
    <select id="getAllEssay" resultType="com.example.imitatecsrf.model.Essay">
        select * from essay
    </select>
</mapper>

 

2.1 准备一个被攻击应用

2.1.1 准备前端页面

这个被攻击应用是一个具备转账功能的应用,有一个粗糙的登录页面,我们访问一下:(此时还没有登录cookie也是空的)

页面代码:

<body>
<div id="app">
  <div v-if="showLoginOrNot">
    <h1>用户登录</h1>
    <br>
    <form th:method="post">
      <label for="username">用户名:</label>
      <input id="username" type="text" name="username" v-model="username" placeholder="请输入用户名">
      <br>
      <br>
      <label for="password">密码:</label>
      <input id="password" type="password" name="password" v-model="password" placeholder="请输入密码">
      <br>
      <br>
    </form>
    <button @click="submitLoginInfo()">提交</button>
  </div>
    <div v-if="!showLoginOrNot">
      <div class="essayContent" v-for="essay in essayList" :key="essay.id">
        <h1>{{essay.title}}</h1>
        <p v-html="essay.content">{{essay.content}}</p>
        <hr>
      </div>
    </div>
</div>
  <script src="https://cdn.bootcdn.net/ajax/libs/axios/1.3.6/axios.js"></script>
  <script src="/js/vue.js"></script>
  <script>
    new Vue({
      el: "#app",
      data: {
        username: "",
        password: "",
        showLoginOrNot: true,
        essayList: []
      },
      methods: {
        submitLoginInfo() {
          axios.post(
                  "/login",
                  {
                    username: document.getElementById("username").value,
                    password: document.getElementById("password").value
                  }
          ).then(response => {
            // 登录展示文章列表
            this.showLoginOrNot = false
            if (response.data.code === 200) {
               this.essayList = response.data.data
            } else {
              // 登录失败
              alert("用户名/密码错误,请重新登录")
              this.username = ""
              this.password = ""
            }
          });
        }
      }
    })
  </script>
</body>

 2.1.2 准备控制器

@RestController
@Slf4j
public class BusinessController {
    @Resource
    private UserService userService;

    @PostMapping("/login")
    public Result login(@RequestBody User user,  HttpServletResponse response) {
        // 每次登录都给客户端发放一个新的cookie
        String username = user.getUsername();
        String password = user.getPassword();
        if (userService.findUserByUsername(username) == null || !userService.findUserByUsername(username).getPassword().equals(password)) return Result.fail();
        // 登录成功
        String cookieKey = user.getUsername();
        String cookieValue = String.valueOf(userService.findUserByUsername(username).getId());
        System.out.println(cookieValue + " " + cookieKey);
        Cookie cookie = new Cookie(cookieKey, cookieValue);
        cookie.setMaxAge(24 * 60 * 60 * 60);
        response.addCookie(cookie);
        log.info("用户{}登录成功,密码为{},Cookie:{}-{}设置完毕", user.getUsername(), user.getPassword(), cookieValue, cookieKey);

        // 把文章列表也返回给用户
        List<Essay> essays = userService.getAllEssay();
        return Result.ok(essays);

    }

    @GetMapping("/transfer")
    public String transfer(@RequestParam Integer money, @RequestParam String to) {
        return "向" + to + "转账人民币:" + money + "元";
    }
}

2.1.3 准备拦截器和视图控制器

@Slf4j
public class PassPortInterceptor implements HandlerInterceptor {
    private UserService userService;
    public PassPortInterceptor(UserService userService) {
        this.userService = userService;
    }
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 拦截请求
        Cookie[] myCookie = request.getCookies();
        if (myCookie == null)   {
            log.info("无cookie");
            return false;
        }
        log.info("客户端携带的Cookie有" + myCookie.length + "项");
        for (int i = 0; i < myCookie.length; i++) {
            log.info("第{}个为{}-{}", i + 1, myCookie[i].getName(), myCookie[i].getValue());
            String username = myCookie[i].getName();
            Integer userId = Integer.valueOf(myCookie[i].getValue());
            // cookie校验成功,放行
            if (userService.findUserByUsername(username) != null
                    && userService.findUserByUsername(username).getId() == userId) {
                return true;
            }
        }
        return false;
    }
}

配置拦截器和视图控制器:

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
    @Resource
    UserService userService;
    
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/loginPage").setViewName("loginPage");
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new PassPortInterceptor(userService)).excludePathPatterns("/loginPage", "/login");
    }
}

2.1.4  测试一下

(1)测试登录接口

登录成功,并且向用户展示了文章列表,而且也成功地给客户的浏览器中添加了cookie:

测试成功 

 (2) 测试转账接口/transfer

直接调用:/transfer?to=张三&money=50000

得到:

 

 

测试成功

(3)测试拦截器是否工作正常

在没有登录(没有cookie)的情况下 ,还能不能调用 /transfer 接口?我先删除cookie,然后再次调用 /transfer?to=张三&money=50000

 因为我没有设置被拦截之后重定向,所以得到了一片空白,测试成功

2.2 准备一个攻击应用

攻击应用就很简单啦,用SpringBoot脚手架生成一个应用,开一个新端口,然后创建一个index页面:

<body>
<form method="get" action="http://127.0.0.1:8080/transfer">
    <h1>这是一个抽奖页面</h1>
    <br>
  <input type="hidden" name="money" value="9999">
  <input type="hidden" name="to" value="hacker">
  <input type="button" onclick="submit()" value="开始抽奖">
</form>
</body>

页面长这样:

2.3 发起攻击

一个正规网站上怎么会出现钓鱼链接?比如说,这个网站上有帖子啊,帖子是大家发的,里面就可能会有钓鱼链接。比如我就在一个帖子里放了我的钓鱼链接:

 <p>参考 <a href="http://127.0.0.1:9000/" ref="nofollow noopener noreferrer">点我抽奖</a>          </p>

用户看到的效果是(圈起来的就是我放的链接):

 当我们一点击这个链接,就会跳转到抽奖页面,当我们一点击“开始抽奖”,钓鱼网站就会帮我们调用 /transfer 接口,使用我们的cookie去正规网站给自己转钱,效果是这样子的:

然后我就被转走了9999元 

三、总结

3.1 CSRF不会有跨域问题吗?

一般来说,跨域就是 协议&域名&端口号 有一个不一致就是跨域了,我上面那两个应用端口是不一致的,那不就跨域了吗?

谁在阻止跨域?是浏览器处于安全策略阻止非同源请求。事实上,每次跨域请求都是正常发送的,服务端也会正常返回,只是被浏览器拦截了。所以每次请求都会到达服务端。

也就是说,至少攻击页面的表单请求是可以发出的,而且由于使用了目标网站认可的cookie,请求会被响应。

当浏览器收到服务端响应的数据时,会判断给数据的源和当前页面的源是否同源。针对不同的源,如果后端没有做相应的处理,则会被浏览器过滤。

再一个就是我从银行网站跳转到攻击网站,从攻击网站跳转回银行网站,这里是两次跨域,为什么没有被同源策略阻止呢?

因为同源策略一般限制三种行为:

(1) Cookie、LocalStorage 和 IndexDB 无法读取。

(2) DOM 无法获得。

(3) AJAX 请求不能发送。

第一次,我只是单纯地做了页面跳转,是页面跳转,而不是AJAX,所以不受限制。第二次,我向非同源页面发送请求,既不是AJAX请求,也没有返回DOM,也不读取Cookie所以不会受到限制。

最后最后,为什么浏览器会在访问钓鱼页面的时候带上银行应用的Cookie?因为浏览器对于Cookie使用的同源策略为:

浏览器使用 cookie 情况主要包括以下几点:

  1. 除了跨域 XHR 请求情况下,浏览器在发起请求的时候会把符合要求的 cookie 自动带上。(域名,有效期,路径,secure 属性)

  2. 跨域 XHR 的请求的情况下,也可以携带 Cookie。

  3. 浏览器允许跨域提交表单

  也就是说,浏览器中有页面或网站向某个域名发送请求时,其请求都会自动带上该域名下的所有 cookie。

参考文献:

csrf攻击模拟_模拟csrf攻击_涛歌依旧的博客-CSDN博客

常用的本地存储——cookie篇

Web安全之CSRF攻击的防御措施

关于跨域与CSRF的那些小事

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/717281.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

模板类的开发

模板类的开发 栈定长数组变长数组 栈 入栈和出栈使用引用是为了传递参数 注意构造函数初始化列表使用模板的写法 注意析构函数delete指针需要 [ ] 测试 定长数组 重载了括号运算符 调用的其实是数组 使用int 使用char 变长数组

比亚迪车载Android开发岗三面经历~

前言 首先&#xff0c;我想说一下我为什么会想去比亚迪这样的车企做车载Android开发。我是一名有5年经验的Android开发工程师&#xff0c;之前一直在互联网软件公司工作&#xff0c;做过移动端App和IoT产品的开发。但我一直对汽车领域很感兴趣&#xff0c;也希望自己的技术能应…

TOT(Tree of Thought) | GPT-4+dfs搜索算法提升大模型复杂问题解决能力

大家好&#xff0c;我是HxShine。 今天分享一篇普林斯顿大学的一篇文章&#xff0c;Tree of Thoughts: Deliberate Problem Solving with Large Language Models[1]&#xff1a;思维之树:用大型语言模型解决复杂问题。 这篇工作还是非常有借鉴意义的&#xff0c;OpenAI的Andr…

2023年07月在线IDE流行度最新排名

点击查看最新在线IDE流行度最新排名&#xff08;每月更新&#xff09; 2023年07月在线IDE流行度最新排名 TOP 在线IDE排名是通过分析在线ide名称在谷歌上被搜索的频率而创建的 在线IDE被搜索的次数越多&#xff0c;人们就会认为它越受欢迎。原始数据来自谷歌Trends 如果您相…

深度学习基础

1 机器学习、深度学习、人工智能 1.1 机器学习 机器学习是一门专门研究计算机怎样模拟或实现人类的学习行为&#xff0c;以获取新的知识或技能&#xff0c;重新组织已有的知识结构使之不断改善自身性能的学科。 基本步骤&#xff1a;获取数据、数据预处理、特征提取、特征选择…

postman几种常见的请求方式

1、get请求直接拼URL形式 对于http接口&#xff0c;有get和post两种请求方式&#xff0c;当接口说明中未明确post中入参必须是json串时&#xff0c;均可用url方式请求 参数既可以写到URL中&#xff0c;也可写到参数列表中&#xff0c;都一样&#xff0c;请求时候都是拼URL 2&am…

【win怎么给两个屏幕设置不同壁纸】

在现在经济的发展下&#xff0c;多数用户都拥有了两个屏幕&#xff0c;那么如何在不适用壁纸软件的情况下&#xff0c;将两个屏幕设置成不同的壁纸呢&#xff1f; 操作 首先将需要进行设置的图片选好&#xff0c;将其保存到桌面上&#xff0c;紧接着框选两张图片&#xff0c;…

探索Gradio库中的Image模块及其强大功能

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

84、基于stm32单片机超市自助存储柜快递箱系统设计(程序+原理图+流程图+参考论文+开题报告+任务书+设计资料+元器件清单等)

单片机主芯片选择方案 方案一&#xff1a;AT89C51是美国ATMEL公司生产的低电压&#xff0c;高性能CMOS型8位单片机&#xff0c;器件采用ATMEL公司的高密度、非易失性存储技术生产&#xff0c;兼容标准MCS-51指令系统&#xff0c;片内置通用8位中央处理器(CPU)和Flash存储单元&a…

Go程序结构- package和import

1、包和文件 在Go语言中包的作用和其他语言中的库或模块的作用类似&#xff0c;用于支持模块化、封装、编译隔离和重用。关键点如下&#xff1a; (1)包中保存一个或者多个.go结尾的文件&#xff0c;而包的目录就是包的导入路径 (2)中Go中通过一条简单的规则来管理标识符是否对外…

下个版本已定!C++自救新动作!

自去年年底&#xff0c;美国安全局&#xff08;NSA&#xff09;在其所发布的《Software Memory Safety》报告中点名批评C之后&#xff0c;C之父Bjarne Stroustrup一顿回怼后&#xff0c;做出决定&#xff1a;内部自救。现在&#xff0c;就让我们看看下一个版本的C&#xff0c;究…

【Go】Go 语言教程--语言变量(五)

往期教程&#xff1a; Go 语言教程–介绍&#xff08;一&#xff09;Go 语言教程–语言结构&#xff08;二&#xff09;Go 语言教程–语言结构&#xff08;三&#xff09;Go 语言教程–数据类型&#xff08;四&#xff09; 文章目录 变量声明多变量声明值类型和引用类型简短形…

【微服务】springboot 适配多数据源设计与实现

目录 一、问题背景 1.1 mysql读写分离 1.2 适配多种类型数据库 1.3 多数据源 二、适配多数据源场景和问题 2.1 支持快速切换其他数据源 2.2 代码层面最小化改造 2.3 数据迁移问题 2.4 跨库事务问题 三、多数据源适配解决方案 3.1 自己造轮子 3.2 基于providerId方式…

年少轻狂,中年失意,晚年凄惨的杜甫

诗圣杜甫的一生&#xff0c;几乎和苦难、倒霉紧紧拴在了一起。 裘马轻狂&#xff0c;恣意漫游的青年 公元712年&#xff0c;发生了两件值得历史铭记的大事情。第一件事&#xff0c;唐玄宗在这一年继位&#xff1b;第二件事&#xff0c;伟大的诗人杜甫在这一年出生。 杜甫字子…

openstack平台IsolatedHostsFilter的使用记录

文章目录 前言已有的经验思路一&#xff1a;image元数据思路二&#xff1a;flavor元数据思路三、IsolatedHostsFilter&#xff1a;使用filter来限制总结 前言 甲方的云平台新到了一些海光的机器&#xff0c;希望能加入到已有的计算集群里面。问题不大&#xff0c;但是有些小的…

怎么开发zblog插件?

要开发 ZBlog 插件&#xff0c;可以按照以下步骤进行&#xff1a; 1. 创建插件目录&#xff1a;在 ZBlog 的插件目录中创建一个新的目录&#xff0c;目录名称即为插件的名称&#xff0c;例如 "myplugin"。 2. 创建插件入口文件&#xff1a;在插件目录下创建一个 PHP …

Android Studio实现内容丰富的安卓宿舍管理平台

如需源码可以添加q-------3290510686&#xff0c;也有演示视频演示具体功能&#xff0c;源码不免费&#xff0c;尊重创作&#xff0c;尊重劳动。 项目编号086 1.开发环境 android stuido jdk1.8 eclipse mysql tomcat 2.功能介绍 安卓端&#xff1a; 1.注册登录 2.查看公告 3.报…

docker 教程笔记,win11

1、如何执行container 当git clone一个带Dockerfile的项目时&#xff0c;先打开终端&#xff0c;跳转到该项目文件夹。 使用如下命令构建镜像image docker build -t project_name . 注&#xff1a; project_name为该项目名&#xff0c;后面必须要有空格和点。 执行完毕后&#…

「TCG 规范解读」TCG 规范架构概述(下)

修订历史: 2023.3.4 2023.6.18 2023.7.2 可信计算组织(Ttrusted Computing Group, TCG)是一个非盈利的工业标准组织,它的宗旨是加强不同计算机平台上计算环境的安全性。TCG 于 2003 年春成立,并采纳了由可信计算平台联盟(the Trusted Computing Platform Alliance, TCPA)…

python函数的基本定义

python定义函数的规则&#xff1a; 函数代码块以 def 关键词开头&#xff0c;后接函数标识符名称和圆括号()&#xff1b; 任何传入参数和自变量必须放在圆括号中间。圆括号之间可以用于定义参数&#xff1b; 函数的第一行语句可以选择性地使用文档字符串—用于存放函数…