📄 2026.02.06 (Day 72) - 파일 업로드, 인증/인가, 파라미터 변조 취약점


1. 핵심 개념 정리

파일 업로드 취약점 심화

# 핵심 개념 설명 실무/보안 관점
1 WebShell 업로드 jsp, jspx 등 실행 가능한 파일을 업로드하여 서버 제어 시스템 장악, 데이터 유출, 추가 공격의 거점 확보
2 파일명 무작위화 우회 UUID로 파일명 변경해도 다운로드 경로로 직접 접근 orgFileName 파라미터로 원본 확장자 유지 가능
3 업로드 경로 노출 download.jsp에 filePath, fileName 파라미터로 실제 경로 노출 경로 추측 없이 정확한 WebShell 위치 파악 가능
4 확장자 필터링 우회 .jsp, .jspx, .jspf 등 다양한 JSP 실행 가능 확장자 존재 단순 .jsp만 차단하면 .jspx로 우회 가능
5 직접 접근 경로 탐색 /data/files/, /files/ 등 여러 경로 시도로 실행 가능 경로 발견 업로드 디렉토리가 웹 루트 내부에 있으면 직접 실행 가능

불충분한 인증 및 인가

# 핵심 개념 설명 실무/보안 관점
6 인증 vs 인가 인증은 신원 확인, 인가는 권한 확인 로그인(인증)만으로는 부족, 각 작업마다 권한(인가) 검증 필요
7 프로세스 검증 누락 중간 단계를 건너뛰고 최종 단계 직접 접근 비밀글 비밀번호 확인 없이 view.jsp 직접 접근
8 권한 레벨 미검증 사용자 레벨 확인 없이 고급 자료 다운로드 허용 레벨 2 자료실인데 레벨 1 사용자가 URL로 직접 접근
9 파라미터 기반 인가 서버가 클라이언트 파라미터(권한, ID)를 신뢰하여 처리 board=admin, author=admin 등 파라미터 조작으로 권한 우회
10 백엔드 검증 부재 프론트엔드에서만 검증, 서버는 요청을 그대로 신뢰 Burp Suite로 요청 가로채서 파라미터 변조

파라미터 변조 공격

# 핵심 개념 설명 실무/보안 관점
11 Negative Injection 음수 입력으로 로직 우회 또는 금액 증가 배팅액을 음수로 설정하여 지면 오히려 돈 증가
12 Integer Overflow int 범위(-21억~21억) 초과 시 값 반전 42억 입력 시 음수로 변환되는 현상 악용
13 가격 조작 클라이언트에서 전송하는 가격을 서버가 신뢰 실제 가격 대신 1원으로 변조하여 구매
14 수량 조작 재고 확인 없이 클라이언트 수량 신뢰 count=-1000으로 설정하여 총액을 음수로
15 작성자 변조 게시글 작성 시 author 파라미터 조작 author=admin으로 설정하여 관리자 글로 위장

2. 실습 내용 정리

실습 1-1: WebShell 업로드 및 직접 접근 (문제 5번)

목표: JSP WebShell을 업로드하고 여러 경로를 시도하여 실행

실습 환경:

  • 대상 시스템: https://lab.eqst.co.kr:8440
  • 업로드된 파일명: 19f72501-bbb2-4b36-a71e-d94ed8679d1d.jsp (UUID 무작위화)
  • 원본 파일명: test.jsp

WebShell 구조 (test.jsp):

  • request.getParameter(“cmd”) 로 외부에서 명령어 입력 수신
  • Runtime.getRuntime().exec(cmd) 로 OS 명령 실행
  • 실행 결과를 BufferedReader 로 읽어 HTML 출력

실습 단계:

  1. test.jsp 파일 업로드
  2. 다운로드 링크에서 실제 저장 경로 정보 획득
    • URL 예: /data/download.jsp?filePath=/files/qna/20260206/&fileName=UUID.jsp&orgFileName=test.jsp
    • filePath 파라미터에서 실제 경로 (/files/qna/20260206/) 노출
  3. 여러 직접 접근 경로 시도
    • /data/files/qna/20260206/test.jsp -> 404 Not Found
    • /files/qna/20260206/test.jsp -> 404 Not Found
    • /data/files/qna/20260206/UUID.jsp -> 404 Not Found
    • /files/qna/20260206/UUID.jsp -> WebShell 실행 성공!
  4. WebShell로 명령 실행
    • ?cmd=whoami -> tomcat (실행 사용자 확인)
    • ?cmd=ls -la /var/www/html -> 웹 루트 디렉토리 목록 조회
    • ?cmd=cat /etc/passwd -> 시스템 사용자 목록 확인

