7. 用户管理


— 几乎每个web应用都必须去处理授权和认证。避免你自己重复造轮子,建议你去使用通用的插件。但是请保持它们是最新的。一些额外的预防措施可以让你的应用更加安全。


有一些Rails可用的授权和认证插件。密码加密以后保存好于直接保存纯文本密码。最流行的插件是可以避免session定制的restful_authentication。 然而早期的版本在某些情况下你即使没有用户名和密码也可以登陆。


每个新用户可以通过一个带激活码链接的电子邮件来激活他的帐户。帐户激活之后,数据库里激活码那一列的值会被设为NULL, 如果某人发送一个这样的请求,他将被以第一次激活的用户身份记录到数据库里。 (有机会成为管理员):




这是可能的,因为在某些服务器上, 以id为参数的这种方式,如params[:id], 将会是nil。然而,这里有以个activation action里的finder方法:


User.find_by_activation_code(params[:id])


如果参数是nil,那么SQL查询的结果会是:


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


这样,在数据库记录里的第一个激活用户就被查到,返回这个结果,***者就登陆了. 你能找到更多的信息在我的blog帖子里。  不时的更新你的插件是明智的. 此外,你可以查看你的应用找到更多的这样的漏洞。
7.1.暴力猜解帐户


— 暴力猜解帐户***是使用错误的登陆证书去尝试。用更通用的错误信息来阻断这种***,可能需要输入一个验证码.


你web应用的用户名列表可能会被拿去用一组密码来做暴力猜解, 因为大多数的人不使用复杂的密码。大多数的密码是字典里单词和数字组合。因此配备一组用户名名单和一个***字典,一个自动程序,可能在几分钟之内就能找到正确的密码。


因为这个,大多数的web应用会在它们不正确的时候显示一个通用的错误信息 “用户名或密码不正确”。如果显示的错误信息是“用户名没有被找到”,那么***者就会自动编制一份用户名名单。


然而,大多数的web应用设计者都忽视了忘记密码页。 当输入用户名或email地址的时候,这些网页往往很诚实的就显示找到(没有找到)的消息。这使***者能够编制一份可以暴力猜解帐号的用户名名单。


在忘记密码这个网页上也显示一个通用的错误消息可以减轻这种***。此外,在某一个ip地址多次登陆失败之后你可以要求输入一个验证码。但是请注意,这不是一个完全防弹的做法, 这些自动的程序也可以频繁的改变自己的ip地址。只是给***增加障碍。
7.2.帐户劫持


— 很多web应用很容易劫持帐户。为什么不能使它变得更困难呢 ?
7.2.1. 密码


想想这种情况,一个***者窃取了一个用户的session cookie,因此他们可以共用同一个应用。如果这个应用可以很容易就修改了密码,那么***者只需要几个鼠标点击就劫持了这个用户的帐号。或者,如果修改密码的表单容易受到CSRF***的话,那么***者就会通过引诱受害者到一个藏有制作好的CSRF IMG -TAG的网页来修改他的密码。对策是,让修改密码的表单不能被CRSF***,当然在改变密码的时候,也需要用户去输入旧密码。
7.2.2. E-Mail


然而,***者也可以通过修改email地址来接管帐户。当他修改email地址之后,他会去一个忘记密码网页,可能一个新的密码就会被发送到***者的电子邮箱里了。对策是,当修改email地址的时候,也需要输入一个密码。
7.2.3. 其他


依靠不同的web应用,可能有更多的劫持用户帐户的方法。在许多情况下,CSRF和XSS都有助于这样做。例如,Google Mail的一个CSRF漏洞,在这个概念验证的***中,受害者会被引诱到一个被***者控制的站点。在这个站点有一个制作好的IMG-tag, 该tag的结果是,发送一个http get请求去改变Google Mail的邮件过滤器设置。如果这个受害者登陆到Google Mail,***者会改变过滤器设置将他的所有邮件转发到***者的邮箱里。这几乎和劫持整个帐户一样邪恶。对策是, 审核你的web应用逻辑来堵上所有的XSS和CSRF漏洞。
7.3. 验证码(CAPTCHA )


— 一个验证码是质问-响应的测试以便于确定这个响应是不是被一个计算机产生的。它往往通过让用户输入一副字母被扭曲的图片上的字符,来保护评论不被自动的垃圾发布机器人侵害,这个消极的CAPTCHA的主意,不是去让用户证明他是一个人,而是让一个机器人证明他是个机器人。


不仅仅是垃圾信息发布机器人是一个问题, 不要忘了还有自动登陆机器人。一个流行的CAPTCHA API是reCAPTCHA ,  它是显示来自于旧书的两个单词的扭曲图片。它还增加了一个直角线,而不是一种扭曲的背景和早期CAPTCHAS做的那样对文字的高水平包装,因为后者被破解了。作为奖励,使用reCAPTCHA有助于数字化旧书。ReCAPTCHA 也有一个同名的Rails插件实现这个API。


通过这个API你可以得到两个key,一个公有key,一个私有key,你必须把他们放到你的Rails环境。之后,你可以在view层使用 recapcha_tags方法,在controller层使用verify_recaptcha方法。如果校验失败verify_recaptcha会返回false。使用CAPTCHAs的问题是,它们很麻烦。一些视障用户对某些扭曲的CAPTCHAs难以阅读。这个消极的CAPTCHA的主意,不是去让用户证明他是一个人,而是让一个机器人证明他是个机器人。


大多数的机器人是愚蠢的,它们检索网络,并且把垃圾信息放到它们能找到的每一个表单域里。Negative CAPTCHAs趁机利用它们的愚蠢,在表单里包含一个“蜜罐”,人类用户可以通过JavaScript 和CSS来隐藏它。


