웹쉘(Web Shell)은 웹 서버에 올려 두는 악성 스크립트로, 공격자가 브라우저만으로 서버 명령을 실행하고 파일을 열어 보게 해 줍니다. 파일 하나로 서버를 원격 조종할 수 있어서, 해킹 사고에서 가장 흔하게 발견되는 “뒷문”입니다.
웹쉘은 어떻게 들어오나
- 업로드 기능의 취약점: 게시판, 이미지 업로드에서 확장자 검사가 허술하면 PHP 파일이 그대로 올라갑니다.
- 오래된 플러그인·CMS의 취약점: 원격 코드 실행이나 임의 파일 업로드 취약점을 통해 심어집니다.
- 탈취된 관리자 계정: 관리자 화면의 테마·플러그인 편집 기능으로 직접 코드를 넣습니다.
- FTP·SSH 계정 유출: 계정 정보가 새어 나가면 파일을 그대로 올릴 수 있습니다.
웹쉘은 이렇게 숨는다
- 이미지처럼 위장:
logo.jpg.php처럼 이중 확장자를 쓰거나, 이미지 헤더 뒤에 PHP 코드를 이어 붙입니다. - 정상 파일에 한 줄 끼워 넣기: 기존 파일 끝에 코드가 추가되어 파일 이름만 봐서는 구분되지 않습니다.
- 난독화:
base64_decode,gzinflate, 문자 치환으로 코드를 알아보기 어렵게 만듭니다. - 이름과 위치 흉내:
wp-includes안의 정상 파일과 비슷한 이름을 씁니다. - 지워도 되살아남: 웹쉘 여러 개를 흩어 두거나 크론으로 다시 만들게 합니다.
서버에서 의심 파일 찾기
# 이미지·문서 폴더 안의 실행 파일
find /var/www -path "*/uploads/*" -type f \( -name "*.php" -o -name "*.phtml" -o -name "*.phar" \)
# 이중 확장자
find /var/www -type f -regex ".*\.\(jpg\|png\|gif\)\.php"
# 최근 7일 내에 바뀐 PHP 파일
find /var/www -type f -name "*.php" -mtime -7
# 대표적인 난독화·실행 패턴
grep -rlE "(eval|assert)\s*\(\s*(base64_decode|gzinflate|str_rot13|\\\$_(POST|REQUEST|GET))" /var/www
grep -rlE "(system|shell_exec|passthru|exec)\s*\(\s*\\\$_(POST|REQUEST|GET)" /var/www
패턴 검색은 정상 코드를 잘못 잡거나(오탐), 정교한 웹쉘을 놓칠 수(미탐) 있습니다. 결과를 목록으로만 믿지 말고 열어서 확인하세요.
접근 로그로 확인
웹쉘은 보통 같은 파일에 POST 요청이 반복됩니다.
awk '$6 ~ /POST/ {print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
설치한 적 없는 파일이나 uploads 아래 경로가 상위에 나오면 의심합니다.
업로드 폴더에서 PHP 실행 막기
웹쉘이 올라와도 실행되지 않게 하면 피해를 크게 줄일 수 있습니다.
Nginx
location ~* ^/wp-content/uploads/.*\.(php|phtml|phar)$ {
deny all;
}
Apache (wp-content/uploads/.htaccess)
<FilesMatch "\.(php|phtml|phar)$">
Require all denied
</FilesMatch>
이 설정은 업로드 폴더에만 적용하는 것이고, 다른 경로의 취약점으로 들어오는 웹쉘까지는 막지 못합니다.
웹쉘을 찾았다면
- 파일 하나만 지우고 끝내지 마세요. 같은 공격자가 다른 곳에도 숨겨 두었을 가능성이 큽니다.
- 로그와 파일을 보존하고, 침입 경로(취약한 플러그인, 유출된 계정)를 찾아야 재발을 막을 수 있습니다.
- 비밀번호와 키를 모두 교체해야 합니다(복구 후 재발 방지 체크리스트 참고).
TCP-80.NET의 해킹 복구·이전 서비스는 소스 사본에서 웹쉘 패턴을 점검하고, 변조된 파일을 공식 원본과 비교하며, 결과를 리포트로 드립니다.