PHP-Script / Shellscript: PHP-Backdoors auf einem Webserver finden

PHP-Script / Shellscript: PHP-Backdoors auf einem Webserver finden

Hier findet sich ein PHP-Script und ein Shellscript, welches dabei helfen soll PHP-Backdoors auf Webservern zu finden. Das Script ist natürlich nicht perfekt und findet auch viele false positives, aber es ist zumindest ein guter Anfang.

Beide durchsuchen alle relevanten Dateien rekursiv nach typischen PHP-Backdoor-Mustern und schreiben einen Abschluss-Report, ohne Dateien zu verändern. Danach kann der Report durchgesehen werden, um verdächtigte Dateien manuell nachzuprüfen.

PHP-Script:

<?php
// find_backdoors.php
// Usage: php find_backdoors.php /path/to/search [output_report_path]
// Writes matches into a temp file and renames to the final report when finished.

$root = $argv[1] ?? '.';
$reportPath = $argv[2] ?? 'report.txt';

// Optional: restrict to these file extensions (empty = all files)
$extensions = ['php','phtml','inc','php5','php7','html','txt'];

// Patterns to search for (PCRE, case-insensitive).
$patterns = [
    '/system\s*\(\s*\$_POST\b/i',
    '/system\s*\(\s*base64_decode\s*\(/i',
    '/\$_REQUEST\s*\[\s*[\'"]cmd[\'"]\s*\]/i',
    '/eval\s*\(\s*\$_POST\b/i',
    '/eval\s*\(\s*base64_decode\s*\(\s*\$_POST/i',
    '/\bpreg_replace\s*\(.*\/e[imsx]*\//i',
    '/base64_decode\s*\(\s*[\'"][A-Za-z0-9+\/]{80,}={0,2}[\'"]\s*\)/i',
    '/(gzinflate|gzuncompress|str_rot13)\s*\(/i',
];

// helper: check extension filter
function allowed_ext($path, $exts) {
    if (empty($exts)) return true;
    $ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
    return in_array($ext, $exts, true);
}

// Prepare atomic temp file in same directory as final report (best for rename)
$reportDir = dirname($reportPath) === '' ? getcwd() : dirname($reportPath);
$tempFile = $reportDir . DIRECTORY_SEPARATOR . 'report_' . getmypid() . '.tmp';

// open temp file for writing (will overwrite if exists for this pid)
if (($out = @fopen($tempFile, 'w')) === false) {
    fwrite(STDERR, "ERROR: cannot open temp file for writing: {$tempFile}\n");
    exit(2);
}

// iterate files recursively
$it = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator($root, RecursiveDirectoryIterator::SKIP_DOTS)
);

foreach ($it as $fileInfo) {
    if (!$fileInfo->isFile() || !$fileInfo->isReadable()) continue;
    $filePath = $fileInfo->getPathname();
    if (!allowed_ext($filePath, $extensions)) continue;

    // open as text and iterate lines to get line numbers
    try {
        $fh = new SplFileObject($filePath, 'r');
    } catch (RuntimeException $e) {
        continue;
    }

    $lineNo = 0;
    while (!$fh->eof()) {
        $line = $fh->fgets();
        $lineNo++;
        if ($line === "" || trim($line) === "") continue;

        foreach ($patterns as $pat) {
            if (preg_match($pat, $line, $m)) {
                $snippet = trim($line);
                $snippet = preg_replace("/\s+/", " ", $snippet);
                if (strlen($snippet) > 300) {
                    $snippet = substr($snippet, 0, 290) . '...';
                }
                // Format: file:line:pattern:snippet
                $outLine = "{$filePath}:{$lineNo}: {$pat} : {$snippet}\n";
                fwrite($out, $outLine);
                // break to avoid duplicate hits on same line (optional)
                break;
            }
        }
    }
}

// close temp file and atomically rename to final report
fclose($out);

