实现 CSRF 攻击简单示例
CSRF 简例
CSRF与 XSS 攻击经常在公司安全检查中出现,部分项目组也会来找我询问如何解决。网上关于其概念的介绍很多,但终感觉纸上得来终觉浅,决定写一个简单的例子来运行一下来了解其攻击方式,来更深刻理解其原理。关于其概念,这篇英文文章 有比较全面介绍,中文的访问不到,在这里不再赘述其内容,下面我们以一个简单的例子来实战一下 CSRF 攻击。
一个银行扣款的例子
假设一家银行提供了三种功能:1 查看当前额度,2 扣款。伪银行攻击其扣款功能。为代码逻辑简单只使用扣款,现实场景为转账。当用户登陆银行网站后,打开伪银行页面,在伪银行触发请求,导致银行扣款。下面实现只是能实现攻击,但攻击方案可能看起来并不完善,我想这并不影响阐述攻击方案的实现。当然这也不是骇客教学课程,能够阐述攻击方式足以。
构建项目所涉及技术
- spring boot
- spring security
构建银行项目
- 初始化一个 spring boot 2 项目,项目名为 csrf-bank
- 添加主要依赖
org.springframework.boot spring-boot-starter-security org.springframework.boot spring-boot-starter-web
- 安全配置,并配置用户密码为u/p,spring security 默认开启 CSRF,先关闭
/** * 项目访问控制 * @author seal */@Configurationpublic class WebScurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { super.configure(http); // 关闭 CSRF 校验 http.csrf().disable(); // 前后端分离可使用的校验方式 //http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); } /** * 配置默认登陆用户名及密码 u/p 用户 * @param auth * @throws Exception */ @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("u").password("p").roles("USER"); } /** * 密码加密规则,spring boot 2.0 没有默认配置 * 这里使用的是已经过期的明文比对,建议生产不要使用 * @return */ @Bean public static NoOpPasswordEncoder passwordEncoder() { return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance(); }}
- 银行查询余额及扣款业务
/** * 银行业务 * @author seal */@RestController@RequestMapping("/bank")public class BankController { /** * 模拟存款总额为 10 */ private static Integer money = 10; /** * 获取当前存款 * @return */ @GetMapping("/money") public int getMoney(){ return money; } /** * 模拟消费一元 * @return */ @PostMapping("/money") public int putMoney(){ money -= 1; return money; }}
- 查询及扣款页面
BANK
构建伪银行业务
- 初始化一个 spring boot 2 项目,项目名为 csrf-fakebank
- 添加主要依赖
org.springframework.boot spring-boot-starter-web
- 添加一个攻击页面
FakeBankMoney
- 为同时启动两个项目,调整此项目启动端口 application.properties
server.port=8081
成功攻击演示
- 启动两个项目
- 通过 访问银行项目,u/p 登陆系统
- 点击“获取当前金额”,显示为 10
- 点击“花一元”,显示为 9,至此显示自身运行正常
- 通过 访问伪银行
- 点击“帮忙花一元”,显示 8
- 不完美的是这里的url地址改变了,但是确实在伪银行页面触发了银行系统的扣款,若这是一个转账,则就会将钱转走。
成功防御演示
- 修改 csrf-bank 项目的 WebScurityConfig 类的 configure 方法
@Overrideprotected void configure(HttpSecurity http) throws Exception { super.configure(http); // 关闭 CSRF 校验 //http.csrf().disable(); // 前后端分离可使用的校验方式 http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());}
这个配置是针对前后端分离的,CookieCsrfTokenRepository 通过在Cookie t添加 XSRF-TOKEN,在后端获取与发放值进行比较实现检验;若非前后端分离项目,与上面的文件相比则可直接注释 http.csrf().disable(); 此行即可。spring security 通过主动往 form 表单添加一个 hidden 来实现。后台在 CsrfFilter 的 doFilterInternal 实现,有兴趣的可以去查看下,需注意的是此 filter 对http请求为 "GET", "HEAD", "TRACE", "OPTIONS" 的方法不处理。这也与 Restful 契合,有次与人沟通 Restful 跟我说,我们就是用 get 方法去更新能行吗? 能行,功能能实现。但是使用大家更认可的方式,或者约定俗称的,就像 maven 一样,“约定优于配置”,更合理使用其定的规范。不知者不罪,知道了还不遵守费解。还有写代码要写的合理,不要停留在能运行,完成功能。
- 重启 csrf-bank 项目
- 通过 访问银行项目,u/p 登陆系统
- 点击“获取当前金额”,显示为 10
- 点击“花一元”,显示为 9,至此显示自身运行正常
- 通过 访问伪银行
- 点击“帮忙花一元”,显示 403 错误
总结
现在银行等许多网站都会要求输入短信验证码,与这里实现思路基本一致。希望看到此文章的各位,若有外网动态网站,建议找个安全公司给扫描下,并根据安全建议及时修复。
代码详见