PHP 中修复了一个非常古老的安全漏洞,该漏洞涉及在单个主进程共享多个 PHP-FPM 池的环境中处理其 OPCache 的方式。这是当今运行 PHP 最常见的方式,它也可能会影响到您。
漏洞
PHP 有一种方法可以加速其解释器的动态特性,称为字节码缓存。 PHP 在每次页面加载时都会被解释,这意味着 PHP 被转换为服务器理解并可以执行的字节码。 由于大多数 PHP 页面不会每秒更改一次,因此 PHP 将字节码缓存在内存中并将其用作响应,而不必每次都编译(或“解释”)PHP 脚本。
在默认的 PHP-FPM 设置中,进程树看起来像这样。
php-fpm: master process (/etc/php-fpm.conf)
\_ php-fpm: pool site1 (uid: 701)
\_ php-fpm: pool site2 (uid: 702)
\_ php-fpm: pool site3 (uid: 703)
有一个 PHP-FPM 主进程以 root 用户身份启动。 然后,它会生成额外的 FPM 池,每个 FPM 池都可以作为自己的用户运行来为网站提供服务。 PHP 操作缓存(即字节码缓存)存储在主进程中。
所以这里的问题是:当 PHP 从内存中获取脚本时,它不验证用户 ID,它存储在它的字节码中。
PHP中“共享内存”的概念是指所有的东西都存储在同一个内存段中,包含一个文件只是为了检查字节码中是否已经存在一个版本。
如果字节码中有脚本版本,则无需额外检查即可提供。
如果字节码中不存在某个版本的脚本,FPM 池(作为非特权 uid 运行)将尝试从磁盘读取它,Linux 将阻止读取进程无法访问的文件。
解决方案:升级 PHP 并设置其他配置
经过一段时间后,此错误现已解决,您可以使用其他主配置来修复它。
$ cat /etc/php-fpm.conf
...
opcache.validate_permission (default "0")
引导 OPcache 在每次访问缓存文件时检查文件可读性。
当少数
用户(PHP-FPM 池)重用公共 OPcache 共享内存时,应在共享托管环境中启用此指令。
opcache.validate_root (default "0")
该指令可防止不同“chroot”
环境中的文件名冲突。应该为可能在
不同“chroot”环境中提供请求的站点启用它
opcache.validate_permission和opcache.validate_root的引入意味着可以强制 PHP 的 OPCache 也检查文件的权限并强制验证文件的根路径,以避免 chrooted 环境读取彼此的文件(更多关于即在原来的错误报告)。
但是,默认值是不安全的。
我理解它们为什么会这样,以保持与以前版本的兼容性并避免在次要版本中破坏更改,但是您必须明确启用它们以防止这种行为。
最低版本:PHP 5.6.29、7.0.14、7.1.0
这个修复没有得到太多关注,为了适当地缓解这种情况,需要升级PHP;
5.6.29
7.0.14
7.1.0
即使 OPCache 在 PHP 5.5 中被引入,低于 5.6 的任何东西都已经结束生命并且不会得到这个修复。需要最新的 5.6 或更高版本来缓解这种情况。
如果仍然运行 PHP 5.5(很多人都这样做)并且想要安全,最好的办法是运行多个 PHP 主控(每个池一个主控)或完全禁用 OPCache。
将本地权限间接提升到 root
这是 PHP 的一个长期存在的问题,实际上比乍一看更严重。
如果设法破坏单个网站(对于大多数未更新的 PHP CMS 来说,这不是很难),则此共享内存允许访问所有其他网站,前提是这些网站的页面已缓存在 OPCache 中。
可以有效地使用此共享内存缓冲区作为读取其他网站的 PHP 文件、读取其配置、访问其数据库并窃取其所有数据的通道。为此通常需要对服务器进行 root 访问,但 PHP 的 OPCache 提供了一个方便的快捷方式来完成类似的事情。
所需要的只是站点的路径,可以通过opcache_get_status()检索(如果启用,再次 - 默认情况下),或者猜测路径,这在统用系统环境中通常也不难.
这实际上使这个共享主机架构几乎和本地提权漏洞一样轻松,这取决于想要完成什么。目标是从通常需要 root 权限的服务器窃取数据,拿到root权限不需要。
如果目标是通过获得 root 权限并运行仅限 root 的脚本(又名:在较低端口上绑定等),那将无法进行。
好消息是新的 PHP 配置现在已修复。 坏消息是需要耗时间去更新到最新版本才能启用。
For Mattias Geniar,2017 年 2 月 28 日 @mattiasgeniar
自翻译版,仅用于个人学习,带有个人笔记,不保证翻译准确,仅供参考。