首页/新闻资讯/正文详情

Flask SSTI实战:从模板注入原理到过滤绕过与flag读取

发布时间:2026/9/26 10:02:08 来源:云帆数科 栏目:资讯中心
Flask SSTI实战:从模板注入原理到过滤绕过与flag读取
1. 一道 Flasks SSTI 题为什么能让新手卡三天先说结论Simple_SSTI_2 是 Bugku 上一道入门级 Flask 模板注入题但它卡住的人绝对不比那些中型难题少。原因很简单——这题考的不仅仅是你能不能打出{{7*7}}而是你有没有把服务端渲染、模板上下文、过滤器白名单、WAF 绕过这一整条链路想清楚。很多人在第一步就走了弯路一上来就疯狂尝试各种语句结果永远卡在flag读取那一环。我最早做这道题的时候其实也一度以为它要做的是“命令执行”因为在页面上能看到一个像查询功能的输入框。我试了id、ls这类命令发现全都无效。这时候如果你怀疑“自己思路错了”那方向就对了——这题根本不是命令注入而是模板注入。你要注入的不是 shell 命令而是 Jinja2 模板表达式。弄清楚这一点整道题的出口才真正打开。这道题的适用人群也很清楚刚入门 Web 安全的选手想搞懂 SSTI 原理却找不到手感的人以及已经会一点 Flask 但没实际打过模板注入题的开发向同学。这篇文章我要把整道题的思路、工具、关键 payload 的构造过程、踩坑点全部拆开讲一遍尤其会解释那些“为什么必须这样写”的底层逻辑尽量让你做完之后不只是拿到 flag而是真正理解 SSTI 是怎么回事。2. SSTI 到底在注入什么先补上这段基础2.1 模板引擎的工作方式它不是文本拼接在讲这题之前我必须先把 Flask 默认模板引擎 Jinja2 的原理讲清楚。很多人误解了模板注入的本质以为服务端是简单地拼接字符串然后执行但实际上 Jinja2 是先解析再渲染。举个例子正常服务端代码可能是这样写的from flask import Flask, request, render_template_string app Flask(__name__) app.route(/) def index(): name request.args.get(name, guest) return render_template_string(h1Hello, {{ name }}/h1, namename)这段代码里用户传的name是作为一个变量值被传入模板的。注意这里的关键用户在 URL 中传?nameworld服务端拿到字符串world将其作为变量name的值然后在模板渲染时{{ name }}会被替换成world。这个流程没有注入问题。但如果代码是这样写的app.route(/) def index(): name request.args.get(name, guest) return render_template_string(h1Hello, {{ name }}/h1)问题就出现了。用户传的name被直接拼进了模板字符串里然后再交给render_template_string解析渲染。此时如果你传name{{7*7}}那么最终拼接出来的模板字符串是h1Hello, {{ {{7*7}} }}/h1这就会导致异常。如果服务端代码对输入做了一定的拼接处理或者直接把整个用户输入放进模板里你传入的{*7*7}这类内容就会被当作模板语法执行。这就是 SSTI 的基本雏形。2.2 为什么{{7*7}}是万能探测点几乎所有 SSTI 题的入门探测都是这个表达式原因在于它简单、无副作用、易于观察。传进去之后如果页面显示49说明你的输入确实进入了模板的渲染上下文并且作为表达式被执行了。如果显示的是原文{{7*7}}或一堆报错那就说明要么输入没被解析要么存在某种过滤机制。Bugku 上这题输入{{7*7}}后的返回是 49让你确认两件事模板引擎是 Jinja2。用户输入会被直接渲染。存在某种基于黑名单的过滤后面会讲。过滤方式是替换或删除敏感字符串而非彻底拦截。2.3 从模板表达式到任意读文件中间需要什么很多新手会问{{7*7}}能执行了但这离读取文件还远着呢。你需要理解一个关键概念在 Jinja2 模板中表达式能访问的范围取决于模板上下文。默认情况下上下文里只有你传入的变量和 Flask 内建的一些对象。但 Python 对象的属性访问和方法调用是可以通过魔术方法链不断延伸的。比如是一个字符串对象通过.__class__可以拿到它的类str通过str.__mro__可以拿到继承链找到object基类再通过object.__subclasses__()可以拿到当前 Python 解释器加载的所有类的列表。这个列表里包含了很多危险的类比如os._wrap_close、subprocess.Popen、warnings.catch_warnings、甚至是filePython2 中的内建文件类型。这就是一条完整的链路.__class__.__mro__[2].__subclasses__()拿到子类列表之后你可以从中筛选出能够执行命令的类比如subprocess.Popen然后通过它调用系统命令。或者你也可以找文件操作相关的类来直接读文件。这也就是为什么.__class__.__mro__[2]是 SSTI 的核心入口。3. 这道题的第一个坑过滤与绕过3.1 黑名单的内容字母与数字Bugku 这题的过滤方式很多人描述得很模糊但实际操作时你会遇到一个非常明显的问题——你输入常规的__class__这种带下划线的英文内容时会被过滤机制删除或替换。换句话说过滤规则针对的是字母和数字。这里的“过滤字母数字”有两种常见实现直接删除输入中所有的字母和数字。将包含某些关键词的内容删掉或替换为空。如果是第一种那你输入的整个字符串几乎被清空了基本上没法正常构造 payload。如果是第二种相对好处理一些。实际测试下来这题属于第二种它有自己的敏感列表包括但不限于class、mro、subclasses等常见 SSTI 关键词。3.2 绕过思路字符串拼接与 Jinja2 的过滤器既然不允许直接出现敏感词那我们换个方式构造它们。Jinja2 有一个强大特性过滤器可以处理字符串。举个例子{{ [__class__] }}这样写之后Jinja2 在解析时会把它当成一个字符串字面量__class__来处理。它不会因为中间有拼接语法就阻止你因为这里并不涉及 Python 语法层面的拼接而是在 Jinja2 解析阶段就把两个字符串字面量合在一起了。所以__class__等同于__class__。这个绕过方式很基础但相当有效。在把这题卡住的人里相当一部分不是用不了这个技巧而是没想到可以拆开字符串。其实你用 Python 写代码的时候ab本来就是允许的只是很少有人在日常代码里这么写而已。另一种绕过方式是借助 Jinja2 的|attr过滤器。它是专门用来获取对象属性的比如{{ |attr(__class__) }}这条表达式等价于.__class__。而且|attr里面传的字符串也照样可以进行拼接配合起来就是完整的绕过组合拳{{ |attr(__class__)|attr(__mro__) }}3.3 实际测试哪些字符没被过滤在这个题里测试下来有几个关键点_下划线没有被过滤。.点号没有被过滤。单引号双引号没有被过滤。敏感英文单词会被过滤。数字似乎没有过滤。这就给了我们很大的操作空间。你可以在下划线和点号都合法的情况下去构造一个完整的 payload。当然不同的源码版本可能会有细微差别但整体思路是一致的。如果你在自己搭的测试环境里想要模拟这道题的过滤规则可以写一个简单的逻辑blacklist [class, mro, subclasses, flag, os, system, popen] def filter_blacklist(s): for word in blacklist: s s.replace(word, ) return s这种替换型过滤是最容易绕的因为你把它删掉之后只要用拼接的方式让敏感词出现在最终解析结果里即可。4. 构造完整攻击链从读取文件到拿到 flag4.1 获取所有子类的通用方式先不管这题的过滤我们要形成一套通用思维。常规 SSTI 读取文件的 payload 有很多种写法但它们共同的起点都是拿到object类的所有子类。Jinja2 里访问当前对象类的方式是.__class__拿到类对象之后通过__mro__拿到继承关系元组元组里最后一个元素通常是object。我习惯用[2]来定位它因为字符串对象的 MRO 通常是class str, class object但不同 Python 版本、不同对象它的位置可能有差异。更稳妥的方式是配合__base__比如.__class__.__base__这个方法能直接拿到底层基类不依赖于索引。4.2 定位文件读取类的思路拿到子类列表后你需要在这个列表里找到合适的目标。文件读取类的常见目标有_io.FileIO、_io.TextIOWrapper等。在 Python3 环境下_io.FileIO是一个非常好用的类它能以二进制方式打开文件。你不需要完全理解它内部机制只需要知道它的构造函数接收一个文件路径参数。筛选目标类的方式是在浏览器里逐个看但这样效率太低。我的习惯是先不筛选直接把整个列表打印出来再用 Python 脚本对列表内容做索引匹配。当然在比赛中你可能不能运行脚本那就只能在页面里搜索关键词了。一种比较流行的方式是import subprocess for index, subcls in enumerate(.__class__.__mro__[2].__subclasses__()): if FileIO in str(subcls): print(index, subcls)在本地你有 Python 环境可以这么干但在线上题目中你要么靠经验直接猜常见索引要么就把整个名单打印出来然后逐段翻看。4.3 这道题的经典经典请求与注入点这道题的典型注入 URL 格式类似于/?flag{{...payload...}}是的传入参数名可能就是flag看起来就像一个查询功能。这里有个小细节注入点在查询参数中而不是在路径中。你直接把 payload URL 编码后放进查询参数即可。测试过程是这样的先访问/看到页面提示。输入{{7*7}}返回 49。尝试构造子类读取。由于过滤规则里class、mro、subclasses会被替换掉你就需要写成拼接形式来看待。4.4 过滤存在的幸与不幸拼接过滤词即可碰到这种“替换型过滤”我有一个经验过滤函数本身就是在帮你做字符串拼接。因为它只是去掉敏感词而不会在去除后检查上下文所以你可以这样绕过原始目标表达式.__class__.__mro__[2].__subclasses__()全部替换为拼接形式[__class__][__mro__][2][__subclasses__]()这样过滤函数去掉class和subclasses后原本的内容反而被破坏了不对这里需要仔细想一下。如果你直接传__class__过滤函数会把class这个词删掉于是剩下__。这个结果已经不是我们要的了。但如果传的是__class__过滤函数会在字符串里找class这个子串吗注意__class__里并没有连续完整的class字母因为中间被单引号截断了。所以过滤函数实际上不会匹配到它。然后在 Jinja2 解析阶段两个相邻字符串字面量__cl和ass__会被拼接为一个整体__class__。最终进入属性访问的就是完整的关键词。这就是拼接绕过的完整原理。4.5 读取 flag 文件的实际载荷因为 flag 文件通常是/flag或者/flag.txt最好的选择是找一个可以直接读取文件的类。在 Python3 的_io.FileIO位于子类列表中通过它我们可以实现文件读取。借用拼接过滤的思路最终的一个可用 payload 大概长这样/?flag{{%20[__class__][__mro__][2][__subclasses__]()[94](-1,/flag,-1).read()%20}}这里需要解释几个部分[94]这个索引值不是绝对的不同环境下_io.FileIO在子类列表中的位置不同通常分布在 90 到 110 之间。你需要实测确定。(-1, /flag, -1)这是FileIO构造函数的三个参数。第一个参数-1表示使用默认缓冲区第二是文件名第三是打开模式标志-1表示只读。这些参数比较复杂但在实际构造中传(-1, /flag, -1)是可行的。另一种写法是直接open(/flag).read()如果环境里有open函数的话。.read()文件读取方法把内容读出来。如果[94]不对你可以用更稳妥的方法来定位比如用列表遍历。但在线上环境通常你得做二分测试把[94]替换成不同的数字观察页面有没有出现文件内容或者异常回显。如果FileIO在这个环境里不可用另一个备选方案是借助os模块的popen执行命令然后读取输出。找到包含os类名的方式一般是通过warnings.catch_warnings的__init__方法中访问os。但这个思路链路更长对于这题来说属于备选方案。5. 实操记录我在解题时实际踩过的坑5.1 第一个坑误判注入类型我最早看到这个输入框时第一反应是 SQL 注入试了单引号、报错语句都没反应。后来看到{{7*7}}返回 49才确认是 SSTI。这个误判浪费了大概二十分钟。回头想想其实只要先输入一组特殊字符串比如${{7*7}}或者{{7*7}}就能快速识别模板引擎。遇到查询功能先别急着跑 SQLMap先确认后端有没有模板渲染。5.2 第二个坑过度关注索引忽略了过滤规则我在构造__subclasses__()时因为没有注意过滤规则直接传__subclasses__结果页面没有任何反应。尝试多次后才发现敏感词已经被过滤掉了。这个问题其实很典型——你以为是 WordPress 题目执行不了实际是关键词被替换了。排查方式很简单输入{{class}}看回显是class还是空。如果是空就说明这个关键词被过滤了。5.3 第三个坑Python 版本影响子类索引同一个题目环境在 Python3.7 和 Python3.10 下_io.FileIO的索引位置完全不同。所以网上很多 WriteUp 里写的[94]不一定适用于你的实例。如果你发现某个固定索引不行先不要慌试着用脚本把所有带 FileIO 的类名打印出来。在没有本地的脚本执行条件下你可以采用笨办法但也很有效用{{...__subclasses__()}}把整个列表回显出来然后复制内容到本地数索引。这种方式虽然麻烦但至少可以精确定位。还有一种方式是利用 Jinja2 的条件表达式和循环能力来做自动筛选。比如{% for c in .__class__.__base__.__subclasses__() %}{% if FileIO in c.__name__ %}{{ loop.index0 }}{% endif %}{% endfor %}这段代码会遍历所有子类并打印出FileIO类对应的索引位置。不过它本身也含有敏感词需要先拼接绕过。实际比赛中如果规则允许这个方法才是最优解。6. 常用替代 payload 与建议的调试流程6.1 三种类型的 payload 预案平时打 CTF我会在本地准备三类 SSTI payload 备忘类型一读文件{{ .__class__.__mro__[2].__subclasses__()[index](-1, /flag, -1).read() }}类型二执行命令优先找subprocess.Popen{{ .__class__.__mro__[2].__subclasses__()[index](cat /flag, shellTrue, stdout-1).communicate() }}类型三利用os模块{{ ().__class__.__base__.__subclasses__()[index].__init__.__globals__[os].popen(cat /flag).read() }}这三个方案是递进关系第一个不行换第二个第二个找不到相关的类就试第三个。实际做题时不要死磕一种。以下是类型的适用场景对比表格方便你出门前参考方案核心利用对象适用场景难点文件读取_io.FileIO只在拿到 flag 文件路径时子类索引定位命令执行subprocess.Popen文件读取受限但命令可执行构造函数参数复杂os模块链os.popen需要动态执行系统命令需要借助__globals__链6.2 调试时的操作顺序我给你梳理一套比较顺利的调试流程这个顺序在 Bugku 这道题以及其他大部分 Flask SSTI 题里都适用先用{{7*7}}确认模板注入。用{{class}}测过滤关键词。用{{[__class__]}}测属性访问是否可行如果不行再考虑拼接。用拼接形式访问__mro__和__subclasses__。打印子类列表找出可用的类及索引。构造最终读取 flag 的 payload。如果读取失败尝试命令执行方案。如果全部失败重新检查过滤规则是否还有其他限制。这套流程不仅适用于这题也可以作为 SSTI 类题目的通用方法论。环境千变万化但链路是不变的确认注入点、确认过滤器、构造访问链、寻找利用点。7. 常见问题速查SSTI 做题时总被卡住的地方我整理了几个高频问题基本上你在做题过程中遇到的“为啥我这个不行”都能在这里找到答案。7.1 输入{{7*7}}没回显 49是怎么回事可能性一页面渲染结果里{{7*7}}被转义了你看到的是原文。这种情况说明服务端做了输出编码不代表不能注入。你试试看传入{{7*7}}后页面源码里有没有 49。可能性二模板引擎不是 Jinja2而是 Twig、Smarty 等其他引擎语法有差异。可能性三注入点根本不在这个参数中可能藏在 Cookie、Header 里。7.2 为什么__class__老是被替换成空典型的黑名单替换。你可以尝试使用attr过滤器或者用拼接字符串。拼接的核心原理再强调一遍Jinja2 的解析阶段会把相邻字符串字面量合并黑名单无法提前感知这种合并后的结果。7.3 子类里找不到能用FileIO的索引可能你找错类型了。在不同 Python 版本中FileIO可能存在但可能不叫这个名字或者它的完整类名包含路径前缀。你可以先输出全部子类的类名搜索关键词io和File。如果你要自己快速写脚本定位在本地可以用这段代码import inspect classes .__class__.__mro__[2].__subclasses__() for i, c in enumerate(classes): if FileIO in str(c) or file in str(c).lower(): print(i, c)7.4 为什么FileIO读取空文件或报错FileIO读取文件时如果你给的路径是相对路径它会基于进程当前工作目录去解析。很多 CTF 环境里 flag 在根目录/flag少部分在/flag.txt但也不排除在/app/flag这种位置。如果你不确定可以先用命令执行方式执行ls和find / -name *flag*。7.5 页面无法回显怎么判断 payload 是否执行有一种情况是回显被注释掉或吞掉了。如果你执行的是命令类 payload它可能执行成功但输出没有显示。这时可以尝试把执行结果写入一个文件再通过文件读取接口去读:{{ .__class__.__mro__[2].__subclasses__()[index](cat /flag /tmp/result, shellTrue, stdout-1) }}然后再用文件读取类去读/tmp/result。7.6 如何判断当前环境到底是不是 Flask当你不确定模板引擎时用{{7*7}}和常见引擎的探测语法交叉验证。Jinja2 的{{...}}是最常见的Tornado 也是{{...}}但它可用的对象和过滤器会不同。如果 URL 路径带/static/或/login这类 Flask 特征可以先按 Flask 来试。8. 从这道题延伸出的通用方法论8.1 思维模型入口到目标的链路拆解我做渗透测试和 CTF 题目时有一个习惯先把目标链路画出来而不是盲目试探。SSTI 题目从入口到目标无非是这几层输入点查询参数、表单、头渲染点模板直接渲染用户输入过滤层黑名单、转义、长度限制执行链路属性访问、子类获取、对象利用目标动作读文件、命令执行、反弹 shell链路拆解的价值在于一旦某个环节卡住你能迅速判断是链路断了还是某一个节点需要替换方案。比如你在执行命令时发现os导入不了你可以换subprocess的方式而不必从头再来。8.2 本地搭个实验环境才最快我认为做 SSTI 题最有效的学习方式就是在本地搭一个与题目过滤规则相似的实验环境不断测试 payload。你不需要完全复现 Bugku 的源码只要能构造出“模板渲染黑名单替换”的环境即可from flask import Flask, request, render_template_string app Flask(__name__) blacklist [class, mro, subclasses, flag, os, system, popen] def filter_blacklist(s): for word in blacklist: s s.replace(word, ) return s app.route(/) def index(): query request.args.get(flag, ) return render_template_string({{ filter_blacklist(query) }})这个代码虽然不能完全等同于 Bugku 的源码但对于练习拼接绕过已经够了。你可以随意修改黑名单观察不同过滤规则下的绕过方式。把这道题彻底搞懂你对 SSTI 的理解会有一个质的飞跃。8.3 工具与辅助脚本如果你在命令行环境下调试可以用 Python 快速构建本地测试脚本也可以使用 Burp Suite 的 Repeater 反复发包。我建议准备一个小工具集合包括Python requests 脚本用于自动化发送 payload 并判断回显。一个本地 Flask 模拟环境方便快速测试。一个子类索引定位脚本。这些工具准备的时间不长但能显著提高刷题效率。9. 最后说点实在的SSTI 题的关键不是背 Payload在这个题上花了一整天之后我最大的体会是SSTI 的题目刷几十道不在于你会不会背华丽的绕过链而在于你有没有形成一个可以应对各种过滤规则的思维方式。所谓思维就是当你面对一个未知的过滤规则时能快速试出它是替换型还是阻断型能在不依赖网上现成 WriteUp 的情况下自主构造拼接字符串自主定位可用索引。这道 Bugku 题目本身并不复杂但它像一面镜子照出了很多初学者对模板渲染原理的模糊理解。如果你在做这道题时也卡了很久不要灰心我建议你回到基础亲手写一个 Flask 的render_template_string例子把用户输入直接拼进去运行多试几组 payload感受一下“输入是如何变成模板代码的”。这道题之后你可以继续挑战几道更复杂的 SSTI 题比如涉及{%...%}语句块、涉及多个过滤器叠加、涉及长度限制的场景。你会发现基础链路永远是那几条确认注入、探测过滤、构造链、找对象、执行动作。回到题目本身如果你按我上面的流程走完应该能拿到一个明确的结果flag 文件被成功读取页面回显了内容。如果你还想再深入一点不妨试着换一种绕过方式比如用编码解码的方式处理关键词或者用 Jinja2 的{% set %}语句来赋值这些玩法都能帮你把 SSTI 的基础打得更加扎实。

