代码审计之Java

Published: 2022年03月16日

In Vuln.

Java

准备

工具

漏洞挖掘第一步当然是把环境搭起来,然后安装一些必要的工具:

1.Idea:不会还有人用eclipse吧?!!

2.jclasslib/fernflower/bytecodeviewer/jd-gui:各种反编译工具

3.arthas/btrace:各种插桩工具,动态分析工具

4.javaassist/asm:字节码修改工具

5.jarjarbigs:简单但实用的小工具,将所有class和jar合并成一个jar

由于jar和class不便于搜索,所以可以将其先反编译为java文件,此处使用fernflower,可下载最新版编译,idea也自带此jar包:

git clone https://github.com/fesh0r/fernflower.git  # 下载软件
gradlew.bat  # 安装gradle
gradlew.bat build  # 编译项目,最新版默认使用jdk11,可修改build.gradle里的配置

cd workdir  # 切换到项目目录
mkdir src  # 创建新文件夹存放源码
java -jar fernflower.jar -mpm=15 ./class ./src  # 反编译,单个方法超时时间15秒

注:为了调试与审计方便,建议先将jar解压,再将反编译的java和原始class放在同一个目录,这样能方便审计java并在class上调试。另外可以修改fernflower源码不处理非class文件防止破坏其他文件。

知识

1.Java基础语法

2.Java EE语法,就JSP/Servlet/MVC这些

3.其他高级语法:反射,动态代理(proxy/cglibc),动态代码生成,注解,类加载

4.各种框架组件:Spring/Struts/Mybatis/Jackson...

img

语言特性漏洞

XXE

Java下解析XML的机会很多,库也很多,配置也千奇百怪,遇到它自己要解析xml则搜下标准的安全的配置,不符合就可能有问题:

javax.xml.parsers.DocumentBuilderFactory;
javax.xml.parsers.SAXParser
javax.xml.transform.TransformerFactory
javax.xml.validation.Validator
javax.xml.validation.SchemaFactory
javax.xml.transform.sax.SAXTransformerFactory
javax.xml.transform.sax.SAXSource
org.xml.sax.XMLReader
org.xml.sax.helpers.XMLReaderFactory
org.dom4j.io.SAXReader
org.jdom.input.SAXBuilder
org.jdom2.input.SAXBuilder
javax.xml.bind.Unmarshaller
javax.xml.xpath.XpathExpression
javax.xml.stream.XMLStreamReader
org.apache.commons.digester3.Digester
.....

在使用时,没有关闭一些特性就会有问题:

http://apache.org/xml/features/disallow-doctype-decl true
http://xml.org/sax/features/external-general-entities false
http://xml.org/sax/features/external-parameter-entities false
http://apache.org/xml/features/nonvalidating/load-external-dtd false

SSRF

Java下的SSRF功能比较有限,从sun.net.www.protocol.包下面可以看到所支持的协议:gopher file ftp http https jar mailto netdoc...,通常就使用file来读文件/列目录,以及使用http/https来发请求,在审计时,就找一些发请求的类,如:

URL             // 这个出现的最多,但有可能只是用于解析url未打开连接
HttpURLConnection / URLConnection       // 这两个通常是URL创建的,前者只支持http协议
Request                                                         // 同样误报很多
ImageIO                                                         // 读文件时可触发请求,但是无法获取非图片的结果
OkHttpClient
HttpClients / HttpClient                
...

模板注入

模板引擎可将模板与数据分离,并生成最终的文本(代码并执行),此时若能控制模板的内容则能把我们的恶意代码嵌入最终生成的目标并被执行,这里主要关注HTML的模板,在Java中常见的有JSP/Thymeleaf/Freemarker和Velocity等:

Thymeleaf

它是服务侧的Java模板引擎,能处理HTML/XML/CSS等多种类型的模板,也是SpringBoot的默认模板引擎,其本身不支持什么危险功能(执行代码或调用函数),但它有两个特性值得关注(其他见语法参考):

1.变量表达式${expr}实际会使用EL来处理变量,默认为OGNL,在SpringBoot中为SpEL,因此利用它可以进行表达式注入

2.它的模板里支持__expr__预处理语句,在执行表达式前会先将中间的字符串当表达式执行

