本文共 3856 字,大约阅读时间需要 12 分钟。
本地文件包含(LFI)在Web渗透测试中时不时的会遇到,通过加强对LFI渗透测试技术的研究,可以帮助渗透测试人员在未来的渗透测试过程中,识别和测试LFI漏洞。
目前我已经找到了一种将LFI转换为多个Web框架的远程文件包含(RFI)的方法
由于我一直在评估一些开源软件,所以就会发现代码执行的一个方法就是依赖于我选择的驻留在Web服务器上的JAR文件。当以特定方式配置时,Web应用程序将加载JAR文件并在文件中搜索同类文件。有意思的是,在Java类中,如下所示,你可以定义正在处理的类上执行的静态块:
public class LoadRunner {static {System.out.println("Load runner'ed");}public static void main(String[] args) {}}
编译和加载这个Java类,如下所示:
通过能够获取的代码来运行加载的JAR文件,并获得将Web服务器指向加载JAR的文件路径的能力,这样,我现在就必须找到一种方式让应用程序以某种方式引用JAR。
于是我查看了应用程序中的所有请求处理程序的上传文件以及其他网络服务,以寻找方法将文件转换成JAR。
文件描述符
当然,大多数框架都会将上传的文件放在服务器的磁盘上,但上传的路径是不确定的(通常使用GUID或其他随机标识符)。
在Linux中,当用一个进程打开一个文件时,它将在/proc/目录下打开一个指向该文件的文件描述符。因此,如果我有一个PID为1234的进程,并且该进程对磁盘上随机位置的某个文件有一个打开的文件句柄,那么该文件的上传的路径就是/ proc / 1234 / FD / *。这意味着,你不需要猜测GUID或其他随机值,而只需要得到HTTP请求的处理程序的PID和上传的文件的文件描述符。这样,用于引用上传文件的搜索空间就会大幅减少。不仅如此,如果你已经有LFI,那么在磁盘上经常会出现包含处理HTTP请求的Web服务器的PID编号的文件。
加载文件描述符
为了利用此功能, 我需要找到接受文件上传的处理程序请求,并在尝试LFI所有PID文件描述符时上传文件。
在我测试过的文件描述符在访问FILES目录时被延迟加载的框架中,并且使用Flask特别是即使在HTTP GET请求中也填充了这个FILES目录,以下列超简单的Flask应用程序为例:
# -*- coding: utf-8 -*-import osfrom flask import Flask, requestUPLOAD_FOLDER = "/tmp"app = Flask(__name__)app.config["UPLOAD_FOLDER"] = UPLOAD_FOLDER@app.route("/", methods=["GET"])def show_me_the_money(): x = request import code code.interact(local=locals())if __name__ == "__main__": app.run()
在这个应用程序中,我有一个单一的处理程序允许在基地址处对HTTP GET请求实施mount命令。让我在Ubuntu VM中运行这个应用程序,并将文件上传到其中,看看我可以找到什么。最好是能通过HTTP GET请求上传文件,对于以前没有进行过导入代码技巧的人来说,这是一个调试Python代码和库的好方法,你可以在code.interact调用中放入一个REPL。
以下是通过HTTP GET请求上传文件的简单脚本:
# -*- coding: utf-8 -*-import requestsresponse = requests.get( "http://127.0.0.1:5000/", files={ "upload_file": open("/tmp/hullo", "rb"), },)
而在/tmp/hullo的文件中,我看到很多包含“Hello World”字样的代码行:
然后,我运行服务器,上传文件,将其放在Flask请求处理程序的上下文中的REPL中:
使用请求处理程序的PID,我可以查看磁盘上的打开的文件描述符:
然后我返回到REPL并访问上传的文件:
现在文件已从Web服务器中访问过了,然后,我返回到/ proc目录,看看是否可以找到上传文件的内容(根据上述信息,应该是文件描述符5):
上图是延迟加载后的文件描述符,这就是我上传的文件!对于我正在评估的应用程序,我确认这种上传和引用文件的方法就是我想要运行的JAR!
我可以通过多次上传相同的文件来进一步减少文件上传路径的不确定性。例如,我修改了使用相同文件的九个副本提交文件上传的代码:
# -*- coding: utf-8 -*-import requestsresponse = requests.get( "http://127.0.0.1:5000/", files={ "upload_file": open("/tmp/hullo", "rb"), "upload_file2": open("/tmp/hullo", "rb"), "upload_file3": open("/tmp/hullo", "rb"), "upload_file4": open("/tmp/hullo", "rb"), "upload_file5": open("/tmp/hullo", "rb"), "upload_file6": open("/tmp/hullo", "rb"), "upload_file7": open("/tmp/hullo", "rb"), "upload_file8": open("/tmp/hullo", "rb"), },)
运行此脚本后,访问处理程序中的FILES目录,并检查请求处理程序PID中的fd目录的内容,我看到所有上传的文件都有打开的文件描述符:
可以看到上传的相同文件的九个副本都有着不同的文件描述符。
使用这种方法,你可以保证具有特定号码的文件描述符都指向你上传的文件。想象一下,假如你要提交100个文件的请求。
如何实施攻击利用
总而言之,这是一种大大减少为了攻击利用目的而引用上传文件所需的搜索工作量的方法,这样,在许多情况下也使LFI成为RFI。如果你要使用此方法进行攻击利用,请考虑以下问题:
1.当访问FILE目录时,我了解的框架(Django和Flask)会延迟加载文件引用。因此,你必须定位访问FILES目录的请求处理程序。一旦访问了FILES目录,文件描述符将在请求处理期间保持开放状态。
2.默认情况下,其他框架可能会填充这些文件描述符,而这正是我将要研究的内容。
3.当处理请求正文中的上传文件时,有些框架区分不出不同的请求方法,这意味着此攻击可能会在非幂等(non-idempotent )的HTTP Verbs中发生。
4.PID并不意味着随机化,无论你的目标是什么(Ubuntu上的Apache,Fedora上的Nginx等),如果你希望将其转化为漏洞,请创建一个本地设置,并查看与Web服务器和请求处理程序相关联的PID。一般来说,当你将服务安装到* nix时,它们将在重新启动时以类似的顺序启动。由于PID也按顺序分配,这意味着你可以大大减少PID搜索空间。
5.请求处理程序只需要访问要处理的所有上传文件的FILES目录,这就是说,如果处理程序中的功能期望上传的文件是PDF,以便请求处理程序的代码被执行,并且你想上传一个JAR,那么只需上传两个文件并且它们都将被赋予文件描述符。
6.尝试找到加载文件描述符的请求处理程序,为了我的评估的顺利进行,我发现有一个处理程序会逐行处理一个文件的全部内容,所以我在想要执行的JAR中上传了一个巨大的文件,。
7.请注意,如果你正在上传的文件较小,则可能只读入内存,并且不会打开任何文件描述符。当对Flask进行测试时,我发现1MB以下的文件会被直接加载到内存中,而超过1MB的文件则会放在磁盘上。因此,你可能需要相应地填充任何可供攻击利用的有效载荷。
**后续更新**
我想知道为什么查看的框架在加载文件描述符时是很缓慢的?当然,解析HTTP请求body中的全部内容一旦获取POST参数则获取上传的文件的内容是没有意义的。
果然,经过一些调查,对于Flask和Django来说,这并不是文件被缓慢地加载,而是直到访问请求正文的内容才被处理。 因此,使用此攻击,你可以定位访问请求body中存储的数据的任何请求处理程序。 一旦访问了包含在body内的数据,文件描述符将被填充。
访问Django中请求body的内容如下所示:
通过此访问填充的文件描述符如下所示:
访问Flask中请求body的内容如下所示:
通过此访问填充的文件描述符如下所示:
转载地址:http://tdxbx.baihongyu.com/