Problem

간단한 JS 조작으로 서비스 방향성 훼손 가능.

서비스 방향성

‘CS 백지노트’는 기술 면접을 효과적으로 준비하기 위해 기획된 서비스입니다. 실제 면접 환경과 유사한 경험을 제공하기 위해 다음과 같은 핵심 로직을 설계했습니다.

  • 시간 제한: 실제 면접처럼 긴장감 있게 즉각적인 답변을 도출해야 합니다.
  • 랜덤 질문: 예측 불가능한 질문에 대응하는 능력을 기릅니다.
  • 직접 작성: 단순 복사가 아닌, 스스로의 지식을 백지에 인출하며 학습합니다.

현재 상태

하지만 위와 같은 방향성은 사용자가 브라우저 설정이나 개발자 도구를 통해 스크립트를 조작할 경우 쉽게 훼손될 우려가 있었습니다. 예를 들어, disable javascript 설정을 통해 붙여넣기 감지 로직과 타이머를 간단히 무력화하거나, 개발자 도구에서 변수 값을 수정하여 시간을 임의로 늘리는 것이 가능했습니다. 이처럼 클라이언트 단에 의존하는 로직은 사용자의 의도에 따라 언제든 무력화될 수 있기에, 이를 보완할 수 있는 근본적인 방어 로직이 필요하다고 판단했습니다.


Analysis

“클라이언트 사이드 제어의 한계 인지”

핵심 원인

  • JS의 취약성
    • 클라이언트 제어권의 한계 (타이머 조작): 프론트엔드 코드는 사용자의 로컬 환경에서 실행되므로, 사용자가 실시간으로 변수 값을 수정하거나 함수의 실행을 강제로 중단하는 것을 원천적으로 차단하기 어렵습니다.
    • 이벤트 리스너의 한계 (붙여넣기 방지): paste 감지와 같은 JS 기반 방어책은 사용자가 JS 실행 자체를 비활성화할 경우, 해당 리스너가 등록되지 않아 무용지물이 됩니다.

제약 사항

  • 데이터 신뢰성 확보: 사용자의 자율적인 학습 환경은 존중하되, 최종 제출되는 데이터만큼은 정해진 규칙(시간 제한, 직접 작성)을 준수했다는 신뢰도가 보장되어야 합니다.
  • 개발 효율성: 3일이라는 짧은 구현 기간 내에 완료해야 하는 프로젝트였기에, 복잡한 보안 라이브러리를 도입하기보다는 기존 기술 스택을 활용하여 빠르게 적용 가능한 로직이 절실했습니다.

³ Solution

프론트엔드에서는 귀찮게 만들고, 백엔드에서 검증한다.

사용자의 우회 경로를 최대한 차단하고, 최종적인 무결성은 서버에서 확정 짓는 다중 방어 전략을 세웠습니다.

전략 1. 클라이언트 사이드: 귀찮게 방해하기

  1. JavaScript 필수 환경 조성
    • <noscript> 태그를 사용하여 JS 비활성화 시 서비스 이용 불가 경고를 노출합니다.
    • 답변 제출 로직을 일반적인 HTML form 전송이 아닌 Fetch API(Async) 방식으로 구현하여, JS가 꺼진 상태에서는 아예 답변 제출이 불가능하도록 설계했습니다.
  2. 붙여넣기 방지
    • paste 이벤트를 인터셉트하여 외부 텍스트의 유입을 막고, 사용자가 직접 내용을 입력하도록 유도했습니다.

전략 2. 서버 사이드: JWT를 활용한 타이머 무결성 검증

서버 사이드 검증을 위해 JWT(JSON Web Token) 를 선택한 이유는 다음과 같습니다.

  • 기존 로그인 로직에서 이미 JWT를 사용 중이었기에 추가 인프라 없이 즉시 적용이 가능했습니다.
  • Payload에 민감한 정보가 아닌 시작 시간 정보만 담으므로 토큰의 길이가 길지 않고, 서버에 별도의 세션 저장소를 유지할 필요가 없어 리소스 소모가 적습니다
  • 토큰의 특성상 발행 후 강제 만료는 어렵지만, 3분 내외의 단기 세션인 문제 풀이 환경에서는 큰 제약이 되지 않는다고 판단했습니다.

검증 프로세스는 다음과 같습니다.

  1. 질문별 단기 토큰 발행: 사용자에게 랜덤 질문이 추출되어 보내질 때, 해당 문제의 ID와 시작 시간을 담은 JWT를 발급하여 함께 전달합니다.
  2. 최종 검증:
    • 답변 제출 시, 클라이언트는 1번의 토큰을 함께 전달하고, 시작시간과 현재 시간을 비교합니다.
    • 이때 네트워크 지연 등을 고려한 오차 범위를 적용하여 시간 엄수 여부를 판정합니다.

Result

변경 후

  • 데이터 신회도 상승: 클라이언트에서 타이머 멈춤 등을 시도하더라도, 서버의 최종 검증 단계에서 결과값이 거절되므로 학습 데이터의 무결성을 확보했습니다.
  • 실전 학습 환경 조성: 사용자가 기술 면접과 유사한 긴장감을 유지하며 정직하게 학습할 수 있는 환경을 제공하게 되었습니다.

회고

보안은 클라이언트와 서버가 상호 검증하는 구조일 때 가장 견고해진다는 점을 배웠습니다. 특히 공격자 관점에서 직접 JS 조작을 시도해 보았던 경험이 실질적인 방어 로직을 설계하는 데 큰 밑거름이 되었습니다.

Javascript JWT Session