infra
Platform

모듈 맵

[Infra Ops] 계정 권한 관리, 보안 헤더, TLS 강화 실무

0 / 52 완료

펼치기
0 / 52 완료0%

Infra-ops · 47 / 52

[Infra Ops] 계정 권한 관리, 보안 헤더, TLS 강화 실무

관리자 경로 제한, CORS/Cookie 보안 설정, 보안 헤더, TLS hardening까지 — 서버를 안전하게 유지하는 인프라 보안 운영 실무

🚨INCIDENT ALERT
HIGH

서비스를 배포하고 보안팀으로부터 메일을 받습니다. "취약점 스캔 결과 — TLS 1.0 활성화, 보안 헤더 미설정, 관리자 페이지 외부 접근 허용." 이게 얼마나 위험한 것인지, 어떻게 고쳐야 하는지 막막합니다.

보안은 한 번에 완벽하게 만드는 것이 아닙니다. 실무에서는 "지금 당장 꼭 해야 하는 것"부터 순서대로 적용합니다. 이 모듈은 인프라 엔지니어가 직접 Nginx 설정으로 적용할 수 있는 보안 강화 항목들을 다룹니다.

이번 챕터에서 배울 것
  • 1Nginx에서 관리자 경로를 IP 기반으로 제한하고 숨김 파일 접근을 차단할 수 있다
  • 2보안 헤더(X-Frame-Options, HSTS, CSP 등) 5개를 Nginx에 설정할 수 있다
  • 3CORS 설정에서 와일드카드 대신 특정 출처만 허용하는 방법을 적용할 수 있다
  • 4쿠키에 Secure, HttpOnly, SameSite 속성을 설정하는 방법을 설명할 수 있다
  • 5TLS 1.0/1.1을 비활성화하고 강화된 cipher suite를 적용할 수 있다

계정 권한 관리

💡개념

root 직접 접속 차단과 불필요 계정 관리

보안 감사 결과서를 받았습니다. 첫 번째 항목이 "root 직접 로그인 허용"입니다. 서버를 처음 세팅했을 때 편의를 위해 놔뒀던 설정인데, 공격자가 SSH 브루트포스를 시도할 때 root 계정이 존재하면 하나의 계정만 크래킹하면 시스템 전체를 장악할 수 있습니다. 감사에서 지적받기 전에, 서버 세팅 시 기본적으로 처리해야 하는 계정 보안 항목들이 있습니다.

서버 보안의 기본은 공격 표면을 줄이는 것입니다. 필요 없는 계정을 없애고, root 직접 접속을 막고, 필요한 권한만 부여하는 것이 출발점입니다.

root 직접 접속 차단과 불필요 계정 관리

로컬 터미널
# root 직접 SSH 로그인 차단 (/etc/ssh/sshd_config)
grep "PermitRootLogin" /etc/ssh/sshd_config
# 변경: PermitRootLogin no
sudo sed -i 's/#PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
sudo systemctl reload sshd

# 활성 계정 목록 확인
awk -F: '$3 >= 1000 {print $1, $3, $7}' /etc/passwd
# UID 1000 이상 = 일반 사용자 계정

# 불필요 계정 잠금 (삭제보다 잠금 권장 — 파일 소유자 관계 유지)
sudo usermod -L testuser        # 계정 잠금
sudo passwd -S testuser         # 상태 확인 (L = Locked)

# 로그인 시도 실패 기록 확인
sudo last -F | grep "failed"
sudo lastb | head -20           # 로그인 실패 기록

관리자 경로 제한

💡개념

Nginx에서 IP 기반 접근 제어

관리자 경로(/admin, /management, /actuator 등)를 인터넷에 공개하면 브루트포스 공격의 대상이 됩니다. 내부 IP에서만 접근하도록 제한하는 것이 기본 보안 조치입니다.

Nginx
# /etc/nginx/conf.d/app.conf

server {
    listen 80;
    server_name example.com;

    # 관리자 경로 — 내부 IP만 허용
    location /admin {
        allow 10.0.0.0/8;      # 내부 사설 IP 대역
        allow 192.168.0.0/16;  # 사무실 IP 대역
        deny all;              # 나머지 전부 차단
    }

    # Spring Boot Actuator — 내부만 허용 (민감 정보 노출 방지)
    location /actuator {
        allow 127.0.0.1;
        allow 10.0.0.0/8;
        deny all;
    }

    # 숨김 파일/디렉터리 접근 차단 (.git, .env 등)
    location ~ /\. {
        deny all;
        return 404;
    }

    # 특정 파일 확장자 차단
    location ~* \.(sql|bak|log|conf|env)$ {
        deny all;
        return 404;
    }

    # 일반 요청
    location / {
        proxy_pass http://localhost:8080;
    }
}

