作者:@Ambulong
第一章: sprintf/vsprintf 中的 argument numbering/swapping
1.1 函数间的区别
在PHP中,我们主要通过sprintf函数和vsprintf函数来格式化字符串,同时会对参数进行类型的转换。这两个函数的区别在于sprintf函数在第一个参数之后可接收多个不同类型参数,vsprintf的第一个参数之后只接收一个数组参数(即:第二个参数只能是数组)。
sprintf函数
vsprintf函数
1.2 format参数
sprintf/vsprintf函数的第一个参数$format指定了如何格式化后面的参数。
常见的格式化类型如下:
标识 | 类型 |
---|---|
%s | 字符串 |
%d | 整数 |
%f | 浮点数 |
以下两个例子的输出结果是一样的
|
|
输出结果:
1.3 format参数延伸
sprintf/vsprintf函数还可以用来将字符串自动补位,如:
例一:”123”用0补齐5位变成”00123”:
0表示要补上的数字为0,5表示的是位数,d表示类型为整数。
例二:”123”用.补齐5位变成”..123”:
‘.表示要补上的字符为。(字符需要加上’),5表示的是位数,d表示类型为整数。
需要了解更多关于format的描述,请参见 sprintf()
Argument numbering/swapping
sprintf/vsprintf的格式化字符串支持Argument numbering/swapping(中文直译:参数交换),即可以指定格式化标识表示的是第几个参数。
例一:
例二:
例三:
注:Chapter 1由@Ambulong与@乐清小俊杰共同完成。
第二章: wpdb类中的prepare()函数
在Wordpress的数据库操作类wpdb(文件: /wp-includes/wp-db.php)中有一个prepare()函数,该函数主要用来对将要执行SQL语句进行预处理,如:
以上例子将会返回下列字符串:
但是该函数没有并没有对传入的$query参数进行严格的过滤,如果$query参数内容或部分内容可控,就可能导致SQL注入。
prepare函数的代码如下:
|
|
该函数主要做了以下几件工作:
1). 判断$args[0]是否数组,如果是则使$args=$args[0]。
2). 将$query中’%s’替换为%s。
3). 将$query中”%s”替换为%s。
4). 再将%s替换为’%s’。
5). 将$args用mysql_real_escape_string转义。
6). 返回vsprintf( $query, $args )。
经分析,该函数可能导致两个问题:
1). 逻辑漏洞
若程序中存在类似下列的代码:
按正常的业务逻辑,prepare将返回vsprintf( 'update articles set title = %s where id = %d and uid = %d', array($_GET['title'], $_GET['id'], get_current_uid() )
的执行结果。
但是此时format后的第一个参数($_GET[‘title’])我们完全可控,如果我们使第一个参数为数组,我们就可以控制用户ID,如:$_GET[‘title’] = array(‘title’, ‘id’ ,’xxx’),此时prepare将返回vsprintf( 'update articles set title = %s where id = %d and uid = %d', array('title', 'id' ,'xxx')
。
此时,一个越权漏洞就产生了。
2). SQL注入
若程序中存在类似下列的代码:
我们使得tag=%s
,则$append="and tag = '%sa'"
。此时的$query
将为$wpdb->prepare( 'select * from articles where uid = %d and cid = %d and tag = \'%s\'', get_current_uid(), $_GET['cid'])
,经prepare处理后等同于$query = vsprintf('select * from articles where uid = %d and cid = %d and tag = \'\'%s\'a\'', array(get_current_uid(), $_GET['cid']));
。
此时的%s将处于单引号之外,如果%s可控,将导致SQL注入。此时,就要用到前面1.3部分提到的Argument numbering/swapping,我们可以使tag=%2$s
,但是此时不存在%s
,经prepare函数处理后,$query = vsprintf('select * from articles where uid = %d and cid = %d and tag = \'%2$s\'', array(get_current_uid(), $_GET['cid']));
,虽然此时的%2$s经vsprintf函数格式化后将等于$_GET['cid']
的值,但是参数被包含在引号之内,无法导致SQL注入。
这时我们就需要用到1.3内的字符串自动补位。我们使tag=%2$%s abc
,经prepare处理后$query = vsprintf('select * from articles where uid = %d and cid = %d and tag = \'%2$\'%s\' abc\'', array(get_current_uid(), $_GET['cid']));
。此时的关键部分为tag = '%2$'%s' abc'
,此时的%2$'%s
为格式化标识,里面2代表第二个参数(即$_GET['cid']
),’%表示用%填充,s表示格式化为字符串,默认的填充位数为0。
范例:
此时的abc将在单引号外,且用户可控,即产生了SQL注入漏洞。
参考链接
- Wordpress SQLi by slavco - https://medium.com/websec/wordpress-sqli-bbb2afcc8e94