|
去年冬天排查一个线上告警,序列日志里频繁出现ClassCastException和InvalidClassException,化漏堆栈指向Dubbo的洞排DecodeableRpcInvocation。当时第一反应是序列序列化配置出了问题,但翻遍代码也没找到异常类。化漏后来抓包看了下TCP流,洞排发现请求体里藏着一串base64编码的序列ysoserial特征字符串——典型的反序列化攻击尝试。 Dubbo的化漏反序列化漏洞不是新鲜事,但排查起来容易踩坑。洞排核心就一条:攻击者把恶意序列化数据塞进RPC请求体,序列服务端反序列化时触发readObject里的化漏gadget链。Dubbo默认用Hessian2,洞排但很多老项目还开着Java原生序列化,序列或者Kryo、化漏FST没配白名单。洞排你如果看到com.alibaba.com.caucho.hessian.io或java.io.ObjectInputStream相关的异常,别急着改代码,先确认序列化协议。 具体怎么查?我习惯从流量入手。Dubbo默认端口20880,直接tcpdump抓包,过滤port 20880,用Wireshark打开看TCP payload。恶意请求通常有这几个特征:请求头里serialization字段是java或hessian2,但body长度异常大;或者body开头出现aced0005(Java序列化魔数)或caca(Hessian2魔数),后面跟着java.util.PriorityQueue、com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl这类gadget类名。有一次我甚至看到请求体里直接明文写了bash -i >& /dev/tcp/,攻击者连编码都懒得做。 如果流量抓不到,就看服务端日志。Dubbo在反序列化失败时会打印HessianFieldException或JavaDeserializationException,但更隐蔽的是那些没抛异常却导致CPU飙升的请求。我遇到过一种情况:攻击者用HashMap构造哈希碰撞,反序列化时触发hashCode()死循环,线程池瞬间打满。这种不会报错,只能靠jstack看线程栈,如果大量线程卡在HashMap.hash()或readObject,基本可以确认。 排查完流量和日志,还得检查依赖。Dubbo本身不负责gadget拦截,但commons-collections、fastjson、groovy这些库如果版本过低,就是现成的利用链。用mvn dependency:tree搜一下commons-collections:3.1、fastjson:1.2.24,有就赶紧升。另外Dubbo的serialization配置里,java和nativejava直接禁掉,hessian2要开whitelist,比如serialization.security.whitelist=com.yourpackage。别用*,那等于没开。 有个细节容易忽略:Dubbo的TokenFilter和AccessLogFilter默认不拦反序列化。真正起作用的是SerializationSecurityManager,但它在2.7.8之后才默认开启。如果你还在用2.6.x,要么升级,要么自己写Filter在onResponse之前检查DecodeableRpcInvocation的serializationId。我见过一个团队用@Activate(group = "provider")自定义过滤器,把java序列化的请求直接throw new RpcException("blocked"),简单有效。 最后说个真实案例。某金融公司内网Dubbo服务被反序列化打穿,攻击者用CommonsCollections6执行了curl下载挖矿脚本。事后复盘发现,他们网关只做了HTTP层WAF,Dubbo的TCP流量根本没进检测。所以排查不能只看应用日志,网络层抓包和依赖扫描得同步做。现在我看到aced0005开头的数据包,第一反应就是关掉Java原生序列化——这玩意儿在RPC场景里,跟把Runtime.exec暴露在公网没区别。 |