10 분 소요

모의해킹 학습 노트: 파일 업로드 심화, 파라미터 변조, JavaScript 분석


1. 파일 업로드 취약점 심화

1.1 공격 시나리오 전체 흐름

파일 업로드 취약점의 궁극적 목표는 단순히 파일을 올리는 것이 아니라, 서버에서 명령어를 실행하는 것이다.

웹셸 업로드 → 실행 경로 찾기 → 명령어 실행 → 서버 내부 탐색 → 소스코드/중요 정보 탈취

1.2 파일 선택 시점에 업로드되는 케이스

일반적으로 파일은 “등록” 버튼을 누를 때 서버로 전송된다고 생각하지만, 파일을 선택하는 순간 바로 업로드되는 경우가 있다. Burp Suite 인터셉트를 켜고 파일을 선택했을 때 요청이 날아가는지 확인해야 한다.

1.3 서버 저장 시 랜덤 파일명 생성

서버가 업로드된 파일의 이름을 랜덤으로 바꿔서 저장하는 경우, 웹셸을 업로드하더라도 실행할 수 있는 경로를 찾아야 한다.

경로 찾기 방법:

  • 업로드 응답에서 파일 경로 확인
  • 게시판에 첨부된 이미지를 우클릭 → “이미지 주소 복사”
  • 소스코드 분석으로 저장 경로 유추
  • 디렉토리 구조 유추 (예: /upload/, /files/, /attachments/)

1.4 JSP vs JSPX

구분 JSP JSPX
문법 <% java code %> XML 태그 형식 <jsp:scriptlet>
용도 일반적인 Java 웹 페이지 JSP의 XML 기반 대체
우회 활용 .jsp 확장자 필터링 시 .jspx로 우회

웹셸 핵심 코드:

Runtime.getRuntime().exec(request.getParameter("cmd"))

1.5 확장자 필터링 우회

.jsp 확장자가 차단될 때 시도할 수 있는 확장자:

  • .jspx — XML 기반 JSP
  • .jsp3, .jspv — 서버 설정에 따라 실행될 수 있는 변형
  • 대소문자 혼용: .JsP, .JSP

1.6 취약점 진단 vs 모의해킹 관점

관점 판단 기준
취약점 진단 위험 확장자 파일이 업로드 가능하면 취약 (실행 여부 무관)
모의해킹 업로드 + 실행까지 성공해야 공격 성공

진단 시에는 .jsp 파일이 올라가기만 해도 취약점으로 보고한다. 실행 권한이 없더라도 추후 다른 취약점과 체이닝될 수 있기 때문이다.

1.7 대응 방안

  • 백엔드에서 확장자 검증 (화이트리스트 방식, 프론트엔드만으로 불충분)
  • 업로드 디렉토리 실행 권한 제거
  • 파일명 랜덤화 + 원본 파일명은 DB에 저장
  • 불필요한 개발 파일 삭제 (.bak, .old, test.php 등)

2. 정보 노출과 파라미터 변조

2.1 정보 수집 방법

파라미터 변조의 핵심은 “어디에 뭘 넣을 수 있는가”를 파악하는 것이다. 이를 위해 정보 수집이 선행되어야 한다.

방법 찾을 수 있는 것
소스코드 보기 (Ctrl+U) 주석, 숨겨진 링크, 사용하지 않는 함수
개발자 도구 (Elements) hidden 필드, name 속성, disabled 속성
URL 패턴 유추 /qna/write → /notice/write, /admin
불필요한 파일 존재 .bak, .old, .txt → 백엔드 소스코드 노출

2.2 인증(Authentication) vs 인가(Authorization)

구분 정의 예시
인증 누구인지 확인 (로그인 여부) 로그인 안 한 사용자 → 로그인 페이지로 이동
인가 무엇을 할 수 있는지 확인 (권한 레벨) 일반 사용자 → 관리자 기능 접근 차단

핵심: 프론트엔드에서 버튼을 숨기는 것은 UX 처리일 뿐이다. 백엔드에서 세션의 권한을 확인하지 않으면, URL을 직접 입력하여 접근할 수 있다.

2.3 프로세스 검증 누락

비밀번호 찾기 같은 다단계 절차에서 중간 단계를 건너뛰어도 최종 단계가 실행되면 취약하다.

[정상 흐름]
아이디 입력 → 본인 인증 → 비밀번호 찾기 질문 → 새 비밀번호 등록

[공격 흐름]
아이디 입력 → (건너뛰기) → (건너뛰기) → 새 비밀번호 등록

대응: 각 단계마다 세션에 “이전 단계를 통과했는지” 저장하고, 최종 단계에서 모든 이전 단계의 검증 여부를 확인해야 한다.

