The Nessus Attack Scripting Language Reference Guide

Renaud Deraison < deraison@cvs.nessus.org >

Version 1.0.0pre2

Introduction Introduction
    1.1  What is NASL ?
    1.2  What NASL is not ?
    1.3  Why not using Perl/Python/tcl/ whatever you like for Nessus ?
    1.4  Why should you write your tests in NASL ?
    1.5  What this guide will teach you
    1.6  NASL limitations : what to not expect
    1.7  Thanks
2  The basics : NASL syntax
    2.1  Comments
    2.2  Variables, variables types, memory allocation, includes
    2.3  Numbers and strings
    2.4  Anonymous / Non Anonymous arguments
        2.4.1  Non Anonymous functions
        2.4.2  Anonymous functions
    2.5  For and while
    2.6  User-defined functions
    2.7  Operators
        2.7.1  The 'x' operator
        2.7.2  The ' > < ' operator
3  The NASL Network related functions
    3.1  Sockets manipulation
        3.1.1  How to open a socket
        3.1.2  Closing a socket
        3.1.3  Writing to a socket, and reading from it
        3.1.4  Higher level operations
    3.2  Raw packets manipulation
        3.2.1  Forging an IP packet
        3.2.2  Forging a TCP packet
        3.2.3  Forging a UDP packet
        3.2.4  Forging an ICMP packet
        3.2.5  Forging an IGMP packet
        3.2.6  Sending a raw packet
        3.2.7  Reading raw packets
    3.3  Utilities
4  String manipulation functions
    4.1  The ereg() function for regular expressions
    4.2  The egrep() function
    4.3  The crap() function
    4.4  The string() function
    4.5  The strlen() function
    4.6  The raw_string() function
    4.7  The strtoint() function
    4.8  The tolower() function
5  Writing a Nessus Security test
    5.1  How to write an efficient Nessus test
        5.1.1  Determining whether a port is open
        5.1.2  The Knowledge Base (KB)
    5.2  NASL script structure
        5.2.1  The register section
        5.2.2  The attack section
        5.2.3  CVE compatibility
        5.2.4  An example
    5.3  Tuning your script
        5.3.1  Asking nessusd to execute the script only if it is necessary
        5.3.2  Be smart enough to use the result of the other scripts
    5.4  So you want to share your new script ?
6  Conclusion
A  The knowledge base
B  The 'nasl' utility

1  Introduction

1.1  What is NASL ?

NASL is a scripting language designed for the Nessus security scanner. Its aim is to allow anyone to write a test for a given security hole in a few minutes, to allow people to share their tests without having to worry about their operating system, and to garantee everyone that a NASL script can not do anything nasty except performing a given security test against a given target.
Thus, NASL allows you to easily forge IP packets, or to send regular packets. It provides you some convenient functions that will make the test of web and ftp server more easy to write. NASL garantees you that a NASL script :

1.2  What NASL is not ?

NASL is not a powerful scripting language. Its purpose is to make scripts that are security tests. So, do not expect to write a third generation web server in this language, nor a file conversion utility. Use perl, python or whatever scripting language to do this - they are 100 times faster

NASL was designed rather quickly, so you may spot some inconstencies in its syntax. Please, let me know if you find some.

1.3  Why not using Perl/Python/tcl/whatever you like for Nessus ?

I know that there is a lot of very good scripting languages around here, and that NASL is really weak compared to them. But none of these languages is secure, in the sense that you can easily write a test that will be a trojan and will indeed open a connection to a third party host - letting it know that you are a Nessus user, and even eventually send the name of your targets to this evil third party host. Or worse, it could send your passwd file, or whatever.

Another problem with many of these scripting language : a lot of them are memory hungry. It can also be an headache if you want to configure them for Nessus. Just think about Perl. Perl is good. Perl is beautiful (according to some). But how much time will you have to spend to install all the modules that may be necessary for writing efficient Nessus tests ? Net::RawIP is only one of them.

NASL, on the other hand, does not take a huge amount of memory. This way, you can launch 20 threads of nessusd at the same time, without the need of having 256Mb of RAM. NASL is also self-sufficient. That is, you will not have to install a dozen of packages for each new security test.

1.4  Why should you write your tests in NASL ?

You may already wonder whether it is worth or not to learn yet another scripting language to write your tests, rather than coding them in C or Perl, or whatever. What you must know is that :