相关推荐

AI部署卡点本质是协作契约缺失
AI部署卡点本质是协作契约缺失

1. 这不是技术问题,是协作链路的系统性断裂“算法选好了,预算批了,项目却卡在部署上:改一次参数,等一次研发,工期就这么拖没了”——这句话我去年在三个不同行业的客户现场都听过,语气从困惑到疲… · 2026/9/26 10:02:08

PHP反序列化漏洞入门:从魔术方法到Payload构造实战
PHP反序列化漏洞入门:从魔术方法到Payload构造实战

提到 SWPUCTF 新生赛,很多新手第一反应是去背exp、抄脚本。但“[SWPUCTF 2022 新生赛]1z_unserialize”这道题,核心就一个unserialize,是我见过最适合把反序列化漏洞从“会抄”变成“会看”的入门题。它不考验什么高深技巧,就是把… · 2026/9/26 10:02:08

AWD作战管理台实战:集中信息与自动化攻防的完整落地指南
AWD作战管理台实战:集中信息与自动化攻防的完整落地指南

1. 项目缘起:为什么我需要一个AWD作战管理台先说结论:AWD(Attack With Defense,攻防兼备)比赛不是一个人能撑起来的游戏,但很多队伍实际打起来,却常常变成“一个人扛三路”的局面。我在打CTF的第… · 2026/9/26 10:02:02

