Server-Side Template Injection (SSTI) 취약점 (PortSwigger Academy) - 개념과 공격 흐름 정리
SSTI 취약점의 발생 원리, 공격 방식, 그리고 위험성에 대한 정리
Server-Side Template Injection (SSTI)
이번에는 CORS 다음으로 자주 나오는 취약점인
Server-Side Template Injection(SSTI)를 정리해본다.
이건 처음 보면 XSS랑 비슷해 보이는데,
실제로는 훨씬 위험한 케이스가 많다.
SSTI란 무엇인가
템플릿 엔진은 HTML 같은 결과물을 만들기 위해
정적인 템플릿과 데이터를 합쳐주는 역할을 한다.
예를 들어 이런 코드가 있다고 보면 된다.
1
$output = $twig->render("Hello ", ["name" => "carlos"]);
여기서 name은 그냥 데이터로 들어가고,
템플릿은 그 값을 화면에 출력만 한다.
이 상태에서는 문제가 없다.
취약점이 생기는 순간
문제는 개발자가 데이터를 넣는 게 아니라
템플릿 자체를 만들어버리는 경우다.
1
$output = $twig->render("Hello " . $_GET['name']);
여기서 name은 더 이상 단순한 데이터가 아니다.
템플릿 코드로 해석될 수 있는 문자열이 된다.
그래서 공격자가 이렇게 보내면
1
/?name={{7*7}}
서버는 이걸 계산해서 출력해버린다.
1
Hello 49
이 시점에서 서버에서 코드가 실행되고 있는 상태인것을 확인할 수 있다.
왜 위험한가
SSTI가 위험한 이유는 코드가 “브라우저”가 아니라 “서버”에서 실행된다는 점이다.
그래서 상황에 따라서는
- 서버 파일 읽기
- 환경 변수 접근
- 내부 서비스 접근
- 명령 실행 (RCE)
까지 이어질 수 있다.
XSS는 사용자 브라우저에서 끝나지만
SSTI는 서버 자체를 건드릴 수 있다.
어떻게 발생하는가
개발자가 사용자 입력을 템플릿 “데이터”로 넣지 않고 템플릿 “문자열”에 붙여버리는 이 차이 하나 때문에 취약점이 생긴다.
개발자 입장에서는 그냥 문자열 이어붙인 건데,
템플릿 엔진은 그걸 코드로 해석해버리기 때문.
탐지하는 방법
이 취약점은 눈에 잘 안 보인다.
XSS 처럼 바로 alert가 뜨는 것도 아니고,
SQL injection처럼 에러가 바로 나오는것도 아니라서
일부러 의심하고 넣어봐야 드러난다.
우선은 제일 먼저 아무 생각 없이 특수문자 던져보기를 시도해본다.
1
${{<%[%'"}}%\
이걸 넣었을 때
- 에러가 나거나
- 응답이 이상하게 바뀌거나
- 일부가 사라지거나
한다면, SSTI를 의심 해볼 수 있겠다.
하지만 반응이 없다고 해서 SSTI가 아니라고 확정하긴 힘들다. 그 다음 해볼 수 있는 방법은, 계산식 같은 템플릿 문법을 넣어보는 것이다.
1
{{7*7}}
라고 입력 했을때, 응답이 문자열 그대로 나오면 안전한 것이지만,
1
49
이렇게 계산돼서 나오면 서버에서 계산했다는 뜻이며,
템플릿이 실행되고 있다는 의미다.
템플릿 코드 안으로 들어가는 케이스
1
engine.render("Hello {{"+greeting+"}}")
이런 구조에서는
입력값으로 템플릿 문법을 탈출하는 게 가능하다.
1
?greeting=data.username}}
이런 식으로 닫아버리고 뒤에 코드를 붙이면
템플릿 구조 자체를 바꿔버릴 수 있다.
XSS와 SSTI
위와 같은 예제에서,
1
?greeting=data.username}}<tag>
와 같이, <tag>를 집어서 테스트를 해볼 수 있는데, XSS인지 확인하기 위해서다.
곧 바로 SSTI부터 보는게 아니라 XSS부터 배제하고 본다.
위와 같이 전송하게 되면 세 가지 반응이 나올 수 있겠다.
Hello Carlos<tag>처럼 그대로 나오는 경우 - XSS 가능성 존재Hello Carlos<tag>escape 처리되는 겨웅 - 필터링有, XSS 막혀있음Hello값이 깨지거나 사라진 경우 - 내부에서 뭔가 처리되고 있음, SSTI 의심 가능
따라서 SSTI를 확인할때는 XSS가능성도 같이 확인을해서, 구분을 지어주는 방법도 있다.
- <
tag>넣어서 XSS 여부 확인 - XSS 아니면 템플릿 쪽으로 의심
- 그 다음 SSTI payload 넣기
공격 흐름와 가능한 이유
실제로 공격은 대략 이런 순서로 진행된다.
1
2
3
4
1. 템플릿 실행되는지 확인 (테스트 값 삽입)
2. 어떤 템플릿 엔진인지 추정 (검색 혹은 HackTricks 참조)
3. 가능한 문법으로 접근 범위 확장
4. 파일 / 시스템 접근 시도
템플릿을 “문자열”로 다루다가
그 안에 사용자 input을 그대로 붙여버리는 경우에 취약점이 생긴다.
가끔은 의도적으로 사용자가 템플릿을 수정할 수 있게 만들어놓기도 하는데,
이런 경우에 특히나 더 조심해야 한다.
방어 방법
핵심은 사용자 입력을 절대 템플릿 코드로 취급하지 않는것이며,
항상 데이터로 전달해야 한다.
그리고 가능하면 로직이 없는 템플릿 엔진을 쓰는 것도 방법이다.
sandbox를 거는 방법도 있지만
이건 우회되는 경우가 많아서 완전한 해결책은 아니다.
SSTI PortSwigger Labs
아래 HackTricks 사이트를 참고해서 예제를 풀어보도록 하겠다.
링크: https://hacktricks.wiki/en/pentesting-web/ssti-server-side-template-injection/index.html
SSTI 취약점을 테스트 해보고 결과를 분석해 서버가 어떤 템플릿 엔진을 사용하는지 찾아볼 수 있는 유용한 웹사이트다. Template Engine에 대한 분류는 아래를 참고하면 좋을것 같다.
1
2
3
4
5
6
Python: Jinja2, Mako
PHP: Twig, Smarty
JavaScript: EJS, Handlebars, Pug
Java: Thymeleaf, FreeMarker, Pebble
C#: Razor
Ruby: ERB, HAML, Slim
Lab1 : Basic server-side template injection
Lab2 : Basic server-side template injection (code context)
Lab3 : Server-side template injection using documentation
Lab4 : Server-side template injection in an unknown language with a documented exploit
Lab5 : Server-side template injection with information disclosure via user-supplied objects