Medium.com如何实现安全嵌入内容而免受XSS攻击?
如何在Django+Medium Editor中安全实现类似Medium的链接嵌入功能
首先得明确核心原则:绝对不能让用户直接提交或控制HTML代码,包括<iframe>和<script>。Medium的逻辑也是用户只给链接,服务端自己生成安全的嵌入代码——这是防XSS的关键。下面结合Django生态,一步步实现:
1. 先搞个可信嵌入源白名单
你得先定义哪些平台是允许嵌入的,比如YouTube、Gist、Vimeo这些。白名单要精确到域名和路径格式,比如:
- YouTube只允许
https://www.youtube.com/embed/[视频ID]这种结构的链接 - Gist只允许
https://gist.github.com/[用户名]/[GistID]
为什么要这么严格?因为如果只检查域名,攻击者可能用https://youtube.com.evil-domain.com/xxx这种钓鱼域名绕过,所以必须同时验证域名和路径格式。
2. 服务端做链接解析与验证(核心安全环节)
在Django里写一个专门处理嵌入链接的视图,流程是:
- 接收用户粘贴的纯文本链接
- 用
urllib.parse.urlparse解析链接,提取域名、路径 - 把域名标准化(比如去掉
www.,统一转小写),检查是否在白名单里 - 对不同平台做路径格式校验(用正则表达式)
- 验证通过就生成安全的
<iframe>代码,不通过就转成普通链接
举个代码例子:
from django.http import JsonResponse from urllib.parse import urlparse import re # 定义可信嵌入源的配置:域名、路径正则、iframe模板 TRUSTED_EMBEDS = { "youtube.com": { "path_pattern": r"^/embed/[a-zA-Z0-9_-]+$", "iframe_template": '<iframe width="560" height="315" src="https://www.youtube.com/embed/{video_id}" sandbox="allow-same-origin allow-scripts allow-popups" frameborder="0" allowfullscreen></iframe>' }, "gist.github.com": { "path_pattern": r"^/[a-zA-Z0-9_-]+/[a-zA-Z0-9]+$", "iframe_template": '<iframe src="https://gist.github.com{path}.js" sandbox="allow-same-origin allow-scripts" frameborder="0"></iframe>' } } def process_embed_link(request): if request.method != "POST": return JsonResponse({"error": "仅支持POST请求"}, status=400) raw_link = request.POST.get("link", "").strip() if not raw_link.startswith(("http://", "https://")): return JsonResponse({"html": f'<a href="{raw_link}" target="_blank">{raw_link}</a>'}) parsed_url = urlparse(raw_link) domain = parsed_url.netloc.lower() # 处理带www的域名,比如www.youtube.com → youtube.com if domain.startswith("www."): domain = domain[4:] # 检查域名是否在白名单 if domain not in TRUSTED_EMBEDS: return JsonResponse({"html": f'<a href="{raw_link}" target="_blank">{raw_link}</a>'}) embed_config = TRUSTED_EMBEDS[domain] # 校验路径格式 if not re.match(embed_config["path_pattern"], parsed_url.path): return JsonResponse({"html": f'<a href="{raw_link}" target="_blank">{raw_link}</a>'}) # 生成安全的iframe iframe_html = "" if domain == "youtube.com": video_id = parsed_url.path.split("/")[-1] iframe_html = embed_config["iframe_template"].format(video_id=video_id) elif domain == "gist.github.com": iframe_html = embed_config["iframe_template"].format(path=parsed_url.path) return JsonResponse({"html": iframe_html})
3. 给iframe加上安全属性
生成iframe时一定要加sandbox属性,这是浏览器层面的安全限制,比如:
allow-same-origin:允许iframe访问自身的源(部分平台需要)allow-scripts:允许iframe执行脚本(YouTube、Gist都需要)allow-popups:允许iframe弹出窗口(比如YouTube的全屏按钮)- 不要加
allow-top-navigation,防止iframe跳转或控制父页面
这样即使嵌入的内容有意外风险,也无法突破iframe的沙箱限制。
4. 集成Medium Editor的粘贴事件
Medium Editor默认会把粘贴的链接转成普通<a>标签,我们需要监听粘贴事件,拦截纯文本链接,发送到后端处理:
// 初始化Medium Editor const editor = new MediumEditor('.editable', { placeholder: '开始写内容...', toolbar: { /* 你的工具栏配置 */ } }); // 监听粘贴事件 editor.elements[0].addEventListener('paste', async (e) => { const clipboardData = e.clipboardData || window.clipboardData; const pastedText = clipboardData.getData('text').trim(); // 简单判断是否是HTTP链接 if (/^https?:\/\/[^\s]+$/.test(pastedText)) { e.preventDefault(); // 阻止默认粘贴行为 try { // 发送到后端处理 const response = await fetch('/process-embed/', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'X-CSRFToken': getCSRFToken() // 记得获取Django的CSRF Token }, body: `link=${encodeURIComponent(pastedText)}` }); const data = await response.json(); // 把后端返回的安全HTML插入编辑器 editor.pasteHTML(data.html); } catch (err) { // 出错时降级为普通链接 editor.pasteHTML(`<a href="${pastedText}" target="_blank">${pastedText}</a>`); } } }); // 获取Django的CSRF Token函数 function getCSRFToken() { let cookieValue = null; if (document.cookie && document.cookie !== '') { const cookies = document.cookie.split(';'); for (const cookie of cookies) { const trimmedCookie = cookie.trim(); if (trimmedCookie.startsWith('csrftoken=')) { cookieValue = decodeURIComponent(trimmedCookie.substring(10)); break; } } } return cookieValue; }
5. 额外安全加固
- URL规范化:处理链接时可以用
urllib.parse.urlunparse把链接转成标准格式,避免攻击者用特殊字符或参数绕过校验 - 定期更新白名单:根据业务需求添加新的可信平台,不要随便开放未知域名
- 渲染时的二次检查:即使后端生成了HTML,渲染页面时也要确保用Django的
mark_safe标记(但前提是你已经严格验证了内容,不要滥用mark_safe) - 绝对禁止用户提交HTML:编辑器要设置为只允许纯文本或经过处理的内容,不要开启富文本直接提交HTML的功能
这样一套流程下来,就能像Medium一样安全实现链接嵌入,既方便用户,又彻底避免XSS风险。
内容的提问来源于stack exchange,提问作者Trizzaye
相关产品推荐
相关产品推荐

