当前位置:首页 > 文章列表 > Golang > Go问答 > Go html/template 生成 javascript URL 为什么被过滤

Go html/template 生成 javascript URL 为什么被过滤

来源:17golang原创 2026-10-20 20:43:56 0浏览 收藏

如果 Go 的 html/template 把你传入的 javascript:... 输出成 #ZgotmplZ,通常不是模板写错了,而是它在 href 这类 URL 上下文中主动拦截了危险协议。这样做是为了避免不可信数据变成可执行脚本。

官方地址:https://pkg.go.dev/html/template

这类问题的处理顺序很简单:先确认需求是不是“跳转到一个地址”,如果是,就改成 https 或站内路径;如果确实需要特殊协议,再由服务端对固定来源做 allowlist,最后才考虑 template.URL。不要把用户输入直接强转成可信类型。

为什么 javascript: 会变成 #ZgotmplZ

html/template 和 text/template 的关键区别是,它会结合 HTML、CSS、JavaScript、URI 等上下文进行转义。href="{{.URL}}" 中的值不是普通文本,而是一个可能影响浏览器行为的 URL。

对于普通字符串,模板包会检查协议。http:、https: 和 mailto: 属于文档明确列出的允许协议;其它协议会按不安全处理。命中 URL 过滤器后,输出中的值会被替换成 #ZgotmplZ,这正是它的运行时占位标记。

html/template 在 URL 上下文中从普通字符串经过协议过滤得到安全占位结果的静态结构图
图1:URL 上下文、协议过滤器和 #ZgotmplZ 的关系示意图;这是静态说明图,不是运行截图或执行证据。

普通字符串和 template.URL 不是一回事

默认情况下,模板把数据当成不可信的普通文本。下面的例子用同一个模板展示普通字符串和可信 URL 类型的职责差异:前者会被过滤,后者表示调用方已经完成了额外的安全判断。

package main

import (
	"os"
	"html/template"
)

func main() {
	// URL 上下文会对普通字符串做协议过滤。
	t := template.Must(template.New("link").Parse(`打开`))
	_ = t.Execute(os.Stdout, "javascript:alert('blocked')")

	// template.URL 只适合来自可信、固定来源的已审查值。
	trusted := template.URL("javascript:trustedAction()")
	_ = t.Execute(os.Stdout, trusted)
}

这里的第二种写法不是“关闭安全检查”的通用修复方案。官方文档明确提示,template.URL 的内容会按原样进入模板,因此只要来源可被用户控制,类型转换本身就可能制造注入风险。多数业务不应该让动态脚本通过 href 传递。

更稳妥的改法:让链接表达导航

如果点击动作本质上是打开页面、提交确认或进入下一步,优先把它建模成一个普通 HTTP 路由。脚本需要的业务参数放进查询参数或服务端会话中,模板只负责输出可导航的 URL。

package main

import (
	"html/template"
	"net/url"
	"os"
)

type PageData struct {
	ActionURL string
}

func main() {
	// 用 url.Values 编码参数,避免手工拼接破坏 URL 结构。
	q := url.Values{}
	q.Set("next", "/orders")
	data := PageData{ActionURL: "/confirm?" + q.Encode()}

	// 普通站内路径由 html/template 按 URL 上下文安全输出。
	t := template.Must(template.New("page").Parse(`继续`))
	_ = t.Execute(os.Stdout, data)
}

如果前端需要根据状态决定行为,可以输出受限的 data-action 值,让已经加载的脚本通过事件监听器查表处理。这样“可执行代码”和“服务端数据”仍然是两条边界,模板不会把任意字符串当作 JavaScript。

从外部动作数据经过协议 allowlist 到可信 URL 和 href 输出的静态架构图
图2:把业务动作拆成 allowlist、可信 URL 和普通 href 的结构示意图;它用于解释边界,不代表某次程序运行结果。

必须使用特殊协议时怎样收紧边界

少数内嵌应用可能确实有固定的特殊协议需求。这时要把“允许哪些完整字符串”写成代码中的固定集合,而不是接收一个任意 URL 后调用 template.URL(value)。

package main

import (
	"errors"
	"html/template"
	"strings"
)

var errUnsafeURL = errors.New("unsafe action URL")

func trustedActionURL(name string) (template.URL, error) {
	// 只匹配固定动作,不把 name 拼进可执行脚本。
	switch strings.TrimSpace(name) {
	case "open-help":
		return template.URL("javascript:openHelp()"), nil
	case "close-panel":
		return template.URL("javascript:closePanel()"), nil
	default:
		return "", errUnsafeURL
	}
}

即使这样,也要确认固定脚本不包含外部输入,并把这段转换集中在少数可审查函数中。更推荐使用普通链接和事件映射;allowlist 只能缩小风险,不能把 javascript: 变成适合任意数据的协议。

几个容易混淆的边界

现象实际含义处理方式
#ZgotmplZ不安全值到达 URL 或 CSS 上下文检查协议和数据来源,不要只改输出字符串
template.URL调用方声明内容已经可信只接收固定或严格 allowlist 的值
JavaScript 上下文,不是 href URL 上下文传递结构化数据,让模板负责 JS 安全编码
template.HTML声明整段 HTML 已可信不要拿它解决 URL 协议问题

排查结论

遇到 “javascript URL 被过滤” 时,可以按下面的顺序处理:

  1. 确认占位值是否来自 href、src 等 URL 上下文。
  2. 检查输入是不是普通字符串,以及协议是否为 http、https 或站内相对路径。
  3. 若需求只是导航,改用普通路由和参数编码。
  4. 若确有特殊协议,先做固定值 allowlist,再集中转换为 template.URL。
  5. 不要用 template.HTML、字符串替换或关闭转义来掩盖问题。

相关问题

为什么不是 Execute 返回错误?

URL 过滤属于模板输出安全处理的一部分,危险值通常会以 #ZgotmplZ 进入结果,而不是作为普通业务错误返回。

把 javascript: 改成 URL 编码可以吗?

不应把编码当成安全修复。应该先决定动作是否真的需要脚本,再选择普通路由或固定动作映射。

template.URL 能不能接收数据库字段?

只有当数据库字段来自受控配置、经过明确协议和内容 allowlist,并且不会混入用户可控脚本时才应考虑;普通业务字段不应直接转换。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go template Execute 失败后为什么已经输出半段内容Go template Execute 失败后为什么已经输出半段内容
上一篇
Go template Execute 失败后为什么已经输出半段内容
Go regexp 编译失败怎样在启动阶段暴露配置错误
下一篇
Go regexp 编译失败怎样在启动阶段暴露配置错误
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    569次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    614次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    641次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    601次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    418次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码