1.5  What this guide will teach you

This guide teaches you how to write your own Nessus tests in NASL. This is my first attempt to write a comprehensive document, so I may have written complicated things.

1.6  NASL limitations : what to not expect

As I stated before, NASL is not a powerful language. The biggest limitations as of now are :

1.7  Thanks

I would like to thank the following persons for their advices regarding the design of NASL. Without them, NASL would be more akward than it is already :

I always appreciate suggestions and complaints about the language. Do not hesitate to share your opinion (be it good or bad) with me.

2  The basics : NASL syntax

NASL syntax is very similar to C, except that a lot of boring stuff has been removed. You do not have to care about the type of your objects, nor do you have to allow memory for them or free it. You do not need to declare your variables before you use them. You just have to focus on the security test you want to perform.

If you do not know C, then you will have a hard time reading this manual has it is currently intended for C programmers. Just complain and this guide will be made more readable in the future.

2.1  Comments

The comment char is '#'. It only comments out the current line.

Examples :

Valid comments are :

    
    
 	a = 1;  # let a = 1 
	
	# Set b to 2 :
	b = 2;

Invalid comments would be :

   
   	#
	  Set a to 1 :
	  		#
			
	 a = 1;
	
	 a = # set a to 1 # 1;
	 
	  		

2.2  Variables, variables types, memory allocation, includes

You do not need to declare variables before you use them. You do not have to care about the variable types. The NASL interpretor will yell at you if you try to do bogus things, such as adding an IP packet to a number. And you do not have to care about memory allocation nor do you have to care about the includes. There is no include. Memory is allocated when needed.

2.3  Numbers and strings

Numbers can be entered in three bases : decimal, hexadecimal, or binary.

All these lines are correct :


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

The strings must be quoted. Note that, unlike C, the characters are not interpolated unless you explicitly ask to interpolate them using the string() function.


 a = "Hello\nI'm Renaud";           # a equals to "Hello\nI'm Renaud"
 b =  string("Hello\nI'm Renaud");  # b equals to "Hello
                                    #              I'm renaud"  	

 c = string(a);                     # c equals to b

The string() function will be dealt with in the ``String Manipulation'' section.

2.4  Anonymous / Non Anonymous arguments

2.4.1  Non Anonymous functions

One thing which is different with C is the way NASL handles the arguments of a function. In C, you must know by heart which argument must be at which place. And this quickly becomes an headache when a function you call has more than 10 arguments. For instance, imagine a C function which will forge an IP packet for you. This function requires a dozen of arguments. If you want to use it, then you will have to remember their exact order or read the the documentation of this function. This is a waste of time, and this is what NASL attempts to avoid.

So, when the order of the arguments of a function is important, and when the different arguments of the function have different types, then the function is a non anonymous function. That is, you have to give the name of the elements. If you forget some elements, then you will be prompted for them at runtime.

2.4.2  Anonymous functions

The anonymous functions are functions that take only one argument, or arguments of the same type.

Examples :

	send_packet(my_packet);
	send_packet(packet1, packet2, packet3);
These functions may have options. For instance, the send_packet() function waits for an answer. If you feel there is no need to read the host's answer, then you can deactivate the pcap, and speed up the test :
	send_packet(packet, use_pcap:FALSE);

2.5  For and while

The for and while work like in C :

For :

	for(instruction_start;condition;end_loop_instruction)
	{
	 #
	 # Some instructions here 
	 #
	}
or
	for(instruction_start;condition;end_loop_instruction)function();

While :

	while(condition)
	{
	 #
	 # Some instructions here
	 #	
	}
or
	
	while(condition)function();

Examples :

	# Count from 1 to 10
	for(i=1;i<=10;i=i+1)display("i : ", i, "\n");
	

	# Count from 1 to 9, and say the type
	# of each number (even or odd)
	for(j=1;j<10;j=j+1){
		if(j & 1)display(j, " is odd\n");
		else display(j, " is even\n");		
		}


	# Do something completely useless :
	
	i = 0;
	while(i < 10)
	{
	 i = i+1;
	}

2.6  User-defined functions

NASL now supports user-defined functions. A user-defined function is defined like this :


function my_function(argument1, argument2, ....)

User-defined functions must use non-anonymous arguments. Recursion is handled.

