SSRF

介绍


SSRF(Server-Side Request Forgery),服务端请求伪造,是通过构造形成由服务器端发起请求的一个漏洞,可以以此访问外网无法访问的内网系统。

一般SSRF会出现在以下场景中:

  • 能够对外发起网络请求的场景。
  • 从远程服务器请求资源的场景(Upload from URL,Import & Export RSS Feed)。
  • 数据库内置功能(Oracle、MongoDB、MSSQL、Postgres、CouchDB)。
  • Webmail收取其他邮箱邮件(POP3、IMAP、SMTP)。
  • 文件处理、编码处理、属性信息处理(ffmpeg、ImageMagic、DOCX、PDF、XML)。

常见的后端实现


file_get_contents()

1
2
3
4
5
6
7
8
9
10
<?php
if (isset($_POST['url'])) {
$content = file_get_contents($_POST['url']);
$filename ='./images/'.rand().';img1.jpg';
file_put_contents($filename, $content);
echo $_POST['url'];
$img = "<img src=\"".$filename."\"/>";
}
echo $img;
?>

这段代码从用户指定的url中获取图片,然后写入一个路径随机的文件中,最后显示该图片。


rand()函数返回伪随机整数。

语法:rand(min,max).


file_put_contents()函数将字符串、数组或数据流写入文件。

用法:

  1. 如果设置了 FILE_USE_INCLUDE_PATH,那么将检查 filename 副本的内置路径
  2. 如果文件不存在,将创建一个文件
  3. 打开文件
  4. 如果设置了 LOCK_EX,那么将锁定文件
  5. 如果设置了 FILE_APPEND,那么将移至文件末尾。否则,将会清除文件的内容
  6. 向文件中写入数据
  7. 关闭文件并对所有文件解锁

fsockopen()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php 
function GetFile($host,$port,$link) {
$fp = fsockopen($host, intval($port), $errno, $errstr, 30);
if (!$fp) {
echo "$errstr (error number $errno) \n";
} else {
$out = "GET $link HTTP/1.1\r\n";
$out .= "Host: $host\r\n";
$out .= "Connection: Close\r\n\r\n";
$out .= "\r\n";
fwrite($fp, $out);
$contents='';
while (!feof($fp)) {
$contents.= fgets($fp, 1024);
}
fclose($fp);
return $contents;
}
}
?>

从fsockopen开始建立与指定host的连接,再通过fwrite()$fp写入$out内容($out即为请求头),向host发送请求头,使用$contents储存响应头和响应体,最后返回$contents内容。

但是因为host是用户指定,所以也存在SSRF漏洞。

fsockopenfile socket open

套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合。

简单理解,Socket给每个应用程序提供了一个唯一的入口(ip:端口)便于发送和接收数据。

fsockopen()函数可以打开Internet或者Unix套接字连接,连接到指定hostname资源。

用法:

1
2
3
4
5
6
7
fsockopen(
string $hostname,
int $port = -1,
int &$error_code = null,
string &$error_message = null,
?float $timeout = null
): resource|false

然后就可以把由fsockopen返回的句柄当成文件进行读写,写入即发送请求。

curl_exec()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php 
if (isset($_POST['url'])) {
$link = $_POST['url'];
$curlobj = curl_init();
curl_setopt($curlobj, CURLOPT_POST, 0);
curl_setopt($curlobj,CURLOPT_URL,$link);
curl_setopt($curlobj, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($curlobj);
curl_close($curlobj);

$filename = './curled/'.rand().'.txt';
file_put_contents($filename, $result);
echo $result;
}
?>

这段代码使用curl获取用户指定url的数据,将数据放到伪随机文件名的文件中。

关于curl

curl(Client URL)是一个强大的命令行工具,用于在 Linux/Unix 系统中传输数据,支持HTTP、HTTPS、FTP、SFTP等协议。

语法如下:

1
curl [options] [url]
  • options可选的多种参数,包括:

    参数 说明 示例
    -X 指定HTTP方法 curl -X POST http://…
    -d 发送POST数据 curl -d “key=a” http://…
    -G 将-d数据作为GET参数发送 curl -G -d “key=a” http://…
    -H 添加请求头 curl -H “Content-Type=application/json” http://…
    -o 将输出保存到文件 curl -o output.txt http://…
    -O 使用远程文件名保存
    -L 跟随重定向
    -I 只获取头部信息
    -c 保存服务器返回的Cookie curl -c cookies.txt -b cookies.txt http://…
    -b 发送存储的Cookie curl -c cookies.txt -b cookies.txt http://…
    -F 用于multipart/form-data类型的表单提交 curl -F “file=@localfile.txt” http://…
    -v 显示请求
    -i 只显示响应头

JSON数据的请求:

1
2
3
curl -X POST -H "Content-Type: application/json" \
-d '{"name":"John","age":30}' \
https://api.example.com/users

测试网站响应时间:

1
curl -o /dev/null -s -w "Connect: %{time_connect} TTFB: %{time_starttransfer} Total: %{time_total}\n" https://example.com

使用代理服务器:

1
curl -x http://proxy.example.com:8080 https://target.com

php中的cURL:

需要引入libcurl包。

函数 描述 用法
curl_init() 初始化一个curl会话
curl_setopt() 设置一个curl传输选项 bool curl_setopt ( resource $ch , int $option , mixed $value )
curl_exec() 执行一个curl会话
  • curl_setopt()$option说明

    参数 描述
    CURLOPT_POST 启用时发送一个常规的POST请求,类型为application/www-form-urlencoded
    CURLOPT_URL 设置需要获取的URL,也可以在curl_init()中设置。
    CURLOPT_RETURNTRANSFER curl_exec()返回的信息以文件流的形式而不是纯文本的形式输出。
    对应值为true,curl_exec()返回的信息才可以用变量接收。
  • curl_setopt()$value说明:

    0为false,1为true。false代表关闭功能,true代表打开功能。

    有时候需要对功能进行显式设置,因为功能的默认值可能不合预期。

参考资料