在编码阶段,开发人员的安全意识水平和安全编程能力将直接影响系统自身的安全性和健壮性。规范对开发人员的编码提出统一的安全要求,主要涉及安全编码原则、输入处理、输出处理、 数据访问、文件操作、安全特征、 WEB 开发要求等方面。
安全编码原则
最小功能性
软件应只包含那确实真正需要的功能:
- 只运行明确定义的功能。
- 系统调用只在确实需要的时候。
- 只在确实需要的时候访问数据。
最小授权原则
应采用适当的身份验证和权限分配模式,确保程序功能实现严格遵循“最小权限”和 “需要知道”安全原则。
谨慎使用多任务和多进程
- 应尽量使用单任务的程序。
- 如果软件需要使用多任务和多进程,应同步所有的进程和任务以避免冲突。
谨慎使用多任务和多进程
- 应尽量使用单任务的程序。
- 如果软件需要使用多任务和多进程,应同步所有的进程和任务以避免冲突。
界面输出最小化
用户界面应只提供必须的功能,确保用户不能通过用户界面直接访问数据或者直接访问被保护对象。
代码应尽量简单
- 宜使用结构化的编程语言,尽量避免使用递归和Go to声明语句。
- 宜使用简单的代码,清除不必要的功能,防止采用信息隐藏方式进行数据保护。
避免高风险的服务、协议
应尽量避免使用不加保护或已被证明存在安全漏洞的服务和通信协议传输文件。
最小授权
用户进程的授权宜采用最小授权法,对于软件的普通用户进程,禁止赋予该类进程特权用户权限。
设计错误、异常处理机制
宜建立防止系统死锁的机制,异常情况的处理和恢复机制:具体包括错误和异常检测、数据回滚、安全错误通知、错误和异常记录、断点保护等。
提供备份机制
为保证运行数据的完整性和可用性,宜设计有效的备份策略,根据业务和系统维护需要提供定期或不定期、自动或手动方式的备份机制。
其他编码原则
- 当一个进程对敏感对象(包含秘密信息或包含不可更改信息)使用完后,应立即清除对象的敏感信息,然后再删除对象。任何不需要使用的资源应及时释放。
- 当输入不可信任数据时,要在该数据的内容和格式上同时加以检查。对于整数,应该检查数据大小是否超出了能够表示的范围;对于输入的字符串,要确保长度没有溢出,保证每一个字符都是有效的。
- 禁止使用通配符的方式进行数据的插入操作。
- 应对面向用户的操作反馈缺省描述进行必要的封装,删除有关后台系统或其它敏感信息。
- 在需要进行文件操作时,应预先设定目前工作路径,使用相对路径表示文件的位置。
- 宜检查所有系统函数调用(System Calls)所传递变量的合法性。
- 应检查所有函数调用的返回代码,尤其是错误代码。
输入处理
验证各种来源的输入
程序默认情况下应对所有的输入信息进行验证,不能通过验证的数据应会被拒绝。宜对如下输入进行验证:
- HTTP请求消息的全部字段,包括GET数据、POST数据、COOKIE和Header数据等。
- 不可信来源的文件。
- 第三方接口数据。
- 从数据库中检索出的数据。
- 对来自命令行以及配置文件的输入。
- 网络服务。
- 注册表值。
- 系统性能参数。
- 临时文件。
对输入内容进行规范化处理后再进行验证
当输入数据包含文件名、路径名、URL 等数据时,应先对输入内容进行规范化处理后再进行验证,如文件路径、URL地址等数据,需要规范化为标准格式后再进行验证。
选择合适的数据验证方式
应根据情况综合采用多种输入验证的方法,包括:
- 检查数据是否符合期望的类型。
- 检查数据是否符合期望的长度。
- 检查数值数据是否符合期望的数值范围。比如检测整数输入的最大值与最小值。
- 检查数据是否包含特殊字符,如:<、>、 "、 '、%、 (、)、 &、+、\、'、"等。
- 应使用正则表达式进行白名单检查尽量避免使用黑名单法。
在服务端进行验证
仅在客户端进行验证是不安全的,应在客户端验证的同时,在服务器端也进行验证。
建立统一的输入验证接口
应建立统一的输入验证接口,为整个应用系统提供一致的验证方法。
控制写入日志的信息
如日志数据中包含输入数据,应对输入数据进行验证,禁止攻击者能够写任意的数据到日志中。
输出处理
限制返回给客户的信息
编码时应限制返回给客户与业务处理无关的信息,禁止把重点保护数据返回给不信任的用户,避免信息外泄。
建立错误信息保护机制
- 禁止将详细错误信息直接反馈到客户端,详细错误信息中包含系统信息、文件和目录的绝对径信息。
- 应对错误信息进行规整和清理后再返回到客户端。
- 宜只向客户端返回错误码,详细错误信息可记录在后台服务器。
数据库访问
合理分配数据库访问权限
- 应按照“最小化原则”为应用程序分配数据库访问权限。
- 避免为应用程序分配过大或不必要的数据库权限。
- 禁止将数据库DBA权限分配给应用程序。
合理存放数据库连接帐号和密码信息
- 禁止在应用程序代码和配置文件明文存放数据库连接帐号和密码信息。
- 数据库连接账户和密码信息可加密后再保存在配置文件中。
对数据库操作的返回数据进行验证
- 应检查SQL请求的返回记录数。
- 数据库的数据做为输入数据时,宜进行验证。
分次提取数据
对于数据库查询操作,如果查询返回的结果较多时,宜设计成分次提取,应避免一次提取过多数据。
通过row(行)级别的访问控制来使用数据库
- 不能依赖应用程序访问控制能够保护数据库的数据,应限制每个请求使用户只能访问他们自己的数据。
- 应限制SQL请求只向当前认证的用户返回结果。
确保数据库资源被释放
- 应保证数据库访问在不需要使用的时候被释放,例如连接、游标等。
- 由于资源泄露会导致系统出错而且很难捕捉到,宜建立一个资源管理模块并且完全按照规则进行操作。
- 宜禁止依赖Java 和.NET 的垃圾回收器来回收资源。
4 在Java中,应该在finally 块中释放资源来保证资源在任何环境下都会被释放。- 在.NET中,应需要使用关键字 using 引入IDisposable 接口来进行资源释放,而不是直接关闭管理资源的对象。
文件操作
对上传文件进行限制
允许用户上传文件时,应对上传文件做如下限制:
- 上传文件类型限制应遵循最小化原则,通过文件检查仅允许上传必须的文件类型。
- 上传文件大小限制,限制文件的容量大小范围。
- 文件保存路径限制,过滤文件名或路径名中的特殊字符(../或..\等)避免文件保存在非预期目录中。
- 应关闭文件上传目录的执行权限,在 UNIX/LINUX 系统环境里,建议把上传目录挂载成独立的逻辑盘或设置为jail环境。
把文件名以及文件内容作为不可信的输入对待
对来自文件系统的所有值都应进行合适的输入验证,确保从文件系统中读取的数据符合期望。
安全的使用文件名
- 应避免在传递的参数中直接使用真实的文件名,尽量使用索引值来映射实际的文件路径。如确实需要在参数中出现文件名,则尽量对文件名进行白名单检查。
- 应禁止在参数中出现../、..\等特殊字串及其变形。
使用文件系统访问控制
- 文件只应被指定的用户访问,而且该用户应该被限制为最小权限,例如对文件的读写权限都应被限制到最低。
- 当应用程序使用被其控制的已存在文件时,应首先验证文件的权限和属组,防止文件存在被篡改的许可权限。
注意文件访问竞争条件
多进程或线程对同一文件进行访问时,应采取适当的加锁策略保证文件数据在访问过程中的一致性。 安全使用临时文件 在程序初始化时宜以最严格的权限策略建立一个安全临时文件夹,该文件夹只有该程序具备读写权限,其他用户无法访问。将所有临时文件都存放在该文件夹中,当临时文件使用完毕后应及时清除临时文件。
禁止下载应用系统自身的配置和数据
- 应将应用系统自身的配置和数据文件保存在WEB空间之外,防止攻击者采用URL探测等手段尝试下载应用系统的配置和数据文件。
- 应禁止在脚本的动态包含功能中直接使用用户提交的数据和文件。
安全特征
关注应用的对象重用
- 对于底层系统的对象可重用性来说,宜提供应用软件对敏感数据使用后立即覆盖的能力,这些敏感数据包括口令、安全密钥、会话密钥或者其它的高度敏感的数据。
- 应提高代码的重复利用率,可创建公共函数库(对象库)供整个软件程序调用。
用户访问控制信息的机密性
应禁止在程序代码中和配置文件中直接明文写入用户名和口令等用户访问控制信息。这些控制信息应加密后存放在配置文件中。
不要在客户端存放敏感数据
由于客户端是不可信任的,应禁止客户端存放敏感数据。特别是在使用 Cookie 时不能把客户重要信息储存在客户端。
避免内存溢出
为防止内存溢出,应注意以下事项:
- 在对缓存区填充数据时应进行边界检查,应判断是否超出分配的空间。
- 应保证系统资源及时释放和服务连接的及时关闭, 应显式关闭、释放使用过的资源(如连接对象、文件句柄等),不要依赖垃圾收集器。
- 软件程序应检查每次内存分配是否失败,并进行处理。
- 应及时释放内存资源,防止内存泄漏,但要避免重复内存释放。
可配置数据保护
- 限制非应用软件用户访问可配置数据。
- 可采用系统访问控制为配置数据文件设置严格的访问权限,仅应用程序可以访问。
禁止在源代码中写入口令
- 应将加密后的口令存储在配置文件,数据库或者其它外部数据源中。
- 应禁止将口令存储在代码中,把口令存储在代码中会导致任何人都可以获得到存储在代码中的口令。
- 应禁止将口令写在发布的软件中,如果需要修改口令,则软件必需通过安装程序补丁等方式替换程序文件,会造成口令难于修改。
随机数
如果应用程序需要随机数,需要找出一种合适的随机数生成方法,要求其生成代价较小并且应满足安全需求。
- 在Java中,宜使用SecureRandom类生成随机数,不要使用Random类。通常不需要设置种子数给SecureRandom,Java 会自动获取一个信息熵比较平均的值。
- 在C和C++中,应避免使用标准的随机数函数,如rand(), srand(), srand48(),drand48(), lrand48(), random()和srandom()。
- 如果操作系统或者应用程序使用的加密库有一个真实的随机源,宜使用该随机源。
异常管理
- 捕捉并处理异常。
- 应使用结构化异常处理机制,捕捉并处理异常现象。
- 宜避免将应用程序置于不协调的状态,该状态可能会导致信息泄漏或拒绝服务攻击。
- 记录详细的错误信息。
- 宜在错误日志中记录详细的错误消息。
- 宜向服务或应用程序的客户发送最少量的信息,确保没有密码或其他敏感数据。如一般性错误消息和自定义错误日志 ID,随后可以将这些信息映射到事件日志中的详细消息。
- 不要向客户端泄漏信息。
- 发生故障时,严禁向客户端泄漏信息,禁止暴露的内容包括函数名以及调试内部版本时出问题的详细信息。
- 宜向客户端返回一般性错误消息。可以将应用程序设置为不向远程用户显示详细错误信息,也可以选择将错误重定向到应用程序页。
- 宜捕捉所有未处理异常并将它们发送到一般错误页的页级别或应用程序级别上,创建全局错误处理程序。
WEB 开发要求
使用POST不能使用GET
使用 Get 方法传递的参数将包含在 URL 里,这些信息会被记录在日志文件中或通过 HTTP 头的Referrer 发送到其它站点上,被存储在浏览器的历史记录中。当参数中包含重要信息时,这些信息也会被记录,这将增大泄密的风险。
创建默认的错误页面
应为HTTP错误创建一个默认的错误页面,丢弃所有的异常,防止攻击者从应用程序的默认出错页面中得到系统信息。
宜使用通用错误消息
应构造错误提示信息来防止诸如用户 id 、网络、应用程序以及服务器环境的细节等重要敏感信息的泄漏。包括:
- 不区分错误的用户名和错误的密码;
- 在返回的报告中不能包含主机信息、网络 DNS 信息、软件版本信息、错误代码或者其它错误的详细信息;
- 不允许把错误的细节放在错误页面的注释里。
应使用强的会话ID
- 会话id 长度应不能低于64位,建议使用128位长度的会话id。
- 要求在确定 session 的 id 长度以及生成 id 的随机种子之前,不要相信 web 程序的容器。因为会话id太短很容易被暴力破解。如果攻击者猜测到授权用户的会话id就可以接管用户的会话。
在每次认证后创建一个新的会话
在用户认证成功后应先将之前的用户会话销毁,然后重新为用户生成新的会话,保证认证成功前后,用户会话ID不一样。
保护Cookie
- 要求在建立Session时用户以SSL方式连接,并且将该会话的安全标识设置为cookie 只能通过安全连接进行传输。
- 不能只依赖HttpOnly cookie带来的任何安全保障,因为其只限于在IE下使用,攻击者也可以很方便的窃取到它。
允许用户终止其会话
- 宜允许用户通过注销来保护自己的帐号。
- 应在会话终止时立即销毁所有会话数据,否则该数据会被攻击者利用。
其它编码要求
- 应把数据与程序放置在不同的目录中。
- 变量在使用前应进行初始化。
- 应留意字节大小差异、精度、符号数/非符号数的区别、舍位截断问题、不同类型数据的转化、非数值运算、极值处理等问题,避免意外的变量使用和运算导致流程的改变。
- 调用函数时应对返回值进行检查。
- 应使用可靠的随机数生成算法,并设置合适的随机数种子。
- 应使用成熟的、高强度的加密算法,禁止使用自创加密算法。
- 宜避免使用HTTP GET方法。
- 宜禁止使用与业务功能无关的第三方代码或库文件,应检查已有第三方代码或库文件的版本和安全性,避免引入安全漏洞。
- 应及时释放内存资源,防止内存泄漏,但要避免重复内存释放。
- 应显式关闭、释放使用过的资源(如连接对象、文件句柄等),不要依赖垃圾收集器。