JumpServer 远程代码执行 CVE-2024-29201&&CVE-2024-29202 漏洞分析
1038786491215925 发表于 江苏 漏洞分析 5844浏览 · 2024-04-03 12:54

简单复现

先简单做下复现,同https://wh0am1i.com/2024/03/30/JumpServer-CVE-2024-29201-CVE-2024-29202/ 中一样配置完成环境并进行传值后,命令成功被执行
在向JumpServer添加playbook,向playbook传值的过程中经过了以下几个过程

  1. 新建Playbook
  2. 向Playbook中添加main.yml
  3. 创建作业
  4. 执行作业

    可以看到,这里经过了以下几个步骤:
  5. 生成一个Playbook ID
  6. 以PATCH方式向<playbook id="">/file 传值</playbook>
  7. 将资产ID与Playbook相绑定,获取一个job id,如果这里资产ID错误,则无法绑定
  8. 执行这个job,并获取一个task id

数据流分析

这个时候来到服务器端进行分析,这里可以看到JumpServer是基于docker进行的,一共有10个容器,如果你是没有进行任何修改的JumpServer服务器,这里可以看到他向外部映射的端口只有80, 2222, 33061, 33062, 63790这四个端口

其余都是通过Docker网桥互通的,并不对外映射,这里可以看到一共存在10个veth分别对应10个应用容器。

