다음을 통해 공유


SignalR 보안 소개

작성자 : 패트릭 플레처, 톰 피츠마켄

경고

이 설명서는 최신 버전의 SignalR용이 아닙니다. ASP.NET Core SignalR을 살펴보세요.

이 문서에서는 SignalR 애플리케이션을 개발할 때 고려해야 하는 보안 문제에 대해 설명합니다.

이 항목에서 사용되는 소프트웨어 버전

이 항목의 이전 버전

이전 버전의 SignalR에 대한 자세한 내용은 SignalR 이전 버전을 참조하세요.

질문 및 의견

이 자습서를 얼마나 좋아했는지와 페이지 하단의 메모에서 개선할 수 있는 사항에 대한 피드백을 남겨주세요. 자습서와 직접 관련이 없는 질문이 있는 경우 ASP.NET SignalR 포럼 또는 StackOverflow.com 게시할 수 있습니다.

개요

이 문서에는 다음 섹션이 포함되어 있습니다.

SignalR 보안 개념

인증 및 권한 부여

SignalR은 사용자를 인증하기 위한 기능을 제공하지 않습니다. 대신 SignalR 기능을 애플리케이션의 기존 인증 구조에 통합합니다. 일반적으로 애플리케이션에서와 같이 사용자를 인증하고 SignalR 코드에서 인증 결과를 사용합니다. 예를 들어 ASP.NET 양식 인증을 사용하여 사용자를 인증한 다음 허브에서 메서드를 호출할 권한이 있는 사용자 또는 역할을 적용할 수 있습니다. 허브에서 사용자 이름 또는 사용자가 역할에 속하는지 여부와 같은 인증 정보를 클라이언트에 전달할 수도 있습니다.

SignalR은 허브 또는 메서드에 액세스할 수 있는 사용자를 지정하는 Authorize 특성을 제공합니다. 허브 또는 허브의 특정 메서드에 Authorize 특성을 적용합니다. Authorize 특성이 없으면 허브에 연결된 클라이언트에서 허브의 모든 공용 메서드를 사용할 수 있습니다. 허브에 대한 자세한 내용은 SignalR Hubs에 대한 인증 및 권한 부여를 참조하세요.

허브에 Authorize 특성을 적용하지만 영구 연결은 적용하지 않습니다. PersistentConnection를 사용할 때 권한 부여 규칙을 적용하려면 AuthorizeRequest 메서드를 재정의해야 합니다. 영구 연결에 대한 자세한 내용은 SignalR 영구 연결에 대한 인증 및 권한 부여를 참조하세요.

연결 토큰

SignalR은 보낸 사람의 ID를 확인하여 악의적인 명령을 실행할 위험을 완화합니다. 각 요청에 대해 클라이언트와 서버는 인증된 사용자의 연결 ID 및 사용자 이름을 포함하는 연결 토큰을 전달합니다. 연결 ID는 연결된 각 클라이언트를 고유하게 식별합니다. 서버는 새 연결을 만들 때 연결 ID를 임의로 생성하고 연결 기간 동안 해당 ID를 유지합니다. 웹 애플리케이션에 대한 인증 메커니즘은 사용자 이름을 제공합니다. SignalR은 암호화 및 디지털 서명을 사용하여 연결 토큰을 보호합니다.

클라이언트 새 연결 요청에서 서버 수신 연결 요청까지, 서버 응답에서 클라이언트 수신 응답까지의 화살표를 보여주는 다이어그램입니다. 인증 시스템은 응답 및 수신된 응답 단계에서 연결 토큰을 생성합니다.

각 요청에 대해 서버는 토큰 내용의 유효성을 검사하여 지정된 사용자로부터 요청이 들어오는지 확인합니다. 사용자 이름은 연결 ID에 해당해야 합니다. SignalR은 연결 ID와 사용자 이름 모두의 유효성을 검사하여 악의적인 사용자가 다른 사용자를 쉽게 가장하지 못하도록 방지합니다. 서버가 연결 토큰의 유효성을 검사할 수 없으면 요청이 실패합니다.

클라이언트 요청에서 저장된 토큰에 대한 서버 수신 요청으로의 화살표를 보여 주는 다이어그램 연결 토큰과 메시지는 클라이언트 상자와 서버 상자에 모두 있습니다.

연결 ID는 확인 프로세스의 일부이므로 한 사용자의 연결 ID를 다른 사용자에게 표시하거나 쿠키와 같이 클라이언트에 값을 저장해서는 안 됩니다.

연결 토큰과 다른 토큰 형식 비교

연결 토큰은 세션 토큰 또는 인증 토큰으로 표시되므로 보안 도구에 의해 플래그가 지정되기 때문에 노출될 경우 위험이 발생합니다.

