docs: Add WAF integration docs and ignore lock files

- Add YARA guide, WAF integration docs, and webshell rules
- Ignore *.lock files in .gitignore

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
kaffa
2026-02-02 04:34:49 +00:00
parent f835b04695
commit 02a17a62b4
4 changed files with 2600 additions and 0 deletions

1
.gitignore vendored
View File

@@ -15,6 +15,7 @@ conf/domains.map
run/
data/
*.state
*.lock
# Python
__pycache__/

983
docs/YARA_GUIDE.md Normal file
View File

@@ -0,0 +1,983 @@
# YARA 규칙 가이드
악성코드 및 웹쉘 탐지를 위한 YARA 사용법 정리
## 목차
1. [YARA란?](#yara란)
2. [설치](#설치)
3. [규칙 문법](#규칙-문법)
4. [웹쉘 탐지 규칙 예시](#웹쉘-탐지-규칙-예시)
5. [CLI 사용법](#cli-사용법)
6. [Python 연동](#python-연동)
7. [실시간 모니터링](#실시간-모니터링)
8. [공개 규칙 모음](#공개-규칙-모음)
9. [다른 방식과 비교](#다른-방식과-비교)
---
## YARA란?
YARA는 악성코드 연구자가 악성 샘플을 식별하고 분류하기 위해 만든 **패턴 매칭 도구**입니다.
| 항목 | 설명 |
|------|------|
| 개발 | VirusTotal (Google 소속) |
| 용도 | 악성코드, 웹쉘, 악성 문서 탐지 |
| 방식 | 문자열 + 바이트 패턴 + 조건 매칭 |
| AI 여부 | ❌ 규칙 기반 (AI 없음) |
| 속도 | ⚡ 매우 빠름 (1-10ms/파일) |
| 라이선스 | BSD-3-Clause (무료) |
### 핵심 개념
```
YARA = 악성코드 전용 grep/정규식
파일 내용에서 특정 패턴을 찾고,
여러 조건을 조합하여 악성 여부를 판단
```
---
## 설치
### Linux (Ubuntu/Debian)
```bash
# 패키지 설치
sudo apt update
sudo apt install yara
# 버전 확인
yara --version
```
### macOS
```bash
brew install yara
```
### Python 바인딩
```bash
pip install yara-python
```
### 소스 컴파일 (최신 버전)
```bash
# 의존성 설치
sudo apt install automake libtool make gcc pkg-config libssl-dev
# 소스 다운로드 및 컴파일
git clone https://github.com/VirusTotal/yara.git
cd yara
./bootstrap.sh
./configure
make
sudo make install
sudo ldconfig
```
---
## 규칙 문법
### 기본 구조
```yara
rule 규칙이름 {
meta:
// 메타데이터 (선택사항)
description = "규칙 설명"
author = "작성자"
date = "2025-01-01"
severity = "high"
strings:
// 탐지할 문자열/패턴 정의
$string1 = "탐지할 문자열"
$string2 = { 48 65 6C 6C 6F } // 헥스 바이트
$regex1 = /정규식 패턴/
condition:
// 탐지 조건
any of them
}
```
### 문자열 정의 방식
```yara
rule StringTypes {
strings:
// 1. 일반 문자열
$text = "eval("
// 2. 대소문자 무시
$nocase = "system(" nocase
// 3. 와이드 문자 (UTF-16)
$wide = "malware" wide
// 4. 둘 다
$both = "shell" wide ascii nocase
// 5. 헥스 바이트 (바이너리)
$hex = { 4D 5A 90 00 } // MZ 헤더
// 6. 헥스 와일드카드
$hex_wild = { 4D 5A ?? ?? } // ??는 아무 바이트
// 7. 헥스 점프
$hex_jump = { 4D 5A [2-4] 00 } // 2-4바이트 사이
// 8. 정규식
$regex = /eval\s*\(\s*\$_(GET|POST)/
// 9. XOR 인코딩 탐지
$xor = "password" xor
// 10. Base64 인코딩 탐지
$b64 = "eval" base64
condition:
any of them
}
```
### 조건문 (condition)
```yara
rule ConditionExamples {
strings:
$a = "eval("
$b = "system("
$c = "$_POST"
$d = "$_GET"
condition:
// 기본 조건
$a // $a가 존재
$a and $b // 둘 다 존재
$a or $b // 하나라도 존재
not $a // $a가 없음
// 개수 조건
any of them // 하나라도 존재
all of them // 모두 존재
2 of them // 2개 이상 존재
2 of ($a, $b, $c) // 지정된 것 중 2개 이상
// 위치 조건
$a at 0 // 파일 시작 위치
$a in (0..100) // 0-100 바이트 범위
// 개수 세기
#a > 5 // $a가 5번 이상 출현
// 파일 크기
filesize < 1MB
filesize > 100 and filesize < 10KB
// 매직 바이트
uint16(0) == 0x5A4D // MZ 헤더 (PE 파일)
uint32(0) == 0x464C457F // ELF 헤더
// 복합 조건
($a or $b) and ($c or $d)
// 반복문
for any i in (1..#a) : (@a[i] < 100)
}
```
---
## 웹쉘 탐지 규칙 예시
### webshell_rules.yar
```yara
/*
* PHP 웹쉘 탐지 규칙
* Author: Security Team
* Last Updated: 2025-02-01
*/
// ============================================
// 기본 웹쉘 탐지
// ============================================
rule PHP_Webshell_Generic {
meta:
description = "일반적인 PHP 웹쉘 패턴"
severity = "high"
strings:
// 코드 실행 함수
$exec1 = "eval(" ascii nocase
$exec2 = "assert(" ascii nocase
$exec3 = "create_function(" ascii nocase
$exec4 = "call_user_func(" ascii nocase
$exec5 = /preg_replace\s*\([^)]*\/e/ ascii
// 시스템 명령 실행
$cmd1 = "system(" ascii nocase
$cmd2 = "exec(" ascii nocase
$cmd3 = "shell_exec(" ascii nocase
$cmd4 = "passthru(" ascii nocase
$cmd5 = "popen(" ascii nocase
$cmd6 = "proc_open(" ascii nocase
$cmd7 = /`[^`]+`/ ascii // 백틱
// 사용자 입력
$input1 = "$_GET" ascii
$input2 = "$_POST" ascii
$input3 = "$_REQUEST" ascii
$input4 = "$_COOKIE" ascii
$input5 = "$_FILES" ascii
condition:
// PHP 파일 시그니처
(uint16(0) == 0x3F3C or uint16(0) == 0x683C) and // <? or <h
// 실행 함수 + 사용자 입력
(any of ($exec*) or any of ($cmd*)) and
any of ($input*)
}
// ============================================
// 난독화 웹쉘 탐지
// ============================================
rule PHP_Webshell_Obfuscated {
meta:
description = "난독화된 PHP 웹쉘"
severity = "critical"
strings:
// 인코딩 함수
$enc1 = "base64_decode(" ascii nocase
$enc2 = "gzinflate(" ascii nocase
$enc3 = "gzuncompress(" ascii nocase
$enc4 = "gzdecode(" ascii nocase
$enc5 = "str_rot13(" ascii nocase
$enc6 = "convert_uudecode(" ascii nocase
// 문자 조합
$chr1 = /chr\s*\(\s*\d+\s*\)/ ascii
$chr2 = /\\x[0-9a-fA-F]{2}/ ascii
$chr3 = /\\[0-7]{3}/ ascii
// 동적 함수 호출
$dyn1 = /\$\w+\s*\(\s*\$/ ascii
$dyn2 = /\$\{\s*\$/ ascii
$dyn3 = /\$\w+\s*=\s*['"]\w+['"].*\$\w+\s*\(/ ascii
// Base64 인코딩된 위험 함수
$b64_eval = "ZXZhbC" ascii // base64("eval")
$b64_exec = "ZXhlYy" ascii // base64("exec")
$b64_system = "c3lzdGVt" ascii // base64("system")
$b64_assert = "YXNzZXJ0" ascii // base64("assert")
condition:
// 다중 인코딩 레이어
(2 of ($enc*)) or
// 문자 조합으로 함수 생성
(#chr1 > 5 or #chr2 > 10) or
// 동적 함수 호출 + 인코딩
(any of ($dyn*) and any of ($enc*)) or
// Base64 인코딩된 위험 함수
any of ($b64*)
}
// ============================================
// 유명 웹쉘 시그니처
// ============================================
rule Webshell_C99 {
meta:
description = "C99 웹쉘"
severity = "critical"
reference = "https://github.com/tennc/webshell"
strings:
$s1 = "c99shell" ascii nocase
$s2 = "c99_buff_prepare" ascii
$s3 = "c99_sess_put" ascii
$s4 = "c99ftpbrutecheck" ascii
$s5 = "c99fsearch" ascii
condition:
2 of them
}
rule Webshell_R57 {
meta:
description = "R57 웹쉘"
severity = "critical"
strings:
$s1 = "r57shell" ascii nocase
$s2 = "r57_pwd_hash" ascii
$s3 = "r57" ascii nocase
$s4 = "Safe_Mode Bypass" ascii
$s5 = "Dumper" ascii
condition:
3 of them
}
rule Webshell_WSO {
meta:
description = "WSO (Web Shell by oRb) 웹쉘"
severity = "critical"
strings:
$s1 = "WSO" ascii
$s2 = "Web Shell by oRb" ascii nocase
$s3 = "wso_version" ascii
$s4 = "FilesMan" ascii
condition:
2 of them
}
rule Webshell_B374k {
meta:
description = "B374k 웹쉘"
severity = "critical"
strings:
$s1 = "b374k" ascii nocase
$s2 = "b374k_config" ascii
$s3 = "b374k 2.8" ascii
condition:
any of them
}
rule Webshell_Weevely {
meta:
description = "Weevely 웹쉘 (백도어 생성기)"
severity = "critical"
strings:
$s1 = /\$\w=\$\w\(\'\',\$\w\(\$\w\(\$\w/ ascii
$s2 = "str_replace" ascii
$s3 = "base64" ascii
condition:
$s1 and $s2 and $s3 and filesize < 5KB
}
// ============================================
// 파일 업로드 취약점 악용
// ============================================
rule PHP_File_Upload_Shell {
meta:
description = "파일 업로드를 통한 웹쉘"
severity = "high"
strings:
$upload1 = "move_uploaded_file" ascii
$upload2 = "$_FILES" ascii
$upload3 = "copy(" ascii
$write1 = "file_put_contents" ascii
$write2 = "fwrite(" ascii
$write3 = "fputs(" ascii
$exec = /(eval|assert|system|exec|shell_exec|passthru)\s*\(/ ascii nocase
condition:
(any of ($upload*) or any of ($write*)) and $exec
}
// ============================================
// 숨겨진 백도어
// ============================================
rule PHP_Hidden_Backdoor {
meta:
description = "숨겨진 PHP 백도어"
severity = "critical"
strings:
// HTTP 헤더를 통한 명령 수신
$hdr1 = "$_SERVER['HTTP_" ascii
$hdr2 = "getallheaders()" ascii
$hdr3 = "apache_request_headers()" ascii
// 특정 파라미터 체크
$chk1 = /if\s*\(\s*isset\s*\(\s*\$_(GET|POST|REQUEST|COOKIE)\s*\[\s*['"][^'"]{32,}['"]\s*\]/ ascii
$chk2 = /md5\s*\(\s*\$_(GET|POST|REQUEST)/ ascii
// 실행
$exec = /(eval|assert|system|exec|passthru|shell_exec)\s*\(/ ascii nocase
condition:
(any of ($hdr*) or any of ($chk*)) and $exec
}
// ============================================
// ASP/ASPX 웹쉘
// ============================================
rule ASPX_Webshell_Generic {
meta:
description = "ASP.NET 웹쉘"
severity = "high"
strings:
$asp1 = "Request.Form" ascii nocase
$asp2 = "Request.QueryString" ascii nocase
$asp3 = "Request[" ascii nocase
$exec1 = "Process.Start" ascii
$exec2 = "cmd.exe" ascii nocase
$exec3 = "powershell" ascii nocase
$exec4 = "Eval(" ascii nocase
$exec5 = "Execute(" ascii nocase
condition:
any of ($asp*) and any of ($exec*)
}
// ============================================
// JSP 웹쉘
// ============================================
rule JSP_Webshell_Generic {
meta:
description = "JSP 웹쉘"
severity = "high"
strings:
$jsp1 = "request.getParameter" ascii
$jsp2 = "Runtime.getRuntime().exec" ascii
$jsp3 = "ProcessBuilder" ascii
$cmd1 = "cmd.exe" ascii nocase
$cmd2 = "/bin/sh" ascii
$cmd3 = "/bin/bash" ascii
condition:
$jsp1 and ($jsp2 or $jsp3) or
($jsp2 or $jsp3) and any of ($cmd*)
}
```
---
## CLI 사용법
### 기본 스캔
```bash
# 단일 파일 스캔
yara rules.yar target_file.php
# 디렉토리 재귀 스캔
yara -r rules.yar /var/www/html/
# 여러 규칙 파일 사용
yara -r rules1.yar rules2.yar /var/www/html/
```
### 출력 옵션
```bash
# 매칭된 문자열 표시
yara -s rules.yar file.php
# 메타데이터 표시
yara -m rules.yar file.php
# 태그만 표시
yara -t webshell rules.yar file.php
# 매칭 개수 제한
yara -l 10 rules.yar file.php
# 에러 무시
yara -w rules.yar /var/www/html/
# 타임아웃 설정 (초)
yara -a 30 rules.yar large_file.bin
```
### 출력 예시
```bash
$ yara -s webshell_rules.yar suspicious.php
PHP_Webshell_Generic suspicious.php
0x15:$exec1: eval(
0x25:$input2: $_POST
PHP_Webshell_Obfuscated suspicious.php
0x35:$enc1: base64_decode(
0x50:$dyn1: $func($
```
---
## Python 연동
### 기본 사용
```python
#!/usr/bin/env python3
"""YARA Python 바인딩 사용 예시"""
import yara
import os
from pathlib import Path
class YaraScanner:
def __init__(self, rules_path):
"""YARA 규칙 로드"""
if os.path.isdir(rules_path):
# 디렉토리의 모든 .yar 파일 컴파일
rule_files = {}
for f in Path(rules_path).glob("*.yar"):
rule_files[f.stem] = str(f)
self.rules = yara.compile(filepaths=rule_files)
else:
# 단일 파일
self.rules = yara.compile(filepath=rules_path)
def scan_file(self, filepath):
"""파일 스캔"""
try:
matches = self.rules.match(filepath)
if matches:
return {
"file": filepath,
"malicious": True,
"matches": [
{
"rule": m.rule,
"tags": m.tags,
"meta": m.meta,
"strings": [
{
"offset": s[0],
"identifier": s[1],
"data": s[2].decode("utf-8", errors="replace")[:100]
}
for s in m.strings
]
}
for m in matches
]
}
return {"file": filepath, "malicious": False}
except yara.Error as e:
return {"file": filepath, "error": str(e)}
def scan_data(self, data, filename="memory"):
"""메모리 데이터 스캔"""
if isinstance(data, str):
data = data.encode()
matches = self.rules.match(data=data)
if matches:
return {
"source": filename,
"malicious": True,
"rules": [m.rule for m in matches]
}
return {"source": filename, "malicious": False}
def scan_directory(self, directory, extensions=None):
"""디렉토리 스캔"""
if extensions is None:
extensions = {".php", ".phtml", ".inc", ".asp", ".aspx", ".jsp"}
findings = []
scanned = 0
for root, dirs, files in os.walk(directory):
# 숨김 디렉토리 제외
dirs[:] = [d for d in dirs if not d.startswith(".")]
for filename in files:
ext = os.path.splitext(filename)[1].lower()
if ext in extensions:
filepath = os.path.join(root, filename)
result = self.scan_file(filepath)
scanned += 1
if result.get("malicious"):
findings.append(result)
print(f"🚨 탐지: {filepath}")
for m in result["matches"]:
print(f" 규칙: {m['rule']}")
return {
"scanned": scanned,
"findings": findings
}
# 사용 예시
if __name__ == "__main__":
scanner = YaraScanner("webshell_rules.yar")
# 파일 스캔
result = scanner.scan_file("/var/www/html/upload/shell.php")
print(result)
# 문자열 스캔
code = '<?php eval($_POST["cmd"]); ?>'
result = scanner.scan_data(code)
print(result)
# 디렉토리 스캔
results = scanner.scan_directory("/var/www/html")
print(f"스캔 완료: {results['scanned']}개 파일, {len(results['findings'])}개 위협")
```
### 콜백 사용 (대용량 스캔)
```python
"""콜백을 사용한 효율적인 스캔"""
import yara
def match_callback(data):
"""매치 발견 시 콜백"""
print(f"규칙: {data['rule']}")
print(f"태그: {data['tags']}")
print(f"메타: {data['meta']}")
# CALLBACK_CONTINUE: 계속 스캔
# CALLBACK_ABORT: 스캔 중단
return yara.CALLBACK_CONTINUE
rules = yara.compile(filepath="rules.yar")
# 콜백과 함께 스캔
matches = rules.match(
"/path/to/file",
callback=match_callback,
which_callbacks=yara.CALLBACK_MATCHES
)
```
---
## 실시간 모니터링
### watchdog + YARA
```python
#!/usr/bin/env python3
"""파일 시스템 실시간 모니터링 + YARA 스캔"""
import yara
import os
import shutil
import logging
from datetime import datetime
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
# 로깅 설정
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
class WebshellMonitor(FileSystemEventHandler):
def __init__(self, rules_path, quarantine_dir="/var/quarantine"):
self.rules = yara.compile(filepath=rules_path)
self.quarantine_dir = quarantine_dir
self.extensions = {".php", ".phtml", ".inc", ".asp", ".aspx", ".jsp"}
os.makedirs(quarantine_dir, exist_ok=True)
logger.info(f"YARA 규칙 로드 완료")
def should_scan(self, filepath):
"""스캔 대상 여부 확인"""
ext = os.path.splitext(filepath)[1].lower()
return ext in self.extensions
def on_created(self, event):
"""파일 생성 시"""
if event.is_directory:
return
if self.should_scan(event.src_path):
self.scan_file(event.src_path, "created")
def on_modified(self, event):
"""파일 수정 시"""
if event.is_directory:
return
if self.should_scan(event.src_path):
self.scan_file(event.src_path, "modified")
def on_moved(self, event):
"""파일 이동 시"""
if event.is_directory:
return
if self.should_scan(event.dest_path):
self.scan_file(event.dest_path, "moved")
def scan_file(self, filepath, event_type):
"""파일 스캔"""
try:
if not os.path.exists(filepath):
return
matches = self.rules.match(filepath)
if matches:
rules = [m.rule for m in matches]
logger.warning(f"🚨 웹쉘 탐지 [{event_type}]: {filepath}")
logger.warning(f" 매칭 규칙: {rules}")
self.quarantine_file(filepath, rules)
self.send_alert(filepath, rules, event_type)
else:
logger.debug(f"✅ 정상: {filepath}")
except Exception as e:
logger.error(f"스캔 오류 ({filepath}): {e}")
def quarantine_file(self, filepath, rules):
"""파일 격리"""
try:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
basename = os.path.basename(filepath)
quarantine_name = f"{timestamp}_{basename}"
quarantine_path = os.path.join(self.quarantine_dir, quarantine_name)
# 메타데이터 저장
meta_path = quarantine_path + ".meta"
with open(meta_path, "w") as f:
f.write(f"원본 경로: {filepath}\n")
f.write(f"탐지 시간: {datetime.now()}\n")
f.write(f"매칭 규칙: {rules}\n")
# 파일 이동
shutil.move(filepath, quarantine_path)
logger.info(f" 격리 완료: {quarantine_path}")
except Exception as e:
logger.error(f"격리 실패: {e}")
def send_alert(self, filepath, rules, event_type):
"""알림 전송 (구현 필요)"""
# Slack, Telegram, Email 등으로 알림
pass
def main():
import argparse
parser = argparse.ArgumentParser(description="YARA 기반 웹쉘 모니터링")
parser.add_argument("-r", "--rules", default="webshell_rules.yar", help="YARA 규칙 파일")
parser.add_argument("-d", "--directory", default="/var/www/html", help="모니터링 디렉토리")
parser.add_argument("-q", "--quarantine", default="/var/quarantine", help="격리 디렉토리")
args = parser.parse_args()
event_handler = WebshellMonitor(args.rules, args.quarantine)
observer = Observer()
observer.schedule(event_handler, args.directory, recursive=True)
observer.start()
logger.info(f"🔍 모니터링 시작: {args.directory}")
logger.info(f" 규칙: {args.rules}")
logger.info(f" 격리 디렉토리: {args.quarantine}")
try:
while True:
import time
time.sleep(1)
except KeyboardInterrupt:
observer.stop()
logger.info("모니터링 종료")
observer.join()
if __name__ == "__main__":
main()
```
### systemd 서비스로 등록
```ini
# /etc/systemd/system/yara-monitor.service
[Unit]
Description=YARA Webshell Monitor
After=network.target
[Service]
Type=simple
User=root
ExecStart=/usr/bin/python3 /opt/security/yara_monitor.py \
--rules /opt/security/rules/webshell_rules.yar \
--directory /var/www/html \
--quarantine /var/quarantine
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
```
```bash
# 서비스 등록 및 시작
sudo systemctl daemon-reload
sudo systemctl enable yara-monitor
sudo systemctl start yara-monitor
sudo systemctl status yara-monitor
```
---
## 공개 규칙 모음
### 주요 소스
| 저장소 | 규칙 수 | 주요 내용 |
|--------|---------|-----------|
| [Neo23x0/signature-base](https://github.com/Neo23x0/signature-base) | 3,000+ | 웹쉘, APT, 악성코드 |
| [Yara-Rules/rules](https://github.com/Yara-Rules/rules) | 2,000+ | 범용 |
| [InQuest/yara-rules](https://github.com/InQuest/yara-rules) | 500+ | 문서 악성코드 |
| [bartblaze/Yara-rules](https://github.com/bartblaze/Yara-rules) | 300+ | 랜섬웨어 |
| [kevthehermit/YaraRules](https://github.com/kevthehermit/YaraRules) | 200+ | 악성코드 |
| [cuckoosandbox/community](https://github.com/cuckoosandbox/community) | 500+ | 샌드박스 분석용 |
### 규칙 다운로드
```bash
# 디렉토리 생성
mkdir -p /opt/yara-rules
cd /opt/yara-rules
# signature-base (웹쉘 전문)
git clone https://github.com/Neo23x0/signature-base.git
# Yara-Rules (범용)
git clone https://github.com/Yara-Rules/rules.git yara-rules
# 규칙 확인
ls signature-base/yara/
# apt_*.yar - APT 공격
# crime_*.yar - 사이버 범죄
# gen_*.yar - 일반 악성코드
# thor_webshells.yar - 웹쉘 (추천)
```
### 규칙 통합 사용
```python
"""여러 규칙 소스 통합"""
import yara
from pathlib import Path
def compile_all_rules(rules_dirs):
"""모든 규칙 디렉토리 통합 컴파일"""
rule_files = {}
for rules_dir in rules_dirs:
for yar_file in Path(rules_dir).rglob("*.yar"):
# 파일명 중복 방지
key = f"{rules_dir.name}_{yar_file.stem}"
rule_files[key] = str(yar_file)
print(f"{len(rule_files)}개 규칙 파일 로드")
return yara.compile(filepaths=rule_files)
# 사용
rules = compile_all_rules([
Path("/opt/yara-rules/signature-base/yara"),
Path("/opt/yara-rules/yara-rules/malware"),
])
```
---
## 다른 방식과 비교
### 탐지 방식 비교표
| 방식 | 원리 | 속도 | 알려진 위협 | 변형 | 제로데이 | AI |
|------|------|------|-------------|------|---------|-----|
| 해시 비교 | 파일 지문 | 0.01ms | ✓✓✓ | ✗ | ✗ | ❌ |
| YARA | 패턴 매칭 | 1-10ms | ✓✓ | ✓ | △ | ❌ |
| 정규식 | 텍스트 매칭 | 1-5ms | ✓ | △ | ✗ | ❌ |
| ML 분류기 | 학습된 모델 | 10-50ms | ✓✓ | ✓✓ | ✓ | ⚠️ |
| LLM | 코드 이해 | 100ms+ | ✓✓ | ✓✓✓ | ✓✓ | ✓ |
### 각 방식의 장단점
**해시 비교**
```
장점: 오탐 0%, 가장 빠름
단점: 1비트만 달라도 탐지 못함
용도: 알려진 악성코드 즉시 차단
```
**YARA**
```
장점: 유연한 패턴, 커뮤니티 활성화, 업계 표준
단점: 규칙 작성 필요, 고급 난독화 취약
용도: 알려진 패턴 + 변형 탐지
```
**AI/ML**
```
장점: 새로운 위협 탐지, 난독화 강함
단점: 오탐 가능, 상대적으로 느림
용도: 제로데이, 고도화된 공격
```
### 권장 조합
```
Layer 1: 해시 비교 (0.01ms)
│ 알려진 악성코드 즉시 차단
│ 오탐: 0%
Layer 2: YARA (1-10ms)
│ 패턴 기반 탐지
│ 오탐: 매우 낮음
Layer 3: AI 모델 (50-100ms)
│ 난독화/제로데이 탐지
│ 오탐: 낮음 (검토 필요)
[최종 판정]
```
---
## 참고 자료
- [YARA 공식 문서](https://yara.readthedocs.io/)
- [YARA GitHub](https://github.com/VirusTotal/yara)
- [YARA 규칙 작성 가이드](https://yara.readthedocs.io/en/stable/writingrules.html)
- [Neo23x0 Signature Base](https://github.com/Neo23x0/signature-base)
- [VirusTotal YARA](https://www.virustotal.com/gui/hunting/yaradetect)

1125
docs/waf-integration.md Normal file

File diff suppressed because it is too large Load Diff

491
docs/webshell_rules.yar Normal file
View File

@@ -0,0 +1,491 @@
/*
* 웹쉘 탐지 YARA 규칙
*
* 사용법:
* yara -r webshell_rules.yar /var/www/html/
* yara -s webshell_rules.yar suspicious.php
*
* Author: Security Team
* Last Updated: 2025-02-02
*/
// ============================================
// PHP 웹쉘 - 기본 패턴
// ============================================
rule PHP_Webshell_Eval {
meta:
description = "eval()을 이용한 PHP 웹쉘"
severity = "high"
strings:
$eval = /eval\s*\(\s*\$_(GET|POST|REQUEST|COOKIE)/ nocase
condition:
$eval
}
rule PHP_Webshell_System {
meta:
description = "시스템 명령 실행 PHP 웹쉘"
severity = "critical"
strings:
$func1 = "system(" nocase
$func2 = "exec(" nocase
$func3 = "shell_exec(" nocase
$func4 = "passthru(" nocase
$func5 = "popen(" nocase
$func6 = "proc_open(" nocase
$input1 = "$_GET"
$input2 = "$_POST"
$input3 = "$_REQUEST"
condition:
any of ($func*) and any of ($input*)
}
rule PHP_Webshell_Assert {
meta:
description = "assert()를 이용한 PHP 웹쉘"
severity = "high"
strings:
$assert = /assert\s*\(\s*\$_(GET|POST|REQUEST)/ nocase
condition:
$assert
}
rule PHP_Webshell_Preg_Replace {
meta:
description = "preg_replace /e 플래그 악용"
severity = "high"
strings:
$preg = /preg_replace\s*\(\s*['"][^'"]*\/e['"]/ nocase
condition:
$preg
}
rule PHP_Webshell_Create_Function {
meta:
description = "create_function()을 이용한 웹쉘"
severity = "high"
strings:
$cf = /create_function\s*\([^)]*\$_(GET|POST|REQUEST)/ nocase
condition:
$cf
}
rule PHP_Webshell_Backtick {
meta:
description = "백틱을 이용한 명령 실행"
severity = "high"
strings:
$bt = /`[^`]*\$_(GET|POST|REQUEST)[^`]*`/
condition:
$bt
}
// ============================================
// PHP 웹쉘 - 난독화
// ============================================
rule PHP_Webshell_Base64_Eval {
meta:
description = "Base64 인코딩된 eval"
severity = "critical"
strings:
$pattern = /eval\s*\(\s*base64_decode\s*\(/ nocase
$b64_eval = "ZXZhbC" // base64("eval")
condition:
$pattern or $b64_eval
}
rule PHP_Webshell_Gzinflate {
meta:
description = "gzinflate 압축 난독화"
severity = "high"
strings:
$gz1 = /eval\s*\(\s*gzinflate\s*\(/ nocase
$gz2 = /eval\s*\(\s*gzuncompress\s*\(/ nocase
$gz3 = /eval\s*\(\s*gzdecode\s*\(/ nocase
condition:
any of them
}
rule PHP_Webshell_Str_Rot13 {
meta:
description = "str_rot13 난독화"
severity = "medium"
strings:
$rot = /eval\s*\(\s*str_rot13\s*\(/ nocase
condition:
$rot
}
rule PHP_Webshell_Chr_Obfuscation {
meta:
description = "chr() 함수를 이용한 난독화"
severity = "high"
strings:
$chr = /chr\s*\(\s*\d+\s*\)\s*\.\s*chr\s*\(\s*\d+\s*\)/
condition:
#chr > 5
}
rule PHP_Webshell_Hex_Obfuscation {
meta:
description = "16진수 문자열 난독화"
severity = "medium"
strings:
$hex = /\\x[0-9a-fA-F]{2}/
condition:
#hex > 10
}
rule PHP_Webshell_Variable_Function {
meta:
description = "변수를 함수로 호출"
severity = "high"
strings:
$vf1 = /\$\w+\s*\(\s*\$_(GET|POST|REQUEST)/
$vf2 = /\$\{\s*\$\w+\s*\}\s*\(/
condition:
any of them
}
rule PHP_Webshell_Multi_Encoding {
meta:
description = "다중 인코딩 레이어"
severity = "critical"
strings:
$enc1 = "base64_decode" nocase
$enc2 = "gzinflate" nocase
$enc3 = "gzuncompress" nocase
$enc4 = "str_rot13" nocase
$enc5 = "convert_uudecode" nocase
condition:
3 of them
}
// ============================================
// 유명 웹쉘 시그니처
// ============================================
rule Webshell_C99 {
meta:
description = "C99 웹쉘"
severity = "critical"
family = "c99"
strings:
$s1 = "c99shell" nocase
$s2 = "c99_buff_prepare"
$s3 = "c99_sess_put"
$s4 = "c99ftpbrutecheck"
$s5 = "c99fsearch"
condition:
2 of them
}
rule Webshell_R57 {
meta:
description = "R57 웹쉘"
severity = "critical"
family = "r57"
strings:
$s1 = "r57shell" nocase
$s2 = "r57_pwd_hash"
$s3 = "Safe_Mode Bypass"
$s4 = "| Encoder"
condition:
2 of them
}
rule Webshell_WSO {
meta:
description = "WSO (Web Shell by oRb)"
severity = "critical"
family = "wso"
strings:
$s1 = "Web Shell by oRb" nocase
$s2 = "wso_version"
$s3 = "FilesMan"
$s4 = "WSO" wide ascii
condition:
2 of them
}
rule Webshell_B374k {
meta:
description = "B374k 웹쉘"
severity = "critical"
family = "b374k"
strings:
$s1 = "b374k" nocase
$s2 = "b374k_config"
$s3 = "b374k 2"
condition:
any of them
}
rule Webshell_Weevely {
meta:
description = "Weevely 백도어"
severity = "critical"
family = "weevely"
strings:
$pattern = /\$\w=\$\w\(\'\',\$\w\(\$\w\(\$\w/
condition:
$pattern and filesize < 5KB
}
rule Webshell_China_Chopper {
meta:
description = "China Chopper 웹쉘"
severity = "critical"
family = "china_chopper"
strings:
$cp1 = /@eval($_POST[/ nocase
$cp2 = /<%eval request\(/ nocase
$cp3 = /<%@ Page Language="Jscript"%><%eval(/ nocase
condition:
any of them and filesize < 1KB
}
rule Webshell_Ani_Shell {
meta:
description = "Ani-Shell 웹쉘"
severity = "critical"
strings:
$s1 = "Ani-Shell"
$s2 = "ani_shell"
condition:
any of them
}
rule Webshell_PHPSpy {
meta:
description = "PHPSpy 웹쉘"
severity = "critical"
strings:
$s1 = "phpspy" nocase
$s2 = "Php Spy"
condition:
any of them
}
// ============================================
// 파일 업로드 공격
// ============================================
rule PHP_File_Upload_Attack {
meta:
description = "파일 업로드를 통한 웹쉘"
severity = "high"
strings:
$upload = "move_uploaded_file" nocase
$write1 = "file_put_contents" nocase
$write2 = "fwrite" nocase
$exec1 = "eval(" nocase
$exec2 = "system(" nocase
$exec3 = "exec(" nocase
condition:
($upload or $write1 or $write2) and any of ($exec*)
}
// ============================================
// 숨겨진 백도어
// ============================================
rule PHP_Hidden_Backdoor {
meta:
description = "HTTP 헤더를 통한 숨겨진 백도어"
severity = "critical"
strings:
$hdr1 = /\$_SERVER\s*\[\s*['"]HTTP_/ nocase
$hdr2 = "getallheaders()" nocase
$exec = /(eval|assert|system|exec)\s*\(/ nocase
condition:
any of ($hdr*) and $exec
}
rule PHP_Long_Encoded_String {
meta:
description = "긴 인코딩된 문자열 (난독화 의심)"
severity = "medium"
strings:
$long_b64 = /['"][a-zA-Z0-9+\/=]{500,}['"]/
condition:
$long_b64
}
// ============================================
// ASP/ASPX 웹쉘
// ============================================
rule ASPX_Webshell_Generic {
meta:
description = "ASP.NET 웹쉘"
severity = "high"
strings:
$asp1 = "Request.Form" nocase
$asp2 = "Request.QueryString" nocase
$asp3 = "Request.Item" nocase
$exec1 = "Process.Start" nocase
$exec2 = "cmd.exe" nocase
$exec3 = "powershell" nocase
condition:
any of ($asp*) and any of ($exec*)
}
rule ASP_Eval {
meta:
description = "ASP Eval 웹쉘"
severity = "high"
strings:
$eval = /Eval\s*\(\s*Request/ nocase
$exec = /Execute\s*\(\s*Request/ nocase
condition:
$eval or $exec
}
// ============================================
// JSP 웹쉘
// ============================================
rule JSP_Webshell_Generic {
meta:
description = "JSP 웹쉘"
severity = "high"
strings:
$param = "request.getParameter" nocase
$exec1 = "Runtime.getRuntime().exec" nocase
$exec2 = "ProcessBuilder" nocase
condition:
$param and ($exec1 or $exec2)
}
rule JSP_Cmd_Shell {
meta:
description = "JSP 명령 실행 쉘"
severity = "critical"
strings:
$rt = "Runtime.getRuntime()"
$cmd1 = "cmd.exe" nocase
$cmd2 = "/bin/sh"
$cmd3 = "/bin/bash"
condition:
$rt and any of ($cmd*)
}
// ============================================
// 일반 의심 패턴
// ============================================
rule Suspicious_PHP_Code {
meta:
description = "의심스러운 PHP 코드 패턴"
severity = "medium"
strings:
$s1 = "$GLOBALS[" nocase
$s2 = "call_user_func_array" nocase
$s3 = "array_map" nocase
$s4 = "array_filter" nocase
$s5 = "ReflectionFunction" nocase
$input = /\$_(GET|POST|REQUEST|COOKIE)/
condition:
2 of ($s*) and $input
}
rule Suspicious_Error_Suppression {
meta:
description = "에러 억제 + 위험 함수"
severity = "medium"
strings:
$suppress = /@(eval|assert|system|exec|shell_exec|passthru)\s*\(/
condition:
$suppress
}
// ============================================
// 이미지 위장 웹쉘
// ============================================
rule PHP_In_Image {
meta:
description = "이미지 파일 내 PHP 코드"
severity = "high"
strings:
$gif = { 47 49 46 38 } // GIF header
$jpg = { FF D8 FF } // JPEG header
$png = { 89 50 4E 47 } // PNG header
$php1 = "<?php"
$php2 = "<?"
$php3 = "<%"
condition:
($gif at 0 or $jpg at 0 or $png at 0) and
any of ($php*)
}