[security] RESTful 애플리케이션에서 CSRF를 방지하는 방법은 무엇입니까?

CSRF (Cross Site Request Forgery)는 일반적으로 다음 방법 중 하나로 방지됩니다.

  • 참조 자 확인-RESTful이지만 신뢰할 수 없음
  • 양식에 토큰을 삽입하고 서버 세션에 토큰을 저장하십시오-실제로 RESTful이 아닙니다.
  • 암호화 된 일회용 URI-토큰과 같은 이유로 RESTful이 아닙니다.
  • 이 요청에 대해 수동으로 비밀번호 보내기 (HTTP 인증에 사용 된 캐시 된 비밀번호가 아님)-RESTful하지만 편리하지 않음

내 생각은 토큰을 생성하기 위해 사용자 비밀, 비밀 스럽지만 정적 인 양식 ID 및 JavaScript를 사용하는 것입니다.

<form method="POST" action="/someresource" id="7099879082361234103">
    <input type="hidden" name="token" value="generateToken(...)">
    ...
</form>
  1. GET /usersecret/john_doe 인증 된 사용자로부터 JavaScript가 가져옵니다.
  2. 응답 : OK 89070135420357234586534346이 비밀은 개념적으로는 정적이지만 보안을 향상시키기 위해 매일 / 시간마다 변경할 수 있습니다. 이것은 유일한 기밀입니다.
  3. JavaScript로 암호화 된 (그러나 모든 사용자에게는 정적입니다!) 양식 ID를 읽고 사용자 시크릿과 함께 처리하십시오. generateToken(7099879082361234103, 89070135420357234586534346)
  4. 생성 된 토큰과 함께 양식을 서버로 보냅니다.
  5. 서버는 사용자 시크릿과 폼 ID를 알고 있기 때문에 클라이언트가 두 결과를 전송하고 비교하기 전에했던 것과 동일한 generateToken 함수를 실행할 수 있습니다. 두 값이 동일한 경우에만 작업이 승인됩니다.

JavaScript 없이는 작동하지 않는다는 사실에도 불구하고이 접근 방식에 문제가 있습니까?

추가:



답변

여기에는 많은 답변이 있으며 그중 상당수에 문제가 있습니다.

하지 말아야 할 것 :

  1. JavaScript에서 세션 토큰을 읽어야한다면 끔찍하게 잘못한 것입니다. 세션 식별자 쿠키에는 항상 HTTPOnly가 설정되어 있어야 스크립트에서 사용할 수 없습니다.

    이 하나의 보호를 통해 XSS의 영향을 상당히 줄일 수 있습니다. 공격자가 더 이상 로그인 한 사용자 세션 토큰을 얻을 수 없기 때문에 모든 의도와 목적을 위해 애플리케이션의 자격 증명과 동일합니다. 당신은 왕국에 열쇠를주기 위해 하나의 오류를 원하지 않습니다.

  2. 세션 식별자는 페이지의 내용에 기록되지 않아야합니다. 이는 HTTPOnly를 설정 한 것과 같은 이유 때문입니다. 이것은 귀하의 csrf 토큰이 귀하의 세션 ID가 될 수 없음을 의미합니다. 다른 값이어야합니다.

해야 할 일 :

  1. OWASP의 지침을 따르십시오 .

  2. 특히 이것이 REST 애플리케이션 인 경우 CSRF 토큰의 이중 제출을 요구할 수 있습니다 . 이렇게하는 경우 상위 도메인 (example.com)이 아닌 특정 전체 도메인 ( www.mydomain.com )으로 정의 하고 “samesite”쿠키 속성도 활용해야합니다. 인기.

암호화 된 임의의 항목을 만들고 ASCII Hex 또는 Base64 인코딩으로 저장 한 다음 서버가 페이지를 반환 할 때 쿠키 및 양식에 추가하면됩니다. 서버 측에서 쿠키 값이 양식 값과 일치하는지 확인하십시오. Voila, CSRF를 죽이고 사용자에게 추가 메시지를 표시하지 않았으며 더 많은 취약점에 노출되지 않았습니다.

