书名:软件开发安全之道——概念、设计与实施
ISBN:978-7-115-61773-6
本书由人民邮电出版社发行数字版。版权所有,侵权必究。
您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。
我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。
如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。
著 [美]洛伦·科恩费尔德(Loren Kohnfelder)
译 徐龙泉
责任编辑 佘 洁
人民邮电出版社出版发行 北京市丰台区成寿寺路11号
邮编 100164 电子邮件 315@ptpress.com.cn
网址 http://www.ptpress.com.cn
读者服务热线:(010)81055410
反盗版热线:(010)81055315
本书共有13章,分为三大部分,即概念、设计和实施。具体内容包括:第一部分(第1~5章)介绍了基础概念,涉及信息安全和隐私基础、威胁建模、对可识别威胁进行防御性缓解的通用策略、安全设计模式,以及使用标准的加密库来缓解常见的风险。第二部分(第6~7章)分别从设计者和审查员的角度讨论了如何使软件设计变得安全,以及可以应用哪些技术来保障安全性。第三部分(第8~13章)涵盖了实施阶段的安全性,在有了一个安全的设计后,这一部分将会阐释如何在不引入额外漏洞的情况下进行软件开发。
本书是针对软件专业人士编写的技术指南,适合那些希望更好地理解软件安全原则、学习如何实践软件安全设计和实施的专业人员,包括架构师、UX/UI设计师、软件开发工程师、编程人员、测试人员和管理人员阅读。
洛伦·科恩费尔德(Loren Kohnfelder)从事编程行业已经超过50年了,他在美国麻省理工学院攻读硕士研究生期间发表的论文《关于一项实用的公钥加密系统》(Towards a Practical Public-key Cryptosystem)(1978年)首次描述了数字证书以及公钥基础设施(PKI)基础。
在他的软件职业生涯中涉及各式各样的编程工作,包括打孔卡、磁盘控制器驱动、链接加载器、电子游戏的编写,还包括在一家半导体研究实验室编写设备控制软件,以及两份在日本的编程工作。在微软公司供职期间,他又开始从事安全方面的工作,加入了微软的IE浏览器团队,之后又加入了.NET平台安全团队,参与了主动安全过程方法的设计。
前几年,他供职于谷歌公司,曾担任安全技术团队的软件工程师,此后又参与组建隐私技术团队,对大型商业系统进行了百次以上的安全设计审查。
自Commodore PET和VIC-20问世以来,克利夫·扬森(Cliff Janzen)就与技术相伴(甚至为技术着迷)。平时他的主要工作就是管理和指导一个优秀的安全技术专家团队,同时不断通过处理安全策略审查、渗透测试和事件响应来跟进技术的发展。他一直对能够从事自己热爱的行业和拥有一位支持自己的伴侣深感幸运。
你即将开始阅读的这本书在很多方面都是独一无二的。它篇幅不大,是一本精心编写的、代码很少却很专业的技术读物。这本安全技术图书并非为安全技术专家编写。正如本书的作者Loren所说,本书融汇个人针对技术的深入观点,是由包装过大型商业产品、发明过重要安全技术,并且在产品安全方面有丰富工作经验的专业人士撰写的。
2006年,我加盟了微软,负责对所有产品和服务进行威胁建模。我们采取的主要做法就是借助本书作者提出的STRIDE(STRIDE是一系列单词的首字母缩写)模型来思考欺骗(Spoofing)、篡改(Tampering)、抵赖(Repudiation)、信息泄露(Information disclosure)、拒绝服务(Denial of service)和权限提升(Elevation of privilege)所带来的威胁。STRIDE已经成为我工作中的一项重要组成部分(它的作用如此之大,我甚至经常需要澄清STRIDE并不是我的原创)。事实上,在我阅读本书之时,我惊喜地发现作者在提到我的“4个问题”框架时,与我提到STRIDE时的评价相差无几。这个框架通过对4个问题进行自问自答来接近问题的核心:我们的工作是什么?哪里有可能出错?我们打算怎么办?我们干得怎么样?本书的大部分内容证明,虽然我和作者从来没有真正在一起工作过,但我们俩是真正的“合作无间”。
如今,世界正在不断发生变化,安全缺陷已经登上了各大媒体的头版。我们的客户对安全性的期待之高前所未有,他们会通过采用自己的评估标准、起草合同条款、给销售和执行人员施加压力、催促兑现新的策略,来推动我们满足他们的需求。现在就是我们把更好的安全设计方案(从概念到代码)集成到软件当中的绝佳时机。本书围绕一个非常困难的主题展开,那就是如何设计出安全的软件。
这个主题之所以困难是因为存在两大挑战。第一大挑战在于,安全和信任既自然又微妙。这部分内容包含在本书的第1章,所以这里不再赘述。第二大挑战在于,软件专业人员往往希望软件不需要进行设计。与其他工程领域的产品不同,软件的可塑性几乎是无穷的。在其他工程领域中,人们只有先制作好模型,才能弯曲钢材、浇筑混凝土或者对硅实施光刻操作。对于软件而言,我们则会创建代码、对代码进行细化,再把代码发布出来,而不会采纳弗雷德·布鲁克斯(Fred Brooks)的著名建议:你搭建的第一个系统很快就会被你丢掉,所以你最好把它当成原型(prototype)。我们所介绍的、关于软件进化的故事很少停留在那些徒劳无功的经历上。我们不会探讨失败的经历,只会探讨提出合理设计方案的过程。即便如此,我也知道读者或许会觉得我(甚至觉得本书的作者)是在宣传自己的设计方案。我也得承认这个主题包含另一项挑战,这项挑战也是本书一直在着力解决的——提供软件设计方面的实用建议。
本书主要写给刚刚投身到安全领域的技术从业者,欢迎你们成为这个领域的一员。你们在阅读本书的过程中会发现,你们针对系统的决策会对系统的安全性构成影响。但是,并非顶尖的技术专家才能做出更好的决策。本书会带你走得足够远。如果你们中有一部分人还希望走得更远,业内也有大量材料可以供你们参考,其他人则只需要把从本书中学到的知识应用到位就可以了。
Adam Shostack
Shostack+ Associates总裁
《威胁建模:设计和交付更安全的软件》的作者
华盛顿大学保罗·艾伦计算机科学与工程学院副教授
如果你始终不能告诉别人你一直在做什么,那么你在做的事也就一文不值。
——埃尔温·薛定谔
同我一起用一次徒步旅行来领略软件安全的美吧!
我印象最为深刻的徒步旅行始于距离考爱岛顶部不远的一座热带雨林,那里经常被朦胧的雾气和细雨所笼罩。起初,徒步小径是一段缓坡,然后需要在一段险恶的地方走一段湿滑、陡峭的斜坡。接下来,我们开始穿越一片山谷,谷中长满了生姜和多刺的马缨丹灌木。这时,路越来越泥泞,我们也开始分心,不时地回头、转身。步行了几公里之后,树木渐渐变得稀疏,温度也开始升高。随着海拔降低,气候逐渐变得干燥。再向前走一段,太平洋的远景开始渐渐在我们眼前展开,这也让徒步团队欣喜不已。
根据我的经验,很多软件从业人员都认为保障安全是一段令人生畏的旅程。这段旅程弥漫着浓雾,甚至让人觉得诡谲难辨。这种认识当然也有合理之处。如果我们把编程与自然环境进行类比,这种体验大致就是如此。
我们徒步旅行的最后一公里是一片由松散火山岩凝结而成的危险地带,因为这座岛屿本身只存在500万年,还不足以形成土壤。代码也像岩石一样坚硬、冷酷,同时它也非常脆弱,一点点缺陷就足以让它全盘崩溃。好在我们对山岭中的徒步路线进行了仔细的遴选,每处最陡峭的位置都有一些天然形成的“把手”——要么是突出的坚固玄武岩,要么是铁心木露出的根部。
在徒步旅行的最后,我们发现自己正沿着峡谷的边缘行进,踩着脚下松软的土地就像踩在滚珠轴承上一样。我们的右手边是落差600多米的悬崖。有些地方,路径的宽度和我们肩膀的宽度差不多。我看到一些有恐高症的徒步旅行者在这里转过身,没有勇气继续前进。但大多数人依然信心满满,因为我们的徒步路径往山壁一侧微微倾斜,我们左侧的风险微乎其微。我们当然面临着同样的风险,但那只是一个缓坡,在最坏的情况下我们也只会滑落至多两米。在撰写这本书的时候,我经常想到这条道路。我也在努力为读者创造这样一条路径,利用这种故事和类比方法来解决那些最棘手的问题,我希望这样可以让读者有所收获。
保障安全是一项充满挑战的工作,其原因如下:安全非常抽象,但主题很庞大,而如今的软件不仅脆弱,且极为复杂。本书要怎样才能在深入解释安全复杂性并且与读者建立联系的同时,不让太多信息压垮读者呢?这里我用徒步旅行者行走在峡谷边缘的精神面对所有挑战。我不想让读者流失,所以对内容进行了简化,并省略了一些可有可无的细节。通过这种方式,我希望读者可免于“跌入峡谷当中”——被大量信息弄得晕头转向、意志消沉,最终选择放弃。希望本书成为一个跳板,激起读者继续探索软件安全实践的兴趣。
当读者来到徒步旅行的最后一段路时,山岭豁然开朗,道路变成康庄大道。绕过最后一段弯道,读者就可以领略到纳帕利海岸的壮丽全景。我们的右手边是一个青翠的悬空山谷,恰似从高山上雕琢而成。一条瀑布飞流直下,汇入下方蜿蜒的河流。错综复杂的海岸线一直延伸到远处,可以看到地平线上临近的岛屿。造访这里带给我们的惊喜永远不会过时。欣赏着美景,啜饮琼浆后,我们还要原路徒步返回。
恰如我永远无法领略这个岛屿的每一寸土地,我也不可能掌握与软件安全有关的所有知识。当然,也没有任何一本书可以完全涵盖所有的主题。我能够为读者展示的东西,只是我自己的经验而已,这一点就和带领我们徒步的导游一样。我们每个人都会围绕着这个主题讲述自己的故事,因此能够长时间从事这项工作实属幸事。我曾目睹这个行业的一些重要发展历程,并且从早期就开始关注技术和软件开发文化的演变。
本书的目的是向读者展示安全技术行业的布局,同时对其中的一些危险给予警告,让读者可以更加自信地自行探索。关于安全问题,基本没有放之四海而皆准的指导方针。我的写作目标是给读者展示一些简单的案例,首先激发读者的兴趣,进而加深读者对核心概念的理解。对于真实世界中的安全挑战,永远都是需要很多背景信息才能更好地评估各种可能的解决方案,最好的决策都是建立在对设计方案、实施细节等有深入了解的坚实基础之上。在读者掌握了基本的思想并且开始把它们付诸实践的时候,工作本身也会随着不断实践而越来越直观。好在,随着时间的推移,哪怕是一点点进步都会让我们觉得自己的努力是值得的。
回首自己供职于主流软件公司安全技术团队的时光,我总是会为失去的一次机会深感遗憾。在一家大型、利润丰厚的企业工作自然有很多好处:不仅有现场按摩和豪华咖啡厅,还有现场安全技术专家指导和设计审查流程。然而,其他软件开发工作很少能够享受到这种程度的安全专业知识和在设计方案中集成安全性所带来的好处。本书旨在让软件社区制定相关标准的实践方法。
设计人员需要平衡数不清的问题,其中那些优秀的设计人员固然知道需要考量哪些安全要素,但他们也很难让安全设计得到有效的审查(我没有从业内任何熟悉的人那里了解到顾问提供过什么服务)。开发人员拥有的安全知识层次也各不相同,除非他们中的一些人把安全知识当成一种专长来加以追求,否则他们的知识层次充其量只能算是零零散散、不成体系。有些企业确实非常重视安全问题,因此它们聘请了一些专家级顾问,不过这种事往往发生在整个流程的后期,它们是亡羊补牢,希望能够在软件发布之前提升它的安全性。在发布之前强化安全性已经成为业内的基本操作——这与实际的安全需求背道而驰。
在过去几年里,我一直都在潜移默化地向其他同事传播安全的理念。在这个过程中,我总能看到一些人心领神会,其他人则不知所云。人们的反应为何差距如此之大?这一直都是一个谜,或许心理层面的因素甚于技术因素,但是这让我们思考更多的问题——“获得”安全性到底指的是什么?我们又应当如何传授这方面的知识?我说的不是那种最前沿的知识,也不是说要让听众掌握这些知识,只是让他们能够充分了解我们所面临的挑战,让同事了解如何循序渐进地提升安全性。以这些作为起点,软件从业人员就可以通过自学来弥补知识方面的缺陷了。这就是本书要努力实现的目标。
在写作这本书的过程中,我对这项工作所面临的挑战了解得越发深入。一开始,我惊讶地发现市面上还没有同类图书;如今,我相信自己已经找到了原因。安全的概念往往和人们的直觉相反,攻击花样百出且相当隐蔽,而软件设计本身已经非常抽象了。如今的软件丰富多样,保护各类软件是一项艰巨的挑战。软件安全至今还是一个有待解决的问题,但是我们对软件安全已经拥有相当的了解,且还在不断改进——要是软件安全不是一个快速变化的目标就好了!我当然不可能对所有问题都给出无懈可击的解答。轻而易举就能给出答案的安全问题都已经被集成到我们的软件平台当中了,剩下的问题都没有那么容易解决。本书会在战略上强调安全意识的概念和发展,这可以让更多人参与到安全工作中来,从而提供各种各样的全新视角以及人们一致关注的安全要点。
我希望读者加入我的这个“私人旅行团”,沿着我最为倾心的路径来领略安全技术的风景,我则会在旅途中向读者分享最有趣的见解以及我为读者提供的有效方法。如果本书可以让读者相信,人们应该在设计阶段就把安全问题考虑在内,在软件开发的整个过程都应该考虑安全问题,以及读者应该在本书之外进行更多的学习和研究,那么我的目的就达到了。
归根结底,知识是建立在确证基础上的。——路德维希·维特根斯坦
我要感谢学术界和业界同人,我从他们那里学到了很多东西,这才是我写作本书的根本原因。安全工作可能完全不会得到任何人的感激,因为成功的安全工作恰恰不会为人所知,而失败的安全工作则会遭到严格的审查。恰因如此,仍然有这么多伟大的人把他们横溢的才华和不懈的努力投入这项事业,才格外让人振奋。
我还要感谢本书手稿阶段的读者(Adam Shostack、Elisa Heymann、Joel Scambray、John Camilleri、John Goben、Jonathan Lundell和Tony Cargile),他们为我提供了大量宝贵的反馈。Adam给予我的支持尤为关键,他主持了大量的研讨会,还不吝笔墨为本书撰写了序言。
如果有人能把写作本书时被纠正过来的错误全部记录下来,那就太有意思了,我也一定可以从中汲取不少教训。感谢别具慧眼的人们,感谢你们订正了本书中的那些错误。
在技术领域外,也有很多朋友让我获益良多。我需要特别感谢Rosemary Brisco在营销方面给我提供的建议,感谢Lisa Steres博士为本书付出巨大的热情和持久的关注。
最后,我要对我的妻子真诚地说一句“谢谢”,感谢她在本书的写作过程中给予我无尽的支持。
本书是针对软件专业人士编写的技术指南,适合那些希望更好地理解软件安全原则、学习如何实践软件安全设计和实施的专业人员阅读。我有幸对本书介绍的很多主题进行了自己的创新,此外,我也见证了这个领域大量技术的发展与落地。本书是根据我自己的从业经验创作的,其中包含了很多实用性很强的观点,读者可以把这些观点付诸实践,让自己正在编写的软件更加安全。
本书有两大核心主题:一是鼓励软件从业者在软件开发过程的早期就关注软件的安全性;二是让整个团队都参与到安全流程当中,并且对软件安全承担起自己的那一份职责。这两方面必然存在很大的改进空间,本书则展示了如何实现这些目标。
在我的职业生涯中,我有得天独厚的机会可以一直奋斗在软件领域的一线。如今,我希望与尽可能多的人分享我的学习成果。二十多年以前,我是微软公司某团队的成员,这个团队首次把威胁建模大规模应用于大型软件公司。后来我在谷歌公司参与了同一项基本实践的演化,同时也体验到了一种应对挑战的全新方式。本书第二部分参考了我所完成的百余项设计评审。过去的经历为我提供了一个极佳的视角来对这些要点重新进行解释。
设计、搭建和操作软件系统是一项存在固有风险的工作。我们的每一次选择、前进的每一步,都会在不经意间升高或者降低系统中引入安全漏洞的风险。本书涵盖了我最熟悉的内容,这些内容来源于我个人的经验。安全意识是我希望传达的首要原则,我也同时展示了如何把安全融入整个开发过程当中。在本书中,我提供了很多设计和代码示例,它们在很大程度上不会依赖某项特定的技术,这是为了让它们的应用尽可能广泛。本书包含大量的故事、类比和示例,这是为了增加阅读的趣味性,同时尽可能有效地表达抽象的概念。
安全意识对有些人来说很容易理解,对另一些人来说则比较难。鉴于此,我着重强调培养这种直觉的方法,帮助读者用全新的方式思考,这类方法可以简化我们工作中的软件安全任务。我应该补充一点,根据我的个人经验,即使对那些比较容易培养安全意识的人来说,他们也一定可以从中获益良多。
本书虽然简洁,但是涵盖了很多方面的问题。在撰写本书的过程中,我已经意识到简洁对这本书的成功至关重要。软件安全领域在广度和深度上都令人生畏,所以我才希望本书尽可能简短,从而让它能够被更多人理解。我的目标是让读者用一种全新的方式来思考安全问题,并且应用在自己的日常工作当中。
本书适合在软件设计、开发等领域已经有所专长的人阅读,包括架构师、UX/UI设计师、程序管理员、软件工程师、编程人员、测试人员和管理人员。技术从业者在理解本书中的概念时应该不会遇到什么障碍——只要他们理解了软件行业的一些基本概念,以及软件架构的基本方法。如今,软件的使用已经非常广泛,类型也相当丰富,我不能说所有软件均依赖安全性,但绝大多数软件确实如此,尤其是那些需要连接到互联网或者需要与人互动的软件。
在撰写本书的过程中,我意识到应该把潜在的读者分为三类。
安全行业的新人——尤其是那些一听到安全就皱眉头的人,他们是我写作时思考的主要受众,因为让所有软件从业者都对安全有所理解是非常重要的,这样大家才能全部参与到提升软件安全性的工作当中。为了让未来的软件更加安全,我们需要所有人的参与,我也希望本书能够帮助那些刚刚开始学习安全技术的人迅速踏上正途。
具备安全基础的读者——是指那些对安全抱有一定的兴趣,但知识层面仍然有待提升的人,他们希望加深自己的理解,学习更多实用技能,从而应用到他们的工作当中。希望本书能够弥补他们的知识空白,提供各种方式让他们可以学以致用。
安全专家——他们或许熟悉本书中的大部分内容,但我相信本书一定会提供一些新的视角,可以给他们提供很多新的内容。具体来说,本书会探讨一系列重要内容,包括安全设计、安全审查和一些很少见诸文字的“软技能”。
本书第三部分会介绍漏洞实施与缓解的方法,包括一些用C或Python语言编写的代码的简短摘录。有些例子假定读者已经熟悉了内存分配的概念,也理解了整数和浮点类型,以及二进制运算。在为数不多的地方,我使用了数学公式,但是复杂度不会超过模和指数运算。如果读者认为这些代码和数学知识的技术性太强,可以跳过这些章节,完全不需要担心因此而无法把握本书叙事的主线。
本书共有13章,分为三大部分,其中包含了概念、设计和实施,以及最终的结论。
本书第1~5章介绍了基础概念。第1章是对信息安全和隐私基础所做的概述。第2章则介绍了威胁建模,以保护资产为背景,对攻击面和信任边界的核心概念进行了具体说明。接下来的三章介绍了一些可供读者使用以实现软件安全的重要工具。第3章探讨了对可识别威胁进行防御性缓解的通用策略。第4章介绍了一些有效的安全设计模式,同时着重说明了一些应该避免的反模式。第5章用工具箱的方式解释了如何使用标准的加密库来缓解常见的风险,同时本章并没有探讨底层的数学原理(这些知识在实践当中很少用到)。
这一部分也许代表了本书对潜在读者最独特也是最重要的贡献。第6章和第7章介绍了如何使软件设计变得安全,以及可以应用哪些技术来实现安全性,它们分别从设计者和审查员的角度分析了问题。在这个过程中,解释了为什么一开始就将安全性融入软件设计是很重要的。
这两章借鉴了本书第一部分介绍的思想,提供了如何将它们结合起来构建安全设计的具体方法。审查方法直接来自作者的行业经验,其中包括了能够适应你的工作方式的分步过程。你可以在阅读这些章节时浏览附录A中的设计文档示例,以此当作将这些想法付诸实践的示例。
第8~13章涵盖了实施阶段的安全性,涉及部署、运维直至生命周期结束。在你有了一个安全的设计后,这一部分将会阐释如何在不引入额外漏洞的情况下进行软件开发。这些章节中包含了代码片段,用来说明漏洞是如何潜入代码中的,以及如何避免出现漏洞。第8章介绍了程序员面临的安全挑战,以及代码中真正的漏洞是什么样的。第9章涵盖了计算机在计算上的弱点,并说明了针对动态内存分配的C风格显式管理是如何对安全性造成破坏的。第10章和第11章涵盖了许多众所周知,但还没有消失的常见错误(比如注入攻击、路径遍历、XSS和CSRF漏洞)。第12章介绍了那些在很大程度上还没有得到充分利用的测试实践,目的是保证我们的代码都是安全的。第13章介绍了安全实施的指导方针,包括一些一般性的最佳实践,同时也对常见的陷阱提出了警示。
本书这一部分摘录的代码一般都会展示需要加以避免的漏洞,同时也会给出修补之后的版本来展示如何让代码更加安全(在书中这两类代码分别标记为“易受攻击的代码”和“修复后的代码”)。书中提供的代码不是为了让读者进行复制,然后运用到生产软件当中的。即使是修复后的代码仍然会因为其他原因而在不同背景下存在漏洞,所以读者不应该把本书提供的任何代码视为绝对安全的代码并加以应用。
后记对本书的内容做了总结,同时对我希望能够产生积极影响的一些方法进行了介绍。在这里,我对本书中的几大重点进行了总结,也对未来进行了展望,并提供了有助于提升软件安全性的一些预测——其中,首要的一点是本书如何为提升未来软件的安全性做出贡献。
附录A是一份设计文档示例,展示了在实践中安全设计文档大致应该如何编写。
附录B是本书中出现的所有软件安全术语的列表。
附录C包含了一些开放式的练习题,以供需要的读者进一步探索。
附录D是一系列重要概念和流程的备忘单。
此外,本书中提到的参考文献汇编可以从异步社区下载获得。
在开始介绍正式内容之前,出于对本书中介绍的安全知识负责的考虑,我想先提出一些警告。为了解释清楚如何让软件更加安全,我需要介绍各类漏洞如何发挥作用,以及攻击者如何对这些漏洞加以利用。从攻击和防御两个方向实践是磨练技能的理想方式,但我们在使用这些知识的时候也需要谨慎。
永远不要在生产系统上试探安全性。比如,在读到有关跨站脚本(XSS)的内容时,读者或许想要试着用经过修改的URL来浏览自己最喜欢的网站,看看会发生什么。千万不要这么干!即使我们的动机是善意的,这种做法从站点管理员角度看仍然和真正的攻击别无二致。读者必须考虑到别人可能会把这类行为解读成威胁。而且与此同时,这种行为在有些国家和地区可能存在法律问题。我们要运用常识,包括思考别人会如何解读我们的行为、我们行为出现差错以及我们的行为跨越红线的后果。因此,如果我们希望尝试一下XSS,可以用伪造的数据来建立自己的Web服务器,然后用这个服务器来练手。
另外,虽然我根据自己在软件领域的多年经验尽可能为读者提供了最好的建议,但没有任何指导方针是无懈可击的,也没有任何指导方针适用于所有环境。本书提到的解决方案绝不是什么“仙丹”,它们只是我提供的建议,或者读者应该掌握的一些常见方法。在对安全决策进行评估的时候,读者应该依靠自己的判断力。没有一本书可以替读者做出判断,但本书可以协助读者找出正确的做法。
本部分内容
■ 第1章 基础
■ 第2章 威胁
■ 第3章 缓解
■ 第4章 模式
■ 第5章 密码学
诚信是基础,而且往往是坚实的基础。哪怕我说过的话让我身陷困境,诚信依旧是我立足的基石。——古德
实现软件安全既需要运用逻辑,又是一项艺术——一项仰赖直觉来做出判断的艺术。它既需要践行者对当代数字系统有所掌握,又需要他们对人与系统之间的交互有所体悟。如果这样说让你感到前路维艰,那么你就已经体会到了本书想要诠释的难点所在。这也解释了为什么实现软件安全自始至终都是一项艰巨的任务,以及为什么在这个领域的任何斩获都需要人们付出艰苦卓绝的努力——哪怕后面的路依然很长。好消息是,因为我们每个人都可以提升自己的认知水平,也都可以切实地参与其中,所以我们的每一分努力都会给软件安全带来实质性的改善。
首先,我们需要准确地思考一下何谓安全。安全定义的主观性颇强,因此厘清安全的基本概念就显得至关重要。本书是我在个人经验基础上进行深入思考的结果。信任是一切安全的基本要素,因为每个人都需要使用别人的劳动成果:当代数字系统已经过于复杂,没有人可以凭一己之力从硅元素开始打造自己的“数字王国”。我们必须信任别人提供的成果(包括硬件、固件、操作系统和编译器),信任这些并不是由我们亲手设计和制造的组件。在这样的基础上,下面将介绍安全的六大经典原则,其中包括信息安全的三大基本原则,以及实现信息安全三元素的三个“黄金标准”。最后,鉴于数字产品和服务正在越来越多地渗透到现代日常生活中最敏感的领域,本章在信息隐私方面增加了一些应该考量的重要因素——其中包括人类因素和社会因素。
虽然本书的读者无疑都对安全、信任和机密性的含义有所了解,但是在本书中,这些词采用了具体的技术表意,其含义值得仔细品味。鉴于此,我建议读者认真阅读本章的内容。至于那些基础更好的读者,请大家试着写出自己对这些术语的定义——每位读者未来都应该尝试做一做这份家庭作业。
世上所有生命体都会本能地远离风险。在面对攻击行为时,他们也都会采取自卫手段,并且躲进他们能够找到的安全港湾。我们与生俱来的生理安全本能在发挥作用时何其伟大,体会到这一点非常重要。与之形成鲜明对比的是,我们在虚拟世界中应对风险的本能付之阙如,攻击者制造虚假信号则易如反掌。在从技术的角度诠释安全之前,我们思考一下在现实世界中人们会采取什么行为(读者很快就会看到,在数字世界,我们需要一套全新的技能来应对风险)。
下面是一则关于汽车销售员的真实故事。在让一位客户进行试驾之后,这位汽车销售员和客户回到了汽车销售点。当汽车销售员下车后,他还在和客户交流,但是同时他已经绕到了车前。“在我看到他眼睛的时候,”这位汽车销售员回忆道,“我当时就想,我的天哪,这个人想偷我的车。”事情转瞬之间发生了:这位假扮成客户的偷车贼挂上前进挡并踩下了油门,与此同时汽车销售员立刻趴在了汽车的引擎盖上。偷车贼疯狂驾驶也不能把汽车销售员从引擎盖上摔下去(好在这位汽车销售员并无大碍,偷车贼也很快被绳之以法,并且被勒令赔偿损失)。
两个人目光交错之际,汽车销售员脑海中就已经在对风险进行某种微妙的计算。电光石火之间,这位汽车销售员已经处理了复杂的视觉信号,他读取了客户的面部表情和肢体语言,明确得出了这位客户要采取攻击行为的结论。现在我们想象一下,当这位汽车销售员成为鱼叉式网络钓鱼攻击(这是一种向特定目标群体(而不是普罗大众)发送欺诈邮件的攻击方式)的目标时会做出什么反应。在数字世界里,因为他无法和攻击者面对面来获得那些重要的信号,所以让他上当就会容易得多。
说到信息安全、计算机、网络和软件,如果我们想要保护数字系统的安全,就必须进行分析和思考来评估我们所面临的风险。虽然比特和代码既无形,也无声,更无味,但这绝不应该妨碍我们努力评估安全风险。每当在线查看数据的时候,我们都是在使用软件把信息用适合人类阅读的格式显示出来。一般来说,在我们和真实的比特之间有不少代码在对信息进行诠释。其实,这还真如镜花水月一般,所以我们必须信任自己使用的工具,相信它们让自己看到的代码就是真实的信息。
软件安全的核心目标是保护数字资产,让它们不会受到各种威胁的侵害。本章会介绍一系列基本安全原则,这些原则在很大程度上推动了软件安全的实现。如果我们从这些重要的原则入手来分析系统,就可以看到漏洞是如何渗透到软件当中的,同时也可以意识到如何才能积极避免和缓解这些问题。这些基本原则以及本书后续章节要介绍的一些设计方法不仅适用于软件领域,也同样适用于设计和管理自行车锁、银行金库或者监狱等。
“信息安全”这个专有名词专门指代数据的保护和访问权限的授予。软件安全则是一个比较宽泛的概念,软件安全专注于可靠软件系统的设计、实施和操作,包括使其通过可靠的方式来实现信息安全。
信任在数字世界也同样重要,但是在数字世界中,人们常常认为信任是理所当然的。软件安全从根本上都要依赖于信任,因为没有人可以控制一个系统的所有组件,没有人可以自己编写所有的软件,也没有人可以对自己合作的所有供应商进行审查。如今的数字系统全都非常复杂,复杂到哪怕是全球顶尖科技“巨头”也不可能从零开始搭建一个完整的科技栈,从硅材料到操作系统、网络、外设,再到把它们结合起来,让它们形成各司其职的软件体系。我们日常使用的系统无不是大规模、复杂的卓绝技术成就。因为没有人可以自己从头搭建所有这些系统,所以企业都会根据功能或者价格来选择自己的软硬件产品——这里值得留意的是,所有选择本质上都是建立在信任基础上的决策。
安全性要求我们认真地分析信任关系——哪怕没人有时间、有资源来对所有资源进行彻底的调查和验证。如果无法做到充分信任,就意味着企业必须完成大量不必要的工作去保护一个很可能不会面临任何实质威胁的系统。反之,如果无条件地信任,未来则有可能会措手不及。说白了,如果你完全信任一个实体,它们也就基本上不需要为失败承担任何后果了。违背信任有下面两种完全不同的形式:恶意行为(如欺骗、谎言、诡计等)和失职行为(如错误、误解、疏忽等)。
在信息缺失的情况下做出重要决策,是人们最需要信任关系的场景。不过,我们与生俱来的信任感依赖的是微妙的感官信号,而这种本能不适用于数字世界。在下文中我们会首先探讨“信任感”这个概念,剖析我们日常产生的信任感到底为何物,然后把信任感的概念推广到软件领域。随着阅读的深入,读者应该尝试把自己对软件的看法和自己建立信任感的本能联系起来。利用好自己的信任本能是一种强有力的手段。久而久之,我们就可以对软件安全运用类似的信任本能,这比多少技术分析都更加有效。
理解信任感的最好方法就是在我们依靠信任来做出判断的时候,仔细品味那种感受。读者可以进行一个思想实验,也可以找一位自己能够绝对信任的人在现实生活中进行尝试。想象和一位朋友走过一条繁华的大街,前面不远处就是熙熙攘攘的车流,街道中有一条人行横道,告诉这位朋友你想让他/她引导你走过这条人行横道。在这个过程中,你完全依靠这位朋友来安全地走过这条人行横道,你会闭上双眼,绝对按照他/她的指示行动。你们两人手拉手走过这条人行横道,你任凭这位朋友帮你转过身,让你面对这条人行横道;在前方出现危险时挡住你,不让你继续前进。你听到耳边汽车呼啸而过,知道自己的朋友在等待安全的时刻才会让你继续前行——这一刻你的朋友已经化身为你的保镖。这时,你的心跳可能会明显加速,你可能会警惕地倾听着身边一切预示着有可能出现危险情况的声音。
现在,这位朋友准确无误地指引你前行。如果你打算就这样闭着眼走在马路上,你的这种感受就是真正的信任——当然也有可能还达不到完全的信任。你的意识可以敏锐地察觉到显而易见的危险,你的感官高度紧张从而快速确认周遭的安全,你的内心深处则有个声音在不断警告你不要再走了。你与生俱来的内部安全监控系统无法收集足够的信息,因此希望你先把眼睛睁开再继续前行。如果这位朋友判断出现了失误怎么办?如果这位朋友口蜜腹剑,想要置你于死地又当如何?归根结底,是你对这位朋友的信任让你忽略这些本能,走过面前的人行横道。
我们应该提升自己对数字信任方面的决策的认识,这样才能帮助别人看清这些决策给安全带来的影响。在理想情况下,当我们给一项重要的服务选择组件或者厂商时,在刚才那种练习中我们用来指导自己做出信任决策的直觉同样能够发挥作用。
上述讨论是为了强调一点,当我们自以为“眼睁睁地看着这些数据”的时候,我们其实看到的只是一种与数据本身距离十万八千里的数据展示方式。其实,我们看到的是屏幕上的一系列像素点,虽然我们认为这些像素点展示了数据,但是我们对这些数据的物理保存位置并不知情,这些数据很可能经历了数百万条命令才被转换为人类可以读懂的信息,并且最终显示在我们的显示器上。数字科技让信任这件事变得相当棘手,因为它如此抽象、迅捷,看不见、摸不着。当我们浏览数据时,一定要切记内存中的数据与我们阅读数据时看到的那些像素点之间隔着大量的软硬件操作。我们又怎么知道这里面有没有哪个环节恶意歪曲了数据的含义呢?数字信息的基本事实是极难直接进行分辨的。
网页浏览器上显示的锁形图标表示浏览器和网页站点之间建立的是安全的连接。这些图标出现与否,只是为了向用户表明一点,即连接是否安全。但是在这个图标的背后,包含一系列数据和大量的计算(这些内容我们会在第11章详细介绍),这些最终都被包含到一个二进制的“是/否”提示符中。哪怕是专家级别的开发人员,让他们手动确认一个实例的可靠性也是一个相当艰巨的任务。我们能做到的只有信任这些软件,我们当然也有理由信任这些软件。这里的关键在于,我们必须弄清楚这种信任的深度和广度,而不是认为所有的信任都是理所当然的。
大多数攻击始于软件的缺陷或者误配,这些问题可能都是那些诚信有加、心存善念的程序员和IT人员制造出来的——毕竟,是人就会犯错。因为软件使用许可都会包含免责声明,所以一切软件都是在用户知悉和认可风险的前提下使用的。如果诚如人们所言,“所有软件都有bug”,那么其中总有一些bug可以被利用,攻击者也总能找到一些bug并且加以利用。软件专家一般很少因为错信了恶意软件,而成为攻击者的目标。
好消息是,我们并不难判断哪些操作系统、编程语言比较可靠。大型企业在提供和支持高质量软硬件时,都有可供追溯的历史,因此信任这些企业就在情理之中。而对于那些难以追溯历史的软件供应商来说,信任它们则存在一定的风险。它们固然也有一些技术高超、动力十足的员工为之努力工作,但这个行业本身缺乏透明性,因此人们很难判断它们的产品是否安全。开源提供了这种透明性,但是开源软件的安全水平取决于项目甲方对开发者的监管是否严格到足以防止开发者在软件中有意无意地插入恶意代码。显然,没有一家软件公司会承诺在发生攻击事件时提供更高级别的安全性或者向用户提供赔偿,以此彰显自己在业内的特殊地位。因此,作为客户,我们也没有这样的安全选项。无论法律、规范还是商业协议,都提供了一些额外的方法来减轻人们执行信任决策时面临的不确定性。
信任决策的重要性固然不可小觑,但是也没人能够永远做出正确的决策。实际情况是,信任决策从来都是不完美的。这就像美国证券交易委员会警告人们的那句话一样:“过去的表现无法担保未来的结果。”好在人们已经学会了如何权衡信任感(虽然人们更擅长面对面,而不是通过数字媒体来判断谁更值得信任),而且在绝大多数情况下,我们的信任决策都是正确的——只要我们信息准确,目标清晰。
信任永远是分不同程度的,在对信任的评估过程中也总是包含一定的不确定性。信任是一个频谱,在这个频谱的一端,比如在进行一场大手术的过程中,我们就是把自己的性命托付给了那些医疗从业者,我们不仅放弃了对自己身体的控制权,也放弃了监控手术操作的任何意识和能力。在最糟糕的情况下,一旦手术失败,我们其实也就没有了任何追诉的机会(关于遗产的合法权利除外)。日常的信任程度就会小得多:信用卡额度的上限是银行没有收到欠款也能承受的损失;汽车则有代客钥匙,我们可以用它限制人们打开后备箱。
鉴于信任是一个频谱,“信任但仍要验证”的策略就是一个强大的工具,可以让我们在绝对信任与绝对不信任之间建立起一座桥梁。在软件领域,我们可以通过授权和审计来达到这样的效果。一般来说,审计包括自动审计(准确地校验大量重复的活动日志)与手动审计(选择性校验,以处理那些比较罕见的情况,它把人工核查作为最终决策的依据)。本章后文还会对审计进行进一步的探讨。
在软件领域,人们有两种选择:信任或不信任。有些系统对应用设置了各式各样的许可,人们需要手动来允许或者禁止它们。如果存在疑问,我们大可以选择不信任——只要我们有理由信任另一个候选的解决方案。如果你在评估的时候标准过高,导致没有任何一种产品可以获得你的信任,那么你就只能大费周章、自己动手开发组件了。
做出信任决策的过程就像给一棵“信任树”剪枝,这棵树如果不加修剪就会长出无穷的枝杈。如果我们相信某项服务或者某台计算机是安全的,我们就不需要花费大量精力进行深入分析了。如果我们无法做到信任,那么我们就需要对系统的更多组成部分(包括很多微组件)加以保护。图1-1演示了做出信任决策的过程。如果没有完全信任的云服务来保存我们的数据,那么我们就必须自己运维服务器,这又需要我们继续加以判断:到底是使用一项可靠的托管服务还是自己搭建Web服务器,到底是使用我们可以信任的现成数据库软件还是自己编写一个数据库软件?不难发现,如果我们不信任供应商,我们就需要继续做出信任决策,毕竟我们不可能完全靠自力更生。
对于那些显然不能信任的输入信息(尤其是从公共互联网或者客户端发来的输入信息),我们当然应该表示怀疑,怎么谨慎都不为过(详见本书第4章)。即使在处理那些可以信任的输入信息时,我们仍然不应以绝对可靠视之。如果我们只是希望降低整个系统的脆弱性,防止因软件问题导致错误传播,则可以在条件允许的情况下,尽可能增加一些安全校验。
图1-1 演示做出信任决策过程的决策树
每个软件项目都会依赖大量存在隐式信任(implicitly trusted)的技术,包括硬件、操作系统、开发工具、库和其他很难核查可靠性的技术,所以我们只能根据供应商的声誉选择相信这些工具。不过,读者还是应该对隐式信任的概念有所了解,并且给予适当的重视——尤其是准备大幅度扩展隐式信任的范围之前。
管理隐式信任没有简便方法,不过下面这种思路也许可以给读者带来启迪:把你认定为可靠的对象数量降至最低。如果你现在已经选择了使用微软或苹果等公司的操作系统,就继续使用它们提供的编译器、库、应用以及其他产品和服务,这样可以把信息暴露的风险降到最低。你可以这样理解个中逻辑,即每多信任一家公司,就给了这家公司一次让你失望的机会。另外,从实用的角度来看,同一家公司产品线中的产品往往兼容性更好,这些产品之间的互操作也经历过更多的测试。
最后,也不要忘了从另一个角度来思考信任决策,那就是在我们提供产品和服务时也需要提升客户的信任感。每个软件产品都必须让终端用户认为这个产品是值得信赖的。在大部分情况下,我们只需要展现出自己的专业性就可以了,但是如果这项产品提供的功能相当重要,就必须让客户有坚实的理由信任我们的产品。
下面是在工作中提升信任感的几种基本方式。
● 保持透明可以提升信任感。公开自己的工作可以让客户评估产品的安全性。
● 让第三方参与,利用第三方的独立性来提升信任感(比如,可以聘请独立的审计人员)。
● 有时,我们的产品就是需要和其他产品进行集成的第三方产品,因为独立交易的双方很难相互勾结,所以这项产品也可以提升客户的信任感。
● 在出现问题的情况下,要主动接受客户的反馈,然后果断采取行动,并且公开披露调查的结果,以及提出防止问题再次发生的措施。
● 有些特性或者设计要素可以把信任感具象化,让客户可以亲眼看到——比如,通过一种存档解决方案来实时显示在不同位置保存了多少份备份。
行动可以提升信任感,空洞的口号则会让那些精明的客户产生怀疑。我们可以提供一些有形的证据,最好是可以让客户自己进行验证的证据。虽然真有能力审查开源代码质量的人寥寥可数,但是开放代码给人们审查(让他们知道总有懂得如何审查代码的人会去审查这些代码)本身也能够提升信任感。
信息安全的指导原则可以追溯到计算机行业早期,彼时计算机还保存在上锁的机房里,房间中还安装着高架地板和空调,这些计算机也才刚刚开始连接到网络当中。这些传统信息安全原则简直就是当代信息安全领域的“经典物理学”:很多应用适用这些信息安全模型,但并不是所有应用都适用,新开发的应用尤其不适合这些传统模型。譬如在现代数据保护方面,人们对信息机密性那些巨细靡遗的思考和处理,在传统信息安全原则中就难觅踪迹。
基本原则可以轻而易举地分为两组,每组三项原则。第一组的三项原则可以称为CIA原则,这三项原则定义了访问数据的要求;第二组的三项原则关注的则是如何控制和监测数据访问,我们称之为黄金标准(gold standard)。这两套原则相互依存,它们只有作为一个整体时才能有效地保护数据资产。
除了防止未经授权的数据访问,还有一个问题是哪些组件和系统有权发起访问。这是一个更加复杂的信任问题,它的复杂程度已经超出了信息安全的范围。不过,为了保护数字系统,人们无法回避这个问题。
传统上,软件安全都构建在信息安全的三大基本原则之上,即机密性(confidentiality)、完整性(integrity)和可用性(availability)。围绕着数据保护的基本概念,这三大基本原则的意义其实都很直观。
机密性:不会泄露信息——只允许已授权的数据访问。
完整性:准确维护数据——不允许未经授权的数据修改或者删除。
可用性:保障数据的可用性——不允许出现严重延迟或者未经授权的数据访问关闭。
这些简单的定义都对它们的目标和防御措施进行了描述。在审视设计方案时,我们应该考虑有哪些破坏信息安全的可能因素,同时考虑如何采取对应的防御措施。
CIA的三大组成部分代表的都是理想模型,而人们一定要避免在安全问题上追求完美。比如,即使是那些经过了可靠加密的网络流量,只要窃听者下定决心,那么他/她也能从这些流量中得出一些信息,比如双方交换的数据量。从技术上看,端点之间的数据交换本身就会削弱交互的机密性。但是从实用角度上讲,除非我们采取极端措施,否则这个问题无法解决,同时这个风险又实在太小,小到忽略它也不会对安全构成影响(隐藏通信数据量的一种方法是让端点始终交换恒定数量的数据。在实际流量较低的时候,则让端点发送虚拟数据包(dummy packet)来维持恒定的数据量)。哪些行为和数据量息息相关,攻击者又会如何对这些信息加以利用?本书第2章会具体解释类似的威胁评估方法。
读者也许已经注意到:授权是CIA各项原则中都固有的元素,它规定数据只能由合法的人员进行访问、修改,数据可用性也只能交由合法人员进行管理。界定哪些行为“合法”非常重要,而授权策略恰恰就是为此而生的,但授权策略并不包含在这些基本的数据保护概念中。授权策略相关内容会在后文的“黄金标准”部分进行探讨。
维护机密性意味着按照一种仅授权者可读的方式来保护隐私信息。这听起来很容易,但是实践起来就涉及很多复杂的问题。
首先,我们必须认真判断哪些信息属于隐私信息,这一点十分重要。设计文档应该明确地对此加以区分。虽然乍看之下,哪些信息属于敏感信息显而易见,但是人们在这方面的看法常常大相径庭,因此如果没有明确的标准就会导致误解。最安全的做法是把所有从外部收集到的信息都默认为隐私信息,直到有明确的策略来进行界定,并且解释清楚为什么可以对这样的设计方法进行适度的松绑。
下面是把数据视为隐私数据的一些原因,这些原因常常为人们所忽视。
● 一位终端用户可能会理所当然地希望他们的数据是隐私数据(即使这些信息被泄露也无伤大雅),除非他们明确告知某些数据并非隐私。
● 人们可能会把敏感数据输入不同用途的文本字段中。
● 信息的收集、处理和保存有可能需要满足各类法律法规的要求,而很多人往往对这些法律法规一无所知(如果欧洲用户访问你的站点,这次访问行为可能就需要符合欧盟的法律法规,例如《通用数据保护条例》)。
在处理隐私信息的时候,我们应该判断哪些访问属于合理的访问行为。判断人们何时、通过何种方式披露隐私信息就属于信任决策范畴。我们不仅应该明确说出访问规则,还应该解释这些规则背后的主观判断。
机密性泄露也有一个频谱。完全的信息泄露是指攻击者获取到了完整的数据集,其中包括元数据。这个频谱的另一端则是程度相当轻微的信息泄露,比如内部错误消息或者其他不会造成什么后果的信息被泄露了出去。以部分信息泄露为例,我们可以设想一下给新的客户分配序列号:心怀不轨的友商可以不断注册新客户,从而获取客户的序列号,然后计算这些客户编号的数值差来判断各个时间段内企业的新客户数量。所有泄露受保护数据详情的行为,在某种程度上都属于机密性遭到破坏的情形。
人们经常对那些看似无关痛痒的信息泄露付之一笑。然而,攻击者利用信息的方式很有可能与开发人员的初衷大相径庭。不仅如此,攻击者也可以把多个信息片段组合起来,从而获得远比其中任何只言片语多得多的内容。仅仅获取某个人的地址邮编或许用处不大,但是如果你还知道对方大致的年龄,同时也知道对方是一位医学博士,你就可以把这些信息组合起来,在一个人口密度不高的区域中定位到这个人的位置。这个过程如今称为去匿名化(denonymization)或者重标识(reidentification)。研究人员通过分析一个貌似由网飞公司发布的匿名数据集,就能在大量用户账户与IMDB账户之间建立匹配关系。这说明了一个道理:你钟爱的那些电影就足以出卖你的个人身份。
在信息安全这个语境里,完整性就是指数据的真实性和准确性,其宗旨是防止数据被随意删改。除了通过技术手段防止数据遭到未经授权的篡改,一份准确的数据来源记录(包括最初数据源,以及之后授权的数据源变更)也是相当重要、强大的完整性保障。
保存重要数据的版本并且记录它们的来源,这本身就是防止篡改攻击的典型手段。这说起来十分简单,就是保留一份良好的备份数据。执行增量备份是一种理想的攻击预防手段,因为增量数据保存简单,同时又以一系列快照的形式翔实地记录了哪些数据执行过变更,以及它们在何时执行过变更。不过,完整性的需求不只是保护数据这么简单,它还包括确保组件、服务器日志、软件源代码与版本,以及其他取证信息的完整性。这些取证信息可以在问题真的发生时,帮助我们判断并找出遭到篡改之前的原始信息源。除了限制管理访问之外,安全摘要(类似于校验和)和数字签名都可以用来执行强有力的完整性校验,这些内容会在本书第5章进行介绍。
读者应当切记,篡改包括很多不同的方式,并不一定是修改存储设备当中的数据。比如在Web应用中,篡改可能发生在客户端一侧,也可能发生在客户端和服务器之间;手段包括欺骗其中某一方修改数据,也包括在页面上修改脚本,等等。
针对可用性的攻击是网络世界无法回避的现实问题,也是最难防御的攻击方式之一。这类攻击最简单的形式是攻击者向服务器发送过量的数据,通过看似合法的手段占用海量服务资源,导致服务资源耗竭。这表明信息会偶尔不可用。虽然永久丢失的数据也属于不可用的数据,但是这类数据一般会被认为属于完整性受到彻底破坏的情况。
匿名的拒绝服务(Denial of Service,DoS)攻击(一般都是为了索取赎金)几乎可以威胁到一切互联网服务,所以防御这类攻击是非常艰巨的挑战。为了更好地防御这类攻击,我们需要利用有能力承受大量负载的基础设施来承载大规模的服务,同时维护系统的灵活性,确保在事件发生时基础设施能够迅速实现迁移。谁也不知道DoS攻击的频率和成本,因为很多受害者都是自行解决问题的。但毫无疑问的是,我们应该提前制定好详细的计划,为应对这类情况做好准备。
很多其他类型的可用性威胁的原理与此类似。对于一台Web服务器来说,攻击者创建的恶意请求可以触发错误,导致崩溃或者无限循环,最终破坏服务器的服务。此外,其他的攻击方式可以导致应用的存储、计算或者通信出现超载,或者使用破坏缓存有效性的模式,这些都可以导致相当严重的问题。对软件、配置或者数据进行未经授权的破坏都可能对可用性产生负面的影响(甚至对备份数据进行破坏,也有可能导致延迟)。
如果CIA是安全系统的目标,那么黄金标准描述的就是达到这个目标的方式。在拉丁语中,Aurum是黄金的意思,因此黄金的化学符号就是Au,碰巧信息安全的重要原则也都是以这两个字母作为首字母的。
● 认证(authentication):用高度可靠的方式来判断主体的身份。
● 授权(authorization):仅允许通过认证的主体执行操作。
● 审计(auditing):为主体所执行的操作维护一份可靠的记录,以便进行监控。
提示:这几个单词不仅长,而且长得差不多,读者可能会遇到一些简写,即用authN代指认证、用authZ代指授权。这样可以通过一种简短的方式来清晰地区分这些术语。
所谓主体是指一切通过了可靠认证的实体,包括一个人、一家企业或单位、一个政府实体,以及一个应用、服务、设备或者其他有权执行操作的对象。
认证的过程,是指通过可靠的方式来建立主体证书可靠性的过程。系统往往会要求用户证明自己了解用户账户所对应的密码,以此为注册用户执行认证。不过,认证的概念也可以更加宽泛。证书包括主体了解之事(如密码)、所有之物(如令牌)或者主体自身的属性(如生物数据)。在后文中,我们会详细对证书进行探讨。
认证后的主体在执行数据访问时,也会受到授权结果的限制。授权会根据预先设定的规则允许或拒绝用户的行为。例如,设置了访问控制规则的文件系统可能会针对某些用户把一部分文件设置为只读。这就像在一家银行中,柜员可能会把达到某个额度的交易记录下来,但是如果额度过大,这次交易就需要得到经理的批准。
如果一项服务保留了一个安全日志,而这个日志可以准确地记录主体执行的操作(包括那些因为授权失败而没有成功执行的操作),那么接下来管理员可以执行审计来对系统的操作进行监控,确保所有操作都是合理的。如果希望实现强大的安全性,准确的审计日志至关重要,因为它们提供了对真实事件的可靠记录。详细的日志可以为我们提供一份历史记录,揭示发生异常情况或者可疑事件时的准确情况。如果读者发现某份重要的文件不见了,那么日志在理想情况下应该对此提供各类详细信息,包括谁删除了这份文件、何时删除了这份文件等,以便技术人员以这份日志为依据,对此事进行深入调查。
黄金标准充当的是一种实现机制,旨在对CIA提供保护。本书此前把机密性和完整性定义为防止未经授权地泄露或篡改信息的行为原则,而可用性则会受到授权管理员的控制。真正兑现授权决策的唯一方法,是确保使用系统的主体都是正常通过认证的主体。审计则负责提供可靠的日志,记录谁、何时执行了什么操作,再由技术人员定期审查违规行为,并保留追究违规者责任的权利。
安全设计方案应该明确地把认证和授权这两者分开,因为把认证和授权结合在一起往往会导致混乱。如果能够把两者明确地分开,审计追踪工作也会变得更加清晰。我们通过下面两个现实生活中的例子,解释为什么认证和授权应该分开。
● “你为什么把那个家伙放进保险库?”“我也不知道,但是他一看就是合法人员啊。”
● “你为什么把那个家伙放进保险库?”“他的ID卡上写着Sam Smith,他还拿着支行经理手写的纸条。”
第二种答复比第一种明显要更加完整,因为第一种答复基本上一文不值,只能证明安保人员没动脑子。即使保险库被人入侵了,第二种答复可以提供详细的信息以供人们进行调查:支行经理有权限放某人进入保险库吗?那个纸条是支行经理写的吗?如果安保人员保留了ID卡的复印件,这份复印件也可以帮助人们找到Sam Smith。如果支行经理写的纸条上只是显示“让持条者进入保险库”(相当于仅做了授权,没有做认证),调查人员就很难弄清楚发生的情况,也很难调查出入侵者的真实身份。
认证的目的是,根据能够证明主体真实身份的证书,测试该主体的真实身份与其所声称的身份是否一致。认证服务也可能会使用一种更强形式的证书,譬如数字签名或者挑战码。这些证书的形式可以证明主体拥有与其身份相关联的私钥,浏览器就是这样通过HTTPS来认证Web服务器的。数字签名是一种更加理想的认证形式,因为数字签名可以让主体在不泄露密码的情况下证明自己掌握密钥。
证书提供的认证信息包括下面这几类。
● 所知之事(something you know):如密码。
● 所有之物(something you have):如安全令牌。在虚拟世界中,所有之物也包括某种类型的证书、密码或者签名文档,这些信息必须是无法伪造的。
● 自身属性(something you are):即生物特征(如指纹、虹膜等)。
● 所处之地(somewhere you are):经过验证的所在地,如与安全场所中私有网络建立的连接。
在这些认证方法中,有很多方法都是相当简单的。你的所知之事可能泄露,你的所有之物可能失窃,你的所处之地有很多方法可以加以操纵,就连你的自身属性都有可能被伪造(甚至如果遭到盗用,我们连修改都很困难)。在当今网络世界,认证基本上已经在网络中得到了普及,认证行为几乎每时每刻都在发生,甚至有时认证这项任务比现实世界中的身份认证都要复杂。例如,在网络中,浏览器会充当信任代理的主体。浏览器会首先在本地执行认证,并且浏览器只有在认证成功的前提下才会把加密的证书发送给服务器。系统通常会使用多个认证要素来避免认证信息遭到盗用的问题,同时频繁地进行审计也是认证的一大重要后盾。用户使用两项认证要素总比使用一项认证要素要好(但也好不了太多)。
不过,当人们加入一家公司、建立自己的账户,或者在忘记密码后请技术支持团队恢复自己的访问时,组织机构必须能够判断人员的真实身份,才能为他们分配证件。
比如,在我入职谷歌公司的时候,所有新员工在周一上午集合,我们对面是几位IT管理员。他们按照新员工名册一一检查了我们的护照或者其他身份证件。检查无误之后,他们才发给我们工牌和公司的笔记本电脑,并让我们设置自己的登录密码。
IT团队检查我们的证书(也就是我们的身份证件等),就是为了判断我们提供的材料是否能够准确无误地证明我们的身份。这份证书所提供的安全性取决于很多因素,包括官方证件及其支持文件(如出生证明)的完整性和准确性、伪造这些证件的难度、非法获得这些证件的难度等。在理想的情况下,一份从出生时注册身份一直更新至今的信息链,应该在我们的一生中都能够保存完整,这样才能唯一地标识我们的身份信息。安全地标识一个人的身份的难度很高。因此,为了保留一些隐私和自由,人们宁愿在日常生活中选择不那么严格的措施。本书的重点并不是如何鉴别一个人的身份,而是前文刚刚提到的黄金标准。身份管理这个更加复杂的问题这里不赘述。
只要条件允许,我们就应该依靠现成、可靠的认证服务,而不应该动辄“自力更生”。哪怕是简单的密码认证也很难安全地落实。如何安全处理自己已经忘掉的密码更是一个麻烦的问题。一般来说,认证的过程应该包括对证书进行校验,并给出认证通过或者认证失败的响应消息。不要采用认证“部分成功”的做法,这样等于在暗示黑客可通过不断试错来破解密码。如果希望降低暴力破解密码的风险,常见的做法是让认证密码从根本上很难通过计算破解,或者在认证流程中引入一些延迟(详见本书第4章介绍的“避免可预测性”)。
在对用户进行认证之后,系统必须找到一种方式来安全地绑定主体身份。一般来说,认证模块会给主体颁发一个令牌,主体在认证中使用令牌即可,这样就可以避免后续被要求执行完整的认证过程。这里的关键在于,主体可以借助代理(如浏览器),直接把认证令牌作为一种证明自己身份的信物,为未来的身份认证请求提供一种安全的方式。这种方式会代表认证主体来绑定存储的令牌,在未来收到请求的时候提供令牌即可。网站往往会通过浏览会话所关联的安全Cookie来达到这个目的。不过,针对其他主体和界面,也有很多不同的方法。
安全绑定认证实体后可以用两种基本方式进行入侵。第一种方式显而易见,那就是攻击者可以盗用受害者的身份。此外,接受认证的主体也有可能与其他人相勾结,把自己的身份信息泄露给别人,甚至把自己的身份强加给别人。几个人分享同一个付费订阅的节目就属于这种入侵方式。网站对这种方式基本上无能为力,因为主体和令牌之间的绑定关系本来就是比较松散的,何况这种关系还取决于主体自己是否愿意合作。
到底应该允许还是应该拒绝重要的操作行为?这类问题应该根据主体在认证时确立的身份来决定。各类系统会通过业务逻辑、访问控制列表或者其他正式的访问策略来实现授权功能。
匿名授权(即不进行认证的授权)适用的场合可以说寥寥无几。在现实生活中,匿名授权相当于拿着车站公共储物柜的钥匙存取个人物品。根据时间设置访问限制(比如,仅允许员工在工作时间访问数据库)也是匿名授权的一个示例。
针对一项资源,应该通过一项单一的要素来实施授权,不要让授权代码散布在整个代码库中,否则这会是运维和审计人员的一场噩梦。因此,应该依靠某一项公共框架来唯一地提供访问授权。精良的设计方案可以帮助开发人员正确地处理系统。总之,无论何时何地,都建议从众多标准的授权模型中选择其一,而不是使用混搭的方案。
基于角色的访问控制(Role-based Access Control,RBAC)可以在认证和授权之间建立起一座桥梁。RBAC会根据分配给认证主体的角色(role)来提供访问授权,这样就可以通过统一的框架简化访问控制。比如一家银行,柜员、经理、贷款专员、保安、财务审计师和IT管理员等占了一半的角色。RBAC并不会为每个人单独定义访问权限,而会根据每个人的职责指定一个或者多个角色,从而为人们自动分配相关联的唯一权限。在更高级的RBAC模型中,一个人可以拥有多个角色,人们可以为不同的访问主动选择使用不同的角色。
授权机制远比传统操作系统提供的简单读/写访问控制要细致得多。人们可以设计更加强大的授权机制,以便在不损失重要功能的前提下对访问进行限制,提升安全性。这些更高级的授权模型包括基于属性的访问控制(Attribute-based Access Control,ABAC)、基于策略的访问控制(Policy-based Access Control,PBAC)等。
读者想想银行柜员的例子,就可以发现对授权进行精细化可以达到收紧策略的目的。
限速:一位柜员每小时可能最多处理20笔交易。如果超出了这个数量,这些交易就很可疑了。
每日时间:交易必须在工作时间内完成。
不服务自己:禁止柜员用他们的个人账户进行交易。
多个主体:超过10000美元的交易需要经理批准(以此消除一个坏人一次性就可以转走大量资金的风险)。
最后,对于某些数据来说,只读访问的访问级别依然过高,密码就属于这类数据。系统往往会通过比较明文密码的摘要的方式来校验密码,以免泄露明文密码。用户名和密码都会被发送给一台前端服务器,由前端服务器计算密码的摘要值,然后把摘要值发送给认证服务,同时迅速摧毁这个明文密码的一切痕迹。认证服务无法从证书数据库中读取明文密码,但是它可以读取摘要值,它会用这个值来与前端服务器提供的值进行比较。通过这种方式,认证服务就可以对证书进行校验了,但认证服务永远无法访问任何密码。因此,即使认证服务遭到入侵,也不会导致密码被泄露出去。除非界面设计能够提供这类安全方案,否则它们会失去这样一个减少数据泄露可能性的机会。本书会在4.2.2节进一步探讨这个话题。
为了让组织机构能够对系统的行为进行审计,系统必须基于所有对运维安全至关重要的信息生成一份可靠的日志。日志中包括认证和授权事件、系统启动与关闭、软件更新、管理访问等。审计日志也同样必须能够抗篡改。在理想情况下,最好是连管理员也无法插手修改这些日志,这样可以将其视为绝对可靠的记录信息。审计是黄金标准中的一个重要环节,因为网络中总是会发生这样或那样的事件,认证和授权策略的漏洞也总是难免的。审计可以自始至终为人们提供必要的信息。在工作中,当授权主体做出与人们信任相悖的行为时,审计信息可以帮助人们规避由此带来的风险。
如果处理得当,审计日志可以为日常检测、系统性能级别评测、错误和可疑行为检测带来巨大帮助,也可以在事件发生后用于判断攻击实际发生的时间和评估攻击带来的损失。切记,要想对一个数字系统进行彻底的保护,不仅要正确地实施策略,还要成为一位负责任的信息资产管家。审计可以确保可靠的、通过了认证的主体在自己的权限范围内采取的操作都是合理的。
在2018年5月,推特[1]公布了一个让人尴尬的bug:他们发现在不经意地修改了一段代码之后,原始登录密码就直接显示在他们的内部日志当中。虽然这件事导致密码遭到滥用的可能性不高,但是它会打击用户的信心,因此绝对不应该发生。日志应该记录操作的细节,但是不存储任何实际的隐私信息,这样才能把信息泄露的可能性降到最低,毕竟技术人员中有很多人日常就会查看这些日志。关于如何满足这样的需求,本书附录A中的设计文档示例详细列出了满足标准的日志记录工具。
[1] 已更名为“X”。
系统也必须有能力阻止人们通过篡改日志来掩饰那些恶意的操作,如果攻击者有能力修改日志,他们当然会把自己的操作痕迹清除得干干净净。对于那些特别敏感的高风险日志,应该由不同管理和操作团队负责的独立系统来管理其审计日志,以防内部肇事者掩盖自己的操作痕迹。虽然做不到尽善尽美,但是独立系统的存在本身就会对那些“耐人寻味”的业务产生强大的抑制作用,这就像一排高度有限的栅栏和一处位置显眼的视频监控摄像头就可以有效地防御入侵一样。
此外,任何尝试规避独立系统的行为都会显得相当可疑,每一项错误的操作都会给攻击者造成严重的影响。一旦被发现,他们也很难对自己的罪行进行抵赖。
不可抵赖性(non-repudiability)是审计日志的一项重要的属性。如果日志显示,一位有名有姓的管理员在某个时间点允许了一条命令,之后系统立刻就崩溃了,那么这次系统崩溃的责任就很难推卸给别人。反之,如果一家单位让多名管理员分享同一个账户(千万不要这样做),这家单位就无法判断谁执行了哪些操作,每个人也就都有了抵赖的口实。
最后,审计日志只有在人们监控日志内容时才能发挥作用,因此务必认真地分析那些异常事件,然后不断跟进,并且在必要情况下采取合理的行动。为了达到这样的目的,我们应该遵循Goldilocks原则,即日志记录的数量和规模应当适宜。一方面,日志数量过大就会让人们更容易忽视日志的内容,毕竟人们很难从大量嘈杂无序的日志中提取出有用的信息。另一方面,缺少细节的日志则有可能遗漏关键的信息。因此,找到一个合理的平衡点会是一项长期且艰巨的任务。
除了信息安全的基础(即CIA和黄金标准)之外,本书要介绍的另一基本话题与信息隐私这个领域有关。安全和隐私的边界很难清晰地界定,它们既紧密相关又大不相同。在本书中,我会把重点放在它们的共同点上,但也不会去统一这两个概念,而是把安全和隐私都包含在构建软件的流程这个话题中。
为了尊重人们的数字信息隐私,我们必须考虑其他人为因素,对机密性原则进行扩展。这些因素包括:
● 客户希望人们采用的信息收集与使用方式;
● 明确界定如何合理使用和披露信息的策略;
● 与收集和使用各类信息相关的法律法规;
● 与处理个人信息有关的政治、文化和心理等因素。
随着软件在人们生活中所扮演的角色愈发重要,人们和软件之间的联系也变得越来越紧密。软件开始涉足人们生活的敏感领域,这就催生了很多复杂的问题。过去发生的那些信息事故和滥用导致的事故让人们越来越清醒地认识到其中存在的风险。随着社会开始通过法律的方式来应对这些新的挑战,妥善处理个人信息的难度也水涨船高。
在软件安全领域,人们对从业者提出了下列要求:
● 要考虑到收集和共享数据会给客户和相关人员带来哪些影响;
● 要把可能的问题都标记下来,并且在必要的时候请教相关专家;
● 应该针对隐私信息的使用方式建立明确的策略和指导方针并严格遵守;
● 要把这些策略和指导方针转化成强制执行的软件代码;
● 要维护一份准确的记录,包括获取、使用、共享和删除数据的历史信息;
● 对数据访问授权和特殊访问行为进行审计,确保这些行为符合安全策略。
如果说对系统控制进行维护、为人们提供合法访问是一份既简单又枯燥的安全工作,那么涉及隐私信息的工作就很难找到一个合适的字眼来形容了。随着社会开始通过收集数据来深入探索人们的未来,我们仍然坚定捍卫人们维护自己隐私的愿望,并且制定保障人们隐私的规范。保护隐私殊非易事,明智的做法是让使用数据的行为尽可能透明。这就包括让所有人都能轻而易举地读懂我们的策略、收集尽可能少的数据,尤其是涉及个人身份的数据信息。
收集信息必须拥有明确的目标,保留信息必须保证信息确有价值。除非设计方案中就规划好了合规的用法,否则应该避免提前收集这样的信息。不要因为信息“将来有可能用到”就随随便便收集,这样做的风险很高,绝不是什么好做法。如果未来合法使用某些数据的必要性不高,最合理的做法就是安全地删除这些数据。针对那些特别敏感的数据,或者为了实现最大程度的隐私保护,我们应该采用直截了当的手段:只要数据泄露的风险超过了保留这些数据的益处,我们就应该删除这些数据。有时候,保留几年前的电子邮件确实比较方便,但是这种做法恐怕难以满足任何清晰的商业需求。反之,内部电子邮件如果通过某种方式泄露了出去,有人就可能需要为此承担责任。所以,最好的策略往往就是删除这样的邮件,而不是想着“说不定哪天用得到”,于是就一直保留着所有的数据。
处理信息隐私的完整流程超出了本书的范畴,但是对任何系统(只要这个系统的目的是收集人们的数据)的设计来说,隐私和安全性都是两个紧密相关的属性。毕竟,人是操作几乎所有数字系统的主体,虽然操作的方式不一而足。采用强大的隐私保护方式是建立强大安全性的必经之路,所以本章的目标就是唤起人们的意识,即把保护隐私融入软件的设计之中。
虽然保护隐私是一个复杂的问题,但是解决隐私问题的一大最佳实践却是人尽皆知的:人们必须明确表达出自己对隐私问题的关注。隐私策略和安全性不同,隐私策略能够在一定程度上权衡信息服务会在多大程度上利用客户的数据。“我们会反复使用你们的数据,还会把这些数据卖给别人”,这当然是隐私保护的一个极端,但“早晚有一天我们不会再给你们的数据提供保护”也称不上是合理的安全立场。当用户的期望和实际的隐私策略相脱节时,或者当用户违反了明确的安全策略时,隐私问题就会暴露出来。用户的期望和隐私策略脱节是因为安全人员没有向用户主动解释他们处理数据的方式。用户违反安全策略则是因为安全策略仍然不够清晰,或者负责人对其熟视无睹,又或者这些安全策略在一场安全事故中遭到了破坏。
提示:本书附录D包含CIA和黄金标准的汇总表格,以兹读者参考。