분석 포인트:

  • 파일 업로드 시 확장자 검증 여부
  • 업로드 디렉토리의 스크립트 실행 권한 설정
  • 파일명 무작위화 적용 여부 (UUID 사용)
  • 직접 접근 가능한 경로 존재 여부

보안 인사이트:

  • 파일명을 UUID로 변경해도 경로가 예측 가능하면 무의미
  • download.jsp의 파라미터에서 실제 저장 경로가 노출됨
  • /files/ 디렉토리가 웹 루트 내부에 있어 직접 실행 가능
  • 날짜 기반 디렉토리 구조(20260206)는 예측 가능

실습 1-2: .jspx 확장자 우회 (문제 6번)

목표: .jsp 필터링을 .jspx 확장자로 우회

실습 환경:

  • 대상 시스템: https://lab.eqst.co.kr:8441
  • 업로드된 파일명: f9a28533-fbaf-4f7b-a5f0-e9c724033e42.jspx
  • 원본 파일명: test.jspx

JSPX 형식 WebShell 구조:

  • XML 형식의 JSP로, Tomcat 서버에서 동일하게 실행됨
  • jsp:root 루트 태그 + xmlns:jsp 네임스페이스 선언
  • jsp:directive.page 로 import, jsp:scriptlet 로 Java 코드 삽입
  • request.getParameter(“cmd”) -> Runtime.getRuntime().exec() -> 결과 출력

실습 단계:

  1. test.jspx 파일 업로드 (서버가 .jsp만 차단하고 .jspx는 허용하는 경우 통과)
  2. 다운로드 링크에서 경로 확인
  3. 직접 접근 시도
    • /data/files/qna/20260206/UUID.jspx -> WebShell 실행 성공!
  4. 명령 실행
    • ?cmd=cat /flag.txt

Tomcat/JSP 서버에서 실행되는 확장자 목록:

  • .jsp - 기본 JSP 파일
  • .jspx - XML 형식 JSP
  • .jspf - JSP Fragment (포함 파일)
  • .jsv - JSP Variant (일부 서버)
  • .jsw - JSP Wrapper (일부 서버)

블랙리스트 필터링의 한계:

  • .jsp만 차단하면 .jspx, .jspf로 우회 가능
  • 대소문자 변형: .Jsp, .JSP, .JsP
  • 더블 확장자: .jsp.txt (일부 서버에서 .jsp로 실행)

탐지 패턴:

  • .jsp 외 다른 JSP 관련 확장자 업로드 시도
  • XML 형식의 JSPX 파일 패턴 탐지
  • 업로드된 파일 내 Runtime.getRuntime().exec() 패턴

방어 방법:

  • 화이트리스트 방식: jpg, png, pdf만 허용
  • 파일 내용(Magic Number) 검증
  • 업로드 디렉토리에서 스크립트 실행 권한 제거
  • 업로드 디렉토리를 웹 루트 외부에 배치

실습 2-1: 프로세스 검증 누락 - 비밀글 직접 접근

목표: 비밀번호 확인 단계를 건너뛰고 비밀글 직접 조회

정상 프로세스:

  • write.jsp -> list.jsp -> viewcheckpw.jsp?id=1 -> (비밀번호 입력) -> view.jsp?id=1

공격 방법:

  • view.jsp?id=1 에 직접 접근 (비밀번호 확인 단계 건너뜀)
  • 서버가 비밀번호 확인 여부를 검증하지 않아 비밀글 내용 노출

취약한 코드 구조 (view.jsp):

  • request.getParameter(“id”) 로 게시글 ID만 받음
  • 비밀번호 확인 여부를 세션에서 검증하는 로직 없음
  • DB에서 내용 바로 조회 후 출력 -> 누구나 URL로 직접 접근 가능

안전한 코드 구조:

viewcheckpw.jsp (비밀번호 확인 페이지):

  • 비밀번호 일치 확인 시 session.setAttribute(“verified_post_” + id, true) 저장
  • 세션에 인증 정보 기록 후 view.jsp 로 리다이렉트