利用它们就可能实现命令执行,最常见的是在SpringBoot中,能控制视图名称,而在接下来的处理中,它会使用~{template name}分段表达式来处理它,利用预处理语句插入EL语句来执行代码,这通常有三种情况:

1.返回的视图名称可控

2.未指定视图(无返回值)且,它会将URI PATH的最后一部分当作视图处理(handler的参数中不能有HttpServletResponse)

3.直接就是设置视图的位置值可控

注意:在设置了ResponseBody/RestController注解,或返回值前含redirect:时不会走Thymeleaf处理的逻辑,因此不存在漏洞

除此之外,也要注意它单独使用的场景,只要模板可控则可能存在漏洞。

Freemarker

它是一个模板引擎,它仅是一个jar包,基于模板,生成文本输出(HTML网页、电子邮件、配置文件、源代码等)。它的功能很强大,包括直接在${expr}里调用函数等,详见语法手册,另外它还支持很多内建函数,特别是隐藏在Seldom used and expert built-ins里的这三个函数有点东西:

1.value?api.someJavaMethod(args)用于调用value所表示的对象的任意方法,不过使用该功能需要启用api_builtin_enabled功能(从2.3.22默认关闭)

2.s?eval用来评估表达式不多说

3."classname"?new(args)用来创建实现TemplateModel接口的实例,当然未实现这个接口也会触发到类的静态初始化块

利用这些就能实现任意代码/命令执行。

Velocity

它依然是很流行的模板引擎,其可用于生成HTML/SQL/PostScript等,Apache Solr(漏洞一例)就用了它,直接看它的语法文档有如下内容值得注意:

1.除了在Java代码中,还可以使用#set($var=val)创建变量,这里的变量包括简单类型(int/float等的装箱)与容器类型(List/Array/Map等)

2.可以通过$var.method(args)去调用变量的方法

3.#parse可用于解析外部模板文件(为了安全限制了目录),#evaluate可用于评估模板语言(就是eval啦)

这里主要看第2点就好了,用它可以调用方法,那么$var.getClass().forName('java.lang.Runtime').getRuntime().exec('calc')就可以执行命令了。

参考

[0] Java安全之freemarker 模板注入

表达式注入

表达式语言主要被用于简化完成一些常用工作,避免冗长代码书写,目前我只遇到过JSTL-EL/OGNL/SPEL这几种,下面分别介绍:

JSTL-EL

它已经是JSP标准里的一员了(因此也叫JSP-EL或直接叫EL),及通常在JSP中${expr}形式出现,当然也可以单独出现,除了读写查询它本身只支持调用taglib中定义的函数(形式为${ns:func(param1, param2, ...)}),而且它通常只会出现在JSP中因此通常不会有什么问题,了解即可。

OGNL

OGNL即Object-Graph Navigation Language,看名字就知道它是操作Java对象的,它的功能比JSTL-EL要强大得多,像struts2/arthas等都使用了它(嗯,经典的S2-XX漏洞),其语法请看语法参考,这里更关注用它执行代码(读写操作也很有用哦):

1.@class@method(args)可直接调用静态方法,method(args)可调用当前对象的方法

2.new class(args)用于创建对象

3.(x)(y)用于表达式评估(Eval),即它先对x计算获取结果作为新的表达式再以y作为根对象进行计算,这里前一个括号可以不写(写上主要是防止歧义,因为它会优先尝试调用方法)

在代码中,看Ognl.getValueOgnl.setValue的表达式是否可控,其他位置通常会以%{expr}/${expr}形式出现,至于怎么利用可看看S2漏洞分析和[0]。

SPEL

SpEL即Spring Expression Language,Spring家的功能当然也足够强大,先看看它的语法,这里依然只关注执行代码,其他见语法参考

1.T(class).method(args)可调用指定类的静态方法

2.obj.method(args)直接调用对象的方法,它还支持很多内置函数,也可以用registerFunction在Java代码中去新增函数

3.new class(agrs)用于创建对象

再来看它的使用,它可以单独使用也经常在Spring的配置和注解中使用:

1.在单独使用时,它需要先创建解析器(通常用SpelExpressionParser)来解析表达式(parseExpression),再获取结果(getValue)

2.在Spring中,它在配置中为#{expr}形式,也可以在配置中@Value("#{expr}")

