실습 환경
claude code를 이용해 Nginx + Mysql + PHP 8.3으로 구성된 파일 업로드 기능을 가진 웹 서버를 구성한다.
실습
파일을 업로드 할 수 있는 웹 애플리케이션이다.


원격 명령 실행이 가능한 웹 셸 코드를 작성한다. 현재 기술 스택으로는 PHP를 사용하고 있으므로, 악성 스크립트를 PHP로 작성한다.

해당 파일을 [저장] → [업로드] → [접근] 하고, 명령을 실행한다. 실습 환경에서는 웹 서버에 연결된 데이터베이스 정보를 조회해보겠다.

이렇게 소스 코드를 유출하거나, 권한 상승 취약점 등 다른 공격과 연계하여 더 큰 피해를 줄 수도 있다.
취약점이 발생한 이유
현재 서버는 단순히 업로드 파일을 저장하는 기능만 구현하여 취약점이 발생한 이유가 복합적으로 나타난다.
- 확장자를 검증하지 않는다.
- MIME Type을 검증하지 않는다.
- 매직 바이트를 검증하지 않는다.
- 파일 크기를 검증하지 않는다.
- 파일 저장 시 파일 이름을 원본 그대로 저장한다.
- 브라우저를 통해 업로드한 파일에 직접 접근이 가능하다.
대응 방안
파일 확장자 검증
업로드한 파일의 확장자를 검증하기 위한 함수를 정의하고 파일 업로드 코드에 적용해준다. 현재 실습에서는 [jpg, jpeg, png]만 허용하도록 설정했다.

MIME Type 검증
MIME Type을 검증하는 함수를 추가한다. 그리고 해당 함수를 파일 업로드 코드에 적용시킨다.

매직 바이트 검증
매직 바이트를 검증하고 [jpeg, jpg, png]가 아니면 차단하는 함수를 작성한다. 그리고 파일 업로드 코드에 적용한다. jpeg와 jpg는 같은 매직 바이트를 사용한다.

파일 크기 검증
업로드하는 파일의 크기를 10KB로 제한한다. 이를 넘을 경우 차단하고, 에러 메시지를 전송한다.

예측 불가능한 파일명
클라이언트가 악성 파일을 업로드 하더라도 직접 접근하는 것을 방지하기 위해 파일명을 예측할 수 없는 파일명으로 저장한다. 실습에서는 저장 형식은 [현재 시간 + 무작위 16자리 + 확장자]으로 파일을 저장한다.

업로드 디렉터리의 특정 확장자 실행 차단
Nginx의 설정 파일을 수정하여 대응한다. 나의 환경에서는 [/etc/nginx/sites-available/default] 파일을 수정했다. 아래의 설정을 추가하면 디렉터리에 접근하더라도 스크립트 실행을 막을 수 있다.
해당 설정을 할 때 주의할 것은 php 실행 코드보다 우선 적용을 해야 하므로 위에 보안 설정을 적용해야 한다. 빨간색 상자가 추가해야 할 보안 설정이다.

시큐어 코딩 후 실습
파일 확장자 검증
파일 확장자를 검증하는 코드를 추가하여 .php 확장자는 차단하는 것을 확인할 수 있다.


MIME Type 검증
MIME Type 검증을 추가하여 Content-Type이 일치하지 않으므로 파일 업로드를 차단하는 것을 볼 수 있다. 알림 창에 “text/x-php”라고 출력되는 것은 finfo()가 파일 내용을 기반으로 추정하기 때문에 환경에 따라 application/x-php를 출력할 수도 있다.


매직 바이트 검증
파일들의 매직 바이트를 xxd 명령으로 확인해보면 jpeg, png 파일의 매직 바이트가 시큐어 코딩에 적용한 매직 바이트와 동일하다. 이 매직 바이트가 동일하므로 해당 파일은 업로드에 성공할 것이고, .php는 파일을 구분하기 위한 매직 바이트가 존재하지 않으므로, 업로드에 실패할 것이다.

common.php 파일은 업로드에 실패했다.

이미지 파일인 server.png는 업로드에 성공했다.


파일 크기 검증
10KB를 넘는 파일을 업로드하면 차단을 제대로 수행하는지 테스트하기 위해 약 13KB의 zip 파일을 업로드한다.

파일 제한 크기를 넘어서므로 업로드를 차단하고, 에러 메시지를 응답 받는다.

예측 불가능한 파일명
[horse.jpeg] 파일을 업로드를 하면, 웹 페이지의 파일 목록에는 [horse.jpeg]로 출력되지만, 서버의 데이터베이스와 uploads 디렉터리를 확인하면 유추할 수 없는 파일명으로 변경되어 저장된 것을 볼 수 있다. 공격자는 파일 업로드에 성공하더라도 저장된 파일명을 알 수 없어 접근이 거의 불가능 해진다. (우연히 파일명을 맞출 수 있는 가능성이 존재하긴 한다.)

파일 목록은 원본 파일명 그대로 출력한다. 그 이유는 데이터베이스에 원본 파일명을 저장하는 컬럼이 존재하기 때문이다.


실제 저장 경로를 확인하면 예측 불가능한 파일명으로 저장된 것을 확인할 수 있다.

업로드 디렉터리의 특정 확장자 실행 차단
특정 확장자에 대한 접근이 차단되어 이미지 파일은 접근이 가능하다. 웹 셸의 경우, .php 확장자를 가지고 있기 때문에 해당 파일에 접근하면 status code 403을 응답한다.
