如何跟踪log4j漏洞原理及发现绕WAF的tips( 二 )


ldap://192.168.34.96:1389:/a",而后var2可以使用getHost和getPort方法获取host和port,说明var2对象在创建时解析了ldap地址 。跟进LdapURL类到达Uri#parse方法

  • com.sun.jndi.toolkit.url.Uri#parse
private void parse(String var1) throws MalformedURLException {int var2 = var1.indexOf(58);if (var2 < 0) {throw new MalformedURLException("Invalid URI: " + var1);} else {this.scheme = var1.substring(0, var2);++var2;this.hasAuthority = var1.startsWith("//", var2);int var3;if (this.hasAuthority) {var2 += 2;var3 = var1.indexOf(47, var2);if (var3 < 0) {var3 = var1.length();}int var4;if (var1.startsWith("[", var2)) {var4 = var1.indexOf(93, var2 + 1);if (var4 < 0 || var4 > var3) {throw new MalformedURLException("Invalid URI: " + var1);}this.host = var1.substring(var2, var4 + 1);var2 = var4 + 1;} else {var4 = var1.indexOf(58, var2);int var5 = var4 >= 0 && var4 <= var3 ? var4 : var3;if (var2 < var5) {this.host = var1.substring(var2, var5);}var2 = var5;}if (var2 + 1 < var3 && var1.startsWith(":", var2)) {++var2;this.port = Integer.parseInt(var1.substring(var2, var3));}var2 = var3;}var3 = var1.indexOf(63, var2);if (var3 < 0) {this.path = var1.substring(var2);} else {this.path = var1.substring(var2, var3);this.query = var1.substring(var3);}}}此时var1="
ldap://192.168.34.96:1389/a"
  • var2第一次赋值为(char)58也就是 : 在ldap中的索引,如果不存在 : 则直接报错
  • this.scheme赋值为第1个字符到 : 之间的字符串,也就是ldap、ldaps
  • var2第二次赋值自加1,而后检查冒号后是否存在//,如果不存在,则host和port都直接为null,进入path和query解析部分,也就是路径和参数
  • 第一个冒号后存在//,则进入if语句,var2第三次赋值,再加2,也就是跳过了//继续向后判断
  • (char)47 也就是/,给var3=var1.indexOf("/", var2),实际上为://后第一个/的索引,这是用来找到host和port的一个定位,但很有可能后面没有/(即var1="ldap://192.168.1.1:1389",此时var3直接赋值为var1.length,也就是var1最大索引+1)
  • 再往下走,会先判断://和var3直接有没有 [ 和 ] 符号对,且 ] 不能在var3后面否则会直接报错,这里有个意外情况就是ldap://[localhost:1389]/a这样写的话,会将localhost:1389当成host
  • 如果没有出现[]符号对,则赋值var4为://后的第一个:的索引,然后判断var4>=0 且 var4<=var3,也就是冒号:必须存在且在var3的前面,条件达成则赋值为var5=var4,否则var5=var3,即从://和:之间获取host,或者从://和/之间获取host 。此时出现骚操作"ldap://localhost/:",则host=localhost,骚操作"ldap://localhost",则host=null
  • 继续往后走,如果正常在://和var3之间出现冒号,则可以截取出port,如果前面的骚操作"ldap://localhost/:",则port为默认值-1,这个-1在后面大有可为:)
后面解析path和query的部分就不看了,回到
com.sun.jndi.url.ldap.LdapURLContextFactory#getUsingURLIgnoreRootDN也就是上面那个图片的位置,此时host和port都解析好了,正式开启发起ldap请求
LDAP发起
com.sun.jndi.url.ldap.LdapURLContextFactory#getUsingURLIgnoreRootDN,执行到new LdapCtx("", var2.getHost(), var2.getPort(), var1, var2.useSsl()),即此时LdapURL已经解析完成,host和port都有了,跟进LdapCtx的构造方法,代码如下
public LdapCtx(String var1, String var2, int var3, Hashtable<?, ?> var4, boolean var5) throws NamingException {this.useSsl = this.hasLdapsScheme = var5;if (var4 != null) {this.envprops = (Hashtable)var4.clone();if ("ssl".equals(this.envprops.get("java.naming.security.protocol"))) {this.useSsl = true;}this.trace = (OutputStream)this.envprops.get("com.sun.jndi.ldap.trace.ber");if (var4.get("com.sun.jndi.ldap.netscape.schemaBugs") != null || var4.get("com.sun.naming.netscape.schemaBugs") != null) {this.netscapeSchemaBug = true;}}this.currentDN = var1 != null ? var1 : "";this.currentParsedDN = parser.parse(this.currentDN);this.hostname = var2 != null && var2.length() > 0 ? var2 : "localhost";if (this.hostname.charAt(0) == '[') {this.hostname = this.hostname.substring(1, this.hostname.length() - 1);}if (var3 > 0) {this.port_number = var3;} else {this.port_number = this.useSsl ? 636 : 389;this.useDefaultPortNumber = true;}this.schemaTrees = new Hashtable(11, 0.75F);this.initEnv();try {this.connect(false);} catch (NamingException var9) {try {this.close();} catch (Exception var8) {}throw var9;}}这里主要关注hostname和port_number两个参数,即下面的代码块


推荐阅读