참고 : @krubo가 아래에 언급했듯이 이중 제출 기술 에는 몇 가지 약점이있는 것으로 밝혀졌습니다 (이중 제출 참조) . 이 약점에는 다음이 필요하기 때문에 :

  1. 상위 도메인으로 범위가 지정된 쿠키를 정의합니다.
  2. HSTS 설정에 실패했습니다 .
  3. 공격자는 사용자와 서버 사이의 일부 네트워크 위치를 제어합니다.

약점은 “실제 보안 위험”보다는 “멋진 데프콘 토크”범주에 더 많이 해당한다고 생각합니다. 어쨌든 이중 제출을 사용하려는 경우 자신을 완전히 보호하기 위해 몇 가지 추가 조치를 취하는 것이 나쁘지 않습니다.


새로운 업데이트 07/06/2020

이중 제출을 수행하는 새로운 방법은 이전과 같이 요청 본문에 암호화 임의 문자열을 생성하고 전달하는 것입니다. 그러나 쿠키가 동일한 정확한 값을 갖기보다는 쿠키가 인증서에 의해 서명되는 문자열의 인코딩 된 값이되도록하십시오. 이것은 여전히 ​​서버 측에서 확인하기 쉽지만 공격자가 모방하기가 훨씬 더 어렵습니다. 내 게시물 앞부분에 설명 된 samesite 쿠키 속성 및 기타 보호 기능을 계속 사용해야합니다.


답변

이게 맞습니까?

  • 쿠키를 통해 로그인 한 사용자를 CSRF로부터 보호하기를 원합니다.
  • 동시에 앱의 Basic, OAuth 및 Digest 인증 요청을위한 RESTful 인터페이스가 필요합니다.

그렇다면 사용자가 쿠키를 통해 로그인했는지 확인 하고 CSRF를 적용한 후에 만 ​​적용하는 것은 어떻습니까?

확실하지 않지만 다른 사이트에서 기본 인증 또는 헤더와 같은 것을 위조 할 수 있습니까?

내가 아는 한 CSRF는 쿠키에 관한 것 입니까? RESTful 인증은 쿠키에서 발생하지 않습니다.


답변

인증 / 승인하려면 서버에 어떤 상태가 필요합니다. http 세션 일 필요는 없지만 분산 캐시 (예 : memcached) 또는 데이터베이스에 저장할 수 있습니다.


인증에 쿠키를 사용하는 경우 가장 쉬운 해결책은 쿠키 값을 두 번 제출하는 것입니다. 양식을 제출하기 전에 쿠키에서 세션 ID를 읽고 숨겨진 필드에 저장 한 다음 제출하십시오. 서버 측에서 요청의 값이 쿠키에서 가져온 세션 ID와 동일한 지 확인합니다. 다른 도메인의 악한 스크립트는 쿠키에서 세션 ID를 읽을 수 없으므로 CSRF를 방지합니다.

이 체계는 세션 전체에서 단일 식별자를 사용합니다.

더 많은 보호를 원하면 세션별로 고유 ID를 생성하십시오.

또한 JS에서 토큰을 생성하지 마십시오. 누구나 코드를 복사하고 다른 도메인에서 실행하여 사이트를 공격 할 수 있습니다.


답변

정적 양식 ID는 전혀 보호를 제공하지 않습니다. 공격자가 직접 가져올 수 있습니다. 공격자는 클라이언트에서 JavaScript를 사용하도록 제한되지 않습니다. 그는 서버 측에서 정적 양식 ID를 가져올 수 있습니다.

제안 된 변호를 완전히 이해하고 있는지 모르겠습니다. 어디에서 GET /usersecret/john_doe왔습니까? 페이지 JavaScript의 일부입니까? 이것이 문자 그대로 제안 된 URL입니까? 그렇다면 이것이 username비밀이 아니라고 가정합니다. 즉, 브라우저 또는 플러그인 버그가 도메인 간 GET 요청을 허용하는 경우 evil.ru가 사용자 비밀을 복구 할 수 있음을 의미합니다. 도메인 간 GET을 수행 할 수있는 사람이 검색 할 수 있도록 허용하는 대신 인증시 사용자 비밀을 쿠키에 저장하지 않는 이유는 무엇입니까?

CSRF에 저항하기를 원하는 자체 인증 시스템을 구현하기 전에 “교차 사이트 위조에 대한 강력한 방어”를 주의 깊게 읽었습니다 . 사실, 나는 내 자신의 인증 시스템을 구현하는 것을 전혀 재고 할 것입니다.


