웹 애플리케이션 상호작용 (Requests)

사이버 보안Beginner
지금 연습하기

소개

웹 애플리케이션 상호작용 실습에 오신 것을 환영합니다. 사이버 보안과 웹 자동화 분야에서 웹 애플리케이션과 프로그래밍 방식으로 상호작용하는 것은 필수적인 기술입니다. 데이터 스크래핑, 취약점 테스트, 반복적인 작업 자동화 등 무엇을 하든 HTTP 요청을 안정적으로 송수신할 수 있어야 합니다.

이번 실습에서는 강력한 Python requests 라이브러리 사용법을 배웁니다. HTTP GET 및 POST 요청을 보내고, 서버 응답에서 중요한 정보를 추출하며, 폼 제출을 자동화하고, 세션 쿠키를 관리하여 인증 상태를 유지하는 스크립트를 작성하게 됩니다.

원시 소켓 (raw sockets) 과 비교했을 때, requests는 더 높은 추상화 수준에서 작동합니다. 헤더, 인코딩, 리다이렉트, 쿠키와 같은 많은 HTTP 세부 사항을 자동으로 처리해 줍니다. 따라서 패킷 단위의 메커니즘보다는 웹 동작과 워크플로우에 집중해야 하는 보안 자동화 작업에 매우 적합합니다.

HTTP GET 요청 보내기

이 단계에서는 Python 을 사용하여 기본적인 HTTP GET 요청을 보내는 방법을 배웁니다. GET 메서드는 특정 리소스에서 데이터를 요청할 때 사용하며, 웹 브라우저에 URL 을 입력할 때 웹 페이지를 요청하는 방식과 매우 유사합니다.

이를 가장 단순한 형태의 웹 자동화라고 생각하면 됩니다. 리소스를 요청하고 응답을 확인하는 것이죠. 많은 정보 수집 (reconnaissance), 테스트 및 모니터링 작업이 바로 여기서 시작됩니다.

먼저 프로젝트 디렉토리에 있는지 확인하세요. 이 실습에서 작성할 모든 Python 스크립트는 이곳에 저장됩니다.

cd ~/project

이제 미리 준비된 로컬 웹 서버로 GET 요청을 보내는 Python 스크립트를 작성해 보겠습니다. nano 텍스트 편집기를 사용하여 get_request.py라는 파일을 만듭니다.

nano get_request.py

편집기에 다음 Python 코드를 입력하세요:

import requests

url = 'http://127.0.0.1:5000/get'
response = requests.get(url)

print("Response Body:")
print(response.text)

파일을 저장하고 편집기를 종료합니다 (nano에서는 Ctrl+O, Enter로 저장하고 Ctrl+X로 종료합니다).

위 코드는 requests 라이브러리를 가져오고, 대상 URL 을 정의한 뒤 requests.get() 함수를 사용하여 요청을 보냅니다. 서버의 응답은 response 변수에 저장되며, 응답 본문의 원시 텍스트를 출력합니다.

Python 인터프리터를 사용하여 스크립트를 실행하세요:

python3 get_request.py

다음과 유사한 출력이 나타나야 합니다:

Response Body:
{"message":"GET request successful"}

HTTP 상태 및 헤더 추출

이 단계에서는 HTTP 응답에서 상태 코드와 헤더 등 더 자세한 정보를 추출하는 방법을 배웁니다.

응답 본문은 전체 정보의 일부일 뿐입니다. 상태 코드는 서버가 요청을 어떻게 이해했는지 알려주며, 헤더는 콘텐츠 유형, 캐싱 정책, 쿠키, 서버 소프트웨어와 같은 메타데이터를 설명합니다. 훌륭한 자동화 도구는 코드, 헤더, 본문이라는 세 가지 계층을 모두 확인합니다.

서버가 HTTP 요청에 응답할 때, 상태 코드 (예: 성공 시 200, 찾을 수 없을 때 404) 와 응답에 대한 메타데이터 (콘텐츠 유형이나 서버 소프트웨어 등) 를 제공하는 헤더를 포함합니다.

프로젝트 디렉토리에 check_status.py라는 새 파일을 만듭니다:

nano check_status.py

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

import requests

url = 'http://127.0.0.1:5000/get'
response = requests.get(url)

print(f"Status Code: {response.status_code}")

if response.status_code == 200:
    print("The request was successful!")

print("\nResponse Headers:")
print(f"Content-Type: {response.headers.get('Content-Type')}")
print(f"Server: {response.headers.get('Server')}")

파일을 저장하고 편집기를 종료합니다.

이 스크립트에서 response.status_code는 서버가 반환한 정수 상태 코드를 가져옵니다. response.headers는 서버가 보낸 모든 HTTP 헤더를 포함하는 딕셔너리 형태의 객체입니다. .get() 메서드를 사용하여 특정 헤더를 안전하게 추출합니다.

스크립트를 실행하여 추출된 정보를 확인하세요:

python3 check_status.py

출력 결과는 다음과 같아야 합니다:

Status Code: 200
The request was successful!

Response Headers:
Content-Type: application/json
Server: Werkzeug/3.0.1 Python/3.10.12

폼 제출 자동화 (POST)

