阿里云WebShell伏魔挑战赛新思路挖掘

admin 2024年6月19日12:41:54评论17 views字数 4140阅读13分48秒阅读模式
前  言

去年的WebShell引擎检测绕过思路分享中,主要介绍了当下主流引擎对WebShell检测引擎的几种检测方法,再针对各个检测方法,逐一的利用Java语法的trick去进行绕过。重心放在了检测引擎的行为上,依赖对Java语法和trick的先验知识进行绕过。在今年的比赛中,去年文中列出的绕过方法基本上已经被引擎修复完成。结合今年阿里举办的恶意代码检测挑战赛的比赛经历,分享一下在已有的trick都被ban,如何从0研究出新的绕过思路,把重心转移到WebShell本身上,通过分析jsp的解析过程,挖掘绕过方法。

jsp解析逻辑

Tomcat处理jsp的核心的逻辑是它实现了一个处理jsp的Servlet:org.apache.jasper.servlet.JspServlet,这个Servlet处理所有以jsp为后缀的请求。

阿里云WebShell伏魔挑战赛新思路挖掘

当我们上传一个jsp文件后,在这个service处下断点,然后请求这个jsp,跟进代码,程序在org.apache.jasper.servlet.JspServletWrapper中调用如下代码进行编译

阿里云WebShell伏魔挑战赛新思路挖掘

这是一个JspCompilationContext对象,它在JspServletWrapper的构造方法中被生成,其中jspuri是文件名,options保存了jsp文件的参数信息。

阿里云WebShell伏魔挑战赛新思路挖掘

阿里云WebShell伏魔挑战赛新思路挖掘

整个从jsp到生成class的编译过程都是发生在JspCompilationContextcompile方法中

阿里云WebShell伏魔挑战赛新思路挖掘

这里的JspCompilationContext类和compile方法都是public属性,所以其实可以直接在jsp中用如下写法去编译任意jsp文件,而且参数都是可控的。

阿里云WebShell伏魔挑战赛新思路挖掘

那么这里就存在着一个理论上可行的绕过点:我们是否可以上传一个jsp文件,这个文件在被上传和执行时,不存在恶意特征,然后我们通过控制参数,从而使再次编译时触发恶意命令,形成二次编译。接下来就可以带着这个目标去寻找可以被利用的特征。

继续往下看,编译jsp文件的最终实现类是org.apache.jasper.compiler.Compiler

阿里云WebShell伏魔挑战赛新思路挖掘

这个类的compile方法对jsp进行了编译。

阿里云WebShell伏魔挑战赛新思路挖掘

代码中的方法名非常明显,generateJava方法代表着生成java文件。跟进该方法。

前面的部分主要是通过调用如下代码:

阿里云WebShell伏魔挑战赛新思路挖掘

生成一个pageInfo对象,接着获取jsp文件中的属性,后续根据属性的不同进行不同的配置。通过一连串的if进行选项配置。

阿里云WebShell伏魔挑战赛新思路挖掘

这些配置在jsp可控,那就可以通过控制一些特定的配置,使样本看起来有一点点的反常,如果引擎未能识别到这种参数,那就存在绕过的可能。一个典型的例子是p牛分享过的,利用trimDirectiveWhitespaces属性忽略jsp不同块之间的空白字符。

样本如下:

阿里云WebShell伏魔挑战赛新思路挖掘

对应到代码上是:

阿里云WebShell伏魔挑战赛新思路挖掘

不加该属性,生成的java文件会是这样:

阿里云WebShell伏魔挑战赛新思路挖掘

添加属性后,输出的java文件不会再被添加换行。

generateJava接着会调用下面的代码:

阿里云WebShell伏魔挑战赛新思路挖掘

试可以发现这段代码会返回jsp编译的java文件的路径

阿里云WebShell伏魔挑战赛新思路挖掘

接着又是一些处理jsp的标签和控制信息的操作,创建了一个ParserController对象,并调用其parseDirectivesparse方法对jsp文件进行读取和解析,保存到directivespageNodes对象中。

阿里云WebShell伏魔挑战赛新思路挖掘