deny all만 쓰면 403을 반환해 경로 존재 여부가 노출됩니다. return 404를 함께 쓰면 경로가 아예 없는 것처럼 보입니다.

보안 헤더 설정

💡개념

응답 헤더로 브라우저 보안 정책 강제하기

보안 점검 결과 "X-Frame-Options 헤더 미설정"이 취약점으로 나왔습니다. 개발팀에 전달했더니 "그게 뭔데요?"라는 답이 왔습니다. 이 헤더들은 코드 레벨 취약점이 아니라 브라우저에게 보내는 정책 지시입니다. 설정하지 않으면 클릭재킹, XSS, MIME 스니핑 같은 공격에 그대로 노출됩니다. Nginx 설정에 몇 줄만 추가하면 애플리케이션 코드를 건드리지 않고도 이 취약점들을 한번에 처리할 수 있습니다.

보안 헤더는 서버가 브라우저에게 "이 사이트에서는 이런 보안 정책을 따르라"고 지시하는 방법입니다. 코드 변경 없이 Nginx 설정만으로 주요 클라이언트 사이드 공격을 방어할 수 있습니다.

Nginx
# /etc/nginx/conf.d/security-headers.conf
# 또는 server 블록 내부에 추가

# Clickjacking 방지 — iframe에서 이 페이지를 로드 차단
add_header X-Frame-Options "SAMEORIGIN" always;

# MIME 스니핑 방지 — 브라우저가 Content-Type을 변경하지 못하게 함
add_header X-Content-Type-Options "nosniff" always;

# 구형 브라우저용 XSS 필터 (최신 브라우저는 CSP로 대체)
add_header X-XSS-Protection "1; mode=block" always;

# HSTS — HTTPS 연결 강제 (1년)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

# Content Security Policy — 리소스 출처 제한
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'" always;

# Referrer Policy — 외부 링크 클릭 시 Referer 헤더 제어
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

# Permissions Policy — 브라우저 기능 접근 제한 (카메라, 마이크 등)
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

always 키워드를 붙여야 4xx, 5xx 에러 응답에도 헤더가 포함됩니다.

헤더별 목적 요약:

헤더방어 대상
X-Frame-OptionsClickjacking (악성 iframe으로 클릭 유도)
X-Content-Type-OptionsMIME 스니핑 기반 공격
Strict-Transport-SecuritySSL Stripping (HTTP 다운그레이드)
Content-Security-PolicyXSS (인라인 스크립트 실행)
Referrer-Policy민감한 URL 정보 외부 노출
로컬 또는 서버
# 보안 헤더 적용 확인
curl -I https://example.com \
  | grep -E "X-Frame|X-Content|Strict|Content-Security|Referrer"

서버 보안 점검 체크리스트 — 최소 보안 기준

CORS 설정

💡개념

안전한 CORS 정책 구성

CORS(Cross-Origin Resource Sharing)는 API를 어떤 도메인에서 호출할 수 있는지를 제어합니다. 개발 편의를 위해 *로 열어두고 운영에 그대로 배포하는 경우가 흔합니다.

Nginx
# 특정 출처만 허용하는 CORS 설정
map $http_origin $cors_origin {
    default "";
    "https://www.example.com"     $http_origin;
    "https://app.example.com"     $http_origin;
    "https://admin.example.com"   $http_origin;
}

server {
    location /api/ {
        if ($cors_origin) {
            add_header "Access-Control-Allow-Origin" $cors_origin always;
            add_header "Access-Control-Allow-Methods" "GET, POST, PUT, DELETE, OPTIONS" always;
            add_header "Access-Control-Allow-Headers" "Authorization, Content-Type" always;
            add_header "Access-Control-Allow-Credentials" "true" always;
        }

        # Preflight 요청 처리
        if ($request_method = "OPTIONS") {
            add_header "Access-Control-Max-Age" 1728000;
            add_header "Content-Length" 0;
            return 204;
        }

        proxy_pass http://localhost:8080;
    }
}

map 지시어로 허용 출처 목록을 관리하면, 새 도메인 추가가 목록에 한 줄 추가로 끝납니다.

Cookie 보안 설정

💡개념

세션 쿠키 보안 강화

쿠키에 보안 속성을 설정하지 않으면 세션 하이재킹, XSS를 통한 쿠키 탈취 위험이 있습니다. 세 가지 속성을 반드시 설정해야 합니다.

