SOFTMOA PROJECTS

대기업 그룹사 전산시스템 보안 고도화 — 외부 취약점진단 전면 대응과 다층 방어 체계 구축

구축 배경과 구현 범위를 확인해보세요.

Contents

보안 관제 대시보드, 공격 경로 차단·요청 제한·fail2ban 차단·로그인 잠금 현황과 시간대별 차단 요청

대기업 그룹사의 기업 홈페이지와 콘텐츠 관리자 시스템을 새 구조로 재구축하면서, 외부 전문기관의 취약점진단에서 지적된 항목을 코드와 서버 설정으로 모두 조치하고 공격 로그 분석에서 나온 위험까지 막는 다층 방어 체계를 구축했습니다. 진단 보고서를 받아 항목별로 실제 서버에 같은 요청을 재현해 확인하고, 하루 동안 13건의 보안 변경을 테스트 서버에 반영해 응답 헤더 검사까지 배포 자동 검증에 묶었습니다. 점검은 소프트모아가 직접 개발한 보안 점검 프레임워크로 수행했습니다.

프로젝트 개요

기존 시스템은 외부 취약점진단에서 소스코드 영역 개선필요 판정을 받았고, 운영 서버 접근 로그에는 2주 동안 WordPress·PHP 관리도구 탐색 약 4만 8천 건, 환경 파일(.env, .git) 탈취 시도 약 3천 건이 쌓여 있었습니다. 공격 요청이 애플리케이션까지 들어와 서버 오류(5xx)를 낸 기록도 2만 건에 달했습니다.

재구축 시스템에서는 단순히 지적 항목을 하나씩 지우는 데 그치지 않고, 공격이 어디에서 멈춰야 하는지를 층별로 다시 설계했습니다. 클라우드 방화벽, 웹 서버(nginx), 자동 차단(fail2ban), 애플리케이션(Spring Security), 프로세스 송신 통제(systemd), 데이터베이스(PostgreSQL) 각 층이 서로 다른 공격을 맡아, 한 층이 뚫려도 다음 층에서 막히도록 구성했습니다.

구분내용
대상대기업 그룹사 기업 홈페이지(국문·영문, 공개 페이지 106개)와 콘텐츠 관리자 시스템
범위외부 취약점진단 지적 항목 조치, 공격 로그 분석, 서버·애플리케이션 보안 통제 구축, 코드 보안 점검(OWASP Top 10:2025)
방식진단 요청 재현 → 원인 추적 → 코드·설정 조치 → 회귀 테스트 → 배포 자동 검증
결과코드·설정으로 고칠 수 있는 지적 항목 전부 조치, 정적 분석 재지적 대비 소명 근거 정리, 실제 악용 가능한 고위험 취약점 0건

자체 개발 보안 점검 프레임워크

이번 작업은 소프트모아가 웹 프로젝트용으로 직접 개발해 운영하는 보안 점검 프레임워크로 진행했습니다. 점검을 세 가지 모드로 나누고, 모드마다 수집 스크립트와 판정 기준, 보고서 형식을 정해 두어 누가 점검해도 같은 근거와 같은 결론이 나오게 만든 체계입니다.

모드점검 내용근거 자료
버전 취약점(updates)설치된 라이브러리·런타임·OS 패키지의 공개 취약점(CVE), 실제 악용 목록(KEV), 악용 가능성 점수(EPSS), 지원 종료(EOL)와 올릴 버전의존성 해석 결과, 서버 패키지 목록
공격 로그와 보안 통제(posture)웹 서버 접근 로그의 공격 징후 분류, 노출·분리·로그인 보호·CSRF·차단·요청 제한·fail2ban·로그 보존·DB 감사·백업·TLS 통제의 충족 여부접근 로그 분석기, 서버 상태 수집기
코드 취약점(code)모든 진입점을 목록화한 뒤 OWASP Top 10:2025 분류별로 인가, 주입, SSRF, 파일 처리, 인증, 예외 처리를 추적진입점 목록, 소스 추적, 기존 테스트
  • 안전 우선 설계: 모든 모드는 읽기 전용으로 시작합니다. 동작 중인 서버에 공격 요청을 보내지 않고, 점검 중에는 서버·방화벽·차단 상태를 바꾸지 않습니다.
  • 정보 보호: 보고서에는 방문자 IP, 쿼리 값, 계정, 세션·토큰 값이 남지 않도록 자동으로 가립니다(출발지는 src-009 같은 익명 번호로 표기).
  • 근거 중심 판정: 발견 항목마다 "무엇인지, 어떻게 공격하는지, 판정, 근거, 해결 방법과 담당"을 적고, 판정은 해당·해당(완화)·해당 없음·미확인으로 나눕니다. 확인하지 못한 것은 통과로 쓰지 않습니다.
  • 점검 기록 관리: 점검마다 실행 기록과 모드별 보고서를 남기고, 고객 제출 보고서에 인용한 기록은 지워지지 않게 보존합니다.