2.4 비밀글 읽기 (파라미터 변조 기본)

게시판에서 비밀글의 클릭을 프론트엔드에서 막아놓은 경우:

정상: /board/view?id=1  (공개글 → 읽기 가능)
정상: /board/view?id=3  (공개글 → 읽기 가능)
공격: /board/view?id=2  (비밀글 → URL 직접 입력으로 접근)

프론트엔드에서 클릭을 막았을 뿐, 백엔드에서 해당 글의 작성자와 요청자가 일치하는지 확인하지 않는다면 그대로 노출된다.

2.5 관리자 페이지 URL 유추

별도의 공격 기법 없이 URL을 직접 입력해서 시도한다:

/admin
/manager
/administrator
/admin/login
/notice/write  (QnA에만 글쓰기가 있을 때)

강사 코멘트: “이거 다짜고짜 그냥 뒤에다가 주소를 쳐보는 겁니다. 상상력과 눈치만 있으면 됩니다.”


3. 데이터 평문 전송과 응답 변조

3.1 평문 전송 논란

HTTPS 환경에서 Burp Suite에 비밀번호가 보이는 것은 평문 전송이 아니다. 본인이 설치한 프록시에서 복호화된 내용을 보는 것이므로, 제3자가 볼 수 있는 것은 아니다.

환경 평문 전송 여부 취약 여부
HTTP 통신 평문 무조건 취약 (이유 불문)
HTTPS + Burp에서 보임 아님 (프록시 복호화) 일반적으로 안전
HTTPS + 파라미터 암호화 이중 보호 금융권 등에서 권장

주의: 금융권에서는 HTTPS임에도 추가적으로 JavaScript로 비밀번호를 암호화하여 전송한다. 이는 PC방 등에서 개발자 도구로 파라미터를 볼 수 있는 상황에 대비한 것이다.

3.2 응답 변조

프론트엔드에서 로그인 성공/실패를 분기하는 경우, 응답을 변조하여 우회할 수 있다.

[정상 응답]
{ "result": "N" }  → 프론트엔드: 로그인 실패 메시지

[변조 응답]
{ "result": "Y" }  → 프론트엔드: 메인 페이지로 이동

핵심: 응답 변조 자체가 취약점이 아니라, 백엔드에서 세션 검증을 하지 않는 것이 취약점이다. 응답을 Y로 바꿔서 메인 페이지로 이동했는데, 세션에 로그인 정보가 없으면 결국 아무것도 못 한다. 하지만 세션 검증이 없으면 실제로 로그인된 것처럼 동작한다.


4. 쇼핑몰 금액 변조 공격

4.1 Integer Overflow

상품 개수를 극단적으로 큰 값(예: 2조 개)으로 입력하면, 정수 오버플로우가 발생하여 총 금액이 음수로 전환될 수 있다.

4.2 배송비 음수

정상: shipmentfee=3000   → 총액 = 상품가 + 3000
공격: shipmentfee=-2759900 → 총액 = 2,760,000 - 2,759,900 = 100원

4.3 포인트 조작

보유 포인트보다 큰 값을 usepoint 파라미터에 입력:

보유 포인트: 2,000
공격: usepoint=2762900  → 총액 = 2,763,000 - 2,762,900 = 100원

4.4 totalprice 직접 조작

결제 단계에서 totalprice 파라미터를 서버가 그대로 신뢰하는 경우:

정상: /payment?orderlistid=51815&totalprice=2763000&payment=examplepay
공격: /payment?orderlistid=51815&totalprice=100&payment=examplepay

주문 생성 시에는 서버에서 가격을 재계산하지만, 결제(payment) 단계에서 클라이언트 파라미터를 그대로 신뢰하는 것이 문제다.

4.5 없는 파라미터 삽입

서버 코드에서 setpoint라는 파라미터를 처리하는 로직이 있지만, 프론트엔드에는 해당 입력 필드가 없는 경우:

정상 요청: _csrf=xxx&password=&email=xxx&phone=xxx
변조 요청: _csrf=xxx&password=&email=xxx&phone=xxx&setpoint=99999

개발자 도구에서 hidden 필드를 추가하거나, Burp Suite에서 파라미터를 직접 추가하면 된다.

4.6 HTML 변조 방법

개발자 도구 → Elements 탭에서:

  • Edit as HTML → 직접 HTML 코드 수정
  • readonly 속성 삭제 → 입력 가능하게 변경
  • name 속성 추가/변경 → 서버로 전송되는 파라미터명 조작
  • hidden 필드의 value 수정