还有两个重要概念,ParserContext主要用于表达式模板,如B3taMa0 is #{id.name}就是个模板里面嵌了表达式,靠解析上下文可正确处理它;而另一个EvaluationContext表示执行上下文来定义能使用哪些功能,如StandardEvaluationContext为全功能(默认)而SimpleEvaluationContext只有受限功能(如不支持调构造函数)。

参考

[0] 浅析 OGNL 的攻防史

[1] 由浅入深SpEL表达式注入漏洞

[2] Bean Stalking: Growing Java beans into RCE

JSP文件包含

含动态包含<jsp:include page="<%=file%>"></jsp:include><c:import url="<%= url%>"></c:import>和静态包含<%@include file="xxx.jsp"%>

命令与代码注入

常见的命令注入发生在new ProcessBuilder.commandRuntime.getRuntime.exec,注意下后者执行命令时可能需要转义(原理)。

而代码注入有很多,比如new javax.script.ScriptEngineManager().getEngineByName(XX).eval()可执行JS代码,利用URLClassLoader等加载远程Class或直接提取数据来执行任意代码。

文件上传

multipartfileUploadgetParts获取文件上传点[1],看文件名能不能穿越或传恶意文件,再看文件内容能不能造成危害。

对于spring可搜索multipartResolver看是否修改了默认解析器,它的StandardMultipartFile(默认)不过滤目录穿越而CommonsMultipartFile会过滤[0]。

参考

[0] Spring MultipartFile 文件上传的潜在威胁 -- JOHNSON (2021)

[1] 任意文件上传漏洞 -- 园长

WEB框架

Web Application

Servlet/JSP

将库和类文件添加到项目里,如一般项目会把classeslib放入WEB-INF目录下,另外可能需要调试J2EE相关的库,可以将其添加到项目结构里:

img

Spring

现在遇到的一般都是基于Spring框架的,Spring家族里有很多东西,包括spring,spring mvc, spring boot, spring cloud等,它们有各自的应用场景:

img

理解spring需要懂它的面向切面编程(AOP)/控制反转(IOC)/依赖注入(DI)等概念:

1.IOC:控制反转,将对象的生成交由Spring实现,这样方便实现DI和AOP

2.AOP:面向切面编程,可用于避免面向对象编程时必须的层级关系,作为补充它通过功能来复用代码

3.DI:依赖注入,可自动根据属性类型,名称等注入所需的实例

spring mvc一般了解它的处理流程即可,可见Spring MVC的介绍与执行流程

img

这里重点关注匹配handler部分,它的配置方式特别多,但可以在调试时直接在DispatcherServlet(org/springframework/web/servlet/DispatcherServlet.class)的doDispatch上下断点,输出它的this.handlerMappings即可获取所有的HandlerMapping:

# 最常见的注解形式存储在这里面
RequestMappingHandlerMapping
    .mappingRegistry
        .mappingLookup  # 获取uri->handlerMethod
    .interceptors
# 下面两个都继承自`AbstractUrlHandlerMapping`,都使用配置文件,很像
BeanNameUrlHandlerMapping                                   # 注在配置里bean的name或aliasname以`/`开头时,解释为url->handler
SimpleUrlHandlerMapping                                     # 配置文件里实现的
    .handlerMap # 获取uri->handler
    .interceptors

