유저 인증 - XSS, CSRF

Dayeon myeong·2021년 2월 18일
5

백엔드공부

목록 보기
3/3

로그인은 어떻게 이루어지나

계정 정보를 요청 헤더에 넣는 방식

계정정보를 요청에 담아 보내는 방식. HTTP 요청에 인증할 수단에 비밀번호를 넣는다.

해커가 HTTP 요청을 가로채서 계정정보를 알 수 있다. 최악의 인증방식

쿠키 방식

  1. 클라이언트 -> 서버 첫 요청 시 서버는 응답할 때 쿠키에 저장하고자 하는 유저 정보를 Header 의 Set Cookie 로 함께 전달합니다.
  2. 클라이언트에 저장된 쿠키는 이후 매 요청 때마다 현재 브라우저에 저장된 쿠키를 Header 의 쿠키로 전달합니다.
  3. 서버는 클라이언트의 요청(Request)에서 쿠키 값을 참고하여 비즈니스 로직을 수행합니다. (ex 로그인 상태 유지, ...)

쿠키는 클라이언트 로컬에 저장되어있기 때문에 다른 사용자에 의해서 임의로 변경이 가능하므로 보안성이 낮습니다.

세션/쿠키 방식

  1. 사용자가 로그인을 합니다.
  2. 서버에서는 계정정보를 읽어 사용자를 확인한 후, 사용자의 고유한 ID값을 부여합니다.
  3. 브라우저에 대한 세션 저장소를 생성하여 이 ID값을 저장한 후, 이와 연결되는 세션 ID를 발급받습니다.
  4. 서버는 생성한 세션 ID를 쿠키값으로 만들고 Set Cookie라는 쿠키 헤더를 통해서 클라이언트 브라우저로 보냅니다.(response)
  5. 브라우저는 이 쿠키값을 저장해 두었다가 서버에 인증이 필요한 요청마다 쿠키에 저장한 세션id를 쿠키 헤더를 통해서 서버에 전달합니다.
  6. 서버는 전송받은 세션ID를 이용해 해당 세션 저장소에 대조 한 후 대응되는 정보를 가져옵니다.
  7. 인증이 완료되고 서버는 사용자에 맞는 데이터를 보내줍니다.

세션 저장소를 보통 redis를 많이 사용한다고 함.

http 요청 중에 쿠키가 노출되더라도 쿠키 자체(세션 id)는 유의미한 값을 가지고 있지 않으므로 안전할 수 있음. 그러나 해커가 쿠키를 가로챈다면 그 훔친 쿠키를 이용해 http 요청을 보내면 서버는 사용자로 인식해 정보를 그냥 보내주게됨

해결책 1 : https를 사용해 요청 자체를 탈취해도 안의 정보를 읽기 힘들게 한다
해결책 2 : 세션에 유효시간을 설정한다.

access token 토큰 방식 (ft. jwt)

  1. 사용자가 로그인을 한다.
  2. 서버에서는 계정정보를 읽어 사용자를 확인 후, 사용자의 고유한 ID값을 부여한 후, 기타 정보와 함께 Payload에 넣습니다.
  3. JWT 토큰의 유효기간을 설정합니다.
  4. 암호화할 SECRET KEY를 이용해 ACCESS TOKEN을 발급합니다.
  5. 사용자는 Access Token을 받아 저장한 후, 인증이 필요한 요청마다 토큰을 헤더에 실어 보냅니다.
  6. 서버에서는 해당 토큰의 Verify Signature를 SECRET KEY로 복호화한 후, 조작 여부, 유효기간을 확인합니다.
  7. 검증이 완료된다면, Payload를 디코딩하여 사용자의 ID에 맞는 데이터를 가져옵니다.

세션/쿠키의 경우 만일 쿠키가 악의적으로 이용된다면, 해당하는 세션을 지워버리면 됨. 하지만 jwt는 한 번 발급되면 유효기간이 완료될 때까지 계속 사용 가능. 따라서 악의적인 사용자는 유효기간이 지나기 전까지 정보를 가져갈 수 있음

해결책 : 기존의 access token의 유효기간을 짧게하고 refresh token이라는 새로운 토큰을 발급하면 됨.