这里有一些如何通过JavaScript/CSS来隐藏‘蜜罐’域的ideas:


    *


      放置这些fields到页面的不可见区域
    *


      使这些元素非常小,或者颜色和页面的背景色一样
    *


      就让它们显示在页面上,但是要告诉人类用户,让它们为空。


大多数简单的negative CAPTCHA就是一个隐藏的‘蜜罐’域。在服务端,你可以检测这个域的值:如果它包含任意文本,它肯定是个机器人。然后你可以忽略或者是返回一个有意义的结果,但是不把其保存到数据库里。这种方法会让机器人感到满意并离开。你这样做也会使用户更麻烦。


在Ned Batchelder's blog post,你能找到更先进的negative CAPTCHAs文章 :


    *


      包含一个存储当前UTC时间戳的字段,并在服务端检查它,如果它是太遥远的过去,或者是将来,那么这个form是无效的。
    *


      随机的字段名
    *


      包含一个以上所有类型的‘蜜罐’字段,包括提交按钮。


请注意,这仅仅是使你免于自动机器人的骚扰,而对于那些有针对性的完全定制的机器人则不能阻止。因此在Negative CAPTCHAs可能不是很好的保护措施。
7.4. 日志


— 告诉Rails不要把密码放在日志文件里。


默认情况下,Rails会记录到web应用的所有请求。但是日志文件会是一个巨大的安全问题,因为它们可能包含登陆证书或是信用卡号码等等。设计一个web 应用的安全理念应该是考虑如果***者获得(全部)访问web服务器的权限会发生什么事情。如果日志文件以明文列出secret和密码,那么把它们加密保存到数据库里是完全无用的。通过在controller里的filter_parameter_logging方法你可以在log文件里过滤某些请求参数。这些参数会在log文件里会以 [FILTERED]标记。


filter_parameter_logging :password


7.5. 良好的密码


— 你发现去记住你所有的密码是很难的了吗 ?不要把它们都写下来,可以用一句容易去记住的句子里每个单词的首字母的组合。


一个叫Bruce Schneier的安全技术员,他分析了34,000个真实世界来自于MySpace钓鱼***的用户名和密码。这证明了大多数的密码是很容易被破解的。20个最常用的密码是:


password1, abc123, myspace1, password, blink182, qwerty1, **you, 123abc, baseball1, football1, 123456, soccer, monkey1, liverpool1, princess1, jordan23, slipknot1, superman1, iloveyou1 and monkey.


有趣的是,这些密码里仅有4%是字典里的单词,并且绝大多数实际上是字母。然而,***字典包含了大量的今天的密码,并且它们尝试了各种(字母数字混合)组合。如果你个***者知道你的用户名,并且你使用了弱密码,那么你的帐户会很容易就被破解。


一个好的密码是一个长的字母混合体。由于这种密码很难去记忆,所以最好是只输入一个你很容易记住的句子里每个单词的第一个字母。例如,"The quick brown fox jumps over the lazy dog" will be "Tqbfjotld".注意,这只是一个例子,你不应该使用众所周知的短语组成这样的密码,因为它也有可能出现在***字典里。
7.6. 正则表达式


— 在Ruby的正则表达式里有个共同的缺陷是匹配字符串的开始和结束的^和$,而不是\A和\z.


Ruby用一种和其他语言略有不同的做法去匹配字符串的开始和结束。这就是为什么很多Ruby和Rails的书都犯这样的错。那么这个安全威胁是怎么样的呢? 假设你有一个文件model并且你像这样来验证这个文件名 :


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


这个意思是说,在保存之前,这个model会验证仅由字母数字字符,点,+和-的组合的文件名。程序里增加里^和$以便文件名这个字符串从开始到结束都包含这些字符。然而,在Ruby里,^和$匹配的是行开始和行结束。因此这样的一个文件名可以毫无问题的通过这个过滤器 :


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


由于%0A在URL编码里是一个换行符,所以Rails会自动将其转为"file.txt\n<script>alert(hello)</script>".  这个文件名能通过这个过滤器因为这个正则表达式能一直匹配到行尾,其余的都不重要。正确的表达式应该这样:


/\A[\w\.\-\+]+\z/
[source, ruby]


7.7. 权限提升


— 改变一个单一的参数可能给用户未授权的访问。请记住,每个参数都可能被改变,不管你对它做了多少隐藏和混淆。


最常见的使用者可能篡改的是id参数。比如在 [url]http://www.domain.com/project/1[/url], 因为1是id.它会给controller里的params[:id]一个可用的值. 在这里,你可能做这样的事情:


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


这对于某些web应用是没有关系的,但是如果用户没有权限去看所有的projects就不行了。如果用户把id修改成了43,并且他不被允许看到那个信息,他会想尽办法来访问它。相反,查询用户的访问权限:


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


根据你的web应用,可能会有更多的参数用户可以篡改。经验告诉我们,在未证明之前,没有一条用户输入的数据是安全的,并且来自于用户的每个参数都有可能被操纵。


不要被安全,困惑和javascript安全愚弄了。Mozilla Firefox的web开发工具栏让你可以审查和修改每个form表单的隐藏字段。JavaScript能被用来验证用户的输入数据,但是也肯定不能防止***者发送带有意想不到数据的恶意请求。Mozilla Firefox的Live HTTP Headers插件记录每个请求,并可以重复和改变它们。这是一个避开任何JavaScript验证的简单方法。而且即使是客户端代理,也可以让你拦截来自互联网的任意请求和响应。