首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 實作 TCP 連接埠掃描器:服務可用性檢測與 Banner Grabbing

     編輯:PHP 綜合知識

應用場景

  • 伺服器服務狀態監控(HTTP、SSH、MySQL、Redis 等)
  • 網路安全審計:發現開放連接埠與服務版本
  • 防火牆規則驗證
  • CI/CD 部署前依賴服務就緒檢查

核心實作:Socket 連線與 Banner 擷取

<?php
/**
 * TCP 連接埠掃描與 Banner Grabbing
 * @param string $host 目標主機(IP 或域名)
 * @param int $port 連接埠號
 * @param float $timeout 連線逾時(秒)
 * @return array ['success' => bool, 'banner' => string, 'error' => string]
 */
function scanPort(string $host, int $port, float $timeout = 3.0): array {
    $socket = @fsockopen($host, $port, $errno, $errstr, $timeout);
    
    if (!$socket) {
        return [
            'success' => false,
            'banner'  => '',
            'error'   => "Connection failed ($errno): $errstr"
        ];
    }

    // 設定讀取逾時
    stream_set_timeout($socket, $timeout);

    // 針對常見協議發送適當請求以誘導 Banner 回應
    $request = match ($port) {
        21    => "USER anonymous\r\n",           // FTP
        22    => "",                             // SSH 會主動發送 Banner
        25    => "EHLO scanner\r\n",             // SMTP
        80    => "HEAD / HTTP/1.0\r\n\r\n",      // HTTP
        110   => "USER test\r\n",                // POP3
        143   => "A001 CAPABILITY\r\n",          // IMAP
        3306  => "\x0a",                         // MySQL 握手包觸發
        6379  => "PING\r\n",                     // Redis
        default => "\r\n",                       // 通用換行嘗試
    };

    if ($request) {
        fwrite($socket, $request);
    }

    // 讀取回應(限制大小防止記憶體耗盡)
    $banner = '';
    while (!feof($socket) && strlen($banner) < 4096) {
        $banner .= fread($socket, 1024);
        $meta = stream_get_meta_data($socket);
        if ($meta['timed_out']) break;
    }

    fclose($socket);

    return [
        'success' => true,
        'banner'  => trim($banner),
        'error'   => ''
    ];
}

// 批次掃描範例
$targets = [
    ['example.com', 80],
    ['example.com', 443],
    ['192.168.1.1', 22],
    ['db.internal', 3306],
];

foreach ($targets as [$host, $port]) {
    $result = scanPort($host, $port);
    $status = $result['success'] ? '✅ 開放' : '❌ 關閉/過濾';
    echo "{$host}:{$port} {$status}\n";
    if ($result['banner']) {
        echo "  Banner: " . str_replace(["\r", "\n"], ['\\r', '\\n'], $result['banner']) . "\n";
    }
}

進階功能擴充

1. 非同步並發掃描(Swoole / ReactPHP)

// 概念示意:使用 Swoole 協程並發
use Swoole\Coroutine;

Coroutine\run(function () {
    $hosts = ['192.168.1.1', '192.168.1.2', '192.168.1.3'];
    $ports = [22, 80, 443, 3306];
    
    $channel = new Swoole\Coroutine\Channel(count($hosts) * count($ports));
    
    foreach ($hosts as $host) {
        foreach ($ports as $port) {
            Coroutine::create(function () use ($host, $port, $channel) {
                $result = scanPort($host, $port, 1.0);
                $channel->push([$host, $port, $result]);
            });
        }
    }
    
    for ($i = 0; $i < count($hosts) * count($ports); $i++) {
        [$host, $port, $result] = $channel->pop();
        // 處理結果...
    }
});

2. 服務指紋識別

function identifyService(string $banner, int $port): string {
    $signatures = [
        'SSH-'           => 'SSH',
        '220 '           => 'FTP',
        '220-'           => 'FTP',
        'HTTP/'          => 'HTTP',
        'Server:'        => 'HTTP',
        'MySQL'          => 'MySQL',
        'Redis'          => 'Redis',
        'PostgreSQL'     => 'PostgreSQL',
        'Microsoft-IIS'  => 'IIS',
        'Apache'         => 'Apache',
        'nginx'          => 'Nginx',
    ];
    
    foreach ($signatures as $sig => $name) {
        if (stripos($banner, $sig) !== false) {
            return $name;
        }
    }
    return 'Unknown';
}

3. 結果輸出格式化(JSON / CSV / Table)

function outputResults(array $results, string $format = 'table'): void {
    match ($format) {
        'json' => print(json_encode($results, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)),
        'csv'  => {
            echo "Host,Port,Status,Service,Banner\n";
            foreach ($results as $r) {
                echo "{$r['host']},{$r['port']}," . 
                     ($r['success'] ? 'Open' : 'Closed') . "," .
                     $r['service'] . "," .
                     '"' . str_replace('"', '""', $r['banner']) . "\"\n";
            }
        },
        default => {
            // 表格輸出
            printf("%-20s %-6s %-8s %-15s %s\n", 'Host', 'Port', 'Status', 'Service', 'Banner');
            foreach ($results as $r) {
                printf("%-20s %-6d %-8s %-15s %s\n", 
                    $r['host'], $r['port'], 
                    $r['success'] ? 'Open' : 'Closed',
                    $r['service'],
                    mb_strimwidth($r['banner'], 0, 60, '...')
                );
            }
        }
    };
}

法律與道德提醒

⚠️ 重要提醒
  • 僅對擁有授權的系統進行掃描
  • 未經授權的連接埠掃描可能違反《電腦犯罪條例》等法規
  • 企業環境請納入資安政策,限定掃描範圍與頻率
  • 生產環境監控建議使用專業工具(Nmap、Zabbix、Prometheus Blackbox Exporter)

總結

PHP 的 fsockopen/stream_socket_client 提供了輕量級的 TCP 連線能力,配合協議特定請求可實作基礎的連接埠掃描與 Banner Grabbing。實務部署時應注意:非同步並發提升效率、服務指紋識別增加價值、嚴格遵守法律授權範圍。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved