转载

Java Web安全-代码审计(二)

点击上方“ 凌天实验室 ”可订阅哦!

文接上回,穿越捷径: Java Web安全-代码审计(一)

4

Java Web安全-代码审计(二)

Java Web基础

Java Web安全-代码审计(二)

1. Java分层思想

为了更好的管理项目我们通常会采用分层架构的方式来开发Java Web项目,分层设计的好处在于可以非常方便的分清楚包之间的业务逻辑关系。

常见的JavaWeb项目分层:

视图层(View 视图)
控制层(Controller、Action 控制层)
服务层(Service)
业务逻辑层BO(business object)  
实体层(entity 实体对象、VO(value object) 值对象 、模型层(bean)。
持久层(dao- Data Access Object 数据访问层、PO(persistant object) 持久对象)

基于Java分层架构的示例项目:

Java Web安全-代码审计(二)

2. Java模块化开发

如今的较为大型的 Java Web 项目通常都采用了模块化方式开发,借助于 Maven Gradle 依赖管理工具,Java可以非常轻松的完成模块化开发。除此之外使用 OSGi ( Open Service Gateway Initiative  可实现模块热部署)技术开发来Java动态模块化系统也是较为常见的。

采用模块化开发也会给我们做代码审计带来一定的难度,因为需要在更多的依赖库中去寻找需要我们审计的代码。

使用Maven开发的JavaWeb项目示例:

Java Web安全-代码审计(二)

Java Web安全-代码审计(二)

3. 什么是Servlet?

Servlet 是在 Java Web容器 上运行的 小程序 ,通常我们用 Servlet 来处理一些较为复杂的服务器端的业务逻辑。值得注意的是在 Servlet3.0 之后( Tomcat7+ )可以使用注解方式配置 Servlet 了。

基于注解的Servlet

Java Web安全-代码审计(二)

Servlet3.0 之前的版本都需要在 web.xml 中配置, Servlet 两对标签 ,由 <servlet> <servlet-mapping> 组成, Spring MVC 框架就是 基于Servlet技术 实现的。

基于配置实现的Servlet

Java Web安全-代码审计(二)

HttpServlet类

Java Web安全-代码审计(二)

实现一个 Servlet 很简单,只需要继承 javax.servlet.http.HttpServlet 类并重写 doXXX 方法或者 service 方法就可以了,其中需要注意的是重写 HttpServlet 类的 service 方法可以获取到上述七种Http请求方法的请求。

4. JSP、Servlet之间的关系

JSP、JSPX文件是可以直接被Java容器直接解析的动态脚本,jsp和其他脚本语言无异,不但可以用于页面数据展示,也可以用来处理后端业务逻辑。

从本质上说JSP就是一个Servlet,因为jsp文件最终会被编译成class文件,而这个Class文件实际上就是一个特殊的Servlet。

JSP文件会被编译成一个java类文件,如 index.jsp 在Tomcat中 Jasper 编译后会生成 index_jsp.java index_jsp.class 两个文件。而index_jsp.java 继承于 HttpJspBase 类, HttpJspBase 是一个实现了 HttpJspPage 接口并继承了 HttpServlet 的标准的 Servlet __jspService 方法其实是 HttpJspPage 接口方法,类似于 Servlet 中的 service 方法,这里的 __jspService 方法其实就是 HttpJspBase service 方法调用。

Java Web安全-代码审计(二)

5. 什么是Filter

Filter是JavaWeb中的过滤器,用于过滤URL请求。通过Filter我们可以实现URL请求资源权限验证、用户登陆检测等功能。Filter是一个接口,实现一个Filter只需要重写 init doFilter destroy 方法即可,其中过滤逻辑都在 doFilter 方法中实现。

Filter和Servlet一样是Java Web中最为核心的部分,使用Servlet和Filter可以实现后端接口开发和权限控制,当然使用Filter机制也可以实现MVC框架, Struts2 实现机制就是使用的Filter。

Filter的配置类似于Servlet,由 <filter> <filter-mapping> 两组标签组成,如果Servlet版本大于3.0同样可以使用注解的方式配置Filter。

Java Web安全-代码审计(二)

6. Filter和Servlet的总结

对于基于 Filter Servlet 实现的简单架构项目,代码审计的重心集中于找出所有的 Filter 分析其过滤规则,找出是否有做全局的安全过滤、敏感的URL地址是否有做权限校验并尝试绕过 Filter 过滤。第二点则是找出所有的 Servlet ,分析 Servlet 的业务是否存在安全问题,如果存在安全问题是否可以利用?是否有权限访问?利用时是否被Filter过滤等问题,切勿看到 Servlet JSP 中的漏洞点就妄下定论,不要忘了 Servlet 前面很有可能存在一个全局安全过滤的 Filter

Filter Servlet 都是 Java Web 提供的API,简单的总结了下有如下共同点。

  1. Filter Servlet 都需要在 web.xml 注解 ( @WebFilter @WebServlet )中配置,而且配置方式是非常的相似的。

  2. Filter Servlet 都可以处理来自Http请求的请求,两者都有 request response 对象。

  3. Filter Servlet 基础概念不一样, Servlet 定义是容器端小程序,用于直接处理后端业务逻辑,而 Filter 的思想则是实现对Java Web请求资源的拦截过滤。

  4. Filter Servlet 虽然概念上不太一样,但都可以处理Http请求,都可以用来实现MVC控制器( Struts2 Spring 框架分别基于 Filter Servlet 技术实现的)。

  5. 一般来说 Filter 通常配置在 MVC Servlet JSP 请求前面,常用于后端权限控制、统一的Http请求参数过滤( 统一的XSS SQL注入 Struts2命令执行 等攻击检测处理)处理,其核心主要体现在请求过滤上,而 Servlet 更多的是用来处理后端业务请求上。

7. 初识JavaWeb MVC框架

传统的开发存在结构混乱易用性差耦合度高可维护性差等多种问题,为了解决这些毛病分层思想和MVC框架就出现了。 MVC 即模型( Model )、视图( View )、控制器( Controller ), MVC模式的目的就是实现Web系统的职能分工。

截至2018年底,绝大多数的新项目都已然改为了基于 Spring Boot Spring MVC 实现,也就是说曾经站在JavaWeb MVC最巅峰的 Struts2 框架已经逐渐陨落。

7.1 Spring MVC 控制器

在Spring进入了3.0时代,使用Java注解的方式也逐渐的流行了起来,曾经写一个Spring的控制器我们通常要在xml中声明Spring bean并配置处理的URL,而在新时代的Spring项目中我们通常用 Spring MVC注解 就可以轻松完成 Spring MVC 的配置了。

一个基于Spring 注解配置的控制器:

package org.javaweb.codereview.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class IndexController {

	@RequestMapping("/index.php")
	public String index() {
		return "/index.html";
	}

}

Spring Controller注解:

  1. @Controller

  2. @RestController

  3. @RepositoryRestController

Spring MVC 请求配置注解:

  1. @RequestMapping

  2. @GetMapping

  3. @PostMapping

  4. @PutMapping

  5. @DeleteMapping

  6. @PatchMapping

Spring MVC除了上述6种Http请求处理注解以外还有Spring Data JPA Rest提供的特殊的@RepositoryRestResource注解, @RepositoryRestResource 是基于 Spring Data JPA REST 库实现的, Spring Data JPA REST 提供的API可支持通过JPA查询数据并处理Http请求服务。

基于XML配置的Spring MVC

对于一些老旧的项目可能还保留了一些基于xml配置的方式Spring MVC项目,这里只简单的介绍下如何配置不做过多的描述。基于配置方式的控制器一般是在Controller类中实现了Spring的 org.springframework.web.servlet.mvc.Controller 接口的 handleRequest 方法(当然还有其他途径,如: AbstractCommandController SimpleFormController 但都已经过时了)。

TestController.java示例代码:

package org.javaweb.codereview.controller;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author yz
 */
public class TestController implements Controller {

	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
		ModelAndView mv = new ModelAndView();
		mv.setViewName("index");

		return mv;
	}

}

