CJ Commerce 모의해킹 결과보고서 — 취약점 8건을 문서로 옮기기

🛡️ 강사 제공 인가된 실습 환경에서 수행

취약점을 찾는 것과 그걸 남이 읽고 고칠 수 있는 문서로 만드는 건 완전히 다른 일이다. 공격 과정은 CJ Commerce 모의해킹 — RFP 분석부터 서버 명령 실행까지에 적었고, 이 글은 그 결과를 보고서라는 산출물로 옮기면서 판단한 것들에 대한 기록이다.

진단 대상은 부트캠프에서 강사가 제공한 가상기업 쇼핑몰(사설망 192.168.10.100:19191)이고, 진단일은 2026-07-08이다.

발견한 취약점

No취약점위험도
1SQL Injection (파라미터 조작) — 정보노출·민감정보·인증우회 포함
2SQL Injection (Blind — 추가 지점)
3File Download (경로 조작 / LFI) — 소스·크레덴셜 노출 포함
4File Upload (웹쉘 업로드 → RCE) — 필터 우회 포함
5Command Injection (OS 명령 실행)
6XSS (Stored / 관리자 화면 실행)
7취약한 비밀번호 저장 (약한 해싱)
8관리자 페이지 통제 미흡

각 항목은 취약점 개요 → 상세설명 → 조치 방안 세 절로 쓰고, 개요에는 취약점 내용·위험도·산정 근거와 해당 URL·파라미터를 표로 붙였다. 상세설명은 Step 1, 2, 3… 으로 재현 순서를 그대로 따라갈 수 있게 쓰고 각 Step마다 증빙 화면을 넣었다.

보고서를 쓰면서 판단한 것들

1. 취약점을 몇 개로 쪼갤 것인가

처음엔 발견한 걸 전부 개별 항목으로 세서 12건쯤 나왔다. 줄여서 8건으로 정리했는데, 기준은 “조치가 하나로 끝나는가” 였다.

인증 우회를 예로 들면, 처음엔 별도 항목으로 뒀다. 관리자 계정을 탈취했으니 독립된 취약점처럼 보였다. 그런데 조치 방안을 쓰다 보니 적을 게 없었다. MFA를 논리적으로 우회한 게 아니라 SQL Injection으로 뽑은 크레덴셜과 보안질문 답을 그냥 입력한 것뿐이었기 때문이다. 근본 원인이 SQLi 하나면 조치도 SQLi 하나다. 그래서 1번 항목에 통합하고, 대신 그 판단 근거를 보고서에 각주로 남겼다.

※ 인증 우회의 근본 원인은 SQL Injection이다. MFA 등 별도 인증수단을 논리적으로 우회한 것이 아니라, SQLi로 획득한 크레덴셜·보안질문 답변을 그대로 사용해 계정에 접근한 것이므로 본 항목에 통합한다.

반대로 SQL Injection은 2건으로 나눴다. 검색·상품 파라미터의 오류 기반 SQLi와 쿠폰 적용 기능의 Boolean-based Blind SQLi는 진입점도 다르고 확인 방법도 달라서, 한 항목으로 묶으면 개발자가 한쪽만 고치고 닫을 위험이 있었다.

2. 위험도를 매기는 근거를 쓰기

‘상/중/하’만 적으면 읽는 쪽에서 납득할 수가 없다. 그래서 표에 산정 근거 칸을 따로 만들고, 어떤 사실 때문에 그 등급이 나왔는지를 문장으로 적었다.

LFI를 ‘상’으로 올린 것도 /etc/passwd를 읽었기 때문이 아니라 config.php 노출로 DB 접속정보가 평문으로 나왔고, 그게 웹쉘 업로드 경로와 내부 로직 파악으로 직접 연계됐기 때문이다. 단독으로 보면 정보 노출이지만 체인의 중간 고리라서 위험도가 올라간다. 이 연결을 안 적으면 ‘상’이 과해 보인다.

3. 체인을 따로 그리기

