웹쉘(Web Shell)은 웹 서버에 올려 두는 악성 스크립트로, 공격자가 브라우저만으로 서버 명령을 실행하고 파일을 열어 보게 해 줍니다. 파일 하나로 서버를 원격 조종할 수 있어서, 해킹 사고에서 가장 흔하게 발견되는 “뒷문”입니다.


웹쉘은 어떻게 들어오나

  1. 업로드 기능의 취약점: 게시판, 이미지 업로드에서 확장자 검사가 허술하면 PHP 파일이 그대로 올라갑니다.
  2. 오래된 플러그인·CMS의 취약점: 원격 코드 실행이나 임의 파일 업로드 취약점을 통해 심어집니다.
  3. 탈취된 관리자 계정: 관리자 화면의 테마·플러그인 편집 기능으로 직접 코드를 넣습니다.
  4. 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의 해킹 복구·이전 서비스는 소스 사본에서 웹쉘 패턴을 점검하고, 변조된 파일을 공식 원본과 비교하며, 결과를 리포트로 드립니다.