지난 실습에서 IntraPortal(Spring Boot)을 EC2에 올렸다.
앱 자체는 이전 시리즈에서 CSRF, BCrypt, 보안 헤더까지 시큐어코딩으로 하드닝을 해둔 상태다.
여기서부터는 관점을 바꿔서 방어자가 아니라 공격자의 위치에서 Kali 한 대로 배포된 EC2를 외부에서 스캔하고 실제로 어느 지점에서 보안이 뚫리는지 확인한다.
1. 진단 시나리오
진단은 외부 공격자(Kali) 시점에서 EC2에 네트워크로 노출된 표면(attack surface)을 훑는 흐름이다.
포트 스캔으로 열린 포트를 찾고, 열린 포트를 통해 전송 계층·인증·관리 경로를 순서대로 확인한다.

Kali에서는 네트워크에 노출된 표면을 훑어 실제로 뚫리는 지점을 찾고, 내부 접속으로는 인스턴스가 탈취됐을 때 자격증명이 새어나갈 위험이 있는지 확인한다.
2. 네트워크 포트 스캔으로 노출된 표면 파악하기
먼저 EC2의 공격 표면을 스캔한다.
Nmap으로 대상 EC2 서버의 열린 포트를 찾고, 열린 포트에 한해 서비스 버전까지 탐지한다.
1️⃣ 열린 포트 탐색과 버전 탐지
-Pn은 대상이 ping에 응답하지 않아도 살아 있다고 가정하고 스캔을 진행하는 옵션이다.
AWS 보안 그룹이 ICMP를 막는 경우가 많아 클라우드 대상 스캔에서는 사실상 필수 옵션이다.
--open은 열린 포트만 출력해 결과를 정리해준다.
# 열린 포트 탐색
nmap -Pn --open 43.203.254.182

# 대상 포트 버전 탐지
nmap -sV -Pn -p 22,8080 43.203.254.182

2️⃣ 스캔 결과
| 포트 | 상태 | 서비스 | 버전 |
| 22/tcp | open | ssh | OpenSSH 10.2p1 Ubuntu 2ubuntu3.2 |
| 8080/tcp | open | http | Apache Tomcat (Spring Boot 내장) |
| 나머지 998개 | filtered | — | 보안 그룹이 차단 |
998개 포트가 filtered로 나온다는 것은 보안 그룹이 기본 차단(default deny)으로 잘 동작하고 있다는 뜻이다.
문제는 열려 있는 두 포트다.
SSH(22)와 HTTP(8080)가 전체 대역(0.0.0.0/0)에 개방되어 있고, -sV 결과로 OpenSSH와 Tomcat의 세부 버전 정보까지 그대로 노출됐다.
버전이 노출되면 공격자는 해당 버전에 알려진 취약점을 곧바로 조회해 공격 벡터를 좁힐 수 있다.
3. HTTP 평문 통신과 앱 방어 상태를 함께 확인하기
포트가 열린 것을 확인했으니 8080에서 오가는 트래픽이 어떻게 보호되는지 본다.
curl 요청으로 배포 레이어와 애플리케이션 레이어의 보안 상태를 동시에 확인할 수 있다.
1️⃣ 응답 헤더와 로그인 요청 방식 확인
# 응답 헤더 확인
curl -I http://43.203.254.182:8080/login
# 로그인 요청 전송 방식 확인
curl -v -X POST http://43.203.254.182:8080/login \
-d "username=admin&password=admin123" 2>&1 | grep -iA3 "password\|HTTP\|location"
2️⃣ 전송 계층(배포 계층) - 취약

http:// 요청에 HTTPS 리다이렉트 없이 HTTP/1.1 200이 돌아왔다.
평문 서비스가 확정된 것이다.