我们在整个复现过程中,只向80端口,也就是jms_web传值便完成了整个命令执行,而且根据复现结果,其命令执行却是在jms_celery容器中。
这个时候对此处的br-ab8ac2f1cea3进行tcpdump抓包监听,并重复整个复现过程,观察其中数据包传输流向,究竟是哪个数据包发送给了jms_celery并导致了其远程命令执行。
使用docker inspect确认各个关键容器的IP地址如下:
jms_web:192.168.250.11
jms_core:192.168.250.4
jms_celery:192.168.250.3
jms_redis:192.168.250.10
jms_mysql:192.168.250.5
我们先看jms_web和jms_core之间的通信,这里优先考虑由jms_web向jms_core传值,因为这里调用的是80端口的web服务器进行复现。所以使用ip.addr == 192.168.250.11 && ip.addr == 192.168.250.4进行流量过滤,可以发现这里jms_web将从创建Playbook开始的所有报文都转发给了jms_core,初步判断这里jms_web是反代理的jms_core中8080端口的/api/v1/*中的部分内容。

同时可以发现这里也传输了调用jms_celery时的结果返回

这个时候我们进入jms_core容器查看payload是否在本地落地,这里直接搜索Playbook的ID,可以看到这个文件在此处落地了。

root@jms_core:/opt/jumpserver# find ./ -name fcbdb397-c895-491e-8253-9e9e5f48f020
./data/ops/playbook/fcbdb397-c895-491e-8253-9e9e5f48f020
root@jms_core:/opt/jumpserver# cat ./data/ops/playbook/fcbdb397-c895-491e-8253-9e9e5f48f020/main.yml
[{
     "name": "RCE playbook",
     "hosts": "all",
     "tasks": [
       {
         "name": "this runs in Celery container",
         "shell": "id > /tmp/pwnd",
         "\u0064elegate_to": "localhost"
} ],
     "vars": {
     "ansible_\u0063onnection": "local"
     }
}]

这个时候继续分析抓到的pcap包,查看其网络通信内容,寻找这个payload去往celery的路,在分析celery的通信过程中,发现其并未和jms_core直接通信,而是和数据库mysql及redis进行通信。
先对mysql进行内容分析,发现其中存储了ops_playbook的id值,jobs的id值及crontab,及用于ansible的任务规划,没有发现序列化后的yml文件



然后是Redis,其中存储了连接记录,资源详情等内容,但是没有发现序列化后的yml文件

代码分析

从补丁开始分析,补丁中主要对apps/ops/ansible/runner.py进行了修补,并且将原有的PlaybookRunner替换成了SuperPlaybookRunner。其中SuperPlaybookRunner为PlaybookRunner的子类,并且其中增加了一个"LOCAL_CONNECTION_ENABLED": "1"的条件。

同时升级了ansible-core的版本

看一下ansible-core做了哪些改动,将开发者遗留的doc和test删除丢进winmerge进行比较,发现其主要修改了/lib/ansible/plugins/connection/local.py这个文件,增加了一个判断语句,如果没有设置LOCAL_CONNECTION_ENABLED则默认禁用本地链接

可以看到,在3.10.7中,修改了manager.py, job.py中的PlaybookRunner调用,可以发现在job中依旧调用PlaybookRunner,禁用本地连接;在manager中使用SuperPlaybookRunner,启用本地连接

这里可以简单看一下他的任务执行流程

  1. 获取一个job, 如果是playbook,检查一下危险词
  2. get_runner,向ansible下发命令self.current_job.playbook.entry
  3. 看到/apps/ops/ansible/runner.py 中的PlaybookRunner类,在这个类中利用ansible_runner.run运行了这个playbook

    可以看到,假如在没有任何过滤的情况下,向该函数传入playbook_path, inventory_path, project_directory就可以进行命令执行,其中playbook_path也就是刚刚main.yml的值,我们这里回到刚刚的3.10.6中,将playbook修改成非Unicode编码的样子,也就是他编码前的样子,将其保存并执行时会发现他触发了check_danger_keywords。
    [{
      "name": "RCE playbook",
      "hosts": "all",
      "tasks": [
        {
          "name": "this runs in Celery container",
          "shell": "id > /tmp/pwnd",
          "delegate_to": "localhost"
    } ],
      "vars": {
      "ansible_connection": "local"
      }
    }]
    


    跟过去看一下check_dangerous_keywords中的函数内容,可以看到他是以正则过滤的方式筛选dangerous_keywords,如果playbook中含有上面的keywords,则返回当前危险字符的位置和文件

    到这边我还是想知道他是怎么传值的,所以从JobExecution一步步向上跟进代码,可以看到在JobExecutionSerializer中被调用,将反序列化后的数值填入这个JobExecution类

    继续向上,就来到了JobViewSet,这是一个Django调用的前台页面,用于执行job


    再从写入流程,也就是此处的playbook跟进

    跟进到了API view,也就是上面所说jms_web反代理到jms_core的内容,可以看到其中的patch函数,如果获取到的HTTP方法是PATCH,也就是之前所使用修改main.yml的方法。这个函数在获取到pk和playbook_id并校验之后,将其存储到file_path中,也就是在jms_core中落地

后续跟进到api的\apps\terminal\automations\deploy_applet_host__init__.py,找到了文件解析点

在这里进行了celery命令执行

简单小结

其实到这里已经把漏洞成因分析的七七八八了,以unicode编码的形式绕过了正则过滤,将值传入runner中,使其在localhost运行;补丁中更新了ansible-core的版本,并使用其新特性,将原有的PlaybookRunner区分成两个部分,在manager.py中保留原有的SuperPlaybookRunner,使其允许在localhost执行命令,而在job.py中不允许在localhost执行命令。

CVE-2024-29202

至于CVE-2024-29202,修复补丁中启用了jinja2的SandboxedEnvironment作为NativeEnvironment完成了修复,其执行方式与绕过方式与29201类似,不做过多赘述了

疑点

这里有一个疑点,不知道他是怎么在jms_celery上执行的,yml是在jms_core上的,mysql和redis也未发现序列化后的内容?但是jms_web与jms_celery不通信,jms_core与jms_celery也不通信,jms_celery只和jms_redis与jms_mysql通信。而mysql中存储的是运行结果,Redis中是各种key和session,所以数据流并不完整,只能找到输入点、部分执行点和输出点,很想知道这里是怎么执行到jms_celery中去的,如果有相关解答还请留言,对JumpServer并不是很了解,期待有大手子可以解答。

1 条评论
某人
表情
可输入 255