Example :

function fact(n)
{
  if((n == 0)||(n == 1))
    return(n);
  else
    return(n*fact(n:n-1));
}

display("5! is ", fact(n:5), "\n");

User-defined function may not contain other user-defined functions (actually, they can but the NASL interpretor will yell at you if you call the function that defines its subfunction more than once)

Note that if you want your function to return a value (that's the purpose of a function after all), then you have to use the function return(). Since return() is a function, you must use parenthesis, that is, the following is incorrect :


function func()
{
   return 1; # parenthesis are missing here !
}
  

2.7  Operators

The standard C operators work in NASL. That is, +,-, *, / and % work. At this time, the operators priority is not taken in account, but this will change. In addition to this operators, the binary operators | and & are implemented.

In addition to this, there are two operators that do not exist in C :

2.7.1  The 'x' operator

for and while are great and handy. But because the condition has to be evaluated at each iteration, then there is a loss of performance, which can be of some trouble if you want to send a SYN storm or whatever. The 'x' operator will repeat the same function N times, and will go really fast (at native C speed actually).

Example :

	send_packet(udp) x 10;

Will send the same udp packet ten times.

2.7.2  The ' > < ' operator

The >< operator is a boolean operator which returns true if a string of chars A is contained in a string B.

Example :


	a = "Nessus";
	b = "I use Nessus";
	
	if(a >< b){
		# This will be executed since
		# a is in B
		display(a, " is contained in ", b, "\n");
		}

3  The NASL Network related functions

NASL will not let you open a socket to another host that the host that nessusd wants to test.

3.1  Sockets manipulation

A socket is a way to communicate with another host using TCP or UDP. It is like a pipe, designed to send data on a given port of a given protocol.

3.1.1  How to open a socket

The functions open_sock_tcp() and open_sock_udp() will open a TCP or UDP socket. These two functions are using anonymous arguments. You can currently open a socket on only one port at once, but this will eventually change in the future.
Example :
# Open a socket on TCP port 80 :
soc1 = open_sock_tcp(80);
# Open a socket on UDP port 123 :
soc2 = open_sock_udp(123);

The open_sock functions will return 0 if the connection could not be established on the remote host. Usually, open_sock_udp() will never fail, since there is no way to determine whether the remote UDP port is open or not, whereas the open_sock_tcp() function will return 0 if the remote port is closed.
A trivial TCP port scanner would be like this :

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  Closing a socket

The function close() is used to close a socket. It will internally perform a shutdown() before actually closing the socket.

3.1.3  Writing to a socket, and reading from it

Reading and writing to a socket is done using one of these functions :

The functions that are used to read data from a socket have an internal timeout value of five seconds. If the timeout is reached, then they will return FALSE.
Example :

# This Example displays the FTP banner of the remote host :

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  Higher level operations

NASL has a set of high level functions, regarding FTP and WWW.

Examples :


#
# 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
#
  # open a connection to the remote host
 soc = open_sock_tcp(21);
 
 # Log in as the anonymous user
 if(ftp_log_in(socket:soc, user:"ftp", pass:"joe@"))
 {
  # Get a passive port
  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 packets manipulation

NASL allows you to forge your own IP packets, and will attempt to behave in an intelligent way with the packet forged. For instance, if you change a parameter in a TCP packet, then the TCP checksum will be recomputed silently. If you append a layer to an IP packet, then the ip_len element of the IP packet will be updated - unless you deliberately say to not do it.

All the raw packets functions use non-anonymous arguments. Their names comes straight from the BSD include files. So, the 'length' element of an ip packet is called ip_len and not 'length'.

3.2.1  Forging an IP packet

The function forge_ip_packet() will forge a new IP packet. The function get_ip_element() will return an element of a packet, whereas the function set_ip_elements() will change the elements of an existing IP packet.

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

The ip_sum argument of this function is optional. If it is not set, it will be automatically computed. The field ip_p may be a numeric value, or one of the constants IPPROTO_TCP, IPPROTO_UDP, IPPROTO_ICMP, IPPROTO_IGMP or 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");

The function get_ip_element() will return one element of a packet. The element must be one of "ip_hl", "ip_v", "ip_tos", "ip_len", "ip_id", "ip_off", "ip_ttl", "ip_p", "ip_sum", "ip_src" or "ip_dst". Note that the quotes have their importance.

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

The function set_ip_elements() change the value of the IP packet <ip_variable> and recomputes the checksum if the element ip_sum is not altered. Since this function will not create a new packet in memory, you should prefer it to forge_ip_packet() when you have to send multiple, nearly similar, IP packets.

Last but not least, there is a function dump_ip_packet(<packet>) which will print the IP packet in human readable form on screen. You should only use this for debugging purpose.

3.2.2  Forging a TCP packet

The function forge_tcp_packet() is used to forge a TCP packet. Its syntax is :

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

The option th_flags must be one of TH_SYN, TH_ACK, TH_FIN, TH_PUSH or TH_RST. Flags can be combined using the | operator. th_flags may also be a numeric value. ip_packet must have been generated with forge_ip_packet() or must have be a packet read using send_packet() or pcap_next().

The function used to change TCP elements is set_tcp_elements(). It's syntax is similar to 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   : <checkum>],
			      [data     : <data>] );
			      

This function will automatically recompute the checksum of the packet, unless you explicitly set the th_sum element.

The function used to get one element of a TCP packet is get_tcp_element(). Its syntax is :

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

element_name must be one of "tcp_sport", ""th_dport", "th_flags", "th_seq", "th_ack", "th_x2", "th_off", "th_win", "th_urp", "th_sum". Note the quotes !

3.2.3  Forging a UDP packet

The UDP functions are nearly the same as for TCP functions :

 udp = forge_udp_packet(ip:<ip_packet>,
                        uh_sport : <source_port>,
			uh_dport : <destination_port>,
			uh_ulen  : <length>,
			[uh_sum  : <checksum>],
			[data    : <data>]);

The functions set_udp_elements() and get_udp_elements() work the same way as for the TCP functions.

3.2.4  Forging an ICMP packet

3.2.5  Forging an IGMP packet

3.2.6  Sending a raw packet

Once you have set up a packet using forge_*_packet(), you can send it using the send_packet() function.

This function syntax is :

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

If the argument pcap_active is set to TRUE (the default), then this function will wait for a reply from the host the packet was sent to. You can set up the argument pcap_filter to define what kind of packet you want. See the pcap (or tcpdump) manual to learn more from pcap filters.

3.2.7  Reading raw packets

You can read a packet using the pcap_next() function, the syntax of which is :

 reply = pcap_next();

This function will read a packet from the last interface you used, with the last pcap filter you used on this interface.

3.3  Utilities

NASL provides several handy functions that usually makes your coding easier.

4  String manipulation functions

NASL handles strings as numbers. So, you can play with the ==, <, and > operators safely.

Example :

 a = "version 1.2.3";
 b = "version 1.4.1";
 
 if(a < b){
 	#
	# Will be executed, since version 1.2.3 is lower
	# than version 1.4.1
       }
       
 c = "version 1.2.3";
 
 if(a==c) {
       # Will also be evaluated
       }

It is also possible to get the n-th character of a string, the same way as in C :

 a = "test";
 b = a[1];  # b equals to "e"

You can also add and substract strings :

 a = "version 1.2.3";
 b = a - "version ";   # b equals "1.2.3"
 
 a = "this is a test";
 b = " is a ";
 c = a - b;            # c equals to "this test"
 
 a = "test";
 a = a+a;              # a equals to "testtest"
In addition to this and to the >< operator defined above, NASL has a set of functions dedicated to forge or modify strings :

4.1  The ereg() function for regular expressions

Pattern-matching operations are done through the ereg() function. Its syntax is :

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

The pattern syntax is egrep-style. Please refer to man 1 egrep for more details about it.

Example :

	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  The egrep() function

egrep() returns the first line that matches the pattern <pattern> in a multi-lined text. When it is used against a one-line text, then it is similar to ereg(). If no line in the text matches, then it returns FALSE. Syntax :

	str = egrep(pattern : <pattern>, string: <string>)

Example :


	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  The crap() function

The function crap() is very convenient to test for buffer overflows. It has two syntaxes :

4.4  The string() function

This function is used to make strings of chars or of other strings. It syntax is : string(<string1>, [<string2>, ..., <stringN>])

This function will interpolate the blackslashed characters such as \n or \t.

Example :


  name = "Renaud";
    
  a = string("Hello, I am ", name, "\n");       # a equals to "Hello, I am Renaud" 
                                                # (with a new line at the end)
  b = string(1, " and ", 2, " makes ", 1+2);    # b equals to "1 and 2 makes 3"
  c = string("MKD ", crap(4096), "\r\n");       # c equals to "MKD XXXXX.....XXXX"
                                                # (4096 X's) followed by a carriage
                                                # return and a new line

4.5  The strlen() function

strlen() returns the length of a string :


a = strlen("abcd"); # a is equal to 4 

4.6  The raw_string() function

Example :

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

4.7  The strtoint() function

This function converts a NASL integer into a binary integer. Its syntax is :

 value = strtoint(number:<nasl_integer>, size:<number_of_bytes>);

This function is suitable to use with raw_string(). The size argument is the number of bytes the nasl integer must be written to. It can be, 1, 2 or 4.

4.8  The tolower() function

This function is used to convert a string to lower case. Its syntax is tolower(<string>). This function will actually return the string <string> in lowered letters.

Example :


 a = "Hello";
 b = tolower(a); # b equals to "hello"

5  Writing a Nessus Security test

5.1  How to write an efficient Nessus test

All the security test are launched by nessusd, in a very short period of time, so a well written test must use the results of the other security test. For instance, a test which wants to open a connection to a FTP server should first check that the remote port is open, before opening a connection on port 21. This saves little time and bandwidth against a given host, but this dramatically speeds up the test against a firewalled host which would silently drop TCP packets going to port 21.

5.1.1  Determining whether a port is open

The function get_port_state(<portnum>) returns TRUE if the port is open, and FALSE if it is not. This function will return true if the port has not been scanned, that is, if its status is unknown.

This function uses very little CPU, so you should call it as much as you want.

5.1.2  The Knowledge Base (KB)

Each host is associated to an internal knowledge base, which contains all the information gathered by the tests during the scan. The security tests are encouraged to read it and to contribute to it. The status of the ports, for instance, is in fact written somewhere in the knowledge base.

The KB is divided into categories. The ``Services'' category contains the port numbers associated to each known service. For instance, the element Services/smtp is very likely to have the value 25. However, if the remote host has a hidden SMTP server on port 2500, and none on port 25, then this item will have the value 2500.

See Annex B for details about the knowledge base elements.

Basically, there are two functions regarding the knowledge base. The get_kb_item(<name>) function will return the value of the knowledge base item <name>. This function is anonymous. The function set_kb_item(name:<name>, value:<value>) will mark the new item <name> of value <value> in the knowledge base.

Note : You can not read back an knowledge base item you have added. For instance, the following piece of code will not work and never execute what it should :


set_kb_item(name:"attack", value:TRUE);
if(get_kb_item("attack"))
{
 # Perform the attack - will not be executed
 # because our local KB has not been updated
}

This is due to the fact that for some security and code stability reason, the Nessus server will in fact start each new security test with a copy of the knowledge base, not the original one, and the function set_kb_item() will in fact add an element into the orginal knowledge base, within nessusd, but will not update the current security test knowledge base.

5.2  NASL script structure

Each NASL script must register itself to the Nessus server. That is, it must tell nessusd its name, its description, the name of its author, and more. Thus, each NASL script that will be run with nessusd must have the following structure :


#
# Nasl script to be used with nessusd
#

if(description)
{
 # register information here...
 
 #
 # I will call this section the 'register' 
 # section
 #
 
 exit(0);
}

#
# Script code here. I will call this section the
# 'attack' section.
#

The variable description is a global variable that will be set to TRUE or FALSE depending on whether the script must register or not.

5.2.1  The register section

The register section must call the following functions :

As you may have noticed, most of these functions take a language1 argument. In fact, this is not how they work.

NASL provides Nessus multilingual support. Each script must support the english language, and the exact syntax for all these functions is in fact :

 script_function(english:english_text, [francais:french_text, 
                                        deutsch:german_text,
					...]);

In addition to these functions, the function script_dependencies() may be called. It tells nessusd to launch the current script after some other script. This is useful when you want to use the results that another script must store in the KB. The syntax is :

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

where filename is the name of the script to be launched after, as it is stored on disk.

5.2.2  The attack section

The attack section may contain anything you think is useful for an attack. Once your attack is done, you can report a problem using the security_warning() and security_hole() functions which work the same way. security_warning() must be used when the attack was a success but is not a great security problem. That is, it will not allow instant access to an attacker. These two functions have the following syntaxes :

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

In the first case, the data displayed by the client is the script description, as entered with script_description(). It is handy, because of the multilingual support.

In the second case, then the client will display the data argument. This is handy if you must display information caught on the fly, such as a version number.

5.2.3  CVE compatibility

CVE is an attempt to settle a common denominator to all the security-related products. See http://cve.mitre.org for more details.

Nessus is fully CVE-compatible. If you write a script that tests for a CVE-defined security problem, then call the script_cve_id() function in the description section of your plugin. script_cve_id() is defined as :

	script_cve_id(string);

Example :

	script_cve_id("CVE-1999-0991");

It is important to make a separate call to this function, rather than just writing the CVE id in the report, so that the Nessus clients may make an active use of it.

5.2.4  An example

In addition to security tests, NASL can be used to do some maintenance. Here is a script example that will ensure that each host is running ssh, and tell the user which hosts are not running it :

#
# Check for 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);
}

#
# First, ssh may run on another port. 
# That's why we rely on the plugin 'find_service'
#


port = get_kb_item("Services/ssh");
if(!port)port = 22;

# declare that ssh is not installed yet
ok = 0;
if(get_port_state(port))
{
 soc = open_sock_tcp(port);
 if(soc)
 {
  # Check that ssh is not tcpwrapped. And that it's really
  # SSH
  data = recv(socket:soc, length:200);
  if("SSH" >< data)ok = 1;
 }
 close(soc);
}

#
# Only warn the user that SSH is NOT installed
#  
if(!ok)
{
  report = "SSH is not running on this host !";
  security_warning(port:22, data:report);
}

5.3  Tuning your script

During a test, nessusd will launch more than 200 scripts. If all of them were badly written, then a test would take even more time than it currently does. That's why you must absolutely make whatever you can to make your script go as fast as possible.

5.3.1  Asking nessusd to execute the script only if it is necessary

The best way to optimize your script is to tell nessusd when to not launch it. For instance, let's imagine that your script attempts to connect to the remote TCP port 123. If nessusd knows that this port is closed, then it's no use to start your script, since it will not do anything. The functions script_require_ports(), script_require_keys() and script_exclude_keys() are designed for this purpose. They must be called in the description section of the script.

5.3.2  Be smart enough to use the result of the other scripts

