Piwigo2.7.1 由于 in_array() 函数参数设置不当引起的SQL注入复现及代码审计

环境搭建

配置信息

系统:Windows 10
浏览器:FireFox
集成环境:PHPStudy
PHP版本:5.5.38
Apache
Mysql
审计软件:Seay

搭建完成后页面截图如下:

环境搭建完毕!


代码审计

问题代码出在 picture.php 中的 第 268 行中:

if (isset($_GET['action']))
{
  switch ($_GET['action'])
  {
    case 'add_to_favorites' :
    {
      $query = '
INSERT INTO '.FAVORITES_TABLE.'
  (image_id,user_id)
  VALUES
  ('.$page['image_id'].','.$user['id'].')
;';
      pwg_query($query);

      redirect($url_self);

      break;
    }
    case 'remove_from_favorites' :
    {
      $query = '
DELETE FROM '.FAVORITES_TABLE.'
  WHERE user_id = '.$user['id'].'
    AND image_id = '.$page['image_id'].'
;';
      pwg_query($query);

      if ('favorites' == $page['section'])
      {
        redirect($url_up);
      }
      else
      {
        redirect($url_self);
      }

      break;
    }
    case 'set_as_representative' :
    {
      if (is_admin() and isset($page['category']))
      {
        $query = '
UPDATE '.CATEGORIES_TABLE.'
  SET representative_picture_id = '.$page['image_id'].'
  WHERE id = '.$page['category']['id'].'
;';
        pwg_query($query);

        include_once(PHPWG_ROOT_PATH.'admin/include/functions.php');
        invalidate_user_cache();
      }

      redirect($url_self);

      break;
    }
    case 'add_to_caddie' :
    {
      fill_caddie(array($page['image_id']));
      redirect($url_self);
      break;
    }
    case 'rate' :
    {
      include_once(PHPWG_ROOT_PATH.'include/functions_rate.inc.php');
      rate_picture($page['image_id'], $_POST['rate']);
      redirect($url_self);
    }

其中漏洞触发在 case 'rate'中的 rate_picture($page['image_id'], $_POST['rate']); 当中,这个方法来自 include/functions_rate.inc.php文件中,于是溯源到此文件中:

这里仅放重点代码:

function rate_picture($image_id, $rate)
{
  global $conf, $user;

  if (!isset($rate)
      or !$conf['rate']
      or !in_array($rate, $conf['rate_items']))
  {
    return false;
  }
...
INSERT
  INTO '.RATE_TABLE.'
  (user_id,anonymous_id,element_id,rate,date)
  VALUES
  ('
    .$user['id'].','
    .'\''.$anonymous_id.'\','
    .$image_id.','
    .$rate
    .',NOW())
;';

可以看到,在下列代码:

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

中的 in_array() 函数中,对于第三个参数,并没有设置为 true, 这里写出PHP官方文档对 in_array() 函数的定义:

其中 strict 参数的值为 TRUE, 则 in_array() 函数还会检查 needle 的类型是否和 haystack 中的相同。

这里溯源 $conf['rate_items'] 如下 (在 include/config_default.inc.php 文件第 236 行):

// rate_items: available rates for a picture
$conf['rate_items'] = array(0,1,2,3,4,5);

这里由于 strict 参数不为 true,所以可以与 $conf['rate_items'] 进行弱类型比较,造成 SQL 注入

漏洞复现

这里直接使用 sqlmap 指定参数 rate 即可进行脱裤(基于盲注)

CTF例题

//index.php
<?php
include 'config.php';
$conn = new mysqli($servername, $username, $password, $dbname);
if ($conn->connect_error) {
    die("连接失败: ");
}

$sql = "SELECT COUNT(*) FROM users";
$whitelist = array();
$result = $conn->query($sql);
if($result->num_rows > 0){
    $row = $result->fetch_assoc();
    $whitelist = range(1, $row['COUNT(*)']);
}

$id = stop_hack($_GET['id']);
$sql = "SELECT * FROM users WHERE id=$id";

if (!in_array($id, $whitelist)) {
    die("id $id is not in whitelist.");
}

$result = $conn->query($sql);
if($result->num_rows > 0){
    $row = $result->fetch_assoc();
    echo "<center><table border='1'>";
    foreach ($row as $key => $value) {
        echo "<tr><td><center>$key</center></td><br>";
        echo "<td><center>$value</center></td></tr><br>";
    }
    echo "</table></center>";
}
else{
    die($conn->error);
}

?>

//config.php
<?php  
$servername = "localhost";
$username = "fire";
$password = "fire";
$dbname = "day1";

function stop_hack($value){
    $pattern = "insert|delete|or|concat|concat_ws|group_concat|join|floor|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile|dumpfile|sub|hex|file_put_contents|fwrite|curl|system|eval";
    $back_list = explode("|",$pattern);
    foreach($back_list as $hack){
        if(preg_match("/$hack/i", $value))
            die("$hack detected!");
    }
    return $value;
}
?>

# 搭建CTF环境使用的sql语句
create database day1;
use day1;
create table users (
id int(6) unsigned auto_increment primary key,
name varchar(20) not null,
email varchar(30) not null,
salary int(8) unsigned not null );

INSERT INTO users VALUES(1,'Lucia','Lucia@hongri.com',3000);
INSERT INTO users VALUES(2,'Danny','Danny@hongri.com',4500);
INSERT INTO users VALUES(3,'Alina','Alina@hongri.com',2700);
INSERT INTO users VALUES(4,'Jameson','Jameson@hongri.com',10000);
INSERT INTO users VALUES(5,'Allie','Allie@hongri.com',6000);

create table flag(flag varchar(30) not null);
INSERT INTO flag VALUES('HRCTF{1n0rrY_i3_Vu1n3rab13}');

sqlmap跑不出来,清明节自己手工注入试试~

这里先简单看看他过滤了什么语句

$pattern = "insert|delete|or|concat|concat_ws|group_concat|join|floor|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile|dumpfile|sub|hex|file_put_contents|fwrite|curl|system|eval";

可以看出他对一下列表的语句进行了正则匹配过滤:

  • insert
  • delete
  • or
  • concat
  • concat_ws
  • group_concat
  • join
  • floor
  • ../../ 等路径遍历的
  • union
  • into
  • load_file
  • outfile
  • dumpfile
  • sub
  • hex
  • file_put_contents
  • fwrite
  • curl
  • system
  • eval

这里有个代码出了点问题,就是上文提到的in_array()函数参数不全的问题:

if (!in_array($id, $whitelist)) {
    die("id $id is not in whitelist."); //这里whitelist中存放的是[1,2,3,4,5];
}

继续往下看,

参考链接

红日安全-代码审计Day1

在这个月准备跟随 这个博文逐步复现学习代码审计,希望自己的能力能够逐渐提高,而不像现在这么菜~

Comments

添加新评论