항목별로 끊어 쓰면 개별 취약점은 다 보이는데 “그래서 이 서비스가 얼마나 위험한가”가 안 보인다. 그래서 요약표 아래에 공격 체인을 한 줄로 붙였다.

정보수집
  → 파일 다운로드 경로 조작(LFI)으로 소스·DB 접속정보 확보
  → SQL Injection으로 DB 전체 열람 (민감정보·크레덴셜)
  → 파일 업로드(웹쉘) + SQLi(파일명 조회)로 원격 명령 실행(RCE)
  → DB 직접 접근 → 관리자 계정 탈취
  → 관리자 기능 장악

웹쉘 업로드만 놓고 보면 저장 파일명이 bin2hex(random_bytes(10))으로 랜덤이라 경로를 모르면 못 부른다. 방어처럼 보인다. 그런데 그 파일명이 inquiry_attachments 테이블에 저장되고, 그 테이블은 SQLi로 읽힌다. 두 취약점이 각각은 미완성인데 합치면 RCE가 된다. 이런 건 항목별 서술로는 절대 안 드러나서 체인을 따로 그렸다.

4. 조치 방안은 “무엇을” 이 아니라 “어떻게”

입력값을 검증한다 같은 문장은 아무것도 지시하지 않는다. 실제로 코드에서 손댈 지점이 보이도록 썼다.

  • SQLi → 모든 DB 쿼리에 Prepared Statement(파라미터 바인딩) 적용
  • 파일 다운로드 → 파일명 화이트리스트 + realpath()로 기준 디렉터리 이탈 검사
  • 파일 업로드 → 확장자 화이트리스트·MIME·매직바이트 검증을 함께, 저장 경로는 웹 루트 밖으로
  • 비밀번호 저장 → SHA-256 단일 해시를 bcrypt/Argon2 등 salt 포함 알고리즘으로 교체

업로드 항목에서 “함께”를 강조한 건 진단 중에 확장자만 막힌 상태, MIME만 막힌 상태를 각각 우회해봤기 때문이다. 매직바이트(GIF89a)와 확장자를 조합하면 하나씩 건 필터는 다 넘어간다.

공개하면서 가린 것

포트폴리오로 올리기 위해 원본에서 세 가지를 가렸고, 그 기준을 보고서 표지에 명시했다.

가린 것

  • 실습 환경의 정답 플래그 CJ{…} — 후속 교육 기수의 학습을 방해하지 않기 위해
  • 사람이 설정한 형태의 평문 비밀번호 (관리자·판매자 계정) — 재사용 위험 차단
  • 비밀번호 재설정 보안질문의 답

그대로 둔 것

  • rockyou 사전으로 즉시 크랙되는 admin / password — 이 값 자체가 7번 취약점의 내용이라 가리면 근거가 사라진다
  • 기본값 형태의 DB 계정 정보
  • 합성 고객 데이터 (example.test 도메인, 순번형 이름·전화번호)

가릴지 말지의 기준은 “이 값을 지우면 취약점이 설명되는가” 로 잡았다. password가 비밀번호라는 사실은 취약점 그 자체라 남기고, 관리자 계정의 실제 문자열은 아무 설명도 더하지 않으니 지웠다. 가린 자리는 지웠다는 걸 알 수 있게 검은 막대로 남겼다.

남은 아쉬움

  • 재현 가능성: Step별 스크린샷은 넣었지만 요청 원문(raw HTTP)을 부록으로 붙이지 않았다. 개발자가 그대로 복사해 확인하기엔 부족하다.
  • 조치 우선순위: 위험도는 매겼지만 “무엇부터 고칠 것인가”를 별도로 정리하지 않았다. 위험도 순서와 조치 난이도 순서는 다른데 그걸 구분해 적지 않았다.
  • 재진단 기준: 조치 후 무엇을 확인하면 닫힌 것으로 볼지를 항목마다 적어두면 훨씬 쓸모 있는 문서가 된다.

다음 보고서에서는 이 셋을 채울 생각이다.