티스토리 뷰

728x90
반응형

문제요약 : Python Arbitrary Code Execution

 

문제 첫 페이지

문제에 처음 접속하면 위와 같은 페이지가 나옵니다.

소스코드 보기를 하면 아래와 같은 힌트를 보실 수 있습니다.

/debug 경로에 접속해보면 아래와 같이 웹서버의 소스코드를 그대로 볼 수 있습니다.

이 소스코드를 단계적으로 분석해보면 아래와 같습니다.

 

1 - 쿠키 분석

우선 SECRET_KEY가 노출되었기 때문에 Flask 세션 쿠키 조작이 가능해집니다.

SECRET_KEY를 이용한 세션 조작에 대해 자세한 건 아래 포스트를 참고해주세요.

https://domdom.tistory.com/295?category=1023003 

 

[Research] Crack Flask Cookies (Secret Key)

상식적으로 웹서버에서 세션/쿠키를 암호화하는 데 쓰이는 Secret Key 라는 것이 노출되면 안된다고 노출되지 않게 잘 관리하도록 권유합니다. 개인적으로 필자는 웹서버에 대한 이해가 아직 부족

domdom.tistory.com

현재 페이지에서의 쿠키 값을 디코딩하면 아래와 같습니다.

2 - 명령어 실행 타겟

ingredient = session.get('ingredient', None)
measurements = session.get('measurements', None)

recipe = '%s = %s' % (ingredient, measurements)
# recipe 가 20글자가 넘을 시 아래 코드 실행
if ingredient and measurements and len(recipe) >= 20:
  regex = re.compile('|'.join(map(re.escape, ['[', '(', '_', '.'])))
  matches = regex.findall(recipe)

  if matches: 
      return render_template('index.html', blacklisted='Morty you dumbass: ' + ', '.join(set(matches)))

  # recipe 가 300 글자 넘으면 안됨
  if len(recipe) > 300: 
      return func(*args, **kwargs) # ionic defibulizer can't handle more bytes than that

  calc(recipe) # exec(recipe, builtins, garage)
  return func(*args, **kwargs) # rick deterrent

중요한 부분만 뽑아 보면 위와 같은데, 세션 정보에서 ingredient 와 measurements 부분의 정보를 받아서

각각의 변수에 넣고, 그 변수의 값들을 recipe 라는 변수에 문자열 포매팅해서 다시 대입합니다.