XML配置具体的bean

<bean name="/test.do" class="org.javaweb.codereview.controller.TestController"/>

7.2 Struts2控制器

Struts2主要的开发模式是基于xml配置,在 struts.xml 中配置Action地址和对应的处理类。

Java Web安全-代码审计(二)

不过Struts2( 2.1.6 版本开始)也可以使用 struts2-convention-plugin 插件来实现基于注解方式的配置。

Java Web安全-代码审计(二)

需要注意的是Struts2的参数是可以通过get/set方法传入的,如上图 TestActionAnnotation 类的 username 变量是可以直接在Http请求中的URL传入的。

7.3 快速找出Http请求请求URL

代码审计中我们可以选择优先从 Controller Servlet JSP 中入手,也可以选择从漏洞点反向推出Http请求的入口地址,这里将讲解下如何快速找到这些请求入口,因为 Struts2 Spring MVC 的原理比较接近,所以本节只以 Spring MVC 为例。

7.3.1 查找Spring MVC所有的控制器

如果有源码的情况下可以使用find命令或者IDEA的全局搜索功能即可快速搜索到所有的控制器,如果只有class文件的情况下可以使用find命令:

find ~/cms/ -type f -name "*.class" |xargs grep -E "Controller|@RestController|RepositoryRestController"