⚠️:这里得到的不一定指只能通过该键获取,很多其他的uri也可能被匹配,spring在查找handler时,可能会做一些奇怪的操作(如可匹配//会去;a=b等等),或者开发自行添加了路径匹配规则(如修改UrlPathHealper),从而绕过一些限制,这部分前者需要研究Spring的内部,后者就是全局搜索spring看开发者用了它的哪些功能,这些功能有什么影响。

至于拦截器,它分adaptedInterceptorsmappedInterceptors,后者是在返回handler时动态匹配的,可以写脚本批量生成所有,但由于它的配置很简单直接搜源码就可以确定了!

历史漏洞
  1. CVE-2022-22965:利用参数绑定修改Tomcat的日志配置实现写马
  2. CVE-202X-XXXXX: 和Shiro一起造成的各种问题
  3. ...
参考

[0] SpringBoot内置tomcat启动原理

[1] 一些spring源码解读

[2] 设计模式角度解读

[3] Java框架级SSM代码审计思路

Struts

struts和struts2有些区别,但都很少见了,在我开始挖Java应用后只见过一次,关于它的基础可看W3school,先得了解它的处理过程,请求先经过其他过滤器最后到达FilterDispatcher,它调ActionMapper来确认是否该处理,是就调ActionProxy它根据配置来确定action对应的classinterceptor,它创建ActionInvocation来执行action并根据配置获取结果处理方法:

Struts2-Architecture.png

现在一般也就是学它的那一系列漏洞,主要是ognl表达式的问题,还有就是基于它开发程序的业务逻辑漏洞了。

参考
  1. 从零开始学java web - struts2 RCE分析
  2. 教程:初识struts2/配置文件详解与结果视图/表单参数自动封装与参数类型自动转换/输入校验/struts2拦截器与自定义拦截器/ognl表达式与ActionContext、ValueStack/Struts2 S2-059 漏洞分析

  3. Struts2-059 远程代码执行漏洞(CVE-2019-0230)分析

Web Services

JAX-RS

Java API for RESTful Web Services是针对RESTful的,编程核心是ProviderResource

1.Resource:用于处理请求,就像handler,会被@Path/@GET等方法修饰...

2.Provider:用于增强功能,类似于插件,有多种提供者,它们都会被@Provider注解并实现如下接口:

ContainerRequestFilter&ContainerResponseFilter: 过滤器
ExceptionMapper: 异常映射注册后会捕获处理对应的异常
MessageBodyReader&MessageBodyWriter: 实现请求响应流与相应对象的转换  @Produces和@Consumes表示匹配对应Mime时生成/解析请求
ContextResolver: 解析上下文提供者

这些提供者和资源会以两种方式被加入应用里:

1.通过注解自动扫描

2.通过编程的方式手动添加到Application.getClasses()/getSingletons()里,这里前者的类实例生命周期为每个请求,而后者是整个应用。

常见的实现有jboss家的resteasy/eclipse家的jersey/Apache下的CXF等。

JAX-WS

Java API for RESTful Web Services是针对传统的SOAP/WSDL的,了解下web service协议,常见的框架有WebService/xfire(cxf)/axis等。

组件

持久化

mybatis

mybatis是半自动化的,需要开发编写SQL语句,此时参数可用#{}${}方式放置,后者直接替换如SQL语句再预编译因此存在SQLi,有三种情况无法用#{},因此易出问题[1]

1.like后无法直接用,like concat('%', #{}, '%')的方式是安全的

2.in后的参数

3.oder by后的参数,可用白名单限制

hibernate

它在直接使用SQL语句,且有拼接时有危险,另外它可能有反序列化问题。

  1. ysoserial Java 反序列化系列第二集 Hibernate1
  2. ysoserial Java 反序列化系列第三集 Hibernate2

H2

H2是一个用Java开发的嵌入式数据库,它本身只是一个类库,可以直接嵌入到应用项目中,所有语句都是在当前jvm里执行的。当存在javac/groovy时,它可以执行Java命令[1],堆叠注入(猜测未验证),注意每次修改后函数名也需要修改:

// http://www.h2database.com/html/commands.html?highlight=alias&search=alias#create_alias
CREATE ALIAS DDrrArr AS $$void replace() throws java.io.IOException{new java.io.File("webapps/ROOT/weixin.jsp").renameTo(new java.io.File("webapps/ROOT/weixin.jsp.bak"));java.io.FileWriter writer = new java.io.FileWriter("webapps/ROOT/weixin.jsp");writer.write("<%=123;%>");writer.close();}$$;
CALL DDrrArr();
// groovy
CREATE ALIAS biu AS '@groovy.transform.CompileStatic
    static String biu(String cmd){
        return cmd.execute().text;
}
';
CALL biu('calc');

而不存在编译器时,也可以利用先写Native库再执行(涉及任意文件写)的方式实现RCE[0]。

⚠️:注意如果要使用任意文件写,不要用concat(char(0xcc),char(0x0a),...)hextoraw('00cc000a...')这种形式,它会进行编码,应该使用X'cc0a...'直接表示二进制数据!

遇到嵌入式的H2文件可用idea打开(需下载驱动,注意1和2版本不兼容),也可以使用dbvisualizer打开。

参考

[0] Exploiting H2 Database with native libraries and JNI -- Markus Wulftange (2019)

[1] JavaWeb中的信息泄漏——H2 database -- tkswifty (2021)

JDBC

当JDBC连接可控时,可能能做很多事,特别是存在H2的情况(见上面的),详细请见Make JDBC Attack Brilliant Again,这里只记一下某产品里用到的过滤关键字:

new InsecurityElement[]{
    new InsecurityURLParameter("INIT="), 
    new InsecurityURLParameter("allowLoadLocalInfile="), 
    new InsecurityURLParameter("autoDeserialize="), 
  new InsecurityURLParameter("clientRerouteServerListJNDIName="), 
  new InsecurityURLParameter("jcr:jndi:"), 
  new InsecurityURLParameter("slaveHost="), 
  new InsecurityURLParameter("sqlite::resource"), 
  new InsecurityURLParameter("mysql:fabric"), 
  new InsecurityURLParameter("socketFactory="), 
  new InsecurityURLParameter("loggerFile="), 
  new InsecurityURLResource()  // URL file Path
};

安全

Shiro

特别常见的鉴权框架,其架构如下:

Shiro Architecture Diagram

理解它的几个概念即可:

1.Subject: 使用系统的对象,如用户,某程序等

2.Realms: 进行认证和授权的地方,每一个realm记录了一种自定义的鉴权方式,如使用本地数据库,使用ldap等都在这里实现

3.SecurityManager: 是框架核心,使用它完成对资源的访问限制

在使用时主要就是定义Realms和配置SecurityManager,其中配置时可用配置文件,在spring中也可直接用注解的方式,最终它会有个过滤器链字典,请求到来时根据uri依次匹配链,匹配到即依次执行对应的过滤器链,因此常见的问题就是这里path匹配有问题造成权限绕过,当然它还有经典的由于使用remenberme反序列化漏洞,具体的可以先看看历史漏洞

Spring Security

历史漏洞

1.CVE-2022-22978 regexMatchers 认证绕过漏洞:在黑名单里想用.匹配所有,但实际它不匹配换行从而绕过

参考

[0] JAVA安全编码与代码审计

[1] java代码审计手书

[2] 技术分享|浅谈Java Web漏洞分析

容器

相关的容器很多,一般挖掘应用时常见的有tomcat/jboss/resin/jetty等,web.xml里将web-app的版本号换成3.0以上时可支持注解方式,于是注册Servlet和Filter就有三种方式:配置文件/addServlet与addFilter代码/注解,实际上为防止遗漏,查看实际存在的Servlet和Filter可使用如下方式:

应用 Servlet Filter
Jetty org/eclipse/jetty/servlet/ServletHandler:doScope下断后,通过打印this._servletMappings可获取 同样的地方下断后,打印this._filterPathMappings
Tomcat org/apache/tomcat/util/http/mapper/Mapper.class:internalMapWrapper下断后看contextVersionXXWrappers,也可以往上看有哪些context(不同版本路径可能不同) 在同样的地方下断后,输出contextVersion.XXWrappers[x].object.getParent().filterMaps (filterConfigs可见所有过滤器信息)
...

Tomcat笔记

img

调试时,在startup.sh里,修改如下:

# 添加
export JPDA_SUSPEND='n'
export JPDA_ADDRESS=5005
# 修改
exec "$PRGDIR"/"$EXECUTABLE" jpda start "$@"

或者在catalina.sh里,插入如下指令:

Java_OPTS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005"

Servlet和Filter使用的匹配模式都是一样的,但是前者按照语法优先级(精确>路径>扩展>缺省[2])匹配一个即停止,而后者按照Filter添加顺序优先级依次处理,这里特别注意匹配并不是通配,没有*就是精确匹配,有一个/*在末尾才是路径匹配!这在过滤器中尤为关键,如不确定请手动尝试每条路有防止漏掉。

参考

[0] Some notes about Resin web application server -- Manh Phan (2019)

[1] tomcat 核心架构解析 -- heibaiying (2020)

[2] web.xml文件中servlet的url-pattern匹配规则 -- 码——皇 (2019)