4.7 금액 변조 대응 방안

  • 사용자 입력값으로 금액을 결정하지 않는다
  • 서버에서 DB 조회 → 단가 × 수량으로 직접 계산
  • 배송비, 포인트, 할인 등 모두 서버에서 계산
  • 음수 검증, 범위 검증 필수
  • 결제 금액은 세션 또는 서버 DB에서 관리하고 클라이언트 파라미터를 신뢰하지 않는다

5. JavaScript 분석 기법

5.1 실행 시점 이해

HTML은 위에서 아래로 파싱되며, JavaScript 코드가 참조하는 요소보다 위에 위치하면 해당 요소를 찾지 못해 에러가 발생한다.

<!-- ❌ 에러: btn 요소가 아직 로드되지 않음 -->
<script>
  document.getElementById("btn").addEventListener("click", function() { ... });
</script>
<button id="btn">클릭</button>

<!-- ✅ 정상: btn 요소가 먼저 로드됨 -->
<button id="btn">클릭</button>
<script>
  document.getElementById("btn").addEventListener("click", function() { ... });
</script>

<!-- ✅ 정상: 로드 완료 후 실행 -->
<script>
  window.onload = function() {
    document.getElementById("btn").addEventListener("click", function() { ... });
  };
</script>
<button id="btn">클릭</button>

보안 분석 관점: JavaScript가 외부 파일(.js)로 분리되어 있으면, 해당 파일을 찾아서 분석해야 한다. <script src="practice.js"> 태그에서 경로를 확인한다.

5.2 분석 도구와 기법

이벤트 리스너 브레이크포인트

개발자 도구 → Sources → Event Listener Breakpoints에서:

  • Mouse → click 체크 → 버튼 클릭 시 해당 이벤트 핸들러에서 디버깅 시작
  • Keyboard → keydown 체크 → 키보드 입력 시 중단

전체 파일 검색

Ctrl + Shift + F로 모든 소스 파일에서 키워드 검색. 에러 메시지, 함수명, 파라미터명 등을 검색하면 관련 코드를 빠르게 찾을 수 있다.

팁: 알림 메시지가 화면에 표시되면, 해당 메시지 텍스트를 그대로 검색하면 관련 코드를 찾을 수 있다.

예: "쿠폰 번호 길이가 맞지 않습니다" → 검색 → 검증 로직 발견

콘솔에서 함수 직접 호출

페이지에 정의된 JavaScript 함수를 콘솔에서 직접 호출할 수 있다.

// 페이지에 noticeWrite('Y') 함수가 있다면
noticeWrite('Y')

// 암호화 함수 직접 호출
encryptString("0")

// 특정 암호화 결과 확인
encryptMemberid("admin")

5.3 요소 ID/Name에서 힌트 얻기

HTML 요소에 id나 name이 붙어 있다면, JavaScript에서 해당 요소를 제어하는 코드가 존재한다는 의미다.

<button id="submitBtn">제출</button>

→ 소스 전체에서 submitBtn으로 검색하면 관련 이벤트 핸들러를 찾을 수 있다.

거꾸로, 이벤트 핸들러 속성(onclick, onsubmit 등)이 직접 태그에 있다면 해당 함수를 추적한다.


6. JavaScript 필터링 우회

6.1 비밀번호 검증 우회 (쇼핑몰 5번)

프론트엔드에서만 비밀번호 복잡도를 검증하고, 백엔드에서는 검증하지 않는 경우.

정상 흐름:

  1. 비밀번호 입력
  2. JavaScript 정규표현식으로 8자 이상, 대소문자+숫자+특수문자 검증
  3. 통과하면 encryptString() 함수로 암호화
  4. 암호화된 값을 서버로 전송

공격 방법 1 — Burp Suite 응답 변조:

  1. JavaScript 파일이 로드되는 시점을 인터셉트
  2. if (정규식.test(password)) 부분을 삭제하거나 조건을 반전
  3. Forward하여 변조된 JS가 브라우저에 적용됨

공격 방법 2 — 콘솔에서 직접 암호화:

// 페이지에 있는 encryptString 함수를 직접 호출
encryptString("0")
// 결과: U2FsdGVkX1/x16S+TcLYdcWB4/w7euL4eYllBuJ3cOo=

// 이 값을 Burp Suite에서 password 파라미터에 직접 넣기
// (URL 인코딩 필요: + → %2b, = → %3d)

6.2 파일 업로드 JS 필터링 우회

프론트엔드에서 확장자를 JavaScript로 검증하는 경우:

// 원본 코드
if (!/\.(gif|jpg|jpeg|png)$/i.test(filename)) {
    alert("gif, jpg 파일만 선택해주세요");
    return;
}