답변

CSRF 예방 치트 시트 에는 편안한 서비스에서 사용할 수 있는 몇 가지 방법 이 있습니다. 가장 RESTful 상태 비 저장 CSRF 완화는 Origin 또는 HTTP 리퍼러 를 사용하여 요청이 신뢰하는 도메인에서 시작되었는지 확인하는 것입니다.


답변

JavaScript 없이는 작동하지 않는다는 사실에도 불구하고이 접근 방식에 문제가 있습니까?

사용자 비밀은 클라이언트에 보내는 경우 비밀이 아닙니다. 우리는 일반적으로 이러한 비밀을 사용하여 해시를 생성하고 양식과 함께 보내고 비교를 위해 다시 기다립니다.

RESTful이 되려면 요청에 처리 방법에 대한 모든 정보가 포함되어야합니다. 이를 수행 할 수있는 방법 :

  • REST 클라이언트에 csrf 토큰 쿠키를 추가하고 양식과 함께 숨겨진 입력으로 동일한 토큰을 보냅니다. 서비스와 클라이언트가 서로 다른 도메인에있는 경우 자격 증명을 공유해야합니다. 서비스에서 2 개의 토큰을 비교해야하며 동일하면 요청이 유효합니다.

  • REST 서비스에 csrf 토큰 쿠키를 추가하고 리소스 표현 (숨겨진 입력 등)과 함께 동일한 토큰을 보낼 수 있습니다. 다른 모든 것은 이전 솔루션의 끝과 동일합니다. 이 솔루션은 RESTfulness의 가장자리에 있습니다. (클라이언트가 쿠키를 수정하기 위해 서비스를 호출하지 않을 때까지 괜찮습니다. 쿠키가 http 전용 인 경우 클라이언트는 이에 대해 알지 못하며 그렇지 않은 경우 클라이언트가 설정해야합니다.) 더 많은 작업을 수행 할 수 있습니다. 각 양식에 다른 토큰을 추가하고 쿠키에 만료 시간을 추가하는 경우 복잡한 솔루션입니다. 양식과 함께 만료 시간을 다시 보낼 수 있으므로 토큰 유효성 검사가 실패한 이유를 알 수 있습니다.

  • 서비스의 리소스 상태에서 사용자 암호 (사용자별로 다름)를 가질 수 있습니다. 표현을 작성하여 각 양식에 대한 토큰 (및 만료 시간)을 생성 할 수 있습니다. 실제 토큰 (및 만료 시간, 메서드, URL 등) 및 사용자 시크릿에서 해시를 생성하고 해당 해시를 양식과 함께 보낼 수도 있습니다. 물론 “사용자 비밀”을 비밀로 유지하므로 양식과 함께 보내지 않습니다. 그 후 서비스가 요청을 받으면 요청 매개 변수와 사용자 시크릿에서 해시를 다시 생성하고 비교할 수 있습니다. 일치하지 않으면 요청이 유효하지 않습니다.

REST 클라이언트가 자바 스크립트 주입 가능한 경우 이들 중 어느 것도 보호 할 수 없으므로 모든 사용자 콘텐츠를 HTML 엔터티에 대해 확인하고 모두 제거하거나 innerHTML 대신 항상 TextNodes를 사용해야합니다. SQL 주입과 HTTP 헤더 주입으로부터 자신을 보호해야합니다. 사이트를 새로 고칠 때 간단한 FTP를 사용하지 마십시오. 기타 등등 … 사이트에 악의적 인 코드를 삽입하는 방법에는 여러 가지가 있습니다.

GET 요청은 항상 서비스와 클라이언트가 읽는다는 것을 언급하는 것을 거의 잊었습니다. 서비스에 따르면 이것은 클라이언트가 브라우저에서 URL을 설정하면 리소스 또는 여러 리소스의 표현을 가져야하며 리소스에 대해 POST / PUT / DELETE 메서드를 호출해서는 안됩니다. 예를 들어 GET http://my.client.com/resource/delete -> DELETE http://my.api.com/resource매우 나쁜 솔루션입니다. 그러나 CSRF를 방해하고 싶다면 이것은 매우 기본적인 기술입니다.


답변