로그인 POST 역시 HTTP/1.1 평문으로 전송되어, 중간자(MITM) 위치를 확보한 공격자가 패킷을 가로채면 password가 그대로 노출될 수 있다.
3️⃣ 애플리케이션 계층 - 정상
반면 지난 번 시큐어코딩으로 하드닝한 IntraPortal 앱 자체는 방어가 잘 되고 있었다.
curl -v -X POST http://43.203.254.182:8080/login \
-d "username=admin&password=admin123" 2>&1 | grep -iA3 "password\|HTTP\|location"
curl -v는 요청과 응답 헤더를 방향 기호와 함께 상세히 출력한다.
>로 시작하는 줄은 Kali가 서버로 보낸 요청, <로 시작하는 줄은 서버가 돌려준 응답이다.
grep -iA3으로 HTTP 등이 매칭된 줄과 그 다음 3줄까지 추려, 응답 상태 코드와 주요 보안 헤더만 한눈에 보이게 정리했다.
| -i | 대소문자 무시 (HTTP, http, Http 모두 매칭) |
| -A3 | 매칭된 줄 + 그 아래 3줄까지 함께 출력 (After 3) |
| password|HTTP|location | password, HTTP, location 중 하나라도 포함된 줄을 매칭 (|는 OR) |

서버 응답 쪽을 보면 다음 사항들을 확인할 수 있다.
1) CSRF 토큰이 없는 POST는 HTTP/1.1 403으로 거부됐다.
2) Set-Cookie에 HttpOnly가 붙어있으므로 스크립트의 쿠키 접근이 차단됨을 알 수 있다.
HttpOnly는 쿠키에 붙는 플래그인데, 이게 설정되면 JavaScript(document.cookie)로 해당 쿠키를 읽을 수 없게 된다.
쿠키는 브라우저가 HTTP 요청을 보낼 때만 자동으로 첨부하고, 스크립트 단에서는 접근 자체가 차단된다.
공격자가 XSS로 악성 스크립트를 심는 데 성공했다고 가정하면, 그 스크립트가 document.cookie로 세션 쿠키(JSESSIONID)를 읽어 외부 서버로 전송할 수 있다.
세션 쿠키를 탈취하면 공격자는 피해자의 세션을 그대로 가로채 로그인 상태를 훔친다(Session Hijacking).
HttpOnly가 걸려 있으면 XSS가 성공하더라도 스크립트가 쿠키를 읽지 못하니, 세션 쿠키 탈취 경로가 막히는 것이다.
XSS 자체를 막는 것은 아니지만 피해 범위를 줄여주는 방어 계층이다.
3) X-Content-Type-Options: nosniff, X-XSS-Protection: 1; mode=block이 세팅되어 있다.
두 헤더 모두 브라우저가 콘텐츠를 처리하는 방식을 제한해서 공격 표면을 줄이는 역할을 한다.
X-Content-Type-Options: nosniff
브라우저에는 MIME 스니핑이라는 동작이 있다.
서버가 Content-Type: text/plain이라고 보내도, 브라우저가 내용물을 보고 HTML이라고 판단하면 렌더링해 버리는 것이다.
공격자가 이걸 악용하면, 텍스트 파일로 위장한 HTML/JavaScript를 업로드해서 브라우저가 스크립트로 실행하게 만들 수 있다.
nosniff가 걸려 있으면 브라우저는 서버가 선언한 Content-Type을 그대로 따르고 추측하지 않는다.
서버가 text/plain이라고 선언했으면 무조건 텍스트로 처리한다.
X-XSS-Protection: 1; mode=block
브라우저 내장 XSS 필터를 활성화하는 헤더다.
1은 필터를 켜라는 뜻이고, mode=block은 XSS 패턴이 감지됐을 때 페이지 렌더링 자체를 차단하라는 지시다.
예를 들어 URL 파라미터에 <script>alert(1)</script> 같은 값이 들어 있고 그것이 응답 본문에 그대로 반영되면, 브라우저가 이를 감지해서 해당 페이지를 아예 그리지 않는다.
다만 이 헤더는 현재 대부분의 최신 브라우저에서 더 이상 지원하지 않기 때문에 현대적인 방어에서는 이 헤더 대신 Content-Security-Policy(CSP)로 XSS를 차단한다.
하지만 레거시 브라우저를 쓰는 사용자가 있을 수 있으므로 추가 방어 계층으로 남겨두는 것이 일반적이다.
4) Cache-Control: no-cache, no-store, max-age=0, must-revalidate가 걸려 있다.
로그인 페이지가 브라우저나 프록시에 캐시되지 않도록 막고 있다.
Cache-Control은 브라우저와 중간 프록시가 이 응답을 저장(캐시)해도 되는지를 제어하는 헤더다.
| 디렉티브 | 의미 |
| no-store | 응답을 디스크/메모리에 아예 저장하지 마라 |
| no-cache | 저장됐더라도 사용 전에 반드시 서버에 재검증하라 |
| max-age=0 | 캐시 유효 시간을 0초로 설정 (즉시 만료) |
| must-revalidate | 만료된 캐시를 서버 확인 없이 자의적으로 재사용하지 마라 |
4. 노출된 관리 경로(Actuator)를 점검하기
Spring Boot Actuator는 애플리케이션의 상태, 환경 변수, 메트릭 등을 노출하는 관리 엔드포인트다.
편리한 만큼 인증 없이 열려 있으면 심각한 정보 노출로 이어진다.
특히 /actuator/env는 환경 변수를 통째로 드러내 DB 자격증명 같은 민감 데이터가 새어나갈 수 있다.
curl -s -o /dev/null -w "%{http_code}\n" http://43.203.254.182:8080/actuator
curl -s -o /dev/null -w "%{http_code}\n" http://43.203.254.182:8080/actuator/health
curl -s -o /dev/null -w "%{http_code}\n" http://43.203.254.182:8080/actuator/env
| 옵션 | 의미 |
| -s | 진행률 표시 끄기 (silent) |
| -o /dev/null | 응답 본문은 버린다 (내용은 필요 없고 상태 코드만 볼 것) |
| -w "%{http_code}\n" | 상태 코드만 출력한다 |
| -m 5 | 5초 안에 응답 없으면 타임아웃 |