跟进其解析jsp文件的函数。在parserCtl.parseDirectives中,会用其doParser方法

阿里云WebShell伏魔挑战赛新思路挖掘

其中的determineSyntaxAndEncoding会调用getPageEncodingForJspSyntax方法,内容如下:

阿里云WebShell伏魔挑战赛新思路挖掘

也就是通过jsp的pageEncoding和contentType等配置去设置读取jsp文件的编码。根据代码的含义,支持如下写法:

阿里云WebShell伏魔挑战赛新思路挖掘

利用编码绕过也已经是的老生常谈的话题了,一个常见的例子:

阿里云WebShell伏魔挑战赛新思路挖掘

如果引擎不能识别这种编码,则会形成绕过。

接着设置一个ServletWriter象。这里的ServletWriter实际上是一个指向生成java文件的PrintWriter对象的封装。生成前,会从Options中获取JavaEncoding属性的值,作为文件写入的编码。

阿里云WebShell伏魔挑战赛新思路挖掘

阿里云WebShell伏魔挑战赛新思路挖掘

阿里云WebShell伏魔挑战赛新思路挖掘

接着调用Generator.generate。这个方法实际上就是对jsp到java代码写入的实现类。函数先通过generateCommentHeadergeneratePreamble以及generateXmlProlog等方法写入一些注释、xml的控制信息,接着通过观察者模式创建了一个GenerateVisitor对象,根据传入的代码类型不同,调用不同的Node进行处理。

阿里云WebShell伏魔挑战赛新思路挖掘

阿里云WebShell伏魔挑战赛新思路挖掘

jsp中输入的<%…%>块代码代码被Scriptlet类捕获,并调用其visit方法

阿里云WebShell伏魔挑战赛新思路挖掘

可以看到n.getText()被直接out.printMultiLn到了文件中。没有进行任何过滤的直接拼接到了文件里。

以一个WebShell为例:

阿里云WebShell伏魔挑战赛新思路挖掘

最后生成的文件如下,被<% %>包裹的代码直接被打印到了_jspService函数中:

阿里云WebShell伏魔挑战赛新思路挖掘

那么很容易想到,是否可以在jsp中注入代码,闭合掉其他上面的_jspService方法,再闭合掉后续的代码,新建一个函数或者代码块进行恶意操作执行。这样生成的jsp会看起来是个不正常的jsp文件。

实现如下:

阿里云WebShell伏魔挑战赛新思路挖掘

再次访问WebShell,生成的java文件如下,成功的新生成了一个名为foo的函数,并在原本的_jspService逻辑中进行了调用。

阿里云WebShell伏魔挑战赛新思路挖掘

如果引擎只是对jsp文件本身进行分析,面对这种畸形的jsp文件,可能数据流分析之类的分析方法就无法正常运行。

除了jsp中直接输入的<%…%>代码,在Generator类中还可以看到程序对于很多jsp的参数属性信息也是直接使用print输出到java文件中,因此都存在注入的问题。

比如在jsp文件中,在处理jsp代码前通过generateXmlProlog函数处理xml信息,代码如下:

阿里云WebShell伏魔挑战赛新思路挖掘

在这个函数中,如果包含了DoctypeNameDoctypepublicDoctypeSystem等属性,那么就会将这部分内容打印到java文件中。

这部分内容的本意是在在java文件中加入一句out.write(“<!DOCTYPE NAME SYSTEM  DOCTYPESYSTEM “); 但是我们可以在doctypeName等参数中加入”);及在doctypeSystem添加out.print(” 对原本的代码进行闭合,从而注入新的恶意代码。样本如下:

阿里云WebShell伏魔挑战赛新思路挖掘

访问jsp文件,生成的java文件如下:

阿里云WebShell伏魔挑战赛新思路挖掘

原样本中被引号包裹的字符串,最终被当作了代码进行执行,并且对象的传递放在了不同的字符串中,同样引擎如果没能正确编译jsp文件后再检测,就会存在漏报的可能。

二次编译

到这里从jsp到java的过程就结束了,现在回过头来思考文章之前提到的那个问题,是否有什么控制参数,可以让我们的jsp文件被编译成功,并且我们通过设置这个参数,让jsp再次编译时产生不同的效果?要解决这个问题需要先回顾哪里存在控制参数。前文提到的第一处是在pageInfo生成时一连串的if。就像p牛的那个例子,但同样以那个例子为例,如果没有 trimDirectiveWhitespaces='true',则jsp文件在第一次被上传时就无法正确解析,也就无法被利用。

阿里云WebShell伏魔挑战赛新思路挖掘

前文提到的第二处控制参数配置在那段代码之后。程序通过ParserController对象解析jsp中的编码设置,对jsp文件使用特定编码进行读取,在那以后会从Options对象中,获取javaEncoding属性,对输出的java文件进行输出。这里的Options是代码中可控的,所以我们可以控制代码在被运行时,再用一个指定的编码控制输出文件的编码。综合这些思路,提出一种理论上可行的绕过方法:

1. 寻找两种编码,这两种编码对换行符之类的控制字符存在解析差异。即第一种编码不会将解析成换行符而是一个普通的字符,第二种编码会解析为换行符。并且这两种编码在解析其他文本时存在尽量少的区别;

2. jsp文件在pageEncoding中标注为第一种编码,此时上传上去的文件会以这种编码解析文件。其中以注释符//开头,并在其后写恶意代码。此时由于编码会解析为普通字符,因此这段代码只是一段注释,不会被保存到java文件中,更不会被解析;

阿里云WebShell伏魔挑战赛新思路挖掘

3. 在jsp的代码中包含如下代码:

阿里云WebShell伏魔挑战赛新思路挖掘

当jsp运行时,就会使用编码二设置Java文件的PrintWriter编码,编码二会把识别成换行,恶意的代码也就逃逸了出来,从而后续被编译和执行。

思路清楚,问题就是如何找到这样的两种编码。使用下面的代码fuzz一下:

阿里云WebShell伏魔挑战赛新思路挖掘

可以找到不少满足条件的编码,但是大部分的编码存在其他的解析差异,导致无法使用,因此还需要手动测试一下。

阿里云WebShell伏魔挑战赛新思路挖掘

最后找到两种满足条件的编码x-IBM1097IBM1026,制作jsp文件的代码如下:

阿里云WebShell伏魔挑战赛新思路挖掘

最终效果如下,在tomcat的jsp编译过程中,生成的java文件长这样:

阿里云WebShell伏魔挑战赛新思路挖掘

恶意代码被注释掉了。

但是随着程序运行,jsp中的其他代码被执行,此java文件会被重新写入,此时java文件长这样:

阿里云WebShell伏魔挑战赛新思路挖掘

从而被成功执行。

参考链接

本文没有分享太多的绕过样本,而是着重分享了如何从0开始挖掘绕过的思路。本文中分享的Tomcat解析jsp代码只是冰山一角,Tomcat源码中还有许多对于jsp中各种标签、语法的解析代码,以及后续的从java到class文件的编译过程,都存在着未被发现的可以用来绕过的可能。

参考链接

[1] 浅谈JspWebshell之编码

[2] 知识星球

【版权说明】

本作品著作权归YYHY所有

未经作者同意,不得转载

阿里云WebShell伏魔挑战赛新思路挖掘
YYHY

天工实验室安全研究员

专注于代码审计、Web安全

往期回顾
01
macOS中四类TCC BYPASS案例分析
02
某OA业务逻辑缺陷导致RCE的利用链解析
03  【GeekCon 2024】TI C2000 DSP Chip Hacking: 绕过德州仪器C2000芯片的安全保护机制
04
Openfind Mail2000 认证前RCE漏洞分析
阿里云WebShell伏魔挑战赛新思路挖掘
每周三更新一篇技术文章  点击关注我们吧!

原文始发于微信公众号(破壳平台):阿里云WebShell伏魔挑战赛新思路挖掘

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年6月19日12:41:54
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   阿里云WebShell伏魔挑战赛新思路挖掘https://cn-sec.com/archives/2864719.html

发表评论

匿名网友 填写信息