| Server IP : 119.195.102.159 / Your IP : 216.73.217.134 Web Server : nginx/1.18.0 System : Linux picell 5.15.0-181-generic #191-Ubuntu SMP Fri May 22 19:09:02 UTC 2026 x86_64 User : altablue ( 1000) PHP Version : 8.3.31 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /home/altablue/ |
Upload File : |
#!/bin/bash
# ============================================================
# π WordPress μ½μ΄ 체ν¬μ¬ κ²μ¦ (WP Checksum Verify)
# μλ² λ°°ν¬ κ²½λ‘: /home/altablue/wp_checksum_verify.sh
#
# WordPress.org 곡μ APIμμ μ 곡νλ 체ν¬μ¬κ³Ό
# λ‘컬 μ½μ΄ νμΌμ MD5 ν΄μλ₯Ό λΉκ΅νμ¬ λ³μ‘°λ₯Ό νμ§ν©λλ€.
#
# μ¬μ©λ²:
# ./wp_checksum_verify.sh --verify # λͺ¨λ μ¬μ΄νΈ μ½μ΄ νμΌ κ²μ¦
# ./wp_checksum_verify.sh --cache # 체ν¬μ¬ μΊμ κ°±μ
# ./wp_checksum_verify.sh --status # κ²μ¦ μν νμΈ
# ============================================================
set -uo pipefail
# ββ μ€μ λ‘λ βββββββββββββββββββββββββββββββββββββ
CONFIG_FILE="${CONFIG_FILE:-/etc/security-watchdog/config}"
if [ ! -f "$CONFIG_FILE" ]; then
echo "β μ€μ νμΌμ μ°Ύμ μ μμ΅λλ€: $CONFIG_FILE"
exit 1
fi
source "$CONFIG_FILE"
# ββ κ²½λ‘ μ€μ βββββββββββββββββββββββββββββββββββββ
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
NOTIFY="$SCRIPT_DIR/telegram_notify.sh"
LOG_FILE="${LOG_DIR}/wp_checksum.log"
VERIFIED_FILES_LIST="${BASELINE_DIR}/wp_verified_files.txt"
# μΊμ μ ν¨ μκ° (μ΄) β 24μκ°
CACHE_MAX_AGE=86400
mkdir -p "$LOG_DIR" "$BASELINE_DIR"
# ββ λ‘κ·Έ ν¨μ βββββββββββββββββββββββββββββββββββββ
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}
# ββ νμ λꡬ νμΈ ββββββββββββββββββββββββββββββββ
check_dependencies() {
local missing=()
if ! command -v curl &>/dev/null; then
missing+=("curl")
fi
if ! command -v python3 &>/dev/null; then
missing+=("python3")
fi
if ! command -v md5sum &>/dev/null; then
missing+=("md5sum")
fi
if [ ${#missing[@]} -gt 0 ]; then
log "β νμ λκ΅¬κ° μ€μΉλμ§ μμμ΅λλ€: ${missing[*]}"
echo "β νμ λκ΅¬κ° μ€μΉλμ§ μμμ΅λλ€: ${missing[*]}"
echo " μ€μΉ: sudo apt install ${missing[*]}"
exit 1
fi
}
# ββ WordPress λ²μ μΆμΆ βββββββββββββββββββββββββββ
# wp-includes/version.php μμ $wp_version κ°μ μΆμΆ
get_wp_version() {
local site_root="$1"
local version_file="${site_root}/wp-includes/version.php"
if [ ! -f "$version_file" ]; then
log " β οΈ version.php μμ: $version_file"
return 1
fi
local version
version=$(grep -oP "\\\$wp_version\s*=\s*'[^']+'" "$version_file" | grep -oP "'[^']+'" | tr -d "'")
if [ -z "$version" ]; then
log " β οΈ WordPress λ²μ μ μΆμΆν μ μμ΅λλ€: $version_file"
return 1
fi
echo "$version"
}
# ββ 체ν¬μ¬ λ€μ΄λ‘λ λ° μΊμ βββββββββββββββββββββββ
# WordPress.org APIμμ 체ν¬μ¬ JSONμ λ€μ΄λ‘λνκ³ μΊμμ μ μ₯
fetch_checksums() {
local version="$1"
local cache_file="${BASELINE_DIR}/wp_checksums_${version}.json"
# μΊμ μ ν¨μ± νμΈ (24μκ°)
if [ -f "$cache_file" ]; then
local cache_mtime
cache_mtime=$(stat -c%Y "$cache_file" 2>/dev/null || echo 0)
local now
now=$(date +%s)
local age=$(( now - cache_mtime ))
if [ "$age" -lt "$CACHE_MAX_AGE" ]; then
log " πΎ μΊμ μ¬μ© (${age}μ΄ κ²½κ³Ό): $cache_file"
echo "$cache_file"
return 0
fi
log " π μΊμ λ§λ£ (${age}μ΄ κ²½κ³Ό), μ¬λ€μ΄λ‘λ"
fi
# APIμμ 체ν¬μ¬ λ€μ΄λ‘λ
local api_url="https://api.wordpress.org/core/checksums/1.0/?version=${version}&locale=ko_KR"
log " π 체ν¬μ¬ λ€μ΄λ‘λ: $api_url"
local http_code
http_code=$(curl -s -o "$cache_file" -w "%{http_code}" --connect-timeout 10 --max-time 30 "$api_url" 2>/dev/null)
if [ "$http_code" != "200" ]; then
log " β API μμ² μ€ν¨ (HTTP $http_code): $api_url"
rm -f "$cache_file" 2>/dev/null
return 1
fi
# JSON μ ν¨μ± κΈ°λ³Έ κ²μ¦
if ! python3 -c "import json,sys; json.load(open(sys.argv[1]))" "$cache_file" 2>/dev/null; then
log " β μ ν¨νμ§ μμ JSON μλ΅: $cache_file"
rm -f "$cache_file" 2>/dev/null
return 1
fi
log " β
체ν¬μ¬ λ€μ΄λ‘λ μλ£: $cache_file"
echo "$cache_file"
return 0
}
# ββ μΊμ κ°±μ (λͺ¨λ μ¬μ΄νΈ) βββββββββββββββββββββββ
refresh_cache() {
log "π 체ν¬μ¬ μΊμ κ°±μ μμ"
check_dependencies
local refreshed=0
local failed=0
local versions_done=()
for dir in "${WATCH_DIRS[@]}"; do
if [ ! -d "$dir" ]; then
continue
fi
while IFS= read -r wp_config; do
local site_root
site_root=$(dirname "$wp_config")
local version
version=$(get_wp_version "$site_root") || continue
# λμΌ λ²μ μ€λ³΅ κ°±μ λ°©μ§
local already_done=false
for v in "${versions_done[@]+"${versions_done[@]}"}"; do
if [ "$v" = "$version" ]; then
already_done=true
break
fi
done
if [ "$already_done" = "true" ]; then
continue
fi
# κΈ°μ‘΄ μΊμ μμ ν κ°μ μ¬λ€μ΄λ‘λ
rm -f "${BASELINE_DIR}/wp_checksums_${version}.json" 2>/dev/null
if fetch_checksums "$version" >/dev/null; then
((refreshed++))
versions_done+=("$version")
else
((failed++))
fi
done < <(find "$dir" -maxdepth 3 -name "wp-config.php" -type f 2>/dev/null)
done
log "π μΊμ κ°±μ μλ£: ${refreshed}κ° λ²μ κ°±μ , ${failed}κ° μ€ν¨"
if [ "$failed" -gt 0 ]; then
"$NOTIFY" "WARNING" "π <b>체ν¬μ¬ μΊμ κ°±μ </b>
β
μ±κ³΅: ${refreshed}κ° λ²μ
β μ€ν¨: ${failed}κ° λ²μ "
else
log " β
λͺ¨λ μΊμ κ°±μ μ±κ³΅"
fi
}
# ββ κ°λ³ μ¬μ΄νΈ κ²μ¦ ββββββββββββββββββββββββββββββ
verify_site() {
local site_root="$1"
local site_name
site_name=$(basename "$site_root")
log " π μ¬μ΄νΈ κ²μ¦: $site_name ($site_root)"
# WordPress λ²μ μΆμΆ
local version
version=$(get_wp_version "$site_root")
if [ $? -ne 0 ] || [ -z "$version" ]; then
log " β οΈ WordPress λ²μ νμΈ λΆκ°, 건λλ: $site_root"
return 1
fi
log " π WordPress λ²μ : $version"
# 체ν¬μ¬ JSON κ°μ Έμ€κΈ° (μΊμ λλ λ€μ΄λ‘λ)
local cache_file
cache_file=$(fetch_checksums "$version")
if [ $? -ne 0 ] || [ -z "$cache_file" ] || [ ! -f "$cache_file" ]; then
log " β 체ν¬μ¬μ κ°μ Έμ¬ μ μμ΅λλ€ (v$version)"
return 1
fi
# JSONμμ 체ν¬μ¬ νμ±: "MD5ν΄μ νμΌκ²½λ‘" νμμΌλ‘ μΆλ ₯
local checksum_list
checksum_list=$(python3 -c "
import json, sys
d = json.load(open(sys.argv[1]))
checksums = d.get('checksums', {})
for k, v in checksums.items():
print(f'{v} {k}')
" "$cache_file" 2>/dev/null)
if [ -z "$checksum_list" ]; then
log " β 체ν¬μ¬ νμ± μ€ν¨: $cache_file"
return 1
fi
local total_files=0
local matched_count=0
local modified_count=0
local missing_count=0
local modified_files=()
local missing_files=()
local verified_paths=()
# κ° νμΌμ MD5 체ν¬μ¬ λΉκ΅
while IFS=' ' read -r expected_md5 rel_path; do
[ -z "$expected_md5" ] && continue
[ -z "$rel_path" ] && continue
((total_files++))
local full_path="${site_root}/${rel_path}"
if [ ! -f "$full_path" ]; then
((missing_count++))
missing_files+=("$rel_path")
continue
fi
# λ‘컬 νμΌ MD5 ν΄μ κ³μ°
local local_md5
local_md5=$(md5sum "$full_path" 2>/dev/null | cut -d' ' -f1)
if [ "$local_md5" = "$expected_md5" ]; then
((matched_count++))
verified_paths+=("$full_path")
else
((modified_count++))
modified_files+=("$rel_path")
log " π΄ λ³μ‘° κ°μ§: $rel_path"
log " 곡μ MD5: $expected_md5"
log " λ‘컬 MD5: $local_md5"
fi
done <<< "$checksum_list"
# wp-admin, wp-includes λ΄ κ³΅μ λͺ©λ‘μ μλ μΆκ° νμΌ νμ§
local extra_count=0
local extra_files=()
for check_dir in "wp-admin" "wp-includes"; do
local abs_dir="${site_root}/${check_dir}"
if [ ! -d "$abs_dir" ]; then
continue
fi
while IFS= read -r -d '' file; do
local rel
rel="${file#${site_root}/}"
# 체ν¬μ¬ λͺ©λ‘μ μλμ§ νμΈ
if ! echo "$checksum_list" | grep -qF " ${rel}"; then
((extra_count++))
extra_files+=("$rel")
fi
done < <(find "$abs_dir" -type f -name "*.php" -print0 2>/dev/null)
done
# κ²μ¦λ νμΌ λͺ©λ‘ κΈ°λ‘ (λ€λ₯Έ μ€ν¬λ¦½νΈμμ νμ©)
for vp in "${verified_paths[@]+"${verified_paths[@]}"}"; do
echo "$vp" >> "$VERIFIED_FILES_LIST"
done
log " π κ²°κ³Ό: μ 체=${total_files}, μΌμΉ=${matched_count}, λ³μ‘°=${modified_count}, λλ½=${missing_count}, μΆκ°=${extra_count}"
# κ²°κ³Όλ₯Ό stdoutμΌλ‘ λ°ν (νΈμΆμμμ μ§κ³μ©)
echo "SITE:${site_name}"
echo "ROOT:${site_root}"
echo "VERSION:${version}"
echo "TOTAL:${total_files}"
echo "MATCHED:${matched_count}"
echo "MODIFIED:${modified_count}"
echo "MISSING:${missing_count}"
echo "EXTRA:${extra_count}"
if [ ${#modified_files[@]} -gt 0 ]; then
echo "MODIFIED_FILES:"
for f in "${modified_files[@]}"; do
echo " ${f}"
done
fi
if [ ${#missing_files[@]} -gt 0 ]; then
echo "MISSING_FILES:"
for f in "${missing_files[@]}"; do
echo " ${f}"
done
fi
if [ ${#extra_files[@]} -gt 0 ]; then
echo "EXTRA_FILES:"
for f in "${extra_files[@]}"; do
echo " ${f}"
done
fi
echo "---"
}
# ββ 체ν¬μ¬ κ²μ¦ μλ£ μ¬λΆ νμΈ ββββββββββββββββββββ
# λ€λ₯Έ μ€ν¬λ¦½νΈ(malware_scanner.sh λ±)μμ νΈμΆνμ¬
# νΉμ νμΌμ΄ 곡μ μ½μ΄ νμΌμΈμ§ λΉ λ₯΄κ² νλ³
is_checksum_verified() {
local filepath="$1"
if [ ! -f "$VERIFIED_FILES_LIST" ]; then
return 1
fi
if grep -qxF "$filepath" "$VERIFIED_FILES_LIST" 2>/dev/null; then
return 0
fi
return 1
}
# ββ μ 체 κ²μ¦ μ€ν ββββββββββββββββββββββββββββββββ
verify_all() {
log "π WordPress μ½μ΄ 체ν¬μ¬ κ²μ¦ μμ"
check_dependencies
local scan_start
scan_start=$(date +%s)
# κ²μ¦ λͺ©λ‘ μ΄κΈ°ν
> "$VERIFIED_FILES_LIST"
local site_count=0
local total_modified=0
local total_missing=0
local total_extra=0
local total_matched=0
local all_modified_details=""
local overall_status="CLEAN"
# WATCH_DIRS λ΄ WordPress μ¬μ΄νΈ νμ
for dir in "${WATCH_DIRS[@]}"; do
if [ ! -d "$dir" ]; then
continue
fi
while IFS= read -r wp_config; do
local site_root
site_root=$(dirname "$wp_config")
((site_count++))
# μ¬μ΄νΈ κ²μ¦ μ€ν
local result
result=$(verify_site "$site_root")
# κ²°κ³Ό νμ±
local modified
modified=$(echo "$result" | grep "^MODIFIED:" | head -1 | cut -d: -f2)
local missing
missing=$(echo "$result" | grep "^MISSING:" | head -1 | cut -d: -f2)
local extra
extra=$(echo "$result" | grep "^EXTRA:" | head -1 | cut -d: -f2)
local matched
matched=$(echo "$result" | grep "^MATCHED:" | head -1 | cut -d: -f2)
local site_name
site_name=$(echo "$result" | grep "^SITE:" | head -1 | cut -d: -f2)
local version
version=$(echo "$result" | grep "^VERSION:" | head -1 | cut -d: -f2)
modified=${modified:-0}
missing=${missing:-0}
extra=${extra:-0}
matched=${matched:-0}
total_modified=$((total_modified + modified))
total_missing=$((total_missing + missing))
total_extra=$((total_extra + extra))
total_matched=$((total_matched + matched))
# λ³μ‘°λ νμΌ μμΈ μμ§ (ν
λ κ·Έλ¨ μλ¦Όμ©)
if [ "$modified" -gt 0 ]; then
overall_status="CRITICAL"
local mod_files
mod_files=$(echo "$result" | sed -n '/^MODIFIED_FILES:/,/^---$\|^[A-Z_]*:/p' | grep "^ " | head -10)
all_modified_details="${all_modified_details}
π <b>${site_name}</b> (v${version}):
${mod_files}"
elif [ "$missing" -gt 0 ] && [ "$overall_status" = "CLEAN" ]; then
overall_status="WARNING"
fi
done < <(find "$dir" -maxdepth 3 -name "wp-config.php" -type f 2>/dev/null)
done
local scan_end
scan_end=$(date +%s)
local duration=$((scan_end - scan_start))
# κ²μ¦λ νμΌ λͺ©λ‘ μ λ ¬ λ° μ€λ³΅ μ κ±°
if [ -f "$VERIFIED_FILES_LIST" ]; then
sort -u "$VERIFIED_FILES_LIST" -o "$VERIFIED_FILES_LIST"
local verified_total
verified_total=$(wc -l < "$VERIFIED_FILES_LIST")
log " π κ²μ¦ λͺ©λ‘ μμ±: ${verified_total}κ° νμΌ β $VERIFIED_FILES_LIST"
fi
log "π κ²μ¦ μλ£: ${site_count}κ° μ¬μ΄νΈ, μΌμΉ=${total_matched}, λ³μ‘°=${total_modified}, λλ½=${total_missing}, μΆκ°=${total_extra} (${duration}μ΄)"
# κ²°κ³Όμ λ°λ₯Έ μλ¦Ό
if [ "$overall_status" = "CRITICAL" ]; then
# λ³μ‘° νμΌ λ°κ²¬ β CRITICAL μλ¦Ό
"$NOTIFY" "CRITICAL" "π <b>WordPress μ½μ΄ λ³μ‘° κ°μ§!</b>
π <b>κ²μ¦ κ²°κ³Ό:</b>
ββββββββββββββββββββ
π μ¬μ΄νΈ: ${site_count}κ°
β
μΌμΉ: ${total_matched}κ°
π΄ λ³μ‘°: ${total_modified}κ°
β λλ½: ${total_missing}κ°
π μΆκ°: ${total_extra}κ°
β±οΈ μμ: ${duration}μ΄
π΄ <b>λ³μ‘°λ νμΌ:</b>
${all_modified_details}"
elif [ "$overall_status" = "WARNING" ]; then
# λλ½ νμΌλ§ μμ β WARNING μλ¦Ό
"$NOTIFY" "WARNING" "π <b>WordPress μ½μ΄ νμΌ λλ½ κ°μ§</b>
π <b>κ²μ¦ κ²°κ³Ό:</b>
ββββββββββββββββββββ
π μ¬μ΄νΈ: ${site_count}κ°
β
μΌμΉ: ${total_matched}κ°
β λλ½: ${total_missing}κ°
π μΆκ°: ${total_extra}κ°
β±οΈ μμ: ${duration}μ΄"
else
# λͺ¨λ μ μ β λ‘κ·Έλ§ κΈ°λ‘ (ν
λ κ·Έλ¨ μλ¦Ό μμ, λ
Έμ΄μ¦ κ°μ)
log " β
λͺ¨λ μ½μ΄ νμΌ μ²΄ν¬μ¬ μΌμΉ"
fi
# μ¬μ΄νΈλ₯Ό μ°Ύμ§ λͺ»ν κ²½μ°
if [ "$site_count" -eq 0 ]; then
log " β οΈ WordPress μ¬μ΄νΈλ₯Ό μ°Ύμ μ μμ΅λλ€"
echo "β οΈ WATCH_DIRS λ΄μμ wp-config.phpλ₯Ό μ°Ύμ μ μμ΅λλ€."
echo " κ°μ λμ: ${WATCH_DIRS[*]}"
fi
}
# ββ μν νμΈ βββββββββββββββββββββββββββββββββββββ
show_status() {
echo "π WordPress μ½μ΄ 체ν¬μ¬ κ²μ¦ μν"
echo "ββββββββββββββββββββββββββββββββββ"
echo ""
# μΊμλ 체ν¬μ¬ νμΌ νμ
echo "π¦ μΊμλ 체ν¬μ¬:"
local cache_count=0
for cache_file in "${BASELINE_DIR}"/wp_checksums_*.json; do
if [ ! -f "$cache_file" ]; then
continue
fi
((cache_count++))
local version
version=$(basename "$cache_file" | sed 's/wp_checksums_//;s/\.json//')
local cache_mtime
cache_mtime=$(stat -c%Y "$cache_file" 2>/dev/null || echo 0)
local now
now=$(date +%s)
local age=$(( now - cache_mtime ))
local age_hours=$(( age / 3600 ))
local age_status="β
"
if [ "$age" -ge "$CACHE_MAX_AGE" ]; then
age_status="β οΈ λ§λ£"
fi
local file_count
file_count=$(python3 -c "
import json, sys
d = json.load(open(sys.argv[1]))
print(len(d.get('checksums', {})))
" "$cache_file" 2>/dev/null || echo "?")
echo " ${age_status} v${version} β ${file_count}κ° νμΌ, ${age_hours}μκ° μ κ°±μ "
done
if [ "$cache_count" -eq 0 ]; then
echo " (μμ) β --cache λλ --verify λ‘ μμ±νμΈμ"
fi
echo ""
# κ²μ¦ λͺ©λ‘ μν
echo "π κ²μ¦ λͺ©λ‘:"
if [ -f "$VERIFIED_FILES_LIST" ]; then
local verified_count
verified_count=$(wc -l < "$VERIFIED_FILES_LIST" 2>/dev/null || echo 0)
local list_mtime
list_mtime=$(stat -c%Y "$VERIFIED_FILES_LIST" 2>/dev/null || echo 0)
local list_date
list_date=$(date -d "@${list_mtime}" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "μ μ μμ")
echo " β
${verified_count}κ° νμΌ κ²μ¦ μλ£"
echo " π
λ§μ§λ§ κ°±μ : ${list_date}"
echo " π κ²½λ‘: $VERIFIED_FILES_LIST"
else
echo " (μμ) β --verify λ‘ μμ±νμΈμ"
fi
echo ""
# κ°μ§λ μ¬μ΄νΈ λͺ©λ‘
echo "π WordPress μ¬μ΄νΈ:"
local found_sites=0
for dir in "${WATCH_DIRS[@]}"; do
if [ ! -d "$dir" ]; then
continue
fi
while IFS= read -r wp_config; do
local site_root
site_root=$(dirname "$wp_config")
local site_name
site_name=$(basename "$site_root")
local version
version=$(get_wp_version "$site_root" 2>/dev/null) || version="νμΈ λΆκ°"
((found_sites++))
echo " π ${site_name} β v${version} (${site_root})"
done < <(find "$dir" -maxdepth 3 -name "wp-config.php" -type f 2>/dev/null)
done
if [ "$found_sites" -eq 0 ]; then
echo " (μμ)"
fi
echo ""
# μ΅κ·Ό λ‘κ·Έ
echo "π μ΅κ·Ό κ²μ¦ λ‘κ·Έ:"
if [ -f "$LOG_FILE" ]; then
tail -5 "$LOG_FILE" | sed 's/^/ /'
else
echo " (λ‘κ·Έ μμ)"
fi
}
# ββ λ©μΈ ββββββββββββββββββββββββββββββββββββββββββ
case "${1:-}" in
--verify|-v)
verify_all
;;
--cache|-c)
refresh_cache
;;
--status|-s)
show_status
;;
--help|-h)
echo "π WordPress μ½μ΄ 체ν¬μ¬ κ²μ¦"
echo ""
echo "μ¬μ©λ²:"
echo " $0 --verify λͺ¨λ μ¬μ΄νΈ μ½μ΄ νμΌ κ²μ¦"
echo " $0 --cache 체ν¬μ¬ μΊμ κ°μ κ°±μ "
echo " $0 --status νμ¬ μν νμΈ"
echo " $0 --help λμλ§"
;;
*)
echo "β μ΅μ
μ μ§μ ν΄μ£ΌμΈμ. --help λ‘ μ¬μ©λ² νμΈ"
exit 1
;;
esac