access token + refresh token을 활용한 토큰 방식 (ft.jwt)

  1. 사용자가 ID , PW를 통해 로그인합니다.
  2. 서버에서는 회원 DB에서 값을 비교합니다(보통 PW는 일반적으로 암호화해서 들어갑니다)

db에 pw는 해쉬함수로 해싱을 한 pw를 저장.
이때 salting,key 스트레칭,adaptive key derivation function(bcrypt,,) 을 이용하여 더 안전하게 해싱한 pw 저장

3~4. 사용자가 확인 되면 Access Token, Refresh Token을 발급합니다. 이때 일반적으로 회원DB에 Refresh Token을 저장해둡니다.
5. 사용자는 Refresh Token은 안전한 저장소에 저장 후, Access Token을 헤더에 실어 요청을 보냅니다.
6~7. Access Token을 검증하여 이에 맞는 데이터를 보냅니다.
8. 시간이 지나 Access Token이 만료됐다고 보겠습니다.
9. 사용자는 이전과 동일하게 Access Token을 헤더에 실어 요청을 보냅니다.
10~11. 서버는 Access Token이 만료됨을 확인하고 권한없음을 신호로 보냅니다.

** Access Token 만료가 될 때마다 계속 과정 9~11을 거칠 필요는 없습니다.
사용자(프론트엔드)에서 Access Token의 Payload를 통해 유효기간을 알 수 있습니다. 따라서 프론트엔드 단에서 API 요청 전에 토큰이 만료됐다면 바로 재발급 요청을 할 수도 있습니다.

  1. 사용자는 Refresh Token과 Access Token을 함께 서버로 보냅니다.
  2. 서버는 받은 Access Token이 조작되지 않았는지 확인한 후, Refresh Token과 사용자의 DB에 저장되어 있던 Refresh Token을 비교합니다. Token이 동일하고 유효기간도 지나지 않았다면 새로운 Access Token을 발급해줍니다.
  3. 서버는 새로운 Access Token을 헤더에 실어 다시 API 요청을 진행합니다.

access token이 만료될 때마다 새롭게 발급하는 과정에서 생기는 http 요청 횟수가 많아짐. 서버의 자원 낭비

refresh token도 db에 저장 해야 되나..? -> redis에 저장하기로

XSS , CSRF 공격

  1. XSS 공격
    해커가 클라이언트 브라우저에 javascript를 삽입해 실행하는 공격.
    공격자가 < input > 태그를 통해 javascript를 서버로 전송해 스크립트를 실행하거나 url에 javascript를 적어 클라이언트에서 스크립트 실행이 가능하다면 공격자가 사이트에 스크립트를 삽입해 xss 공격을 할 수 있다. 이때 공격자는 Javascript를 통해 사이트의 글로벌 변숫값을 가져오거나 그 값을 이용해 사이트인 척 API 콜을 요청할 수도 있다. 다시 말하면 공격자의 코드가 내 사이트의 로직인 척 행동할 수 있다는 거다.

  2. CSRF 공격
    공격자가 다른 사이트에서 우리 사이트의 API 콜을 요청해 실행하는 공격이다. API 콜을 요청할 수 있는 클라이언트 도메인이 누구인지 서버에서 통제하고 있지 않다면 CSRF가 가능한데, 이때 공격자가 클라이언트에 저장된 유저 인증정보를 서버에 보낼 수 있다면, 제대로 로그인한 것처럼 유저의 정보를 변경하거나 유저만 가능한 액션들을 수행할 수 있다.

클라이언틍에서는 보통 인증정보(세션 id, access token, refresh token)를 localstorage나 쿠키에 저장한다. 페이지를 리프레시하거나 창을 닫고 다시 접속할 때도 로그인 정보가 이어지도록 둘 다 브라우저에 방식.

쿠키는 브라우저를 종료해도 계속해서 정보가 남아있음.

localstorage는 강제적으로 지우지 않는 이상 영구적임. 데이터 만료 시점이 없다.

쿠키와 localstorage는 위 두가지 공격에 취약할 수 있다.

추가적으로 sesstion storage가 있는데 여기에 저장된 데이터는 페이지 세션이 종료되면(웹 브라우저를 닫을 경우) 지워진다.

인증정보를 local storage에 저장했을 때

