Portswigger-Basic server-side template injection (code context)
풀이
Tornado template을 사용하는데, SSTI 취약점이 있다. 이를 이용해 임의 코드를 실행하여 Carlos의 홈 디렉토리에 있는 morale.txt 파일을 삭제해야 한다. wiener:peter 계정이 주어진다.
기본적으로 사용자가 변경 가능하면서 화면의 내용에 영향을 끼칠 수 있는 입력값을 찾는다.
_0.png)
로그인하면 다음과 같은 페이지에 접근할 수 있다. Preferred name에서 Name, First name, Nickname을 고를 수 있다. 입력란을 통해서는 이메일을 변경할 수 있다.
_1.png)
일단 이메일을 화면에 표시하고 있으므로, 이메일에 페이로드를 넣을 수 있는지 테스트해보았다. 이메일 형식이 아니면 업데이트를 해주지 않고, 또한 Tornado SSTI 페이로드가 작동하지 않는 것으로 보아 인코딩이 적용되는듯 하다. (SSTI 취약점이 없는것 같다.)
_2.png)
하단의 Preferred name 을 수정하면 댓글 작성자에 표시하는 내용이 바뀐다. Preferred name으로 보여주는 Name, First name, Nickname를 변경할 수는 없을까? 또는 Name, First Name, Nickname 이외의 데이터를 보여주게 할 수는 없을까?
이메일을 변경하는 페이로드를 잡아 경로나 파라미터를 수정하여 nickname이나 name, first name을 바꿔볼 수 없을까 테스트해보았지만 불가했다. 이를 바꾸는 기능은 일단은 불가해보인다.
_3.png)
Preferred name 수정하기를 누르면 위와 같은 페이로드가 간다. user.name 이라는, 필드 이름으로 보이는 값을 보내 설정한다. 만약 이름 대신 my page에서 변경 가능한 email을 표시하도록 설정할 수 있다면 어떨까?
_4.png)
user.name 대신 user.email 로 설정한 뒤 페이로드를 보내보았다.
_5.png)
다시 댓글이 있는 페이지를 열어보면 위와 같은 파이썬 에러 메시지가 뜬다. User instance has no attribute 'email' 라는 에러 메시지를 보아 표시할 데이터를 변경하는 데에는 성공했지만, user.email 이라는 변수는 없기에 에러를 띄운 것으로 보인다.
에러 메시지로 추측해보자면, 입력받은 파이썬 변수를 Tornado 템플릿을 통해 출력하는 것으로 보인다.
_6.png)
user.name 대신 user 로 설정하고 다시 댓글 페이지를 새로고침 해보면 위와같이 뜬다. User 클래스 메타데이터에 접근하여 eamil 변수 이름을 알아내야겠다.
_7.png)
user.__dict__ 로 User 클래스를 조회하면 {'first_name': 'Peter', 'nickname': 'H0td0g', 'name': 'Peter Wiener'} 뿐이다. User 클래스에 email이 없다.
_8.png)
그런데, 입력한 변수의 이름을 그대로 템플릿에 반영한다면, 변수 이름에 템플릿 인젝션을 할 수는 없는걸까? 그래서 이번엔 user.name 대신 7*7을 입력했고, 이름 대신 49가 떴다. 템플릿에 7*7이 그대로 반사되어 실행이 된 것이다. 이제 SSTI 취약점이 존재한다는 것을 확인했다.
_9.png)
Carlos의 홈 디렉토리에 있는 morale.txt 파일을 삭제해야 한다. 먼저 Carlos의 홈 디렉토리란 어디일지 찾는다. 현재 디렉토리에 대한 정보를 얻는다.
Tornado는 템플릿에서도 라이브러리를 임포트할 수 있다. os 라이브러리를 임포트하고, 현재 디렉토리를 조회했다.
중간중간 에러메시지를 참고하여 에러를 우회하도록 페이로드를 짰다. blog-post-author-display=""}}{%import os%}{{os.getcwd() 와 같은 페이로드를 보냈다.
""}}는 라이브러리를 임포트하기 위해{%import os%}를 넣어야하는데, {{입력값}} 형태에서 나오기 위해 넣었다. 단순히 }}만 넣으면 {{}}로 아무 내용이 없는 괄호가 되어 에러가 났다. 그래서 빈 문자열을 추가했다.{%import os%}를 추가하여 필요한 라이브러리를 임포트한다.{{os.getcwd()를 추가하여 원하는 작업을 실행한다.
최종적으로 템플릿은 {{""}}{%import os%}{{os.getcwd()}} 형태를 갖추었을 것이다.
이렇게 현재 디렉토리가 /home/carlos 라는 것을 알아냈다. 이제 morale.txt 파일을 삭제하면 된다.
_10.png)
=""}}{%import os%}{{os.remove("morale.txt") 페이로드를 보낸 후 댓글 페이지를 새로고침하면 파일 삭제 코드가 실행되어 파일을 삭제한다. 한 번 파일을 삭제한 뒤 또 실행했기에 위와 같은 에러가 떴다.
이 취약점은 사용자가 입력한 값을 검증하지 않아서, SSTI 페이로드를 sanitize 하거나 인코딩하지 않고 처리했기에 발생했다. 변수 이름을 사용자에게 직접 입력받아 사용해서 문제가 발생했다.
tags: writeup, ssti, wstg-inpv-18, tornado, web hacking