0x00 前言
2019年7月,FortiGuard实验室发现并报告了9款常见WordPress插件中的9个SQL注入漏洞,这些插件类别较广,包括广告、捐赠、图库、表单、简讯以及视频播放器。目前有成千上万个WordPress站点正在使用这些插件,其中包括在某些领域中位居前列的某些站点。
FortiGuard实验室为这些漏洞分配了编号以及对应的CVE ID,具体为:FG-VD-19-092、FG-VD-19-094、FG-VD-19-095、FG-VD-19-096、FG-VD-19-097、FG-VD-19-098、FG-VD-19-099、FG-VD-19-101以及FG-VD-19-102。
有趣的是,9个漏洞中有8个都使用相同的代码模式,因此存在SQL注入风险。尽管存在漏洞利用风险,但许多开发者并没有仔细过滤用户提供的数据。虽然WordPress提供了各种内置的方法,用来确保用户提供的数据尽可能被过滤,但依然避免不了漏洞的出现。
在本文中,我们将介绍WordPress提供的一些安全机制,分析我们发现的某些漏洞,解释攻击者如何利用这些漏洞,以及从开发者角度出发介绍如何防御这些漏洞。
在本文撰写时,这些问题已经被修复完毕,相应厂商也推出了补丁,整个响应过程非常迅速。
0x01 背景知识
当用户输入数据没有经过正确过滤,可以用来构造SQL查询语句时,就会出现SQL注入漏洞。考虑如下示例:
图1. WordPress中的SQL查询示例
在初步分析时,有人会认为这段代码存在SQL注入漏洞,因为$id
来自于$_GET
,并且没有过滤直接传递给SQL查询。好消息是,最新版的WordPress默认会向$_POST/$_GET/$_REQUEST/$_COOKIE
添加魔术引号(Magic Quotes)[1],这样可以有助于WordPress保持一致性,提供最佳的安全功能。因此,实际上上述代码并不存在漏洞。
除了强制向所有输入值添加反斜杠外,WordPress还内置了一些过滤及转义函数,可以过滤用户输入以及/或者保护输出数据[2]。开发者可以使用sanitize_email()
来过滤邮件地址,或者使用sanitize_text_field()
来过滤文本框值,或者使用sanitize_sql_orderby()
来验证SQL ORDER BY
字句等。WordPress中的sanitize_*()
类辅助函数已经覆盖了大多数用户输入类型。
虽然WordPress Core已经尽力帮助开发者防御因恶意用户输入导致的常见攻击,但错误的编码风格以及对转义函数的误用依然可以造成简单但严重的后果。
0x02 漏洞分析
FG-VD-19-092:AdRotate Plugin中的SQL注入
这是一个经典的SQL注入漏洞,位于AdRotate插件中(v5.2及更早版本),免费版及专业版都被波及。漏洞点位于dashboard/publisher/adverts-edit.php
中,第52行:
图2. adverts-edit.php
中使用SELECT
语句从DB中提取广告
$ad_edit_id
变量用来构造SQL查询语句,这个变量来自于adrotate_manage
函数的$_GET
:
图3. 用户可以控制$ad_edit_id
由于esc_attr
只会转义HTML属性,SQL查询语句中并没有使用双引号来转义$ad_edit_id
,因此我们可以将payload注入$ad_edit_id
,执行任意SQL语句。
图4. 5.2版AdRotate插件的数据库版本
虽然管理接口只对管理员角色开放,但因为缺乏CSRF token,因此未授权攻击者有可能使用这个SQL注入漏洞构造XSS攻击,通过最少的用户交互来远程窃取信息(包括会话令牌):
图5. 利用SQL注入构造XSS
开发者在补丁中为查询语句的$ad_edit_id
添加引号,解决了这个问题。
图6. 开发者在5.3版AdRotate中修复了这个SQL注入漏洞
时间线:
- Fortinet在2019年7月9日向AJDG Solutions反馈漏洞。
- AJDG Solutions要求提供更多信息,并在2019年7月10日确认了该漏洞。
- AJDG Solutions于2019年7月12日发布了针对该漏洞的补丁。
FG-VD-19-099:NextGEN Gallery插件中的SQL注入
从2007年开始,NextGEN Gallery一直都是业界标准的WordPress图库插件,每年都会新增150万次下载量。这款插件有许多优点,对简单的照片图库场景非常易用,对要求苛刻的摄影师、视觉艺术家以及图像专业人士来说也足够强大。在本文撰写时,NextGEN Gallery的活跃安装数已超过900,000次,是图库类最流行的插件[3]。
漏洞位于AJAX API中,在撰写文章时,用户可以使用该API附加来自图库的照片。
图7. get_displayed_gallery_entities_action
处理用户提供的输入
modules/attach_to_post/package.module.attach_to_post.php
中的get_displayed_gallery_entities_action
函数负责显示所选图库中的照片,代码在119行通过POST方法提取数组参数displayed_gallery
,用来创建一个图库对象,然后通过esc_sql
辅助函数来转义这个对象的属性。代码在130行调用get_entities
,我们可以继续跟进modules/nextgen_gallery_display/package.module.nextgen_gallery_display.php
中的这个get_entities
函数。
图8. get_entities
会根据returns
请求来调用对应的函数
由于returns
请求的值为both
,因此代码会在832行调用_get_image_entities
。
图9. _get_image_entities
会提取已显示图库中的所有图像
该函数的主要功能就是构造请求,返回已显示图库中的所有图像。大家可能会注意到,在第1041行,代码会根据$sort_by
以及$sort_direction
执行排序操作,而这两个参数来自于前面创建的图库对象。通过身份认证的任何用户只要具备NextGEN Gallery的使用权限,就可以操控这些参数来构造这个图库对象。虽然该对象的所有属性都使用esc_sql
进行处理,但实际上攻击者不需要转义引号就能在ORDER BY
字句中执行SQL注入攻击。因此,这种情况下esc_sql
并不能帮助NextGEN Gallery防御此类漏洞攻击。
图10. NextGEN Gallery中的Blind-SQL注入,通过恒真表达式返回所选图库中的所有图像
图11. NextGEN Gallery中的Blind-SQL注入,恒假表达式返回空结果
Imagely团队的补丁非常有效,在ORDER BY
字句中只允许某些值,可以安全保护查询语句。
图12. Imagely通过白名单方式修复该问题
时间线:
- Fortinet于2019年7月23日向Imagely反馈该漏洞。
- Imagely确认该漏洞,于2019年7月24日通过NextGEN Gallery 3.2.10发布补丁。
- 2019年8月27日,Imagely通过NextGEN Gallery 3.2.11发布补丁,完全解决该问题。
通过相同的分析模式,我又在7个插件中找到了SQL注入点,其中有个插件的确尝试部署白名单机制,但因为有个很小的编程错误导致功亏一篑。
FG-VD-19-098:Impress Give插件中的SQL注入
Give是WordPress上评价最高、下载最多并且功能最全的捐助插件[4]。
漏洞代码位于includes/donors/class-give-donors-query.php
的get_order_query
函数中。
图13. Give插件中的漏洞代码
如代码中的注释所述,get_order_query
尝试删除ORDER BY
字句中不存在的列,通过esc_sql
辅助函数来过滤排序值。然而,这种删除操作并没有达到预期的效果,因为当467行unset
不存在的列后,代码又会在470行重新插入转义值。其实很多人都知道,esc_sql
并不足以防御ORDER BY
子句中的SQL注入攻击。因此,代码构造出来的查询语句依然存在漏洞。我们可以使用相同的Blind-SQL注入技术来利用这个漏洞。
Impress团队只添加了一行补丁代码,就可以达到修复目标,保证查询语句的安全性。
图14. Impress团队在Give插件中的修复方式
时间线:
- Fortinet在2019年7月11日反馈了该漏洞
- 2019年7月11日,Give反馈正在研究漏洞报告
- 2019年7月13日,Give确认漏洞并发布补丁。Give团队要求我们尽可能在8月11日左右披露漏洞细节,以便为用户提供尽可能多的时间进行升级。
其他漏洞也满足相同的代码模式,修复方式也非常类似。
0x03 建议
这里我们想提醒开发者在开发WordPress插件时需要注意的一些点,以便防御SQL注入攻击。WordPress社区推出了一个非常有用且完整的插件开发手册[5],作为开发者,我们应当始终遵循WordPress的编程标准及安全开发实践。此外如果按照如下标准,我们不仅可以防御SQL注入攻击,还能防御因为恶意用户输入导致的其他漏洞:
- 永远不要信任用户输入。在使用用户提供的数据前,始终要执行验证及过滤操作。
- 如果我们不确定输入数据是否安全,那么就要使用与该数据最匹配的内置过滤函数来处理。比如,如果使用
sanitize_sql_orderby()
函数就能解决前面提到的所有漏洞。 - 在绝大多数情况下,我们需要使用预处理语句(Prepared Statement),不要使用
esc_sql
。 - 在极少数情况下,我们无法简单去使用预处理语句,此时我们可以使用
esc_sql
,将转义值放在引号内。
0x04 总结
WordPress是最主流的CMS,占有61.0%的市场份额,这意味着有34.3%的网站使用了WordPress[6]。根据Wordfence提供的数据,其中52%的漏洞都与WordPress插件有关[7]。这些统计数据表明针对WordPress插件的攻击也是网络犯罪分子最实用的一种攻击途径。SQL注入并不是一项新的技术,但始终会对Web应用以及Web服务器造成严重的威胁。为了防御这类威胁,开发者应当遵循开发标准以及安全的开发实践。
0x05 参考资料
[1] WordPress, “Function Reference/stripslashes_deep”, https://codex.wordpress.org/Function_Reference/stripslashes_deep
[2] WordPress, “Validating Sanitizing and Escaping User Data”, https://codex.wordpress.org/Validating_Sanitizing_and_Escaping_User_Data
[3] Imagely, “WordPress Gallery Plugin – NextGEN Gallery”, https://wordpress.org/plugins/nextgen-gallery/
[4] Impress, “Give – Donation Plugin and Fundraising Platform”, https://wordpress.org/plugins/give/
[5] WordPress, “Plugin Handbook”, https://developer.wordpress.org/plugins/
[6] W3Techs, “Usage statistics and market share of WordPress”, https://w3techs.com/technologies/details/cm-wordpress/all/all
[7] Wordfence, “How Attackers Gain Access to WordPress Sites” (2016), https://www.wordfence.com/blog/2016/03/attackers-gain-access-wordpress-sites/