58. SSTI: 서버사이드 템플릿 인젝션 완전 분석
SSTI는 사용자 입력이 템플릿의 데이터가 아니라 템플릿 소스로 해석될 때 발생한다. 브라우저에서 실행되는 XSS와 달리 서버 렌더러의 권한과 네트워크 위치가 영향을 받으므로, 진단은 자기 소유 애플리케이션이나 승인된 CTF에서 무해한 표현식으로만 수행한다.
먼저 평가와 출력의 차이를 확인한다
{{7*7}}이 49로 렌더링되면 해당 문법을 평가하는 엔진이 있을 가능성이 높다. 반대로 {{7*7}}이 그대로 출력되면 이 입력 지점은 템플릿 소스가 아니라 일반 변수일 수 있다. ${7*7}이나 <%= 7*7 %>도 엔진마다 의미가 다르므로, 결과 하나만으로 Jinja2·Twig·FreeMarker를 확정하지 않는다. 응답 본문, 오류 형태, 사용 중인 프레임워크와 템플릿 파일을 함께 확인한다.
취약한 구조는 다음처럼 사용자 문자열을 템플릿으로 컴파일하는 경우다.
# 취약: user_text가 템플릿 소스가 됨
render_template_string(user_text)
# 안전한 방향: 고정 템플릿에 데이터를 전달
render_template("greeting.html", message=user_text)
두 번째 코드도 greeting.html 안에서 message를 위험한 HTML로 출력하면 XSS가 될 수 있으므로, 컨텍스트에 맞는 출력 인코딩이 별도로 필요하다. SSTI와 XSS는 서로 다른 층의 문제다.
권한 경계를 분석한다
템플릿 엔진이 변수, 필터, 함수, 객체 속성 중 무엇을 노출하는지 확인한다. 샌드박스가 있어도 파일 접근·네트워크 호출·무한 루프·큰 메모리 할당을 완전히 막았다고 가정하지 않는다. 엔진별 RCE 문자열을 복사해 붙이는 방식은 버전과 설정에 종속되고 실제 시스템에 위험하므로, 교육 환경에서는 문자열·산술·고정된 표시값처럼 부작용 없는 표현식으로 평가 여부만 검증한다.
오류 메시지는 엔진 식별의 단서가 되지만 비밀키나 환경 변수를 출력하는 실험은 하지 않는다. 확인해야 할 것은 입력이 어느 단계에서 컴파일되는지, 템플릿 파일이 고정되어 있는지, 컨텍스트 객체가 어떤 메서드를 노출하는지다.
방어와 회귀 테스트
사용자 입력을 템플릿 소스에 연결하지 말고, 템플릿을 코드와 함께 배포해 런타임 업로드를 막는다. 필요하면 엔진의 샌드박스·허용 필터·실행 시간 제한을 겹쳐 적용하되, 샌드박스를 유일한 방어로 삼지 않는다. 회귀 테스트에는 {{7*7}}, ${7*7}, <%= 7*7 %>와 일반 텍스트를 넣어 모두 데이터로 출력되는지 확인한다. 최종 판단은 “계산식이 보였다”가 아니라, 사용자가 통제한 문자가 서버의 템플릿 평가 경로에 들어갔는지와 그 경로가 차단됐는지에 있다.