본문 바로가기

개발자 강화/프론트엔드

[매일메일] 민감한 데이터는 어디에 저장해야 할까? (FE.250211)

  localStroage sessionStorage
데이터 지속성 영구적 저장
브라우저 닫거나, 장치 재부팅해도 유지
현재 브라우저 세션 동안만 유지
브라우저 탭 닫으면 데이터 삭제
범위 동일한 도메인 내의 모든 탭에서 데이터 공유 동일한 도메인 내에서 탭 간 데이터 공유 안함
예시 테마 설정(다크모드)
장바구니 데이터
로그인 후 인증 데이터 일시 저장
특정 탭에서만 사용할 데이터 관리
목적 장기 데이터 저장 탭 단위 데이터 관리
일시적 데이터 저장
보안 영구적으로 저장되므로 민감한 데이터 저장 주의 세션 종료 시 데이터가 자동으로 삭제 되지만
보안적으로 완전히 데이터를 방어할 수는 없음
  Cookie
데이터 지속성 만료 시간 설정 가능
범위 동일한 도메인 내 모든 탭에서 공유
예시 세션 유지(로그인 상태)
추적 정보 저장(광고 추적, 사용자 방문 기록)
이 팝업 7일간 다시보지 않기(만료 시간 설정 가능)
목적 브라우저-서버 사이 데이터 교환 및 세션 유지
보안 HTTP Only 쿠키는 자바스크립트에서 접근 불가

민감한 데이터는 어디에 저장해야 하는가!

HTTP-Only 쿠키 🍪

1. HttpOnly 속성이 설정된 쿠키는 자바스크립트에서 접근 불가능함. (서버에게 전송되기만 함)

- document.cookie로 클라이언트에서 접근할 수 없음. 쿠키 탈취 공격을 방어할 수 있음.

- XSS(Cross-Site Scripting) 공격을 방지하는 중요한 보안 기능

 


여기서 잠깐! XSS가 뭡니까

XSS 공격

공격자가 웹 사이트에 악성 클라이언트 측 코드를 주입할 수 있는 보안 공격

 

Q. 언제 발생하는가?

1) 데이터가 신뢰할 수 없는 소스를 통해 웹 앱에 입력됨

2) 악성 콘텐츠에 대한 유효성 검사 없이 동적 콘텐츠를 웹 사용자에게 전송함

 

Q. 공격의 유형

1) 개인 데이터(쿠키, 세션 정보)를 공격자에게 보냄

2) 공격자가 제어하는 웹 페이지로 피해자를 리다이렉션함

 

Q. 공격의 종류

1) 저장된 XSS 공격

악성 스크립트를 직접 서버에 저장하고, 피해자가 해당 데이터를 조회할 때 발생

(기존 멀쩡한 웹사이트에 댓글, 게시판, 프로필 정보 등에 악성 스크립트를 삽입함)

(텍스트 인풋 창에 <script/> 태그로 감싸서 해커가 의도한 동작을 넣는 방식)

 

해당 페이지를 방문한 사용자 브라우저에서 그 스크립트를 자동으로 실행함

 

2) 반사된 XSS 공격

사용자를 속여 악성 링크를 클릭하게 만듦

(요즘 많이 뿌리는 문자 낚시 링크같은거 아닐까)

 

https://xxx.com/search?query=<script>alert('xss')</script> 

URL에 포함된 악성코드가 사용자 브라우저에서 실행됨

세션에 저장된 쿠키를 뽑아서 특정 서버로 보내는 등의 스크립트가 포함되어 있을 수 있음

 

내가 보낸 요청이 그대로 그대로 돌아와서 나를 공격함(그래서 반사된 스크립트)

 

3) DOM 기반 XSS (DOM-Based XSS)

서버에서 직접 응답 주는 게 아니라, 사용자 브라우저에서 실행되는 JavaScript가 조작됨

웹사이트의 DOM이 변경되어 악성 코드가 실행됨

 

사용자가 검색창에 <script>alert('XSS')</script>를 입력하면 HTML에서 입력값 검증 없이 그대로 실행됨

원래 개발자가 의도한 동작이 아닌데 실행될 수 있음

사용자가 직접 입력하지 않아도, 위 검색어를 쿼리문으로 하는 링크로 사용자를 리다이렉션 시킬 수도 있음

 

어라? 그런데 React input 창에는 HTML 태그 넣어도 아무 문제 없지 않아요?

React는 JSX 내부에서 HTML을 자동으로 Escape 처리함