// try to rename (overwrite if exists)
if (@rename($tempFile, $reportPath) === false) {
    // fallback: try copy + unlink
    if (@copy($tempFile, $reportPath) && @unlink($tempFile)) {
        fwrite(STDOUT, "Report written to: {$reportPath}\n");
    } else {
        fwrite(STDERR, "ERROR: failed to move temp report to {$reportPath}\n");
        // keep temp file for inspection
        exit(3);
    }
} else {
    fwrite(STDOUT, "Report written to: {$reportPath}\n");
}

exit(0);

Shellscript:

#!/usr/bin/env bash
set -euo pipefail

# find_backdoors.sh
# Usage:
#   ./find_backdoors.sh                # uses default ROOT and writes ./report_YYYY-MM-DD.txt
#   ./find_backdoors.sh /path/to/root
#   ./find_backdoors.sh /path/to/root /path/to/report.txt

ROOT_DEFAULT="/var/www/vhosts/"
ROOT="${1:-$ROOT_DEFAULT}"

# default report: report_CURRENTDATE.txt (YYYY-MM-DD)
REPORT="${2:-report_$(date +%F).txt}"

# TMP placed beside final report for atomic move
REPORT_DIR="$(dirname "$REPORT")"
if [[ -z "$REPORT_DIR" || "$REPORT_DIR" == "." ]]; then
  TMP="./report_$$.tmp"
else
  TMP="$REPORT_DIR/report_$$.tmp"
fi
: > "$TMP"

# Exclude common large dirs
EXCLUDE_GLOBS=( --glob '!.git' --glob '!node_modules' --glob '!vendor' )

# Patterns (PCRE2 / -P style)
PATTERNS=(
  '(?i)system\s*\(\s*\$_POST\b'
  '(?i)system\s*\(\s*base64_decode\s*\('
  '(?i)\$_REQUEST\s*\[\s*[\"\x27]cmd[\"\x27]\s*\]'
  '(?i)eval\s*\(\s*\$_POST\b'
  '(?i)eval\s*\(\s*base64_decode\s*\(\s*\$_POST'
  '(?i)preg_replace\s*\(.*\/e'
  '(?i)base64_decode\s*\(\s*[\"\x27][A-Za-z0-9+/]{80,}={0,2}[\"\x27]\s*\)'
  '(?i)(gzinflate|gzuncompress|str_rot13)\s*\('
)

# prefer ripgrep
if command -v rg >/dev/null 2>&1; then
  RG_BASE=(rg --pcre2 -nH --hidden -S "${EXCLUDE_GLOBS[@]}" "$ROOT")
  for p in "${PATTERNS[@]}"; do
    "${RG_BASE[@]}" -e "$p" >> "$TMP" || true
  done
else
  # grep fallback - limit to common text/web extensions
  GREP_BASE=(grep -RInP --binary-files=without-match --exclude-dir=.git --exclude-dir=node_modules --exclude-dir=vendor)
  INCLUDES=(--include='*.php' --include='*.phtml' --include='*.inc' --include='*.html' --include='*.txt')
  for p in "${PATTERNS[@]}"; do
    "${GREP_BASE[@]}" "${INCLUDES[@]}" -e "$p" "$ROOT" >> "$TMP" || true
  done
fi

# atomic move
mv -f "$TMP" "$REPORT"

printf 'Report written to: %s\n' "$REPORT"

Welche Möglichkeiten gibt es sonst noch um bösartige Dateien zu finden?

ClamAV hat jedoch nicht erfolgreich eine Test-Webshell gefunden:

[{"Expires":1,"Discard":false,"Value":"<?php exit; echo isset($_POST['SAMPLE']) ? eval($_POST['SAMPLE']) : die('no'); ?>","Path":"\/","Name":"SAMPLE","Domain":"localhost","Secure":false,"Httponly":false,"Max-Age":3}]

Ich habe testweise eine solche Backdoor bei virustotal hochgeladen und dabei sind (nur) 6 Virenscanner angesprungen:

Darunter das sehr einfach zu nutzende und kostenlose Trend Micro House Call. So könnte etwa ein Backup aller Dateien erstellt und heruntergeladen werden, dann lokal auf eurem Windows-Rechner entpackt werden, um direkt danach einen Scan mit TrendMicro House Call auszuführen.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert