■ 上海 陈峻
编者按: 笔者分享了如何在遵守OWASP Top 10的前提下,如何对单位PHP网站进行全方位安全开发实践工作,笔者在几个关键点方面进行了阐述。
经历了大半年的研发与测试,笔者单位网站的新版本在上个月正式上线运营。该版本采用Apache2.4+PHP 7.2+MySQL 5.7的应用环境。本月,笔者和网站开发部门负责PHP部分的工程师们进行了有关DevSecOps方面的交流。他们分享了如何在遵守OWASP Top 10的前提下进行全方位安全开发实践工作。
总的说来,他们是在分析了现有网站架构特点、并审查了过往事故记录的基础上,逐步摸清了整体设计上存在的漏洞,以及当前各类代码程序之间的依赖关系。因此在新的网站构建过程中,他们从代码层面和架构层面两个维度进行了设计与开发。下面便是他们总结出的针对PHP安全加固与防护的各个关键点。
1.过滤并验证数据
过去,他们对网站的传入数据以及操作符类型,并未进行严格的检查。因此,内部审计人员曾建议通过使用strict_types的类型声明,以避免出现:浮点型参数被强制转换为整形、再被判断为布尔型的潜在攻击逻辑。
如今,考虑到新版网站的内容会丰富许多,而且经常需要与访客及用户进行信息互动,因此他们需要将内容进行转义和预编译,并生成HTML、CSS和 JavaScript等输出形式。于是,在页面的源代码中,他们增设了各种HTTP的安全头部,并且在服务器应用上添加了内容安全策略(CSP),以类似于“白名单”的形式限制访客及用户浏览器的各种潜在加载和执行行为,进而避免网站受到CSRF和XSS攻击。
同时,对于网站上的数据,无论它们是来自应用内部的某个配置文件,还是源于服务器的环境变量,亦或是经由GET或POST方法传递而来,都绝不盲目地信任与接收,而是采取相应的程序过滤(如 filter_var)与工具验证。在具体实现的过程中,借鉴了Symfony框架来分离各种事务与逻辑。特别针对PHP程序的开发部分,更是用到了Laravel Web开发框架。
2.使用参数化查询
过去,访客及用户在网站上触发定制化查询时,前端页面会自动生成相应查询语句,然后经由业务层传递给后端的数据库,后续的分析、编译、优化和执行等工作则全部由数据库来完成。可见,该过程不但耗时较长,而且一旦出现略有不同的查询参数,数据库则需要频繁地执行重复性解析,以满足查询需求。网站的整体响应速度和系统性能也会在面对并发量大的循环查询时受到一定影响。
同时,从安全的角度来看,由各种外部数据被动态且临时性拼装出的SQL字符串,对于后端数据的查询、乃至插入与更新都是十分危险的,网络攻击者很容易构成SQL注入攻击。
针对上述两点隐患,为了减少硬件资源占用率、提高软件运行效率、并保证查询的安全性,他们采用了MySQLi(不再是以前传统的mysql_函数)和pg_query_params扩展库,同时也引入了预处理语句和相应的存储过程。当然,对于其他项目而言,不同的数据库可能会用到不同的扩展库和对应的PDO(PHP数据对象)。
3.限制访问路径
过去,他们直接通过编写PHP脚本及接口的调用去读取服务器的系统文件(包括诸如/etc/passwd之类的敏感文件),修改各种网络连接方式,以及发送打印作业等任务。而攻击者恰好可以利用此类后门,通过include()或fopen()方法来查找系统的文件路径。
如今,他们通过配置和使用open_basedir,来限制程序只访问指定目录里的文件。也就是说,外部访客或PHP进程完全无法访问到指定目录之外的任何信息。而且通过配置safe_moade_exec_dir,避免将PHP脚本与会话直接存放到可执行目录下。上升到理论层面,这实际上是对应用程序采取了运行环境的隔离(类似于沙箱的概念)。另外,通过诸如chroot之类的配置,将部分容器进程及应用服务限制为只允许操作其对应的运行环境。
4.防范XXE
无论是哪一版的OWASP Top 10,其中有一项风险便是安全配置的缺失。而在本次新网站的设计中,为了防止出现读取任意文件、执行系统命令、探测内网端口等XML外部实体注入的攻击,他们既通过启用libxml_disable_entity_loader来禁用外部实体,又对访客及用户所提交的XML数据进行了关键字过滤。另外也对文件的上传实施了相应的过滤,甚至是限制。
5.配置SSL/TLS
过去,由于没有用到https://的安全防护,访客从网站的URL上便可直观地获取各类参数信息。
如今,他们不但在服务器上通过配置TLS的最新版本实现了流量的强加密,而且启用了session.cookie_secure。同时,为保证访问请求的合法性及网站数据的机密性,对于与MySQL数据库、Apache服务器、以及远程调用等连接,都采用了TLS和公钥加密等“加持”方式。
6.隐蔽特征信息
默 认 情 况 下,PHP和Apache都会在HTTP包头中带有其相应的版本信息,这样对于那些谙熟软件版本漏洞的攻击者或工具来说,便有了可乘之机。此次,他们在新版网站中既对传输数据的包头进行了信息隐藏,又在生产环境中避免了各种程序相关错误、警告或异常信息的出现或泄露。
7.做好断舍离
既然理解了原有各类代码程序之间的依赖关系,他们在开始添加新的代码之前对已有程序进行了梳理和重构,重点“清洗”了生产环境中各种不再用到或从没用到的类、接口、方法及调用库。而对更新的代码,则使用Composer进行了各种依赖项的检测。同时,在测试环节中,分别运用SensioLabs DeprecationDetector来检查静态代码;运用IsDeprecated来检查PHP程序。
8.安全编码
为了最小化在开发过程中所产生的代码漏洞,尽量避免在程序中使用引用传递,而且使用持续集成(CI)工具phpsecurity-scanner,来对程序进行扫描和测试。一旦出现问题,工具将无法完成编译。
另外,为了避免在缓存或本地配置文件中存储SSH密钥、访问密码、API令牌等敏感信息,还采用了PHP dotenv,来自动且动态地部署并加载各类环境变量。
1.非暴力,拒绝DDoS
DDoS攻击曾给单位网站造成巨大破坏。当时就算做了流量清洗,也是治标不治本。因此,在本轮新架构设计中针对暴力登录之类的密集型连接攻击,启用了Fail2Ban。即通过监控系统的各类日志(特别是预定义好的失败登录数),实时调用正则表达式来匹配和筛选日志中的错误信息,进而调用防火墙(iptables)来屏蔽由此类IP地址所发出的连接请求。
2.会话管理
会话(Session)如同访客及用户伸向网站的“钩子”。它们不但会占用一定的系统资源,更可能会“钩破”(会话劫持)系统。因此,PHP团队成员进行了如下针对会话的管控与实践:
(1)如前所述,使用SSL来安全地传递只包含会话ID的cookie。
(2)除了设定基本的会话过期时长(session.gc_maxlifetime),还在用户更新密码、或出现违规事件时,做到了如下四步:
及时中断当前会话;删除含有会话信息的cookie;要求重新进行身份认证;运用session_regenerate_id新产生会话。
(3)准备专用的MySQL数据库来对会话,特别是Session Handler,进行存储、获取、日志和交互。
(4)配置session.use_strict_mode,让会话模块仅接受由它自己创建的有效会话ID,而拒绝由用户自己提供的会话ID。
(5)配置session.sid_length为48,通过会话 ID的长度来提高抗攻击能力。
(6)配置session.hash_function,用高强度的哈希算法来保护会话 ID。
(7) 配 置 session.cookie_lifetime,保证访客及用户关闭浏览器时,会话ID cookie 能被立即删除。
3.事无巨细
网络安全法中规定了企业应当监测、记录网站的运行状态、安全事件、用户登录等日志,并留存不少于六个月。因此,针对网站上线运行后可能出现的各种异常、以及访客与用户的非法输入和违规使用行为,利用monolog之类的日志扩展库对各种日志进行抓取和导出,以便后端的事件管理工具予以深入分析。
新版网站上线运行已一月有余,各方面状态比较稳定,并未出现显著的安全事故。网站开发团队也将上述针对PHP方面的安全加固与防护实践以清单的形式分享了出来,希望能够在整个技术部门形成正反馈,持续迭代、不断完善,通过优雅的代码打造出真正意义上的“硬核”站点。