【漏洞预警】WordPress全版本WPDB SQL注入预警及简要分析 – 安全客 – 有思想的安全新媒体
漏洞作者:Anthony Ferrara 漏洞等级:高危 影响版本:4.8.2 及之前版本 影响范围:WordPress版本4.8.2及之前版本受到该漏洞的影响: $wpdb->prepare()可以接收和执行不安全的查询,导致潜在的SQL注入(SQLi)。 WordPress核心并不直接容易受到这个问题的影响。 简要分析 WordPress 4.8.3中修复了一个重要的SQL注入漏洞,这个漏洞于2017年9月20日通过hackerone报给WordPress官方。 影响写法 以下$wpdb写法受该漏洞影响 不要将用户的输入传递给查询端,如以下写法1
|
1
2
|
$where = $wpdb->prepare(” WHERE foo = %s”, $_GET[‘data’]);
$query = $wpdb->prepare(“SELECT * FROM something $where LIMIT %d, %d”,1,2);
|
理论上写法2也是不安全的
|
1
2
|
$where =”WHERE foo = ‘”. esc_sql($_GET[‘data’]) . “‘”;
$query = $wpdb->prepare(“SELECT * FROM something $where LIMIT %d, %d”,1,2);
|
应该单独的构建查询和参数,然后通过prepare方法执行。如以下写法3
|
1
|
<p style=”text-indent: 2em;”>$where =”WHERE foo = %s”;<br>$args = [$_GET[‘data’]];<br>$args[] =1;<br>$args[] =2;<br>$query = $wpdb->prepare(“SELECT * FROM something $where LIMIT %d, %d”, $args);<br></p>
|
缺陷分析 为了了解本次报告中关于$wpdb的容易受攻击的代码,我们来跟进分析WordPress内部关于WPDB::prepare的部分。先来看源码(4.8.2版本之前)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
publicfunctionprepare( $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)是相同的,这点很重要。 如以下的代码
|
1
2
3
|
$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的一个小特性
|
1
2
|
vsprintf(‘%s, %d, %s’, [“a”, 1,”b”]);//”a, 1, b”
vsprintf(‘%s, %d, %1$s’, [“a”, 2,”b”]);//”a, 2, a”
|
注意,%n$s不会读取下一个参数,而是读取第n个位置的参数 我们可以基于这个验证上面的查询,假设我们发送以下请求信息给服务端
|
1
2
|
$_GET[‘items’] = [‘%1$s’];
$_GET[‘baz’] =”test”;
|
现在,查询将被改为SELECT * FROM foo WHERE bar IN (‘test’) AND baz = ‘test’;但这改变了查询的含义,为了更明显的说明sql注入,可以构造这样的请求数据
|
1
2
|
$_GET[‘items’] = [‘%1$c) OR 1 = 1 /*’];
$_GET[‘baz’] =39;
|
需要说明的是:sprintf还接受另一种类型的参数:%c它的作用就像chr()将十进制数字转换成一个字符。这里ASCII表39是’(单引号)的ASCII码。那么整个查询执行的sql语句是:
|
1
|
SELECT * FROM foo WHERE bar IN (”) OR1=1/*’ AND baz = ‘test’;
|
事实证明,该缺陷也存在WordPress的核心文件/wp-includes/meta.php。
|
1
2
3
4
5
6
7
|
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.3。用户可以通过点击后台的仪表盘->更新。 参考 https://blog.ircmaxell.com/2017/10/disclosure-wordpress-wpdb-sql-injection-technical.html https://wordpress.org/news/2017/10/wordpress-4-8-3-security-release/














暂无评论内容