7.3.2 查找所有的请求处理URL

查找请求处理URL的方式同理,使用如下find命令查找所有class中的请求处理注解:

find ~/cms/ -type f -name "*.class" |xargs grep -E "RequestMapping|GetMapping|PostMapping|PutMapping|DeleteMapping|PatchMapping|RepositoryRestResource"

7.4 Spring MVC和Struts2控制器小结

这一小节我们只是简单的介绍下 Spring MVC Struts2 的控制器,在后面的框架服务章节将会详细介绍。至于如何去快速定位Struts2的action请自行参考Spring MVC的Controller查找方式这里不再讲解。

5

Java Web安全-代码审计(二)

Java语言的动态性

Java Web安全-代码审计(二)

Java语言动态性一直以来都比较差,并不像PHP那样灵活。在Java中的动态性往往需要使用一些曲折的方式来实现.这里简单列举了Java十余种动态性相关技术并总结部分技术实现安全问题。

  1. Java反射机制

  2. MethodHandle

  3. JDK动态代理

  4. 使用JVM上的动态语言(如: Groovy JRuby Jython )

  5. 表达式库(如: OGNL MVEL SpEL EL )

  6. JSPJSPXQuercus (Resin容器提供了PHP5支持)

  7. 字节码库(如: Asm Javassist Cglib BCEL )

  8. ScriptEngineManager(脚本引擎)。

  9. 动态编译(如:JDT、JavaCompiler)

  10. ClassLoaderURLClassLoader

  11. 模版引擎(如: FreemarkerVelocity )

  12. 序列化、反序列化(包含 Java 对象序列化XMLJSON 等)

  13. JNI JNA (Java调用C/C++)

  14. OSGi ( Open Service Gateway Initiative )

  15. RMI(Java远程方法调用,基于对象序列化机制实现)

  16. WebService

  17. JDWP ( Java Platform Debugger Architecture Java调试协议)

  18. JMX(Java Management Extensions)

1. Java反射机制特性

Java反射机制可以无视类方法、变量访问权限修饰符,可以 调用任何类的任意方法、访问并修改成员变量值 。也就是说只要发现一处Java反射调用漏洞几乎就可以为所欲为了。当然前提可能需要你能 控制反射的类名、方法名和参数

一行代码即可实现反射调用Runtime执行本地命令:

Runtime.class.getMethod("exec", String.class).invoke(Runtime.class.getMethod("getRuntime").invoke(null), "whoami")

获取一个类的对象(如Runtime类)我们一般会采用如下几种方式:

Class.forName("java.lang.Runtime")、"".getClass().forName("java.lang.Runtime")
Runtime.class
ClassLoader.getSystemClassLoader().loadClass("java.lang.Runtime")

Java反射获取类方法有两种方式:

  1. getMethod(xxx) , getMethods()

  2. getDeclaredMethod(xxx) getDeclaredMethods()

区别在于 getMethod会返回当前类和父类的所有public方法 ,而 getDeclaredMethod返回的是当前的所有方法

Java反射获取类成员变量有两种方式:

  1. getField(xxx) getFields()

  2. getDeclaredField(xxx) getDeclaredFields()

getField getDeclaredField 区别同上,如果想要 调用private修饰的Field或者Method 只需要设置下 setAccessible为true 就可以了,如: xxxMethod.setAccessible(true)

Java的大部分框架都是采用了反射机制来实现的(如: Spring MVC ORM框架 等),所以我们不得不掌握Java反射机制来提升我们的代码审计能力。