Nginx
# Nginx에서 Proxy를 통해 쿠키에 보안 속성 추가
# (애플리케이션이 Set-Cookie를 발급할 때 속성이 없으면 Nginx에서 추가)
proxy_cookie_flags ~ secure httponly samesite=strict;

애플리케이션 레벨(Spring Boot 예시):

YAML
# application.yml
server:
  servlet:
    session:
      cookie:
        secure: true       # HTTPS에서만 전송
        http-only: true    # JavaScript에서 접근 불가 (XSS 방어)
        same-site: strict  # 외부 사이트 요청에 쿠키 미포함 (CSRF 방어)

쿠키 보안 속성 의미:

속성방어 대상효과
Secure평문 전송HTTP 요청에서 쿠키 미전송
HttpOnlyXSS 탈취JavaScript document.cookie 접근 차단
SameSite=StrictCSRF외부 사이트에서 발생한 요청에 쿠키 미포함

TLS Hardening

💡개념

TLS 버전과 암호화 스위트 강화

TLS 설정이 취약하면 구버전 프로토콜과 약한 암호화 알고리즘을 이용한 공격에 노출됩니다. 현재 권장 설정은 TLS 1.2 + 1.3만 허용하고 구버전과 약한 cipher를 제거하는 것입니다.

Nginx
# /etc/nginx/conf.d/ssl.conf (또는 server 블록 내)

# TLS 버전 — 1.2와 1.3만 허용 (1.0, 1.1 제거)
ssl_protocols TLSv1.2 TLSv1.3;

# 강화된 Cipher Suite (ECDHE 기반, 순방향 암호화)
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';

# 서버가 cipher 선택권을 가짐 (클라이언트가 약한 것 선택하지 못하도록)
ssl_prefer_server_ciphers on;

# ECDH 파라미터 최적화
ssl_ecdh_curve secp384r1;

# OCSP Stapling (인증서 유효성 검증 빠르게)
ssl_stapling on;
ssl_stapling_verify on;

# DH 파라미터 (Diffie-Hellman 키 교환)
ssl_dhparam /etc/nginx/ssl/dhparam.pem;
로컬 터미널
# DH 파라미터 생성 (최초 한 번만)
openssl dhparam -out /etc/nginx/ssl/dhparam.pem 2048

# 설정 적용 후 TLS 버전 확인
openssl s_client -connect example.com:443 -tls1   2>&1 | grep "handshake failure"
# TLS 1.0이 비활성화됐으면 handshake failure 출력됨

openssl s_client -connect example.com:443 -tls1_2 2>&1 | grep "Cipher"
# TLS 1.2는 정상 접속됨

실습

1현재 보안 헤더 설정 상태 확인

운영 서버 또는 테스트 서버에서 보안 헤더 적용 여부를 확인합니다. 헤더가 없으면 출력이 비어있습니다. 적용 후 다시 실행해 각 헤더가 응답에 포함됐는지 확인합니다.

curl -s -I https://example.com | grep -E 'X-Frame-Options|Strict-Transport|Content-Security|X-Content-Type'
🔍실행 후 확인할 것
  • Strict-Transport-Security 헤더가 있고 max-age 값이 충분히 긴가 (31536000 이상 권장)
  • X-Frame-Options가 SAMEORIGIN 또는 DENY로 설정됐는가
  • X-Content-Type-Options: nosniff 가 포함됐는가
  • Content-Security-Policy 헤더가 있는가 (default-src 'self' 이상)
  • Server 헤더에 Nginx 버전이 노출되지 않는가 (server_tokens off 설정 확인)
2TLS 버전 및 인증서 확인

OpenSSL 클라이언트로 실제 TLS 연결을 맺어 사용 중인 프로토콜 버전과 Cipher Suite를 확인합니다. TLSv1.3 또는 TLSv1.2가 출력되어야 정상이며, TLSv1이 출력되면 구버전이 활성화된 것입니다.

openssl s_client -connect example.com:443 -showcerts 2>/dev/null | grep -E 'Protocol|Cipher|subject'
🔍실행 후 확인할 것
  • Protocol 버전이 TLSv1.2 또는 TLSv1.3인가
  • Cipher Suite에 ECDHE가 포함됐는가 (순방향 암호화 지원)
  • RC4, DES, 3DES 같은 취약한 cipher가 없는가
  • openssl s_client -tls1 (TLS 1.0)로 접속 시도 시 handshake failure가 나는가

트러블슈팅

원인: includeSubDomains와 긴 max-age를 한 번에 설정하면, 브라우저 캐시에 저장된 HSTS 정책 때문에 HTTP로 접속을 시도하는 모든 서브도메인이 즉시 차단됩니다. HSTS는 한번 브라우저에 저장되면 만료까지 수정이 어렵습니다.

