Server-Side Templete Injection (SSTI) 공격

고둑·2021년 9월 28일
0

스터디 발표

목록 보기
3/4

2021년 9월 25일 CCE2021 예선전을 진행했다.
영화 촬영 스케줄이랑 겹쳐서 온전히 집중은 못해서 아쉬웠다...
그래도 웹 100점짜리는 다 풀었으니까 그걸로 일단 만족하기로했다.

이번에 진행했던 CTF에서 SSTI 취약점를 이용하는 것으로 추정한 문제가 나와서 한 번 정리해보려한다.
사실 SSTI 취약점을 사용하는 문제는 아닐 수도 있지만 재오픈 계획이 없는 문제이기때문에 SSTI 취약점을 사용하는 문제라고 믿기로 하였다.

SSTI란?

Server-Side Templete Injection, 즉 SSTI 취약점은 템플릿 엔진을 이용하여 웹 어플리케이션을 구동할 때, 사용자의 입력이 적절하게 필터링 되지 않아 템플릿 구문을 삽입 할 수 있을 때 발생한다.

성공적으로 템플릿 엔진에 코드를 삽입하면 서버측에서 실행을 시키기때문에 최악의 경우 RCE 취약점으로 연계가 가능한 매우 위험한 취약점이다.

웹 템플릿 엔진

처음 SSTI를 워게임에서 접했을때는 웹 개발에 대한 지식이 전무해서 웹 탬플릿에 대한 이해를 못했었다. 그리고 추후에 이 부분을 공부하다 보니 이전보다는 이해도가 생긴 것 같아 이에 관한 것을 간단히 적어보려고 한다.

웹 탬플릿 엔진이란

템플릿 엔진이란 템플릿 양식과 입력 자료를 합성하여 결과 문서를 출력하는 소프트웨어를 말한다.
그 중 웹 템플릿 엔진은 브라우저에서 출력되는 문서를 위한 소프트웨어이다.

웹 탬플릿 엔진은 결합하는 위치에 따라 서버 사이드 템플릿 엔진과 클라이언트 사이드 템플릿 엔진으로 분류할 수 있다.

  • 서버사이드 탬플릿 엔진
    서버에서 DB나 API에서 가져온 데이터와 사전에 설정해놓은 템플릿을 합쳐서 HTML을 만든 후 클라이언트에게 전송하는 방식

  • 클라이언트사이드 템플릿 엔진
    데이터와 템플릿을 합쳐 클라이언트에서 HTML을 완성하는 방식
    서버는 api 호출에 대한 응답 데이터만 제공해주고 화면은 클라이언트가 렌더링하는 방식이다.
    사실 안써봐서 잘 모른다...

우리가 집중적으로 볼 방식은 서버사이드 템플릿 엔진이다.

왜 웹 탬플릿 엔진을 사용하는가??

우리가 웹 페이지를 사용하다보면 생각보다 공통된 부분이 많은 것을 볼 수 있다.
특정한 데이터만 바뀌고 전체적인 형식은 같은 경우 HTML만을 이용하려면 공통된 부분이 있던지 말던지 그냥 생으로 코드를 작성해야한다.

하지만 이런 경우 매우 비효율적이므로 탬플릿 엔진을 이용하게 되는것이다.

탬플릿 엔진을 이용하면 HTML보다는 상대적으로 간단한 코드를 이용하게 되므로 코드량을 줄일 수 있게 된다.
그리고 분명 데이터만 바뀌는 상황이 존재하는데 그럴 경우 재사용이 가능하므로써 효율적으로 페이지를 출력할 수 있다.
웹페이지를 수정할때도 탬플릿만 수정하면 되니까 유지보수성이 매우 증가하게된다.

이러한 장점이 있으므로 웹 탬플릿 엔진을 이용한다고 볼 수 있다.

서버사이드 탬플릿의 취약점