OWASP Top 10:2025 분류별 코드 보안 점검 보고서와 발견 항목별 조치 현황

공격 로그 분석

테스트 서버를 연 지 하루 반 만에 접근 로그 70,849줄을 전수 분석해 자동 스캐너의 탐색을 찾아냈습니다. 서버가 공개되자마자 wpprobe, masscan 같은 스캐너와 브라우저로 위장한 요청이 환경 파일과 관리도구를 찾기 시작했고, 분석 결과를 그대로 차단 규칙으로 옮겼습니다.

위험 요소공격 방법막는 장치
환경 파일·저장소 탈취/.env, /.git/config, /.ftpconfig 요청nginx 서버 단계에서 444로 연결 종료
PHP 관리도구 탐색phpMyAdmin, elFinder 커넥터, config.phpPHP·ASP·JSP·CGI 확장자 444 종료
WordPress 탐색/wp-login.php, /xmlrpc.php, /wp-json/WordPress 경로 444 종료
경로 조작장비 취약점을 노린 상위 경로(../) 요청경로 정규화, 파일 저장소 루트 이탈 검사
SQL 주입OR 1=1 형태의 조건 조작jOOQ 값 바인딩
반복 공격같은 IP의 연속 탐색fail2ban이 1시간 동안 80·443 차단

수행 업무

1. 공격 경로 차단과 요청 제한

  • 숨김 파일(인증서 갱신용 .well-known 제외), PHP·ASP·JSP·CGI 확장자, wp-admin, phpMyAdmin, elFinder 경로는 애플리케이션에 넘기지 않고 nginx 서버 블록에서 444로 바로 끊습니다. 업로드 파일 위치 같은 우선 매칭 규칙보다 먼저 적용되도록 배치했습니다.
  • 관리자 로그인은 IP당 분당 10회, 관리자 API는 초당 20회로 제한하고, 넘으면 관리자 화면이 그대로 읽을 수 있는 표준 오류 형식(problem+json) 429를 돌려줍니다.
  • 공개 조회수 증가 요청에도 IP당 분당 30회 제한을 걸어 조회수 조작과 쓰기 부하를 막았습니다. 제한에 걸려도 화면은 정상으로 보이고 조회수만 세지 않습니다.

2. fail2ban 자동 차단과 정규식 결함 수정

  • 444 차단 응답이 10분에 20회, 요청 제한 초과가 10분에 10회인 IP를 1시간 동안 80·443 포트에서 막습니다.
  • fail2ban이 정규식을 대조하기 전에 날짜 부분을 빈 대괄호로 바꾼다는 동작 때문에, 날짜를 요구하던 필터가 차단 응답을 하나도 세지 못하는 결함을 찾아 고쳤습니다. 실제 서버 로그로 444 줄은 모두 적중하고 정상 요청은 한 줄도 걸리지 않는 것을 검증했습니다.

3. 관리자 로그인 무차별 대입 방어

  • 같은 계정에 연속 5회, 같은 IP에서 15분 안에 20회 실패하면 15분 동안 비밀번호 확인 없이 429로 거절하고, 거절한 시도도 감사 기록(LOCKED)에 남깁니다.
  • 잠금은 회원 식별자 기준이라 대소문자, 악센트, 앞뒤 공백을 바꾼 아이디로 우회할 수 없습니다. 로그인에 성공하면 그 계정의 실패 수를 지웁니다.
  • 세션은 로그인 때 ID를 교체하고, 쿠키는 Secure·HttpOnly·SameSite=Lax, 모든 쓰기 요청은 CSRF 토큰을 요구합니다.

관리자 인증 감사 기록, 연속 실패 뒤 15분 잠금(LOCKED)과 로그인 보호 정책

