前言 前一阵遇到一个很有意思的问题,Dubbo客户端调用服务端接口,报错了,抛了一个异常,结果到了客户端这边,却变成了一个HashMap,而HashMap里面存放的正是报错信息!
当时的第一反应就是不可能,这不可能。
排查问题 虽然嘴上说不可能,但是事实就摆在眼前,没办法,也只能接受这个现实。那接下来就是漫长的排查问题了(其实也还好,就一个多小时)。
经过漫长的排查,最终定位到了Hessian中的ClassFactory这个类。ClassFactory这个类看名字就知道干什么的了,负责类的加载。当我们调用Dubbo接口返回结果时,就是通过这个类来加载类信息的。
我们可以看下它的加载代码:
1 2 3 4 5 6 7 8 9 10 public Class<?> load(String className) throws ClassNotFoundException { if (isAllow(className)) { return Class.forName(className, false , _loader); } else { log.log(Level.SEVERE, className + " in blacklist or not in whitelist, deserialization with type 'HashMap' instead." ); return HashMap.class; } }
从代码上,我们就能看出来,当指定的类不被允许时,就会返回HashMap类信息。
但是为什么我们的类不被允许加载呢???
继续看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 private boolean isAllow (String className) { ArrayList<Allow> allowList = _allowList; LinkedList<Allow> allowList = _allowList; if (allowList == null ) { return true ; } if (_allowClassSet.containsKey(className)) { return true ; } int size = allowList.size(); for (int i = 0 ; i < size; i++) { Allow allow = allowList.get(i); Boolean isAllow = allow.allow(className); if (isAllow != null ) { if (isAllow) { _allowClassSet.put(className, className); } return isAllow; } } if (_isWhitelist) { return false ; } _allowClassSet.put(className, className); return true ; }static { _staticAllowList = new ArrayList <Allow>(); _staticAllowList.add(new Allow ("java\\..+" , true )); ClassLoader classLoader = ClassFactory.class.getClassLoader(); try { String[] denyClasses = readLines(classLoader.getResourceAsStream("DENY_CLASS" )); for (String denyClass : denyClasses) { if (denyClass.startsWith("#" )) { continue ; } _staticAllowList.add(new AllowPrefix (denyClass, false )); } } catch (IOException ignore) { } }
通过上述代码,我们可以看到,Hessian在内部维护了一份DENY_CLASS名单。如果你的结果类在这个名单里面或者父类在这个名单里面(通过类路径匹配),抱歉,你只能获得一份HashMap。
解决办法也很简单,避开这份名单就好。
参考: Add Default Deny List
参考: CVE-2022-39198 Apache Dubbo Hession Deserialization分析