论坛首页 编程语言技术论坛

Rails每周一题(七): Security Guide(中)

浏览 3023 次
精华帖 (2) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2009-05-03   最后修改:2009-08-08

上篇 中介绍了对session的攻击方法以及应对策略,在此篇继续介绍其它一些对网站的攻击方法以及应对策略。在阅读本文的过程中,你会发现,其实避免很多安全问题并不困难。只是很多时候,我们并没有把安全问题这个概念放在心里。

 

 

跨站请求伪造CSRF

 

跨站请求伪造在网页上注入恶意代码或者一些恶意链接,来访问用户已被认证的网站。如果session未过期,攻击者就可以进行一些恶意的操作。

 

 

 

csrf

 

 

 

我们知道在用户访问网站时,cookie里面都会带有session id。而有争议的一点是,当这个请求来自另外一个域时,session id也会被附在cookie上。 让我们来看看一个攻击的例子:

 

1. Bob浏览一张被黑客恶意改造过的网页,此网页有一个image element,但是此element并不指向一张图片,而是链接至Bob的项目管理站点:

<img src="http://www.webapp.com/project/1/destroy">

 

2. 假设此时Bob在www.webapp.com上的session仍然有效。

3. 当Bob浏览此页面时,它会试图从www.webapp.com导入图片。同时,会附带发送带有正确session id的cookie。

4. www.webapp.com确认了用户信息,并执行了相应操作。

5. 图片未显示,同时Bob也未注意到任何攻击的发生。

 

应对策略:

 

1. 适当使用GET和POST。当操作会对数据状态进行更改,或者用户需要对操作负责时,使用POST请求。当然,在RESTful下,还有PUT和DELETE请求。通过正确应用HTTP verbs,可以避免上述问题的出现。

 

    在Rails里面,可以通过在controller里面增加类似代码:

 

 

verify :method => :post, :only => [:transfer], :redirect_to => {:action => :list} 

 

   但是黑客还是可以通过更加复杂的攻击方法来绕过这个限制,比如:

 

 

<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> 

 

2. 上面问题的解决方法是通过在所有非GET请求中包含一个security token -- 在服务器端会检查此security token。在Rails2.0以及后来的版本中,只要在application controller里面加入如下一行即可:

 

 

protect_from_forgery :secret => "123456789012345678901234567890..." 
 

加入这行后,Rails会在所有由Rails生成的form和Ajax请求中 包含security token,这个security token通过当前session和服务端的secret计算而得。如果security token验证没有通过,会抛出ActionController::InvalidAuthenticityToken异常。(注意,在使用 CookieStore session存储机制时,不需要secret。)

 

 

重定向

 

重定向至陷阱网站。

 

假如我们的controller中有这样一个action:

 

def legacy 
   redirect_to(params.update(:action=>'main')) 
end 

 

这个action是一个遗留action,它现在做的工作只是把对它的访问转向到main action。

 

此时,如果攻击者用这样一个url对此action发起请求。

 

http://www.example.com/site/legacy?param1=xy&param2=23&host=www.attacker.com 

 

那么legacy action会把请求重定向至www.attacker.com。请注意url最后的host参数。

 

应对策略:

 

通过一个白名单(白名单,而不是黑名单)对legacy action的参数进行过滤。

 

 

文件相关攻击

 

文件上传

 

—  不要让上传文件覆盖重要文件,并且注意异步处理媒体文件。

 

假设我们把上传文件存于目录“/var/www/uploads",然后用户上传了一个名字叫做“../../../etc/passwd”的文件。

 

那么passwd文件就有被覆盖的危险。(当然,Ruby解释器需要有相应的权限进行此操作才可以。从这里看出让web server,数据库等其他一些程序在一个低权限的用户上跑是多么重要。)

 

应对策略:

 

对文件名进行验证。参考attachment_fu plugin 的例子:

 

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 

 

 

拒绝服务

 

同步处理上传文件的一个很大的缺点在于它使网站易于被拒绝服务 所攻击。假如攻击者同时上传很多文件,服务器可能会被拖垮。

 

应对策略:

 

异步处理上传文件。在服务器端保存文件后,在后台安排一个进程对文件进行处理。

 

 

上传可执行文件

 

不要把上传文件置于Rails的public目录下 -- 如果这个目录是Apache的home目录。

 

Apache Web服务器有个特点,就是它会执行DocumentRoot目录下的有特定扩展名的文件,比如PHP和CGI文件。假设攻击者上传了一个file.cgi文件,那么当他下载这个文件时,这个文件就会被执行。

 

应对策略:

 

如果Rails的public目录是Apache的home目录,就不要在此目录下存储上传文件。至少把文件存于下一级目录。

 

 

文件下载

 

不要让用户下载任意文件。

 

send_file('/var/www/uploads/' + params[:filename])

 
上面这段代码可以让用户下载任意文件。假如用户输入的文件名是 “../../../etc/passwd”,那么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' 

 

 

