原文:Security for building modern web apps
译者:杰微刊—张迪 php
这篇文章的灵感来自于另外一篇文章,它是关于“在今天,构建Web应用以前要知道的事情”的。并不长,但遗漏了一些关于安全性的建议,因此我就此动笔,分享一些这方面的知识。 html
本文重点是写给那些来自初创公司,而且想要从头开始开发一个Web应用的开发者,他们并不知道太多信息安全的知识,也不想花太多时间考虑其应用程序的安全性。一些重要的内容就不在这里讨论了,诸如威胁建模(threat modeling),持续交付安全(continuous delivery security)等等。这篇文章的目标不是要取代现有的代码安全检查表(例如,OWASP,SANS),而是要从今天的视角补充一下它们。毕竟,安全概念是很老的,(例如,安全设计原则是在70年代被定义的),它会在今天乃至将来都继续存在,因此安全也须要与时俱进,适应现实。 前端
注:虽然像这类的文章都是有益的,可是安全是一个过程,必须从一开始就与开发过程紧密关连。要始终考虑找一个应用安全专家来帮助你。 git
客户端 Client github
输出过滤(Output filtering):著名的跨站点脚本(Cross-Site Scripting),也被称为“XSS”或“HTML注入”,在没有输出过滤和执行某些代码时就会出现问题。防护方法依赖于上下文,如: HTML标签属性上的动态值(onclick、onload等),或标签内部(如,$("p:first").innerHTML=dangerousVariable)。只有在把动态变量存储在HTML标签的属性中时,这种危险代码才会生效。过滤输入对安全会有帮助,可是记住,XSS取决于上下文,因此不是全部的过滤都是有效的。这里有我对XSS的详细解释(PT-BR)。 web
使用静态页面(Use Static Pages):单页应用程序(SPA)的优势除了因为ajax请求而减小的通讯阻塞外,还有就是它拥有一个静态前端。这就意味着有更少的攻击面和更低的成本,所以你能够在Amazon S3上存储你的全部内容,并让Amazon保证其安全,在你没有一个安全技术团队或者你的安全技术团队不如Amazon擅长这个领域的状况下,让Amazon提供安全保证是很是棒的。SPA的缺点是缺少自定义HTTPS的证书支持(custom https certificate support)。你须要转移到Amazon CloudFront(CDN)上,这很容易实现,它将提高你的web应用的可用性。缺点是须要处理文件夹失效(assets invalidations),但不会太多。他们用一些使用文件名的版本管理技术,虽然很糟糕,但有总比没有好。 ajax
避开第三方网站的JSONP反应指令和多种JS文件(甚至广告网络):若是容许第三方网站在你的网站注入JavaScript代码,而且毫无保留地信任它们,结果就是加大了你的网站的被攻击可能性。当心那些响应数据的API,不要让他们轻易被执行。看看Troy Hunt的案例吧。 算法
不要留下HTML注释:有的安全工具能够用于搜索HTML注释,并呈现给攻击者,以查看是否有任何用处,例如OWASP WebScarab。删除HTML注释。若是你须要注释,就在页面生成的时候使用动态语言来添加注释,这些注释就不会出如今响应中了。 spring
客户端校验(服务器端固然也要执行):服务器端校验不能被替代,有两个优势:1)更好的用户体验,由于反馈迅速;2)阻止了后台的无用请求,从而提升有效性。 数据库
退出(logout)应在每个页面都是可见的:请不要忘记这一点。最好是在预期的地方,如点击用户的头像以后的右上角。
若是你将数据存储在客户端,必定要谨慎:相同的威胁适用于移动端,也适用于其余客户存储的设备,会形成数据丢失和被盗。若是你将数据存储在客户端了,就要假设有人会看到它,因此不要存储重要信息。存储就要加密,并把key保存在cookie里(没有可被JavaScript读取到的HTTPOnly标记),至少保存到当前会话结束。当用户注销的时候要删除全部信息。根据数据,你可能想要使用例如HMAC的技术来防止完整性违规(integrity violations)。不管如何,记得这样使用它。固然,服务器中也要保存key。当用于session存储机制时,Rails的cookie会和服务器的APP SECRET一块儿使用。为了增强这个概念,可使用Json Web Tokens(JWT),它是目前作这件事(data + signature + algorithm used + expiration + base64 encoding + json format)的标准。
考虑用Json Web Tokens(JWT)取代session:你可使依赖于JWT的无状态服务器,而不是session和数据库。缺点是保密性差,看上一条就知道了。这个方法能够提升应用的有效性,若是把它们存储在LocalStorage而不是cookie中,还能够防止CSRF攻击。CSRF发生时浏览器无反应(dumbness),即便是跨域请求,cookie也有被传输到服务器的风险。
切记,LocalStorage会受到XSS影响,HttpOnly标识的Cookies则不会:虽然这有利于存储session标识符(cookie w/ HttpOnly标识),但仍有CSRF的风险。这是一个权衡,记住这一条便可。
服务器端 Server
选择一个web框架,至少是MVC:远离构建web应用程序的脚本。最经常使用的框架已经给了你一些保护(例如,CSRF保护,Security头),若是你正在写PHP,直接使用它们就行了。可是,要当心,你可能会在下面一条跌倒:
避免太过异想天开:我认为这是开发人员中最多见的缺陷。他们对某些有用的功能或框架十分满意,而且盲目地相信它们。这为许多安全漏洞和bug的产生留下了空间。最多见的例子是OAuth库。使用SSO前,必定要了解它的工做细节。不然你会身份验证失败。在开发过程当中也没有免费的午饭。在开发以前,在你的应用程序里插入一些未知代码,作一些code review,静态分析,检查已知bug(CVE),并在可能的状况下阅读一下RFC,可是不要盲目地去作,尤为是在web应用程序的关键部分,如身份验证、受权、责任和支付处理/储值卡。
验证CORS源(CORS Origin):除非你打算向整个世界开放API,你应该只容许单页应用的源地址被调用,以免其余网站的浏览器内(in-browser)调用。
默认设置Cookie标识HTTPOnly:HTTPOnly标识有更多的Cookies是必须的,这能防止Javascript访问cookie值(如会话cookie),这样作能保护Cookies中的信息,即便发生XSS。实际上,恕我直言,HTTPOnly应该是默认属性才对,non-httponly只有在异常中才使用。没有这个标识的cookie仅能用于客户端访问,例如一个根据用户偏好显示或隐藏菜单的标识符。LocalStorage对它的支持也很好,因此咱们应该再也不使用没有HTTPOnly的Cookie。
默认为Cookie设置secure标示:secure标示容许cookie只能经过HTTPS链接传输,这是伟大的,但你须要有一个HTTPS端口监听工具。现在,它应该是一个必备设置,不只为了安全,并且为了增长你的谷歌搜索查询排名。据我所知,你不能够在Amazon S3上使用自定义证书。你须要将你的自定义证书部署到Amazon CloudFront(CDN)上,这对你的密钥来讲是有害的,但对于小团队来讲别无选择。CloudFlare想到了这一点,开发出了无需key的SSL,但你须要创建一个能处理全部SSL握手的服务器,至少是使用这个钥匙的一部分标头,这也意味着须要更多的服务器和更高的成本。
避免业务逻辑Bypass:最多见的缺陷之一就是受权bypass,甚至在facebook上你能够看到这种事情发生。例如,编辑用户账户的细节时,你能确保若是用户输入嵌入了另外一个用户的user_id时,你的应用可以阻止此次更新么?你须要在全部的控制器(controller)上仔细确认。这一般是一些开发人员必须本身实现的验证,因此一般被忽略,或实现得很难看。你本身测试一下,也邀请一个有作安全程序背景的人来测试一下,甚至作一些单元测试来验证你的controller。质量分配漏洞(Mass Assignment Vulnerability)也值得注意,homakov利用它攻击过GitHub。你须要将你的模型参数列入白名单,不然攻击者会经过猜想他们的名字,利用“framework magic”,经过请求参数构建出模型对象。
在你的API中放置CSRF保护: Web框架一般建议你使用CSRF保护,当你构建API时,看到“请求中缺乏CSRF token”的消息时,你通常会禁用它以后继续编码。不要那么作。CSRF真的很危险,提醒你本身,确保添加一个CSRF token,即便是在API被调用时。你能够经过如下3种方式作到这一点:
① 有状态session:在每个session上添加CSRF随机token,检查每个请求中它们是否匹配。
② 无状态的双Cookie提交技术:攻击者能够操纵请求体(request body),但不能操纵cookies,由于它们来自另外一个域,在cookie和请求中向服务器发送相同的随机值,并检查它们是否匹配;若是你的用户(或第三方脚本,如广告)能够控制任何子域,你也有一些技术能够bypass。从Blackhat的文章中获得更多的信息。
③ 无状态的Json Web Token:存储在LocalStorage中,并在每一个请求中发送。攻击者不能访问跨域的LocalStorage。
不要让全部操做都得到访问你AWS账户所有资源的权限:你不会浪费太多时间为你应用的AWS访问凭证找出正确的许可。不要傻到容许访问全部东西。若是你将key上传到一个公共的GitHub库,你就完蛋了,会被攻击,设置权限下降风险吧。
不要将证书存储在源代码里:从源代码部署之外的环境或文件中去读取证书。刚开始会有些麻烦,但一些函数库使它很是容易,如ruby的dotenv gem。
当进行服务端到服务端的通讯时,验证端点证书(endpoint),考虑pin它或它的公钥:当你浏览一些HTTPS网站,浏览器会验证其信任的CA。但当你进行从服务端到服务端的通讯时,谁来作验证呢?一般没人,因此你须要本身设置逻辑去验证端点证书。验证经过以前,不要容许别的操做,不然SSL/TLS就没意义了。除了在传输过程当中加密数据,HTTPS的另外一个目标是验证端点的真伪,从而防止中间人攻击。能够考虑使用证书pinning(Certificate Pinning),或者更好的公钥pinning(Public Key pinning)。OWASP有一篇很好的文章详细解释了这一点,因此我不赘述了。最基本的是你只能和你所期待的人交谈,例如,从给定的X509证书中生成一个摘要(digest),并把它与硬编码摘要(hard coded digest)做比较。可是有一个问题,若是证书撤销或者改变,服务将会被拒绝。更好的选择是使用公钥锁定,由于公钥存在于X509证书中,除非证书使用其余密钥对从新生成,不然不管是被撤销仍是改变,均可以顺利的经过公钥被验证。这些对移动应用程序也是必须的。
设置安全头(Security Headers):经过在响应中设置安全头,便可保护web应用免遭点击劫持(Clickjacking)、反射型XSS(Reflected XSS)和 IE内容探测(IE content guessing)的攻击(注:若是你发送配置正确,Ruby on Rails能为你作大部分的工做)。更多细节,请查看OWASP页面。
① X-FRAME-OPTIONS:用“否定”或“同源”来防止“点击劫持”。
② X-XSS-Protection:“1;mode=block”迫使XSS反射保护,在Chrome中是默认的, IE中不支持。
③ X-Content-Type-Options:“nosniff”遗憾的是,IE试图猜想web页面的内容,即便这个content/type意味着其余内容类型。若是IE检测HTML代码,它将容许txt文件执行脚本。经过使用这个标头禁用它。
④ Strict-Transport-Security:“max-age=16070400;includeSubDomains”HTTP Strict-Transport-Security(HSTS)保证安全(HTTP经过SSL/TLS)的链接服务器。即便是用户类型的HTTP(user types http),浏览器都将强制HTTPS,这是很棒的。
⑤ 还有其余的,例如Content Security Policy(CSP),就不在这里讨论了。
在“注册”和“忘记密码”页面使用验证码:多亏了谷歌的reCaptcha,现在的验证码已经不是很烦人了。今天,你能够验证用户是不是基于他的行为而不只仅是人类挑战,从而防止假帐户和疯狂的发送电子邮件。
存储API密钥就像你存储密码同样(或尽量这么作):若是双方泄漏的影响是相同的,那么为何储存一个比另外一个更安全?其实是有一些不一样之处的,但关键是不要在明文中存储API密钥。API密钥应该是系统生成的随机字符,因此他们不会受到字典攻击(dictionary attack),就像密码,可是,在数据库/文件系统/ OS中,API密钥将在未经加密的文字或数据中可用。也就是说,至少一些hash是必要的。若是你使用像scrypt或BCrypt这样的工具,你就要当心了。scrypt或BCrypt由于其缓慢的哈希计算,很是建议用于密码。缓慢的哈希计算也会致使服务被拒绝。你输入一次密码,获得一个session ID;可是API就不一样了,API验证时刻都要被调用,因此速度缓慢会下降应用的可用性。存储API Key的摘要,足够知足你的使用了SHA256或SHA512算法的应用了。远离MD5和SHA1。必定要远离!
至少为用户使用UUID做为主键,而不是顺序ID:防止用户账户的猜想/暴力破解和轻易复制。有更多的优点和少数劣势,但它是值得的。注意:相较于连续整数,UUID并不会使应用更安全,仅是从安全的维度增长了不可法猜想性和模糊度。
忘记密码和电子邮件确认的token:为忘记密码或电子邮件确认生成一个token时,请确保使用安全的伪随机数生成器(RPNG),不然可能被猜到。使用能够信任的库/语言API。也为这个token设置截止日期/时间。设想一下使用情景,用户不想改变本身的密码,但一周后,有人拦截了电子邮件,访问了那个URL,并改变他的密码。这是没必要要的风险。
在邮箱更新时通知旧邮箱:帐户侵权以后最多见的行为是改变账户的电子邮箱,来防止其全部者恢复密码和登陆,因此必定要发送一封电子邮件到过去的电子邮箱,在恢复过程添加一个选项。Facebook就是这样作的。这招还适用于敏感数据更新。不管是谁在操做,但帐户全部者必须被通知。
禁用端口80而不是重定向到443:这样作以后会增大攻击面。若是80端口不须要了,那就禁用它。记住,你的API只应该在443中监听。若是你想从80重定向到443,在<插入CDN名>这个选项处操做。
老是使用通用类的错误信息:记住要始终使用通用的错误信息,例如,在登陆尝试时,不要说“用户名无效或密码无效”,只说“证书无效”,让暴力破解更难,虽然能够在注册时枚举电子邮箱,由于你的系统可能会(也应该)让每一个账户的电子邮箱是惟一的。若是你的应用程序产生一个异常,只是说“出错了”,不要暴露异常堆栈。我也建议你使用一些方法来收集全部的异常,并发送到你的邮箱或展示在Raygun, Sentry, Airbrake的dashboard上。
确认用户的电子邮箱或电话:在发送电子邮件或者通知以前要先确认这个邮箱或者电话是否属于该用户。值得推荐的作法是非阻塞法,即让用户能够在没有确认的状况下登陆,但这也会影响线上用户的使用。看看Facebook:你可使用未经证明的帐户1天。以后,你必须在登陆以前确认邮件或电话。我常思考10分钟后邮件失效这样的服务,像上文提到的,好处并非发送邮件给并不须要它们的用户,而是让你免于被用户标示为垃圾邮件。
其余方面(不排斥其余安全措施)
不要由于供应商有很酷的功能或超低价格就选择他们:你的数据是危险的,那么你的名声也是危险的。有一个叫作“不肯信任”原则,这意味着信任以前你须要当心。减小你的信任也是一件好事。你越相信,就越危险。也就是说,我一般建议安全第一。一开始Bitbucket彷佛比GitHub更便宜,但它没有两因素身份认证。你的源代码值多少钱呢?AWS引起了公有云市场的竞争;当他们开始关注敏感信息的安全性时,他们彷佛作了一件伟大的工做。因此只是在价格便宜的状况下还不足以让我换一个服务商。全部的事情都要被考虑到,但要知道,静态页面接受任何东西,常常会看到企业主页上宣称它们经过APT和SSL(不推荐使用)实现了网站安全。尽可能不要轻易相信,当你信任时,先验证!
(REST)面向API的开发:若是你细看AWS,你会发现API是第一位的,而后是web UI,最后是SDKs。API是可怕的,由于它是独立于语言的。但我我的认为,这是未来发展的必然趋势。还值得仔细观察的是HATEOAS。它使各部分之间的可视化隔离变得容易。客户端是静态页面,服务器是接收输入和为前端产生输出的大脑。它能更明确地分离角色和记录,例如web服务器必须验证输入。不然non API的web应用程序更会混乱。
委托办理信用卡:将风险委托给信任的实体是一个好建议。若是你本身去作这件事,就要从一开始就储存信用卡数据,再想想,这样你要担负多大的责任。若是你委托给可信的支付提供商,如Stripe或PayPal,会不会更好?我以为会,除非你能作的更好。因此,要确保你的应用不要碰触信用卡数据。能够重定向到他们的网站来完成整个过程。
如今去哪?
有太多的信息了,去搜索吧。OWASP和SANS会给你很大帮助。他们有不少项目、物品、清单和工具。我还建议关注你的工具和供应商方面的安全建议。除此以外,常去Reddit的/r/netsec逛逛。
原文地址:http://www.jointforce.com/jfperiodical/article/933?f=jf_tg_bky