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:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -15,6 +15,7 @@ conf/domains.map
|
||||
run/
|
||||
data/
|
||||
*.state
|
||||
*.lock
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
|
||||
983
docs/YARA_GUIDE.md
Normal file
983
docs/YARA_GUIDE.md
Normal 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
1125
docs/waf-integration.md
Normal file
File diff suppressed because it is too large
Load Diff
491
docs/webshell_rules.yar
Normal file
491
docs/webshell_rules.yar
Normal 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*)
|
||||
}
|
||||
Reference in New Issue
Block a user