Nessus Attack Scripting Language
リファレンスガイド


Renaud Deraison < deraison@cvs.nessus.org >
Version 1.0.0pre2


和訳:美森勇気

1 イントロダクション
1.1 NASLとは?
1.2 NASLはこういうものではない
1.3 どうして Nessusに Perl/Python/tcl といった 好きなものを何でも使わないのですか?
1.4 どうしてテストをNASLで書くべきなのですか?
1.5 このガイドから何を学べますか?
1.6 ASLの限界:期待すべきでないこと
1.7 謝辞
2 基本:NASLの構文
2.1 コメント
2.2 変数、変数の型、メモリアロケーション、インクルード
2.3 数字と文字列
2.4 「匿名」と「匿名でない」引数
2.4.1 「匿名でない」関数
2.4.2 「匿名」関数
2.5 forwhile
2.6 ユーザ定義関数
2.7 演算子
2.7.1 'x' 演算子
2.7.2 ' >< ' 演算子
3 NASLのネットワーク関連関数
3.1 ソケットの扱い
3.1.1 ソケットのオープンのしかた
3.1.2 ソケットのクローズのしかた
3.1.3 ソケットへのリード、ライト
3.1.4 より高いレベルの操作
3.2 ローパケット(Raw packet)の扱い
3.2.1 IPパケットの捏造
3.2.2 TCPパケットの捏造
3.2.3 UDPパケットの捏造
3.2.4 ICMPパケットの捏造
3.2.5 IGMPパケットの捏造
3.2.6 ローパケットを送る
3.2.7 ローパケットを読む
3.3 ユーティリティ
4 文字列操作関数
4.1 正規表現のための ereg() 関数
4.2 egrep()関数
4.3 crap()関数
4.4 string()関数
4.5 strlen()関数
4.6 raw_string()関数
4.7 strtoint()関数
4.8 tolower()関数
Nessusでセキュリティテストを書く
5.1 有効なNessusテストの書きかた
5.1.1 ポートがオープンしているかどうかの確認
5.1.2 ナレッジベース (KB)
5.2 NASLスクリプトの構造
5.2.1 レジスターセクション
5.2.2 アタックセクション
5.2.3 CVEとの互換性
5.2.4 例
5.3 スクリプトのチューニング
5.3.1 必要時のみnessusdがスクリプトを実行するよう、 問いあわせる
5.3.2 他のスクリプトによる結果を使えるよう賢くす る
5.4 新しいスクリプトを共有したいですか?
6 結論
A ナレッジベース
B naslユーティリティ


1 イントロダクション

1.1 NASLとは?

NASLは、Nessusセキュリティスキャナのためにデザインされたスクリプト言語 である。 誰にでも数分で与えられたセキュリティホールのテストが書け、OSを心配する 必要なくそのテストを人々と共有でき、NASLスクリプトが、指定されたターゲッ トへの与えられたセキュリティテスト以外の悪さを出来ない、ということが目 的になっている。 即ち、NASLでIPパケットを簡単に捏造したり、規則に則ったパケットを送信し たりできる。 ウェブやftpサーバのテストをより簡単に書くためのいくつかの便利な機能を 提供する。 NASLは、NASLスクリプトについて、以下を保証する:

1.2 NASLはこういうものではない

NASLは、強力なスクリプト言語ではない。 その目的は、セキュリティテストのスクリプトを作るためにある。 だから、この言語で第3世代のウェブサーバを書いたり、ファイル変換プログ ラムを書いたりすることを期待してはならない。 これをするには、Perl、Python、その他何でもスクリプト言語を使いなさい。 それらは100倍速い。

NASLは比較的短時間でデザインされているので、構文に不定な部分を見付ける かもしれない。もし何か見つけたら、私に知らせてほしい。

1.3 どうして Nessusに Perl/Python/tcl といった 好きなものを何でも使わないのですか?

