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 처리는 <를 < >를 >로 변환해서 브라우저가 그냥 일반 텍스트로 인식하도록 만드는 것임
<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
'개발자 강화 > 프론트엔드' 카테고리의 다른 글
🌟[매일메일] 이벤트 전파(Event Propagation)란? (FE.250106) (0) | 2025.02.14 |
---|---|
[개발, 매일메일] @tanstack/react-query v4/v5 차이...(suspense, error-boundary 처리) (0) | 2025.02.14 |
[공부] BFF API란? (1) | 2025.02.10 |
🌟[공부, 매일메일] Suspense란? (2) | 2025.02.10 |
[매일메일] React의 Concurrent Mode(동시성 모드) (FE.250207) (0) | 2025.02.08 |