우회 방법:

  1. Burp Suite에서 JS 파일 인터셉트 (인터셉트 룰 설정 필요)
  2. if 조건의 느낌표(!) 삭제 → 조건 반전 (허용 확장자일 때만 차단)
  3. 또는 if문 전체 삭제

7. Burp Suite 인터셉트 고급 설정

7.1 JS 파일 인터셉트 설정

기본 설정에서는 JS, CSS, 이미지 파일이 인터셉트에서 제외되어 있다.

설정 방법:

  1. Proxy → Settings → Intercept Client Requests
  2. 기본 규칙의 |js$|css$|png$|... 부분 삭제
  3. → JS 파일도 인터셉트에 잡히게 됨

7.2 캐시 방지 (Match & Replace)

같은 JS 파일을 반복 요청하면 브라우저가 캐싱하여 서버에 요청을 보내지 않는다.

Match & Replace 설정:

유형 Match Replace 설명
Response header If-Modified-Since (빈 값) 조건부 요청 제거
Response header If-None-Match (빈 값) ETag 캐시 제거

이렇게 하면 매번 서버에서 새로 JS 파일을 받아오므로, 변조가 매번 적용된다.

7.3 응답 인터셉트 설정

요청뿐 아니라 응답도 인터셉트해야 JS 파일을 변조할 수 있다.

  1. Proxy → Settings → Intercept Server Responses
  2. “Intercept responses based on the following rules” 활성화
  3. 규칙 추가 또는 기존 규칙 조정

8. strcmp 취약점

8.1 PHP strcmp + 느슨한 비교

if (strcmp($_POST['login_id'], 'admin@example.com') == 0 &&
    strcmp($_POST['login_pwd'], 'random_password') == 0)
{
    // 로그인 성공
    $_SESSION['login_id'] = $_POST['login_id'];
}

8.2 취약점 원리

strcmp의 동작:

  • 두 문자열이 같으면 0 반환
  • 다르면 양수 또는 음수 반환

PHP 5.3 이상에서의 문제:

  • strcmp(문자열, 배열) → NULL 반환 (에러이지만 값은 NULL)
  • 느슨한 비교(==)에서 NULL == 0 → true
strcmp("password", array()) → NULL
NULL == 0 → true  (느슨한 비교)
NULL === 0 → false (엄격한 비교)

8.3 공격 방법

파라미터 이름 뒤에 []를 붙여서 배열로 전송:

정상: login_id=admin@example.com&login_pwd=password
공격: login_id=admin@example.com&login_pwd[]=anything

login_pwd[]로 보내면 PHP에서 배열로 인식하고, strcmp는 NULL을 반환하여 로그인이 성공한다.

8.4 대응 방안

// ❌ 느슨한 비교 (취약)
if (strcmp($input, $expected) == 0)

// ✅ 엄격한 비교 (안전)
if (strcmp($input, $expected) === 0)

실무 참고: 실제 운영 환경에서는 이런 방식으로 로그인을 구현하지 않는다. 비밀번호는 해시화하여 DB에 저장하고, password_verify() 등의 함수로 비교한다. strcmp 취약점은 개념적으로 알아두면 되는 수준.


9. 비밀번호 찾기 인가 우회

9.1 공격 시나리오

비밀번호 찾기 기능에서 클라이언트 측 암호화에 의존하여 사용자를 식별하는 경우.

[정상 흐름]
1. 본인 계정으로 비밀번호 찾기 진행
2. 질문/답변 확인
3. 새 비밀번호 등록 페이지 도달
4. JavaScript가 memberid를 암호화 → encryptid 파라미터 생성
5. 본인의 비밀번호가 변경됨

[공격 흐름]
1~3. 동일
4. encryptid를 "admin"을 암호화한 값으로 변조
5. admin의 비밀번호가 변경됨

9.2 난독화된 JavaScript 분석

/js/practice04.js 파일이 JSFuck 스타일로 난독화되어 있었지만, 디코딩하면:

function encryptMemberid(memberid) {
    var passphrase = "examplekim";
    return CryptoJS.AES.encrypt(memberid, passphrase).toString();
}

암호화 키가 클라이언트에 노출되어 있으므로, 누구나 임의의 memberid를 암호화할 수 있다.

9.3 공격 방법

방법 1 — 콘솔에서 암호화 값 생성 후 hidden 필드 변조:

// admin을 암호화
CryptoJS.AES.encrypt('admin', 'examplekim').toString()
// 결과 예시: U2FsdGVkX18GRDol3Wx1dy8Yy4unjH0NbQ5lZBHMcp0=