このあたりに、多くの非常に良いスクリプト言語が存在することを知っており、 NASLは、それらと比較すると本当に非力であることを知っている。 しかしいずれの言語も、簡単にトロイの木馬を書けたり、実はサードパーティ のホストに接続をはれる、という意味でセキュアではない。 - サードパーティホストにあなたはNessusユーザであることが知れ、また、結 局、邪悪なサードパーティホストにあなたのターゲットの名前を送ってしまう かもしれない。 より悪いことに、あなたのパスワードファイルなど、何でも送ってしまうかも しれない。

多くのスクリプト言語についての別の問題: 多くは、メモリに飢えている。 もしNessus用に設定するのなら、これもまた頭痛の種になる。 Perlについて考えてみよう。 Perlは良い。(いく人かによれば)Perlは美しい。 しかし、有効なNessusテストを書くために必要な全てのモジュールをインストー ルするのに、どれほどの時間を費やさねばならないだろうか?Net::RawIPは、 そのうちの1つにすぎない。

他方、NASLは巨大なメモリを使ったりしない。これにより、256MBのRAMも持つ ことなく、同時にnessusdを20スレッド動かすことが出来る。 NASLはまた、自己完結している。つまり、新しいセキュリティテストのたびに、 たくさんのパッケージをインストールする必要がない。

1.4 どうしてテストをNASLで書くべきなのですか?

あなたは、セキュリティテストを書くために、CやPerlや、その他でコーディ ングするのではなく、さらに別のスクリプト言語を学ぶ価値があるかどうか 既に疑問に思っているかもしれない。知っておくべきことは以下の通り:

1.5 このガイドから何を学べますか?

このガイドで、NASLを用いてあなた自身のNessusテストを書く方法を学べる。 これは、包括的なドキュメントを書く初めての試みなので、複雑なことを書い たかもしれない。

1.6 NASLの限界:期待すべきでないこと

以前言ったように、NASLは強力な言語ではない。 現在の最大の制限は、以下の通り:

1.7 謝辞

NASLのデザインについてのアドバイスで、以下の人に感謝の意を示したい。 彼らがいなければ、NASLは今より不便なものになっていただろう。

私は、この言語についての提案や苦情を、いつもありがたく思う。あなたの意 見(良いにしろ悪いにしろ)を私と共有することをためらわないで欲しい。


2 基本:NASLの構文

NASLの構文は、たくさんの退屈なものを取り除いたこと以外はCと非常に類似 している。 オブジェクトの型について、メモリの確保や開放について気にする必要がない。 使う前に変数を宣言する必要がない。 実行したいセキュリティテストに焦点をあてるだけで良い。

もしCを知らなければ、現在のところCプログラマを想定しているので、このマ ニュアルを読むのは厳しい時間になるだろう。 とりあえず文句を言い、将来、このガイドは、より読みやすくなるだろう。

2.1 コメント

コメントの文字は'#'である。 現在の行だけをコメントアウトする。

例:

有効なコメント:


    a = 1;  # let a = 1

    # Set b to 2 :
    b = 2;


有効でないコメント:

   #
     Set a to 1 :
 		#

	 a = 1;

	 a = # set a to 1 # 1;

2.2 変数、変数の型、メモリアロケーション、インクルード

使う前に宣言する必要はない。 変数の型について、気にする必要はない。 数字にIPパケットを加えるような、おかしなことをしようとすると、 NASLインタープリタはあなたをどなりつける。 そして、メモリ割り当てやincludeについて気にする必要がない。 includeは、存在しない。 メモリは、必要時に割り当てられる。

2.3 数字と文字列

数字は、3種の基数で入力できる:10進、16進、2進である。

これらは、すべて正しい:


 a = 1204;
 b = 0x0A;
 c = 0b001010110110;
 d = 123 + 0xFF;


文字列は、引用符にかこまれてなければならない。 Cと異なり、string()関数を用いて明示的に内挿しない限り、文字が内挿され ないことに注意。


 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 と等しい


string()関数は、「文字列の扱い」セクションで扱う。

2.4 「匿名」と「匿名でない」引数

2.4.1 「匿名でない」関数

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);


ユーザは、引数(ip_lenなど)がないことに関する注意をランタイムに受けるだ ろう。 もちろん、セキュリティテストはユーザとの直接のインタラクションはあって はならないが、これはデバッグや素早いコーディングには便利である。

