본문 바로가기
  • True knowledge exists in knowing that you know nothing. -Socrates-
web/취약점

Python2.X input 취약점 (코드 실행), 그리고 os.setuid에서 root까지..

by intadd intadd 2020. 11. 25.

안녕하세요.

얼마전 웹 퍼져를 짜는 도중 엄청 신기한 취약점을 발견했습니다. 

이미 있었던 취약점이지만, 좀 놀랐던 취약점이라 때문에 포스팅 해봅니다. 

 

 

코로나 2단계로 카페를 못가는 심각한 상황

(할게 많지만 1시간만 쉬자고 다짐하며 포스팅을 한다. 1시간 넘어가면 포스팅은 미룬다라는 마인드.. 사실 오늘 아리스토텔레스 정치학 고전독서 시험을 보는데 1시간 책읽고 10점 받아서 기분이 매우 안좋음, (100점 만점))

Python2.X 버전에서 유효한 Input 함수를 이용한 파이썬 코드 실행 취약점입니다. 

 

3줄 요약 

1. Code 

sample.py

2. POC  --> os.system("ls -al")

os.system("ls -al")

3.  

그냥 png인데 gif로 보이는 이유는..

 

이상 자세한 설명은 생략

 

 

ㅋㅋㅋ 다시 본문으로 돌아와서 왜 input에서 이런 취약점이 발생한는가?

https://docs.python.org/2/library/functions.html#input

Python2 Doc를 살펴보면 input함수는 eval(raw_input())과 동일하다고 합니다. 

음.. Python을 배울 때 이런건 배웠던 적이 없었는데, 그래서 저도 몰랐나 봅니다. 하하 (아마 다른 사람들도 많이 모르지 않았을까.. 근데 이미 블로깅 까지 되어있더라구요 [1])

그러면, 사실 설명은 다 끝났다고 생각합니다.
이제 응용을 해보면  import os가 없는 상황에서도 취약점을 발생시킬 수 있습니다.

극단적으로 Code를 짜보면, 한줄 코드도 가능합니다. 

 

sample2.py

python2.7 built in function인 __import__를 이용하여 os를 사용하는 방법입니다. 

python2.7 sample2.py


이정도로 python2.X 의 input 함수의 위험성은 충분히  증명이 된듯합니다. 
음. 아직도 python2.X를 사용하고 있는 환경이라면 점검해 볼만한것 같습니다. 
input함수를 사용하는 것 자체로 상당히 위험합니다. (사실 2 버전을 고집하는것 부터..)

좀 더  위험성을 부각시키는 시나리오를 짜보겠습니다. 

[시나리오 1] 


1.  Python2.X 버전을 이용한 input 함수 사용

2.  이후 바이너리화 하여 실행파일로 사용

/home 디렉토리에 두겠습니다.

3.  해당 바이너리에 setuid 설정


1~2 단계는 그럴 수 있겠지만, 3번은 억지가 있을 수 있습니다. ㅎㅎ 
3번의 시나리오를 설정한 이유는 input 함수를 사용한다는 가정이 있기 때문입니다. 
Input을 통해 무언가를 할려면 일반 권한의 shell을 보유해야한다고 생각했습니다. 뭘 넣어야 뭐가나오니까


간단하게 구성을 설명하면 setuid가 설정된 binary를 보유한 좌측 서버, ssh로 일반 권한으로 접속한 terminal입니다. 

뭐 크게 의미없는 사진. 단지 일반 쉘이 있다는 가정을 보여줌

사실 "간단하게 /bin/bash 실행하고 root 되는거 보여주면 되겠다라고 생각했습니다. 왜냐면 setuid 걸려있으니까"라고 생각했습니다. 

일단 시도 

내가 만만해?

어림도 없는 403입니다. 

분명 setuid가 걸려있는데 왜  euid는 root가 아닌가?

나 아직 일반계정인데?

--

target의 binary 파일의 euid는 root가 맞습니다.
다만, Python의 os 모듈은 binary setuid를 따라가는 건 아닌 것 같습니다.

간략한 테스트 

test.py


바이너리로 변경 후, setuid설정한 뒤, 소유자 확인 

확실히, root가 맞습니다. 

OS 모듈이 실행중인 binary의 setuid의 영향이 없다는 테스트

test.py
test2의 소유자는 root 이지만 os. system으로 실행한 sleep은 일반 소유자임을 확인


생각보다 귀찮아진 케이스
(잘 찾아보면 어딘가엔 기술되어 있을 것 같지만,, 찾기에는 너무 귀찮다.)

으이구

음.. 생각을 해보면 음... 개인적인 생각으로 출발한 아이디어이지만, 어차피 setuid가 걸려있으므로 음.. 
os 모듈이 접근하는 process의 uid를 변경하면 되지 않을까라는 생각을 해봤습니다. 권한 문제가 있겠지만, os모듈을 돌리는 binary의 소유자는 root(binary setuid)이기 때문입니다.  (정확히 os 모듈이 접근하는 proccess의 uid를 바꾸는?? 는 잘 모르겠습니다. 이렇게 표현하는게 맞나? )

그러므로 os 모듈의 setuid 함수를 사용해봅니다.
os 모듈을 통해 setuid를 설정해주는 것 부터 테스트

일단 에러는 발생하지 않았습니다. 

targrt 의 setuid가 걸려있지 않았더라면 에러가 났을 겁니다. 
이제 input 함수 내에서 os 모듈의 setuid를 호출 후, os 모듈의 system을 호출해야합니다. 
위의 input Doc 설명에서 있드시 input은 eval(raw_input) 함수와 동일합니다. 
저는 의미없는 연산을 추가하면서 문법 오류 문제를 무시했습니다.  os.setuid의 return 값은 None 이고, os.system의 return 값은 int입니다. 둘다 str 처리하고 더 해줬습니다. str() 내에 코드를 입력하면 뭐 실행은 될테니까요 ㅎ

짠~

(흐름은 os.setuid() --> 반환값 str 변환 --> os.system() --> 반환값 str 변환 --> + 이 됩니다.)

출력 값의 마지막을 확인해보면 print문에 의해서 None (os.setuid 반환 값) 과 0 (os.system 반환값) 이 문자열로 더해진 값 None0도 확인할 수 있습니다. 


뭐 다시 돌아와서 os 모듈이 접근?하는 process의 euid도 변경했습니다.  
본 목적이었던 root로 shell실행을 해보면 

이렇게 됩니다. 진짜 아주 0 되는거야 

분명 어딘가에는 더 좋은 방법이 있을 겁니다. ㅎㅎ 
포스팅 하다 급하게 해본거라 표현이 맞는지도 잘 모르겠습니다. 


그래서 결론은 Python2.X 버전의 Input 함수는 사용하지 말자. 쓰라고 가르치지도 말자. (애초에 2버전은 안가르치겠지)
위 시나리오에서 setuid를 거는 억지가 좀 있었지만 취약점에 초점을 맞춰보면 

이 코드의 문제점을 좀 더 관심있게 볼 수 있을 것이라고 생각한다. 


다시 학교 과제하러 가보겠습니다. 

댓글0