Python 으로 포트 스캐너 만들기

PythonBeginner
지금 연습하기

소개

이 프로젝트에서는 Python 을 사용하여 대상 서버의 열린 포트를 탐지하는 서버 포트 스캐너를 개발합니다. 이 도구는 보안 정책을 검증하려는 시스템 관리자와 호스트에서 작동 중인 네트워크 서비스를 식별하려는 공격자 모두에게 매우 중요합니다. 포트 스캐닝의 방법론과 그 의미를 포함한 필수적인 측면들을 살펴볼 것입니다. 또한 Python 의 기능을 활용하여 효율성과 성능을 높이기 위한 멀티스레딩 방식에 집중하며, 단순하면서도 효과적인 포트 스캐너를 직접 제작해 봅니다.

포트 스캐닝은 어떤 포트가 열려 있는지 확인하기 위해 일련의 서버 포트에 요청을 보내는 과정입니다. 이 단계는 보안을 유지하는 측면과 취약한 서비스를 찾으려는 공격자 측면 모두에서 매우 중요합니다. 먼저 포트 스캐닝의 기본 개념과 그 영향에 대해 알아보는 것으로 시작하겠습니다.

핵심 개념:

  • 포트 스캐너는 서버나 호스트에서 열려 있는 포트를 탐지하는 데 도움을 줍니다.
  • 보안 평가 용도로 사용되기도 하며, 공격자가 취약한 서비스를 식별하는 데 사용하기도 합니다.
  • 가장 단순한 형태의 포트 스캐닝은 특정 범위의 포트에 대해 TCP 연결을 시도하는 방식입니다.

👀 미리보기

우리가 만들게 될 포트 스캐너 도구의 실행 모습입니다:

python port_scanner.py 127.0.0.1 5000-9000

출력 결과:

Opened Port: 8081
Scanning completed.

🎯 학습 과제

이 프로젝트를 통해 다음 내용을 배우게 됩니다:

  • Python 의 소켓 프로그래밍 기능을 활용하여 네트워크 포트와 상호작용하는 방법.
  • 네트워크 스캐닝 작업의 효율성을 높이기 위해 Python 에서 멀티스레딩 방식을 구현하는 방법.
  • 유연한 포트 스캐닝을 위해 사용자 입력을 받는 명령줄 도구를 Python 으로 개발하는 방법.

🏆 학습 성과

이 프로젝트를 마치면 다음을 할 수 있게 됩니다:

  • Python 의 소켓 라이브러리를 사용하여 네트워크 연결을 생성하고, 포트 가용성을 테스트하며, 네트워크 예외 상황을 처리할 수 있습니다.
  • Python 의 멀티스레딩을 이해하고 적용하여 동시 작업을 수행함으로써, 네트워크 집약적인 작업의 성능을 획기적으로 향상시킬 수 있습니다.
  • 실용적인 명령줄 포트 스캐너 도구를 제작하여 Python 스크립팅 기술을 강화하고 명령줄 인자 파싱에 대한 이해를 높일 수 있습니다.

TCP 연결 테스트 구현하기

첫 번째 단계는 대상 IP 에서 특정 TCP 포트가 열려 있는지 테스트하는 함수를 작성하는 것입니다. 이 함수는 지정된 포트로 연결을 시도하여 상태를 확인합니다.

/home/labex/project 디렉토리에 있는 port_scanner.py 파일을 열고 다음 Python 함수를 추가하세요:

import argparse
from queue import Queue
from socket import AF_INET, gethostbyname, socket, SOCK_STREAM
import threading

def tcp_test(port: int, target_ip: str) -> None:
    with socket(AF_INET, SOCK_STREAM) as sock:
        sock.settimeout(1)
        result = sock.connect_ex((target_ip, port))
        if result == 0:
            print(f"Opened Port: {port}")

이 코드는 대상 IP 주소의 특정 포트에 TCP 연결을 시도하는 tcp_test 함수를 정의합니다. 각 포트에서 너무 오래 기다리지 않도록 타임아웃을 설정하고, connect_ex를 사용하여 연결을 시도합니다.

connect_ex가 0 을 반환하면 연결에 성공했음을 의미하며, 이는 해당 포트가 열려 있다는 뜻입니다. 이 경우 함수는 열린 포트를 알리는 메시지를 출력합니다.

✨ 솔루션 확인 및 연습

동시 포트 점검 설정하기

다음으로, 각 스레드가 큐에서 포트를 가져와 처리할 때 사용할 worker 함수를 만듭니다. 이 함수는 tcp_test 함수를 호출하여 각 포트의 상태를 확인합니다.

port_scanner.py 파일에 다음 코드를 추가하세요:

def worker(target_ip: str, queue: Queue) -> None:
    while not queue.empty():
        port = queue.get()
        tcp_test(port, target_ip)
        queue.task_done()

