Java代码审计
Posted 谷哥的小弟
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java代码审计相关的知识,希望对你有一定的参考价值。
版权声明
- 本文原创作者:谷哥的小弟
- 作者博客地址:http://blog.csdn.net/lfdfhl
代码审计概述
代码审计是一种安全测试方法,它通过对软件应用程序代码的静态分析和动态测试来确定应用程序中存在的安全漏洞。其主要目的是检测应用程序中可能被攻击者利用的安全漏洞,如输入验证问题、访问控制问题、缓冲区溢出、SQL注入等。通过进行代码审计可以发现应用程序中的潜在安全漏洞并提出修复建议,以提高应用程序的安全性。
代码审计的主要工作包括以下几个方面:
-
静态分析:对源代码进行静态分析,寻找漏洞,包括但不限于输入验证问题、访问控制问题、缓冲区溢出、SQL注入等。
-
动态测试:对应用程序进行动态测试,模拟黑客攻击,以找到潜在漏洞。
-
修复建议:根据发现的漏洞提出修复建议,并协助开发人员进行漏洞修复。
-
报告撰写:总结检测结果,形成详细的审计报告,包括漏洞描述、危害等级和修复建议等信息。
-
安全规范审核:对代码是否符合安全开发规范进行审核,如密码强度、加密算法、访问控制等。
Java与代码审计
Java代码审计主要是指对基于Java开发的应用程序的代码进行安全审计。Java作为一种广泛使用的编程语言,其在Web应用程序、客户端应用程序等各个领域中得到了广泛的应用。而Java应用程序中可能存在的漏洞也是由Web应用程序漏洞(如SQL注入、文件上传漏洞等)到Java虚拟机漏洞,从常见的认证授权缺陷到加密算法弱点等。而Java代码审计就是对基于Java的各种应用程序,包括但不限于CMS、电商平台、OA系统等进行源代码分析、漏洞检测和修复建议等一系列工作。
Java代码审计通常需要使用一些专门的工具,如JArchitect、FindBugs、Fortify等。这些工具可以自动化地检测源代码中的潜在漏洞,并给出修复建议。同时,Java代码审计还需要结合静态分析和动态测试来进行综合判断。
代码审计工具
代码审计工具可以分为自动化审计和人工审计两类。其中,自动化审计是以自动化工具的方式查找代码的安全漏洞,这样的工具一般称为静态代码检测工具(SAST)。常见的SAST工具有:
- Fortify
- Checkmarx
- FindBugs
- PMD
- SonarQube
以上工具都有其各自的优缺点,不同的工具适用于不同的场景和需求。另外,人工审计优势在于查找业务逻辑相关漏洞,但对应成本也远高于自动化工作。
代码审计报告
代码审计报告的具体格式和内容可以因不同情况而异,但是一般应包括以下方面的内容:
-
报告概览:对项目的总体情况进行概述。
-
漏洞列表:列出所有发现的漏洞及其危害等级、漏洞描述、修复建议等信息。
-
数据库安全:介绍数据库的安全性能以及存在的问题。
-
代码结构:介绍源代码结构的组成部分、设计原则等。
-
代码规范:介绍代码是否符合规范,并提出问题和建议。
-
总结和建议:对整个项目存在的问题进行总结,并提出修复建议
Java 代码审计 — 2. Reflection
参考:
简介
反射机制是 java 语言的动态性的重要体现,也是 java 的各种框架底层实现的灵魂。通过反射我们可以:
- 获取到任何类的成员方法 (
Methods
)、成员变量 (Fields
)、构造方法 (Constructors
) 等信息。 - 动态创建 java 类实例、调用任意的类方法、修改任意的类成员变量值等。
总而言之,程序在运行时的行为是固定的,如果想在运行时改变,就需要用到反射技术。
java 反射在编写漏洞利用代码、代码审计、绕过 RASP 方法限制等中起到了至关重要的作用。
假想一个场景,如果我们需要根据用户输入来动态的创建类对象。可能会想到这样的代码。
# className 为用户输入的动态参数。
String className = "java.lang.Runtime";
Object object = new className();
但这个操作是不行的,java 静态编译特性决定了编译无法通过。而借助反射机制可以完成这个目的。
练习中学习反射
我们以 java.lang.Runtime
为例,因为它有一个 exec 方法可以执行系统命令,所以在很多 exp 中都能看到通过反射调用它来 rce 。这块我们就尝试通过它来执行系统命令。
在进入代码之前,介绍下基本步骤:
-
获取目标类 Class 对象,以便获取目标类的构造方法。
-
获取目标类构造方法,以便创建目标实例。
因为 Runtime 构造方法是 private 的,无法直接调用,所以需要获取到修改一下访问权限。
-
创建目标实例,以便调用执行类中的方法。
-
获取目标类中要执行的方法,并调用执行该方法。
-
获取执行输出。
// 获取Runtime类对象
Class runtimeClass1 = Class.forName("java.lang.Runtime");
// 获取构造方法。
Constructor constructor = runtimeClass1.getDeclaredConstructor();
// 因为构造方法是 private 的,无法直接调用,所以需要修改方法的访问权限。
// 创建Runtime类示例,等价于 Runtime rt = new Runtime();
constructor.setAccessible(true);
Object runtimeInstance = constructor.newInstance();
// 获取Runtime的exec(String cmd)方法。
Method runtimeMethod = runtimeClass1.getMethod("exec", String.class);
// 调用exec方法,等价于 rt.exec(cmd)
Process process = (Process) runtimeMethod.invoke(runtimeInstance, cmd);
// 获取命令执行结果
InputStream in = process.getInputStream();
// 输出命令执行结果
System.out.println(IOUtils.toString(in, "GBK"));
获取 Class 对象
java 反射操作的是 java.lang.Class
对象,所以我们需要先想办法获取到这个对象,通常我们有如下几种方式获取一个类的 Class 对象,以 java.lang.Runtime
为例:
String className = "java.lang.Runtime";
Class runtimeClass1 = Class.forName(className);
Class runtimeClass2 = java.lang.Runtime.class;
Class runtimeClass3 = ClassLoader.getSystemClassLoader().loadClass(className);
// 通常也可以通过 对象实例.getClass() 这种方式获取。但是 java.lang.Runtime 这个类的构造方法是私有的,不能直接通过 new 创建对象实例。
// Class runtimeClass4 = runtimeInstance.getClass();
- 几个获取 Class 的方式有些区别,涉及是否初始化目标类的问题,详见文章末尾。
- 如果需要反射内部类,则有 特殊的语法
获取构造方法
因为我们最终要执行 exec 函数是需要一个对象实例的,所以我们需要创建一个对象实例,并且由于 Runtime 的构造方法是私有的,所以我们需要使用 constructor 对象来修改访问权限。
从 Runtime 类代码注释,可以看到它本身是不希望除了其自身以外的任何人去创建该类实例,因此我们没办法 new
一个 Runtime 类实例。我们可以借助反射机制,修改方法访问权限从而间接的创建出了 Runtime 对象。
下面是 Class 对象获取构造方法的相关函数。
-
getConstructor
和getDeclaredConstructor
前者只能获取到公有的构造方法,而后者可以获取到所有构造方法。
创建类实例
获取到 Constructor
以后我们可以通过 constructor.newInstance()
来创建类实例。
- 若无访问权限,则可以使用
constructor.setAccessible(true)
进行修改。
获取类方法
为了执行 exec 这个方法,我们需要获取到这个方法。
下面是 Class 对象获取方法的相关函数。
-
getMethod
和getDeclaredMethod
前者会返回当前类公有方法和继承的公有方法,而后者会返回当前类所有方法。
调用类方法
获取到 java.lang.reflect.Method
对象后,我们可以通过其 invoke
方法来调用该方法。
- 如果调用的是 static 方法,则实例对象需要传入
null
- 若无调用权限,则可以使用
method.setAccessible(true)
进行修改
修改类的成员变量
Java 反射不但可以获取类所有的成员变量名称,还可以无视权限修饰符实现修改对应的值。
-
getField
和getDeclaredField
前者会返回当前类公有字段和继承的公有字段,而后者会返回当前类所有字段。
-
若无修改权限,则可以使用
field.setAccessible(true)
进行修改。 -
若修改 final 属性的变量,则需要 特殊的语法
其它
初始化
以下顺序的代码块,哪个会先执行呢?
import org.junit.Test;
class TestInit{
{
System.out.println("{}");
}
static {
System.out.println("static{}");
}
public TestInit(){
super();
System.out.println("public TestInit(){}");
}
}
public class TestXXX {
@Test
public void test1(){
try {
String className = "JNDI.TestInit";
Class.forName(className);
// 触发 static{}
// Class.forName(className,false,this.getClass().getClassLoader());
//都不触发
// Class runtimeClass2 = TestJNDI.class;
//都不触发
// Class runtimeClass3 = ClassLoader.getSystemClassLoader().loadClass(className);
// 都不触发
// Class runtimeClass4 = new TestInit().getClass();
// 触发顺序 static{} , {} , public TestInit(){}
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 关于类中的代码段
- static{} 是在类初始化时调用的。
- {} 代码会放在构造函数中 super() 后,但当前构造函数内容前。
- 关于几种获取 Class 对象方法
- forName 第二个参数就是控制是否执行类的初始化,默认为 true。
以上是关于Java代码审计的主要内容,如果未能解决你的问题,请参考以下文章