// hidden 필드 변조
$('input[name="encryptid"]').val('U2FsdGVkX18GRDol3Wx1dy8Yy4unjH0NbQ5lZBHMcp0=');

방법 2 — Burp Suite에서 응답 변조: 비밀번호 찾기 답변 입력 후, 다음 페이지로 넘어가는 응답에서 memberid 값을 admin으로 변경. JavaScript가 admin을 암호화하여 자동으로 전송한다.

9.4 대응 방안

  • 비밀번호 변경 대상은 세션에 저장된 인증된 사용자 ID만 사용
  • 클라이언트 파라미터(encryptid)를 신뢰하지 않음
  • 암호화 키는 서버에만 보관
  • 비밀번호 찾기 각 단계에서 일회용 토큰 발급 및 검증

10. E2E 암호화 우회

10.1 정상 흐름

폼 데이터 입력 → JavaScript가 AES 암호화 → 암호화된 데이터 서버 전송 → 서버에서 복호화

10.2 공격 원리

E2E 암호화는 전송 구간을 보호하는 것이지, 암호화 전 데이터 조작을 막지는 못한다. 암호화 함수가 호출되기 직전에 데이터를 변조하면, 변조된 데이터가 암호화되어 서버로 전송된다.

10.3 Match & Replace를 이용한 공격

Burp Suite의 Match & Replace 기능으로 JavaScript 코드를 변조:

설정 값
Type Response body
Match e2edatasend(param)
Replace param = prompt("", param); e2edatasend(param)

결과:

  1. 주문하기 버튼 클릭
  2. 암호화 함수 호출 직전에 prompt 창이 표시됨
  3. totalprice=2763000을 totalprice=100으로 수정
  4. OK 클릭 → 수정된 데이터가 AES 암호화되어 서버로 전송
  5. 서버는 복호화 후 100원으로 처리

10.4 대응 방안

  • E2E 암호화는 전송 구간 보호 용도, 서버에서 복호화 후 반드시 재검증
  • 상품 ID + 수량만 받고 서버에서 금액을 직접 계산
  • 클라이언트에서 전송된 금액 파라미터를 신뢰하지 않음

11. BeautifulSoup 크롤링 (실무 활용)

보안 업무에서 보안뉴스 헤드라인 자동 수집 등에 활용한다.

import requests
from bs4 import BeautifulSoup

response = requests.get('https://www.boannews.com')
soup = BeautifulSoup(response.text, 'html.parser')
titles = soup.select('.news_title')  # CSS 셀렉터로 선택
for title in titles:
    print(title.get_text())

개발자 도구 활용 팁:

  1. 수집하고 싶은 요소를 개발자 도구로 선택
  2. 우클릭 → Copy → Copy selector
  3. 복사된 CSS 셀렉터를 soup.select()에 사용

12. 핵심 원칙 정리

  1. 프론트엔드 검증은 UX용, 백엔드 검증이 진짜 보안이다
  2. 사용자 입력값을 절대 신뢰하지 않는다 — 금액, 권한, ID 모두 서버에서 검증
  3. 금액 관련은 서버에서만 계산/결정한다 — 클라이언트 파라미터(totalprice, shipmentfee 등)를 그대로 사용하지 않는다
  4. 모든 단계마다 인증/인가를 검증한다 — 프로세스 중간 단계를 건너뛸 수 없도록
  5. 암호화 키는 서버에 보관한다 — JavaScript에 키를 노출하면 의미 없음
  6. 불필요한 파일/기능은 제거한다 — .bak, .old, test 파일 등
  7. 에러 메시지에 민감 정보를 노출하지 않는다
  8. 파라미터 변조는 결국 인증/인가 취약점이다 — 보고서에서도 이 관점으로 분류

13. 면접 대비 포인트

파라미터 변조 관련

  • “파라미터 변조”는 공격 기법이지 취약점 분류가 아님 → 인증/인가 취약점으로 분류
  • 대응의 핵심은 “서버에서 모든 것을 검증”하는 것

JavaScript 분석 관련

  • 프론트엔드 코드는 모두 공격자가 볼 수 있고 변조할 수 있음
  • JavaScript 난독화는 시간을 벌어줄 뿐 근본적 보안 대책이 아님
  • E2E 암호화도 암호화 전 단계에서 변조 가능

실무 관점

  • 개발자와의 논쟁: “프론트에서 막았는데 왜 취약?” → 백엔드 검증 필수 설명
  • “실행 안 되는데 왜 취약?” → 공격 대상 확대 가능성, 체이닝 가능성
  • 보고서 작성 시 시나리오 기반 설명 + 구체적 대응 코드 예시 포함

댓글 남기기