这个指南描述的是在web应用里普遍的安全问题,同时也给出了在Rails里如何避免这些问题。如果你有任何问题,请mail作者,Heiko Webers, at 42 {et} rorsecurity.info. 读完此文后,你应该会了解:
1.所有的对策已经被高亮显示了
2.在Rails里session的概念, 该放什么在session里,以及一些流行的攻击方法
3.只是浏览一个站点,怎么就有安全问题呢?(with CSRF)
4.当你使用files或提供一个管理界面的时候需要注意些什么
5.The Rails-specific mass assignment problem
6.如何管理用户:登陆,注销以及对所有层面的攻击方法。
7.以及最流行的注入攻击方法。
一 介绍
web application框架帮助开发者建立种种web应用,某些框架在安全方面也帮你省心不少。事实上,一个框架并不比另一个安全。对于大多数的框架来 说,如果你正确使用它,可以建立安全的应用。Ruby on Rails有许多聪明的helper方法,例如防止注入攻击的方法,这让sql注入变成了困难的事情。很高兴看到我审定的所有rails app都有一个良好的安全级别。
一般没有这种即插即用的安全,安全依赖于正在使用的框架,有时候也依赖于开发方式。它取决于web应用的所有环境:后端存储,网络服务器和网络应用程序本身(以及可能的其他层或应用程序) 。
Gartner组织估计大约75%的攻击都是在web应用,并且在300个调查的web应用里,97%都是容易被攻击的。这是因为web应用容易攻击,因为它们易于理解和操作,甚至是懒人。
在 受到威胁的web应用中,包括用户帐户劫持,绕开访问控制,阅读或修改敏感数据,或出示虚假的内容。或攻击者可以安装一个特洛伊木马程序或不请自来的电子 邮件发送软件,目的是在金融活动或造成损害品牌名称,修改公司的资源。为了防止攻击,最大限度地减少其影响和消除攻击点,首先,你必须充分了解攻击方法, 以便找到正确的对策。这正是该指南的目的。
为了开发安全的Web应用你必须不断更新的所有层次和了解你的敌人。要不断更新订阅的安全邮件列表,请阅读安全博客,并更新和安全检查的习惯。我手工做这些,因为这你才能找到讨厌的合乎逻辑的安全问题。
二 Sessions
我们的安全之旅最好是从最易受到特别攻击的sessions开始。
2.1 什么是sessions
— HTTP is a stateless protocol Sessions make it stateful.
- HTTP是一个无状态协议,Sessions使它有状态。
大 多数的应用需要对一个特别用户的某些状态进行跟踪。比如,一个购物车的内容,一的当前登陆用户的id,如果没有sessions这个好主意,用户必须在每 一个请求都得去标识验证身份。如果一个新用户访问这个应用,Rails会自动创建一个新的session。如果用户之前使用过这个应用,它会自动加载一个 存在的session。
一个session通常是一个hash和一个session id(通常是一个32个字符的字符串)来标识这个hash。Rails里你可以用如下方式保存和使用session:
session[:user_id] = @current_user.id
User.find(session[:user_id])
2.2 session id
— The session id is a 32 byte long MD5 hash value.
- session id就是个一32位的md5 hash值。
一个session id由一个随机字符串的hash值组成。这个随机字符串是当前的时间,一个0和1之间的随机数字,一个ruby解释器进程id数字和一个常量字符串组成。
2.3 session劫持
- 窃取用户session id的攻击者可以在一个web应用里使用受害者的名字。
许
多web应用都有一个验证系统:一个用户提供一个用户名和密码,web应用检查并且存储相应的user
id到session的这个hash里。从现在开始,session是有效的。在每个http请求里, 应用会加载这些在session里用user
id来标识的用户,并不需要新的验证。这个session id是放在cookie里用来标识这个session的。
因此cookie作为web应用的临时验证。任何人得到一个别人的cookie,他就可以伪装为这个人使用对应的web应用-可能会有严重的后果。这里有一些session劫持的方法,以及对策:
1.在不安全的网络进行嗅探cookie。无线局域网就是这样一个例子。在这样的一个网络里,特别容易去监听所有链接的客户端, 这就是不去咖啡店工作的原因(某些sb就喜欢去星巴克打开本本装比)。对于web应用开发者,这就意味着去提供一个安全的ssl连接。
2.大多数的人在公共场所工作之后不清除cookie,所以如果是最后一个没有在web应用注销的用户,你有可能伪装成这个用户。在web应用里提供给用户一个注销按钮,并且放在醒目的位置。
3.许多跨站点脚本( XSS )攻击的目的是获得用户的cookie 。你之后会看到关于xss的更多内容。
4.Session定制,之后会读到.
大多数攻击者的目的是为了钱,失窃的银行登陆帐号的价格范围从$10 - $1000不等(取决于可用的资金数额),信用卡号码是$0.40-$20,在线拍卖网站的帐号为$1-$8,email密码为$4-$30 ,参考赛门铁克的网络安全威胁报告。
2.4 session 准则
-这里有一些session的一般准则
1.不要在session里存储大的对象。相反,你应该把它们存储在数据库里,把它们的id保存在session里。这将消除同步的麻烦,而且也不会占用你session的存储空间(取决于你选择的session的存储)
2.关键的数据不应该被存储在session里。如果用户清除了cookie或者关闭了浏览器,它们都将丢失,用一个客户端session存储,用户可以读取数据。
2.5 session存储
- Rails为session提供了很多存储机制,最重要的是ActiveRecordStore和CookieStore。
大多数实际生活的应用选择ActiveRecordStore (或其衍生物)的文件存储由于性能和维修的原因。 ActiveRecordStore保持session ID和散列在一个数据库表,在每次请求里都保存和检索这个hash。
Rails2
介绍了一种新的session存储机制, CookieStore. 它直接在客户端的cookie里保存这个session
hash。服务端从cookie里检索这个session hash,无须session id。这将大大增加速度的应用,但它是一个有争议的存储选项,你必须思考安全的影响:
1.cookie意味着严格的大小限制,4k。 这点是好的,本身就不应该存储大的数据在session里,前面也说过。存储一个当前用户的数据库id一般是没有问题的。
2.客户端能看到你存储在session里的一切。因为是明文的(实际是base64编码的),所以,你别想在这里存储任何秘密。为了防止session hash被篡改,会从服务端加一个secret到cookie的末尾。
这就意味着,这个安全存储取决于这个scret(digest算法,默认是sha512,至今没有被破解),所以不要用一个简单的scret,例如字典里的一个单词,或者比30个字符短的任意字符串。把这个scret放在environment.rb里:
config.action_controller.session = {
:session_key => ‘_app_session’,
:secret => ‘0x0dkfj3927dkc7djdh36rkckdfzsg...’
}
但是也有经过加密session hash的CookieStore派生存储机制,为了客户端不能看到它。
2.6 针对于CookieStore sessions的重放攻击
- 另一种必须要知道的攻击是针对于cookiestore攻击是重放攻击。
它的运作方式如下:
1.用户收到贷款,金额是储存在一个session里,(这是坏主意,但是我们做这些只是示范)
2.这个用户买了一些东西
3.他的new,lower credit将要被存储在session里。
4.邪恶的用户得到了他的cookie并且覆盖了当前的cookie。
5.这个用户有了他的credit, 就可以进行重放攻击了。
在
session里加一个(nonce)暂时标志(随机值)可以解决重放攻击这个问题。一个nonce仅有效一次,并且服务端保持对所有有效nonce的跟
踪,如果你有多个应用服务器,它可能甚至会更复杂,避免把nonce存储在数据库里,这样会破坏整个CookieStore。
最好的解决方法是不要存放这种数据在session里,而是在数据库里。这个例子,存储credit在数据库里,logged in user id 放在session里。
2.7 session 定制攻击
- 除了窃取用户的session id,攻击者还可能去定制一个session id来伪装,这就是所谓的session定制。
这个攻击的重点是定制一个用户的可以识别他的session id,并且强制用户的浏览器用这个id。因此后来攻击者也不需要去窃取一个session id了。来看看攻击是如何展开的:
1.攻击者创建一个有效的session id: 他登陆想定制session的那个web应用的登陆页面,这时就通过response在cookie里有一个session id了(图中的1,2)
2.他可能保持这次会话(session)。session是会过期的,比如每隔20分钟,这会大大的减少了攻击者的时间。因此,他会不断的访问这个web应用以便于保持session可用。
3.现在,攻击者会强迫用户来使用这个session id(看图里的3),因为你不能改变另一个域的cookie,攻击者不得不在目标web应用里运行一个javascript脚本。通过往目标web应用注入javascript代码来完成这次攻击。下面是一个例子:
<script> document.cookie="_session_id=16d5b78abb28e3d6206b60f22a03c8d9"; </script>
4. 攻击者引诱受害人向被感染的网页上的JavaScript代码。通过查看网页时,受害者的浏览器将把session id改变成攻击者定制好的session id。
5.因为这个陷进session id没有被用,web应用会需要用户去验证。
6.从现在开始,受害者和攻击者都会共有一个相同的session在这个web应用里。此时受害者并没有注意到这次攻击。
2.8 session 定制 --- 对策
- 一行代码就保护你不被session定制侵害。
最有效的对策就是在成功登陆以后,创建一个新的session id,并且宣告旧的session id失效。那样,攻击者就不能用固定的session id了。这是一个对方session劫持的好的对策。Rails里我们可以这样来产生一个新的session:
reset_session
如果你用流行的rails插件,RestfulAuthentication,请在SessionsController#create action里加 reset_session. 注意,这个行为消除了原session里存储的所有值,你必须把这些值得转移到新的session里。
另
一个有效的对策是,在session里保存用户的特定属性,在每次请求进来的时候验证他们,如果信息不匹配,则拒绝访问。可以用远程ip地址,或者是访问
浏览器名称,尽管后者是不常用的。当保持ip的时候要注意,应该保持用户真实ip,而不是互联网服务供应商或大型组织的ip,如果这些变化以后,某些用户
可能无法使用这个web应用,或者会有限制。
2.9 session 过期
- 永不过期的session会更加方便这些session攻击。
session过期时间在服务端设置更加安全。下面是一个例子, 如何在数据库里设置session的过期时间。如果超过20分钟就调用Session.swap(20m):
class Session < ActiveRecord::Base
def self.sweep(time_ago = nil)
time = case time_ago
when /^(d+)m$/ then Time.now - $1.to_i.minute
when /^(d+)h$/ then Time.now - $1.to_i.hour
when /^(d+)d$/ then Time.now - $1.to_i.day
else Time.now - 1.hour
end
self.delete_all "updated_at < '#{time.to_s(:db)}'"
end
end
这节讲述了session定制需要保持连接。虽然你有过期时间,但攻击者也不傻,
他会保持每隔五分钟使session可用,不过期。一个简单的解决方法是,增加一个created_at到你的session表里。现在你可以删除被创建
了很久的那些session,用下面的这行替换上面swap方法的:
self.delete_all "updated_at < '#{time.to_s(:db)}' OR created_at < '#{2.days.ago.to_s(:db)}'"
三 Cross-Site Reference Forgery (CSRF)
- 这个攻击方法包含恶意代码或是一个用户信任的已验证的web应用页面的链接。如果session没有过期,攻击者就可能执行未授权的命令。
在session那一章里,你已经了解,大多数的Rails应用都使用基于cookie的session。要么他们在cookie里存储一个session
id,服务端有个session hash,要么整个session
hash都在客户端。当向一个域名发送请求时,如果能找到这个域名的cookie,浏览器会自动附带上这个cookie。但是,问题是,来自于不同域名的
站点的请求,也会发送这个cookie。来看这个例子:
1.Bob同志浏览了一个留言板和一个由hacker制作的html标签内容。这个元素引用的是一个bob的项目管理应用程序里的命令,而不是一个图片。
2.<img src="http://www.webapp.com/project/1/destroy">
3.Bob的session在www.webapp.com还是活的,因为他刚刚离开几分钟还没有注销。
4.当浏览器发现这个img标签,他试图从www.webapp.com加载这个图像,正如前面所说,他将会发送一个带有有效session id的cookie(bob的cookie, 他刚登陆www.webapp.com)。
5.位于www.webapp.com的web应用验证了对应session hash的用户信息并且删除了id为一的那个project。之后它返回了一个出乎浏览器意料的结果,所以它没有显示图像。
6.Bob并没有注意到这次攻击,但是一些天以后,他发现id为一的那个project离他而去了。
有重要的一点要注意,实际制作的图像或链接不一定必须位于Web应用的网域,它可以在任何地方-在一个论坛,博客帖子或电子邮件。
CSRF是一个不可忽略的重要的安全问题。
3.1 CSRF对策
- 第一点,遵循W3C标准,正确使用GET和POST,第二点,在non-GET请求中使用一个安全token将使你远离CSRF.
HTTP协议提供两种类型的请求 - GET和POST(还有其他,但是大多数浏览器不支持),万维网联盟( W3C )为HTTP GET或POST提供了一个选择清单:
Use GET if:
这个Interaction更像是问题,它是一个安全操作,比如,如查询,读操作,或查阅
Use POST if:
这个Interaction更像是命令,或者
这个Interaction依用户期望的方式改变资源状态,比如订阅一个服务。
用户为这个interaction产生的结果负责。
如果你的应用是restful的,你可能会用额外的http动词,例如 PUT ,DELETE。 然而今天大多数的浏览器并不支持它们,仅仅支持GET和POST。Rails使用一个隐藏的_method来处理这一障碍(我在这篇文章http://blackanger.blog.51cto.com/140924/108678最后一段引用DHH的话也说过)。
在一个controller里的验证方法确保具体的actions不会过度使用GET.
来看一个例子,仅在transfer 这个action里使用post方法,如果使用了其他的HTTP动词,则会被重定向到list 这个action:
verify :method => :post, :only => [:transfer], :redirect_to => {:action => :list}
这个防范措施,使攻击失效,因为web应用不会接受浏览器从images发送的一个get请求。但这仅仅是第一步,因为post请求也可以被自动发送。下面的一个例子可以动态创建一个发送post请求的form表单:
<a href="http://www.harmless.com/" onclick="
var f = document.createElement('form');
f.style.display = 'none';
this.parentNode.appendChild(f);
f.method = 'POST';
f.action = 'http://www.example.com/account/destroy';
f.submit();
return false;">To the harmless survey</a>
或者,攻击者可以把这些代码放在图片的鼠标滑过事件处理里面:
<img src="http://www.harmless.com/img" width="400" height="400" onmouseover="…" />
这里还有很多可能性,包括在后台对受害者进行ajax攻击。解决的办法是在服务端对非GET请求使用security token, 在Rails2.x 版本,只需要在application controller里加一行代码:
protect_from_forgery :secret ⇒ "123456789012345678901234567890…"
它会在所有Rails里生成的表单和ajax请求里自动包含一个根据当前的session和服务端的secret计算出来的security
token。 你用CookieStorage作为session 存储,就不需要这个secret了。 如果这个security
token和所期望的不匹配,就会抛出一个ActionController::InvalidAuthenticityToken 的异常。
请注意,跨站点脚本(xss)漏洞绕过所有的CSRF保护。XSS可以让攻击者访问页面的所有元素,所以他可以从一个表单里读取CSRF Security token, 或者直接提交表单,稍后会有更多xss的内容奉献。
四 重定向和文件
另一类安全问题是围饶在web应用里重定向和文件的使用。
4.1 重定向
web应用里的重定向是一个被低估的craker工具:它不仅可以让用户掉入一个陷进网站,而且还可以创造一个完备的攻击。
当
用户被允许由一个URL重定向的时候,它由可能就是个漏洞。最明显的攻击是将用户重定向到一个和原始页面一模一样的假页面。这个所谓的‘钓鱼攻击’通过给
用户发送一封包含正常的不让人起疑的链接的email,
通过XSS方式往web应用里注射这个恶意链接或者把链接放到一个虚假的网站(域名看起来差不多,页面也相同的站点)。它是毫不让人怀疑的,因为这个链接
的起始URL是这个正常的web应用,指向恶意站点的URL都被隐藏在重定向参数里:http://www.example.com/site/redirect?to= www.attacker.com. 这里由个例子:
def legacy
redirect_to(params.update(:action=>'main'))
end
如果用户访问legacy这个action,将会被重定向到mail action。这样做的意图是为了维护legacy的参数,并把这些参数传到main action。 但是,如果它包含了一个host key,就会被攻击者利用。
http://www.example.com/site/legacy?param1=xy¶m2=23&host=www.attacker.com
如果它在url的尾部, 将很难被注意到,用户就被神不知鬼不觉的重定向到了攻击者的页面。一个简单的对策是,在legacy action里只包含预期的参数(白名单的办法,不是取消所有意向不到的参数)。如果你重定向到一个网址,你需要用白名单或是正则表达式检查它。
4.1.1 Self-contained XSS(自载的xss攻击)
另一个重定向和self-contained XSS 攻击是通过firefox和opera里使用的一种数据协议。该协议直接在浏览器里显示其内容,可以使任何内容,从html,javascript到图片。(这不是一个漏洞,而是一个功能)
data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K
这个例子是一个Base64编码的显示简单消息框的javascript。在一个重定向的URL里,一个攻击者能用它里面的恶意代码重定向这个url。
更详细的攻击原理可以参看这里:http://www.gnucitizen.org/blog/self-contained-xss-attacks/
对策:do not allow the user to supply (parts of) the URL to be redirected to.(说实话,这里我没看明白)
4.2 文件上传
- 确保文件上次不覆盖重要的文件,和同步的媒体文件进程。
很
多web应用允许用户上传文件,用户可以选择的文件名字,应该随时被过滤,因为攻击者可以用恶意的文件名覆盖服务器上任意文件。如果你在/var/www
/uploads存储上传文件,用户输入一个文件名:../../../etc/passwd, 它可能覆盖一个重要文件。
当过滤用户输
入的文件名时,不要试图去删除恶意的部分。想一想,如果去掉所有的../ , 攻击者输入.. .. //,
那么结果就成了../。最好是用白名单的方式,用一套可接受的字符去检查文件名的有效性。一旦它不是一个有效的文件名,则拒绝它(或替换它),但是不删除
它们。这有一份来自attachment_fu插件的文件名清单:
def sanitize_filename(filename)
returning filename.strip do |name|
# NOTE: File.basename doesn't work right with Windows paths on Unix
# get only the filename, not the whole path
name.gsub! /^.*(\|/)/, ''
# Finally, replace all non alphanumeric, underscore
# or periods with underscore
name.gsub! /[^w.-]/, '_'
end
end
一个重大的缺陷是,同步处理文件上传(比如attachment_fu插件处理多个图片上传),使其易受dos攻击。攻击者可以从很多电脑同时启动图像上传,从而增加服务器负载,最终让服务器崩溃。
要
解决这一点,最好是异步处理媒体文件。Save the media file and schedule a processing request
in the database. A second process will handle the processing of the
file in the background.
4.3 Executable code in file uploads
- 上传文件里的代码在特定的环境下可能被执行,如果Rails / Public 目录是Apache的主目录,请不要把文件上传到此。
如果一个攻击者上传个file.cgi, 有人下载这个文件,代码就会被执行。
4.4 File downloads
- 确保用户无法下载任意文件。
正如你必须过滤上传的文件名一样,对下载文件也应该这样做。下面这个send_file()方法发送文件到客户端,如果你用了一个文件名没有过滤, 用户输入什么都可以被下载。
send_file('/var/www/uploads/' + params[:filename])
简单的通过“../../../etc/passwd”这样一个文件名去下载服务器的登陆信息。简单的解决方式是,检查请求的文件是在允许的目录下:
basename = File.expand_path(File.join(File.dirname(__FILE__), '../../files'))
filename = File.expand_path(File.join(basename, @file.public_filename))
raise if basename =!
File.expand_path(File.join(File.dirname(filename), '../../../'))
send_file filename, :disposition => 'inline'
另外一个方式是在数据库里存储文件名。这种方式可以避免上传文件的代码被执行。attachment_fu用的就是类似的方式。
标签:Rails,
3楼 李骥平 评论于 2008-11-18 09:24:48