view.jsp (안전한 조회 페이지):

  • session.getAttribute(“verified_post_” + id) 로 비밀번호 확인 여부 검증
  • 미인증 시 viewcheckpw.jsp 로 리다이렉트
  • 조회 후 session.removeAttribute(“verified_post_” + id) 로 일회성 인증 제거

프로세스 검증 누락 유형:

  1. 은행 이체: 1.아이디 -> 5.이체 직접 접근 (2, 3, 4 단계 우회)
  2. 결제: 장바구니 -> 결제 완료 직접 접근 (결제 우회)
  3. 회원가입: 약관동의 -> 가입완료 직접 접근 (본인인증 우회)
  4. 레벨 제한: 레벨1 -> 레벨2 자료실 직접 접근 (권한 우회)

방어 원칙:

  • 각 단계마다 이전 단계 완료 여부를 세션에서 검증
  • 단순 URL 접근으로 중요 단계 우회 불가하도록 설계
  • 최종 단계에서 전체 프로세스 재검증
  • 민감 작업은 재인증(비밀번호 재입력) 필수

실습 2-2: 권한 레벨 미검증 - 고급 자료 무단 다운로드

목표: 레벨 1 사용자가 레벨 2 자료실 파일 다운로드

실습 환경:

  • 사용자 레벨: 1 (일반 회원)
  • 자료실 레벨: 2 (프리미엄 회원 전용)
  • 대상 파일: 대외비문서.docx

공격 방법:

  1. 레벨 2 자료실 목록 접근 시도 -> “권한 부족” 메시지 (목록 레벨 검증)
  2. 다운로드 URL 직접 입력
    • /board/lv2/download?file=대외비문서.docx -> 파일 다운로드 성공 (다운로드에 레벨 검증 없음)
  3. 게시글 ID 직접 접근
    • /board/view?id=123 -> 레벨 2 게시글 내용 노출

취약한 코드 구조 (download.php):

  • $_SESSION[‘user_id’] 로 로그인 여부만 확인
  • 사용자 레벨 확인 없이 파일명으로 바로 파일 경로 구성
  • readfile($filepath) 로 파일 전송

안전한 코드 구조 (download.php):

단계별 검증:

  1. 로그인 여부 확인: $_SESSION[‘user_id’] 존재 검증
  2. 사용자 레벨 확인: $_SESSION[‘user_level’] >= required_level 검증
  3. 파일 존재 여부: 파일명 대신 file_id (정수 ID) 사용 -> DB 조회
  4. 파일의 요구 레벨: DB에서 파일별 required_level 조회 후 비교
  5. 다운로드 로그 기록: logDownload($user_id, $file_id)
  6. 안전한 파일 경로: basename() 으로 파일명만 추출 후 전송

권한 검증이 필요한 시점:

  1. 페이지 접근 시 (list.jsp)
  2. 게시글 조회 시 (view.jsp)
  3. 파일 다운로드 시 (download.php)
  4. 수정/삭제 작업 시 (edit.jsp, delete.jsp)

방어 원칙:

  • 모든 요청에서 세션의 권한 레벨 확인
  • 파일 경로 대신 파일 ID 사용 (경로 추측 방지)
  • DB에 파일별 요구 레벨 저장
  • 권한 검증 실패 시 명확한 에러 메시지
  • 접근 시도 로그 기록 (감사 추적)

실습 3-1: Negative Injection - 음수 배팅 공격

목표: 배팅액을 음수로 설정하여 지면 오히려 돈이 증가하도록 조작

실습 환경:

  • 초기 보유금액: 1,000,000원
  • 배팅 시스템: 이기면 배팅액×2, 지면 배팅액 차감
  • 취약점: 음수 입력 검증 부재

정상 배팅 로직:

  • balance = 1,000,000, betting_amount = 10,000
  • 승리: balance + (betting_amount * 2) = 1,020,000
  • 패배: balance - betting_amount = 990,000

음수 배팅 공격:

  • betting_amount = -10,000,000 설정
  • 잔액 확인: 1,000,000 >= -10,000,000 -> True (조건 통과!)
  • 패배 시: balance - (-10,000,000) = 1,000,000 + 10,000,000 = 11,000,000 (1천만원 증가!)
  • 즉, 음수 빼기 = 더하기가 되어 지는 것이 오히려 이득

Burp Suite 요청 변조:

정상 요청:

  • POST /betting/place
  • amount=10000&game_id=123

변조 요청:

  • POST /betting/place
  • amount=-10000000&game_id=123

취약한 코드의 문제점:

  • int(request.form.get(‘amount’)) 로 음수 그대로 수용
  • 잔액 비교 시 양수 여부 검증 없음
  • else(패배) 분기에서 balance -= betting_amount -> 음수 빼기 = 더하기 발생

안전한 코드 구조:

입력 검증 단계:

  1. try/except 로 정수 파싱 예외 처리
  2. betting_amount <= 0 검증 -> 양수만 허용
  3. 최대 배팅액 제한 (MAX_BET = 1,000,000)
  4. 잔액 충분성 확인 (balance < betting_amount)

결과 검증:

  • 계산 후 balance < 0 이면 0으로 보정
  • 음수 잔액 발생 시 log_security_event() 로 보안 이벤트 기록

음수 입력 취약점이 발생하는 영역:

  1. 쇼핑몰 수량: count=-100 -> 총액이 음수로
  2. 포인트 사용: point=-10000 -> 포인트 증가
  3. 할인 쿠폰: discount=-5000 -> 가격 증가
  4. 송금: amount=-1000000 -> 받는 사람 돈 차감

방어 원칙:

  • 모든 금액/수량 입력값에 대해 0보다 큰지 검증
  • 최소값/최대값 범위 제한
  • 계산 결과도 검증 (음수 결과 차단)
  • 서버 측에서 재계산 (클라이언트 값 신뢰 금지)

실습 3-2: 파라미터 변조 - 작성자 조작 (쇼핑몰 12, 13번)

목표: 일반 사용자가 admin 계정으로 글 작성 및 삭제

실습 환경:

  • 문제 12: 일반 사용자로 공지사항 작성/삭제
  • 문제 13: 묻고답하기 게시판에 admin으로 변조하여 작성
  • 취약점: author 파라미터를 서버가 신뢰

공격 방법 (Burp Suite 요청 변조):

정상 요청:

  • POST /board/qna/write
  • title=문의사항&content=배송은 언제 오나요?&author=user1

변조 요청 (공지사항 작성):

  • POST /board/notice/write
  • title=긴급공지&content=시스템 점검&author=admin&board=notice
  • 결과: 일반 사용자가 admin 이름으로 공지사항 작성!

삭제 요청 변조:

  • POST /board/delete
  • id=123&author=admin
  • 서버가 author 파라미터를 신뢰하면 admin 글도 삭제 가능

취약한 코드의 문제점 (write_process.php):

  • $_POST[‘author’] 로 클라이언트에서 작성자 직접 수신
  • $_POST[‘board’] 로 게시판 종류도 클라이언트에서 수신
  • 게시판 종류별 권한 검증 없이 바로 INSERT

안전한 코드 구조 (write_process.php):

작성자 및 권한 처리:

  1. 작성자: $_SESSION[‘user_id’] 에서 가져오기 (파라미터 완전 무시)
  2. 권한 레벨: $_SESSION[‘user_level’] 에서 가져오기
  3. 허용 게시판 화이트리스트: $allowed_boards = [‘qna’, ‘free’]
  4. 관리자 레벨(9 이상)만 notice 게시판 추가
  5. in_array($board, $allowed_boards) 로 게시판 권한 검증
  6. Prepared Statement 로 안전하게 INSERT

안전한 코드 구조 (delete_process.php):

  1. intval($_POST[‘id’]) 로 게시글 ID 정수 변환
  2. $_SESSION[‘user_id’] 로 현재 사용자 확인
  3. DB에서 게시글의 실제 author 조회 (파라미터 author 무시)
  4. 본인 글이거나 관리자(레벨 9 이상)인 경우만 삭제 허용
  5. log_action() 으로 삭제 작업 로그 기록

파라미터 변조 방지 원칙:

  1. 사용자 정보는 세션에서만 가져오기 (author, user_id, user_level 등은 파라미터로 받지 않음)
  2. 권한 확인은 서버에서만 (클라이언트 JavaScript 검증은 우회 가능)
  3. 게시판/기능별 권한 매트릭스 구성
    • notice: 관리자만 작성
    • qna: 로그인 사용자
    • admin: 관리자만 접근
  4. 모든 파라미터 화이트리스트 기반 검증

