PHP代码审计:变量覆盖

0x00 前提

本文是给作为给小伙伴分享教学的一篇文章,给讲一下,所以写的比较简陋。。。见谅而且都比较基础,只是简单讲课提纲吧

0x01 变量覆盖审计

0x00 简介

变量覆盖,顾名思义就是可以覆盖已有变量值,导致变量覆盖的漏洞

常见的造成的代码审计的情景是代码中出现以下关键词:

  • register_globals=on
  • extract()函数
  • parse_str()函数
  • import_request_variables()函数
  • $$

0x01 变量覆盖演示

extract()

extract(array,extract_rules,prefix)函数

https://www.runoob.com/php/func-array-extract.html

该函数可以从数组中将变量导入到当前的符号表,即将数组中的键值对注册成函数,使用数组键名作为变量名,使用数组键值作为变量值。

这里我们要注意一下该函数的第二个参数

  • EXTR_OVERWRITE - 默认。如果有冲突,则覆盖已有的变量。
  • EXTR_SKIP - 如果有冲突,不覆盖已有的变量。

这就为我们提供了覆盖的可能。

1
2
3
4
5
6
<?php
$a = 'a';
echo $a.'</br>';
extract($_GET);
echo $a
?>

可以看到我们初始变量值为a但是覆盖之后就变成了我们输入的值。

1
http://127.0.0.1/test/extract.php?a=123

image-20200907141301903

修复:

在使用extract()函数时,可以指定将第二个参数设置为EXTRS_KIP

parse_str()

parse_str()函数用于把查询字符串解析到变量中,如果没有array参数,则由该函数设置的变量将覆盖已存在的同名变量。

在没有array参数的情况下使用此函数,

并且在PHP 7.2中将废弃不设置参数的行为,此函数没有返回值。

1
2
3
4
5
6
7
8
<?php
$a = "giao";
echo "a:" . $a;
echo "<br>";
$b = $_GET['b'];
parse_str($b);
echo "a_2:" . $a;
?>
1
http://127.0.0.1/test/parse_str.php?b=a=zeo

image-20200907145448737

$$

典型的例子就是foreach来遍历数组中的值作为变量。

$$是一种可变变量的写法,它可以使一个普通变量的值作为可变变量的名字,这种类型常常会使用遍历的方式来释放变量的代码

1
2
3
4
5
6
7
8
9
<?php
$a = 10;
echo $a;
echo "<br>";
foreach ($_POST as $k => $v){
$$k = $v;
echo $a;
}
?>

image-20200907141129332

import_request_variables()

import_request_variables ( string $types , string $prefix )

https://www.runoob.com/php/php-import\_request\_variables-function.html

import_request_variables() 函数将 GET/POST/Cookie 变量导入到全局作用域中。该函数在最新版本的 PHP 中已经不支持。

import_request_variables() 函数将 GET/POST/Cookie 变量导入到全局作用域中。如果你禁止了 register_globals,但又想用到一些全局变量,那么此函数就很有用。

版本要求:PHP 4 >= 4.1.0, PHP 5 < 5.4.0

第二个参数$types:指定需要导入的变量,可以用字母 G、P 和 C 分别表示 GET、POST 和 Cookie,这些字母不区分大小写,所以你可以使用 g 、 p 和 c 的任何组合。POST 包含了通过 POST 方法上传的文件信息。注意这些字母的顺序,当使用 gp 时,POST 变量将使用相同的名字覆盖 GET 变量。任何 GPC 以外的字母都将被忽略。

image-20200907104858013

全局变量

当你在升级PHP到PHP5.4及之后的版本的时候,是否发现register_global配置指令不再生效了呢

因为从PHP5.4开始register_global配置指令被移除了。

1
2
3
4
5
<?php
echo "register_globals: " . (int)ini_get("register_globals");
echo '<br>';
echo "a=" . $a;
?>

image-20200907114032777

0x02 深x服edr实例

简化后的代码

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
<?php
#var_dump($_REQUEST);


$show_form = function($params) use(&$strip_slashes, &$show_input) {
extract($params);

$host = isset($host) ? $strip_slashes($host) : "127.0.0.1";
$path = isset($path) ? $strip_slashes($path) : "";
$row = isset($row) ? $strip_slashes($row) : "";
$limit = isset($limit) ? $strip_slashes($limit) : 1000;

// 绘制表单
echo "<pre>";
echo '<form id="studio" name="studio" method="post" action="">';
$show_input(array("title" => "Host ", "name" => "host", "value" => $host, "note" => " - host, e.g. 127.0.0.1"));
$show_input(array("title" => "Path ", "name" => "path", "value" => $path, "note" => " - path regex, e.g. mapreduce"));
$show_input(array("title" => "Row ", "name" => "row", "value" => $row, "note" => " - row regex, e.g. \s[w|e]\s"));
$show_input(array("title" => "Limit", "name" => "limit", "value" => $limit, "note" => " - top n, e.g. 100"));
echo '<input type="submit" id="button">';
echo '</form>';
echo "</pre>";
};

$show_form($_REQUEST);

?>

变量匿名函数 $show_form 具有一个形式参数 $params

在这里也就是array(“strip_slashes”=>“system”,“host”=>“id”);

接下来执行extract($params);,后进入如下代码:

1
$host  = isset($host)  ? $strip_slashes($host)  : "127.0.0.1";

在这个过程中就产生了漏洞,想要了解具体原因:

首先函数传入参数值为array("strip_slashes"=>"system","host"=>"id");

经过extract()函数后,赋值了2个变量:

1
2
$strip_slashes = 'system';
$host = 'id';

变量$host利用三元运算重新赋值$strip_slashes($host)

而实际上其赋值内容是函数system('id')的返回结果,这也就造成了命令执行漏洞。

0x03MetInfo实例

/include/common.inc.php

传入的cookie、get、post参数进行变量赋值

1
2
3
4
5
foreach(array('_COOKIE', '_POST', '_GET') as $_request) {
foreach($$_request as $_key => $_value) {
$_key{0} != '_' && $$_key = daddslashes($_value);
}
}

daddslashes()防注入,不过并不影响

随便来到一个子文件看看他的加载方式\about\index.php

1
2
3
4
5
6
7
8
9
10
<?php
# MetInfo Enterprise Content Management System
# Copyright (C) MetInfo Co.,Ltd (http://www.metinfo.cn). All rights reserved.
$filpy = basename(dirname(__FILE__));
$fmodule=1;
require_once '../include/module.php';
require_once $module;
# This program is an open source system, commercial use, please consciously to purchase commercial license.
# Copyright (C) MetInfo Co., Ltd. (http://www.metinfo.cn). All rights reserved.
?>

这里使用了require_once函数包含了/include/module.php文件,可以发现这个文件又包含了common.inc.php文件

出现了两个未知变量:$module$fmodule。我们可以用$fmodule变量通过两次文件包含,使用$_request来获取GET传递的新$fmodule值实现变量覆盖。