상기한 장점이 있는 서버사이드 템플릿은 치명적인 단점이 있다.
바로 클라이언트에서 RCE(Remote Code Excute)가 가능한 데이터를 넘겨주게 되면 입력 값에 충분한 검증을 거치지 않는 서버는 사용자가 입력한 데이터를 그대로 탬플릿 엔진에 넘겨주게 되고 탬플릿에 들어가서 렌더링 되는 과정에서 사용자가 입력한 코드를 그대로 실행하게 된다.
그리고 이 모든 과정이 서버 단계에서 일어나기때문에 서버가 가지고 있는 민감한 정보들을 탈취할 수 있는 위험이 있다.

SSTI 취약점 공격 방법

사용 중인 웹 탬플릿 엔진 특정하기

저번에 블로그에 글 올렸던 SQL 인젝션을 하기 위해 필요한 DBMS Fingerprint 처럼 웹 탬플릿 엔진도 엔진마다 작동하는 문법이 다르기때문에 엔진을 특정해줘야한다.

깔끔하게 정리된 그림이 있어서 가져왔다. (재사용성↑↑ 효율성↑↑)

사진을 보면 특정한 엔진에서만 작동하는 문법을 이용하여 사용중인 엔진을 특정할 수 있는 것을 볼 수 있다.

그리고 많은 워게임 사이트나 CTF에서 이용하는 FLASK에서는 추가적인 설정이 없으면 Jinja2 엔진을 이용하게 된다.
그리고 내가 본 소수의 문제 중에 대부분은 Jinja2 기반의 SSTI문제였다. 따라서 Jinja2 기반으로 설명을 하도록하겠다.

공격 진행

Jinja2 탬플릿 구문 중 {{...}} 구문은 중괄호 안에 들어가는 내용을 동적으로 보여주는 효과를 가지고 있다.
이러한 특징을 이용하여 중괄호 안에 특정한 코드를 입력하여 서버에서 정보를 탈취가 가능하게된다.

기본적으로 Flask의 경우 app.py에서 사용되는 대부분의 정보가 config 클래스에 들어간다.
CTF나 워게임에서 app.secret_key를 알아야지 문제를 풀 수 있는 경우가 있는데 이런 경우 {{config}} 와 같이 공격 구문을 페이로드에 삽입하여 날리면 알 수 있다.

이 뿐만이 아니라 codecs.IncrementalDecoder이나 subprocess.Popen를 이용해 ls와 같은 명령어를 실행시키고 결과를 받아 볼 수 있는 공격 방식도 있지만 나중에 자세히 적도록 하겠다.

실습

SSTI 공격이 가능한 app.py를 만들었다.

from flask import Flask, request, render_template_string
from random import choice
import string

string_pool = string.digits + string.ascii_lowercase
LEN = 50
global flag
flag = ""

for i in range(LEN):
    flag += choice(string_pool)

app = Flask(__name__)
app.secret_key = 'FLAG{%s}'%flag

@app.route('/')
def index():
    pram = request.args.get('pram', '')
    html = '''
        <html>
        <div class="center">
        <h1>SSTI를 연습해봅시다!</h1>        
        <h2>%s</h2>
        </html>
    '''% pram
    return render_template_string(html)

app.run('0.0.0.0', 8080)

로컬에서 돌려보면 아래와 같은 화면이 나온다.

일단 {{7*7}}를 삽입한 페이로드를 날려보면 아래와 같이 나옴을 확인할 수 있고 SSTI 공격이 가능한 것을 볼 수 있다.

flag는 랜덤으로 지정했기 때문에 코드를 봐선 알 수가 없다.
따라서 플래그를 탈취하기 위해선 SSTI를 이용하여 Secert_key를 알아내야한다.

app.py에서 지정해놓은 secert_key의 값은 config 클래스에 저장이 되어있기 때문에 config를 조회해보면

많은 데이터가 배열 형태로 저장이 되어있는 것을 볼 수 있다.
이 많은 데이터 중 flag가 저장되어있는 SECRET_KEY만 따로 조회하기 위해 공격 구문 {{config['SECRET_KEY']}}을 페이로드에 삽입해서 보내면 아래와 같이 원하는 플래그 값만 뜨게 된다.

이런 방식으로 많은 문제에서 플래그를 숨겨놓는다.
처음보면 매우 생소할 수 있으나 한 번 익숙해지면 많이 편해질것이다.

profile
문워킹은 하지말자

0개의 댓글