Java反射机制实现无关键字执行命令

import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Scanner;

/**
 * @author yz
 */
public class ReflectionTest {

	public static void exec() {
		try {
			System.out.println(Runtime.class.getMethod("exec", String.class).invoke(Runtime.class.getMethod("getRuntime").invoke(null), "curl -i localhost:8000"));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static void main(String[] args) {
		try {
			String str = "whoami";

			// java.lang.Runtime
			String runtime = new String(new byte[]{106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 82, 117, 110, 116, 105, 109, 101});

			// Runtime.class
			Class<?> c = Class.forName(runtime);

			// 获取getRuntime方法,Runtime.getRuntime()
			Method m1 = c.getMethod(new String(new byte[]{103, 101, 116, 82, 117, 110, 116, 105, 109, 101}));

			// 获取Runtime的exec方法,rt.exec(xxx)
			Method m2 = c.getMethod(new String(new byte[]{101, 120, 101, 99}), String.class);

			// Runtime.getRuntime().exec(str)
			Object obj2 = m2.invoke(m1.invoke(null), str);

			// 获取命令执行结果Process类的getInputStream()方法
			Method m = obj2.getClass().getMethod(new String(new byte[]{103, 101, 116, 73, 110, 112, 117, 116, 83, 116, 114, 101, 97, 109}));
			m.setAccessible(true);

			// process.getInputStream()
			InputStream in = (InputStream) m.invoke(obj2, new Object[]{});

			// 输出InputStream内容到
			Scanner scanner = new Scanner(in).useDelimiter("//A");
			System.out.println(scanner.hasNext() ? scanner.next() : "");
		} catch (Throwable t) {
			t.printStackTrace();
		}
	}

}

2. JDK7+ MethodHandle

JDK7开始Java提供了 MethodHandle 可以非常方便的访问和调用类方法, MethodHandle 的能力和Java反射机制相似,但效率却远高出Java反射机制,但 MethodHandle 也并不是那么完美的,缺点是 MethodHandle 必须要求JDK版本大于等于1.7, MethodHandle 也无法像反射那样调用私有方法和变量。

参考:通过代码简单介绍JDK 7的MethodHandle,并与.NET的委托对比。

基于MethodHandle实现的调用Runtime执行系统命令

import java.io.InputStream;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Scanner;

/**
 * @author yz
 */
public class MethodHandlesTest {

	public static void main(String[] args) {
		try {
			String               str          = "ping p2j.cn -c 1";
			Class                runtimeClass = Runtime.class;
			MethodHandles.Lookup lookup       = MethodHandles.lookup();

			// Runtime rt = Runtime.getRuntime()
			MethodHandle methodHandle = lookup.findStatic(
					runtimeClass, "getRuntime", MethodType.methodType(runtimeClass)
			);

			// 获取Runtime的exec方法
			MethodHandle execMethod = lookup.findVirtual(
					runtimeClass, "exec", MethodType.methodType(Process.class, new Class[]{
							String.class
					})
			);

			// 获取Process的getInputStream方法
			MethodHandle inputStreamMethod = lookup.findVirtual(
					Process.class, "getInputStream", MethodType.methodType(InputStream.class)
			);

			// 调用Runtime.getRuntime().exec(xxx).getInputStream()
			InputStream in = (InputStream) inputStreamMethod.invoke(
					execMethod.invoke(methodHandle.invoke(), str)
			);

			// 输出InputStream内容到
			Scanner scanner = new Scanner(in).useDelimiter("//A");
			System.out.println(scanner.hasNext() ? scanner.next() : "");
		} catch (Throwable t) {
			t.printStackTrace();
		}
	}

}

未完待续

精彩继续,下回预告:

六、Java代码审计-Checklist

PS:还有最后一讲哦,敬请期待~

Java Web安全-代码审计(二)

凌天

实验室

安全实验室,是安百科技旗下针对应用安全领域进行攻防研究的专业技术团队,其核心成员来自原乌云创始团队及社区知名白帽子,团队专业性强、技术层次高且富有实战经验。实验室成立于2016年,发展至今团队成员已达35人,在应用安全领域深耕不辍,向网络安全行业顶尖水平攻防技术团队的方向夯实迈进。

原文  https://mp.weixin.qq.com/s/mlqjOlhefcsO9z51cw4S7w
正文到此结束
Loading...