我在WordPress.org上发现了一个蠕虫特性的存储XSS漏洞

利用在一个流行的wordpress插件上发现的漏洞进行攻击可以很轻而易举的攻陷全球成千上万的网站,比如最近的WP GDPR Compliance。一个插件出现漏洞则代表着所有使用这个插件的网站都存在巨大风险。但是能不能在wordpress圈子内找到一个比一个流行插件漏洞更具有攻击性的漏洞呢?这就是这篇文章的主要内容。本文将介绍研究人员今年五月份在wordpress上发现的一个具有蠕虫特性的存储XSS漏洞的过程

事件简介

WordPress.org掌管着wordpress cms所使用的所有插件和主题仓库。此外,它还管理着主题/插件开发者用于修改代码的账号。在今年的五月份,研究人员在wordpress.org上发现了一个具有蠕虫特性的存储XSS漏洞。漏洞点出现在上文所提到的仓库中的插件版本编号处。因此任何一个插件开发者都拥有实现本次XSS攻击的条件。
研究人员在开发coderisk.com网站时检测到该漏洞(他们的工作是为wordpress每一个插件的版本进行排序,因此注意到这个漏洞)。他们通过自己的一个插件对该漏洞进行了验证。在下文当中,将会讲述漏洞成因以及它是如何影响其他插件的用户。

 

技术细节

因为wordpress.org的开源性,我们得以通过代码审计来判断该漏洞存在的可能性。

wordpress的插件存储方式

WordPress.org使用wordpress CMS构建。在官网上展示的插件内容只是一个模板文件所生成的,插件开发者无法通过操作来通过展示的内容影响官网的安全性。
插件开发者不与官网直接交互,而是通过SVN来完成修改插件文件,更新插件等等操作。当一个插件开发者通过了wordpress的审核后,他就拥有了与SVN交互的能力,可以通过获得的SVN地址来对他的插件进行修改等。这里所获得的权限相当于他获得了wordpress.org开发者用户账号一样。wordpress官网上的插件信息(比如插件名,插件描述,插件版本等)通过插件的readme.txt和插件php主文件获取。官网将会时刻关注插件仓库的变化以便于及时更新插件的数据。

XSS注入点

我们发现这个漏洞的原因是在版本号输出到仓库的插件页面前并没有做任何的过滤处理。

下面的代码展示了版本号是如何输出的

wordpress.org/public_html/wp-content/plugins/plugin-directory/widgets/class-meta.php

第43行 <li><?php printf( __( 'Version: %s', 'wporg-plugins' ),
第44行 '<strong>' . get_post_meta( $post->ID, 'version', true ) . '</strong>' ); ?></li>

如同上文所说,在仓库里,插件通过特定的模板展示它的信息。插件的其他信息,比如版本号,会作为meta data插入到模板中展示。

下面的代码则展示了版本号从数据库中取出并没有经过任何的过滤和清洗就输出了。那么现在,如果版本号在存储到数据库前也没有进行任何的过滤的话,则存储型XSS存在。而版本号是从插件的php主文件的一个特别的header中读出。

wordpress.org/public_html/wp-content/plugins/plugin-directory/cli/class-import.php

第56行 namespace WordPressdotorgPlugin_DirectoryCLI;
第57行     ⋮
第58行 class Import {
第59行     ⋮
第60行     public function import_from_svn( $plugin_slug ) {
第61行         ⋮
第62行         $data = $this->export_and_parse_plugin( $plugin_slug );
第63行         ⋮
第64行         $headers = $data['plugin_headers'];
第65行         ⋮
第66行         update_post_meta( $plugin->ID, 'version', wp_slash( $headers->Version ) );

正如我们从代码中看到的一样,方法WordPressdotorgPlugin_DirectoryCLIImport:import_from_svn()(第60行)负责将SVN中的更改与存储在插件仓库中的数据同步。而方法export_and_parse_plugin()(第62行)负责将插件的信息提取出来(从上文提到过的readme.txt和php主文件),并保存重要的更改。版本号作为元数据插入到变更中,最终被更新到官网上的插件介绍中。而在变更和保存的过程中,对于版本号只应用了wp_slash()方法,而这个方法没有对XSS作任何保护。至此利用链完整

 

漏洞的危害

利用这个漏洞,需要一个开发者账号。因为在wordpress上传一个插件就可以获取到开发者账号了,因此该漏洞利用难度低。

攻击者可以将payload插入到它的插件版本号中,每一个在官网的插件仓库中浏览到该恶意插件的用户都会被攻击。那么这个漏洞的危害到底有多大呢?

我们来想象一下,一个有开发者权限的用户可以在WordPress.org上添加其他账号作为插件的贡献者。这样一来被添加的账号会获得对该插件SVN的完全控制权,从而可以修改插件的代码。不仅如此,被添加的账号还具有插件的访问控制权限,可以删除和添加其他账号作为插件的贡献者,比如可以不需要经过任何认证删除插件的作者。

我们可以在payload中添加一些简单的ajax来进行这样的操作。攻击者可以设计一个payload,使得当拥有开发者权限的用户访问受此XSS感染的插件页面时,向被感染的用户的插件添加一个账号作为贡献者。之后,攻击者再利用这个秘密添加的贡献者账号,继续将payload插入到本次被感染的插件的版本号中,从而进行下一轮的传播。对于payload的首轮传播可以在论坛上发布新插件(含payload的恶意插件)宣传帖子进行。

 

另一个在admin仪表盘上的反射性XSS

在找到第一个漏洞后,我们使用RIPS 对WordPress.org代码库进行了扫描。发现了另一个在admin仪表盘上的反射性XSS漏洞。

wordpress.org/public_html/wp-content/plugins/plugin-directory/admin/tools/class-stats-report.php


第1行 public function show_stats() {
第2行     ⋮
第3行     if ( isset( $_POST['date'] ) && preg_match( '/[0-9]{4}-[0-9]{2}-[0-9]{2}$/', $_POST['date'] ) ) {
第4行         $args['date'] = $_POST['date'];
第5行     } else {
第6行         $args['date'] = '';
第7行     }
第8行     ⋮
第9行     $stats = $this->get_stats( $args );
第10行     ⋮    
第11行     printf(
第12行         __( 'Displaying stats for the %1$d days preceding %2$s (and other stats for the %3$d most recent days).', 'wporg-plugins' ),
第13行         $stats['num_days'],
第14行         $stats['date'],
第15行         $stats['recentdays']
第16行     );

在上面代码的第3行中,从$ _POST [‘date’]收到的用户输入用preg_match进行正则匹配。然后没有经过其他转移,直接输出了这个从用户接受的值(第14行)。而这里的正则匹配模式显然缺了个^使得可绕过构造payload,比如<script>alert(1)</script> 0000-00-00

 

总结

在本文中,我们介绍了在WordPress.org网站上发现的两个漏洞。第一个是插件库中一个严重的存储型XSS,任何在插件库中拥有插件的用户都可以利用它。如果恶意攻击者利用它,会造成严重的后果。第二个是wordpress.org/plugins admin仪表盘中反射型的XSS,它展示了正则表达式中的一个小疏忽也会导致一个大漏洞。

(完)