2.4.2 「匿名」関数

匿名の関数(anonymous function)は、引数を1つだけ取るか、同じタイプの引数だ けを取る。
例:



	send_packet(my_packet);
	send_packet(packet1, packet2, packet3);


これらの関数は、オプションを持つかもしれない。 例えば、send_packet()関数は答えを待つ。 もしホストの返す値を見る必要がないのなら、pcapを使わないことでテストを スピードアップできる:


	send_packet(packet, use_pcap:FALSE);


2.5 forとwhile

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;
	}



2.6 ユーザ定義関数

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");



ユーザ定義関数は、他のユーザ定義関数への呼び出しを許していない (実は、 それは可能であるが、もし1度より多くサブファンクションを定義する関数を呼び だすと、NASLインタープリタが警告を出す。)

もし関数に値を返させたいならば(結局のところ、それが関数の目的である)、 return()関数を使わなければならない。return()は関数なので、かっこを使わ なければならない。つまり、以下のものは誤りである:


function func()
{
   return 1; # かっこが抜け落ちている!
}

2.7 演算子

NASLにおいて、標準的なCの演算子が動く。 つまり、+,-,*,/,%は動作する。 ここで、演算子の優先順位は計算されていないが、将来変わるだろう。 これらの演算子に加えて、ビット演算子の|と&が実装されている。

これに加え、Cには存在しない2つの演算子がある。

2.7.1 'x' 演算子

for や while はすばらしく、便利である。 しかし、状況がそれぞれの繰り返しで評価されなければならないので、パフォー マンスの低下を招き、もしSYNストームなどを送ろうとしているときには問題 となり得る。 'x'演算子は同じ関数をN回繰り返し、しかも本当に高速である(実際、ネイティ ブのCの速さになる)。
例:


	send_packet(udp) x 10;


では、同じudpパケットを10回送る。

2.7.2 ' >< ' 演算子

><演算子はもし文字列Aが文字列Bに含まれるならば真を返すというブー リアン演算子である。
例:




	a = "Nessus";
	b = "I use Nessus";
	
	if(a >< b){
	        # a が b に含まれるので、これは実行されるだろう

		display(a, " is contained in ", b, "\n");
		}


3 NASLのネットワーク関連関数

NASLは、nessusdがテストしたいもの以外のホストに対してソケットを開くこ とを許さないだろう。

3.1 ソケットの扱い

ソケットは、別ホストとTCPやUDPを用いて通信するための方法である。 それは、パイプのように、与えられたプロトコルの与えられたポートにデータ を送るよう、デザインされている。

3.1.1 ソケットのオープンのしかた

open_sock_tcp()open_sock_udp()関数がTCPないしUDPのソケッ トを開く。 これら2つの関数は、匿名の引数を取る。 現時点では一度に1つのポートしかオープンできないが、将来は変更する予定 である。


# TCP、ポート80にソケットを開く:
soc1 = open_sock_tcp(80);
# UDP、ポート123にソケットを開く:
soc2 = open_sock_udp(123);


リモートホストと通信を確立できなかったならば、open_sock関数は0を返す。 普通、リモートのUDPポートが開いているかどうかを知る手立てがないので、 open_sock_udp()は決して失敗しない。 一方、もしリモートのポートが閉じていたならば、open_sock_tcp()関 数は0を返すであろう。
平凡なTCPポートスキャナは、このようになる:


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);
 }
}

3.1.2 ソケットのクローズのしかた

ソケットをクローズするためにclose()関数が使われる。 実際にソケットをクローズする前に、内部的にはshutdown()が実行される。

3.1.3 ソケットへのリード、ライト

ソケットへのリード、ライトは、これらの関数のうちの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);
}

3.1.4 より上位のレベルの操作

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);
 }

3.2 ローパケット(Raw packet)の扱い

