From 93b02d012485a93b5c6a4db6565ac1724cbf768d Mon Sep 17 00:00:00 2001 From: pdmarf <135653545+pdmarf@users.noreply.github.com> Date: Fri, 17 Apr 2026 21:51:27 +0100 Subject: [PATCH] Initial commit: consolidate security scripts Bring in check-nextjs-rce.sh and README-scanner.md from existing Gitea repo, plus npm-security-check.sh from local bin/security. --- README-scanner.md | 71 +++++++++++ check-nextjs-rce.sh | 123 +++++++++++++++++++ npm-security-check.sh | 267 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 461 insertions(+) create mode 100644 README-scanner.md create mode 100755 check-nextjs-rce.sh create mode 100755 npm-security-check.sh diff --git a/README-scanner.md b/README-scanner.md new file mode 100644 index 0000000..07d6809 --- /dev/null +++ b/README-scanner.md @@ -0,0 +1,71 @@ +# Next.js RCE Vulnerability Scanner + +Quick scanner for CVE-2025-66478 / CVE-2025-55182 (CVSS 10.0) + +## Usage + +```bash +curl -o check-nextjs-rce.sh http://100.120.125.113:3000/pdm/security-tools/raw/branch/main/check-nextjs-rce.sh + chmod +x check-nextjs-rce.sh + sudo ./check-nextjs-rce.sh +``` + +## What it checks + +- Scans all package.json files on the system +- Checks Docker containers for Next.js +- Identifies vulnerable versions (15.0-15.5.6, 16.0-16.0.6) + +## Patched Versions + +- Next.js 15.5.7+ +- Next.js 16.0.7+ + +## How to Update Next.js + +### For npm projects: +```bash +# Update to latest patched version +npm install next@latest + +# Or specify exact version +npm install next@15.5.7 +``` + +### For yarn projects: +```bash +# Update to latest patched version +yarn upgrade next@latest + +# Or specify exact version +yarn upgrade next@15.5.7 +``` + +### For Docker containers: +```bash +# 1. Update package.json in your project +sed -i 's/"next": "15\.[0-5]\.[0-6]"/"next": "15.5.7"/g' package.json + +# 2. Rebuild Docker image +docker compose build + +# 3. Restart container +docker compose down +docker compose up -d + +# 4. Verify version +docker compose exec npm list next +``` + +### Verify the update: +```bash +# Check installed version +npm list next +# or +yarn list next + +# Verify no vulnerabilities remain +npm audit +# or +yarn audit +``` diff --git a/check-nextjs-rce.sh b/check-nextjs-rce.sh new file mode 100755 index 0000000..837f18a --- /dev/null +++ b/check-nextjs-rce.sh @@ -0,0 +1,123 @@ +#!/bin/bash +# Next.js CVE-2025-66478 / CVE-2025-55182 Vulnerability Checker +# Checks if Next.js installations are vulnerable to critical RCE + +echo "=== Next.js RCE Vulnerability Scanner ===" +echo "CVE-2025-66478 / CVE-2025-55182 (CVSS 10.0)" +echo "" + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +VULNERABLE=0 +SAFE=0 +UNKNOWN=0 + +# Function to check if version is vulnerable +check_version() { + local version=$1 + local major=$(echo $version | cut -d. -f1) + local minor=$(echo $version | cut -d. -f2) + local patch=$(echo $version | cut -d. -f3) + + # Vulnerable versions: + # 15.0.0 - 15.0.4 + # 15.1.0 - 15.1.8 + # 15.2.0 - 15.2.5 + # 15.3.0 - 15.3.5 + # 15.4.0 - 15.4.7 + # 15.5.0 - 15.5.6 + # 16.0.0 - 16.0.6 + + if [ "$major" = "15" ]; then + if [ "$minor" = "0" ] && [ "$patch" -le "4" ]; then + return 1 # Vulnerable + elif [ "$minor" = "1" ] && [ "$patch" -le "8" ]; then + return 1 + elif [ "$minor" = "2" ] && [ "$patch" -le "5" ]; then + return 1 + elif [ "$minor" = "3" ] && [ "$patch" -le "5" ]; then + return 1 + elif [ "$minor" = "4" ] && [ "$patch" -le "7" ]; then + return 1 + elif [ "$minor" = "5" ] && [ "$patch" -le "6" ]; then + return 1 + fi + elif [ "$major" = "16" ]; then + if [ "$minor" = "0" ] && [ "$patch" -le "6" ]; then + return 1 + fi + fi + + return 0 # Safe +} + +echo "Searching for Next.js installations..." +echo "" + +# Method 1: Check package.json files +find / -name "package.json" -type f 2>/dev/null | while read pkg; do + next_version=$(grep -o '"next"[[:space:]]*:[[:space:]]*"[^"]*"' "$pkg" 2>/dev/null | grep -o '[0-9][0-9.]*' | head -1) + + if [ -n "$next_version" ]; then + echo "Found: $pkg" + echo " Next.js version: $next_version" + + if check_version "$next_version"; then + echo -e " Status: ${GREEN}SAFE${NC}" + SAFE=$((SAFE + 1)) + else + echo -e " Status: ${RED}VULNERABLE${NC} - Update to 15.5.7+ or 16.0.7+" + VULNERABLE=$((VULNERABLE + 1)) + fi + echo "" + fi +done + +# Method 2: Check Docker containers +echo "Checking Docker containers..." +docker ps --format '{{.Names}}' 2>/dev/null | while read container; do + echo "Checking container: $container" + + # Try to find Next.js version in container + next_version=$(docker exec "$container" sh -c 'cat /*/package.json 2>/dev/null | grep -o "\"next\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" | grep -o "[0-9][0-9.]*" | head -1' 2>/dev/null) + + if [ -n "$next_version" ]; then + echo " Next.js version: $next_version" + + if check_version "$next_version"; then + echo -e " Status: ${GREEN}SAFE${NC}" + else + echo -e " Status: ${RED}VULNERABLE${NC}" + fi + else + echo -e " Status: ${YELLOW}No Next.js found${NC}" + fi + echo "" +done + +echo "=== Summary ===" +echo -e "${GREEN}Safe installations: $SAFE${NC}" +echo -e "${RED}Vulnerable installations: $VULNERABLE${NC}" +echo "" + +if [ $VULNERABLE -gt 0 ]; then + echo -e "${RED}⚠️ ACTION REQUIRED${NC}" + echo "Vulnerable Next.js installations found!" + echo "" + echo "Patched versions:" + echo " - Next.js 15.0.5, 15.1.9, 15.2.6, 15.3.6, 15.4.8, 15.5.7+" + echo " - Next.js 16.0.7+" + echo "" + echo "Update command:" + echo " npm install next@latest" + echo " # or" + echo " yarn upgrade next@15.5.7" + exit 1 +else + echo -e "${GREEN}✓ All Next.js installations are safe${NC}" + exit 0 +fi diff --git a/npm-security-check.sh b/npm-security-check.sh new file mode 100755 index 0000000..faad061 --- /dev/null +++ b/npm-security-check.sh @@ -0,0 +1,267 @@ +#!/usr/bin/env bash +# npm-security-check.sh +# Scans for NPM/Node.js malware indicators on this VM. + +set -euo pipefail + +HOSTNAME=$(hostname) +DATE=$(date) +LOGFILE="${1:-npm_security_check_${HOSTNAME}_$(date +%Y%m%d_%H%M%S).log}" + +RED='\033[0;31m' +YELLOW='\033[1;33m' +GREEN='\033[0;32m' +BOLD='\033[1m' +RESET='\033[0m' + +ISSUES=0 +WARNINGS=0 + +log() { echo "$*" | tee -a "$LOGFILE"; } +header() { log ""; log "=========================================="; log "$*"; log "=========================================="; } +ok() { log "$(printf "${GREEN}✓${RESET} %s" "$*")"; } +warn() { log "$(printf "${YELLOW}⚠${RESET} %s" "$*")"; (( WARNINGS++ )) || true; } +fail() { log "$(printf "${RED}✗${RESET} %s" "$*")"; (( ISSUES++ )) || true; } + +# ── Header ──────────────────────────────────────────────────────────────────── +log "==========================================" +log " NPM / Node.js Security Check" +log "==========================================" +log "Hostname : $HOSTNAME" +log "Date : $DATE" +log "Log file : $LOGFILE" + +# ── 1. Global npm packages ──────────────────────────────────────────────────── +header "1. Global npm packages" + +KNOWN_GOOD_GLOBALS="npm corepack" +if command -v npm &>/dev/null; then + GLOBALS=$(npm list -g --depth=0 2>/dev/null | tail -n +2 | sed 's/.*── //') + log "$GLOBALS" + # Flag anything that looks like a typosquat or known-bad package + SUSPICIOUS_PATTERNS="(plain-crypto-js|axios-[0-9]|node-fetch-[0-9]{3}|colors-js|event-stream|flatmap-stream|ua-parser-js|coa@|rc@[0-9]|nodemailer-[0-9]{3})" + HITS=$(echo "$GLOBALS" | grep -E "$SUSPICIOUS_PATTERNS" || true) + if [[ -n "$HITS" ]]; then + fail "Suspicious global package(s) found:" + log "$HITS" + else + ok "No suspicious global packages" + fi +else + warn "npm not found in PATH" +fi + +# ── 2. Known malicious packages in lock files ───────────────────────────────── +header "2. Malicious package names in lock files" + +BAD_PKGS=( + "plain-crypto-js" + "axios-proxy" + "node-colors" + "colors-js" + "event-stream" + "flatmap-stream" + "ua-parser-js" + "getcookies" +) + +LOCKFILES=$(find / -name "package-lock.json" -o -name "yarn.lock" -o -name "pnpm-lock.yaml" \ + 2>/dev/null | grep -v node_modules | grep -v "\.vscode-server" | grep -v "\.cache") || true + +if [[ -z "$LOCKFILES" ]]; then + warn "No lock files found to scan" +else + COUNT=$(echo "$LOCKFILES" | wc -l) + log "Scanning $COUNT lock file(s)..." + for pkg in "${BAD_PKGS[@]}"; do + MATCHES=$(echo "$LOCKFILES" | xargs grep -l "\"$pkg\"" 2>/dev/null || true) + if [[ -n "$MATCHES" ]]; then + fail "Found '$pkg' in: $MATCHES" + fi + done + ok "No known-malicious package names found" +fi + +# ── 3. Node processes and their origin ──────────────────────────────────────── +header "3. Running Node/Next.js processes" + +NODE_PROCS=$(ps aux | grep -E "[n]ode|[n]ext-server|[n]pm|[n]px|[p]npm" | grep -v grep || true) + +if [[ -z "$NODE_PROCS" ]]; then + ok "No Node.js processes running" +else + log "$NODE_PROCS" + # Check for processes running as root outside of Docker containers + ROOT_PROCS=$(echo "$NODE_PROCS" | awk '$1 == "root" {print}' || true) + if [[ -n "$ROOT_PROCS" ]]; then + # Check if each root process is inside a Docker cgroup (normal) + while IFS= read -r proc; do + PID=$(echo "$proc" | awk '{print $2}') + CGROUP=$(cat /proc/"$PID"/cgroup 2>/dev/null | grep -c "docker" || true) + if [[ "$CGROUP" -gt 0 ]]; then + ok "PID $PID runs as root but is inside a Docker container (normal)" + else + warn "PID $PID is a root Node process outside Docker — review manually" + log " Command: $(cat /proc/"$PID"/cmdline 2>/dev/null | tr '\0' ' ' || echo 'unreadable')" + fi + done <<< "$ROOT_PROCS" + else + ok "No root-owned Node processes outside Docker" + fi +fi + +# ── 4. Outbound network connections from Node processes ─────────────────────── +header "4. Node process network connections" + +if command -v lsof &>/dev/null; then + NODE_CONNS=$(lsof -i TCP -a -c node -a -s TCP:ESTABLISHED 2>/dev/null || true) + if [[ -n "$NODE_CONNS" ]]; then + log "$NODE_CONNS" + # Flag connections to non-443/80 ports on public IPs + UNUSUAL=$(echo "$NODE_CONNS" | awk '!/localhost|127\.0\.0|192\.168|10\.|172\.(1[6-9]|2[0-9]|3[01])\.|:443|:80|:22/ && /ESTABLISHED/' || true) + if [[ -n "$UNUSUAL" ]]; then + warn "Unusual outbound Node connections (non-standard ports or IPs):" + log "$UNUSUAL" + else + ok "Node connections look normal (443/80 or private IPs)" + fi + else + ok "No established TCP connections from node processes" + fi +else + # Fallback to ss + ALL_CONNS=$(ss -tnp 2>/dev/null | grep "node\|npm" || true) + if [[ -n "$ALL_CONNS" ]]; then + log "$ALL_CONNS" + else + ok "No Node network connections found" + fi +fi + +# ── 5. Known C2 indicators ──────────────────────────────────────────────────── +header "5. Known C2 / malware indicators" + +# From previous axios supply-chain attack (Apr 2025 npm incident) +C2_IPS=("142.11.206.73" "185.220.101" "194.165.16") +C2_DOMAINS=("sfrclak.com" "discordapp.com/api/webhooks" "ngrok.io") + +ACTIVE_CONNS=$(ss -tn 2>/dev/null || netstat -tn 2>/dev/null || true) + +FOUND_C2=false +for ip in "${C2_IPS[@]}"; do + if echo "$ACTIVE_CONNS" | grep -q "$ip"; then + fail "Active connection to known C2 IP: $ip" + FOUND_C2=true + fi +done + +for domain in "${C2_DOMAINS[@]}"; do + if echo "$ACTIVE_CONNS" | grep -q "$domain"; then + fail "Active connection to suspicious domain: $domain" + FOUND_C2=true + fi +done + +if ! $FOUND_C2; then + ok "No connections to known C2 infrastructure" +fi + +# ── 6. Suspicious processes (miners, RATs) ──────────────────────────────────── +header "6. Suspicious process names" + +SUSPICIOUS_PROCS="(xmrig|minerd|cpuminer|kdevtmpfsi|kinsing|/tmp/[a-z0-9]{8,}|/dev/shm/)" +# Match processes whose executable path (field 11) starts with ../ — not args containing ../ +HITS=$(ps aux | grep -E "$SUSPICIOUS_PROCS" | grep -v grep || true) +DOTDOT=$(ps aux | grep -v grep | awk '$11 ~ /^\.\.\// {print}' || true) +[[ -n "$DOTDOT" ]] && HITS="$HITS +$DOTDOT" +if [[ -n "$HITS" ]]; then + fail "Suspicious processes detected:" + log "$HITS" +else + ok "No suspicious process names" +fi + +# ── 7. Suspicious files in temp directories ─────────────────────────────────── +header "7. Suspicious files in /tmp and /dev/shm" + +for dir in /tmp /dev/shm /var/tmp; do + EXEC_FILES=$(find "$dir" -type f -executable 2>/dev/null | head -20 || true) + JS_FILES=$(find "$dir" -name "*.js" -o -name "*.mjs" 2>/dev/null | head -10 || true) + if [[ -n "$EXEC_FILES" ]]; then + warn "Executable files in $dir:" + log "$EXEC_FILES" + fi + if [[ -n "$JS_FILES" ]]; then + warn "JS files in $dir:" + log "$JS_FILES" + fi +done +ok "Temp directory scan complete" + +# ── 8. npm configuration ────────────────────────────────────────────────────── +header "8. npm configuration" + +if [[ -f "$HOME/.npmrc" ]]; then + log "$(cat "$HOME/.npmrc")" + # Check for non-official registry + ALT_REGISTRY=$(grep -v "^#" "$HOME/.npmrc" | grep "registry" | grep -v "registry.npmjs.org" || true) + if [[ -n "$ALT_REGISTRY" ]]; then + warn "Non-official npm registry configured: $ALT_REGISTRY" + else + ok ".npmrc uses official registry" + fi +else + warn "No .npmrc found (registry defaults to npmjs.org — acceptable)" +fi + +# ── 9. Docker containers quick review ──────────────────────────────────────── +header "9. Docker containers" + +if command -v docker &>/dev/null && docker ps &>/dev/null 2>&1; then + docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}" 2>/dev/null | tee -a "$LOGFILE" + # Flag containers with no named image (just an image ID) + UNNAMED=$(docker ps --format "{{.Names}} {{.Image}}" 2>/dev/null | awk '$2 ~ /^[0-9a-f]{12}$/' || true) + if [[ -n "$UNNAMED" ]]; then + warn "Container(s) using unnamed image IDs (verify these are known):" + log "$UNNAMED" + else + ok "All containers use named images" + fi +else + warn "Docker not available or not accessible" +fi + +# ── 10. Bash history spot-check ─────────────────────────────────────────────── +header "10. Bash history — suspicious patterns" + +HIST_FILE="${HISTFILE:-$HOME/.bash_history}" +if [[ -f "$HIST_FILE" ]]; then + # Look for obfuscated execution patterns (not internal curl to known Tailscale IPs) + SUSPICIOUS_HIST=$(grep -E "(eval\s*\$|base64\s*-d|python.*exec|perl.*eval|/dev/tcp/|bash.*<\(curl.*[^1][^0][^0]\.)" \ + "$HIST_FILE" 2>/dev/null | grep -vE "100\.[0-9]+\.[0-9]+\.[0-9]+" | tail -20 || true) + if [[ -n "$SUSPICIOUS_HIST" ]]; then + warn "Potentially suspicious history entries:" + log "$SUSPICIOUS_HIST" + else + ok "No obviously suspicious history entries" + fi +else + warn "Bash history file not found at $HIST_FILE" +fi + +# ── Summary ──────────────────────────────────────────────────────────────────── +header "SUMMARY" +log "Scan completed at: $(date)" +log "Results saved to : $LOGFILE" +log "" +if [[ $ISSUES -gt 0 ]]; then + log "$(printf "${RED}✗ %d issue(s) found — review output above${RESET}" "$ISSUES")" + exit 1 +elif [[ $WARNINGS -gt 0 ]]; then + log "$(printf "${YELLOW}⚠ Clean but %d warning(s) — review output above${RESET}" "$WARNINGS")" + exit 0 +else + log "$(printf "${GREEN}✓ All checks passed — no indicators of compromise${RESET}")" + exit 0 +fi