Be sure to read the appendix regarding the knowledge base to make sure that your script is as lazy as possible - that is, it must not do something that another script has already done. For instance, rather that directly opening a socket on a given tcp port (using open_sock_tcp()), make sure that this port is open using get_port_state(). The less your script will do, the faster things will go on.

5.4  So you want to share your new script ?

If you plan to share your script then you should obey to these rules :

6  Conclusion

I hope you enjoyed this overview of NASL. Basically, the language should not evolve for a while, so it's safe to learn how to use it and to practice it.

You will see bugs in the NASL interpretor. That is for sure. I do not know how you program, so it is very likely that you will manage to make it crash. Please, do not keep the bugs for you. Share them, and send them to me.

I hope you enjoyed reading this guide.


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

A  The knowledge base

The knowledge base is a set of keys which contains the results of the other plugins. Using the functions script_dependencies(), get_kb_item() and set_kb_item(), then you can make your scripts and upcoming scripts avoid to do something that has already been done.

Here is a sum up of the keys that are set by the plugins :

KB items may have several values. For instance, imagine that the remote host is running two FTP servers : one on port 21 and one on port 2100. Then, the key Services/ftp, which is the symbolic name of the FTP server port is equal to 21 and 2100. If that is the case, then the script will be executed twice : the first time, get_kb_item("Services/ftp") will return 21, the second time it will return 2100. This behavior is automatic and your script should not take care of this - that is, it should consider that a given key always has only one value. Even if that is not the case in real life, because nessusd is in charge of this.

Not all these keys are useful. I have never used several of them. But putting too much elements in the KB is better than the opposite...

B  The 'nasl' utility

The libnasl package now comes with its own standalone interpretor nasl. Do 'man nasl' for more details


File translated from TEX by TTH, version 2.34.
On 16 Apr 2000, 16:20.