NASLを用いて、自身のIPパケットを捏造することが出来、捏造されたパケット に対し、知的な振る舞いを試みるだろう。例えば、もしTCPパケットのパラメー タを変更するならば、TCPのチェックサムが、静かに再計算されるだろう。 もしもIPパケットにレイヤを付加したなら、IPパケットのip_lenの要素が更新 されるだろう - 故意にそうしないように言わない限り。

全てのローパケット関数は、非匿名の引数を用いる。 それらの名称は、BSDのインクルードファイルからまっすぐ来ている。 よって、ipパケットの'length' (長さ)の要素はip_lenと呼ばれ、'length'で はない。

3.2.1 IPパケットの捏造

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>] );


この関数で、ip_sum引数はオプションである。 もしもそれが設定されないならば、自動的に計算される。 ip_pフィールドは数値かもしれないし、 定数値IPPROTO_TCP, IPPROTO_UDP, IPPROTO_ICMP,IPPROTO_IGMP, IPPROTO_IPのいずれかかもしれない。


  <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");


get_ip_element()関数は、パケットの1つの要素を返すだろう。 ここで、要素は "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>  ] 
		);


set_ip_elements()関数は、IPパケット <ip_variable> の値を 変更し、要素ip_sumが変更されていないならばチェックサムを再計算 する。 この関数はメモリ上に新しいパケットを作らないので、複数の、ほとんど似か よったIPパケットを送るときにはforge_ip_packet()よりもこちらを選 ぶべきである。

とりわけ最後に、dump_ip_packet(<packet>)という関数があり、 IPパケットを人が読める型で、画面に表示する。 これは、デバッグの目的にだけ用いられるべきである。

3.2.2 TCPパケットの捏造

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>]);


th_flagsオプションは、TH_SYN, TH_ACK, TH_FIN, TH_PUSH, TH_RSTのいずれか1つでなければならない。フラグは '|'演算子を用いて、 結合させることができる。th_flagsはまた、数値でも構わない。 ip_packetは、forge_ip_packet()で生成されているか、 send_packet()ないしpcap_next()を用いて読み出されたパケッ トでなければならない。

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>] );


この関数では、要素th_sumを特に設定しない限り、パケットのチェッ クサムが自動的に再計算される。

TCPパケットの要素を得るために使う関数は、get_tcp_element()であ る。その構文は以下の通りである。


element = get_tcp_elements(tcp: <tcp_packet>,
                  element: <element_name>);

<element_name>は"tcp_sport", ""th_dport", "th_flags", "th_seq", "th_ack", "th_x2", "th_off", "th_win", "th_urp", "th_sum"のいずれかでなければな らない。引用符に注意!

3.2.3 UDPパケットの捏造

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>]);

set_udp_elements()get_udp_elements() の関数は、TCP用の関 数と同じ方法で動く。

3.2.4 ICMPパケットの捏造

3.2.5 IGMPパケットの捏造

3.2.6 ローパケットを送る

一旦forge_*_packet()を用いてパケットを準備しおわると、 send_packet()関数を用いてそれを送ることができる。

この関数の構文は以下の通り。


 reply = send_packet(packet1, packet2, ...., packetN,
                     pcap_active: <TRUE|FALSE>,
                     pcap_filter: <pcap_filter>);

pcap_active引数が真(デフォルト)にセットされているならば、 この関数は、パケットの送り先のホストからの応答を待つ。 あなたが望むタイプのパケットを定義したpcap_filter引数を準備する ことができる。 pcapフィルタについてより深く学ぶために、pcap(あるいはtcpdump)のマニュ アルを見なさい。

3.2.7 ローパケットを読む

pcap_next()関数を用いてパケットを読み出すことができ、その構文は 以下の通りである。


 reply = pcap_next();

この関数は、あなたが最後に利用したインターフェイスから、 そのインターフェイスで最後に使用したpcapフィルターでパケットを読み出す。

3.3 ユーティリティ

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>]);

この関数は、エラーが発生 (例えば、プログラム <program_number> がリモートのrpc portmapper に登録されていなかった場合)したら、0を返す。


4 文字列操作関数

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) {
       # やはり評価される
       }

Cと同じ方法で、文字列のn番目を得ることも可能である:


 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" になる 

