대상: Apache/.htaccess 공유 호스팅에서 Laravel류 앱(public/만 노출되어야 하는)을 돌리는 모든 사람. 실제 사고에서 추출했습니다(→ .env가 세상에 노출됨) — 당신 자신의 배포를 지키기 위해.
본 사이트의 견해: 이건 '한 사람의 실수'가 아니다
초기 공유 호스팅 문화는 모든 것을 public_html 바로 아래에 두었습니다. (오직 public/만 노출되길 기대하는) Laravel이 그 흐름에 들어가면서, 위험한 레이아웃이 사실상의 표준이 되었습니다 — 벤더 자신의 문서조차 프로젝트를 public_html 안에 둡니다. 그러니 해법은 경계심이 아니라 프로세스입니다: 배포 기본값을 바꾸세요.
배치를 바로잡기(도식)
✗ 위험(본체가 공개 루트에)
public_html/ ├─ .env ← 읽힘! ├─ .git/ vendor/ └─ public/
✓ 안전(본체는 밖, public만)
app-laravel/ ← 웹 루트 밖 ├─ .env .git/ ← 닿을 수 없음 └─ public/ → public_html에서 require됨
환경별로 맞는 해법
".env를 웹에 노출하지 않기"의 구체적인 방법은 서버 종류와 권한에 따라 달라집니다. 먼저 자신에게 해당하는 행을 찾은 뒤 아래 단계를 진행하세요.
| 환경 | 영구 해법 | 응급처치 | 주의할 점 |
|---|---|---|---|
| 공유 호스팅(SSH 있음) | 앱 본체를 공개 폴더 밖으로 옮기고 작은 index.php만 남긴다(2단계) | .htaccess로 거부 | 공개 폴더 자체를 심링크로 만들면 일부 환경에서 500이 반환된다 |
| 공유 호스팅(FTP만) | FTP 클라이언트로 앱 본체를 공개 폴더(public_html 등)보다 한 단계 위에 다시 업로드한다 | .htaccess로 거부 | 대부분의 공유 호스팅은 공개 폴더 위에 쓸 수 있다. 안 된다면 지원팀에 문의한다 |
| 관리 패널에서 공개 폴더를 지정할 수 있음 | 공개 폴더를 app-laravel/public로 직접 지정한다 | 불필요(확인은 한다) | 전환 후 예전 공개 폴더에 남은 앱 사본을 삭제한다 |
| VPS(Apache) | VirtualHost의 DocumentRoot를 public/로 지정한다 | 서버 설정에 거부 규칙 | AllowOverride None(Apache 2.4 기본값)이면 .htaccess는 읽히지 않는다 |
| VPS(Nginx) | root를 public/로 지정하고 점으로 시작하는 파일을 거부한다 | 서버 설정에 거부 규칙 | Nginx는 .htaccess를 읽지 않는다 |
목표는 어디서나 같습니다. 공개 폴더 안에 .env, .git, vendor/, storage/가 없어야 합니다. Laravel 공식 문서도 index.php를 프로젝트 루트로 옮겨 그곳에서 서비스하면 민감한 설정 파일이 대거 인터넷에 공개된다고 경고합니다.
1단계: 응급처치(빠르고, 되돌릴 수 있음)
웹 루트의 .htaccess 맨 위에 거부 블록을 추가하세요(방어적 설정 예시). 일반 페이지는 영향받지 않습니다.
# === SECURITY BLOCK ===
RedirectMatch 404 (?i)/\.(env|git)(\..*)?(/|$)
<FilesMatch "(?i)\.(sql|sql\.gz|bak|old|swp|save|orig|tgz)$|^credentials\.json$|\.bk[._]">
Require all denied
</FilesMatch>
<FilesMatch "(?i)^(phpinfo|info)\.php$">
Require all denied
</FilesMatch>
# if Laravel internals are visible at /<app>/storage/... etc.
RedirectMatch 403 (?i)^/[^/]+/(storage|bootstrap/cache|config|database|resources|routes|tests|vendor)(/|$)
# === END ===응급처치가 목표는 아니다
블랙리스트는 반드시 무언가를 놓칩니다 — composer.lock이나, 새 파일명이 그렇습니다. 2단계까지 시간을 버는 수단으로만 여기세요.
2단계: 재구성(영구 해법)
앱 본체를 웹 루트 밖으로 옮기고, 그저 그것을 로드하는 작은 진입점만 남기세요.
# 1. Move the body outside the web root (same filesystem → mv is instant, atomic)
mv ~/example/public_html/app ~/example/app-laravel
# 2. Put only a minimal entry in the web root
mkdir -p ~/example/public_html/sub
cat > ~/example/public_html/sub/index.php <<'PHP'
<?php require __DIR__ . '/../../app-laravel/public/index.php';
PHP
# 3. Copy Laravel public/.htaccess, symlink static assets
# 4. Clear config cache
rm -f ~/example/app-laravel/bootstrap/cache/{config,services,packages,routes-v7}.php본 사이트 운영에서 얻은 교훈: 심링크보다 bootstrap-redirect
public_html 자체를 public/로 향하는 심링크로 만들면, 일부 환경에서 등록된 서브도메인의 docroot에서 500을 반환할 수 있습니다(따라가지 않음). docroot를 실제 디렉터리로 두고 작은 index.php에서 절대 경로로 require하는('bootstrap-redirect' 형태)가 범위 제약이 있는 공유 호스팅에서 더 안정적입니다. 또한 opcache가 실패한 로드를 기억해 계속 500을 반환할 수 있으니 — 최후의 수단은 진입 파일명을 바꾸는 것(entry.php)입니다.
3단계: 키 교체(이미 봤다고 가정)
노출 창이 조금이라도 있었다면, .env 키를 이미 봤다고 보고 교체하세요. 순서: 외부 API / OAuth 시크릿 → 암호화 키 → 메일 → DB. .env 용어집 참고. OAuth 시크릿 재발급은 refresh_token을 무효화할 수 있으니 — 재인증 흐름을 먼저 준비하세요.
4단계: 자가 점검(습관으로)
마지막으로, 자기 사이트에서 실제로 아무것도 노출되지 않았는지 확인하세요(소유한 도메인에 대해서만).
# A 200 with a body means it's exposed. 403/404 is okay for now.
curl -sI https://your-domain/.env | head -1
curl -sI https://your-domain/.git/config | head -1모든 배포에서 이를 확인하면 배치 실수를 일찍 잡습니다.
직접 확인하는 방법(더 자세히)
4단계의 두 줄 외에도, 아래 확인으로 흔히 놓치는 부분을 더 잡을 수 있습니다. 반드시 자신의 서버와 자신의 도메인에 대해서만 실행하세요.
상태 코드가 아니라 내용을 본다
모르는 URL에 홈 화면과 200을 돌려주는 앱도 있어서, 상태 코드만으로는 판단할 수 없습니다. curl -s https://your-domain/.env | head -c 120처럼 앞부분만 출력해 APP_KEY=나 DB_PASSWORD= 같은 설정 줄이 나오는지 보세요. .git/HEAD는 ref: refs/heads/로 시작하는 줄이 나오면 노출된 상태입니다.
앱 하나가 아니라 전부 확인한다
메인 도메인만이 아니라 서브도메인이나 하위 디렉터리의 앱마다 같은 확인을 반복하세요. 배치 실수는 앱을 추가할 때 그대로 복사되기 쉬워서, 하나를 고쳤다고 나머지도 고쳐진 것은 아닙니다.
서버의 공개 폴더를 검색한다
SSH가 있다면 find ~/public_html -maxdepth 4 \( -name ".env*" -o -name ".git" -o -name "*.sql" -o -name "*.zip" \)처럼 위험한 파일을 직접 찾으세요. 아무것도 출력되지 않는 것이 원하는 결과입니다. FTP만 쓸 수 있다면 FTP 클라이언트에서 "숨김 파일 표시"를 켜고 목록을 살펴보세요.
.htaccess가 실제로 적용되는지 확인한다
비밀 정보가 없는 무해한 더미 파일(예: test만 적힌 check.bak)을 공개 폴더에 두고 URL로 열어 보세요. 403이 나와야 합니다. 열린다면 .htaccess가 읽히지 않는 것이므로 서버 종류(Nginx 등)나 설정을 확인하세요. 확인이 끝나면 더미 파일은 삭제하세요.
디버그 모드가 꺼져 있는지 확인한다
.env에서 APP_ENV는 production, APP_DEBUG는 false여야 합니다. Laravel 문서는 운영 환경에서 APP_DEBUG=true이면 민감한 설정 값이 사용자에게 노출될 위험이 있다고 설명합니다. SSH가 있다면 php artisan about --only=environment로도 같은 내용을 볼 수 있습니다.
흔한 실수와 고치는 법
- .env는 지웠지만 .git은 남겨 둠: 노출된
.git에서 예전.env나 키를 포함한 과거 커밋이 복원될 수 있습니다..git도 공개 폴더 밖으로 옮기고, 한 번이라도 커밋된 시크릿은 재발급하세요. .env.bak,.env.old같은 사본을 공개 폴더에 남겨 둠: 실제 값이 든 사본은.env만큼 민감합니다. 밖으로 옮기거나 삭제하세요..env.example에는 자리표시 값만 두세요.- 프로젝트 전체를 ZIP으로 올리고 압축을 푼 뒤 ZIP을 남겨 둠: ZIP 안에
.env가 들어 있습니다. 압축을 풀면 바로 삭제하세요. - 운영 환경을
APP_DEBUG=true로 돌림: 오류 페이지에 설정 값이 드러날 수 있습니다. 운영에서는 반드시false로 두세요. .htaccess를 썼으니 안전하다고 생각함: Nginx나AllowOverride None환경에서는 아예 읽히지 않습니다. 위의 더미 파일 확인으로 검증하세요.APP_KEY를 갑자기 바꿈: Laravel 문서에 따르면 키를 바꾸면 로그인한 모든 사용자가 로그아웃되고, 예전 키로 암호화된 데이터는 복호화할 수 없게 됩니다. Laravel 11 이상에서는 전환 전에 예전 키를APP_PREVIOUS_KEYS에 넣으세요.
실제 사례: 노출된 .env를 대규모로 수집한 공격
2024년 8월, 한 보안 기업의 연구팀은 웹 서버에 노출된 .env 파일을 자동으로 수집하고, 그 안의 클라우드 자격 증명으로 데이터를 빼낸 뒤 몸값을 요구한 공격을 보고했습니다. 보고서에 따르면 약 11만 개 도메인이 대상이 되었고 9만 개가 넘는 환경 변수가 수집되었으며, 그중 약 7,000개는 클라우드 서비스 자격 증명, 약 1,500개는 소셜 미디어 계정 자격 증명이었습니다.
보고서는 원인을 클라우드 쪽 취약점이 아니라, 실수로 .env를 공개한 피해 조직의 설정 오류로 보았습니다. 여기서 얻을 교훈은 두 가지입니다.
- 노출된
.env는 우연히 발견되는 것이 아니라 프로그램이 대규모로 찾아낸다 .env하나로 클라우드 스토리지나 이메일 같은 다른 서비스까지 피해가 번질 수 있다
작은 사이트라서 발견되지 않을 거라고 생각하지 마세요. 같은 배치 실수의 익명화된 사례는 .env가 온 세상에 노출됨에 있습니다.
다음으로 읽기
- 사고: .env가 온 세상에 노출됨
- 용어집: .env란 무엇인가
- 기초: .env와 API 키의 무엇이 위험한가
- 호스팅 업체가 침해당했을 때: 웹 호스팅이 침해당했을 때 할 일(자신의 설정으로는 막을 수 없는 경우, 영어)
- 침입 여부 확인: 내 웹사이트나 서버가 해킹됐는지 확인하는 법(.env가 공개되어 있었다고 밝혀졌을 때 볼 곳)
출처
- Laravel 문서, "Deployment"(요청은
public/index.php로 향하게 하고, 프로젝트 루트에서 서비스하지 말 것, 운영에서는APP_DEBUG=false): laravel.com - Laravel 문서, "Configuration"(
.env를 소스 관리에 커밋하지 말 것, Debug Mode): laravel.com - Laravel 문서, "Encryption"(
APP_KEY교체와APP_PREVIOUS_KEYS): laravel.com - Apache HTTP Server 2.4, "core - AllowOverride"(
None이면.htaccess를 읽지 않으며, 기본값은None): httpd.apache.org - Apache HTTP Server 2.4, ".htaccess files"(사용할 때와 주의점): httpd.apache.org
- OWASP, "WSTG-CONF-04 Review Old Backup and Unreferenced Files for Sensitive Information": owasp.org
- Unit 42(Palo Alto Networks), "Leaked Environment Variables Allow Large-Scale Extortion Operation in Cloud Environments"(2024년 8월 15일): unit42.paloaltonetworks.com