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
easier to write. NASL garantees you that a NASL script :
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.
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.
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 :
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.
As I stated before, NASL is not a powerful language. The biggest limitations as of now are :
The first NASL interpretor was a modification of a personal project called
pkt_forge, written by Renaud Deraison in 1998, which was an interactive shell to
manipulate packets. The parser was written quickly and it was ugly, memory
management was awful, and the execution of the scripts was slow.
In 2002, Michel Arboi re-wrote most of the NASL engine, and the project was
dubbed ’NASL2’. Michel used bison to handle most of the grammar, and the new
version is way faster, while leaving room for even more improvement in the
future.
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.
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.
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; |
You do not need to declare variables before you use them, but you may want to explicitely mark them as ’local’ in a function, to avoid modifying extern variables (see the relevant section about that).
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.
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.
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.
The function forge_ip_packet() has a lot of elements. These two calls are valid and perform the exact same thing :
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); |
The user will be prompted at runtime for the missing arguments (ip_len, and so on...). Of course, a security test must not directly interact with the user, but this is handy for debugging and quick coding.
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); |
send_packet(packet, use_pcap:FALSE);
|
The for and while work like in C :
For :
for(instruction_start;condition;end_loop_instruction)
{ # # Some instructions here # } |
for(instruction_start;condition;end_loop_instruction)function();
|
While :
while(condition)
{ # # Some instructions here # } |
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; } |
NASL now supports user-defined functions. A user-defined function is defined like this :
function my_function(argument1, argument2, ....) { # # Body of the function # return(some_value); # this is optional } |
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 ! } |
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 :
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.
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"); } |
NASL will not let you open a socket to another host than the host than nessusd wants to test.
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.
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); } } |
You may want your socket to be bound to a special port or to come from a priviledged port (in the case you want your script to connect to a r-service for instance). The functions open_priv_sock_tcp() and open_priv_sock_udp() are here to do that. Their syntax is :
soc = open_priv_sock_tcp(sport:<sport>, dport:<dport>);
soc = open_priv_sock_udp(sport:<sport>, dport:<dport>); |
sport is the source port, and dport is the destination port of the socket. If sport is not specified, then a socket with a source port ¡ 1024 will be opened.
The function close() is used to close a socket. It will internally perform a shutdown() before actually closing the socket.
Reading and writing to a socket is done using one of these functions :
recv(socket:<socketname>,
length:<length> [,timeout : <timeout>) |
recv_line(socket:<socketname>,
length:<length> [, timeout: <timeout>]) |
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); } |
NASL has a set of high level functions, regarding FTP and WWW.
Exemple :
port = get_kb_item("Services/www");
soc = open_sock_tcp(port); req = http_get(item:"/", port:port); send(socket:soc, data:req); r = recv(socket:soc, length:8192); # <r> now contains the index.html # file, plus the web server headers |
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); } |
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’.
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.
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 !
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.
NASL is quite limited when it comes to forging an ICMP packet. Basically, the function accepts the following arguments :
icmp = forge_icmp_packet( ip:<ip_packet>,
icmp_code:<icmp_code>, icmp_type:<icmp_type>, icmp_seq:<icmp_seq>, icmp_id:<icmp_id>, [data:<data>] ); |
The functions get_icmp_element() and set_icmp_elements() work the same way as they does for TCP and ICMP, but can only change these fields.
It should be possible to forge more complicated ICMP paquets by continuing the header in the data parameter, although this has never been tested.
As for ICMP, forging an IGMP packet could be implemented in a better way in NASL. The function forge_igmp_packet() accepts the following arguments :
igmp = forge_igmp_packet( ip:<ip_packet>,
code:<igmp_code>, type:<igmp_type>, group:<igmp_group>, [data:<data>] ); |
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.
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, or your default networking interface if you did
not send any packet before.
This function has optional arguments. Its complete declaration is :
reply = pcap_next([pcap_filter: <filter>,]
[interface: <iface>,] [timeout: <timeout>]); |
NASL provides several handy functions that usually makes your coding easier.
Example :
soc = open_sock_tcp(23);
buffer = telnet_init(soc); display("The remote telnet banner is : ", buffer, "\n"); |
result = getrpcport(program : <program_number>,
protocol: IPPROTO_TCP|IPPROTO_UDP, [version: <version>]); |
This function returns 0 if an error occured (if the program <program_number> is not registered in the remote rpc portmapper for instance).
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" |
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"); } |
The function ereg_replace() gives you the power to change a string in a very flexible way, thanks to the regular expressions. It works the same way as many other regexps tools, so the description will be short.
Its syntax is :
newstr = ereg_replace(pattern:<pattern>,
replace:<replace>, string:<string>); |
Here are some examples :
# extract the server type from the following string : str = "Server : Apache 1.3.2"; server_type = ereg_replace(pattern:"^Server : (.*)$", replace:"\1", string:str); # Another example str = "life is great"; newstr = ereg_replace(pattern:"(.*) (.*) (.*)", replace:"\2 \1", string:str); # 'newstr' is now equal to 'great life' |
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); |
The function crap() is very convenient to test for buffer overflows. It has two syntaxes :
Example :
a = crap(5); # a = "XXXXX";
b = crap(4096); # b = "XXXX...XXXX" (4096 X's) c = crap(length:12, # c = "hellohellohe" (length: 12); data:"hello"); |
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 backslashed 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 |
strlen() returns the length of a string :
a = strlen("abcd"); # a is equal to 4 |
Example :
a = raw_string(80, 81, 82); # a equals to 'PQR'
|
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" |
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.
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.
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 a 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.
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.
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.
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(), security_hole() and security_info() functions which work the same way. security_info() must be used when the attack was a succes but is not an important security problem. 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_info(<port> [, protocol:<proto>]);
security_warning(<port> [, protocol:<proto>]); security_hole(<port> [, protocol:<proto>]); security_info(port:<port>, data:<data> [, 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 the client will display the data argument. This is handy if you must display information caught on the fly, such as a version number.
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.
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); } |
During a test, nessusd will launch more than 600 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.
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.
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.
If you plan to share your script then you should obey to these rules :
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> |
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...
The libnasl package now comes with its own standalone interpretor nasl. Do ’man nasl’ for more details