これと、上で定義した >< 演算子に加えて、 NASLは与えられた文字列を生成したり、変更したりする一通りの関数を持って いる。

4.1 正規表現のための ereg() 関数

ereg()関数でパターンマッチング演算がなされる。その構文は以下の 通り。


	result = ereg(pattern:<pattern>, string:<string>)

パターンの構文はegrepのスタイルである。 それについてのさらなる詳細は man 1 egrep を参照してほしい。



	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");
	}

4.2 egrep()関数

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);

4.3 crap()関数

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");           

4.4 string()関数

この関数は、文字や他の文字列から文字列を作るのに使われる。 その構文は、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

4.5 strlen()関数

strlen()は文字列の長さを返す:

a = strlen("abcd"); # a は4 である

4.6 raw_string()関数

例:

a = raw_string(80, 81, 82); # a equals to 'PQR'

4.7 strtoint()関数

この関数は、NASLの整数をバイナリの整数に変換する。構文は次の通り。

value = strtoint(number:<nasl_integer>, size:<number_of_bytes>);
この関数は、raw_string()と共に使うのが良い。 sizeの引数は、naslの整数が書かれるべきバイト数である。これは1, 2, 4の いずれかである。

4.8 tolower()関数

この関数は、文字列を小文字に変換するのに使われる。 この構文はtolower(<string>)である。 この関数は、小文字にした文字列<string>を実際に返すだろう。
例:


 a = "Hello";
 b = tolower(a); # bは "hello" である


Nessusでセキュリティテストを書く

5.1 有効なNessusテストの書きかた

全てのセキュリティテストは、nessusdによって非常に短期間に浴びせられ るので、うまく書かれたテストは他のセキュリティテストの結果を利用しなけ ればならない。 例えば、FTPサーバへのコネクションを繋ごうとするテストでは、ポート21の コネクションを開くのに先立って、まずリモートポートが開いているかをチェッ クするべきである。 このことで、少しの時間と与えられたホストの帯域が節約されるが、ポート21 へのTCPパケットを静かに落とすようなファイアウォールで守られたホストに 対してのテストでは、劇的にスピードアップが図れる。

5.1.1 ポートがオープンしているかどうかの確認

get_port_state(<portnum>)関数は、もしそのポートが開いてい れば真を、そうでなければ偽を返す。この関数は、もしそのポートがスキャン されていない、つまり、状態が知られていないときは、真を返す。

この関数は、CPU資源を非常に少ししか使わないので、望むだけこれを呼びだ すべきである。

5.1.2 ナレッジベース (KB)

それぞれのホストは、内部のナレッジベース(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がアップデートされないため実行されないだろう
}

これは、セキュリティとコードの安定性の理由からであり、Nessusサーバは、 実はナレッジベースのオリジナルではなく、コピーを使ってそれぞれのセキュ リティテストを始めるだろう。 そして、set_kb_item()関数は要素をnessusd内部にあるオリジナルの ナレッジベースに加えるが、現在のセキュリティテストで使っているナレッジ ベースをアップデートすることはないだろう。

5.2 NASLスクリプトの構造

NASLスクリプトは、自身をそれぞれNessusサーバに登録(レジスター)しなけれ ばならない。つまり、名前、説明、著者などをnessusdに告げなければならな い。 それゆえ、nessusdと共に実行されるであろう各NASLスクリプトは、以下の構 造を持たなければならない:


#
# nessusdと共に用いられるNASLスクリプト
#

if(description)
{
 # ここに登録情報を書く
 
 #
 # このセクションを'register'セクションと呼ぶ
 # 
 
 exit(0);
}

#
# ここにスクリプトコードを書く。
# このセクションを'attack'セクションと呼ぶ。
#

変数の記述はそのスクリプトが登録されなければならないかどうかで真、偽が セットされるグローバル変数である。

5.2.1 レジスターセクション

