Renaud Deraison
deraison@nessus.org![]()
Version 1.4.0
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);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);
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;
}
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.
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.
# 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>)Reads
<length> bytes from the socket <socketname>.
This function can be used for TCP and UDP. The timeout option is in
seconds.
recv_line(socket:<socketname>, length:<length> [, timeout: <timeout>])This function works the same way as
recv(), except that it will stop reading data
as soon as the \n character is read. This function only works
with TCP sockets.
send(socket:<socket>, data:<data> [, length:<length>]) :
send the data <data> on the socket <socket>. The optional
argument length tells the function to only send <length>
bytes on the socket. If it is not set, then the data will be sent until
a NULL character is met.
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.
ftp_log_in(socket:<soc>, user:<login>, pass:<pass>) will
attempt to log into the FTP server connected to the freshly open socket
<soc>. This function returns TRUE if it was possible to
log in as <login> with password <pass>. It returns FALSE
if an error occured.
ftp_get_pasv_port(socket:<soc>) issues a PASV
command on the FTP server, and returns the port to open a connection
onto. This allows NASL scripts to retrieve data via FTP.
This function returns FALSE if an error occurred.
is_cgi_installed(<name>) returns a non-zero value if the
cgi <name> is installed on the remote web server. This
function performs a GET request on the remote web server.
If <name> does not start by a slash (/), then
/cgi-bin/ is appended in the front of it. This function
can also be used to determine the existence of a given file.
http_get(), http_head() and http_post() generate
an HTTP/1.0 or HTTP/1.1 compliant request string. They all share the
same prototype :
http_<operation>(item:<item>, port:<port>)
where :
item is the name of the page to get (ie: "/cgi-bin/mycgi")
port is the port of the web server to whom a request will be
made. This is necessary, so that NASL can forge a HTTP/1.0 or HTTP/1.1
request depending on what the remote server is speaking.
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
cgibin() returns one of the paths entered by the user to use
instead of cgi-bin. This function duplicates the run of the script,
which means that if the user set the CGI path to be '/cgi-bin:/my-cgis'
then the script will be executed twice when cgibin() is called - the first
time, it will return '/cgi-bin', the second time it will return
'/my-cgis'.
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'.
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.
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.
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>]);
pcap_filter is a standard pcap filter (man pcap or man tcpdump for details)
interface is the networking interface you want to use
timeout is the number of seconds you want to wait for a packet. Set this to zero if you do not want any timeout
NASL provides several handy functions that usually makes your coding easier.
this_host() takes no argument an returns the IP address
of the host the script is running on.
get_host_name() takes no argument and returns the
name of the currently tested host.
get_host_ip() takes no argument and returns the
IP adress of the currently tested host.
get_host_open_port() takes no argument and returns the
number of the first open TCP port of the remote host. This is useful for some
scripts such as land or a TCP sequence analyzing program which need to work
against an open port.
get_port_state(<portnum>) returns TRUE if the TCP port
<portnum> is open, or if its state is unknown (for instance, if it was not scanned,
or if it is outside the scanned range).
telnet_init(<soc>) initialize a telnet session
on the freshly opened socket <soc> and returns the first line of telnet
data.
Example :
soc = open_sock_tcp(23);
buffer = telnet_init(soc);
display("The remote telnet banner is : ", buffer, "\n");
tcp_ping() takes no argument and returns
TRUE if the remote host answered to a TCP ping request (send a TCP packet
with the ACK flag set).
getrpcport() is the same as the standard function of
the same name. Its syntax is :
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"In addition to this and to the
>< operator defined above, NASL has a set of
functions dedicated to forge or modify strings :
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 :
crap(<length>) : Will return a string of length <length>
containing the character 'X'
crap(length:<length>, data:<data>) : Will return a string of length
<length>, containing the data <data>
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.
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.
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 :
script_id(<id>) which sets the script id. A script id is a unique number referencing the script. If you plan to distribute your nasl script, then the nessus.org folks will attribute one for you. If you plan to keep your set of scripts private (booo!), then you can use any ID number between 90000 and 99000.
script_version(<version>) sets the version of a script. This is usually the Revision\verb tag of the CVS tree.
script_name(language1:<name>, [...]) which sets the script name as
it will appear in the Nessus client window.
script_description(language1:<desc>, [...]) which sets the script
description as it will appear in the client when the user clicks on the name.
script_summary(language1:<summary>, [...]) sets the script summary
as it appears in the tooltips. It must be a sum up of the description that fits
on one line.
script_category(<category>) sets the script category. It must be
one of ACT_ATTACK, ACT_GATHER_INFO, ACT_DENIAL or
ACT_SCANNER.
ACT_GATHER_INFO : the script will be launched among the
first. You know it will not harm the remote computer.
ACT_ATTACK : the script will attempt to gain some
priviledges on the remote host. It may harm the remote system
(if it tests a buffer overflow for instance)
ACT_DENIAL : the script will attempt to crash the remote
host
ACT_SCANNER : the script is a port scanner
script_copyright(language1:<copyright>, [...]) sets the copyright
of the script. It may be your name, a legal notice or whatever.
script_family(language1:<family>, [...]) sets the script family.
There are no clearly defined families, so you may choose to register
the script in the family ``Joe's PowerTools'', altough I do not recommand it.
The currently used families are :
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.
script_require_ports(<port1>, <port2>, ...) : will make nessusd execute your script if and only if at least one of the ports is open. <port> can be either a numeric value (ie: 80) or a symbolic value, as defined in the knowledge base (ie: "Services/www").
script_require_ports(80, "Services/www")
script_require_keys(<key1>, <key2>, ...) : will make nessusd execute your script if and only if all the keys given in argument are defined in the knowledge base.
script_require_keys("ftp/anonymous", "ftp/writeable_dir") will only execute the script if the remote FTP server offers an anonymous access and if there is a writeable directory in it.
script_exclude_keys(<key1>, <key2>, ...) : will make nessusd not execute your script if at least one of the keys given in argument is set in the knowledge base.
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...
Host/OS
queso.nasl and nmap_wrapper.nasl
Host/dead
ping_host.nasl and all the DoS plugins
Type : boolean
Services/www
find_service.nes
Type : port number
Services/auth
find_service.nes
Type : port number
Services/echo
find_service.nes
Type : port number
Services/finger
find_service.nes
Type : port number
Services/ftp
find_service.nes
Type : port number
Services/smtp
find_service.nes
Type : port number
Services/ssh
find_service.nes
Type : port number
Services/http_proxy
find_service.nes
Type : port number
Services/imap
find_service.nes
Type : port number
Services/pop1
find_service.nes
Type : port number
Services/pop2
find_service.nes
Type : port number
Services/pop3
find_service.nes
Type : port number
Services/nntp
find_service.nes
Type : port number
Services/linuxconf
find_service.nes
Type : port number
Services/swat
find_service.nes
Type : port number
Services/wild_shell
find_service.nes
Type : port number
Services/telnet
find_service.nes
Type : port number
Services/realserver
find_service.nes
Type : port number
Services/netbus
find_service.nes
Type : port number
bind/version
bind_version.nasl
Type : string
rpc/bootparamd
bootparamd.nasl
Type : string
Windows compatible
ca_unicenter_file_transfer_service.nasl,
ca_unicenter_transport_service.nasl, mssqlserver_detect.nasl and
windows_detect.nasl
Type : boolean value
finger/search.**@host
cfinger_search.nasl
Type : boolean value
.** is made
finger/0@host
finger_0.nasl
Type : boolean value
0 is made
finger/.@host
finger_dot.nasl
Type : boolean value
. is made
finger/user@host1@host2
finger_0.nasl
Type : boolean value
www/frontpage
frontpage.nasl
Type : boolean value
ftp/anonymous
ftp_anonymous.nasl
Type : boolean value
ftp/root_via_cwd
ftp_cwd_root.nasl
Type : boolean value
CWD ~ bug (see CVE-1999-0082)
ftp/microsoft
ftp_overflow.nasl
Type : boolean value
ftp/false_ftp
ftp_overflow.nasl
Type : boolean value
The libnasl package now comes with its own standalone interpretor
nasl. Do 'man nasl' for more details
This document was generated using the LaTeX2HTML translator Version 2K.1beta (1.48)
Copyright © 1993, 1994, 1995, 1996,
Nikos Drakos,
Computer Based Learning Unit, University of Leeds.
Copyright © 1997, 1998, 1999,
Ross Moore,
Mathematics Department, Macquarie University, Sydney.
The command line arguments were:
latex2html -split 0 -nosubdir nasl_guide.tex
The translation was initiated by Norbert Tretkowski on 2003-12-14