그리고 recipe 변수에 정규식으로 [, (, _ . 문자들이 포함되서는 안되며,

recipe의 길이는 20글자 이상 300글자 미만이어야 한다고 합니다.

그래야지 calc 함수에 들어가서 최종적으로 exec 함수를 호출하기 때문입니다.

 

3 - 여러 제약사항 우회하기

우선 calc 함수는 아래와 같이 정의되어 있습니다.

exec 함수의 인자를 보면 알 수 있듯이, builtin globals 와 builtin locals는 모두 막혀있습니다.

그렇기 때문에 더더욱 템플릿 렌더링을 통한 결과확인은 물론 아래와 같이 session 값에 직접적으로 값을 조작해서

결과를 확인할 수는 없었습니다.

session['domdom'] = 1234

하지만 아무리 builtin globals 와 locals 가 막혔어도 python 에서 builtin objects 는 사용할 수 있습니다.

예를 들어 "domdomi" 와 같은 string 타입이나 {} 와 같은 dict 타입이 그렇습니다.

그리고 이런 string 이나 dict 타입에서는 또 다수의 속성이 존재합니다.

 

일반적으로 dir() 함수를 이용해서 아래와 같이 객체의 속성 값을 알아낼 수 있습니다.

(아래는 123이라는 Integer(정수) 타입의 속성에는 어떤 것이 있는지 확인하는 예시입니다.)

그리고 당연히 정수형 말고도 string 이나 dict 형에도 여러 속성이 있습니다.

속성 아래에는 또다른 속성이 있을 수 있죠. 이런 원리를 통해서 속성 아래 또다른 속성에

우리가 원하는 builtins 를 찾아 여행을 떠나면 됩니다.

 

감사하게도 누군가가 이미 찾아놓아 주어 쉽게 진행할 수 있었습니다.

{}.__class__.__bases__[0].__subclasses__()[59]()._module.__builtins__

위와 같이 builtins를 가져올 수 있는데, 그럼 이제 import 문을 사용해서 os 와 같은 모듈을 불러올 수 있게됩니다.

자세한 건 아래 링크를 참고해주세요.

https://book.hacktricks.xyz/misc/basic-python/bypass-python-sandboxes

 

Bypass Python sandboxes - HackTricks

os: CompletedProcess, Popen, NullImporter, _HackedGetData, SSLObject, Request, OpenerDirector, HTTPPasswordMgr, AbstractBasicAuthHandler, AbstractDigestAuthHandler, URLopener, _PaddedFile, CompressedValue, LogRecord, PercentStyle, Formatter, BufferingForma

book.hacktricks.xyz

 

하지만 위와 같이 명령을 사용하려고 하였으나, 아까 소스코드에서 보았던

정규식 필터링에 의해서 막히게 됩니다.

regex = re.compile('|'.join(map(re.escape, ['[', '(', '_', '.'])))

이를 우회하기 위해서는 exec 함수 내부의 문자열 안에서 또다른 문자열에서는

두 개의 백슬래시를 이용할 수 있는 점을 활용할 수 있습니다.

예를 들면 아래와 같습니다.

위와 같이 문자열 내부의 문자열에서 백슬래시 두 개를 활용하더라도 hex escape 한 문자를 잘 해석하는 것을 알 수 있습니다.

 

백슬래시를 한 개를 활용한 hex escape 문자의 경우 웹 서버 코드 상에서 각 변수에 대입될 때 해석이 되어버리기 때문에 위와 같이 반드시 2개를 사용해줘야 합니다.

 

그리고 또다른 난제는 바로 exec 함수 안에서의 exec 함수의 실행이었습니다.

exec 함수 안에서 exec 함수를 실행하여 특수문자 필터링은 우회하였다고 하지만,

exec("exec('i=1')")

위와 같은 코드가 완성되기 위해서는 반드시 ( 즉 (여는) 소괄호를 사용해야 합니다.

하지만 이는 방화벽에 의해서 필터링되는 문자이기 때문에 사용할 수 없습니다.

 

하지만 여기서 문득 아래와 같은 코드를 볼 수 있었습니다.

xrange()는 python3 에서 존재한다는 얘기는 들어본적이 없어서 구글링 해보았는데,

알고보니 python2 에서 사용하는 문법이라고 합니다.

https://www.geeksforgeeks.org/range-vs-xrange-python/

 

range() vs xrange() in Python - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

그리고 추가로 Python2 에서는 exec 함수 사용 시 legacy 한 사용법을 사용할 수 있다고 하는데요.

https://books.google.co.kr/books?id=7U1CIoOs5AkC&pg=PA115&lpg=PA115&dq=python+exec+without+parentheses&source=bl&ots=eO0vDgG-GL&sig=ACfU3U1RIF3GK-TDUKpl5TwbjArfMMUkJw&hl=ko&sa=X&ved=2ahUKEwiXvvCGmIT1AhVB_GEKHSXEBxoQ6AF6BAgeEAM#v=onepage&q=python%20exec%20without%20parentheses&f=false 

 

Python Essential Reference

David M. Beazley has been programming Python since 1996. While working at Los Alamos National Laboratory, he helped pioneer the use of Python with scientific computing software. Through his company, Dabeaz LLC, he provides software development, training, a

books.google.co.jp

위 링크를 참고해보시면 아시겠지만, exec "for i in a: print i" 와 같은 형태의 코드로써

( 또는 ) 의 소괄호를 사용하지 않아도 된다고 합니다.

 

그래서 결국 위 취약점들로 인해서 모든 방화벽 제약사항 우회가 가능해졌습니다!

 

4 - Exploit 페이로드 작성하기

이제 아래와 같은 순서대로 페이로드를 작성할 것입니다.

1. Flask 세션 쿠키에 존재하는 Data 부분의 ingredient 와 measurements 속성의 값을 조작한다.

2. 속성의 값으로 builtins 모듈인 import 모듈을 사용해 os 모듈을 불러온다.

3. os 모듈로 listdir 함수를 호출하고 결과를 세션값에 대입하도록 하는 값을 넣고

Flask 세션 쿠키의 data 에 해당하는 영역을 완성한다.

4. 노출된 SECRET_KEY를 이용해서 완성된 data 영역을 flask 세션 모듈로 인코딩한다.

5. 웹브라우저의 세션 값을 조작한 값으로 변경하고 새로고침한다.

6. 서버로부터 받아온 새로운 flask 세션 쿠키 값을 확인하고, 디코딩해서 결과값을 확인한다.

 

위 순서대로 작성한 페이로드를 python 코드로 작성한 것이 아래입니다.

실행 결과를 확인해보니 totally_not_a_loooooooong_flaaaaag 라는 파일이름이 특이해보입니다.

 

이제는 한번 위 파일의 내용을 확인해보겠습니다.

내용은 그냥 cat to* 명령어로 확인했습니다.

그랬더니 아래와 같이 Flag가 출력되는 것을 확인할 수 있었습니다.

 

 

- 끝 -

728x90
반응형
댓글