방어 체크리스트:

  • author, role, level 등은 절대 파라미터로 받지 않음
  • 세션 정보와 DB 정보 교차 검증
  • 권한 변경 작업은 재인증 필수
  • 모든 중요 작업 로그 기록

3. 취약점 비교 분석

인증 vs 인가 차이

구분 인증 (Authentication) 인가 (Authorization) 실무 적용
정의 “당신은 누구인가?” “당신은 무엇을 할 수 있는가?” 둘 다 필수적으로 검증
검증 대상 신원 확인 (ID/PW) 권한 확인 (Role, Level) 각 요청마다 독립적으로 검증
예시 로그인 성공/실패 관리자 페이지 접근 가능/불가 로그인 후에도 매번 권한 확인
우회 시 영향 타인 계정 탈취 권한 없는 작업 수행 둘 다 치명적

파라미터 변조 유형별 비교

유형 변조 대상 공격 예시 영향 방어 방법
금액 조작 price, amount price=1 (실제 10000원) 재정 손실 서버에서 가격 재계산
수량 조작 count, quantity count=-100 음수 총액 양수 검증, 재고 확인
권한 조작 role, level, author author=admin 권한 탈취 세션에서만 가져오기
게시판 조작 board, category board=notice 공지사항 도배 화이트리스트 + 권한 검증

4. 심화 분석

Integer Overflow 공격 원리

구분 signed int unsigned int 오버플로우 결과
범위 -2,147,483,648 ~ 2,147,483,647 0 ~ 4,294,967,295 범위 초과 시 반전
최댓값+1 2,147,483,647 + 1 = -2,147,483,648 4,294,967,295 + 1 = 0 양수 -> 음수
최솟값-1 -2,147,483,648 - 1 = 2,147,483,647 0 - 1 = 4,294,967,295 음수 -> 양수
공격 활용 큰 양수 입력 -> 음수로 변환 - 가격/수량 검증 우회

Integer Overflow 공격 흐름:

  • price = 10,000, count = 2,147,483,647 (int 최대값) 입력
  • total = 10,000 * 2,147,483,647 = 21,474,836,470,000 (int 범위 초과)
  • Overflow 발생 -> total 이 음수로 변환
  • total < balance 조건 통과 -> 구매 성공
  • balance -= (음수) -> balance 증가!

쇼핑몰 가격 계산 취약점

취약한 구매 로직의 문제점:

  • 클라이언트에서 price 파라미터를 직접 수신
  • 음수 검증 없음
  • 오버플로우 검증 없음
  • total = price * count + SHIPPING_FEE 계산 후 잔액 비교만

공격 시나리오:

  1. 가격 조작: id=61&count=1&price=1 -> 10,000원 상품을 1원에 구매
  2. 수량 음수: id=61&count=-100&price=10000 -> total = -997,000 -> balance 증가
  3. Integer Overflow: id=61&count=2147483647&price=10000 -> overflow로 음수 변환

안전한 구매 로직 단계:

  1. count <= 0 검증 -> 양수만 허용
  2. MAX_COUNT = 1,000 으로 최대 수량 제한
  3. DB에서 price 조회 (클라이언트 price 값 완전 무시)
  4. 재고 확인: count > stock 이면 거부
  5. 오버플로우 방지: price * count > 2^31 - 1 이면 거부
  6. 총액 계산 서버에서만 수행 (subtotal + shipping_fee)
  7. 잔액 확인: total > balance 이면 거부
  8. 트랜잭션 처리: 재고 차감 + 잔액 차감 + 주문 기록 원자적 실행

5. 실무/보안 적용

보안 전문가 관점 - 파일 업로드 탐지 및 대응