SignalR의 연결 토큰은 인증 토큰이 아닙니다. 이 요청을 하는 사용자가 연결을 만든 것과 동일한지 확인하는 데 사용됩니다. ASP.NET SignalR을 사용하면 연결이 서버 간에 이동할 수 있으므로 연결 토큰이 필요합니다. 토큰은 특정 사용자와 연결을 연결하지만 요청을 만드는 사용자의 ID를 어설션하지는 않습니다. SignalR 요청을 제대로 인증하려면 쿠키 또는 전달자 토큰과 같이 사용자의 ID를 어설션하는 다른 토큰이 있어야 합니다. 그러나 연결 토큰 자체는 해당 사용자가 요청했음을 클레임하지 않으며 토큰 내에 포함된 연결 ID만 해당 사용자와 연결됩니다.

연결 토큰은 자체 인증 클레임을 제공하지 않으므로 "세션" 또는 "인증" 토큰으로 간주되지 않습니다. 지정된 사용자의 연결 토큰을 가져와서 다른 사용자(또는 인증되지 않은 요청)로 인증된 요청에서 재생하면 요청의 사용자 ID와 토큰에 저장된 ID가 일치하지 않으므로 실패합니다.

연결할 때 그룹에 재참가하기

기본적으로 SignalR 애플리케이션은 연결 시간이 초과되기 전에 연결을 삭제하고 다시 설정하는 경우와 같이 일시적인 중단에서 다시 연결할 때 자동으로 사용자를 적절한 그룹에 다시 할당합니다. 다시 연결할 때 클라이언트는 연결 ID 및 할당된 그룹을 포함하는 그룹 토큰을 전달합니다. 그룹 토큰은 디지털 서명되고 암호화됩니다. 클라이언트는 다시 연결한 후 동일한 연결 ID를 유지합니다. 따라서 다시 연결된 클라이언트에서 전달된 연결 ID는 클라이언트에서 사용한 이전 연결 ID와 일치해야 합니다. 이 확인은 악의적인 사용자가 다시 연결할 때 권한 없는 그룹에 가입하라는 요청을 전달하지 못하도록 방지합니다.

그러나 그룹 토큰이 만료되지 않는다는 점에 유의해야 합니다. 사용자가 과거에 그룹에 속했지만 해당 그룹에서 금지된 경우 해당 사용자는 금지된 그룹을 포함하는 그룹 토큰을 모방할 수 있습니다. 어떤 사용자가 어떤 그룹에 속하는지 안전하게 관리해야 하는 경우 데이터베이스와 같이 해당 데이터를 서버에 저장해야 합니다. 그런 다음, 사용자가 그룹에 속하는지 여부를 서버에서 확인하는 논리를 애플리케이션에 추가합니다. 그룹 멤버 자격을 확인하는 예제는 그룹 작업을 참조하세요.

그룹 자동 다시 가입은 일시적인 중단 후 연결이 다시 연결될 때만 적용됩니다. 사용자가 애플리케이션에서 멀리 이동하여 연결을 끊거나 애플리케이션이 다시 시작되는 경우 애플리케이션은 해당 사용자를 올바른 그룹에 추가하는 방법을 처리해야 합니다. 자세한 내용은 그룹 작업을 참조하세요.

SignalR이 교차 사이트 요청 위조를 방지하는 방법

CSRF(교차 사이트 요청 위조)는 악의적인 사이트가 사용자가 현재 로그인되어 있는 취약한 사이트에 요청을 보내는 공격입니다. SignalR은 악성 사이트에서 SignalR 애플리케이션에 대한 유효한 요청을 만들 가능성이 매우 낮도록 하여 CSRF를 방지합니다.

CSRF 공격에 대한 설명

다음은 CSRF 공격의 예입니다.

  1. 사용자가 양식 인증을 사용하여 www.example.com에 로그인합니다.

  2. 서버에서 사용자를 인증합니다. 서버의 응답에는 인증 쿠키가 포함됩니다.

  3. 로그아웃하지 않고 사용자가 악성 웹 사이트를 방문합니다. 이 악성 사이트에는 다음 HTML 양식이 포함되어 있습니다.

    <h1>You Are a Winner!</h1>
    <form action="http://example.com/api/account" method="post">
        <input type="hidden" name="Transaction" value="withdraw" />
        <input type="hidden" name="Amount" value="1000000" />
        <input type="submit" value="Click Me"/>
    </form>
    

    양식 제출이 악성 사이트가 아닌 취약한 사이트에 게시된다는 것을 주의하세요. CSRF의 “교차 사이트” 부분입니다.

  4. 사용자가 제출 단추를 클릭합니다. 브라우저에는 요청이 포함된 인증 쿠키가 포함됩니다.

  5. 요청은 사용자의 인증 컨텍스트를 사용하여 example.com 서버에서 실행되며 인증된 사용자가 수행할 수 있는 모든 작업을 수행할 수 있습니다.