4. Content-Security-Policy 도입

  • 관리자가 입력한 HTML 본문을 그리는 사이트라, 서버 정화 외에 브라우저 단계의 2중 방어로 CSP를 적용했습니다.
  • 공개 사이트는 요청마다 새 nonce를 만들어 script-src 'nonce' 'strict-dynamic'으로 두고, Next.js가 자기 스크립트와 분석·지도·공유 스크립트에 nonce를 붙이도록 구성했습니다. 태그 관리자, 웹 분석, 지도와 공유 SDK가 모두 정상 동작하는 것을 확인했습니다.
  • 관리자 앱은 정적 페이지 특성에 맞춘 정책을 따로 두고, 외부 스크립트 출처 제한, object-src 'none', base-uri, frame-ancestors를 적용했습니다.

5. 정보 노출 제거와 보안 헤더 정비

  • Server 헤더를 제품명까지 완전히 지우고(nginx headers-more 모듈), Next.js의 X-Powered-By 헤더를 껐습니다.
  • 모든 응답에 Referrer-Policy: strict-origin-when-cross-origin, HSTS, nosniff, X-Frame-Options를 정적 파일까지 빠짐없이 붙였습니다.
  • 숫자 자리에 문자를 넣으면 400 응답에 프레임워크 형 변환 원문과 내부 Java 형 이름이 나가던 것을 "입력값을 확인해 주세요." 같은 일반 문구로 바꾸고, 필드 이름은 그대로 유지했습니다.
  • 외부의 헬스 체크 주소는 404로 숨기고, robots.txt에서 관리자 경로를 지웠습니다. 배포 확인은 내부 주소로 직접 호출하도록 바꿔 운영 영향이 없습니다.
  • 외부 주소를 여는 새 창에 noopener, noreferrer를 붙여 열린 페이지가 원래 탭을 피싱 페이지로 바꾸지 못하게 했습니다.

6. TLS 암호군 강화

  • TLS 1.3과 1.2만 허용하고, 암호군을 ECDHE + AES-GCM, CHACHA20만 남겨 약한 MAC 지적을 받은 CBC-SHA 계열을 제거했습니다.

HTTP 보안 헤더와 TLS 구성 점검 결과, CSP nonce와 strict-dynamic, Server·X-Powered-By 제거

7. 오픈 리다이렉트 차단

  • 옛 주소를 새 주소로 옮겨 주는 규칙(202개)에서 /pub//외부도메인 같은 입력이 외부 사이트로 이동시킬 수 있는 경로를 찾아, 프레임워크의 경로 정규화에 기대지 않고 평가 함수에서 직접 막았습니다. 데이터 이관 도구의 Kotlin 평가기도 같은 테스트 벡터로 함께 고쳤습니다.

8. SSRF 심층 방어: 애플리케이션 내부망 송신 차단

  • 관리자가 입력한 URL을 확인하거나 외부 이미지를 받아오는 기능은 이미 공인 주소만 허용하고 리다이렉트마다 다시 검사합니다. 여기에 DNS 재바인딩으로 검사를 우회하는 경우까지 대비해, 백엔드 프로세스 자체가 사설망, 링크 로컬(클라우드 메타데이터 포함), CGNAT 대역으로는 송수신하지 못하도록 systemd IPAddressDeny로 막았습니다.
  • 허용은 localhost와 데이터베이스 주소뿐이며, 데이터베이스 주소는 접속 설정에서 읽어 배포 때 자동으로 반영하고 IPv4가 아니면 배포를 멈춥니다.

9. 데이터베이스 감사와 로그 보존

  • PostgreSQL에 접속(인증·인가 단계), 접속 종료, DDL(권한 변경 포함) 기록을 켜고 영구 저널로 보내 1년까지 보존합니다. 실패한 인증은 항상 기록됩니다.
  • 웹 서버 로그는 별도 경로에서 하루 단위 120개를 보존해 90일 보존 요건을 넘기고, 배포판 설정 파일을 건드리지 않아 자동 보안 업데이트가 멈추지 않게 했습니다.
  • 데이터베이스는 웹 서버와 분리된 별도 서버에서 사설망으로만 열고, 웹 서버 주소만 접속을 허용합니다.