(Escape 처리는 <를 &lt; >를 &gt;로 변환해서 브라우저가 그냥 일반 텍스트로 인식하도록 만드는 것임

 

<script> 태그가 HTML로 해석되지 않고, 단순한 문자열로 취급됨!

브라우저에서 실행되지 않으므로 XSS 공격 불가능

 

dangerouslySetInnerHTML 속성을 쓰면 React에서도 HTML을 직접 삽입할 수 있음

그런데 태그 이름에서도 보이듯이 아주 좋은 공격 포인트가 돼서 XSS 취약점이 그대로 적용됨

(태그 이름 진짜 쓰면 죽이겠다는 개발자들의 경고가 보이지 않나요...?

근데 저 인턴 때 저 태그 쓰는 레거시 코드 본 적 있음... 님들은 쓰지 마세용)


 

2. HTTP-Only 쿠키는 클라이언트가 아닌 서버에서만 설정 가능

- 일반적으로 서버에서 HTTP 응답의 Set-Cookie 헤더로 설정함

- 클라이언트 측에서 직접 docuemnt.cookie로 설정할 수 없음

 

3. 자동으로 요청과 함께 전송됨

- 브라우저는 쿠키를 자동으로 요청 헤더(Cookie)에 포함해 서버로 전송

- 서버는 응답과 함께 Set-Cookie 헤더를 전송할 수 있음 (key-value 형태)

 

<구현>

- 서버에서 HTTP-Only 쿠키를 설정함

app.post('/login', (req, res) => {
  const token = generateToken(req.body.user);
  res.cookie('authToken', token, {
    httpOnly: true,  // 자바스크립트에서 접근 불가
    secure: true,    // HTTPS에서만 전송됨 (보안 강화)
    sameSite: 'Strict', // CSRF 방지
    maxAge: 3600000  // 쿠키 유효 시간 (1시간)
  });
  res.send('로그인 성공');
});

- 클라이언트 요청 시 FetchAPI에서 credentials: 'include' 옵션을 추가해야 함

fetch('https://example.com/protected-route', {
  method: 'GET',
  credentials: 'include' // HTTP-Only 쿠키 포함
})
.then(response => response.json())
.then(data => console.log(data));

그럼 HTTP Only만 쓰면 만사형통?

아직 CSRF 남았다...

CSRF는 이전에 CORS 다룰 때 등장했던 개념 (CORS란? https://developer-dreamer.tistory.com/124)

 

쿠키 자체를 탈취하는 것이 아니라 피해자의 세션을 이용해 악성 요청을 서버에 보내는 방법이 가능함

 

1. 사용자가 정상적인 로그인을 함(sessionId가 HTTP-Only 쿠키에 저장됨

2. 공격자가 악성 사이트를 피해자에게 클릭하도록 유도함(피싱, 광고)

3. 피해자가 로그인 상태에서 악성 사이트를 방문함

  - 악성 사이트가 피해자 대신 자동으로 요청을 전송함(계좌 이체 요청)

  - 브라우저는 자동으로 HTTP-Only 쿠키를 포함해 요청을 보냄

4. 서버는 정상적인 요청으로 착각하고 처리함 (돈이 빠져나가요...~)

 

CSRF는 직접 쿠키 탈취를 하는 게 아니라, 브라우저의 자동 전송을 악용함

서버는 sessionId가 포함된 요청이 오면 정상적인 사용자 요청이라고 신뢰하는 점을 악용함

 

CSRF 공격 방어하기

res.cookie("csrfToken", "secureRandomString", {
  httpOnly: true, // XSS 방지
  secure: true, // HTTPS에서만 전송
  sameSite: "Strict" // CSRF 일부 방지 가능
});

 

 

서버가 임의의 난수를 생성해서 서버 측에 저장하고, 브라우저에도 전달함

브라우저는 중요한 요청을 보낼 때 CSRF 토큰을 같이 보내서 검증을 함

 

 

CSRF 공격자가 시도하는 브라우저 자동 전송에는 CSRF 토큰이 포함되지 않아서 방어가 가능하다고 함

Spring에서는 기본적으로 CSRF 토큰을 담아서 요청을 하지 않으면 403 포비든 에러를 뱉는다네요~(와 신기하다~)

 

(근데 결국 브라우저에도 이 난수를 세션 스토리지나 로컬 스토리지에 저장할텐데,

이건 XSS로 자바스크립트에 접근해서 털 수 있지 않아요?)

(지금 계속 자료 찾아보는데 개큰 물음표가 머릿속에 떠나질 않음... 해결되면 추가할게요)

 

SameSite 속성을 활용해 다른 사이트에서 요청할 때 쿠키가 자동으로 전송되지 않도록 막을 수 있음

 

app.post("/transfer", (req, res) => {
  if (req.headers.origin !== "https://bank.com") {
    return res.status(403).send("CSRF 공격 차단됨!");
  }
  // 정상 요청 처리...
});

 

서버에서 요청의 Origin 또는 Refere 헤더를 검사해서 정상적인 도메인에서 온 요청인지 확인함

 

아니 그 탈취겠냐고요;

 


개발 완전 처음 하던 초기에 헷갈려했던 개념이었음(그냥 옛날 생각 난다는 뜻)

 

출처

[1] localStorage와 sessionStorage의 차이점에 대해서 설명해주세요. 85번. https://maeil-mail.kr 

[2] MDN docs. HTTP 쿠키 https://developer.mozilla.org/ko/docs/Web/HTTP/Cookies 

[3] MDN docs. 웹 보안>공격 유형 https://developer.mozilla.org/ko/docs/Web/Security/Types_of_attacks#cross-site_scripting_xss 

[4] GPT에게 HTTP Only 쿠키에 대해 묻다

[5] LocalStorage/SessionStorage(vs쿠키와 비교) https://inpa.tistory.com/entry/JS-%F0%9F%93%9A-localStorage-sessionStorage 

[6] Spring JWT 탈취 대비 - XSS, CSRF, RTR https://m42-orion.tistory.com/150 

[7] 로컬 스토리지. 세션 스토리지 (제로초) https://www.zerocho.com/category/HTML&DOM/post/5918515b1ed39f00182d3048