在软件开发初期处理安全需求是防止安全问题最经济的方式。大多数安全需求都属于非功能性需求(Non-Functional Requirements
,NFRs)。很多从业者发现,在敏捷项目中处理安全和其他NFR非常具有挑战性。原因有二:
匹配NFR和特性驱动的用户故事需要付出很大努力;
安全控制常因缺少可见度而被忽视。敏捷过程容易让团队不自觉地侧重于那些可以直观改善客户体验
的新功能开发或缺陷修复。
在本文中,我们会探讨以上两个问题。
在用户故事中处理NFR
敏捷专家们提出过一些方法,用以定义用户故事驱动的开发过程中的NFR。Mike
Cohn在他的文章中讨论了评论者们提出的一些有趣的建议。Scott
Ambler就该主题写了一系列文章,旨在证明并非所有的NFR都可以独立存在于用户故事中。而Dean
Leffingwell可以说在这方面做了最深入的研究,他在自己的著作《敏捷软件需求》中花了整整一章探讨该主题。这些专家中大部分人都认为,NFR大致可以分成两类:
非功能性用户故事:以用户故事的形式展现的可测试功能块。这些用户故事中的参与者(Actor)可能是内部IT人员。例如:“作为安全分析师,我希望系统能抑制不成功的身份认证尝试,这样应用程序在面对暴力破解时不至于太过脆弱”。
约束:这是个跨领域的问题,常常涉及多个用户故事。我们可以将它看作是对所有相关开发工作收取“课税”。例如:开发Web应用时,要求所有开发者对Http表单中的字段进行数据校验即为一种约束。
处理第一类NFR很简单:创建一系列的NFR用户故事,将它们加入某种工作序列,例如Product
Backlog。
NFR约束的挑战:
然而处理第二类NFR则困难很多。敏捷专家们对此提出过一些解决办法,包括:
在特定的用户故事中添加恰当的约束作为验收标准
在某个中央知识库——例如,贴在墙上或者发表在wiki上的一份文件——中维护包含所有约束的列表。
但无论哪个方法都无法解决的问题是,如何针对特定的用户故事选择适用的约束。而且,使用SD
Elements进行安全需求匹配的经验表明,要将这些技术限定在一定范围之内很困难。下面这个列表是标准Web应用需要面对的安全约束中的一部分,我们以此为例:
在SQL语句中以变量绑定的方式阻止SQL注入
对来自客户端的只读数据进行完整性验证,以防止参数篡改
避开存在于HTML、HTML属性、CSS以及JavaScript中的不可信数据,从而防止跨站点脚本攻击)(XSS)
避免客户端JavaScript中基于DOM的XSS
使用安全的算法以避免整型溢出
不接受外部重定向以防止自由重定向攻击
授权给受保护的页面,以防止权限提升攻击)
使用防止跨站请求伪造(CSRF)的令牌
验证输入
尽量使用正则表达式,因为它对于拒绝服务攻击有较强的抵抗力。
为高价值的事务实现事务身份认证
不要硬编码密码
该列表列举的可能只是程序员在实现用户故事时需要考虑的安全约束中的一小部分。若将需要遵循的法规标准——诸如支付卡行业数据安全标准(PCI
DSS)之类——考虑在内,约束还要增加不少。如果你开始添加其他NFR约束,例如可访问性,约束列表会快速增长,转眼间就会超过程序员的承受范围。一旦列表变得臃肿,我们的经验是,程序员往往会将它全部忽略,仅根据自己的记忆来应用NFR约束。而在很多不断专业化的领域——例如应用安全——NFR的数量不断增长,这给程序员的记忆力造成了沉重的认知负担。因此,如何使用静态分析技术侦测代码中违背NFR约束之处就成了关注重点。研究表明,静态分析可以侦测出的可预防缺陷最多不超过总数的40%,也就是说仍有大量的约束漏网,包括特定领域的安全控制,更别说在缺乏定制的情况下辨别静态分析产生的“假阳性”结果也不是那么简单。
应对NFR约束的挑战
为了解决NFR约束过多的问题,我们需要做到以下四点:
按优先级排序:与用户故事和缺陷一样,NFR约束也应拥有不同的优先级,例如:相较于避开HTML中的不可信数据以防止持续的跨站脚本攻击,对HTTP响应头中的不可信数据进行编码或校验以防止HTTP响应拆分攻击就不那么重要。因此我们假定程序员通常没有足够的时间处理所有约束,然后我们提供一种机制为如何定义约束的相对优先级提供建议。之所以说“建议”,是因为优先级有可能随着具体情况的变化而变化,程序员需要自己作出判断——对于他们的特定代码,哪种相对优先级是适用的。你可以选用简单的优先级设置,如“高、中、低”,也可以使用数字范围,
过滤:使用简单的标准常常就可以为特定的用户故事移除大量的NFR约束。举例来说,假如你正在实现的用户故事与表现层无关,那么就可以安全地忽略大量表现层相关的约束。使用标签系统或者简单的Excel过滤器,就可以为特定的用户故事削减约束数量。以下是一些Web应用相关的安全性约束过滤器的范例:
用户故事是否涉及用户输入;
用户故事是否涉及保密数据,如密码、信用卡信息以及非公开的财务数据;
用户故事是否返回一个全新或修正过的用户输出,例如一个网页;
用户故事是否与数据库进行交互;
用户故事是否会暴露或使用来自RESTful
或SOAP
API调用的数据;
用户故事是否会访问受保护的数据(例如,不是所有用户都能查看或修改的数据)。
情境(Context):程序员在写代码或定义任务单(tickets)/
用户故事的时候更容易记起并应用约束。理想情况下,如果把约束列表内嵌入IDE或任务单系统,那么程序员就可以在编码时获取相关的约束。既然现在很多IDE都支持内嵌的Web浏览器,那么使用轻量级的网站或者SharePoint站点就可以实现这个目标。
框架:框架可以大幅减少约束产生的“税收”。例如,像
Django这样的框架自带了CSRF防护,这意味着程序员可以安全地忽略该约束,除非他们主动绕过这个内建的功能。根据我们的经验,多数大型的应用软件都会采用某种形式的定制框架,这些框架可以无缝处理最高优先级的约束。虽然这样的框架定制可能需要较高的先期投入,但如果你将约束看成一种基于用户故事的“税收”,那么减少约束数量就相当于永久性的降低了“税率”。
验证
虽然主动处理NFR很重要,但仍有必要进行验证。如果你有手动代码审查(Code
Review)的经验,就可以检查用户故事中作为验收标准出现的NFR。然而,如果仅使用手动代码审查,很多约束的验证将是非常棘手或者繁重的。静态分析工具则可以自动检查编码标准的遵守情况,其中一些产品特别关注安全方面。将你的静态分析工具与持续集成过程整合在一起,可以帮助你始终遵守编码标准。但也务必注意,不要一味依赖于代码审查。防范特定领域缺陷——如HTTP参数篡改——的NFR要求对背后的业务领域有深刻的理解。将手动验证与针对特定攻击的自动测试结合起来,可以帮助我们建立一套对于特定领域缺陷拥有更强抵抗力的系统。
可见度
难以将安全需求集成进敏捷项目的另一方面原因是安全需求通常缺乏可见度。面向客户的会议,如Scrum的Sprint评审会议),容易使开发人员产生一种固有的倾向,即实现用户故事或修复缺陷时倾向于那些能直接改善用户体验的故事或缺陷。一部分非功能属性,例如易用性和性能,对于系统的使用体验有具体的影响,因此也比较容易向客户展示。而另外一些——如安全性——却很难在Sprint评审会议上给客户留下很好的印象。实际上,某些安全特性,如账号封锁(Account
Lockout),可能给客户体验带来相当负面的影响。大多数安全特性对系统的影响,普通用户很难察觉。因此,安全性NFR是不可见的,它们必须和可见度更好的特性竞争,以夺得宝贵的开发周期。在软件开发初期,准确的说在程序员应该构建安全特性的时候,处理这些“隐形”需求的机会成本会特别高。
让安全特性可见
对于敏捷团队来说,没有“银弹”能让安全需求在开发伊始就拥有最高优先级。但有一些方法可以让安全NFR对客户和/或PO可见。
图形化安全用户故事:如果你在应用程序安全方面足够专业,就可以针对已知的安全弱点创建一套全面的安全控制。然后将它们与NFR用户故事匹配起来,并绘制简单的图形,用以说明系统中已实现的和未实现的安全用户故事数量。这个图可以放在大型可视图表中。
图1:安全用户故事图
当然,这个图没必要局限于安全用户故事,你可以将其他重要的NFR囊括进来。实际上,你可以使用故事点(Story
Point)取代用户故事数量,从而更准确的反映工作量。另外,你有可能希望图中只记录那些针对高风险安全问题的、拥有最高优先级的用户故事或控制。每完成一个故事,“已完成”数量都会增加,客户也会籍由此图直观地了解项目的进度。
展示可利用性(exploitability):没有什么比可利用性展示更能让人们理解一个安全漏洞的各个方面了。这是安全性渗透测试和漏洞评估的主要区别。你可以展示在上一版本的应用中如何成功地利用了漏洞,而在新的版本中却没办法这么做了。然后你可以从对业务和/或客户影响的角度解释漏洞的方方面面。这非常有利于解释为什么在安全控制上花的时间是合理的。
结论
主动处理NFR,特别是安全特性,在敏捷环境中非常重要。不少专家对于如何处理此类需求——特别是当它们与创建用户故事相关时——给出了建议。全面地处理约束是个相当大的挑战。记住本文讨论的提议,你可以极大地增加维护一系列约束的价值。即使不能完全做到以上所提的四点内容以改善对约束的管理,但从长远来看,任何一点改进都能带来很高的回报。进行验证能保证团队始终遵守NFR。最后,提高安全特性的可见度,可以让你证明用在NFR上的时间是合理的,就算它们对系统的改善不能反映到用户体验上。