이 단계에서는 HTTP POST 요청을 보내는 방법을 배웁니다. 데이터를 가져오는 데 사용되는 GET 요청과 달리, POST 요청은 서버로 데이터를 보낼 때 사용합니다. 이는 로그인 자격 증명과 같은 웹 폼을 제출할 때 흔히 사용됩니다.

이러한 구분은 HTTP 의미론을 반영합니다. 실제로 GET 은 주로 리소스를 읽는 데 사용되고, POST 는 데이터를 제출하거나 상태를 변경하는 작업에 자주 사용됩니다. 이러한 차이를 이해하면 애플리케이션의 동작을 예측하고 스크립트가 어떻게 상호작용해야 하는지 파악하는 데 도움이 됩니다.

사용자 이름과 비밀번호로 로그인 폼을 제출하는 것을 시뮬레이션하는 스크립트를 작성해 보겠습니다.

post_request.py라는 새 파일을 만듭니다:

nano post_request.py

다음 코드를 입력하세요:

import requests

url = 'http://127.0.0.1:5000/post'

## 폼 데이터를 딕셔너리로 정의
form_data = {
    'username': 'admin',
    'password': 'secret'
}

## data 매개변수를 사용하여 POST 요청 전송
response = requests.post(url, data=form_data)

print(f"Status Code: {response.status_code}")
print(f"Response Body: {response.text}")

파일을 저장하고 편집기를 종료합니다.

여기서 자격 증명을 포함하는 form_data라는 딕셔너리를 정의합니다. 그런 다음 requests.post()를 사용하고 data 매개변수에 딕셔너리를 전달합니다. requests 라이브러리는 이 데이터를 표준 웹 폼 제출 형식으로 자동 변환합니다.

스크립트를 실행하여 폼을 제출하세요:

python3 post_request.py

성공적인 로그인 메시지가 표시되어야 합니다:

Status Code: 200
Response Body: {"message":"Login successful"}

스크립트의 비밀번호를 잘못된 값 (예: wrongpass) 으로 변경하고 다시 실행하여 서버가 401 Unauthorized 상태 코드로 응답하는지 확인해 볼 수 있습니다.

세션 쿠키 관리

마지막 단계에서는 세션 쿠키를 관리하는 방법을 배웁니다. HTTP 는 상태가 없는 (stateless) 프로토콜이므로 각 요청은 독립적입니다. 사용자가 로그인한 후 이를 기억하기 위해 웹 애플리케이션은 쿠키를 사용합니다.

requests.get()이나 requests.post()를 직접 사용하면 요청 간에 쿠키가 저장되지 않습니다. 세션 (로그인 상태 유지 등) 을 유지하려면 requests 라이브러리에서 제공하는 Session 객체를 사용해야 합니다.

session_request.py라는 파일을 만듭니다:

nano session_request.py

다음 코드를 추가하세요:

import requests

## Session 객체 생성
session = requests.Session()

login_url = 'http://127.0.0.1:5000/login'
profile_url = 'http://127.0.0.1:5000/profile'

print("Attempting to access profile BEFORE login...")
response1 = session.get(profile_url)
print(f"Status: {response1.status_code}, Body: {response1.text.strip()}")

print("\nLogging in to establish a session...")
## 세션 객체는 서버가 반환한 쿠키를 자동으로 저장합니다
session.post(login_url)

print("\nAttempting to access profile AFTER login...")
## 세션 객체는 이 요청과 함께 저장된 쿠키를 자동으로 보냅니다
response2 = session.get(profile_url)
print(f"Status: {response2.status_code}, Body: {response2.text.strip()}")

파일을 저장하고 편집기를 종료합니다.

이 스크립트에서 requests.Session()은 지속적인 세션을 생성합니다. /login으로 POST 요청을 보내면 서버가 세션 쿠키를 설정합니다. Session 객체는 이 쿠키를 저장하고 이후 /profile로 보내는 GET 요청에 자동으로 포함시켜 보호된 경로에 접근할 수 있게 합니다.

스크립트를 실행하세요:

python3 session_request.py

결과를 확인하세요. 첫 번째 시도는 로그인하지 않았기 때문에 실패하지만, 두 번째 시도는 세션 쿠키가 자동으로 관리되어 성공하는 것을 볼 수 있습니다:

Attempting to access profile BEFORE login...
Status: 401, Body: {"message":"Unauthorized"}

Logging in to establish a session...

Attempting to access profile AFTER login...
Status: 200, Body: {"message":"Welcome to your profile!"}

요약

Python requests 라이브러리를 사용하여 웹 상호작용을 자동화하는 방법을 배웠습니다. 기본적인 HTTP GET 요청을 보내고 상태 코드나 헤더와 같은 중요한 메타데이터를 추출하는 것부터 시작하여, HTTP POST 요청을 통해 폼 제출을 자동화하는 단계까지 진행했습니다. 마지막으로 Session 객체를 사용하여 쿠키를 관리하고 여러 요청에 걸쳐 인증 상태를 유지하는 방법을 익혔습니다.

이러한 기술들은 웹 스크래핑, API 통합, 자동화된 보안 테스트 등 다양한 고급 작업의 기초가 됩니다.