로컬 또는 서버
# 현재 HSTS 설정 확인
curl -I https://example.com | grep Strict

# 안전한 HSTS 적용 순서:
# 1단계: 짧은 max-age로 테스트 시작 (300초 = 5분)
add_header Strict-Transport-Security "max-age=300" always;

# 2단계: 모든 서브도메인이 HTTPS로 전환됐는지 확인
# 내부 서비스 포함 전수 점검

# 3단계: 문제 없으면 max-age 늘리기 (86400 = 1일)
add_header Strict-Transport-Security "max-age=86400" always;

# 4단계: 최종 적용 (31536000 = 1년, includeSubDomains 추가)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

# 브라우저에서 HSTS 캐시 삭제 방법 (Chrome):
# chrome://net-internals/#hsts → Delete domain security policies

원인: CSP의 default-src 'self' 또는 script-src 'self' 정책은 인라인 스크립트(<script> 태그 내 코드)와 eval()을 기본으로 차단합니다.

로컬 터미널
# 브라우저 개발자 도구에서 CSP 위반 확인
# Console 탭에서:
# "Refused to execute inline script because it violates the following Content Security Policy directive: 'script-src 'self''"

# 해결 방법 1: unsafe-inline 일시 허용 (보안 수준 낮음, 임시 방편)
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'";

# 해결 방법 2: nonce 방식 (권장)
# Nginx에서 매 요청마다 랜덤 nonce 생성 (lua-nginx-module 필요)
# 또는 애플리케이션에서 nonce를 생성해 헤더와 스크립트 태그에 동일하게 삽입

# 해결 방법 3: 인라인 스크립트를 외부 파일로 분리 (가장 안전)
# <script>...</script> → <script src="/js/app.js"></script>

# CSP Report-Only 모드로 먼저 테스트 (차단 없이 위반 로그만)
add_header Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self'" always;
# 위반 사항이 콘솔에만 기록되고 실제로 차단하지 않음 — 프로덕션 영향 없이 테스트 가능
💼
실무 맥락
현업 패턴

실제 업무에서 이 지식이 쓰이는 상황:

보안팀의 취약점 스캔 결과를 받았을 때, 또는 PCI-DSS/ISO27001 감사를 준비할 때 이 설정들이 체크리스트에 들어갑니다. 코드 변경 없이 Nginx 설정 몇 줄로 보안 점수를 크게 올릴 수 있습니다.

보안 설정 우선순위 (긴급→일반):

즉시 적용 (P0):
□ TLS 1.0/1.1 비활성화
□ 관리자 경로 IP 제한
□ 불필요한 HTTP 메서드(TRACE, DELETE) 차단

빠른 시일 내 (P1):
□ HSTS 설정 (짧은 max-age로 테스트 후 확장)
□ X-Frame-Options, X-Content-Type-Options
□ HttpOnly, Secure 쿠키 속성

품질 개선 (P2):
□ Content-Security-Policy (Report-Only로 시작)
□ CORS 정책 구체화
□ Nginx server_tokens off (버전 정보 숨기기)
로컬 또는 서버
# Nginx 버전 정보 숨기기 (http 블록에 추가)
# server_tokens off;

# 설정 후 확인
curl -I https://example.com | grep Server
# "Server: nginx" 만 나오고 버전 번호 없어야 함

다음 모듈에서는 WAF/WAAP로 웹 공격을 차단하고, CDN 캐시를 관리하는 실무를 다룹니다.

지식 확인

퀴즈 — 4문제

Q1

HTTP Strict-Transport-Security(HSTS) 헤더가 하는 일은?

Q2

CORS에서 Access-Control-Allow-Origin: * 설정의 보안 위험은?

Q3

쿠키의 Secure 속성은 어떤 보안 역할을 하는가?

Q4

TLS 1.0/1.1을 비활성화해야 하는 이유는?

0 / 4 답변

🧪 실습으로 확인하기

Nginx 설치 및 기동

초급

Linux 서버에 Nginx를 설치하고 systemd 서비스로 등록하여 80포트에서 응답하는 상태까지 만든다.

30📋 3단계💻 직접 환경
실습 시작하기 →

이것도 배워보세요

infra-ops중급 · 60
[Infra Ops] 취약점 조치와 보안 심사 대응 실무
인프라 서비스 운영 트랙 계속
linux입문 · 30
[Linux] 개발자가 왜 리눅스 서버와 커맨드라인을 반드시 배워야 하는가
Linux 트랙 시작점