브라우저 저장소에 저장하는 방식. javascript 내 글로벌 변수로 읽기/쓰기 접근이 가능
이 곳에 인증 정보(세션 id, refresh token, access token)을 저장하면 XSS 취약점을 통해 local storage 안에 담긴 값을 불러오거나, 불러온 값을 이용해 api 콜을 위조할 수 있다.

인증정보를 쿠키에 저장했을 때

브라우저에 쿠키로 저장되는데, 클라이언트가 HTTP 요청을 보낼 때마다 자동으로 쿠키가 서버에 전송된다. Javascript 내 글로벌 변수로 읽기 / 쓰기 접근이 가능하다.

해커가 XSS 취약점을 통해 cookie에 담긴 값들을 불러오거나, 불러온 값을 통해 API 콜을 보내면 쿠키에 담긴 값들이 함께 전송되어 로그인한 척 공격을 수행할 수 있다.

쿠키에 세션 id나 accessToken을 저장해 인증에 이용하는 구조에 CSRF 취약점이 있다면 해커는 CSRF 공격으로 API 콜 요청시 자동으로 인증 정보가 쿠키에 담겨 서버로 보내진다. 공격자는 유저 권한으로 정보를 가져오거나 액션을 수행할 수 있다.

쿠키에 refresh token만 저장하고 access token은 다른 곳에 저장하면 CSRF 공격시 쿠키가 서버로 보내져도 access token이 없기 때문에 유저 권한으로 정보를 가져오거나 액션을 수행할 수 없다. 즉, CSRF 공격 방어 가능

secure httpOnly 쿠키 방식

브라우저에 쿠키로 저장되는 건 같지만 javascript 내에서 접근이 불가능하다. secure를 적용하면 https 접속에서만 동작한다.

httpOnly 쿠키 방식으로 저장된 정보는 XSS 공격을 해도 자바스크립트 내에서 httpOnly 쿠키에 접근이 불가능하기 때문에 XSS 방어 가능.
하지만, httpOnly 쿠키에 담긴 값에 접근은 할 수 없지만 XSS 취약점을 노려 API 콜을 해커가 요청하면 httpOnly 쿠키 값도 함께 보내저 유저인 척 행동 가능.

httpOnly 쿠키에 access token이나 세션 id 인증 정보를 저장한다면 CSRF 공격으로 API 콜을 해커가 요청하면 쿠키가 서버로 자동으로 보내지기 때문에 공격자는 유저 액션 수행할 수 있다. 즉, access token이나 세션 id는 httpOnly 쿠키에 저장하면 안됨.

httpOnly 쿠키에 refresh token만 저장한다면 CSRF 공격시 쿠키가 서버로 보내져도 유저 권한으로 일을 수행할 수 없다. 즉, CSRF 공격 방어 가능

결론 : secure httpOnly 쿠키에 refresh token만 저장하고(CSRF 공격 방어), 클라이언트와 서버가 XSS 방어 처리를 해줘야 한다. 그리고 access token은 json payload에 담고 웹 어플리케이션 내 로컬 변수로 이용.

백엔드에서는 어떻게?

  1. POST / login
    request : 이메일 , 비밀번호
    response : refreshtoken, access token

백엔드는 http 응답 set-cookie 헤더에 refresh token 값을 넣고, access token을 json으로 응답한다.

  1. POST / silent-refresh
    request : (쿠키에 담긴 refresh token)
    response : 새 refresh token, 새 access token

백엔드는 http 응답 set-cookie 헤더에 refresh token 값을 넣고, access token을 json으로 응답한다.

웹이 mount 될 때마다(브라우저 창이 꺼지거나 페이지가 리프레시 되는 등 페이지가 리로드 될 때) refresh token을 이용해 새로운 access token을 받아와 웹 어플리케이션 내 지역 변수에 저장하고 사용한다.

  1. 사용자 인가 관련 API

클라이언트쪽에서는 access token을 보내는 API 요청할 때 Authorization 헤더에 access token을 넣어 보내준다. (refresh token은 쿠키 헤더에)

참고 링크

프론트에서 안전하게 로그인 처리하기
쉽게 알아보는 서버 인증

profile
부족함을 당당히 마주하는 용기

0개의 댓글