レジスターセクションは、以下の関数を呼びださなければならない:
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_copyright(language1:<copyright>, [...]) は、そのスクリプトの著作権を設定する。 あなたの名前や、法律上の表示などかもしれない。
script_family(language1:<family>, [...]) はスクリプトのファミリを設定する。 ファミリについて明確な定義はないので、スクリプトを「Joe's PowerTools」 というファミリに属させても良いが、おすすめしない。 現在使われているファミリは以下の通り。
既に述べたように、これらの関数のほとんどはlanguage1引数を取る。 実は、それがどのように働くかということではない。 NASLはNessusのマルチリンガルサポートを提供する。 それぞれのスクリプトは英語をサポートしなければならず、これらすべての関 数に関する正確な構文は以下の通りである。
script_function(english:english_text, [francais:french_text, 
                                        deutsch:german_text,
					...]);
これらの関数に加えて、script_dependencies()関数が呼ばれるかもし れない。 これはnessusdに対して、このスクリプトを別のあるスクリプトの後に起動す るように伝える。 別のスクリプトがKBに入れた結果を利用したいとき、これは有用である。 構文は以下の通り。

script_dependencies(filename1 [,filename2, ..., filenameN]);

ここで、filenameはディスク上にある、先立って起動されるスクリプトの名前である。

5.2.2 アタックセクション

アタックセクションは、あなたがアタックに有用と考えるどんなものでも、含 んでよい。一度アタックが済むと、同様に働く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>]);

最初のケースでは、 クライアントによって提示されたデータがscript_description()で入 力されたスクリプトの詳細である。 多国語サポートのため、これは便利である。

2つ目のケースでは、クライアントがデータの引数を示す。 バージョン番号のように実行時に得られた情報を提示しなければならない場合 に便利である。

5.2.3 CVEとの互換性

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");

レポートにCVEのidを書くよりも、むしろこの関数を独立して呼ぶのが重要である。 すると、Nessusクライアントがそれを積極的に利用するかもしれない。

5.2.4 例

セキュリティテストに加えて、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);
}

5.3 スクリプトのチューニング

テスト中に、nessusdは200以上のスクリプトを実行するだろう。 もし、それらすべてが悪く書かれているなら、テストが現状よりもはるかに時 間がかかることになろう。 これが、あなたが何であれ、スクリプトをつくるとき、出来るだけ早く動くよ うに作ることが、どうしても必要な理由である。

5.3.1 必要時のみnessusdがスクリプトを実行するよう、 問いあわせる

あなたのスクリプトを最適化するための最善の方法は、いつそれを実行しない かを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はスクリプトを実行しない。

5.3.2 他のスクリプトによる結果を使えるよう賢くす る

ナレッジベースを用いて、あなたの作ったスクリプトが、可能な限り怠惰にな るようにappendixを良く読みなさい。 つまり、別のスクリプトが既になしたことをしてはならない。 例えば、与えられたtcpポートを(open_sock_tcp()を用いて)直接的に 開くよりも、get_port_state()を用いてこのポートが開いていること を確認する方が良い。あなたのスクリプトがなすことが少なければ少ないほど、 ものごとは早く進むだろう。

5.4 新しいスクリプトを共有したいですか?

もし、あなたがスクリプトを他人と共有したいと思うなら、以下のルールに従 いなさい:


6 まとめ

このNASLのオーバービューを楽しんでくれることを望む。 基本的にいって、この言語は当面進歩するべきではなく、どのように使うかを 勉強し、練習して間違いない。

NASLインタープリタにバグがあるかもしれない。 それは間違いないことだ。 私は、あなたがどのようにしてプログラムするかは知らないので、クラッシュ させるということもありそうだ。 バグをあなただけで抱えこまず、共有し、私に送ってほしい。

私は、あなたがこのガイドを読むことを楽しんでくれたことを希望する。

				   -- Renaud Deraison
				   <deraison@cvs.nessus.org>


A ナレッジベース

ナレッジベースは、他のプラグインからの結果を持った鍵のセットである。 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に多すぎる要素を置くことは、逆よりも良い。

B naslユーティリティ

現在、libnaslパッケージはスタンドアロンのインタープリタnaslと共 に配布されている。詳細は 'man nasl'すること。