[AWS LAMP] 이미지 업로드 + PHP 조합에서 발생하는 RCE 취약점 처리 방법 : Magic Byte (MIME) 검증 및 업로드폴더 php 실행 금지 처
이미지 업로드 + PHP 조합에서 발생하는 RCE(Remote Code Execution) 는
웹서비스에서 가장 흔한 실제 해킹 루트 중 하나입니다. 특히 PHP + Apache(LAMP) 환경에서 많이 발생합니다.
AWS Lightsail + Bitnami LAMP에서도 구조가 동일하면 그대로 취약합니다.
기본 공격 구조
공격 흐름
1. 공격자가 이미지 업로드
2. 이미지 안에 PHP 코드 삽입
3. 업로드 폴더에서 PHP 실행
4. 서버 쉘 획득
예
/uploads/avatar.jpg
이 파일이 실제로는
avatar.php
처럼 실행되면 끝입니다.
가장 흔한 취약점 (90% 사이트)
확장자만 검사하는 경우
개발자 코드
$ext = pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);if($ext == "jpg" || $ext == "png"){
move_uploaded_file(...);
}
공격 파일
shell.php.jpg
또는
shell.jpg.php
업로드 성공 → 실행 가능
이미지 + PHP 혼합 공격 (Polyglot)
이미지 헤더 + PHP 코드
GIF89a
<?php system($_GET['cmd']); ?>
파일
hack.gif
서버가 확장자만 검사하면
- 이미지로 인식
- PHP 코드 포함
URL
https://site.com/upload/hack.gif?cmd=whoami
결과
www-data
서버 명령 실행됨.
Apache 설정 취약점
Apache에서 이런 설정이 있으면 100% 뚫립니다
AddType application/x-httpd-php .jpg
또는
AddHandler php-script .jpg
그러면
shell.jpg
도 PHP 실행됩니다.
더 위험한 경우 (Bitnami에서 가끔 있음)
uploads 폴더에서 PHP 실행 허용
/opt/bitnami/apache/htdocs/uploads/
이 상태면 공격자 URL
https://site.com/uploads/shell.php
→ 바로 실행
실제 공격자가 업로드하는 웹쉘
예
<?php system($_GET['cmd']); ?>
접속
https://site.com/uploads/shell.php?cmd=ls
결과
index.php
config.php
uploads
완전 서버 장악.
방어 방법 (가장 중요)
① 업로드 폴더 PHP 실행 차단
Apache 설정
# uploads 폴더 내 악성 스크립트 실행 원천 차단
<Directory "/opt/bitnami/apache2/htdocs/uploads">
<FilesMatch "(?i)\.(php|php[3-7]|phtml|phar|inc)$">
Require all denied
</FilesMatch>
</Directory>
또는
<FilesMatch "\.php$">
Deny from all
</FilesMatch>
설정 후 아파치 재시작
sudo /opt/bitnami/ctlscript.sh restart apache
Gemini의 응답
AWS Lightsail의 Bitnami 터미널(SSH) 환경에 접속하신 상태에서, uploads 폴더 내 PHP 파일 실행을 원천 차단하는 설정 방법입니다.
서버 버전에 따라 apache 또는 apache2 폴더명을 사용하므로, 경로 확인부터 재시작까지 단계별로 정리해 드립니다.
1. 정확한 폴더 경로 확인
먼저 현재 서버가 apache 폴더를 쓰는지 apache2 폴더를 쓰는지 확인해야 합니다. 터미널에 아래 명령어를 입력해 보세요.
Bash
ls /opt/bitnami/apache2/htdocs/uploads
- 결과에 파일 목록이 나오면:
apache2경로를 사용하는 것입니다. (대부분 이 경우에 해당합니다.) - “No such file or directory”가 나오면:
apache경로를 사용하는 것이므로, 이후 명령어에서apache2대신apache를 입력하시면 됩니다.
2. 설정 파일 열기
가장 확실하게 적용되는 기본 가상 호스트 설정 파일인 bitnami.conf를 nano 에디터로 엽니다.
Bash
sudo nano /opt/bitnami/apache2/conf/bitnami/bitnami.conf
3. 차단 코드 삽입
파일이 열리면 키보드 방향키를 이용해 <VirtualHost _default_:443> (HTTPS 설정 블록) 안쪽 공간으로 이동한 뒤, 아래 코드를 복사해서 붙여넣습니다. (우클릭 후 붙여넣기)
Apache
# uploads 폴더 내 악성 스크립트 실행 원천 차단
<Directory "/opt/bitnami/apache2/htdocs/uploads">
<FilesMatch "(?i)\.(php|php[3-7]|phtml|phar|inc)$">
Require all denied
</FilesMatch>
</Directory>
주의: 1번 단계에서 apache 폴더임이 확인되었다면, 위 코드의 apache2 부분도 apache로 변경해 주셔야 합니다.
4. 저장 후 종료
Ctrl + O(알파벳 O)를 눌러 저장을 준비합니다.- **
Enter**를 눌러 파일명을 확정합니다. - **
Ctrl + X**를 눌러 nano 에디터를 빠져나옵니다.
5. 아파치 서버 재시작
설정을 적용하기 위해 Bitnami 컨트롤 스크립트로 아파치 서버를 재시작합니다.
Bash
sudo /opt/bitnami/ctlscript.sh restart apache
적용이 잘 되었는지 테스트하는 방법
가장 확실하게 방어가 되었는지 직접 눈으로 확인해 보는 것이 좋습니다.
uploads폴더 안에 테스트용 PHP 파일을 하나 만듭니다.- 브라우저를 열고
https://everhealth.co.kr/uploads/test.php로 접속해 봅니다. - 화면에 ‘Hacked?’라는 글자 대신 403 Forbidden 에러(접근 권한 없음)가 뜬다면 서버 단 방어가 완벽하게 성공한 것입니다!
- 테스트가 끝난 후 해당 파일은 삭제해 줍니다.Bash
sudo rm /opt/bitnami/apache2/htdocs/uploads/test.php
적용하시다가 터미널에서 에러 메시지가 나오거나 막히는 부분이 있다면, 에러 내용을 그대로 복사해서 알려주세요! 즉시 해결책을 찾아드리겠습니다.
echo "<?php echo 'Hacked?'; ?>" | sudo tee /opt/bitnami/apache2/htdocs/uploads/test.php
하지만 여전히 Hacked?가 보인다면,
이런 현상이 발생하는 이유는 Bitnami의 PHP-FPM(FastCGI) 라우팅 설정이 폴더 기반(<Directory>) 접근 제어보다 우선순위가 높아서 아파치가 권한을 검사하기도 전에 PHP 엔진으로 파일을 넘겨버리거나, 서버에 설정된 실제 htdocs 물리적 경로가 미세하게 다르기 때문입니다.
이럴 때는 서버의 물리적 경로를 따지는 대신, 사용자가 접속하는 URL 주소 자체를 검사해서 차단해버리는 <LocationMatch> 방식을 사용해야 합니다. <Location> 구문은 아파치 설정 파일에서 가장 마지막에 평가되므로 기존의 모든 설정을 강력하게 덮어씁니다.
앞서 넣으셨던 <Directory ...> 블록을 지우고, 아래의 코드로 교체해 주세요
# --- Uploads Security (URL 기반 차단) ---
<LocationMatch "^/uploads/.*\.(php|php[3-7]|phtml|phar|inc)$">
Require all denied
</LocationMatch>
저장하고 빠져나온 뒤, 아파치를 다시 재시작합니다.
sudo /opt/bitnami/ctlscript.sh restart apache
② MIME 검사
PHP
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$type = finfo_file($finfo, $_FILES['file']['tmp_name']);
허용
image/jpeg
image/png
image/webp
③ 이미지 재생성
이게 가장 강력한 방법
$image = imagecreatefromjpeg($_FILES['file']['tmp_name']);
imagejpeg($image, "upload.jpg", 90);
효과
- PHP 코드 제거
- polyglot 제거
④ 파일명 랜덤화
avatar_123.jpg
❌ 위험
2f8c93f3d0e.jpg