이 예제에서는 사용자가 양식 단추를 클릭해야 하지만 악성 페이지는 AJAX 요청을 SignalR 애플리케이션으로 보내는 스크립트를 쉽게 실행할 수 있습니다. 또한 SSL을 사용하면 악성 사이트에서 "https://" 요청을 보낼 수 있으므로 CSRF 공격을 방지하지 못합니다.

일반적으로 브라우저는 모든 관련 쿠키를 대상 웹 사이트로 보내기 때문에 인증에 쿠키를 사용하는 웹 사이트에 대해 CSRF 공격이 가능합니다. 그러나 CSRF 공격은 쿠키 악용에 국한되지 않습니다. 예를 들어, 기본 및 다이제스트 인증에도 취약해집니다. 사용자가 기본 또는 다이제스트 인증으로 로그인한 후 브라우저는 세션이 끝날 때까지 자격 증명을 자동으로 보냅니다.

SignalR에서 수행한 CSRF 완화 조치

SignalR은 악의적인 사이트가 애플리케이션에 유효한 요청을 만들지 못하도록 다음 단계를 수행합니다. SignalR은 기본적으로 이러한 단계를 수행하므로 코드에서 아무 작업도 수행할 필요가 없습니다.

  • 도메인 간 요청 사용 안 함 SignalR은 사용자가 외부 도메인에서 SignalR 엔드포인트를 호출하지 못하도록 도메인 간 요청을 사용하지 않도록 설정합니다. SignalR은 외부 도메인의 모든 요청을 유효하지 않은 것으로 간주하고 요청을 차단합니다. 이 기본 동작은 유지하는 것이 좋습니다. 그렇지 않으면 악의적인 사이트가 사용자를 속여 사이트에 명령을 보낼 수 있습니다. 도메인 간 요청을 사용해야 하는 경우 도메인 간 연결을 설정하는 방법을 참조하세요.
  • 쿠키가 아닌 쿼리 문자열에 연결 토큰 전달 SignalR은 연결 토큰을 쿠키가 아닌 쿼리 문자열 값으로 전달합니다. 악성 코드가 발견되면 브라우저에서 실수로 연결 토큰을 전달할 수 있으므로 쿠키에 연결 토큰을 저장하는 것은 안전하지 않습니다. 또한 쿼리 문자열에 연결 토큰을 전달하면 연결 토큰이 현재 연결을 넘어 유지되지 않습니다. 따라서 악의적인 사용자는 다른 사용자의 인증 자격 증명으로 요청을 수행할 수 없습니다.
  • 연결 토큰 확인연결 토큰 섹션에 설명된 대로 서버는 인증된 각 사용자와 연결된 연결 ID를 알고 있습니다. 서버는 사용자 이름과 일치하지 않는 연결 ID의 요청을 처리하지 않습니다. 악의적인 사용자가 사용자 이름과 현재 임의로 생성된 연결 ID를 알아야 하기 때문에 악의적인 사용자가 유효한 요청을 추측할 가능성은 거의 없습니다. 연결이 종료되는 즉시 해당 연결 ID가 유효하지 않습니다. 익명 사용자는 중요한 정보에 액세스할 수 없어야 합니다.

SignalR 보안 권장 사항

SSL(Secure Socket Layers) 프로토콜

SSL 프로토콜은 암호화를 사용하여 클라이언트와 서버 간의 데이터 전송을 보호합니다. SignalR 애플리케이션이 클라이언트와 서버 간에 중요한 정보를 전송하는 경우 전송에 SSL을 사용합니다. SSL 설정에 대한 자세한 내용은 IIS 7에서 SSL을 설정하는 방법을 참조하세요.

그룹을 보안 메커니즘으로 사용하지 마세요.

그룹은 관련 사용자를 수집하는 편리한 방법이지만 중요한 정보에 대한 액세스를 제한하는 보안 메커니즘은 아닙니다. 이는 사용자가 다시 연결하는 동안 그룹에 자동으로 다시 참가할 수 있는 경우에 특히 그렇습니다. 대신 권한 있는 사용자를 역할에 추가하고 허브 메서드에 대한 액세스를 해당 역할의 멤버로만 제한하는 것이 좋습니다. 역할에 따라 액세스를 제한하는 예제는 SignalR Hubs에 대한 인증 및 권한 부여를 참조하세요. 다시 연결할 때 그룹에 대한 사용자 액세스를 확인하는 예제는 그룹 작업을 참조하세요.

클라이언트에서 입력 안전하게 처리

