PHP数据传入点分析

截屏2020-06-06 上午8.28.40

0x01 globals输入

用户操作一个web站点通常需要传入变量,变量“污染”了代码,造成注入问题。用户的输入的数据大致可分为有结构(数组)和无结构两种(字符串,NULL)。

GPC输入

  • 最常见的方式, 用户输入的内容直接赋给一个变量:
1
2
3
4
5
6
7
8
9
// 取值赋给变量
$var = $_GET['value'];

// 名值皆赋给变量
foreach($_GET as $k=>$v)
{
$name = $k;
$var = $v;
}
  • 通过处理数组的方法污染变量,如extract:
1
2
//extract ( array &$array [, int $flags = EXTR_OVERWRITE [, string $prefix = NULL ]] ) : int
extract($_GET);
1

  • import_request_variables

php.net原文中说明,将 GET/POST/Cookie 变量导入到全局作用域中。如果你禁止了 register_globals,但又想用到一些全局变量,那么此函数就很有用。(register_globals是PHP的一个特性。)

1
2
3
4
5
6
//?xss=666 => 666
<?php
$xss = 123;
import_request_variables('G');
echo $xss;
?>

但是这个特性已经在php5.4.0移除。

  • $_REQUEST

​ $_REQUEST中也可包含GPC的内容,在注册后,数组是独立的处理起来互不影响,如果过滤其中一个而没有过滤另一个就有可能造成绕过:

屏幕快照 2019-11-05 下午3.59.00

​ (POEM-RIPS SECURITY CALENDAR 2017 )

0x02 SERVER输入

如通过$_SERVER['REQUEST_URI']$_SERVER['QUERY_STRING']。真实案例(贷齐乐系统最新版SQL注入(无需登录绕过WAF可union select跨表查询))

  • parse_str

    此函数解析字符串中的QUERY STRING 如果没有设置第二个变量result则可能导致变量覆盖。

    函数原型:

    1
    parse_str ( string $encoded_string [, array &$result ] ) : void
    1
    2
    3
    4
    5
    //?xss=666 => 666
    <?php
    $xss= 123;
    parse_str($_SERVER['QUERY_STRING'])
    echo $xss;
  • mb_parse_str

    与parse_str类似,解析 GET/POST/COOKIE 数据并设置全局变量

    1
    2
    3
    4
    5
    6
    //?xss=666 => 666
    <?php
    $xss = 123;
    mb_parse_str($_SERVER['QUERY_STRING'] );
    echo $xss;
    ?>

0x03 其他

  • getenv()得到变量
  • $HTTP_RAW_POST_DATA与PHP输入、输出流

案例分析

让我们看一下,脚本实际如何在用户请求中提取信息。

thinkPHP5

获取某个get变量

1
Reuqest::instance()->get('name');

定位到get方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 设置获取GET参数
* @access public
* @param mixed $name 变量名
* @param mixed $default 默认值
* @param string|array $filter 过滤方法
* @return mixed
*/
public function get($name = '', $default = null, $filter = '')
{
if (empty($this->get)) {
$this->get = $_GET;
}

if (is_array($name)) {
$this->param = [];
return $this->get = array_merge($this->get, $name);
}

return $this->input($this->get, $name, $default, $filter);
}

我们想从请求中拿$_GET['name']的值, \(\_GET的值被统一收集到了\)this->get这个field中来。 在return时,从$this->input中取值。

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
/**
* 获取变量 支持过滤和默认值
* @access public
* @param array $data 数据源
* @param string|false $name 字段名
* @param mixed $default 默认值
* @param string|array $filter 过滤函数
* @return mixed
*/
public function input($data = [], $name = '', $default = null, $filter = '')
{
if (false === $name) {
// 获取原始数据
return $data;
}

$name = (string) $name;
if ('' != $name) {
// 解析name
if (strpos($name, '/')) {
list($name, $type) = explode('/', $name);
} else {
$type = 's';
}
// 按.拆分成多维数组进行判断
foreach (explode('.', $name) as $val) {
if (isset($data[$val])) {
$data = $data[$val];
} else {
// 无输入数据,返回默认值
return $default;
}
}
if (is_object($data)) {
return $data;
}
}

// 解析过滤器
$filter = $this->getFilter($filter, $default);

if (is_array($data)) {
array_walk_recursive($data, [$this, 'filterValue'], $filter);
reset($data);
} else {
$this->filterValue($data, $name, $filter);
}

if (isset($type) && $data !== $default) {
// 强制类型转换
$this->typeCast($data, $type);
}

return $data;
}

一个比较的输入封装, 有默认输出、数组处理, 过滤选择、类型转化等部分。(这里的$filter可控的话就是那个漏洞了)