10. 배포 자동 검증과 서비스 가용성

  • 배포 스크립트가 배포 직후 공개 화면과 관리자 화면의 CSP, Referrer-Policy, Server 헤더 부재를 직접 검사해, 설정이 빠진 상태로 배포가 끝나지 않게 했습니다. 산출물의 비밀값 포함 여부도 배포마다 검사합니다.
  • 모든 웹 서비스가 부팅 때 자동으로 켜지고 비정상 종료되면 5초 안에 다시 뜨도록 구성했습니다.
  • 테스트 단계에서는 클라우드 방화벽으로 80·443을 사무실 주소에만 열어 공개 전 노출을 막았습니다.

클라우드 방화벽, nginx, fail2ban, Spring Security, systemd 송신 차단, PostgreSQL로 이어지는 다층 방어 아키텍처

외부 취약점진단 대응 현황

진단 항목조치
세션 쿠키 Secure 누락Secure, HttpOnly, SameSite=Lax 적용
정적 파일 nosniff, HSTS 누락정적 위치까지 보안 헤더 반복 적용
page, size 오버플로 500 오류범위 검사 후 400 응답, 회귀 테스트
경로 조작경로 정규화, 루트 이탈 검사, UUID 저장 이름
SSRF공인 주소 검사, 리다이렉트 재검사, 프로세스 내부망 송신 차단
위험한 형식 파일 업로드확장자 허용 목록과 파일 서명 동시 검사, PDF 서명, 이미지 해독 검사
CSP 누락공개 사이트 nonce + strict-dynamic, 관리자 전용 정책
TLS 약한 MAC 암호군ECDHE + AES-GCM, CHACHA20만 허용
X-Powered-By, Server 헤더 노출두 헤더 모두 제거
Referrer-Policy 누락strict-origin-when-cross-origin
오류 응답의 내부 형 이름 노출일반 안내 문구로 치환
robots.txt 관리자 경로, 헬스 체크 공개관리자 경로 삭제, 외부 헬스 체크 404
관리자 비밀번호 자동완성자동완성 끔
로그인 반복 시도 방어 부재계정·IP 기준 15분 잠금, 감사 기록
접속·권한·DDL 기록 부재DB 감사 로그와 1년 보존

기술 구성

영역구성
BackendKotlin 2.4, Spring Boot 4.1, Spring Security(CSRF, 세션, 로그인 잠금), jOOQ, JDK 25
FrontendNext.js 16.4 App Router(요청별 nonce CSP), React 19.3, TypeScript 6, Tailwind CSS 4
DatabasePostgreSQL 18(별도 서버, 사설망, 접속·DDL 감사)
ServerUbuntu, nginx(공격 경로 차단, 요청 제한, TLS 1.3, headers-more), fail2ban, systemd(IPAddressDeny, 자동 재시작), journald
점검 도구소프트모아 보안 점검 프레임워크(버전 취약점, 공격 로그·보안 통제, OWASP Top 10:2025 코드 점검)

구현 포인트

  • 지적 항목을 지우는 것이 아니라 재현부터: 진단과 같은 요청을 실제 서버에 읽기 전용으로 다시 보내 응답을 확인한 뒤 고치고, 고친 뒤 같은 요청으로 다시 확인했습니다.
  • 한 층에 기대지 않는 설계: 애플리케이션이 막는 공격도 웹 서버와 운영체제 단계에서 한 번 더 막습니다. SSRF는 코드 검사와 프로세스 송신 차단, XSS는 서버 정화와 CSP, 무차별 대입은 요청 제한과 계정 잠금과 fail2ban이 함께 맡습니다.
  • 도구의 실제 동작까지 검증: fail2ban 필터처럼 설정만으로는 동작한다고 볼 수 없는 장치는 실제 로그로 적중과 비적중을 확인했습니다.
  • 사용성 하한선 유지: CSP를 넣으면서도 분석, 지도, 공유 기능과 첫 화면 모습이 그대로인지 확인했고, 조회수 제한은 화면에 오류를 보이지 않게 했습니다.
  • 되돌아가지 않는 보안: 보안 헤더 검사를 배포 자동 검증에 넣어, 이후 누가 배포하더라도 설정이 빠지면 바로 드러나게 했습니다.

관련 화면

본문의 화면은 고객 시스템의 보안 정보를 보호하기 위해 실제 구성과 점검 결과를 바탕으로 재구성한 예시입니다. 실제 접속 주소, 계정, 서버 이름은 싣지 않았습니다.

Share this

포트폴리오

Have a questions?

견적 및 기술문의

mobile : 010-7931-4813

Contact Form