악의적인 사용자가 다른 사용자에게 스크립트를 보내지 않도록 하려면 다른 클라이언트로 브로드캐스트하기 위한 클라이언트의 모든 입력을 인코딩해야 합니다. SignalR 애플리케이션에는 다양한 유형의 클라이언트가 있을 수 있으므로 서버가 아닌 수신 클라이언트에서 메시지를 인코딩해야 합니다. 따라서 HTML 인코딩은 웹 클라이언트에서 작동하지만 다른 유형의 클라이언트에서는 작동하지 않습니다. 예를 들어 채팅 메시지를 표시하는 웹 클라이언트 메서드는 함수를 호출하여 사용자 이름과 메시지를 안전하게 처리합니다 html() .

chat.client.addMessageToPage = function (name, message) {
    // Html encode display name and message. 
    var encodedName = $('<div />').text(name).html();
    var encodedMsg = $('<div />').text(message).html();
    // Add the message to the page. 
    $('#discussion').append('<li><strong>' + encodedName
        + '</strong>:  ' + encodedMsg + '</li>');
};

활성 연결을 사용하여 사용자 상태 변경 조정

활성 연결이 있는 동안 사용자의 인증 상태가 변경되면 사용자에게 "활성 SignalR 연결 중에 사용자 ID를 변경할 수 없습니다."라는 오류가 표시됩니다. 이 경우 애플리케이션이 서버에 다시 연결하여 연결 ID와 사용자 이름이 조정되었는지 확인해야 합니다. 예를 들어 애플리케이션에서 활성 연결이 있는 동안 사용자가 로그아웃할 수 있도록 허용하는 경우 연결의 사용자 이름이 다음 요청에 대해 전달된 이름과 더 이상 일치하지 않습니다. 사용자가 로그아웃하기 전에 연결을 중지한 다음 다시 시작하려고 합니다.

그러나 대부분의 애플리케이션은 수동으로 연결을 중지하고 시작할 필요가 없다는 점에 유의해야 합니다. 애플리케이션이 로그아웃한 후 사용자를 Web Forms 애플리케이션 또는 MVC 애플리케이션의 기본 동작과 같은 별도의 페이지로 리디렉션하거나 로그아웃한 후 현재 페이지를 새로 고치면 활성 연결이 자동으로 끊어지고 추가 작업이 필요하지 않습니다.

다음 예제에서는 사용자 상태가 변경될 때 연결을 중지하고 시작하는 방법을 보여 줍니다.

<script type="text/javascript">
    $(function () {
        var chat = $.connection.sampleHub;
        $.connection.hub.start().done(function () {
            $('#logoutbutton').click(function () {
                chat.connection.stop();
                $.ajax({
                    url: "Services/SampleWebService.svc/LogOut",
                    type: "POST"
                }).done(function () {
                    chat.connection.start();
                });
            });
        });
    });
</script>

또는 사이트에서 Forms 인증으로 슬라이딩 만료를 사용하고 인증 쿠키를 유효하게 유지하는 활동이 없는 경우 사용자의 인증 상태가 변경됩니다. 이 경우 사용자가 로그아웃되고 사용자 이름이 연결 토큰의 사용자 이름과 더 이상 일치하지 않습니다. 인증 쿠키를 유효하게 유지하기 위해 웹 서버에서 리소스를 주기적으로 요청하는 일부 스크립트를 추가하여 이 문제를 해결할 수 있습니다. 다음 예제에서는 30분마다 리소스를 요청하는 방법을 보여 줍니다.

$(function () {
    setInterval(function() {
        $.ajax({
            url: "Ping.aspx",
            cache: false
        });
    }, 1800000);
});

자동으로 생성된 JavaScript 프록시 파일

각 사용자에 대한 JavaScript 프록시 파일에 모든 허브 및 메서드를 포함하지 않으려면 파일의 자동 생성을 사용하지 않도록 설정할 수 있습니다. 여러 허브 및 메서드가 있지만 모든 사용자가 모든 메서드를 인식하지 않도록 하려면 이 옵션을 선택할 수 있습니다. EnableJavaScriptProxiesfalse로 설정하여 자동 생성을 사용하지 않도록 설정합니다.

var hubConfiguration = new HubConfiguration();
hubConfiguration.EnableJavaScriptProxies = false;
app.MapSignalR(hubConfiguration);

JavaScript 프록시 파일에 대한 자세한 내용은 생성된 프록시 및 해당 프록시가 수행하는 작업을 참조하세요.

예외

개체가 클라이언트에 중요한 정보를 노출할 수 있으므로 예외 개체를 클라이언트에 전달하지 않아야 합니다. 대신 관련 오류 메시지를 표시하는 클라이언트에서 메서드를 호출합니다.

public Task SampleMethod()
{
    try
    { 
        // code that can throw an exception
    }
    catch(Exception e)
    {
        // add code to log exception and take remedial steps

        return Clients.Caller.DisplayError("Sorry, the request could not be processed.");
    }
}