단계/유형 탐지 포인트 로그 예시 대응 방안
WebShell 업로드 - jsp, jspx, php 등 실행 파일 업로드
- Runtime.exec(), system() 패턴
- Base64 인코딩된 악성 코드
POST /upload with test.jsp - 파일 내용 검사 (시그니처 기반)
- 업로드 디렉토리 실행 권한 제거
- 웹 루트 외부 배치
직접 접근 시도 - /files/, /upload/ 경로 직접 요청
- UUID 파일명 스캔 패턴
- 날짜 기반 경로 추측
GET /files/qna/20260206/*.jsp - 디렉토리 인덱싱 비활성화
- URL 접근 제어
- 다운로드는 download.php 경유만
확장자 우회 - .jspx, .jspf, .php5 등 변형
- 더블 확장자 (.jsp.txt)
- 대소문자 변형 (.Jsp)
Upload test.jspx - 화이트리스트 방식
- Magic Number 검증
- 모든 변형 확장자 차단

인증/인가 방어 체크리스트

Django 기반 권한 검증 데코레이터 구조:

  • require_level(min_level) 데코레이터로 뷰 함수에 레벨 검증 적용
  • 1단계: request.user.is_authenticated 로 로그인 확인 -> 미로그인 시 /login 리다이렉트
  • 2단계: request.user.level 과 min_level 비교 -> 레벨 부족 시 log_security_event() + 403 Forbidden

사용 예시:

  • @require_level(2) -> 레벨 2 이상만 프리미엄 파일 다운로드
  • @require_level(9) -> 관리자(레벨 9)만 admin 패널 접근

PHP 프로세스 검증 예시 (view_secret_post.php):

단계별 검증:

  1. $_SESSION[‘user_id’] 로 로그인 확인
  2. DB에서 게시글 정보 조회
  3. 비밀글인 경우 $SESSION[“verified_post” . $post_id] 존재 여부 확인
  4. 인증 시각 확인: time() - $verified_time > 300 이면 만료 (5분 제한)
  5. 권한 확인: 본인 글이거나 레벨 9 이상(관리자)만 허용
  6. incrementViewCount() 로 조회수 증가 후 내용 표시
  7. unset($_SESSION[$verified_key]) 로 일회성 인증 제거

파라미터 변조 방어 설정

Node.js Express 파라미터 검증 미들웨어 구조:

  • express-validator 로 입력값 타입/범위 검증
  • body(‘product_id’).isInt({ min: 1 }) -> 양의 정수 검증
  • body(‘count’).isInt({ min: 1, max: 1000 }) -> 1~1,000 범위 검증
  • validationResult(req) 로 검증 실패 시 400 반환

처리 흐름:

  1. req.session.userId 로 사용자 ID 세션에서 가져오기
  2. DB에서 가격/재고 조회 (클라이언트 price 무시)
  3. 재고 부족 시 400 반환
  4. 총액 서버에서 계산 (subtotal + shippingFee)
  5. Number.MAX_SAFE_INTEGER 초과 시 400 반환
  6. 잔액 확인 후 부족 시 400 반환
  7. beginTransaction() -> 재고/잔액 차감 + 주문 기록 -> commit()
  8. 예외 시 rollback() + 500 반환

종합 보안 점검 체크리스트

파일 업로드 보안:

  • 확장자 화이트리스트 적용 (jpg, png, pdf만)
  • 파일 내용 Magic Number 검증
  • 업로드 디렉토리 스크립트 실행 권한 제거
  • 업로드 디렉토리를 웹 루트 외부에 배치
  • 파일명 UUID로 무작위화
  • 파일 크기 제한
  • 바이러스 스캔 수행

인증/인가 보안:

  • 모든 페이지에서 로그인 여부 확인
  • 각 작업마다 권한 레벨 검증
  • 프로세스 각 단계마다 이전 단계 완료 여부 확인
  • 세션에 인증 상태 저장
  • 중요 작업은 재인증 필수
  • 권한 변경 시도 로그 기록

파라미터 변조 방지:

  • 모든 금액/수량 입력값 양수 검증
  • 가격은 서버에서 DB 조회 (클라이언트 값 무시)
  • author, role, level은 세션에서만 가져오기
  • Integer Overflow 방지 (범위 검증)
  • 최소값/최대값 제한
  • 화이트리스트 기반 파라미터 검증

6. 배운 점 및 인사이트

새로 알게 된 점

  • 파일명 무작위화의 한계: UUID로 파일명을 변경해도 download.jsp의 파라미터에서 실제 경로가 노출되면 무의미하다. 파일 업로드 보안은 파일명 변경만으로는 부족하며, 실행 권한 제거와 웹 루트 외부 배치가 필수다.

  • .jspx 확장자의 위험성: .jsp만 차단하면 .jspx, .jspf 등 다른 JSP 실행 가능 확장자로 우회할 수 있다. 블랙리스트 방식의 확장자 필터링은 항상 우회 가능성이 있으며, 화이트리스트 방식이 안전하다.

  • 프로세스 검증 누락의 심각성: 비밀글 비밀번호 확인 단계를 건너뛰고 view.jsp에 직접 접근하는 것처럼, 각 단계마다 독립적인 검증이 없으면 중요 단계를 우회할 수 있다. 은행 이체의 5단계 인증도 마지막 단계에서 이전 단계들을 재검증하지 않으면 무의미하다.

  • Negative Injection의 교묘함: 배팅액을 음수로 설정하면 지는 것이 오히려 이득이 되는 상황이 발생한다. balance - (-10,000,000) = balance + 10,000,000 처럼 음수 빼기가 더하기로 작용하는 것을 악용한 공격이다.

  • 클라이언트 파라미터의 위험성: author, price, role 등 중요한 값을 클라이언트 파라미터로 받으면 Burp Suite로 쉽게 변조할 수 있다. 이런 값들은 반드시 세션이나 DB에서 가져와야 하며, 클라이언트 입력을 절대 신뢰하면 안 된다.

이전 학습과의 연결고리

  • Path Traversal과 파일 업로드 연계: 이전에 학습한 Path Traversal로 시스템 파일을 읽었다면, 파일 업로드 취약점으로 WebShell을 올려 시스템을 완전히 장악할 수 있다. 두 취약점은 파일 시스템 접근이라는 공통점이 있다.

  • CSRF와 파라미터 변조: CSRF로 사용자를 속여 요청을 보내게 한 후, 파라미터 변조로 금액이나 권한을 조작하는 연계 공격이 가능하다. 두 취약점 모두 서버가 클라이언트 요청을 신뢰하는 것을 악용한다.

  • SQL Injection과 인증 우회: 이전 SQL Injection에서 OR 1=1 로 인증을 우회했다면, 이번에는 프로세스 검증 누락으로 중간 단계를 건너뛰는 방식이다. 목적은 같지만 공격 벡터가 다르다.

실무 적용 아이디어

보안 전문가 관점:

  • WebShell 탐지 시그니처 작성: Suricata/Snort에서 Runtime.getRuntime().exec(), eval(), system() 등 위험한 함수 호출 패턴을 탐지하는 룰 작성. 업로드된 파일에서 이런 패턴이 발견되면 즉시 차단 및 알림.

  • 파라미터 변조 이상 징후 탐지: SIEM에서 동일 사용자가 짧은 시간 내 author 파라미터를 여러 번 변경하거나, 음수 금액/수량을 입력하는 패턴 탐지. 이는 공격 시도의 명확한 신호다.

  • 권한 상승 시도 모니터링: 일반 사용자가 admin 게시판에 글을 작성하거나, 레벨 1이 레벨 2 자료를 다운로드하는 시도를 실시간 탐지하여 알림. 권한 검증 실패 로그를 집중 모니터링.

개발자 관점:

  • 파일 업로드 보안 강화: 업로드 디렉토리는 /var/uploads 같이 웹 루트 완전 외부에 배치하고, 다운로드는 download.php를 경유하여 파일 내용을 스트리밍. 절대 직접 접근 불가하도록 설계.

  • 모든 중요 값은 서버에서 결정: 가격, 작성자, 권한 등은 클라이언트 파라미터를 완전히 무시하고 서버의 세션과 DB에서만 가져오기. 클라이언트는 ID만 전송하고, 나머지는 서버가 조회.

  • 프로세스 상태 관리: 은행 이체처럼 여러 단계를 거치는 작업은 세션에 step_completed 배열을 유지하여 각 단계 완료 여부를 추적. 최종 단계에서 모든 단계 완료를 재검증.


7. Quick Reference

파일 업로드 WebShell 핵심 구조

JSP WebShell 핵심 요소:

  • request.getParameter(“cmd”) -> 명령어 입력 수신
  • Runtime.getRuntime().exec(cmd) -> OS 명령 실행
  • BufferedReader(InputStreamReader(p.getInputStream())) -> 출력 읽기
  • out.println(line + “br 태그”) -> 결과 HTML 출력

JSPX WebShell 핵심 요소:

  • jsp:root + xmlns:jsp 선언
  • jsp:directive.page import 로 java.io.* 임포트
  • jsp:scriptlet 안에 Java 코드 삽입

PHP WebShell 최소 형태:

  • system($_GET[‘cmd’]) -> GET 파라미터로 명령 실행
  • eval($_POST[‘code’]) -> POST로 임의 PHP 코드 실행

파라미터 변조 공격 패턴

금액 조작:

  • POST /purchase
  • price=1&count=1 (실제 10,000원 상품을 1원에 구매)

수량 음수:

  • POST /purchase
  • product_id=61&count=-100 (총액이 음수로 되어 잔액 증가)

작성자 변조:

  • POST /board/write
  • title=공지&content=내용&author=admin&board=notice (일반 사용자가 admin으로 공지사항 작성)

권한 변조:

  • POST /updateAuth
  • loginId=user1&role=admin&level=9 (일반 사용자를 관리자로 변경)

보안 검증 체크리스트

구분 검증 항목 검증 방법 실패 시 조치
파일 업로드 확장자, Magic Number, 크기 화이트리스트, 파일 헤더 검사 업로드 거부, 로그 기록
인증 로그인 여부, 세션 유효성 세션 존재 확인, 타임아웃 검증 로그인 페이지 리다이렉트
인가 권한 레벨, 본인 여부 DB 레벨 조회, 작성자 확인 403 Forbidden, 접근 거부
파라미터 타입, 범위, 음수 여부 양수 검증, 최소/최대값 확인 400 Bad Request, 거부

안전한 코드 패턴

파일 다운로드 안전 코드 구조:

  1. DB에서 파일 정보 조회 (파일 경로 대신 file_id 로 조회)
  2. get_user_level(user_id) 로 사용자 레벨 확인 후 required_level 비교
  3. 웹 루트 외부 경로: /var/uploads 에서 파일 제공
  4. os.path.realpath(filepath).startswith(’/var/uploads’) 로 Path Traversal 방지
  5. log_download(user_id, file_id) 로 다운로드 로그 기록

구매 처리 안전 코드 구조:

  1. count <= 0 or count > 1,000 -> 수량 범위 검증
  2. DB에서 price, stock 조회 (클라이언트 price 무시!)
  3. count > stock -> 재고 부족 거부
  4. total = price * count -> 서버에서만 계산
  5. total > MAX_SAFE_INT -> 오버플로우 방지
  6. with db.transaction() -> 재고 차감 + 잔액 차감 + 주문 기록 원자적 처리

8. 트러블슈팅

문제 원인 해결 방법
WebShell 업로드 후 404 에러 실제 저장 경로와 접근 경로 불일치 - download.jsp 파라미터에서 정확한 경로 확인
- /data/files/, /files/ 등 여러 경로 시도
- 웹 서버 설정에서 Alias 확인
.jsp 필터링으로 업로드 차단됨 블랙리스트에 .jsp만 등록 - .jspx, .jspf, .jsv 등 변형 확장자 시도
- 대소문자 변형 (.Jsp, .JSP)
- Null Byte: .jsp%00.jpg
비밀글을 직접 접근해도 보임 view.jsp에서 비밀번호 확인 여부 미검증 - 세션에 verified_post_{id} 저장
- view.jsp에서 세션 확인
- 미인증 시 checkpw.jsp로 리다이렉트
일반 사용자가 admin 글 작성 가능 author 파라미터를 서버가 신뢰 - author는 파라미터로 받지 않고 세션에서 가져오기
- $author = $_SESSION[‘user_id’]
- 파라미터 author는 완전히 무시
음수 금액으로 돈 증가 음수 입력 검증 부재 - amount <= 0 이면 거부
- 최소값 검증: amount > 0 필수
- 계산 결과도 검증: total < 0 이면 차단

Today’s Insight:

파일 업로드, 인증/인가, 파라미터 변조는 모두 서버가 클라이언트 입력을 신뢰하는 것에서 발생하는 취약점이다. 파일 업로드에서는 확장자와 파일명을, 인증/인가에서는 권한과 프로세스 단계를, 파라미터 변조에서는 가격과 수량을 클라이언트가 제공한다고 믿는 순간 공격에 노출된다. 보안의 핵심 원칙은 “클라이언트를 절대 신뢰하지 말라"는 것이다. 모든 중요한 값은 서버의 세션과 DB에서만 가져오고, 클라이언트는 단지 ID나 선택 사항만 전달하도록 설계해야 한다. 또한 각 단계, 각 요청마다 독립적으로 권한과 프로세스를 검증하여 어떤 단계도 건너뛸 수 없도록 해야 한다. 프론트엔드 검증은 사용자 편의를 위한 것일 뿐, 백엔드에서 모든 검증을 다시 수행하는 것이 필수다.