세 경로 모두 401을 반환했다. 인증이 걸려 있어서 막혀있다는 뜻이다.
가장 민감한 /actuator/env도 보호되고 있었다.
이 항목은 하드닝이 되어 있는 상태라 별도 조치가 필요 없다.
5. 취약점 진단 결과
진단 결과, 발견된 취약점 4건은 보안 그룹 전체 개방, HTTP 평문 통신, 서버 버전 노출, 로그인 평문 전송으로 전부 배포 레이어에 집중됐다.
| 번호 | 발견 항목 | 위험도 | 조치 내용 | 상태 |
| 1 | SSH(22) 전체(0.0.0.0/0) 개방 → 브루트포스 노출 | 높음 | 보안 그룹에서 내 IP만 허용 | 조치 예정 |
| 2 | 8080 HTTP 평문 전체 개방 → 정보 노출 가능 | 높음 | HTTPS 적용 / 접근 IP 제한 | 조치 예정 |
| 3 | 서버 버전 정보 노출 (OpenSSH, Tomcat) | 중 | 배너 최소화 / 버전 숨김 | 조치 예정 |
| 4 | 로그인 요청 평문 전송 (password 노출) | 높음 | HTTPS/TLS 적용 | 조치 예정 |
반면 CSRF 방어, 보안 헤더, Actuator 인증 등 앱·OS 레이어는 이전 실습에서 실행한 하드닝이 유효하다는 것을 확인했다.
| 항목 | 확인 결과 |
| 앱 보안 헤더 (HttpOnly, X-Frame-Options, nosniff) | ✅ 정상 |
| CSRF 방어 | ✅ 403 응답 |
| Actuator 인증 | ✅ 401 |
이번 실습을 통해 시큐어코딩으로 앱을 하드닝해도 그 앱을 올리는 배포 환경의 설정이 허술하면 공격자에게는 열린 문이 된다는 교훈을 얻었다.
'Journey to Security > 취약점진단|모의해킹' 카테고리의 다른 글
| 웹 취약점 진단/모의해킹을 위한 curl - 요청 조작부터 세션 유지까지 (0) | 2026.07.16 |
|---|---|
| 다형성 셸코드(Polymorphic Shellcode) - 시그니처 기반 탐지를 무력화하는 셸코드 변형 기법 (0) | 2026.07.01 |
| 역직렬화 취약점 - 객체 복원과 동시에 공격 코드가 실행되는 원리 (0) | 2026.06.30 |
| Spring Boot 사내 포털 모의침투 - 공격 체인을 끊는 4단계 패치 (0) | 2026.06.30 |
| Spring Boot 사내 포털 모의침투 - MITRE ATT&CK 기반 공격 시나리오 (0) | 2026.06.29 |