[인코그니토 5주차] CORS 취약점과 메시지 검증 필터링
-CORS 취약점과 대응 방안
-WebSocket 메시지 검증 및 필터링 - CSWSH 취약점
CORS
CORS(Cross-Origin Resource Sharing)는 다른 출처(도메인, 프로토콜, 포트)의 자원을 현재 출처에서 요청할 수 있게 해주는 메커니즘이다. 웹은 기본적으로 같은 출처 정책(Same-Origin Policy)을 따르기 때문에, 보안상의 이유로 한 출처에서 로드된 문서나 스크립트가 다른 출처의 자원과 상호작용하는 것을 제한한다. 그러나 현대의 웹 애플리케이션은 다양한 출처의 리소스를 통합하여 사용하는 경우가 많아 CORS가 필수적으로 사용된다.
*같은 출처 정책(Same-Origin Policy, SOP)은 웹 보안의 핵심 원칙 중 하나로, 웹 브라우저가 어떻게 데이터를 다른 출처(origin)의 웹 페이지와 상호작용할 수 있는지를 제한한다. 이 정책은 사용자의 정보를 보호하기 위해 고안되었다.
출처는 URL의 세 부분, 즉 프로토콜(예: http, https), 호스트(도메인 이름), 포트 번호의 조합으로 정의된다. 예를 들어, `https://example.com:8080`에서 프로토콜은 `https`, 호스트는 `example.com`, 포트는 `8080`이다. 같은 출처 정책에서는 이 세 부분이 모두 일치해야 동일 출처로 간주한다. 이 정책의 목적은 보안상의 이유에서 중요한 데이터, 예를 들어 사용자의 세션 쿠키나 토큰 정보 등이 악의적인 웹 사이트에 의해 읽히거나 변경되는 것을 방지하는 데 있다. 예외 및 우회 방법으로는 CORS (Cross-Origin Resource Sharing)와 JSONP(JSON with Padding)이 있다. CORS는 서버가 HTTP 헤더를 사용하여 브라우저에게 특정 출처의 스크립트가 자신의 리소스에 접근할 수 있도록 허용할 수 있고, 이를 통해 안전하게 출처 간 요청을 가능하게 한다. JSONP는 스크립트 태그를 사용하여 출처 간 요청을 가능하게 하는 기법이다. JSONP는 스크립트 태그가 SOP의 제한을 받지 않는 점을 이용한다. 하지만 JSONP는 보안상의 취약점을 가질 수 있으므로 현대의 웹 개발에서는 CORS를 선호한다.
CORS 취약점
CORS 취약점은 서버가 너무 관대하게 CORS 정책을 설정하여, 민감한 정보에 대한 무단 접근이나 변경을 허용할 수 있는 보안 문제를 말한다. 예를 들어, 서버가 모든 출처(Access-Control-Allow-Origin)로부터의 요청을 허용하도록 설정된 경우, 악의적인 웹사이트는 사용자의 브라우저를 통해 기밀 정보를 읽거나 변경할 수 있는 요청을 보낼 수 있다.
CORS 취약점 대응 방안
1. 명시적인 출처 허용
가장 간단한 대응 방안은 Access-Control-Allow-Origin 헤더에 구체적인 출처를 명시하는 것이다. 예를 들어, 오직 https://example.com에서만 리소스에 접근을 허용하고 싶다면, 서버는 이 출처만을 Access-Control-Allow-Origin 헤더에 지정하면 된다. 이 방식은 서버가 허용하는 출처를 명확히 제어할 수 있게 해준다.
서버에 헤더 설정을 하지 않았을 경우, 브라우저에서 CORS(교차 출처 리소스 공유) 정책 위반으로 인해 요청이 차단되었음을 알리는 메시지가 뜬다.
만약 서버에 올바른 헤더를 설정했을 경우, 통과할 수 있다.
2. 사전 요청(Preflight Request) 활용
CORS 요청은 때때로 사전 요청을 보내어 서버가 요청을 허용하는지 확인하는 과정을 거치는데, 복잡한 요청을 보내기 전 브라우저는 OPTIONS 메소드(GET이나 POST가 아닌)를 사용하여 사전 요청을 보내고, 서버는 이에 대한 허용 여부를 응답한다. 서버는 이 단계에서 유효한 출처, 메소드, 헤더 등을 검증하여, 불필요하거나 위험한 요청을 사전에 차단할 수 있다.
개발자 도구를 사용하여 `fetch` 요청을 보내고 예비 요청(Pre-flight request)을 확인할 수 있다. 여기서 언급한 `fetch` 요청은 웹 API에 비동기적으로 HTTP 요청을 보내기 위해 사용되는 JavaScript의 기능으로, 예비 요청은 특정 조건 하에 CORS(Cross-Origin Resource Sharing) 정책을 준수하기 위해 실제 요청을 보내기 전에 서버로 보내지는 자동 HTTP OPTIONS 요청이다.
우선 브라우저에서 `F12` 키를 눌러 개발자 도구로 접속한다. 개발자 도구가 연리면 상단 탭 중 Console을 클릭해서 이곳에서 JavaScript 코드를 직접 입력할 수 있다. Console에 다음과 같이 `fetch` 요청 코드를 입력한다.
await fetch("http://localhost:4000/users/location-registration", {"method":"DELETE"})
이 코드는 `http://localhost:4000/users/location-registration` 주소로 HTTP DELETE 요청을 비동기적으로 보낸다. 예비 요청을 확인하려면, 개발자 도구 내의 "Network" 탭으로 이동한다. `fetch` 요청을 실행한 후에, Network 탭에는 해당 요청에 대한 정보가 로드되는데, 여기서, HTTP 메소드가 OPTIONS인 요청을 찾는다. 이것이 바로 CORS 정책을 준수하기 위해 보낸 예비 요청이다. 예비 요청 후에 실제 `DELETE` 요청이 보내졌는지 확인한다. Network 탭에서 `DELETE` 메소드를 사용하는 요청을 찾아 해당 요청의 세부 내용을 검토한다.
네트워크 요청에 들어가면 preflight 정보를 확인할 수 있다.
3. 자격 증명(Credentials) 요구
CORS 요청 시 자격 증명을 포함시키려면, 서버는 Access-Control-Allow-Credentials 헤더를 true로 설정해야 한다. 그러나 이 설정을 사용할 때는 출처를 모든 출처로 설정할 수 없으며, 명시적인 출처를 지정해야 한다. 이것은 자격 증명이 포함된 요청을 보낼 때 추가적인 보안 조치를 강제한다.
WebSocket 메시지 검증 및 필터링: CSWSH 취약점 대응
메시지 검증 및 필터링의 중요성
WebSocket을 사용할 때, 서버와 클라이언트 간의 모든 데이터 교환은 신뢰할 수 있고 안전해야 한다. 이는 입력 데이터 검증이 부족하거나 전혀 없는 경우, 악의적인 스크립트가 서버로 전송되어 실행될 수 있기 때문이다. 따라서, 서버는 클라이언트로부터 수신된 모든 메시지를 검증하고 필터링하여, SQL 인젝션, 스크립트 삽입, 명령 주입 등의 공격으로부터 시스템을 보호해야 한다.
메시지 검증 및 필터링 방법
우선 입력 데이터를 검증하는 방법이 있다. 서버는 클라이언트로부터 수신된 데이터가 예상 형식과 일치하는지 확인해야 합니다. 예를 들어, JSON 형식의 데이터를 기대하는 경우, 수신된 메시지가 유효한 JSON인지 검사한다. 또, 수신된 데이터 내에서 스크립트, SQL 코드 조각 등의 악의적인 내용을 제거하거나 무해화한다. 또, 액세스 제어를 통해 사용자의 권한을 확인하여, 특정 작업(메시지를 삭제하거나 수정할 수 있는 권한)을 실행할 수 있는 권한이 있는지 검증한다. 그리고 시지의 길이에 제한을 두어, 대량의 데이터가 서버로 전송되어 서비스 거부(DoS) 공격을 유발하는 것을 방지하고, 클라이언트로부터의 요청 수를 제한하여, 과도한 요청으로 인한 서비스 거부를 방지하는 방법이 있다.
CSWSH 취약점 검사 실행 코드 분석
import websocket
import ssl
def check_cswsh_vulnerability(url, origin):
#웹소켓 연결 설정
ws = None
try:
ws = websocket.create_connection(url,
header=["Origin: " + origin],
sslopt={"cert_reqs": ssl.CERT_NONE})
print(f"Connected to {url} with Origin {origin}")
#취약점 검사 메시지 전송
test_message = "Hello, server!"
ws.send(test_message)
print(f"Sent: {test_message}")
#서버로부터의 응답 받기
response = ws.recv()
print(f"Received: {response}")
#만약 서버가 응답을 반환하면, 출처 검증이 제대로 이루어지지 않은 것일 수 있음
print("The WebSocket server may be vulnerable to CSWSH.")
except Exception as e:
print(f"Failed to connect or send: {e}")
print("The WebSocket server may not be vulnerable to CSWSH or is not accessible.")
finally:
if ws:
ws.close()
if __name__ == "__main__":
#웹소켓 서버 URL 및 악의적인 출처 설정
ws_url = "wss://example.com/websocket"
malicious_origin = "https://malicious-site.com"
#CSWSH 취약점 검사 실행
check_cswsh_vulnerability(ws_url, malicious_origin)
ws_url 변수를 검사하고자 하는 웹소켓 서버의 URL로 설정한다.
malicious_origin 변수를 악의적인 출처로 가정한 URL로 설정한다.
이 스크립트는 웹소켓 서버가 Cross-Site WebSocket Hijacking (CSWSH) 취약점에 대해 취약한지 여부를 검사하는 데 사용된다. CSWSH는 공격자가 사용자의 브라우저를 이용해 악의적인 출처에서 웹소켓 서버로 요청을 보내고, 사용자를 대신해 서버와 상호작용할 수 있게 하는 취약점이다. 이는 일반적으로 웹소켓 서버가 출처(Origin)를 제대로 검증하지 않아 발생한다.
1. 웹소켓 연결 설정
`websocket.create_connection` 함수는 주어진 `url`로 웹소켓 연결을 시도한다. 이 때 `header` 인자를 사용해 `Origin` 헤더에 `origin` 변수의 값을 포함시켜, 요청이 특정 출처에서 왔다는 것을 서버에 알린다. 여기서 `origin`은 악의적인 출처를 가정한 값이다.
sslopt={"cert_reqs": ssl.CERT_NONE}`는 SSL 인증서 검증을 비활성화하여, 자체 서명된 인증서나 인증서 오류가 있는 서버와의 테스트를 용이하게 한다.
2. 취약점 검사 메시지 전송:
`test_message`에 담긴 메시지를 서버로 전송한다. 이 메시지는 취약점 검사를 위한 것이며, 실제 공격 시나리오에서는 사용자 세션을 탈취하거나 악의적인 작업을 수행하는 데 사용될 수 있는 데이터가 될 수 있다.
3. 서버로부터의 응답 받기:
`ws.recv()`를 통해 서버로부터의 응답을 대기하고 이를 출력한다. 서버가 응답을 반환한다면, 이는 서버가 출처를 제대로 검증하지 않고 요청을 수락했을 수 있음을 말한다.
4. 취약성 평가:
서버가 응답을 반환하면, "The WebSocket server may be vulnerable to CSWSH."라는 메시지를 출력하여 해당 서버가 CSWSH 취약점에 취약할 수 있음을 알린다. 만약 연결이 실패하거나 예외가 발생하면 서버가 취약하지 않거나 접근할 수 없음을 나타낸다.
5. 연결 종료:
스크립트 실행이 끝나면, `ws.close()`를 호출하여 열린 웹소켓 연결을 안전하게 닫는다.