近日,国家信息安全漏洞共享平台(CNVD)收录了WordPress WPDB SQL注入漏洞(CNVD-2017-32143)。远程攻击者利用该漏洞可造成SQL注入攻击,获取数据库敏感信息。漏洞的详细细节已公开,近期被不法分子利用进行出现大规模攻击尝试的可能性较大。
一、漏洞情况分析
WordPress是使用PHP语言和MySQL数据库开发的,世界上使用最广泛的博客系统,并逐步演化成一款内容管理软件。
2017年10月31日,WordPress官方发布了WordPress安全更新并修复了一处SQL注入漏洞,即$wpdb-prepare()函数可以创建无法预测且不安全的查询,从而导致潜在的SQL注入(SQLi),但WordPress核心并不容易直接受到该漏洞的影响。CNVD对上述漏洞的综合评级为“高危”。
WordPress 4.8.3中修复了一个重要的SQL注入漏洞,这个漏洞于2017年9月20日通过hackerone报给WordPress官方。
影响写法
以下$wpdb写法受该漏洞影响:
不要将用户的输入传递给查询端,如以下写法1
$where?=?$wpdb->prepare("?WHERE?foo?=?%s",?$_GET['data']); $query?=?$wpdb->prepare("SELECT?*?FROM?something?$where?LIMIT?%d,?%d",?1,?2);
理论上写法2也是不安全的:
$where?=?"WHERE?foo?=?'"?.?esc_sql($_GET['data'])?.?"'"; $query?=?$wpdb->prepare("SELECT?*?FROM?something?$where?LIMIT?%d,?%d",?1,?2);
应该单独的构建查询和参数,然后通过prepare方法执行。如以下写法3
$where = "WHERE foo = %s"; $args = [$_GET['data']]; $args[] = 1; $args[] = 2; $query = $wpdb->prepare("SELECT * FROM something $where LIMIT %d, %d", $args);
缺陷分析:
为了了解本次报告中关于$wpdb的容易受攻击的代码,我们来跟进分析WordPress内部关于WPDB::prepare的部分。先来看源码(4.8.2版本之前)
public?function?prepare(?$query,?$args?)?{ ????if?(?is_null(?$query?)?) ????????return; ????//?This?is?not?meant?to?be?foolproof?--?but?it?will?catch?obviously?incorrect?usage. ????if?(?strpos(?$query,?'%'?)?===?false?)?{ ????????_doing_it_wrong(?'wpdb::prepare',?sprintf(?__(?'The?query?argument?of?%s?must?have?a?placeholder.'?),?'wpdb::prepare()'?),?'3.9.0'?); ????} ????$args?=?func_get_args(); ????array_shift(?$args?); ????//?If?args?were?passed?as?an?array?(as?in?vsprintf),?move?them?up ????if?(?isset(?$args[0]?)?&&?is_array($args[0])?) ????????$args?=?$args[0]; ????$query?=?str_replace(?"'%s'",?'%s',?$query?);?//?in?case?someone?mistakenly?already?singlequoted?it ????$query?=?str_replace(?'"%s"',?'%s',?$query?);?//?doublequote?unquoting ????$query?=?preg_replace(?'|(?<!%)%f|'?,?'%F',?$query?);?//?Force?floats?to?be?locale?unaware ????$query?=?preg_replace(?'|(?<!%)%s|',?"'%s'",?$query?);?//?quote?the?strings,?avoiding?escaped?strings?like?%%s ????array_walk(?$args,?array(?$this,?'escape_by_ref'?)?); ????return?@vsprintf(?$query,?$args?); }
这里需要注意三点
1.通过vsprintf格式化字符串,用$args替换占位符,并返回格式化后的一个字符串
2.利用str_replace函数正确处理$query中的占位符
3.如果传递的是参数,该参数是一个数组,那么它将用该数组的值替换参数。
总之,意思是调用$wpdb->prepare($sql, [1, 2])与调用$wpdb->prepare($sql, 1, 2)是相同的,这点很重要。
如以下的代码
$items?=?implode(",?",?array_map([$wpdb,?'_real_escape'],?$_GET['items'])); $sql?=?"SELECT?*?FROM?foo?WHERE?bar?IN?($items)?AND?baz?=?%s"; $query?=?$wpdb->prepare($sql,?$_GET['baz']); 这里涉及到vsprintf的一个小特性 vsprintf('%s,?%d,?%s',?["a",?1,?"b"]);?//?"a,?1,?b" vsprintf('%s,?%d,?%1$s',?["a",?2,?"b"]);?//?"a,?2,?a"
注意,%n$s不会读取下一个参数,而是读取第n个位置的参数
我们可以基于这个验证上面的查询,假设我们发送以下请求信息给服务端
$_GET['items']?=?['%1$s']; $_GET['baz']?=?"test";
现在,查询将被改为SELECT * FROM foo WHERE bar IN (‘test’) AND baz = ‘test’;但这改变了查询的含义,为了更明显的说明sql注入,可以构造这样的请求数据
$_GET['items']?=?['%1$c)?OR?1?=?1?/*']; $_GET['baz']?=?39;
需要说明的是:sprintf还接受另一种类型的参数:%c它的作用就像chr()将十进制数字转换成一个字符。这里ASCII表39是’(单引号)的ASCII码。那么整个查询执行的sql语句是:
SELECT?*?FROM?foo?WHERE?bar?IN?('')?OR?1?=?1?/*'?AND?baz?=?'test';
事实证明,该缺陷也存在WordPress的核心文件/wp-includes/meta.php。
if?(?$delete_all?)?{ ??$value_clause?=?''; ??if?(?''?!==?$meta_value?&&?null?!==?$meta_value?&&?false?!==?$meta_value?)?{ ????$value_clause?=?$wpdb->prepare(?"?AND?meta_value?=?%s",?$meta_value?); ??} ??$object_ids?=?$wpdb->get_col(?$wpdb->prepare(?"SELECT?$type_column?FROM?$table?WHERE?meta_key?=?%s?$value_clause",?$meta_key?)?); }
二、漏洞影响范围
漏洞影响WordPress 4.8.2及之前版本。
三、漏洞修复建议
支持自动更新的用户可以通过点击后台的仪表盘更新到4.8.3版本,也可手动下载更新:https://wordpress.org/download/
附:参考链接:
https://blog.ircmaxell.com/2017/10/disclosure-wordpress-wpdb-sql-injection-technical.html
http://www.cnvd.org.cn/flaw/show/CNVD-2017-32143