SSRF(服务器端请求伪造)漏洞
SSRF(服务器端请求伪造)漏洞(理论部分):
1、概述:
SSRF(Server-Side Request Forgery:服务器端请求伪造)
是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。
一般情况下,SSRF攻击的目标是从外网无法访问的内部系统。(因为它是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内网。也就是说可以利用一个网络请求的服务,当作跳板进行攻击)
SSRF和CSRF(Cross-site request forgery)其实是有点相关的,挺有意思,一个是服务器请求伪造,一个是跨站请求伪造。
2、漏洞成因:
SSRF 形成的原因往往是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制。
如:从指定URL地址获取网页文本内容,加载指定地址的图片,下载等。利用的就是服务端的请求伪造。ssrf是利用存在缺陷的web应用作为代理攻击远程和本地的服务器。
(举个例子,博客里的图片引用一般都是通过url的方式来进行引用的,这其实就是请求了别的服务器上存储的图片数据,如果没有对这个数据进行过滤的话,可能就会被造成SSRF攻击)
3、常见漏洞点:
博客里面用来读取图片的这一段也算。
4、实现攻击方式:
- 可以对外网、服务器所在内网、本地进行端口扫描,获取一些服务的banner 信息
- 攻击运行在内网或本地的应用程序
- 对内网 WEB 应用进行指纹识别,通过访问默认文件实现(如:readme文件)
- 攻击内外网的 web 应用,主要是使用 GET 参数就可以实现的攻击(如:Struts2,sqli)
- 下载内网资源(如:利用
file
协议读取本地文件等) - 进行跳板
- 无视cdn
- 利用Redis未授权访问,HTTP CRLF注入实现getshell
5、SSRF漏洞相关函数和协议:
1、file_get_contents()
函数作用:
将整个文件读入道一个字符串中:
(注意,不只是可以用来读取本地文件,同样也可以用来访问url表示的外部文件,否则就不会出现SSRF了)
file_get_content
函数从用户指定的url获取内容,然后指定一个文件名进行保存,并展示给用户。file_put_content函数把一个字符串写入文件中。
需要将php.ini中的allow_url_fopen设为on状态。预设是启动的。
eg:
|
2、fsockopen()
使用例:
这个函数的作用就是实现用户对于指定url数据的获取,使用套接字与服务器建立tcp
连接,传输数据,变量host为主机名,port为端口,errstr表示错误信息将以字符串的信息返回,30是时限。
|
可以和Python中的socket模块里的,socket.socket函数类比一下。用于建立传输链接。
3、curl_exec()
函数用于执行指定的curl会话。
**什么是cURL?**(https://baike.baidu.com/item/curl/10098606?fr=aladdin)
cURL是一个利用URL语法在命令行下工作的文件传输工具,1997年首次发行。
cURL支持的通信协议有:
FTP、FTPS、HTTP、HTTPS、TFTP、SFTP、Gopher、SCP、Telnet、DICT、FILE、LDAP、LDAPS、IMAP、POP3、SMTP和RTSP。
此外,cURL还支持SSL认证,HTTP POST、HTTP PUT、FTP上传, HTTP form based upload、proxies、HTTP/2、cookies、用户名+密码认证(包含了多种加密方式。)
常见的使用方法:
获得页面
使用命令:curl http://curl.haxx.se
这是最简单的使用方法。用这个命令获得了http://curl.haxx.se指向的页面,同样,如果这里的URL指向的是一个文件或者一幅图都可以直接下载到本地。如果下载的是HTML文档,那么缺省的将只显示文件头部,即HTML文档的header。要全部显示,请加参数 -i,要只显示头部,用参数 -I。任何时候,可以使用 -v 命令看curl是怎样工作的,它向服务器发送的所有命令都会显示出来。为了断点续传,可以使用-r参数来指定传输范围。
获取表单
可以使用各种不同的参数来进行表单的传值,可以使用POST,也可以使用GET,同时也可以使用PUT或者是别的方式进行提交。
有关认证
可以处理各种情况的认证界面,例如提交用户名和密码。
curl -U proxyuser:proxypassword http://curl.haxx. se引用
有的网络资源访问的时候必须经过另外一个网络地址跳转过去,也就是使用了referer(可以参考一下http请求中的)
指定客户端
有的网络资源首先需要判断用户使用的是什么浏览器,符合了标准才能够下载或者浏览,可以使用curl伪装成为需要的浏览器。
Cookie
Cookie是服务器经常使用的一种记忆客户信息的方法,如果cookie被记录在了文件中,则可以根据旧的cookie写出性的,发送到网站。
加密HTTP
可以直接通过cURL访问网站。
http认证
如果是采用证书认证的http地址,证书在本地,则可以使用本地的证书认证来进行访问。
PHP中相关函数在4.0.2版本后被引入,相关函数:
注意:
1.一般情况下PHP不会开启fopen的gopher wrapper |
4、fopen()
用于打开一个文件或者是一个url。
5、readfile()
输出一个文件的内容。
相关协议:
(1)file
: 在有回显的情况下,利用 file 协议可以读取任意内容
(2)dict
:泄露安装软件版本信息,查看端口,操作内网redis服务等
(3)gopher
:gopher支持发出GET、POST请求:可以先截获get请求包和post请求包,再构造成符合gopher协议的请求。gopher协议是ssrf利用中一个最强大的协议(俗称万能协议)。可用于反弹shell
(4)http/s
:探测内网主机存活
(5)FastCGI
:FastCGI协议
(6)Redis
:REPS协议
协议说明:
Gopher协议:
Gopher协议是Internet上一个非常有名的信息查找系统,他将Internet上的文件组织成某种索引,很方便的将用户从Internet的一处带到另一处。在WWW出现之前,GOpher是Internet上最主要的信息检索工具,Gopher站点也是最主要的站点,使用tcp70端口。但是在WWW出现之后,Gopher失去了昔日的辉煌。现在它基本果实,人们很少使用它。
Gopher协议支持发送GET,POST请求,可以先结果GET请求包,和POST请求包,再构成复合Gopher协议的请求。
**gopher会将后面的数据部分发送给相应的端口,这些数据可以是字符串,也可以是其他的数据请求包,比如GET,POST请求,redis,mysql未授权访问等,同时数据部分必须要进行url编码,这样gopher协议才能正确解析。
支持gopher协议的有 和 **
Gopher协议格式:
URL:gopher://<host>:<port>/<gopher-path>_后接TCP数据流 |
默认使用端口:70
如果发起POST请求,需要使用%0d%0a
作为换行符,如果多个参数,需要将链接符号&
也进行URL编码。
可以用于攻击内网的Redis,Mysql,FastCGI,FTP等,也可以发送GET,POST请求,拓宽了SSRF的攻击面。
(例如可以使用file协议,进行本地文件读取)
FastCGI协议:
早期的Web服务器,只能响应浏览器发来的HTTP静态资源的请求,并将存储在服务器中的静态资源返回给浏览器。随着Web技术的发展,逐渐出现了动态技术,但是Web服务器并不能够直接运行动态脚本,为了解决Web服务器与外部应用程序(CGI程序)之间数据胡同,于是出现了CGI(Common Gateway Interface)通用网关接口。简单理解,可以认为CGI是Web服务器和运行在其上的应用程序进行“交流”的一种约定。
当遇到动态脚本请求的时候,Web服务器的主进程就会Fork创建出一个新的进程来启动CGI程序,运行C、PHP等脚本程序的时候,也就是将动态脚本交给CGI程序进行处理。
启动CGI程序需要一个过程,如读取配置文件、加载扩展等。当CGI程序启动后会去解析动态脚本,然后将结果返回给Web服务器,最后由Web服务器将结果返回给客户端,之前Fork出来的进程也随之关闭。这样,每次用户请求动态脚本,Web服务器都要重新Fork创建一个新进程去启动CGI程序,由CGI程序来处理动态脚本,处理完成后进程随之关闭,其效率是非常低下的。
而对于Mod CGI,Web服务器可以内置Perl解释器或PHP解释器。也就是说将这些解释器做成模块的方式,Web服务器会在启动的时候就启动这些解释器。当有新的动态请求进来时,Web服务器就是自己解析这些动态脚本,省得重新Fork一个进程,效率提高了。
使用CGI可以解决Web服务器与PHP解释器的通信问题,但是Web服务器有一个问题,来回启动CGI程序然后再杀掉,是非常浪费资源的,于是出现了优化版本,FastCGI(Fast Common Gateway Interface)快速通用网关接口。
Fast-CGI每次处理完请求后,不会kill掉这个进程,而是保留这个进程,从而使服务器可以同时处理更多的网页请求。这样就会大大的提高效率。
动态网页访问:
当访问动态网站的主页时,根据容器的配置文件,它知道这个页面不是静态页面,Web容器就会去找PHP解析器来进行处理(这里以Apache为例),它会把这个请求进行简单的处理,然后交给PHP解释器。
当Apache收到用户对 index.php 的请求后,如果使用的是CGI,会启动对应的 CGI 程序,对应在这里就是PHP的解析器。接下来PHP解析器会解析php.ini文件,初始化执行环境,然后处理请求,再以CGI规定的格式返回处理后的结果,退出进程,Web Server 再把结果返回给浏览器。这就是一个完整的动态PHP Web访问流程。
这里说的是使用CGI,而FastCGI就相当于高性能的CGI,与CGI不同的是它像一个常驻的CGI,在启动后会一直运行着,不需要每次处理数据时都启动一次, 所以这里引出下面这句概念,FastCGI是语言无关的、可伸缩架构的CGI开放扩展,其主要行为是将CGI解释器进程保持在内存中,并因此获得较高的性能 。
协议分析:
FastCGI其实是一个通信协议,和HTTP协议一样,都是进行数据交换的一个通道。
HTTP协议是 浏览器和服务器中间件 进行数据交换的协议,浏览器将HTTP头和HTTP体用某个规则组装成数据包,以TCP的方式发送到服务器中间件,服务器中间件按照规则将数据包解码,并按要求拿到用户需要的数据,再以HTTP协议的规则打包返回给服务器。
类比 HTTP 协议来说,FastCGI
协议则是 服务器中间件和某个语言后端 进行数据交换的协议,同时直接使用二进制传递数据。Fastcgi 协议由多个 Record
组成,Record 也有 Header 和 Body 一说,服务器中间件将这二者按照 Fastcgi 的规则封装好发送给语言后端,语言后端解码以后拿到具体数据,进行指定操作,并将结果再按照 Fastcgi 协议封装好后返回给服务器中间件。
FastCGI Record的格式:
头部,身体:
Record的头固定8个字节,body的大小由头部中contentLenght指定(两字节),结构如下
typedef struct { |
头由8个 uchar 类型的变量组成,每个变量一个字节。其中,requestId
占两个字节,一个唯一的标志id,以避免多个请求之间的影响;contentLength
占两个字节,表示 Body 的大小。可见,一个 Fastcgi Record 结构最大支持的 Body 大小是2^16
,也就是 65536 字节(两个字节十六位,一位表示1,0两种情况)。
字段选项:
刚才我们介绍了 Fastcgi 协议中Record部分中各个结构的含义,其中第二个字节为 type
,我们将对其进行详细讲解。
type
就是指定该 Record 的作用。因为 Fastcgi 中一个 Record 的大小是有限的,作用也是单一的,所以我们需要在一个TCP流里传输多个 Record,通过 type
来标志每个 Record 的作用,并用 requestId
来标识同一次请求的id。也就是说,每次请求,会有多个 Record,他们的 requestId
是相同的。
type字段选项:
enum FCGI_Type { |
看了这个表格就很清楚了,服务器中间件和后端语言通信,第一个数据包就是 type
为1的 Record,后续互相交流,发送 type
为4、5、6、7的 Record,结束时发送 type
为2、3的 Record。
当后端语言接收到一个 type
为4的 Record 后,就会把这个 Record 的 Body 按照对应的结构解析成 key-value 对,这就是环境变量。环境变量的结构如下:
typedef struct { |
这其实是4个结构,至于用哪个结构,有如下规则:
1、key、value均小于128字节,用FCGI_NameValuePair11
2、key大于128字节,value小于128字节,用FCGI_NameValuePair41
3、key小于128字节,value大于128字节,用FCGI_NameValuePair14
4、key、value均大于128字节,用FCGI_NameValuePair44
PHP-FPM:
官方对PHP-FPM的解释是 FastCGI 进程管理器,用于替换 PHP FastCGI 的大部分附加功能,对于高负载网站是非常有用的。PHP-FPM 默认监听的端口是 9000 端口。
也就是说 PHP-FPM 是 FastCGI 的一个具体实现,并且提供了进程管理的功能,在其中的进程中,包含了 master 和 worker 进程,这个在后面我们进行环境搭建的时候可以通过命令查看。其中master 进程负责与 Web 服务器中间件进行通信,接收服务器中间按照 FastCGI 的规则打包好的用户请求,再将请求转发给 worker 进程进行处理。worker 进程主要负责后端动态执行 PHP 代码,处理完成后,将处理结果返回给 Web 服务器,再由 Web 服务器将结果发送给客户端。
简单来说,这个就是一个基于PHP语言实现的FastCGI进程管理器,因为FastCGI只是一个协议规范,需要每个语言具体去实现,PHP-FPM就是PHP版本的FastCGI协议的具体实现。使用这个,就是实现PHP脚本与Web服务器之间的同行,同时也是一个PHP SAPI,从而构建起了PHP解释器与Web服务器之间的桥梁。(或者可以理解为是PHP的环境,同时也是传输数据用的)
例如当用户对http://127.0.0.1/index.php?a=1&b=2
进行访问的时候,根据Web服务器的目录,Web服务器的中间件,将相关的请求进行变换,修改为key-value对(键值对)
eg:
{ 'GATEWAY_INTERFACE': 'FastCGI/1.0', 'REQUEST_METHOD': 'GET', 'SCRIPT_FILENAME': '/var/www/html/index.php', 'SCRIPT_NAME': '/index.php', 'QUERY_STRING': '?a=1&b=2', 'REQUEST_URI': '/index.php?a=1&b=2', 'DOCUMENT_ROOT': '/var/www/html', 'SERVER_SOFTWARE': 'php/fcgiclient', 'REMOTE_ADDR': '127.0.0.1', 'REMOTE_PORT': '12345', 'SERVER_ADDR': '127.0.0.1', 'SERVER_PORT': '80', 'SERVER_NAME': "localhost", 'SERVER_PROTOCOL': 'HTTP/1.1'} |
总的来说,将具体请求更改为键值对,进行传输,可以看见,其中包含了网关,请求方式,Web服务器根目录,还有服务器名字等具体信息。
这个数组其实就是PHP中的预定义数组,__SERVER的一部分,也就是相当于是PHP中的环境变量,既可以对__SERVER数组进行填充,同时也可以对FPM(环境)告知需要执行的PHP文件。
当PHP-FPM拿到FastCGI的数据包的时候,进行解析,得到上述的环境变量,然后执行SCRIPT_FILENAME
的值指向的PHP文件
(在这个部分)
如果可以进行更改,那就可以实现未授权访问。
PHP-FPM任意代码执行:
Web服务器中间件,会将用户请求设置为环境变量,并且会通过字段SCRIPT_FILENAME
来对中间件执行的PHP文件进行设置。
理论上是只能对已经存在的文件进行执行,而不能进行任意代码执行。
但是因为在PHP 5.3.9以上的版本中,PHP多了一个选项security.limit_extensions
安全选项。
导致PHP-FPM可以进行控制执行的文件只有php,php3,php4,php5,php7这样的文件。
但是可以利用PHP中存在的配置:
auto_prepend_file
:在执行目标文件之前先对auto_prepend_file中指定的文件进行包含。
auto_append_file
:在执行了目标文件之后,对auto_append_file指定的文件进行包含。
也就是说,将
auto_prepend_file
选项设为php://input
(将POST中的内容作为PHP代码执行),那么在执行任何PHP文件之前都要对POST的内容进行一次文件执行。也就是,将HTTP请求的body部分进行改写,就能够执行了。
不过使用php://input使用也需要启用相关的配置。
如何设置环境变量的值:
对于auto_prepend_file这类的环境变量,我们要想设置成php://input,就需要远程设置。
在PHP-FPM中有两个环境变量,PHP_VALUE
和PHP_ADMIN_VALUE
,这两个环境变量可以用来设置PHP配置项。
其中:
PHP_VALUE
:用来设置模式为:PHP_INI_USER
和PHP_INI_ALL
的选项。
PHP_ADMIN_VALUE
:可以用来设置所有选项
通过这个方式对环境变量进行传入:
{ 'GATEWAY_INTERFACE': 'FastCGI/1.0', 'REQUEST_METHOD': 'GET', 'SCRIPT_FILENAME': '/var/www/html/index.php', 'SCRIPT_NAME': '/index.php', 'QUERY_STRING': '?a=1&b=2', 'REQUEST_URI': '/index.php?a=1&b=2', 'DOCUMENT_ROOT': '/var/www/html', 'SERVER_SOFTWARE': 'php/fcgiclient', 'REMOTE_ADDR': '127.0.0.1', 'REMOTE_PORT': '12345', 'SERVER_ADDR': '127.0.0.1', 'SERVER_PORT': '80', 'SERVER_NAME': "localhost", 'SERVER_PROTOCOL': 'HTTP/1.1' 'PHP_VALUE': 'auto_prepend_file = php://input', 'PHP_ADMIN_VALUE': 'allow_url_include = On'} |
通过对传入的环境变量进行修改,可以将PHP-FPM中的环境变量进行设置。
常见的方式就是直接使用PHP_ADMIN_VALUE
进行设置,然后就能通过php://input伪协议来进行任意代码执行。同时也可以根据需要,开启相应的远程文件包含设置。
PHP-FPM未授权访问漏洞:
因为可以通过PHP_VALUE
和PHP_ADMIN_VALUE
这两个环境变量对PHP配置进行设置,所以攻击者可以通过PHP-FPM
执行任意代码,实现代码执行漏洞。
初次之外,因为PHP-FPM和Web服务器中间件是通过网络进行沟通的,所以会有越来越多的集群将PHP-FPM直接绑定在公网上,所有人都可以对起进行访问,也就是说,任何人都可以伪装成Web服务器中间件来让PHP-FPM执行我们想要执行的恶意代码。这就会造成PHP-FPM的未授权访问漏洞。
(简单来说,就是可以通过伪装成Web服务器的方式,来对PHP-FPM发送数据,以执行需要的代码,或者是访问需要的网页,前提是PHP-FPM这个组件在公网,可以直接进行访问)
SSRF中对于FPM/FastCGI的攻击:
可以看见的是,因为上述攻击中,要求我们可以从公网访问到PHP-FPM,如果不是公网,而是内网访问的话,就不能直接发包。要进行攻击的话,就需要从内网进行攻击,例如使用SSRF来进行反打。
只要可以调用相关的函数,就可以通过url进行payload的传输,然后就能进行攻击。
这篇总结相当好
Redis协议(RESP协议):
应用系统和Redis之间通过Redis协议(RESP)进行交互。
请求响应模式
Redis协议使用TCP,也就是客户端和Redis之间保持双工的连接。
也就是说,Redis协议就是专门用于客户端和Redis之间交互的协议。那么什么是Redis呢?
Redis:
REmote DIctionary Server(Redis) 是一个由 Salvatore Sanfilippo 写的 key-value 存储系统,是跨平台的非关系型数据库。
Redis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存、分布式、可选持久性的键值对(Key-Value)存储数据库,并提供多种语言的 API。
Redis 通常被称为数据结构服务器,因为值(value)可以是字符串(String)、哈希(Hash)、列表(list)、集合(sets)和有序集合(sorted sets)等类型。
Redis是完全开源的,遵守BSD协议,是一个高性能的key-value数据库,相较于其他的key-value缓存产品,主要有三个特点:
- Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
- Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
- Redis支持数据的备份,即master-slave模式的数据备份。
Redis的优势:
- 性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
- 丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
- 原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
- 丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性。
Redis和其他的key-value存储有什么不同:
- Redis有着更为复杂的数据结构并且提供对他们的原子性操作,这是一个不同于其他数据库的进化路径。Redis的数据类型都是基于基本数据结构的同时对程序员透明,无需进行额外的抽象。
- Redis运行在内存中但是可以持久化到磁盘,所以在对不同数据集进行高速读写时需要权衡内存,因为数据量不能大于硬件内存。在内存数据库方面的另一个优点是,相比在磁盘上相同的复杂的数据结构,在内存中操作起来非常简单,这样Redis可以做很多内部复杂性很强的事情。同时,在磁盘格式方面他们是紧凑的以追加的方式产生的,因为他们并不需要进行随机访问。
常见的漏洞使用方式:
Redis未授权访问:
Redis 默认情况下,会绑定在 0.0.0.0:6379,如果没有进行采用相关的策略,比如添加防火墙规则避免其他非信任来源 ip访问等,这样将会将 Redis服务暴露到公网上。
如果在没有设置密码认证(一般为空)的情况下,会导致任意用户在可以访问目标服务器的情况下未授权访问 Redis 以及读取Redis 的数据。
也就是当Redis直接被绑定在公网的时候,可以直接伪造请求,然后对Redis
直接进行访问,执行相关的指令。
漏洞危害:
(1)攻击者无需认证访问到内部数据,可能导致敏感信息泄露,也可以恶意执行flushall来清空所有数据
(2)攻击者可通过EVAL执行lua代码,或通过数据备份功能往磁盘写入后门文件
(3)如果Redis以root身份运行,黑客可以给root账户写入SSH公钥文件,直接通过SSH登录受害服务器
Redis常见命令:
那么相关的使用方式,就是当Redis
没有被暴露在公网的情况下,使用SSRF
从内网进行攻击反打。
SSRF利用Redis协议方式:
redis客户端与服务端通信,使用RESP(REdis Serialization Protocal,redis序列化协议)协议通信,该协议是专门为redis设计的通信协议,但也可以用于其它客户端-服务器通信的场景。
RESP可以用于序列化不同的数据类型,如:整型、字符串、数组…并且为错误提供专门的类型;客户端发送请求时,以字符串数组的作为待执行命令的参数。redis服务器根据不同的命令返回不同的数据类型。
RESP协议支持五种数据类型:
1、简单字符串(Simple Strings)
2、错误数据(Errors)
3、整数(Integers)
4、批量字符串(Bulk Strings)
5、数组(Arrays)当客户端请求服务器的时候,会以批量数据类型的数组请求封装。当服务端发送响应给客户端的时候,根据命令实现的不同,返回响应的数据类型。
不同的数据类型是根据请求/响应报文的第一个字节进行区分的:
- 简单字符串以+开头
- 错误数据以-开头
- 整数以:开头
- 批量字符串以$开头
- 数组以*开头
RESP协议发不同部分,使用
\r\n
(CRLF)进行分隔。
类似上面的数据类型,因为是建立在TCP层,因此可以使用wireshark进行抓包分析。