ARTICLE · 1094830
安全编码:从代码安全走向全生命周期的软件安全

软件安全并不是在程序开发完成以后,通过一次漏洞扫描、一次渗透测试或者一次等级测评“检查”出来的。真正的安全应该从软件立项、需求分析和架构设计开始,贯穿编码、测试、发布、运行维护以及漏洞处置的全过程。安全编码(Secure Coding)因此不能简单理解为“程序员写代码时注意安全”,它实际上是将安全要求融入软件开发生命周期的一种工程实践,使安全成为软件功能、性能、可靠性和可维护性之外的一项基本质量要求。IBM将安全编码作为安全软件开发生命周期的重要组成部分,强调安全不应等到测试阶段才介入,而应该进入软件开发的各个阶段;Security Magazine所总结的安全软件开发实践同样强调,应当将安全要求、威胁评估、安全架构、开发、验证和运行维护结合起来,而不是把安全变成软件上线前的一次检查。
因此,安全编码首先不是从“写代码”开始,而应该从“提出安全要求”开始。在需求阶段,需要识别业务、数据、用户、接口以及外部依赖可能面临的安全风险,并形成明确的安全需求;进入架构设计阶段,则需要通过风险分析、威胁建模、安全设计模式等方法识别潜在的架构缺陷和业务逻辑风险。例如,一个系统需要实现患者信息查询功能,开发人员不能只考虑“查询功能能不能正常运行”,还需要考虑谁可以查询、能够查询哪些患者、可以查看哪些字段、是否允许批量查询、接口是否可能被越权调用、敏感数据是否需要脱敏、查询行为是否需要记录以及异常访问是否需要告警。如果这些问题直到编码完成以后才考虑,很多安全缺陷实际上已经固化在系统架构和业务逻辑之中。因此,真正的安全编码,第一步是把安全要求写进需求和设计。
在此基础上,组织还需要建立统一的安全编码规范,而不能主要依赖某个程序员个人的安全经验。不同开发人员掌握的编程语言、开发框架和编码习惯不同,如果没有统一的安全编码要求,同一种漏洞就可能在不同项目中反复出现。安全编码规范应当结合组织自身的技术栈和业务特点,对输入验证、输出编码、数据库访问、身份认证、访问控制、密码学、日志记录、错误处理、第三方组件以及安全测试等作出明确要求。例如,输入数据必须经过服务端验证,不能默认信任客户端数据;数据库操作应优先采用参数化查询;密码、密钥和令牌不能硬编码在源代码中;权限检查必须在服务端执行;关键安全事件应当记录;错误信息不能向外泄露内部系统信息;第三方组件应当纳入安全管理;安全代码审查应当成为软件发布前的重要环节。安全编码规范的意义,就是把“开发人员应该注意安全”转化为能够执行、能够检查、能够验证的具体要求。
安全编码中一个最基本的原则,就是不要默认信任任何输入。用户输入、URL参数、HTTP请求头、API参数、上传文件、第三方接口数据、消息队列数据,甚至内部系统传递过来的数据,都不应该仅仅因为来源于“内部网络”就自动被视为可信。输入验证需要根据具体业务对数据类型、格式、长度、范围、大小以及允许的字符和取值进行验证。但是,仅仅验证格式仍然不够,还需要根据数据最终使用的场景采取适当的处理方式。例如,Web应用需要针对不同输出上下文进行适当编码,数据库访问应采用参数化查询或者预编译语句,使用户输入的数据与SQL语句本身分离,从而降低SQL注入风险。安全编码不是简单增加一个判断条件,而是建立从输入验证、数据处理到安全输出的完整控制链。
密码学同样是安全编码中非常容易出现问题的领域。开发人员不应该自行设计加密算法,也不应该为了方便自行实现密码学核心功能,而应该使用经过验证、持续维护的密码学算法和安全库。尤其需要避免把密码、密钥和访问令牌直接写入源代码、配置文件或者版本控制系统。即使数据库中的敏感数据经过加密,如果密钥同时保存在程序代码中,一旦代码泄露,数据保护机制就可能失去意义。因此,密钥管理不能简单理解为“保存一个密钥”,而应该覆盖密钥的生成、存储、分发、使用、轮换、吊销和销毁等生命周期。对于传输中的敏感信息,也需要根据具体安全要求采取适当的保护措施,避免敏感信息在网络传输过程中被窃取或者篡改。
身份认证和访问授权也不能混为一谈。身份认证解决的是“你是谁”,而授权解决的是“你能够做什么”。一个用户成功登录系统,并不意味着他有权访问系统中的所有数据和功能。例如,普通员工即使成功登录业务系统,也不能因为知道某个URL中的患者ID,就直接访问其他患者的数据。因此,权限控制不能仅仅发生在用户登录的时候,而应当在访问受保护资源的过程中持续进行验证,并针对具体的用户、资源和操作进行授权判断。实际设计中,应贯彻最小权限和默认拒绝原则,只授予用户完成工作所必需的权限,并避免因为前端隐藏按钮、修改URL或者直接调用API而绕过服务端授权控制。
日志也属于安全编码的重要组成部分,但日志并不是“记录得越多越安全”。真正有价值的安全日志,应当能够说明谁、在什么时间、通过什么方式、针对什么对象执行了什么操作以及操作结果是什么。例如登录成功与失败、权限验证失败、关键数据访问、管理员操作、权限变更、配置变更以及重要安全异常等,都可能成为后续安全分析的重要依据。同时还必须防止日志本身成为信息泄露渠道,密码、密钥、访问令牌以及其他敏感信息不能因为方便调试而直接写入日志。日志还需要考虑访问控制、防篡改、安全传输和安全存储等问题。这样,日志才能真正成为安全运营的一部分,而不是简单的程序调试记录。
错误处理同样容易被忽视。很多程序正常运行时看起来没有问题,但发生异常以后,却直接将数据库连接信息、服务器路径、SQL语句、内部组件名称等技术细节返回给用户。这些信息对于开发人员可能有帮助,但对于攻击者同样具有价值。因此,安全错误处理应该形成“对外有限、对内详细”的原则,即用户只获得完成当前操作所需要的必要信息,而服务端则记录足够详细的诊断信息供开发和运维人员分析。这样既能够支持问题定位,又能够减少系统内部结构、数据库信息和敏感配置等内容向外暴露的风险。
代码完成以后,并不意味着安全开发已经完成。软件还需要经过自动化工具和人工验证。静态应用安全测试(SAST)可以从源代码层面发现潜在安全问题,动态应用安全测试(DAST)则可以从运行环境角度验证应用是否存在可利用的安全缺陷,交互式应用安全测试(IAST)能够结合运行时行为和代码上下文进行分析,软件成分分析(SCA)则重点关注第三方开源组件及其依赖关系。这些技术并不是相互替代,而是从不同角度发现问题。与此同时,人工代码审查仍然不可替代,因为自动化工具很难完全理解复杂的业务逻辑。例如,一个程序在语法和技术层面都没有明显漏洞,但如果业务授权逻辑存在缺陷,攻击者仍然可能通过正常功能实现越权操作。因此,自动化安全检测与人工安全代码审查应当形成互补。
现代软件越来越依赖开源框架、第三方库、SDK、容器镜像、插件和供应商开发组件,因此软件安全也不能只关注“我们自己写的代码”。一个组织即使没有在自己的代码中发现明显漏洞,所使用的第三方组件仍然可能存在已知漏洞或者供应链风险。因此,需要建立第三方软件和开源组件的识别、清单化、安全评估、版本管理、漏洞监测、更新修复和使用验证机制。特别是在CI/CD环境中,还需要关注代码来源、构建环境、依赖组件以及最终构建产物的完整性。由此可见,安全编码已经不再只是“开发人员与源代码之间的事情”,而是逐渐扩展到软件供应链、开发环境和构建体系。
软件上线以后,安全工作并没有结束,反而真正进入持续运营阶段。安全开发不能停留在“代码已经扫描过”或者“上线前已经测试过”。软件运行过程中仍然会发现新的漏洞,新的攻击技术也会不断出现,因此需要建立持续的漏洞和安全问题管理机制,对发现的问题进行分析、定级、分派、修复、验证和关闭。更重要的是,对于反复出现的同类漏洞,还应当开展根因分析。如果不同项目中持续出现SQL注入、越权或者敏感信息泄露问题,那么问题可能并不只是某个程序员的一行代码写错了,而可能反映出组织的安全编码规范、开发人员培训、代码审查、测试体系或者安全需求管理存在系统性缺陷。因此,漏洞管理的终点不应只是“把这个漏洞关闭”,而应该进一步降低同类问题再次发生的概率。
随着人工智能逐渐进入软件开发过程,安全编码也面临新的变化。AI可以帮助开发人员生成代码、解释代码、发现漏洞和提出修复建议,但AI生成代码并不天然安全。AI提高了代码生产效率,同时也可能提高不安全代码产生的速度,因此不能因为代码由AI生成,就降低代码审查和安全测试要求。未来的软件开发可能逐渐形成“人提出需求、AI辅助设计、AI生成代码、自动安全分析、人工安全审查、自动化测试、发布验证、运行监测、持续修复”的新模式。安全开发的重点也将从单纯要求“开发人员具备安全编码能力”,进一步发展为建立能够约束、验证和监督AI辅助开发全过程的安全工程体系。
从这个角度看,安全编码实际上可以形成一条完整的安全链条。从安全治理进入安全需求,从安全需求进入威胁建模和安全架构,再进入安全编码、自动化分析、安全代码审查和安全测试,经过安全发布以后进入持续运行监测、漏洞处置和根因分析,最终将运行过程中发现的问题重新反馈到需求、设计、开发和管理体系之中,形成持续改进的闭环。
因此,安全编码不能简单理解为“程序员写代码时多注意几个安全问题”,也不能把安全等同于上线前的一次漏洞扫描。Security Magazine所强调的安全软件开发实践,更侧重于建立贯穿开发和运营的整体安全体系;IBM则进一步将安全要求落实到安全设计、输入验证、输出编码、密码学、身份认证、访问控制、日志、错误处理、安全测试、代码审查以及第三方组件管理等具体实践中。二者结合起来,可以看到一个更加完整的逻辑:安全不是软件开发完成以后附加的一项检查工作,而应该成为软件从需求产生、架构设计、代码编写、测试验证到上线运行全过程的一项基本能力。
真正成熟的软件安全,也不是由安全部门替开发人员写代码,更不是通过一份扫描报告证明“软件安全了”,而是让业务、架构、开发、测试、安全、运维以及供应链相关人员共同承担相应责任,通过制度、规范、工具、流程和运营机制,把安全真正融入软件生命周期。只有这样,安全编码才能从“避免几个常见漏洞”的技术要求,真正发展成为组织持续构建和维护安全软件的工程能力。
往期回顾
等保、关保、数保、个保,网络安全与数据治理“四位一体”的体系化制度框架
网络安全等保系列
等级保护建设参考系列
关基保护系列
数据安全系列
结构化数据(Structured Data)与非结构化数据(Unstructured Data)
医疗机构数据安全和个人信息保护自查简表(基于最新医疗机构管理办法)
个人信息保护系列