========================================== The Shadow Penguin Documents. No. 18 - ポートスキャン Ver2 - Written by UNYUN March.1, 1998 ========================================== - 目次 - 1. はじめに 2. ポートスキャン概要 3. Full-Connection型スキャン 4. Half-Open型スキャン 5. Stealth型スキャン 6. Half-Open型スキャン、Stealthスキャンの検出 7. 非Full-ConnectによるOSの判別法 8. UDPポートスキャン 9. Simple Stealth 使用法 10. UDPscan使用法 11. さいごに 1. はじめに 侵入者は、ターゲットサーバに侵入する前準備として、そのサーバが開いているポート、およびサーバのOS の特定を試みる場合があります。これにより、サーバに存在するセキュリティホールの可能性をチェックます。 OSのバージョン等によっては、致命的なポートもありますので、ポートスキャンは非常に有効な「下調べ」と なります。このテキストでは、様々な方法のポートスキャンとその原理、およびポートスキャンしてきた相手 の検出方法を具体的に解説します。また、Penguin Toolboxにある、"Simple Stealth"、および"UDPscan"と その使用法についても解説します。 なお、このドキュメントは、 (1) TCP/IPの基本的な知識 (2) Socketプログラミングの知識 が前提となります。これらについては、Penguin Academy、および関連書籍、ドキュメント等を参照してくだ さい。また、このドキュメントに掲載されているプログラム、およびSimple Stealth、およびUDPscanはTurbo Linux3.0で動作確認しています。 2. ポートスキャン概要 現在、TCPポートスキャンは大きく分て3つの方法が存在します。まず、ターゲットのサーバおよび検査したい ポートに対して、TCPのFull-Connectionを試みるというものです。この方法は、一般的に用いられている方法 であり、管理者が管轄している機器の検査に用いられたりします。また、通常のアプリケーションも、サービ スを要求したいサーバ・ポートにTCP接続を試み、失敗した場合はサーバ、もしくはサーバアプリケーション のダウンをクライアントアプリケーションのユーザに通知したりします。また、HackTek等のUGツールもこの Full-Connection型ポートスキャンを内蔵します。この方法は、サーバ側で簡単に接続ログを取ることができ ます。次に、Half-Openスキャンと言われる方法でポートの検査を行うというものです。TCPの3-stepハンド シェイクを第2番目のステップで止めてしまいます。TCPのSocketを使って接続待ちを行っているサーバ プログラムは、3-stepハンドシェイクの完了で初めてAccept可能となりますので、途中で止められては ログを取ることはおろか、接続を知る手段はありません。よって、telnetdやftpd等はsyslogにconnection を記録することはできません。ただし、netstat等で接続を監視することが可能ですし、多くの接続監視 Loggerは、このHalf-Openスキャンに対応しています。最後は、StealthScanと呼ばれる方法です。これは、 BSDネットコードのバグを利用したスキャンであり、ほとんどのOSはこの方法でポートスキャン可能です。 3-stepハンドシェイクとは全く別のアプローチであり、ログを取るのは非常に難しいとされています。 netstat等でも監視できませんし、多くの監視接続Loggerはこのスキャンを検出できません。また、Firewall 越えのスキャンも可能な場合があります。ただ、原理的にはログをとることは不可能ではなく、現に最近いく つかのLoggerがこのStealthScanに対応しはじめています。 また、UDPはTCPと違いコネクションレスなので、ポートの状態に応じたUDPパケットがサーバから帰ってくる ことはありません。そのため、UDPパケットをサーバの特定ポートに投げた後、ICMPパケットを監視すること によりポートの状態を知ることができます。 以上の3つのTCPポートスキャン、およびUDPスキャンについて、その原理と手法、およびロギングの方法に ついて解説します。また、TCPのSYN-ACKによるOSの特定方法についても解説します。 3. Full-Connection型スキャン TCP/IPによるパケットの受け渡しをSocketを用いて行う場合、通常socket()をSOCK_STREAMにより作成しま す。サーバは作成されたSocketに対してサービスポートを指定してbind()を行いlisten()し、クライアントか らの接続を監視します。クライアントがconnect()を行い、サーバがクライアントからのconnect要求を受けた 時点で、accept()等により接続を受理します。これにより、クライアント─サーバ間でConnectionが張られ、 send()/recv()によりプロセス層レベルでのパケットの受け渡しを行うことができます。 以上のプロセスをFig 1に示します。 サーバ側 クライアント側 socket()によりSocketを作成 socket()によりSocketを作成 | | bind()によりport等を指定 bind() | | listen()により接続を待つ | | | accept()により接続を受理 ====== connect()により、サーバに接続要求を出す | | recv()/send()で送受信 ================ recv()/send()で送受信 | | close()により切断 close()により切断 Fig 1. TCP Socketを用いた、パケットの受け渡し TCP/IPによる多くのネットワークアプリケーションは、このような方法をとっています。telnetを例に とると、サーバ側では、bind()によりPort23を指定、接続要求を受けたクライアントには、全てaccept() します。accept()した時点で、クライアントに対して TurboLinux release 1.0 (hohoho) Kernel 1.2.3 on an i486 (www.hoge.ne.jp) TTY: ttyp4 login: のような文字列パケットをクライアントに対して送信します。 サーバは、クライアントのconnectに対してacceptした時点でIPアドレスをIPヘッダ内から抽出することが可能 です。つまり、クライアントが、 telnet www.hoge.ne.jp として接続を試みた時点でIPをロギングすることが可能なわけです。TCP Wrapper等は、この原理で特定IPを 許可・拒否しており、ロギングを行います。 クライアント側から、ポートが開いているか閉じているかの検査する方法は、実際にサーバにconnectすれば 分かります。指定したサーバのポートが開いていればconnect()が正常終了します。 この原理に基づくポートスキャンは広く行われています。理由としては、最も簡単な手法であるため、この 原理を使用したポートスャンツールが数多く存在し、広く出回っているためであると考えられます。 Pengin Toolboxにも、この原理を利用したポートスキャナ"EasyScan"があります。 このEasyScanで使用される、portscan()関数を list 1 に示します。 /*--------- Full-Connection PortScan - Programmed by UNYUN -----------------*/ int portscan(int port, char *ipaddr) { struct sockaddr_in addr, server; void timeoutfunc(); /****** Socket生成 ************************************/ sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0){ printf("Socket creation error"); return(-1); } /****** SocketのBind **********************************/ memset((char *) &server, 0, sizeof(server)); server.sin_family = AF_INET; server.sin_addr.s_addr = htonl(INADDR_ANY); server.sin_port = 0; if (bind(sock, (struct sockaddr *) &server, sizeof(server)) < 0) { close(sock); printf("Bind error "); return (-2); } /****** Connect **************************************/ addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(ipaddr); addr.sin_port = htons(port); signal(SIGALRM, timeoutfunc); alarm(TIMEOUT_V); if (connect(sock,(struct sockaddr *)&addr,sizeof(addr))!=0){ close(sock); return (-3); } close(sock); return (0); } void timeoutfunc() { close(sock); } list 1. Full-Connection型ポートスキャン関数 この関数に、ターゲットのポートとIPアドレスを渡すことにより、戻り値でポートの状態を検査することが できます。この関数は、TCPによる通常のクライアントプログラムの接続までの部分とほぼ同じです。 connect()前にタイマーをセットし、TIMEOUT_V(sec)内にサーバがaccept()した場合は、socketを閉じて コネクションを切断し、値0を返します。また、タイムアウトしてしまった場合は、値-3を返します。 この原理を用いたポートスキャンは、OSによっては標準でsyslogに書き込まれます。また、TCP-Wrapperや Sniffer等でも監視することができます。閉じているポートに対して監視したい場合などは、そのポートを listenするサーバプログラムを組めば簡単にログを採取できます。 struct sockaddr_in acc; struct in_addr clientIn; char ClientIP[15]; : 略 : connected_socket = accept(listening_socket, &acc, &acclen); memcpy(&clientIn,&acc.sin_addr.s_addr,4); strcpy(ClientIP,inet_ntoa(clientIn)); のようにすれば、クライアントIPアドレスがClientIPに入りますので、このIPをロギングできます。 4. Half-Open型スキャン "SYNスキャン"とも呼ばれている方法です。この方法によるスキャンは、Full-Connection型とは異なり、 接続は確立されません。TCPの接続は、3-stepハンドシェイクにより実現されていますが、第2ステップまで 実行すれば、ポートの状態を知ることができます。サーバ側は、3ステップ全てを完了しないとaccept()でき ないため、TCPソケットによるlisten、acceptでは、第2ステップで止められたハンドシェイクでIPアドレスを 取得することはできません。ポートの状態は、TCPヘッダ内のセッションフラグの状態を見ることにより検査 することができます。クライアントはステップ1のパケット(自分のアドレス、対象ポート、SYNフラグ等をセ ットしたパケット)をサーバに送ると、クライアントにより指定されたポートをlistenしている場合、サーバは SYNフラグとACKフラグをセットしたステップ2のパケットをクライアントに返します。ポートをlistenしてない 、つまりポートが閉じている場合は、RSTフラグをセットしてクライアントに返します。SYNフラグとACKフラグ がセットされたパケットをクライアントが帰ってきた場合、第3ステップとしてクライアントはACKフラグを セットしたパケットをサーバに送り、コネクションが成立します。つまり、第2ステップの時点で、これらフ ラグの状態をチェックすることにより、サーバの指定ポートの状態を知ることができるのです。Fig 2にこれら のプロセスを示します。 これらのパケットをやりとりするためには、通常使用されるTCP Socket(SOCK_STREAM)ではなく、RAW Socket といわれるソケットを用います。RAW Socketを用いると、パケットをネットワークインタフェース層に直接 送ることができます。つまり、IPヘッダ、およびTCPヘッダを自前で作成できるため、こいった用途には必要 不可欠です。 RAWソケットに関する詳しい解説は、Penguin Academyを参照してください。 [クライアント] [サーバ] 特定ポートに接続要求 ------- SYN -------> 指定されたポートの状態をチェック ポートが開いていることを確認 <----- SYN+ACK ----- ・ポートは開いている ポートが閉じていることを確認 <------ RST -------- ・ポートは閉じている Fig 2. Half-Open型スキャン 各ステップの具体的な手順は、以下のようになります。 (1) Step1. SYNパケット送信 Table 1に示すTCP、IPヘッダを含むパケットを送信します。 ---[ IPヘッダ ]--------------------------------------------------- バージョン : 4 ヘッダ長 : 5 (20byte) 優先順位、TOS : 0 全IP長 : 40 ID : 適当(乱数でよい) フラグメント関連 : なし(0) 生存時間 : 適度な値(最大値255でよい) プロトコル : TCP(IPPROTO_TCP) チェックサム : IPヘッダのチェックサム値 送信元アドレス : クライアントのアドレス 宛先アドレス : サーバのアドレス値 ---[TCPヘッダ]---------------------------------------------------- 送信元ポート : 適当なポート番号 宛先ポート : チェックしたいサーバのポート番号 送信元シーケンス番号 : 適当な番号 確認応答シーケンス番号 : 0 ヘッダ長 : 20 (byte) セッションフラグ : SYN ウインドウサイズ : 適度な値(512等) チェックサム : TCP疑似ヘッダのチェックサム値 緊急データポインタ : 0 Table 1. Half-OpenスキャンのためのStep1パケット このパケットを送信後、TCPのRAWソケットからのパケットをselect()により待ちます。パケットが到着した場 合、そのパケットが対象サーバのものかどうかは、パケット内の送信元アドレス、および送信元ポート、宛先 ポートを参照することにより確認できます。 (2) Step.2 サーバからのreply サーバからreplyされたパケットのセッションフラグを検査します。ACKフラグ、SYNフラグがセットされていれ ば、指定されたポートは開いています。ACKフラグは、セッションフラグの5bit目(下位bitから数えて)、 SYNフラグは2bit目です。セッションフラグに対して、00010010B(12H)のマスクをかければ、ACKフラグ、SYN フラグがセットされているかどうか調べることができます。 これらの原理を利用したポートスキャナはいくつか存在します。nmap、scantcp等は後述するStealth型ス キャンにも対応したポートスキャナとして有名です。Pengin Toolboxにも、この原理を利用したポートスキャ ナ"SimpleStealth"があります。このツールの使用方法については、後述します。 このHalf-Open型スキャンは、SATAN detectorやTCP Wrapper等の通常のLoggerでは検出できません。ただし、 SYNフラグを含むパケットを送ってきたクライアントを全てロギングするようなLoggerで検出することができ、 このスキャンを検出するLoggerもいくつか存在します(tcplog等)。検出の具体的な原理は後述します。 5. Stealth型スキャン この方法は、BSDネットコードのバグを利用したスキャンであり、ほとんどのOSはこの方法でポートスキャン することが可能です(Windows等は不可能です)。将来的にはスキャン不可能になると予測されますが、Solaris7 等比較的新しいOSでも通用します。このスキャン方法は、3-stepハンドシェイクとは全く別のアプローチであ り、Full-Connect型やHalf-Open型と違ってログを取るのは非常に難しいとされています。netstat等でも監視 できませんし、多くの監視接続Loggerはこのスキャンを検出できません。また、Firewall越えのスキャンも 可能な場合があります。しかし、Full-Connect型やHalf-Open型と異なり、確実性はありません。このStealth スキャンには、いくつかの方法がありますが、ここでは"FINスキャン"、および"ACKスキャン"について解説し ます。 [1] FINスキャン ほとんどのOSは、クライアントからFINフラグがセットされたパケットを受信すると、ポートが閉じている場 合は、RSTフラグがセットされたパケットをクライアントに送信します。ポートが開いている場合はクライアン トに対して何も返しません。元来、FINフラグはTCPのコネクションの終了を意味しますので、クライアントとの コネクションが確立されていない場合は「意味不明なパケット」として処理され、通常は強制的に破棄されま すので、クライアントには何も返しません。しかし、ポートが閉じている場合、OSは即座にRSTフラグをセット したパケットを返信してしまいます。通常のTCP接続は、SYN-ACKパケットが基本となるため、Half-Open型でも SYNパケットを検出することにより、コネクションを監視できますが、このようなFIN-RSTパケットを利用した 場合はロギングが難しくなります。また、いくつかのファイアウォールは、内部へのTCP接続をSYNフラグで検 出し、フィルタリング等を行っているため、このようなSYNフラグを含まないパケットは素通りする可能性が あり、ファイアウォール越しでもスキャンできる場合があります。このスキャン方法結果をTable 2に示します。 OS名 結果 --------------------- SunOS 4.1 OK Solaris 2.4 OK Solaris 2.5 OK Solaris 2.7 OK IRIX 5.3 OK IRIX 6.2 OK IRIX 6.3 OK IRIX 6.4 OK TurboLinux 3.0 OK FreeBSD 2.2 OK Windows 95/98 NG Windows NT NG MacOS8 OK Table 2. FINスキャン結果 [2] ACKスキャン FINスキャンと同様のアプローチですが、FINフラグの変りにACKフラグを送信します。ACKフラグは、通常有効 確認応答として使用され、例えば3-stepハンドシェイクの第2ステップでサーバがクライアントにパケットを 送信する際にACKフラグがセットされます。ここで、クライアントが第1ステップでACKフラグをセットしたパケ ットを送信した場合、サーバは何のリクエストに対する有効確認応答なのか理解できず、無条件でRSTパケット をクライアントに返信します。この際、ポートが閉じている場合は、TCPパケットの受信はできないので、Win dowサイズは0となります。また、ポートが開いている場合は、サーバ側で有効なウインドウサイズをTCPヘッダ にセットして返します。このスキャン方法結果をTable 3に示します。 OS名 結果 TTL WindowSize ---------------------------------------- SunOS 4.1 OK 60 10H Solaris 2.4 NG 255 0H Solaris 2.5 NG 255 0H Solaris 2.7 NG 255 0H IRIX 5.3 OK 60 F0H IRIX 6.2 OK 60 F0H IRIX 6.3 OK 60 F0H IRIX 6.4 OK 60 F0H TurboLinux 3.0 NG 255 0H FreeBSD 2.2 OK 49 40H Windows 95/98 NG 255 0H Windows NT NG 255 0H MacOS8 NG 255 0H Table 3. ACKスキャン結果 6. Half-Open型スキャン、Stealthスキャンの検出 Half-Open型、Stealth型スキャンの検出は難しいとされており、特にStealthスキャンは3-stepハンドシェイ クを基礎としないため、特に検出は難しくなります。しかし、最近発表されているLoggerには、これらのスキャ ンを検出できるものがあります。たとえば、tcplogdやscanlogd-v1.3、iplog-1.3等がこれらの接続を検出でき ます。これは、出回っているステルス系ポートスキャナの「特性」がパケットに表われるため、それらのスキャ ナからの接続を監視できます。 Half-Open(SYN)スキャン、およびFINステルススキャンを検出する、簡単なロギングツールをList 2 に示し ます。 /*--------- Half-Open & FIN-Stealth PortScan Detecter - Programmed by UNYUN ----------*/ #include #include #include #include main() { int s; char buf[3000]; struct iphdr *ip = (struct iphdr *)(buf); struct tcphdr *tcp = (struct tcphdr *)(buf+sizeof(struct iphdr)); struct in_addr ipaddr; struct hostent *hostinfo; s=socket(AF_INET, SOCK_RAW, IPPROTO_TCP); for(;;){ read(s, buf, 3000); hostinfo=gethostbyaddr((char *)&ipaddr,sizeof(struct in_addr),AF_INET); ipaddr.s_addr=ip->saddr; if(tcp->syn==1 && tcp->ack==0) printf("Port %d : SYN Scan or Connect from %s¥n",ntohs(tcp->dest),inet_ntoa(ipaddr)); if(tcp->fin==1 && tcp->ack==0) printf("Port %d : FIN Scan from %s¥n",ntohs(tcp->dest),inet_ntoa(ipaddr)); } } List 2. Half-Open & FIN-Stealth PortScan Detector サーバに送りつけられた全てのパケットを読み込み、そのパケットのSYN、およびACKフラグをチェックします。 SYN=1、ACK=0であるなら、3-stepハンドシェイクの最初のパケットであるため、このパケットの送信元IPアド レスを表示します。これで、Full-Connect型、およびHalf-Open型スキャンの検出することができます。 また、FINステルス型スキャンは、FIN=1、ACK=0のパケットをチェックすることにより検出することができま す。通常、クライアントがclose等により接続を切った場合は、SYN=1、ACK=1のパケットが送られてきます。 ACKステルススキャンについては、SYN+ACKによるクライアントからの接続の際のポート番号、クライアントア ドレス、およびシーケンス番号を追跡し、正常なクライアントからのACKパケットかどうか診断する必要があり ます。List 2には、ACKステルススキャンの検出機能はありませんので、興味のある方はTCPのフローを参照 しながらインプリメントしてみてください。 7. 非Full-ConnectによるOSの判別法 Half-Open型スキャンは、副産物としておおまかなOSの特定を行うことができます。 クライアントが、SYNフラグをセットしたパケットをサーバに送ると、サーバがlistenしていればSYNフラグ とACKフラグがセットされたパケットをクライアントに返すということは前述しましたが、サーバがSYN+ACKを 返す時に、サーバ側のWindowサイズも返します。このWindowサイズとは、TCPデータの蓄積用として、送信者 が持っている受信バッファを表すバイト数です。このWindowサイズはOSによって異なる場合が多く、これを おおまかなOS判別のパラメータに用いることができます。いくつかの例をTable 4に示します。なお、これら のOSのWindowサイズは、ローカルでのテストにより確認したものです。 Windowサイズ OS ----------------------------------------------------------------- 2398H Solaris 7 (Intel版), 2.6, 2.5, 2.4 (Sparc版) 1000H SunOS4.1 f000H IRIX 5.3(Indy),6.3(O2) c000H IRIX 6.2(IMPACT) 7fe0H TurboLinux3.0 (Intel) 4000H FreeBSD 2.2.7 8000H Digital 4.0 2180H Windows 95/98/NT4 454bH MacOS 7.5.5, 8.5 Table 4. WindowサイズによるOSの判別 WindowサイズによるOS判別は、同一のWindowサイズを返す異なるOSが存在する場合があり、万能ではありま せん。厳密なOS判別を行うためには、他の様々な情報を参照する必要があります。 また、このようなOS判別スキャンを防ぐ方法としては、OSのWindowサイズの変更等が挙げられます。 8. UDPポートスキャン UDPはTCPと違いコネクションレスなので、ポートの状態に応じたUDPパケットがサーバから帰ってくる ことはありません。そのため、UDPパケットをサーバの特定ポートに投げた後、ICMPパケットを監視すること によりポートの状態を知ることができます。具体的には、UDPパケットを開いているポートに投げた場合は、 ICMPは帰ってきません。しかし、閉じているポートに対してUDPパケットを投げると、「あて先到達不可」 タイプ(Type 3)、「ポート到達不可」コード(Code 3)が指定されたICMPパケットが来ます。これをポート スキャンに用います。 なお、このスキャン方法は、サーバプログラムで簡単に接続元IPをロギングできます。 9. Simple Stealth 使用法 Simple Stealthは、RAWソケットを取り扱うため、root権限で動作させる必要があります。また、動作確認は、 TurboLinux3.0でのみ行いました。 コンパイルは以下のようにします。 % gcc simplestealth.c -o simplestealth rootになった後、以下のようにしてsimplestealthを実行します。 # simplestealth [あなたのIPアドレス] [ターゲットIPアドレス] [検査したポート] [スキャンモード] モードとして以下の3つのうち一つを選択します。 's' : Half-Open(SYN) scan 'f' : Stealth(FIN) scan 'a' : Stealth(ACK) scan ISP等にPPPで接続されたマシンの場合、IPアドレスが変動しますので、ifconfigコマンドであなたのIPアドレ スを確認してください。 以下の環境での実験について、例をあげて解説します。 あなたのIPアドレス : 100.100.100.100 ターゲットのIPアドレス : 200.200.200.200 スキャンしたいポート番号 : 23 [例1. Half-Openスキャン] # simplestealth 100.100.100.100 200.200.200.200 23 s Reply from 200.200.200.200 ACK SYN OS = IRIX TTL = 60 TCP window size = f0H Port 23 : open ターゲットのOSはIRIXであり、ポート23は開いている [例2. Stealth(FIN)スキャン] # simplestealth 100.100.100.100 200.200.200.200 23 f 無応答... ターゲットのポート23は開いています。ctrl+cで停止させてください。 閉じている場合は、Port 23 : close と表示されます。 [例3. Stealth(ACK)スキャン] # simplestealth 100.100.100.100 200.200.200.200 23 a Reply from 200.200.200.200 RST TTL = 60 TCP window size = f0H Port 23 : open ターゲットのポート23は開いています。 10. UDPscan使用法 UDPscanは、RAWソケットを取り扱うため、root権限で動作させる必要があります。また、動作確認は、 TurboLinux3.0でのみ行いました。 コンパイルは以下のようにします。 % gcc udpscan.c -o udpscan rootになった後、以下のようにしてudpscanを実行します。 # udpscan [ターゲットIPアドレス] [検査したポート] [例] # udpscan 200.200.200.200 161 無応答... ターゲットのUDPポート161(SNMP)は開いています。 11. さいごに 様々なポートスキャンの原理とその検出方法について述べました。また、Full-Connectを行うことなくOSを 判別する方法について述べました。これらの手法を含むポートスキャンを確実に監視すれば、いくつかの不正 アクセスを未然に防ぐことが可能です。また、ポートスキャンをよく行っている人、いくらStealth型とはい え、検出方法もちゃんと存在し、実際に監視しているサーバもあることを忘れないようにしましょう。ポート スキャンが見つかると、アカウント停止処分を受けることもあります。 - 参考文献 - [1] Uriel Maimon, "Port Scannning without the SYN flag", Phreak 49, Volume Seven, 15 of 16. [2] Thamer Al-Herbish, "RAW IP Networking FAQ", Feb.1999 [3] W.Richard Stevens著, 井上尚司訳, "詳解TCP/IP", pp.37-56, pp.255-260 [4] Fyodor, "NMAP Ver2.07", Fyodor's Playhouse [5] Phroid, "Linux tcplogd hack able to log any tcp portscan attack", BugTraq, Dec. 1998