SSRF(Server-Side Request Forgery:服务器端请求伪造)
其形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能,但又没有对目标地址做严格过滤与限制。
导致攻击者可以传入任意的地址来让后端服务器对其发起请求,并返回对该目标地址请求的数据
数据流:攻击者----->服务器---->目标地址
根据后台使用的函数的不同,对应的影响和利用方法又有不一样
PHP中下面函数的使用不当会导致SSRF:
file_get_contents()
fsockopen()
curl_exec()
如果一定要通过后台服务器远程去对用户指定("或者预埋在前端的请求")的地址进行资源请求,则请做好目标地址的过滤。
在 PHP 中,cURL 是一个强大的库,用于与各种服务器进行通信(如 HTTP、HTTPS、FTP 等)。
初始化会话
$ch = curl_init(); // 创建一个新的 cURL
设置选项
curl_setopt($ch, CURLOPT_URL, "https://api.example.com/data"); // 设置请求的 URL
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 将响应保存为字符串而非直接输出
curl_setopt($ch, CURLOPT_TIMEOUT, 30); // 超时时间(秒)
执行请求
$response = curl_exec($ch); // 执行请求并获取响应
if ($response === false) {
$error = curl_error($ch); // 获取错误信息
echo "cURL Error: $error";
}
关闭会话
curl_close($ch);
常见场景
get请求
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://api.example.com/users?page=1");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
post请求(表单数据)
$postData = ['name' => 'John', 'email' => 'john@example.com'];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://api.example.com/users");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData)); // 发送表单数据
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
post请求(json数据)
$jsonData = json_encode(['name' => 'John', 'email' => 'john@example.com']);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://api.example.com/users");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonData);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Content-Length: ' . strlen($jsonData)
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
设置请求头
$headers = [
'Authorization: Bearer YOUR_TOKEN',
'User-Agent: MyApp/1.0'
];
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
处理HTTPS和证书
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 跳过 SSL 验证(不安全,仅测试用)
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); // 验证主机名
上传文件
$filePath = '/path/to/file.jpg';
$postData = [
'file' => new CURLFile($filePath, 'image/jpeg', 'file.jpg')
];
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
并行请求(curl_multi_*)
$urls = ["https://api1.com", "https://api2.com"];
$chs = [];
$mh = curl_multi_init();
foreach ($urls as $url) {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_multi_add_handle($mh, $ch);
$chs[] = $ch;
}
// 执行所有请求
$running = null;
do {
curl_multi_exec($mh, $running);
} while ($running > 0);
// 获取结果
foreach ($chs as $ch) {
$response = curl_multi_getcontent($ch);
curl_multi_remove_handle($mh, $ch);
curl_close($ch);
}
curl_multi_close($mh);
错误处理
if (curl_errno($ch)) {
echo 'Error: ' . curl_error($ch);
}
调试技巧
$info = curl_getinfo($ch);
echo "HTTP Code: " . $info['http_code'];
封装为函数
function curlRequest($url, $method = 'GET', $data = [], $headers = []) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
if ($method === 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, is_array($data) ? http_build_query($data) : $data);
}
$response = curl_exec($ch);
curl_close($ch);
return $response;
}
在 PHP 中,file_get_contents() 是一个非常简洁高效的函数,用于读取文件或 URL 内容到字符串中,适合处理本地文件、HTTP/HTTPS 请求、FTP 资源等场景。
基本语法
string file_get_contents(string $filename, bool $use_include_path = false, resource $context = null, int $offset = 0, int $maxlen = null)
- $filename:文件路径或 URL(支持 http://、ftp:// 等协议)。
- $use_include_path:是否在 include_path 中查找文件。
- $context:通过 stream_context_create() 创建上下文,用于设置请求头、超时等(如 HTTP 请求)。
- $offset:起始读取位置(字节)。
- $maxlen:最大读取长度(字节)。
常见用法示例
读取本地文件
$content = file_get_contents('/path/to/file.txt');
echo $content;
读取 URL(HTTP/HTTPS)
$url = "https://jsonplaceholder.typicode.com/posts/1";
$response = file_get_contents($url);
$data = json_decode($response, true);
print_r($data);
带 HTTP 头的 GET 请求
$context = stream_context_create([
'http' => [
'method' => 'GET',
'header' => [
'User-Agent: MyApp/1.0',
'Accept: application/json',
'Authorization: Bearer YOUR_TOKEN'
]
]
]);
$response = file_get_contents('https://api.example.com/data', false, $context);
POST 请求(发送表单或 JSON)
// 发送表单数据
$postData = http_build_query(['name' => 'John', 'age' => 30]);
$context = stream_context_create([
'http' => [
'method' => 'POST',
'header' => 'Content-Type: application/x-www-form-urlencoded',
'content' => $postData
]
]);
$response = file_get_contents('https://api.example.com/submit', false, $context);
// 发送 JSON 数据
$jsonData = json_encode(['name' => 'John']);
$context = stream_context_create([
'http' => [
'method' => 'POST',
'header' => 'Content-Type: application/json',
'content' => $jsonData
]
]);
$response = file_get_contents('https://api.example.com/json-endpoint', false, $context);
处理 FTP 文件
$content = file_get_contents('ftp://user:password@ftp.example.com/file.txt');
读取部分文件内容
// 从第 100 字节开始读取 50 字节
$partial = file_get_contents('largefile.txt', false, null, 100, 50);
错误处理
文件不存在或权限不足会返回 false 并触发 E_WARNING。
建议用 @ 抑制警告(不推荐)或检查返回值:
$content = @file_get_contents('nonexistent.txt');
if ($content === false) {
echo "读取失败!";
}
高级用法:流上下文(stream_context_create)
通过上下文可以配置代理、超时、SSL 验证等:
$context = stream_context_create([
'http' => [
'timeout' => 10, // 超时时间(秒)
'proxy' => 'tcp://proxy.example.com:8080',
'request_fulluri' => true
],
'ssl' => [
'verify_peer' => false, // 跳过 SSL 验证(测试用)
]
]);
$response = file_get_contents('https://example.com', false, $context);
封装函数
function httpRequest($url, $method = 'GET', $data = [], $headers = []) {
$options = [
'http' => [
'method' => $method,
'header' => implode("\r\n", $headers),
'timeout' => 10
]
];
if ($method === 'POST' && !empty($data)) {
$options['http']['content'] = is_array($data) ? http_build_query($data) : $data;
}
$context = stream_context_create($options);
return file_get_contents($url, false, $context);
}
// 使用示例
$response = httpRequest('https://api.example.com/data', 'GET', [], ['User-Agent: MyBot']);
注意事项
大文件:避免用 file_get_contents() 读取大文件(占用内存),改用 fopen() + fread()。
URL 包含参数:需用 urlencode() 处理参数:
$url = "https://api.com/search?q=" . urlencode("PHP 教程");
file_get_contents()与 cURL 的对比
| **特性** | `file_get_contents()` | `cURL` |
| -------- | --------------------- | --------------- |
| **简洁性** | 极简(一行代码) | 需多步配置 |
| **功能** | 基础 HTTP/FTP | 支持 cookie、上传、并发 |
| **性能** | 适合简单请求 | 适合复杂场景 |
| **错误处理** | 较弱(返回 false) | 详细错误信息 |