Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

倒霉蛋李建国 #17

Open
brunoyang opened this issue Apr 26, 2016 · 2 comments
Open

倒霉蛋李建国 #17

brunoyang opened this issue Apr 26, 2016 · 2 comments

Comments

@brunoyang
Copy link
Owner

brunoyang commented Apr 26, 2016

李建国是个倒霉蛋,小时候爬树摔断腿,考试抄错题;长大了骑车被碰瓷,吃饭吃出钢丝球。

一天,李建国打开了某网银要转1000块给王红霞,转账当然是要登录的。转完账后,李建国关闭了 tab 页。随后,李建国打开了不可描述的网站,半分钟后关闭了这个网站。突然,李建国的手机收到银行发来的两条短信,一条是转给王红霞的1000,另一条是不知道转给谁的10000。李建国慌了,他知道网银被盗了。他很疑惑,我啥也没干,咋就被盗了捏,难道见鬼了。

当然,李建国没见鬼,他只是碰上了 CSRF 漏洞。

所谓 CSRF(Cross-site request forgery),跨站攻击,指的是攻击者盗用你的身份,向某个网站发起恶意请求。

我们看李建国的例子,被盗分这么几步:

  1. 浏览并登录网银,在网银的域名下留下 cookie 信息;
  2. 发起转账,假设银行转账接口是 GET 请求的http://bank.com/transfer/1000/to/wang-hong-xia
  3. 浏览恶意网站,恶意网站上有张图<img src="http://bank.com/transfer/10000/to/huai-yin" />;
  4. 该请求带上还未过期的 cookie 信息,将10000转给了坏银。

这个漏洞能够成立,基于以下事实:

  1. 服务器是通过请求中的 cookie 信息辨认用户的;
  2. 浏览器关闭tab页,并不会立即清除保存在本地的 cookie, 同时服务器上保留的会话信息在过期之前不会清除,除非用户关闭tab之前主动登出;
  3. 在B页面上发起A页面上的请求,该请求会带上A域名下的 cookie。

有了以上的信息,CSRF 漏洞就能成立了。


银行知道了该信息后,紧急组织专家堵漏洞。

银行的转账接口使用 GET 请求,这是严重的错误,因为 GET 请求应该是幂等的,不管调用多少次都是一个结果。于是把银行把接口升级为 POST 请求。

恶意网站也升级了,每次访问都会发起 POST 请求。李建国又被盗了10000。

银行知道了该信息后,又紧急组织专家堵漏洞。

这次他们开始校验请求头中的 referer 信息,因为 referer 信息记录了该请求从哪个域名发出。

恶意网站又升级了,这次他们加了一个代理服务器,在请求发给银行之前先通过代理服务器修改referer信息。李建国再次被盗了10000。

银行知道了该信息后,再次紧急组织专家堵漏洞。

这次他们给表单上增加一个隐藏域,<input type="hidden" value="23kh4acsdudesfr45hoiad" name="ctoken" />,在每次表单提交时都带上 ctoken。

恶意网站发现升级也没用了,就去寻找下一个漏洞了……

防范 CSRF

防范 CSRF 最有效的方式,就是每次提交都要求手动输入验证码,但这样的用户体验很差。现在应用最广泛的就是为提交的表单增加伪随机字段。在服务器上生成一串随机字符串,带到页面上并把随机码保存起来。在用户提交回的表单中取出随机码并与服务器上保存的作对比,如果匹配,那就是合法的请求;要是不匹配,就可以认为这是一个非法的请求。

koa-csrf

基于 [email protected]。以下 koa-csrf 简称为kcsrf。

先来看最基本用法

const koa = require('koa');
const csrf = require('koa-csrf');
const session = require('koa-generic-session');

const app = koa();

app.keys = ['session secret'];
app.use(session());
csrf(app);
app.use(csrf.middleware);

app.use(function* () {
  if (this.method === 'GET') {
    this.body = this.csrf;
  } else if (this.method === 'POST') {
    this.status = 204;
  }
});

app.listen(3000);

kcsrf 依托于 session,所以我们引入了koa-generic-session。kcsrf 接受一个配置项,可以传入 saltLength 和 secretLength,分别为盐长度和token长度。这两个参数被透传给 csrf 模块,用于生成 token。

该模块很简单,通过定义一个 getter 方法,通过如下形式传给模板,用于生成html页面。

app.use(function *() {
  this.render({
    csrf: this.csrf
  });
});

而在 getter 方法内部,生成并返回token的同时,还往 session 上增加了一个 secret 字段,用以保存生成的token。

在第二次请求过来时,就会将 token 带回来,位置可以在表单、查询串或自定义头上,将请求中的 token 取出与 session.secret 作对比,就可以判断是否为跨站攻击。

@mlyknown
Copy link

解释的很清楚,hah~~

@tonyjiafan
Copy link

叙述的方式很有趣

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants