나도 멋쟁이사자처럼!

[🦁멋사 보너스 과제] localStorage와 sessionStorage 취약점

hamzgi 2026. 4. 15. 22:00

저번 실습 때 js에 대해서 공부하면서 배웠던 개념이 필요합니다!

 

멋사 실습 과제 웹페이지를 들어가서 우선 F12를 눌러 DevTools에 진입합니다!

웬만하면 HTML,CSS에는 권한이나 로직코드가 들어가지 않으므로 우선 Sources탭에서 JS부터 확인해봤습니다.

그냥 하나씩 훑어 보기에는 너무 긴 코드이기에 Ctrl + F로 원하는 문자열을 찾아보았습니다.

 

우선은 "계정의 역할을 admin으로 변경하면 숨겨진 페이지를 찾을 수 있다"는 힌트를 바탕으로 role과 admin을 검색해봤습니다.

role부터 쳤을때 role에 해당하는 문자열이 여러개 나왔습니다. (19개가 있었습니다.)

그 중에서 뭔가 이 부분이 힌트지 않을까? 싶은 코드가 몇가지 나왔습니다.

여기서 vn에 likelion_client_role이,  or에는 likelion_bonus_gate가 들어간다는 const 선언 코드를 확인했습니다.
그래서 '아 뭔가 저 변수와 role, bonus_gate가 연관이 있겠구나' 하는 생각을 먼저 했습니다.

 

그리고 밑에

sessionStorage.setItem(or, e.role === "admin" ? "admin" : "participant")라는 코드도 무슨의미인가 싶어 찾아본 결과

e.role이 admin이면 admin을 저장, 그렇지 않으면 participant를 저장한다는 내용인 걸 알게 되었습니다.

 

이걸 보고 어? 혹시 그러면 내가 직접 sessionStorage나 localStorage에 .setItem으로 직접 admin이라는 값을 넣으면 admin으로 인식되는거 아닐까? 라는 생각을 하게 되었습니다. 

 

하지만 코드를 조금 더 찾아보자 하고 더 둘러봤습니다. 그러다가 코드 바로 아래에서 아래 이미지에 있는 코드를 발견했습니다.

솔직히 아직 JS 입문자로서 완벽하게 코드를 이해하는데는 어려움이 있었지만 일단 핵심적인 부분이 무엇일까 검색해보며 생각해봤습니다.

 

중요한 포인트만 정리해보자면

const o = 
    n ||                      // 1순위
    t ||                      // 2순위
    (e?.role) ||              // 3순위
    "participant";            // 4순위 (기본값)

변수 "o" 에 우선순위대로 값을 넣는 코드였습니다. 만약 "n"이 "admin"이라면 "o"에 "admin"이 들어가고 그 뒤는 무시한다는 얘기죠.

 

그리고 가장 중요한 부분이 아래 코드인데요

canAccessBonus: o === "admin" || r === "admin" || l === "admin" || r === "bonus" || l === "bonus"

권한 판단을 "o" (사용자의 역할,role), "r" (localStorage의 bonus 값), "l" (sessionStorage의 bonus 값)의 값들로 수행하는 코드인데, 해당 부분에서 취약점이 존재함을 확인할 수 있었습니다.

 

"o", "r", "l" 중에 뭐든 "admin"이나 "bonus"가 있으면 보너스 페이지에 접근을 허용한다는 내용입니다.
근데 저 값들은 전부 서버가 아닌 브라우저의 local,sessionStorage에 저장되므로 사용자가 DevTools의 Console을 통해"admin"이나 "bonus"를 .setItem하게 되면 접근이 가능해지게 됩니다.

 

그래서 결론적으로 확실한 접근을 위해 다음과 같은 코드를 Console에 입력했습니다

localStorage.setItem("likelion_client_role", "admin")
sessionStorage.setItem("likelion_client_role", "admin")

localStorage.setItem("likelion_bonus_gate", "admin")
sessionStorage.setItem("likelion_bonus_gate", "admin")

 

그러자 다음과 같은 화면이 나왔습니다.

성공적으로 admin 페이지에 진입을 성공했습니다. 조금 더 확실하게 확인하기 위해 Application탭에서 local, sessionStorage를 확인해본 결과 아래와 같이

제가 아까 콘솔에서 입력한 값들이 들어있는 것을 확인할 수 있었습니다.

조금 더 자세히 보면 localStorage에는 제가 추가한 admin 값들 이외에도 다른 것들이 있었습니다.

likelion_bonus_entries [{"username":"yamajaki","enteredAt":1776252290804,"rank":1}]
likelion_bonus_gate admin
likelion_client_role admin
likelion_session {"username":"yamajaki","role":"participant"}
likelion_user_state_yamajaki {"isOnline":true,"currentScreen":"bonus","currentStep":0,"currentMission":0,"completedSteps":[]}
likelion_users {"yamajaki":{"username":"yamajaki","password":"cprPrhk1!","role":"participant","solvedMissionIds":[]}}

 

row 1번째 likelion_bonus_entries 에는 유저명과 언제 접근했는지, 순위가 몇위인지 확인할 수 있습니다. (1등 야르~!!!)

 

2,3번째 row에는 제가 직접 넣었던 값이 있습니다.

 

4번째 likelion_session 에는 사용자 정보(세션)이 있는데 잘 보면 role에는 여전히 participant가 들어있습니다. 즉 서버 기준에선 여전히 user(=paricipant)라는 거죠. 제가 프론트만 속였다는 의미입니다.

 

5번째 likelion_user_state_yamajaki 에는 온라인인가, 현재 화면이 어딘가(보너스 화면), 진행 단계와 완료한 단계가 나와있는데 제가 어디까지 진행했는지 기억하는 용도로 보입니다.

 

6번째 likelion_users 에는 전체적인 유저의 DB가 있습니다. 문제는 유저명, 역할, 해결한 문제 ID외에도 비밀번호가 무엇인지도 나와있습니다. 이런 경우를 예상하고 군대에서 사용하던 임시 비밀번호로 계정을 만들길 잘한 것 같습니다. (체계과1!입니다.)

 

이로써 만약 실제 서비스에서 localStorage에 직접 저장하는 방식을 사용하거나, 비밀번호를 그냥 평문으로 저장하면 얼마나 큰일이 발생할지를 깨달았습니다.

 

그리고 갑자기 궁금해져서 만약 제가 순진한척 제 브라우저로 친구한테 회원가입과 로그인을 시키면 그 계정의 비밀번호와 id를 알 수 있을까? 싶어서 테스트를 해봤습니다.

역시 예상대로 test와 test1의 계정의 비밀번호와 유저명이 남아있는 것을 확인할 수 있었고, clear all을 하게되면 해당 계정이 사라지는 것도 확인했습니다.

 

어찌저찌 문제를 풀긴 했지만 여전히 JS코드가 전부 이해된 것은 아니라서 나중에 시간이 나면 임원분들께 더 자세한 설명을 들어야 할 것 같습니다 ㅎㅎ...

 

단순 HTML, CSS, JS 공부 뿐만 아니라, 이런 취약점까지 실제로 공부시켜주는 멋쟁이사자처럼 임원진분들의 노력에 감사하는 마음을 갖게 된 보너스문제였던 것 같습니다!!