worker 함수는 여러 스레드에 의해 실행되도록 설계되었습니다. 큐에서 포트 번호를 지속적으로 가져와 이전에 정의한 tcp_test 함수를 사용하여 대상 IP 의 해당 포트가 열려 있는지 확인합니다.

포트 테스트를 마친 후에는 큐에서 해당 작업을 완료된 것으로 표시합니다. 이 함수를 통해 포트를 동시에 스캔할 수 있어 전체 프로세스 속도가 크게 향상됩니다.

✨ 솔루션 확인 및 연습

스캐닝 프로세스 조율하기

main 함수는 큐를 설정하고, 워커 스레드를 초기화하며, 지정된 포트 범위에 대한 스캐닝 작업을 관리함으로써 전체 프로세스를 조율합니다.

port_scanner.pymain 함수를 이어서 추가하세요:

def main(host: str, start_port: int, end_port: int) -> None:
    target_ip = gethostbyname(host)
    queue = Queue()
    for port in range(start_port, end_port + 1):
        queue.put(port)
    for _ in range(100):  ## Adjust the number of threads if necessary
        t = threading.Thread(target=worker, args=(target_ip, queue,))
        t.daemon = True
        t.start()
    queue.join()
    print("Scanning completed.")

main 함수는 전체 포트 스캐닝 과정을 총괄합니다.

먼저 대상 호스트 이름을 IP 주소로 변환한 다음, 스캔할 포트 번호를 담을 큐를 생성합니다. 지정된 범위 내의 모든 포트를 큐에 넣고, 큐를 동시에 처리할 여러 워커 스레드 (이 예제에서는 100 개) 를 생성하여 각 포트를 확인합니다. 함수는 모든 포트 처리가 완료될 때까지 기다린 후 (queue.join()), 스캐닝이 완료되었음을 알리는 메시지를 출력합니다.

✨ 솔루션 확인 및 연습

포트 스캐너 실행하기

마지막으로 우리가 만든 포트 스캐너를 실행해 볼 차례입니다. 도구를 테스트하기 위해 로컬 호스트의 특정 범위 포트를 스캔해 보겠습니다.

스크립트를 실행 가능하게 만들기 위해 port_scanner.py 끝에 다음 줄을 추가하세요:

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='TCP Port Scanner')
    parser.add_argument('host', help='Host to scan')
    parser.add_argument('ports', help='Port range to scan, formatted as start-end')
    args = parser.parse_args()

    start_port, end_port = map(int, args.ports.split('-'))
    main(args.host, start_port, end_port)

이 코드는 포트 스캐너 스크립트에 명령줄 인자 파싱 기능을 추가하여 도구를 더 다재다능하고 사용자 친화적으로 만듭니다.

argparse 라이브러리를 사용하여 hostports라는 두 개의 필수 인자를 정의합니다.

  • host 인자는 스캔하려는 대상 호스트의 주소나 호스트 이름을 지정하는 데 사용됩니다.
  • ports 인자는 "시작 - 끝" 형식 (예: "5000-8000") 으로 스캔할 포트 범위를 정의하는 문자열을 받습니다. 스크립트는 - 구분자를 기준으로 이를 start_portend_port로 나누고 정수로 변환합니다. 이를 통해 사용자는 명령줄에서 스크립트를 실행할 때 대상과 포트 범위를 쉽게 지정할 수 있습니다.

이제 터미널에서 다음 명령을 실행하여 스캐너를 작동시켜 보세요:

python port_scanner.py 127.0.0.1 5000-9000

출력 결과:

Opened Port: 8081
Scanning completed.

포트 스캐너를 통해 열린 포트 (8081) 를 확인했습니다. 해당 포트에서 웹 서버가 실행 중이므로, 어떤 내용이 호스팅되고 있는지 확인할 수 있습니다.

이를 위해 먼저 추가 버튼을 클릭하고 "Web Service" 옵션을 선택합니다.

Web Service 옵션 선택

그런 다음 포트 번호 8081 을 입력하고 "Access" 버튼을 클릭합니다.

포트 입력 및 웹 서버 접속

시스템의 8081 번 포트에서 실행 중인 웹 서버를 확인할 수 있습니다.

8081 번 포트에서 실행 중인 웹 서버
✨ 솔루션 확인 및 연습

요약

이 프로젝트에서는 기본적인 tcp_test 함수부터 멀티스레드 스캐닝 방식에 이르기까지 Python 으로 기초적인 포트 스캐너를 구축해 보았습니다. 전체 과정을 관리 가능한 단계로 나누어 분석했으며, 최종적으로 대상 서버의 열린 포트를 식별할 수 있는 기능적인 도구를 완성했습니다. 이러한 실습 경험을 통해 네트워크 보안에 대한 이해를 넓혔을 뿐만 아니라 Python 프로그래밍 및 동시성 관리 기술을 연마할 수 있었습니다.