软件系统的安全开发探讨
王晓峰
摘要:众所周知,网络安全正变的越来越重要,同时随着信息技术的深入发展,网络安全问题也日趋严峻,大家都在不断加强信息系统的网络安全防护,加强网络安全信息统筹机制、手段、平台建设,加强网络安全事件应急能力,但是网络攻击复杂度高、隐蔽性强,如果仅仅靠网络安全系统防护,很多威胁难以被准确识别和防御。信息系统安全更多是软件的安全,最难修补的也是软件安全漏洞,只有在软件开发设计阶段做好安全防护设计,才能更好的进行网络安全防护。
关键词 架构 框架 漏洞 容器
概述
在实际工作中我们发现,网络安全的治理,最难治理的就是软件方面的安全,软件安全漏洞的修复往往需要耗费较长的周期和较高的成本,涉及到大量的代码修改,甚至涉及到软件重构等一系列的问题。
过去我们通常是基于业务需求的思路,委托第三方开发,而对第三方的是如何进行设计与开发的,我们并不关心,只关心是否实现了我们的需求。而第三方软件设计及开发通常采用基于到达特定应用目的设计,都是按照正常使用的逻辑思维的。而对安全方面没有过多考虑,这就造成现在大量软件系统都存在安全隐患。实际上如果软件在开发设计阶段就考虑安全问题,进行必要的防护,软件自身的安全漏洞会大幅度的降低,即便出现漏洞也容易修补。
开发设计阶段就考虑安全问题,发现和纠正安全问题也容易,要比在软件开发完已经开始用了以后再发现纠正安全问题的成本低很多,如果因为安全问题而造成的损失代价就更大。
目前主流开发语言有Golang、java、Scala、KotLin、C/C++、C#、JS、Python等等、框架有Gin、Beego、SpringBoot、SpringMVC、Django等等,本文从通用软件开发角度,高度抽象、分层次的对软件开发安全进行论述。
软件开发设计方面的安全
设计思想安全
软件系统设计首先要本着先进性、安全性的原则进行设计,采用高内聚,低耦合的设计思想,这样才能保证软件的模块之间相互依赖度最低,便于软件的维护和功能扩充。应优先选择代表技术发展方向、维护力度大,社区人气旺的技术,这样才能避免出现问题找不到解决方案;技术选型要避免选择已经淘汰的、或缺乏维护的、活跃度低的技术(比如ejb、struts、ibatis、angular1.0等),这样才能避免因技术淘汰,不再有人进行维护,出现安全漏洞也没有相应的补丁的情景发生;也要尽量避免采用服务于小众化的技术,小众化的技术往往成熟度不高,风险较高;要避免使用已经被废弃的函数、方法、类,因为被废弃的最大原因就是安全和效率。尽量采用前后端分离的开发技术,以便提高开发效率。
软件架构安全
架构设计除了要考虑可扩展性、可靠性、强壮性、灵活性、性能等,还要重点考虑安全性。尽量以业务为核心进行分层(组件/模块化)设计,尽量划分各层(组件/模块)的业务功能独立,数据库划分尽量保持相对独立性,这样层次分明,界限清晰,软件就不容易出现漏洞;登陆、权限验证尽量使用SSO单点登陆系统,便于权限管理的清晰和一致性,避免出现管理混乱;系统架构优先考虑成熟的微服务、Servless,微服务或Servless每个服务功能单一,耦合度底,便于安全管理。
选择开发模式
目前业内服务器端通常都采用MVC模式进行结构化开发,客户端采用MVVM或MVP模式进行开发,这样层次分明,各层负责各层的工作,不交叉,界面清晰。这样有利于程序员专注开发,从而提高代码的质量,避免出现漏洞。
对于访问量高得业务,宜采用高并发集群技术、数据库读写分离和缓存(比如redis)的技术,这样可避免单点故障造成整个服务崩溃,确保业务系统的安全运行。
软件开发阶段的安全考虑
做好权限设计
对用户的权限分配应按照最小权限原则进行设计,限制用户只能访问其所必须需的功能、数据、文件或系统信息。避免出现用户获得其不该获取的信息,同时限制只有授权用户才能访问受保护的URL、功能、对象引用、服务、数据。防止非授权访问。
做好数据安全
敏感数据的安全
开发时,要注意保护所有存放在服务器上缓存或临时拷贝的敏感数据,避免被非法访问,临时文件不需要时,要及时删除。
对于敏感信息(包含账号密码、用户身份信息、用户行为轨迹、通信信息、消费信息等),必须采用加密存储,加密传输,加密算法必须采用经实践验证可靠的算法(比如AES加密算法)。
HTTP或HTTPS的访问安全
应禁止在HTTP GET请求参数里传递敏感信息,session只能在HTTP head里传送。Web服务推荐使用HTTPS协议,使用HTTPS要保证证书有效,必须保证域名在业务有效期内不会过期,避免出现因认证域名过期被黑客利用。
要对用户提交的文件,进行文件类型检查,检查是否是期待的类型,要通过验证文件包头信息来确定文件类型,避免用户提交恶意文件到服务器。
系统间数据交换的安全
在不同系统间的大量数据传递,尽量利用统一的异步数据交换系统进行数据传递(比如利用统一的具备安全机制的Kafka集群或RabbitMq集群进行消息传递),以便对数据交换的统一管理及安全管理。
独立中间件系统的安全
需要采用独立中间件系统(比如,Kafka、各种Mq等消息中间件、Redis等缓存、Elasticsearch搜索系统、hadoop大数据处理系统等等)时,应当对消息进行权限验证,防止出现未授权访问。
HASH摘要的安全
由于md5使用的泛滥,社会上已经出现基于MD5的字典库,为了防止黑客利用md5字典进行暴力破解,因此应当使用SM3、SHA1、SHA256、SHA384、SHA512算法,尽量不要再荐使用MD2 、MD4、MD5,如果要使用MD5,应当采用加盐算法。
。
资源安全
开发时,要考虑系统应具备对静态资源防篡改的的能力,确保应用程序和静态资源都是只读的。定时扫描静态资源的hash状态是否变化,一旦发现被篡改,立即自动恢复静态资源原始数据,并产生告警,避免静态资源被黑客修改才生不良影响。
对于语序文件上传的服务,应当对文件上传做限制,禁止上传任意可能被服务器解析执行的文件,防止黑客用来发起恶意攻击。
代码安全
安全编码规范
软件代码应统一采用UTF-8编码格式,避免出现乱码问题,编译打包后的最终代码、和执行文件都应计算出hash值,作为程序的重要数据进行保存,制定防篡改机制,定时检查是否发生篡改事件。
外部工具库安全
尽量不要引用太大的外部工具库,如果必须使用,能删减则删减。冗余的外部库将导致服务器编译效率降低,同时可能会隐藏不安全的代码,引用的外部工具库要从官方的网站下载,避免从其他渠道下载的被插入不安全代码。
身份验证安全
系统上身份验证原则上应采用多因子验证,密码长度应不低于8位,采用大小写字母、数字或特殊字符组合。同时身份信息的传输不要采用GET方式传输。传输时应加密,避免采用不加盐的md5算法。
在服务器端对用户登陆身份进行验证,要限制用户连续尝试登陆和超过登录次数进行锁定,避免黑客利用暴力破解工具不断尝试登陆。
在执行关键操作前(比如付款),必须再次在服务端验证用户身份,确保此用户身份信息与session中一致,避免黑客利用中间人攻击手段对信息进行篡改。
如果使用第三方身份验证代码,必须确保第三方验证代码的可靠(是否是知名企业或团队放出的,评价、口碑是否较好),必须从第三方官网或第三方在github上的专有地址取得代码。
会话安全
在现实工作中,常出现因缺乏session管理,造成越权攻击、中间人攻击的漏洞,因此应当在安全的系统上建立session管理,对于非必须长时间长连接的请求,给session设置合适的超期时间,尽量使一个session空闲时间不要过长。
要限制同一用户的session会话数(限制同一用户ID的并发登陆),最好同一用户只允许一个会话存在(或一种终端一个session,比如手机和电脑同时登陆,存在2个session),一旦同一用户在不同地点登陆,就必须对原有会话进行关闭,并在关闭原有会话时对原有会话进行提示,以避免黑客登陆用户还不知道。另外最好开发时不使用cookie保存敏感信息。
客户端验证的安全
基于客户端的输入验证、隐藏字段、登录次数限制等客户端技术是非常不安全的,攻击者可以使用工具,比如:客户端的Web代理(例如,OWASP WebScarab,Burp)或网络数据包捕获工具(例如,Wireshark),进行应用程序流量分析,提交定制的请求,并绕过所有的接口。
因此程序设计时要本着客户端不可信任原则,必须在服务器端进行相关验证工作。所有的输入都必须在服务器端进行验证,确保输入的数据是期望的类型,对于客户端发送过来的操作,标志用户身份的数据必须从服务器端取得,不得直接使用客户端传过来的标志信息(比如用户ID是12345的用户,发送查询自身消费信息的查询,如果在发送表单数据时,利用抓包工具,把ID信息修改成56789,如果服务器端采用这个ID查询,就会把ID是56789的消费信息发送过来,从而造成用户数据泄露的安全事件)。
开发时要考虑禁止向HTML页面输出未经安全过滤或未正确转义的用户数据,Form表单、AJAX提交的数据必须进行CSRF安全过滤,避免黑客利用JS语句进行跨站脚本攻击。
错误处理和日志
如阿年系统运行出现错误时,不要在客户端显示敏感信息,如系统的详细信息,会话标识,账号信息等敏感信息,避免黑客利用这些信息。最好只告诉客户端出错了,不显示任何多余信息;比如在用户登录失败时,只显示“用户名或密码错误”,不做过多提示。
另外系统需要记录所有的验证失败的的信息(包含客户端IP,访问时间,访问资源),以便维护人员分析是否存在黑盒行为使用。
微服务的安全
目前系统开发,流行使用微服务架构,微服务具有服务发现、负载均衡、容错、熔断等特性,配合kubernetes容器云技术,可以实现弹性伸缩等特性,因此备受欢迎。在使用微服务架构时,尽量使每个微服务功能单一,数据库独立,这样耦合性最低,使用微服务系统必须具备熔断措施,以便避免故障出现雪崩效应。实现微服务的服务治理、负载均衡、熔断器、网关、安全防护、总线(消息系统)功能组件厂家比较多,使用时要采用相容的技术(比如出自同一家的、或经过实践验证的),用网关进行内外服务隔离,外部访问要做权限认证,避免出现非授权访问。
SQL语句操作安全
近些年,不断出现的SQL注入漏洞,都是因开发中对SQL语句设计存在问题或不当使用ORM框架语法造成的,因此操作SQL语句,必须按照查询语句与查询参数分离的方式进行,如果使用ORM框架,要确保映射出的SQL语句满足参数分离的强化SQL语法,严禁使用拼接完整SQL的方法和ORM语句,比如mybatis里面映射参数,推荐使用#{},不要使用不安全的${}方式。
必要的代码重构
随着软件技术的不断进步,以前用的框架或架构很可能变得落伍,性能降低,再加上软件运行阶段不断的修改打补丁,使得原有软件系统变得臃肿、结构不再清晰,安全隐患不断加大,模块或层次之间的耦合度增加,因此代码重构是项目运营到一定程度后必然要做的事情。代码重构,可以改善原有代码设计,增强既有系统的可扩充、可维护性、安全性。如果等软件用到补丁套补丁,已经很难理顺逻辑关系时,再重构,代价就会相当的大。
软件部署中的Docker容器安全
容器技术是这些年备受瞩目的一项技术,Docker是容器技术中中最引人瞩目的实现,几乎已成为应用容器事实标准,目前已开始大量应用到生产系统中了,Docker自身技术的完善和相关技术生态的建立,使之成为现代云计算的基石。
微服务+docker+kubernetes是目前最完美的技术组合,Docker把每个微服务都封装到一个docker容器中,实现了一定的隔离,避免微服务之间的影响,利用kubernetes强大编排能力,实现动态伸缩、服务器负载的均衡,最大限度的发挥服务器的性能。
Docker诞生的目的就是实现隔离,便于软件系统的持续集成和快速部署,尽量减少中间环节,当然这也为其安全控制带来难度。Docker的安全本质是保证docker镜像在创建、存储、传输、运行全生命周期的安全。
由于docker镜像实在开发阶段生成的,最终部署到生产环境,如果开发环境存在安全隐患,那么生成的镜像也会把安全隐患打包进去,因此docker的安全要考虑开发阶段生成镜像的安全和生产极端的安全。
Docker公司与美国互联网安全中心(CIS)合作,制定了,包括了主机安全配置、docker守护进程配置、docker守护程序配置文件、容器镜像和构建、容器运行安全、docker安全操作六大项,99个控制点。只需严格按照《docker安全的最佳实践》进行操作,Docker容器的安全风险就可以避免。
结束语
软件开发安全涉及面比较广,每一项技术都涉及大量的安全知识,本文仅从宏观上进行一些探讨,就如何避免目前常见的软件漏洞进行了肤浅的论述。