보안 등급이란?
CodeScan은 14가지 보안 항목을 종합 평가하여 A~F 등급을 부여합니다. 등급은 감점 방식으로 계산됩니다. 100점에서 출발해 취약점이 발견될 때마다 심각도에 따라 점수가 깎입니다. 예를 들어 SSL 미적용이나 API 키 노출 같은 치명적 문제는 한 건만으로도 20~30점이 빠지고, 서버 버전 노출 같은 정보성 문제는 몇 점 수준입니다.
- A (90점+): 안전, 주요 보안 설정이 잘 되어 있습니다. 자동 스캐너가 잡는 기본 취약점은 대부분 제거된 상태입니다.
- B (70~89): 양호, 일부 개선이 필요합니다. 보통 보안 헤더 몇 개가 빠져 있거나 쿠키 속성이 불완전한 경우입니다.
- C (50~69): 주의, 여러 취약점이 있습니다. 공격자가 자동화 도구로 스캔했을 때 눈에 띄는 표적이 됩니다.
- D (30~49): 위험, 즉시 조치가 필요합니다. 인증 누락이나 민감 파일 노출 같은 실제 침해로 이어지는 문제가 있을 가능성이 높습니다.
- F (30 미만): 심각, 서비스 중단·데이터 유출 위험이 있습니다. 이미 봇에 의해 스캔·악용되고 있을 수 있습니다.
중요한 건 등급 자체가 아니라 등급이 말해주는 공격 표면의 크기입니다. 공격자는 사람이 아니라 봇으로 인터넷 전체를 훑습니다. C~F 등급은 "여기 열려 있다"는 신호를 봇에게 보내는 것과 같습니다. A등급은 이 자동화된 1차 스캔을 통과시키는 최소 방어선입니다.
A등급 받기 위한 체크리스트
아래 6개 항목이 A등급을 좌우하는 핵심입니다. 각 항목마다 왜 위험한지, 직접 확인하는 법, 무엇을 고쳐야 하는지를 순서대로 정리했습니다. 우선순위는 위에서 아래로, 즉 HTTPS와 보안 헤더부터 잡는 것이 점수를 가장 빠르게 올리는 길입니다.
1. HTTPS 필수
HTTP로 서비스하면 로그인 정보, 세션 쿠키, 개인정보가 평문 그대로 네트워크를 지나갑니다. 같은 공용 와이파이에 있는 공격자는 패킷 캡처 도구 하나로 그 내용을 그대로 읽습니다. 이걸 중간자 공격(MITM)이라고 부르며, 카페·공항 같은 개방 네트워크에서 실제로 벌어지는 일입니다.
직접 확인하는 법: 브라우저 주소창에 자물쇠 아이콘이 있는지 봅니다. 자물쇠가 없거나 "주의 요함"이 뜨면 HTTPS가 없는 겁니다. 명령줄로는 다음처럼 확인합니다.
curl -I http://yourdomain.com
# 응답에 "301 Moved Permanently" + Location: https:// 가 있으면
# HTTP 요청이 HTTPS로 강제 리다이렉트되는 정상 상태
조치: Let's Encrypt로 무료 SSL 인증서를 설치하세요. Cloudflare를 DNS 앞단에 두면 인증서 발급·갱신이 자동으로 처리됩니다. 인증서만 깔고 끝내지 말고, HTTP로 들어온 요청을 HTTPS로 강제 리다이렉트하고 아래 HSTS 헤더까지 넣어야 완성입니다. TLS 버전은 1.2 이상만 허용하고 1.0/1.1은 꺼야 합니다.
2. 보안 헤더 설정
보안 헤더는 브라우저에게 "이렇게 방어하라"고 지시하는 규칙입니다. 이게 없으면 XSS(스크립트 삽입)나 클릭재킹 같은 공격이 브라우저 단에서 그대로 통과합니다. 예를 들어 X-Frame-Options가 없으면 공격자가 당신의 사이트를 투명한 iframe으로 자기 페이지에 겹쳐 놓고, 사용자가 "송금" 버튼을 누르도록 유도하는 클릭재킹이 가능합니다.
직접 확인하는 법: 브라우저 개발자도구(F12) → Network 탭 → 페이지 새로고침 → 최상단 문서 요청 클릭 → Response Headers에서 아래 헤더가 있는지 봅니다.
curl -I https://yourdomain.com | grep -iE "strict-transport|content-security|x-frame"
조치: 최소 다음 세 가지를 추가하세요.
# 위험: 아무 헤더도 없는 상태 (기본값)
# 안전:
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Frame-Options: DENY
Content-Security-Policy: default-src 'self'
HSTS는 한번 설정하면 브라우저가 이후 무조건 HTTPS로만 접속하게 만들어 SSL 스트립 공격을 막습니다. CSP는 처음엔 까다로우니 default-src 'self'로 시작해 콘솔에 뜨는 차단 로그를 보며 필요한 출처를 하나씩 허용하는 방식으로 좁혀 나가세요.
3. 불필요한 포트 닫기
열린 포트 하나하나가 공격자에게는 문을 두드릴 수 있는 입구입니다. 특히 인증 없이 노출된 데이터베이스 포트는 즉시 통째로 털립니다. 인증 설정을 안 한 채 외부에 열린 Redis(6379)나 MongoDB(27017)는 공격 봇이 자동으로 찾아내 데이터를 복사하고, 원본을 지운 뒤 "복구하려면 코인을 보내라"는 랜섬 메시지를 남기는 사례가 흔합니다.
직접 확인하는 법: 외부 시점에서 어떤 포트가 열려 있는지 스캔합니다.
# 내 서버에 대해서만 실행 (타인 서버 스캔은 불법)
nmap -Pn yourdomain.com
# 열려 있으면 안 되는 포트 예:
# 22/tcp open ssh
# 3306/tcp open mysql ← 위험
# 6379/tcp open redis ← 매우 위험
조치: 80(HTTP), 443(HTTPS) 외 포트는 방화벽으로 차단하세요. 22(SSH), 3306(MySQL), 5432(PostgreSQL), 6379(Redis), 27017(MongoDB)는 0.0.0.0/0(전체 개방)이 아니라 특정 관리 IP만 허용하도록 클라우드 보안 그룹(AWS Security Group 등)을 설정합니다. 데이터베이스는 애초에 내부 네트워크에서만 접근하도록 두는 것이 원칙입니다.
4. 쿠키 보안 속성
세션 쿠키는 사실상 로그인 상태를 담은 열쇠입니다. 이 쿠키를 훔치면 비밀번호 없이도 그 사용자로 로그인됩니다. HttpOnly가 없으면 XSS로 삽입된 자바스크립트가 document.cookie로 쿠키를 읽어 공격자 서버로 전송할 수 있고, Secure가 없으면 HTTP 구간에서 평문으로 새어 나갑니다.
직접 확인하는 법: 개발자도구(F12) → Application(또는 저장소) 탭 → Cookies에서 각 쿠키의 Secure/HttpOnly/SameSite 열을 확인합니다.
# 위험: 속성 없는 쿠키
Set-Cookie: session=abc123
# 안전: 세 속성 모두 지정
Set-Cookie: session=abc123; Secure; HttpOnly; SameSite=Strict
조치: 인증·세션에 쓰는 모든 쿠키에 Secure, HttpOnly, SameSite=Strict(또는 크로스사이트 로그인이 필요하면 Lax)를 설정하세요. 대부분의 프레임워크는 설정 한 줄로 일괄 적용됩니다. 예: Django SESSION_COOKIE_SECURE = True, SESSION_COOKIE_HTTPONLY = True.
5. API 키 서버사이드 관리
프론트엔드 자바스크립트에 넣은 키는 비밀이 아닙니다. 브라우저로 전송되는 순간 누구나 개발자도구 Sources 탭이나 Network 응답에서 그대로 볼 수 있습니다. 결제·이메일·클라우드 API 키가 노출되면 공격자가 당신 명의로 요금을 발생시키거나 데이터에 접근합니다. 실제로 공개 저장소나 배포된 번들에 올라간 키는 봇이 몇 분 안에 자동 수집해 악용합니다.
직접 확인하는 법: 개발자도구 → Sources 탭에서 Ctrl+Shift+F로 api_key, secret, sk_, AKIA(AWS 키 접두어) 등을 전체 검색합니다. 하나라도 걸리면 노출된 상태입니다.
// 위험: 클라이언트 코드에 키가 박혀 있음
const key = "sk_live_51H...";
fetch("https://api.provider.com", { headers: { Authorization: key } });
// 안전: 키는 서버에만 두고, 브라우저는 자기 서버로만 요청
fetch("/api/proxy/charge", { method: "POST" });
// 실제 외부 API 호출은 서버가 환경변수의 키로 대신 수행
조치: 모든 민감 키는 환경변수(.env)에 두고 서버에서만 사용하세요. 브라우저는 당신의 백엔드로만 요청하고, 백엔드가 프록시 역할로 외부 API를 호출합니다. 이미 노출된 키가 있다면 즉시 재발급(rotate)하는 것이 먼저입니다. 코드에서 지우는 것만으로는 이미 유출된 키를 무효화할 수 없습니다.
6. 에러 페이지 커스텀
디버그 모드가 켜진 채 배포하면, 에러가 날 때마다 서버 파일 경로, 프레임워크 버전, DB 쿼리, 심지어 환경변수까지 화면에 그대로 찍힙니다. 공격자는 일부러 이상한 값을 입력해 에러를 유발하고, 그 상세 페이지에서 서버 구조를 파악한 뒤 다음 공격을 설계합니다. 정보 노출은 단독으로는 낮은 심각도지만, 공격의 정찰 단계에 결정적인 힌트를 줍니다.
직접 확인하는 법: 존재하지 않는 경로나 잘못된 파라미터를 일부러 넣어봅니다. 스택 트레이스나 서버 경로(/home/ubuntu/app/...)가 보이면 디버그 모드가 켜진 상태입니다.
# 위험 (Django)
DEBUG = True
# 안전
DEBUG = False
ALLOWED_HOSTS = ["yourdomain.com"]
# 404.html, 500.html 커스텀 페이지 배치
조치: 프로덕션에서는 디버그·개발 모드를 반드시 끄고(Django DEBUG=False, Flask debug=False, Node NODE_ENV=production), 사용자에게는 내부 정보가 없는 커스텀 404·500 페이지만 보여주세요. 상세 에러는 사용자 화면이 아니라 서버 로그에만 남겨야 합니다.
A등급은 시작점이지 끝이 아니다
위 6가지를 잡으면 대부분의 사이트가 C~D에서 A로 올라옵니다. 하지만 A등급은 자동 스캐너가 잡는 설정 레벨 취약점을 제거한 상태일 뿐, 비즈니스 로직 취약점(권한 우회, 결제 조작 등)까지 안전하다는 뜻은 아닙니다. 그리고 보안 등급은 한 번 A를 받으면 유지되는 게 아닙니다. SSL 인증서 만료, 새로 추가한 API의 인증 누락, 의존성 패키지의 신규 취약점, 배포할 때마다 등급은 다시 흔들립니다. SENTRIX가 실제 진단 현장에서 확인하는 패턴도 동일합니다. 처음 A를 받은 서비스가 몇 번의 기능 추가 뒤 조용히 C로 내려가 있는 경우가 대부분입니다. 그래서 배포 직후 1회 점검이 아니라 정기 재점검이 필요합니다.
지금 내 등급 확인하기
내 사이트가 지금 몇 등급인지, 어떤 항목에서 점수가 깎이는지 30초 만에 확인할 수 있습니다. URL 하나만 입력하면 위 14가지 항목을 자동으로 점검하고, 항목별 조치 방법까지 알려줍니다.