Eclipse 报错 failed to create the Java virtual machine:TaoToken 图文解析 JVM 启动参数配置
Eclipse 报错 failed to create the Java virtual machine:TaoToken 图文解析 JVM 启动参数配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 10:40:27

OpenClaw部署太繁琐?用TypeScript+Docker轻量方案配TaoToken,告别token消耗焦虑
OpenClaw部署太繁琐?用TypeScript+Docker轻量方案配TaoToken,告别token消耗焦虑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 10:40:27

Claude Code提示词案例:用Element Plus el-form搭建联系我们页面表单校验骨架
Claude Code提示词案例:用Element Plus el-form搭建联系我们页面表单校验骨架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 10:40:27

本地模型+静态规则:open-code-review 打造高效代码审查工具
本地模型+静态规则:open-code-review 打造高效代码审查工具

代码审查这件事,我在不同团队里见过太多版本了。有的团队强调“必须走Review才能合并”,结果就是凌晨三点有人挂着一个“LGTM”表情包敷衍了事;有的团队配置了一堆静态检查工具,但规则常年没人维护,跑出来的告警列表比… · 2026/9/26 10:40:27

CMO行为与决策模型-交战条令模型详解
CMO行为与决策模型-交战条令模型详解

第7章 行为与决策模型 前六章描述的是“物理世界如何演化”,本章描述的是“作战方如何决策”。引擎的决策体系是严格分层的:阵营(Side)持有全局条令与目标优先级 → 条令(Doctrine)逐级继承并可在任务、编… · 2026/9/26 10:40:21

Antigravity 配置 boxtool 与 context7 MCP 服务:settings.json 骨架与连通性验证
Antigravity 配置 boxtool 与 context7 MCP 服务:settings.json 骨架与连通性验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 10:40:21

数据库课后习题答案别硬背:当测试用例集刷,效率翻倍
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍

简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21

OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40

向下兼容与向上兼容:接口设计中的兼容性策略与工程实践
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践

一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码