管理员站点的安全问题

 

Admin Site具有一些超权限,所以经常成为黑客的攻击目标。对于Admin Site,要采取一些额外的安全策略。

 

1. 为了防止Admin Site受到XSS的攻击,建议使用Safe ERB 插件阻止用户恶意代码的注入。

 

2. 如果做最坏的打算 -- 管理员的用户名和密码真的被盗了。那么我们可以采取什么措施防护?比如让一些特权操作需要一些特殊的密码。

 

3. 管理站点真的需要在任何地方都可以被访问么?限制管理站点只能从来自一些特殊IP的访问。

 

4. 把管理站点置于子域比如admin.webapp.com或者一个独立域,可以防止XSS的攻击。(相同来源策略)

 

 

Mass Assignment

 

— 如果不做一些预防措施,Modle.new(params[:model])允许攻击者设置任何数据库字段的值。

假设在注册时用户传上来的参数是这样的:

 

params[:user] #=> {:name => “ow3ned”, :admin => true} 
 

那么当程序处理时,会把这个用户置为一个admin用户。

 

应对策略:

 

保护一些字段,防止它被Mass Assignment:

 

 

attr_protected :admin 

 

但这是一个黑名单,如果你新加了一个受保护字段,必须不能忘记在列表上添加上。所以更好的方法是:

 

 

attr_accessible :name 
 

字段受保护之后,你只能给它单独赋值:

 

params[:user] #=> {:name => "ow3ned", :admin => true} 
@user = User.new(params[:user]) 
@user.admin #=> false # not mass-assigned 
@user.admin = true 
@user.admin #=> true 

 

更绝对的一种做法是,在初始的时候加上这么一句:

 

ActiveRecord::Base.send(:attr_accessible, nil) 

 

它会把所有model的白名单置空,你必须对每个model显示地指定attr_accessible名单。

 

 

帐户管理

 

restful_authentication插件的安全漏洞

 

假如我们使用了restful_authentication的激活策略:每个新注册用户都会收到一个激活链接,当访问了激活链接后,数据库里的激活码字段会被置为null。

 

激活链接例子:

 

http://localhost:3006/user/activate?id=xrfqlki3453325xdgl

 

程序处理:

 

User.find_by_activation_code(params[:id]) 

 

假如用户用这样URL访问:

 

http://localhost:3006/user/activate 
http://localhost:3006/user/activate?id= 
 

那么生成的sql就变成这样了:

 

 

SELECT * FROM users WHERE (users.activation_code IS NULL) LIMIT 1 

 

它会找到第一个已经被激活的用户。

 

账户的暴力破解

 

有些黑客采取暴力破解的方式来获取账号,而有时候我们的网站的一些小问题却帮了他们的忙。

 

我们可能在提示信息里面提供任何对黑客有用的信息,比如有些网站在用户输入正确用户名和错误密码时会提示:Your password is invalid,或者在用户名未找到时提示:the user name you entered has not been found。

 

应对策略:

 

正确的做法是只要用户提供的用户名和密码无法登陆,则提供提示信息:user name or password not correct。

 

同时,在发现来自同一个IP的多次失败登录行为之后,需让用户输入验证码进行登录。

 

账户劫持

 

1. 修改密码时要阻止CSRF的攻击。

 

2. 修改email地址或者进行其它一些操作时,需让用户提供密码。

 

日志

 

日志很多时候成为了一个很大的安全隐患,虽然在数据库里我们对密码进行了加密,但在日志里是明文。

 

应对策略:

 

在controller里面加上一行以阻止密码被日志所记录。

 

filter_parameter_logging :password 

 

正则表达式

 

注意^  $  和  \A  \z的区别。

 

比如我们用这样一个验证:

 

class File < ActiveRecord::Base 
   validates_format_of :name, :with => /^[\w\.\-\+]+$/ 
end 
 

那么当用户输入如下文件名时,能很容易地绕过这个验证:

 

file.txt%0A<script>alert('hello')</script> 

 

它会被URL解码为“file.txt\n<script>alert(‘hello’)</script>”。

 

这里的问题在于Ruby里面的正则表达式中^和$匹配的是行首和行尾

 

应对策略

 

正确的做法应该是:

 

 

/\A[\w\.\-\+]+\z/ 

 

权限放大

 

这个问题貌似是我们经常会忽略的问题。

 

比如有这样的句子:

 

@project = Project.find(params[:id]) 

 

那么当用户改变了一个参数之后,他就可以访问其它用户的信息。

 

应对策略:

 

@project = @current_user.projects.find(params[:id]) 
 

 

 

中篇完结。在本篇中,介绍了一些在创建程序的过程中,我们要注意的一些小问题。这些东西其实都不复杂,但如果我们忽视了,则会引起很大的安全问题。

 

 

 

待续,下篇 关于多种注入的威胁,比如sql注入,css注入等。

 

论坛首页 编程语言技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics