NASLは、Nessusセキュリティスキャナのためにデザインされたスクリプト言語 である。 誰にでも数分で与えられたセキュリティホールのテストが書け、OSを心配する 必要なくそのテストを人々と共有でき、NASLスクリプトが、指定されたターゲッ トへの与えられたセキュリティテスト以外の悪さを出来ない、ということが目 的になっている。 即ち、NASLでIPパケットを簡単に捏造したり、規則に則ったパケットを送信し たりできる。 ウェブやftpサーバのテストをより簡単に書くためのいくつかの便利な機能を 提供する。 NASLは、NASLスクリプトについて、以下を保証する:
NASLは、強力なスクリプト言語ではない。 その目的は、セキュリティテストのスクリプトを作るためにある。 だから、この言語で第3世代のウェブサーバを書いたり、ファイル変換プログ ラムを書いたりすることを期待してはならない。 これをするには、Perl、Python、その他何でもスクリプト言語を使いなさい。 それらは100倍速い。
NASLは比較的短時間でデザインされているので、構文に不定な部分を見付ける かもしれない。もし何か見つけたら、私に知らせてほしい。
このあたりに、多くの非常に良いスクリプト言語が存在することを知っており、 NASLは、それらと比較すると本当に非力であることを知っている。 しかしいずれの言語も、簡単にトロイの木馬を書けたり、実はサードパーティ のホストに接続をはれる、という意味でセキュアではない。 - サードパーティホストにあなたはNessusユーザであることが知れ、また、結 局、邪悪なサードパーティホストにあなたのターゲットの名前を送ってしまう かもしれない。 より悪いことに、あなたのパスワードファイルなど、何でも送ってしまうかも しれない。
多くのスクリプト言語についての別の問題: 多くは、メモリに飢えている。 もしNessus用に設定するのなら、これもまた頭痛の種になる。 Perlについて考えてみよう。 Perlは良い。(いく人かによれば)Perlは美しい。 しかし、有効なNessusテストを書くために必要な全てのモジュールをインストー ルするのに、どれほどの時間を費やさねばならないだろうか?Net::RawIPは、 そのうちの1つにすぎない。
他方、NASLは巨大なメモリを使ったりしない。これにより、256MBのRAMも持つ ことなく、同時にnessusdを20スレッド動かすことが出来る。 NASLはまた、自己完結している。つまり、新しいセキュリティテストのたびに、 たくさんのパッケージをインストールする必要がない。
このガイドで、NASLを用いてあなた自身のNessusテストを書く方法を学べる。 これは、包括的なドキュメントを書く初めての試みなので、複雑なことを書い たかもしれない。
以前言ったように、NASLは強力な言語ではない。 現在の最大の制限は、以下の通り:
NASLのデザインについてのアドバイスで、以下の人に感謝の意を示したい。 彼らがいなければ、NASLは今より不便なものになっていただろう。
NASLの構文は、たくさんの退屈なものを取り除いたこと以外はCと非常に類似 している。 オブジェクトの型について、メモリの確保や開放について気にする必要がない。 使う前に変数を宣言する必要がない。 実行したいセキュリティテストに焦点をあてるだけで良い。
もしCを知らなければ、現在のところCプログラマを想定しているので、このマ ニュアルを読むのは厳しい時間になるだろう。 とりあえず文句を言い、将来、このガイドは、より読みやすくなるだろう。
コメントの文字は'#'である。 現在の行だけをコメントアウトする。
例:
有効なコメント:
a = 1; # let a = 1 # Set b to 2 : b = 2;
# Set a to 1 : # a = 1; a = # set a to 1 # 1;
使う前に宣言する必要はない。 変数の型について、気にする必要はない。 数字にIPパケットを加えるような、おかしなことをしようとすると、 NASLインタープリタはあなたをどなりつける。 そして、メモリ割り当てやincludeについて気にする必要がない。 includeは、存在しない。 メモリは、必要時に割り当てられる。
数字は、3種の基数で入力できる:10進、16進、2進である。
これらは、すべて正しい:
a = 1204; b = 0x0A; c = 0b001010110110; d = 123 + 0xFF;
a = "Hello\nI'm Renaud"; # a は "Hello\nI'm Renaud" b = string("Hello\nI'm Renaud"); # b は "Hello # I'm renaud" c = string(a); # c は b と等しい
Cと異なる1つの点として、NASLにおける関数の引数の扱い方がある。 Cでは、どの引数がどの場所にあるのかを暗記しなければならない。 そして、これは10を越える引数を持つ関数を呼びだす際、急速に頭痛の種にな る。 例えば、IPパケットを捏造するCの関数を想像してみなさい。 この関数は、12の引数を要求する。 もしそれを使いたいのなら、その正確な順番を記憶しておくか、この関数のド キュメントを読まなければならない。 これは時間の浪費だし、NASLが避けようとしていることでもある。
よって、関数の引数の順序が重要なとき、またその関数の異なる引数が異なる 型を持つ場合、その関数は「匿名でない」(non anonymous)関数となる。 つまり、要素の名前を与えなければならない。 もしいくつかの要素を忘れたならば、ランタイムに注意されるだろう。
例:
forge_ip_packet()という関数は、多くの要素を持つ。
以下2つの呼び出しは有効であり、まったく同じように動作する:
forge_ip_packet(ip_hl : 5, ip_v : 4, ip_p : IPPROTO_TCP); forge_ip_packet(ip_p : IPPROTO_TCP, ip_v : 4, ip_hl : 5);
send_packet(my_packet); send_packet(packet1, packet2, packet3);
send_packet(packet, use_pcap:FALSE);
forとwhileは、Cにおけるのと同じように動作する:
For : for(instruction_start;condition;end_loop_instruction) { # # ここにいくつかの命令(instruction)を書く # } あるいは、 for(instruction_start;condition;end_loop_instruction)function(); While : while(condition) { # # ここにいくつかの命令(instruction)を書く # } or while(condition)function();
# 1から10まで数える for(i=1;i<=10;i=i+1)display("i : ", i, "\n"); # 1から9まで数え、それぞれの数のタイプ # (偶数か奇数か)を表示する for(j=1;j<10;j=j+1){ if(j & 1)display(j, " is odd\n"); else display(j, " is even\n"); } # まったく役にたたないことをする i = 0; while(i < 10) { i = i+1; }
NASLは、ユーザ定義関数をサポートする。 ユーザ定義関数は、以下のように定義される:
function my_function(argument1, argument2, ....)
ユーザ定義関数は、匿名でない引数を用いなければならない。
再帰を扱える。
例:
function fact(n) { if((n == 0)||(n == 1)) return(n); else return(n*fact(n:n-1)); } display("5! is ", fact(n:5), "\n");
もし関数に値を返させたいならば(結局のところ、それが関数の目的である)、
return()関数を使わなければならない。return()は関数なので、かっこを使わ
なければならない。つまり、以下のものは誤りである:
function func() { return 1; # かっこが抜け落ちている! }
NASLにおいて、標準的なCの演算子が動く。 つまり、+,-,*,/,%は動作する。 ここで、演算子の優先順位は計算されていないが、将来変わるだろう。 これらの演算子に加えて、ビット演算子の|と&が実装されている。
これに加え、Cには存在しない2つの演算子がある。
for や while はすばらしく、便利である。
しかし、状況がそれぞれの繰り返しで評価されなければならないので、パフォー
マンスの低下を招き、もしSYNストームなどを送ろうとしているときには問題
となり得る。
'x'演算子は同じ関数をN回繰り返し、しかも本当に高速である(実際、ネイティ
ブのCの速さになる)。
例:
send_packet(udp) x 10;
><演算子はもし文字列Aが文字列Bに含まれるならば真を返すというブー
リアン演算子である。
例:
a = "Nessus"; b = "I use Nessus"; if(a >< b){ # a が b に含まれるので、これは実行されるだろう display(a, " is contained in ", b, "\n"); }
NASLは、nessusdがテストしたいもの以外のホストに対してソケットを開くこ とを許さないだろう。
ソケットは、別ホストとTCPやUDPを用いて通信するための方法である。 それは、パイプのように、与えられたプロトコルの与えられたポートにデータ を送るよう、デザインされている。
open_sock_tcp()とopen_sock_udp()関数がTCPないしUDPのソケッ
トを開く。
これら2つの関数は、匿名の引数を取る。
現時点では一度に1つのポートしかオープンできないが、将来は変更する予定
である。
例
# TCP、ポート80にソケットを開く: soc1 = open_sock_tcp(80); # UDP、ポート123にソケットを開く: soc2 = open_sock_udp(123);
start = prompt("First port to scan ? "); end = prompt("Last port to scan ? "); for(i=start;i<end;i=i+1) { soc = open_sock_tcp(i); if(soc) { display("Port ", i, " is open\n"); close(soc); } }
ソケットをクローズするためにclose()関数が使われる。 実際にソケットをクローズする前に、内部的にはshutdown()が実行される。
ソケットへのリード、ライトは、これらの関数のうちの1つを使って行われる:
ソケットからデータを読み出すために使われる関数では、内部的に5秒でタイ
ムアウトする。
もしタイムアウトに達すると、偽を返す。
例:
# この例では、リモートホストのFTPのバナーを表示する: soc = open_sock_tcp(21); if(soc) { data = recv_line(socket:soc, length:1024); if(data) { display("The remote FTP banner is : \n", data, "\n"); } else { display("The remote FTP server seems to be tcp-wrapped\n"); } close(soc); }
NASLは、FTPとWWWについて、ひとそろいの高レベルの関数を持っている。
ftp_log_in(socket:<soc>, user:>login<, pass:<pass>)
では、新しく開いたソケット<soc>に接続したFTPサーバへログインする
試みを行う。
もしユーザ名<login>、パスワード<pass>でログインできたなら、
この関数は真を返す。エラーが発生したら偽を返す。
ftp_get_pasv_port(socket:<soc>)
はFTPサーバ上でPASVを発行し、開いたコネクションのポート番号を返す。
これにより、NASLスクリプトはFTP経由でデータを落すことができる。
もしエラーが発生したならば、この関数は偽を返す。
is_cgi_installed(<name>)
は、リモートのウェブサーバに<name>というcgiがインストールされて
いるなら、真を返す。
この関数は、リモートのウェブサーバに対してGETリクエストを行う。
もしも<name>がスラッシュ(/)で始まってないなら、その前に/cgi-bin/
が追加される。
この関数はまた、与えられたファイルが存在するかどうか知るのに使うことも
できる。
例:
# # WWW # if(is_cgi_installed("/robots.txt")){ display("The file /robots.txt is present\n"); } if(is_cgi_installed("php.cgi")){ display("The CGI php.cgi is installed in /cgi-bin\n"); } if(!is_cgi_installed("/php.cgi")){ display("There is no 'php.cgi' in the remote web root\n"); } # # FTP # # リモートホストに接続する soc = open_sock_tcp(21); # 匿名ユーザ(anonymous)としてログインする if(ftp_log_in(socket:soc, user:"ftp", pass:"joe@")) { # パッシブポートを得る port = ftp_get_pasv_port(socket:soc); if(port) { soc2 = open_sock_tcp(port); data = string("RETR /etc/passwd\r\n"); send(socket:soc, data:data); password_file = recv(socket:soc2, length:10000); display(password_file); close(soc2); } close(soc); }
NASLを用いて、自身のIPパケットを捏造することが出来、捏造されたパケット に対し、知的な振る舞いを試みるだろう。例えば、もしTCPパケットのパラメー タを変更するならば、TCPのチェックサムが、静かに再計算されるだろう。 もしもIPパケットにレイヤを付加したなら、IPパケットのip_lenの要素が更新 されるだろう - 故意にそうしないように言わない限り。
全てのローパケット関数は、非匿名の引数を用いる。 それらの名称は、BSDのインクルードファイルからまっすぐ来ている。 よって、ipパケットの'length' (長さ)の要素はip_lenと呼ばれ、'length'で はない。
forge_ip_packet()関数は、新しいIPパケットを捏造する。
get_ip_element()関数はパケットの要素を返すのに対し、
set_ip_elements()は、既に存在するIPパケットの要素を変更するだろ
う。
<return_value> = forge_ip_packet( ip_hl : <ip_hl>, ip_v : <ip_v>, ip_tos : <ip_tos>, ip_len : <ip_len>, ip_id : <ip_id>, ip_off : <ip_off>, ip_ttl : <ip_ttl>, ip_p : <ip_p>, ip_src : <ip_src>, ip_dst : <ip_dst>, [ip_sum : <ip_sum>] );
<element> = get_ip_element( ip : <ip_variable>, element : "ip_hl"|"ip_v"|"ip_tos"|"ip_len"| "ip_id"|"ip_off"|"ip_ttl"|"ip_p"| "ip_sum"|"ip_src"|"ip_dst");
set_ip_elements( ip : <ip_variable>, [ip_hl : <ip_hl>, ] [ip_v : <ip_v>, ] [ip_tos : <ip_tos>,] [ip_len : <ip_len>,] [ip_id : <ip_id>, ] [ip_off : <ip_off>,] [ip_ttl : <ip_ttl>,] [ip_p : <ip_p>, ] [ip_src : <ip_src>,] [ip_dst : <ip_dst>,] [ip_sum : <ip_sum> ] );
とりわけ最後に、dump_ip_packet(<packet>)という関数があり、 IPパケットを人が読める型で、画面に表示する。 これは、デバッグの目的にだけ用いられるべきである。
forge_tcp_packet()関数は、TCPパケットを捏造するために使われる。
その文法は以下の通りである。
tcppacket = forge_tcp_packet(ip : <ip_packet>, th_sport : <source_port>, th_dport : <destination_port>, th_flags : <tcp_flags>, th_seq : <sequence_number>, th_ack : <acknowledgement_number>, [th_x2 : <unused>], th_off : <offset>, th_win : <window>, th_urp : <urgent_pointer>, [th_sum : <checkum>], [data : <data>]);
TCPの要素を変更するのに使われる関数は、set_tcp_elements()である。
その構文は、forge_tcp_packet()と似ている。
set_tcp_elements(tcp : <tcp_packet>, [th_sport : <source_port>,] [th_dport : <destination_port>,] [th_flags : <tcp_flags>,] [th_seq : <sequence_number>,] [th_ack : <acknowledgement_number>,] [th_x2 : <unused>,] [th_off : <offset>,] [th_win : <window>,] [th_urp : <urgent_pointer>,] [th_sum : <checksum>], [data : <data>] );
TCPパケットの要素を得るために使う関数は、get_tcp_element()であ
る。その構文は以下の通りである。
element = get_tcp_elements(tcp: <tcp_packet>, element: <element_name>);
UDPの関数は、TCPの関数とほとんど同じである。
udp = forge_udp_packet(ip:<ip_packet>, uh_sport : <source_port>, uh_dport : <destination_port>, uh_ulen : <length>, [uh_sum : <checksum>], [data : <data>]);
一旦forge_*_packet()を用いてパケットを準備しおわると、 send_packet()関数を用いてそれを送ることができる。
この関数の構文は以下の通り。
reply = send_packet(packet1, packet2, ...., packetN, pcap_active: <TRUE|FALSE>, pcap_filter: <pcap_filter>);
pcap_next()関数を用いてパケットを読み出すことができ、その構文は
以下の通りである。
reply = pcap_next();
NASLは、良くコーディングをより簡単にするような便利ないくつかの関数を提 供する。
this_host()関数は引数を取らず、スクリプトを実行しているホストの IPアドレスを返す。
get_host_name()関数は引数を取らず、現在テストされているホスト名を 返す。
get_host_ip()関数は引数を取らず、現在テストされているホストのIPア ドレスを返す。
get_host_open_port()関数は引数を取らず、リモートホストで、最初 にオープンしているTCPポート番号を返す。 これは、オープンポートに対して働く必要がある(land?)や、TCPシークエンス分 析プログラムのようなスクリプトで有用である。
get_port_state(<portnum>)関数は、TCPポート <portnum> がオープンしているか、その状態が (例えば、スキャンされていないとか、スキャンした範囲外であるとかで) 不明ならば真を返す。
telnet_init(<soc>)関数は、
新しく開いたソケット <soc> にtelnetセッションを初期化し、
telnetデータの最初の行を返す。
例:
soc = open_sock_tcp(23); buffer = telnet_init(soc); display("The remote telnet banner is : ", buffer, "\n");
tcp_ping()関数は引数を取らず、もしリモートホストがTCP pingリク エスト(ACKフラグをセットしたTCPパケットを送る)に答えたら真を返す。
getrpcport()関数は、同名の標準関数と同じである。
その構文は以下の通り。
result = getrpcport(program : <program_number>, protocol: IPPROTO_TCP|IPPROTO_UDP, [version: <version>]);
NASLでは、文字列を数字として扱う。
よって、安全に==, <, >演算子を用いることが出来る。
例:
a = "version 1.2.3"; b = "version 1.4.1"; if(a < b){ # # バージョン 1.4.1よりも1.2.3の方が低いので実行される # } c = "version 1.2.3"; if(a==c) { # やはり評価される }
a = "test"; b = a[1]; # b は "e" である
a = "version 1.2.3"; b = a - "version "; # b は "1.2.3" となる a = "this is a test"; b = " is a "; c = a - b; # c は "this test" となる a = "test"; a = a+a; # a は "testtest" になる
ereg()関数でパターンマッチング演算がなされる。その構文は以下の
通り。
result = ereg(pattern:<pattern>, string:<string>)
if(ereg(pattern:".*", string:"test")) { display("Always executed\n"); } mystring = recv(socket:soc, length:1024); if(ereg(pattern: "SSH-.*-1\..*", string : mystring )) { display("SSH 1.x is running on this host"); }
egrep()は、複数行のテキスト中、パターン<pattern>に一致し
た最初の行を返す。1行のテキストに対して使用したときは、ereg()と
似ている。
もしテキスト中に一致する行がなければ、偽を返す。構文は以下の通り。
str = egrep(pattern :, string: 例: soc = open_soc_tcp(80); str = string("HEAD / HTTP/1.0\r\n\r\n"); send(socket:soc, data:str); r = recv(socket:soc, length:1024); server = egrep(pattern:"^Server.*", string : r); if(server)display(server);)
crap()関数は、バッファオーバーフローをテストするのに大変便利で
ある。これは、2つの構文を持っている:
crap(<length>) : 文字 'X' を持つ長さ<length>の文字列を
返すだろう。
crap(length:<length>, data:<data>) :
データ<data>を持つ長さ<length>の文字列を返すだろう。
例:
a = crap(5); # a = "XXXXX"; b = crap(4096); # b = "XXXX...XXXX" (X が4096個) c = crap(length:12, # c = "hellohellohe" (長さ: 12); data:"hello");
この関数は、文字や他の文字列から文字列を作るのに使われる。 その構文は、string(<string1>, [<string2>, ...,<stringN>]) である。
この関数は、\nや\tのようなバックスラッシュのついた文字を展開するだろう。
例:
name = "Renaud"; a = string("Hello, I am ", name, "\n"); # a は "Hello, I am Renaud" である # (最後に改行がある) b = string(1, " and ", 2, " makes ", 1+2); # b は "1 and 2 makes 3" c = string("MKD ", crap(4096), "\r\n"); # c は "MKD XXXXX.....XXXX" # (4096個のX)につづき、 # CRLF
strlen()は文字列の長さを返す:
a = strlen("abcd"); # a は4 である
例:
a = raw_string(80, 81, 82); # a equals to 'PQR'
この関数は、NASLの整数をバイナリの整数に変換する。構文は次の通り。
value = strtoint(number:<nasl_integer>, size:<number_of_bytes>);
この関数は、raw_string()と共に使うのが良い。
sizeの引数は、naslの整数が書かれるべきバイト数である。これは1, 2, 4の
いずれかである。
この関数は、文字列を小文字に変換するのに使われる。
この構文はtolower(<string>)である。
この関数は、小文字にした文字列<string>を実際に返すだろう。
例:
a = "Hello"; b = tolower(a); # bは "hello" である
全てのセキュリティテストは、nessusdによって非常に短期間に浴びせられ るので、うまく書かれたテストは他のセキュリティテストの結果を利用しなけ ればならない。 例えば、FTPサーバへのコネクションを繋ごうとするテストでは、ポート21の コネクションを開くのに先立って、まずリモートポートが開いているかをチェッ クするべきである。 このことで、少しの時間と与えられたホストの帯域が節約されるが、ポート21 へのTCPパケットを静かに落とすようなファイアウォールで守られたホストに 対してのテストでは、劇的にスピードアップが図れる。
get_port_state(<portnum>)関数は、もしそのポートが開いてい れば真を、そうでなければ偽を返す。この関数は、もしそのポートがスキャン されていない、つまり、状態が知られていないときは、真を返す。
この関数は、CPU資源を非常に少ししか使わないので、望むだけこれを呼びだ すべきである。
それぞれのホストは、内部のナレッジベース(Knowledge base; KB)に関連づけられ ているが、これはスキャン中のテスト結果をまとめたすべての情報を持ってい る。 セキュリティテストはそれを読み、それに寄与することを助長する。 実際、例えば、ポートの状態はナレッジベースのどこかに書かれている。
KBはカテゴリーごとに分かれている。 "Service"カテゴリーには、それぞれの知られたサービスと関連したポート番 号が書かれている。 例えば、"Service/smtp"という要素は、25という値を持っているのが適当だ。 しかしながら、もしもリモートホストが、隠れたSMTPサーバをポート2500に持っ ていて、かつポート25に持っていないなら、この要素は2500という値を持って いるだろう。
Annex Bに、ナレッジベースの要素についての詳細がある。
基本的に、ナレッジベースに関して2つの関数がある。 get_kb_item(<name>)関数は、ナレッジベースのアイテム <name>の値を返すだろう。 この関数は匿名である。 set_kb_item(name:<name>, value:<value>)関数は、ナレッ ジベースに新しいアイテム<name>を値<value>として記録する。
注意:
ナレッジベースに加えたアイテムを読みかえすことはできない。
例えば、以下のコードはうまく動作せず、決してあるべきようには実行されな
い。
set_kb_item(name:"attack", value:TRUE); if(get_kb_item("attack")) { # 攻撃を行う --- # ローカルのKBがアップデートされないため実行されないだろう }
NASLスクリプトは、自身をそれぞれNessusサーバに登録(レジスター)しなけれ
ばならない。つまり、名前、説明、著者などをnessusdに告げなければならな
い。
それゆえ、nessusdと共に実行されるであろう各NASLスクリプトは、以下の構
造を持たなければならない:
# # nessusdと共に用いられるNASLスクリプト # if(description) { # ここに登録情報を書く # # このセクションを'register'セクションと呼ぶ # exit(0); } # # ここにスクリプトコードを書く。 # このセクションを'attack'セクションと呼ぶ。 #
レジスターセクションは、以下の関数を呼びださなければならない:
script_name(language1:<name>, [...])
は、Nessusクライアントウィンドウに現れるスクリプト名をセットする。
script_description(language1:<desc>, [...])
は、ユーザが名前をクリックしたときにクライアントに現れるスクリプトの説
明をセットする。
script_summary(language1:<summary>, [...])
は、ツールチップに現れるスクリプトの概要をセットする。
それは、1行に入る説明でなければならない。
script_category(<category>)
では、スクリプトのカテゴリーを設定する。
これは、ACT_ATTACK, ACT_GATHER_INFO, ACT_DENIAL, ACT_SCANNERのいずれか
でなければならない。
script_function(english:english_text, [francais:french_text, deutsch:german_text, ...]);これらの関数に加えて、script_dependencies()関数が呼ばれるかもし れない。 これはnessusdに対して、このスクリプトを別のあるスクリプトの後に起動す るように伝える。 別のスクリプトがKBに入れた結果を利用したいとき、これは有用である。 構文は以下の通り。
script_dependencies(filename1 [,filename2, ..., filenameN]);
アタックセクションは、あなたがアタックに有用と考えるどんなものでも、含
んでよい。一度アタックが済むと、同様に働く2つの関数、
security_warning()やsecurity_hole()を用いて問題をレポー
トすることができる。
security_warning()は、アタックは成功したが、それが大きなセキュ
リティ上の問題ではないときに用いなければならない。
つまり、攻撃者に対して即時にアクセスを与えないようなものである。
これら2つの関数は、以下の構文を取る:
security_warning(<port> [, protocol:<proto>]); security_hole(<port> [, protocol:<proto>]); security_warning(port:<port>, data:<data> [, protocol:<proto>]); security_hole(port:<port>, data:<data> [, protocol:<proto>]);
2つ目のケースでは、クライアントがデータの引数を示す。 バージョン番号のように実行時に得られた情報を提示しなければならない場合 に便利である。
CVEは、すべてのセキュリティに関連するものについて、共通の標準を策定す ることを試みている。さらなる詳細については http://cve.mitre.orgを見よ。
Nessusは、完全にCVE互換である。
もしCVEに定義されたセキュリティ上の問題についてテストをするスクリプト
を書いたならば、プラグインのscript_cve_id()関数のディスクリプショ
ンセクション(訳注:register section?)で呼びだす。
script_cve_id()は以下のように定義される:
script_cve_id(string);
script_cve_id("CVE-1999-0991");
セキュリティテストに加えて、NASLはメインテナンスするのにも使える。
ここに、それぞれのホストにsshが走っているか、またどのホストに走ってい
ないかを確認するスクリプトの例がある。
# # sshのチェック # if(description) { script_name(english:"Ensure the presence of ssh"); script_description(english:"This script makes sure that ssh is running"); script_summary(english:"connects on remote tcp port 22"); script_category(ACT_GATHER_INFO); script_family(english:"Administration toolbox"); script_copyright(english:"This script was written by Joe U."); script_dependencies("find_service.nes"); exit(0); } # # まず、sshが別のポートで動いているかもしれない。 # それが、'find_service'プラグインを信用する理由である。 # port = get_kb_item("Services/ssh"); if(!port)port = 22; # SSHがインストールされていないことを宣言する ok = 0; if(get_port_state(port)) { soc = open_sock_tcp(port); if(soc) { # sshがtcpwrapperされていないことをチェックする。 # また、本当にSSHであるかをチェックする。 data = recv(socket:soc, length:200); if("SSH" >< data)ok = 1; } close(soc); } # # SSHがインストールされていないことを、単に警告する # if(!ok) { report = "SSH is not running on this host !"; security_warning(port:22, data:report); }
テスト中に、nessusdは200以上のスクリプトを実行するだろう。 もし、それらすべてが悪く書かれているなら、テストが現状よりもはるかに時 間がかかることになろう。 これが、あなたが何であれ、スクリプトをつくるとき、出来るだけ早く動くよ うに作ることが、どうしても必要な理由である。
あなたのスクリプトを最適化するための最善の方法は、いつそれを実行しない かをnessusdに教えることである。例えば、あなたのスクリプトは、リモート のTCPポート123番へ接続しようとするものだったとしよう。もしも、このポー トが閉じているとnessusdが知っているのなら、何もしないだろうからそのス クリプトを起動するのは無駄である。 script_require_ports(), script_require_keys(), script_exclude_keys()といった関数が、この目的に合うようデザイン されている。 これらは、スクリプトのdescriptionセクションで呼びだされなければならな い。
script_require_ports(<port1>, <port2>, ...) : は、もしポートのうちの少なくとも1つが開いているなら、その場合に限って nessusdはこのスクリプトを実行する。 <port>は数値(例:80)でも、ナレッジベースで定義されているシンボリッ クな値(例:Services/www)でも良い。
script_require_keys(<key1>, <key2>, ...) : は、もしも引数に書かれたキーがすべてナレッジベースに定義されているなら、 その場合に限ってnessusdがそのスクリプトを実行する。
例: script_require_keys("ftp/anonymous", "ftp/writeable_dir") は、もしリモートのFTPサーバがアノニマスアクセスを受けつけ、かつ書き込 み可能なディレクトリが存在するときだけ実行される。
script_exclude_keys(<key1>, <key2>, ...) : は、もしナレッジベースに、引数で与えられたキーのうち、少なくとも1つが 存在したならば、nessusdはスクリプトを実行しない。
ナレッジベースを用いて、あなたの作ったスクリプトが、可能な限り怠惰にな るようにappendixを良く読みなさい。 つまり、別のスクリプトが既になしたことをしてはならない。 例えば、与えられたtcpポートを(open_sock_tcp()を用いて)直接的に 開くよりも、get_port_state()を用いてこのポートが開いていること を確認する方が良い。あなたのスクリプトがなすことが少なければ少ないほど、 ものごとは早く進むだろう。
もし、あなたがスクリプトを他人と共有したいと思うなら、以下のルールに従
いなさい:
このNASLのオーバービューを楽しんでくれることを望む。 基本的にいって、この言語は当面進歩するべきではなく、どのように使うかを 勉強し、練習して間違いない。
NASLインタープリタにバグがあるかもしれない。 それは間違いないことだ。 私は、あなたがどのようにしてプログラムするかは知らないので、クラッシュ させるということもありそうだ。 バグをあなただけで抱えこまず、共有し、私に送ってほしい。
私は、あなたがこのガイドを読むことを楽しんでくれたことを希望する。
-- Renaud Deraison <deraison@cvs.nessus.org>
ナレッジベースは、他のプラグインからの結果を持った鍵のセットである。 script_dependencies(),get_kb_item(),set_kb_item() 関数を用い、あなたのスクリプトや、やがてあらわれるスクリプトが、既にな されたことをしなくて済む。
ここに、プラグインによって設定される鍵の要約がある。
KBのアイテムは、いくつかの値を取るかもしれない。 例えば、リモートのホストに2つのFTPサーバ:1つが21で、もう1つが2100、が 走っている場合を考えてみよう。 そのとき、FTPサーバのポートをあらわす名前であるService/ftpの鍵に対する 値は21と2100である。そういう場合には、そのスクリプトは2回実行される。1 度目はget_kb_item("Services/ftp")は21を返し、2度目には2100を返 す。 この振る舞いは自動であり、あなたのスクリプト内で気にする必要はない-つ まり、 nessusdがこれを担当するので、実際にはそうでない場合でさえ 与えられた鍵に対して、常に1つだけの値を持つと考えるべきである。
すべての鍵が有用というわけではない。 私は、これらのうちいくつかは決して使ったことがない。 しかし、KBに多すぎる要素を置くことは、逆よりも良い。
現在、libnaslパッケージはスタンドアロンのインタープリタnaslと共 に配布されている。詳細は 'man nasl'すること。