上篇 中介绍了对session的攻击方法以及应对策略,在此篇继续介绍其它一些对网站的攻击方法以及应对策略。在阅读本文的过程中,你会发现,其实避免很多安全问题并不困难。只是很多时候,我们并没有把安全问题这个概念放在心里。
跨站请求伪造 (CSRF )
跨站请求伪造在网页上注入恶意代码或者一些恶意链接,来访问用户已被认证的网站。如果session未过期,攻击者就可以进行一些恶意的操作。
我们知道在用户访问网站时,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¶m2=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注入等。
1 楼 zadd 2011-01-30 14:09