NASL – Nessus Attack Scripting Language

 

A penetration-test runs actual exploits on the identified machine and clarifies whether is safe from a hacker attack. If a pen-test fails then it is certain that any internal or external entity can exploit your computer resources. These exploits grow by leaps and bounds day-to-day and thus the number of pen tests also increases accordingly. Someone has to keep writing these newer and newer pen tests.

 

The single largest used penetration-testing tool in the world today is Nessus, which is available for the operating systems Linux/Unix and Windows. It is a poster boy for the success of Open Source tools. Nessus has over 8000 pen tests, with fresh ones being written every day. All thee tests are coded not in C or perl but with NASL – a scripting language solely used for writing these tests. Our job is to teach you how to write the most complex pen tests.

 

The software comes free for the Linux operating system whereas for Windows, there is a cost attached to it. Maybe, because the corporate world uses software only when it is paid for !!

 

We first create a directory C:\nasl and set the path variable to C:\program files\tenable\newt. This is where the nasl interpreter nasl.exe has been installed. We then write our first nasl program a.nasl.

 

a.nasl

display("hi","\n");

 

To run the nasl interpreter, the command is

 

nasl c:\nasl\a.nasl

 

This results in an output as in

 

hi

hi

 

NASL is a scripting language therefore it is simple and very easy to use. Unlike C/C++ it has fewer rules that a programmer has to go by. We assume that you have used some language in the past, need not be a programming language, JavaScript would also do. Like every language, nasl has its own set of in-built functions. The richness of a language and the ease of use is the number of functions it offers. A function is a word with open and close brackets.

 

In the above code, the display function is used to display some text on the screen. We can specify multiple strings, which are text but within double inverted commas. A \n signifies a new line. We have supplied two parameters or values to the display function. The display function functions in the same manner as the printf in C. Also, a lot of nasl looks and feels like C. Instead on seeing hi once we see it twice. For some reason, the display function displays things twice.

 

display("hi","\n")

 

parse error, unexpected $, expecting ';'Parse error at or near line 2

 

Every language has a rule that denotes end of line or full stop. Some languages permit the use of enter to indicate end, while others like C use a semi colon. In nasl we have to use a semi colon after the closed brackets of the function. In absence of the semicolon like in the earlier example, an error is thrown. The use of semi colon denotes a logical end.

 

i = 20 ;

display("Value of i is " , i , "\n");

i = i + 10;

display("Value of i is " , i , "\n");

i++;

display("Value of i is " , i , "\n");

i = "Vijay Mukhi";

display("Value of i is " , i , "\n");

 

Output

Value of i is 20

Value of i is 30

Value of i is 31

Value of i is Vijay Mukhi

 

Every language allows the use of variables. In NASL, there is no need to create a variable, it can simply be used. Thus, we have created a variable i and set it to 20. The display function displays its value. Do bear in mind that anything in double inverted comma, including variable names are not evaluated by nasl. The + operator increases its value by 10 followed by the use of ++ to increase the value of the variable by 1. Finally we do the unthinkable, we set the value of variable i to a string. We get no complaints from nasl at all. Thus in nasl we do not declare or define a variable, we simply use it. The data type of a variable can change mid way though our program. Nasl handles all the internal house keeping.

 

i = open_sock_tcp(79);

display("The value of i is " , i , "\n");

 

The value of i is 0

 

Lets start doing something really useful with nasl, i.e write a port scanner and simultaneously learn nasl. NASL has a zillion networking functions. The function open_sock_tcp opens a socket or network connection on a certain port that is supplied as a parameter. As we have not specified any IP address, this function checks whether there is a service or server running on port 79 on our machine. As we have no such server running, it results in 0.

 

i = open_sock_tcp(80);

display("The value of i is " , i , "\n");

 

The value of i is 1

 

An http or www server listens on port 80 and therefore the use of this portno shows 1 as the answer. We have two machines on our network, the one we work on is given a IP address of 70.0.0.10 and the other 70.0.0.2. We now run nasl as

 

Nasl –t 70.0.0.2 c:\nasl\a.nasl

 

The value of i is 1

 

The –t option is used to specify the name of a host that will receive the packets. Our code remains the same but the function will now check for the port number on IP address 70.0.0.2. To confirm this act, we had snort an open source IDS running on another dos box. This helps in tracing the actual packets being send by nessus. You could use ettercap or ethereal or any other packet sniffer or logger.

 

i = 80;

sock = open_sock_tcp(i);

display("The value of sock is " , sock , "\n");

 

The value of sock is 1

 

An old adage in programming is that wherever a number can be used, a variable can be used instead. So we set variable i to 80 and pass this as a parameter to our function open_sock_tcp. Now that we have code that tells us which port is open, we simply replicate it for all ports.

 

for ( i = 10 ; i <= 12 ; i++)

{

display("Value of i is ", i , "\n");

}

 

Value of i is 10

Value of i is 11

Value of i is 12

 

A for loop is used when we want to repeat code. It takes three entities separated by semi colons. Up to the first semicolon is executed once, where we set the value of variable i to 10. The second entity checks the condition, if i is less than 12, as it is true the code in the {} is executed. Here we display the value of i. Finally the last entity in the for loop is executed, i.e the code between the ; and the ). Here the value of i is increased by 1. As long as i is less than 12, the for loop condition holds true. The minute it becomes 13, the for loop quits out.

 

i = 80;

sock = open_sock_tcp(i);

if ( sock)

{

display("Port no " , i , " is Open\n");

}

else

{

display("Port no " , i , " is Closed\n");

}

i = 79;

sock = open_sock_tcp(i);

if ( sock)

{

display("Port no " , i , " is Open\n");

}

else

{

display("Port no " , i , " is Closed\n");

}

 

Port no 80 is Open

Port no 79 is Closed

 

The if statement is used to make decisions in the code thus making the language more intelligent. The sock variable can have a value of 1 or 0. The if statement takes a condition and if true, executes the code following the braces. If false, executes the code following the else, which is optional. True in nasl is 1 or any positive value, 0 means false. Thus using this feature, we get an output which is more readable.

 

for ( i = 20 ; i <= 25 ; i++)

{

sock = open_sock_tcp(i);

if ( sock)

{

display("Port no " , i , " is Open\n");

}

else

{

display("Port no " , i , " is Closed\n");

}

}

 

Port no 20 is Closed

Port no 21 is Open

Port no 22 is Closed

Port no 23 is Closed

Port no 24 is Closed

Port no 25 is Open

 

We now place our code in a for loop which takes the variable i from 20 to 25 and discloses the open and closed ports. A pot scanner works in this manner. It sends a Syn packet to each port. If the port is open, it receives a Syn-Ack. If the port is closed, an Rst packet is received. There is more information on this in the tcp rfc. For some reason, the function sends the packet twice.

 

for ( i = 1 ; i <= 1023 ; i++)

{

sock = open_sock_tcp(i);

if ( sock)

{

display("Port no " , i , " is Open\n");

}

}

 

Port no 7 is Open

Port no 9 is Open

Port no 13 is Open

Port no 17 is Open

Port no 19 is Open

Port no 21 is Open

Port no 25 is Open

Port no 42 is Open

Port no 53 is Open

Port no 80 is Open

Port no 135 is Open

Port no 139 is Open

Port no 445 is Open

Port no 637 is Open

Port no 1002 is Open

 

The above program scans the first 1024 reserved ports and displays the ones that are open. The IP address 70.0.0.2 is a Windows 2000 box with no changes made to the default installation. The output is a clear indication that this out-of-the box windows 2000 os is insecure as it has a large number of ports open by default. When we changed the for loop to iterate 65535 times,  we were horrified to find out the number of ports that were open.

 

Lets move away from the port scanner and learn some more of the language

 

function abc()

{

display("In abc\n");

}

 

abc();

 

In abc

 

Function abc is user-defined. To write such a function we first use the keyword function, the name abc and then the code within brackets. The only restriction here is that the code must be placed before the function call.

 

function abc()

{

display("In abc\n");

return(100);

}

i = abc();

display("abc returns " , i , "\n");

 

In abc

abc returns 100

 

The return function returns a value. In this case as we return 100 there the value of i is 100.

 

function abc( val, zzz)

{

display("In abc\n");

return(val + zzz);

}

 

i = abc(zzz:20 , val:10);

display("abc returns " , i , "\n");

 

In abc

abc returns 30

 

The function call is very different from the way it is in the C language. In these languages we need to remember the order of parameters, however in NASL we use a different approach. Each parameter is given a name and we preface the value of the parameters by the name. Thus we do not call the abc function as (20,10) but as (zzz:20, val:10). The order of parameters is not important thus function abc could be called as (val:10, zzz:20). Which is a better approach is for the user to decide.

 

sock = open_sock_tcp(25);

data = recv_line ( socket:sock , length:1024);

display(data);

 

220 mach2 Microsoft ESMTP MAIL Service, Version: 5.0.2195.6713 ready at  Mon, 4 Jul 2005 09:40:36 +0530

nasl -t 70.0.0.2 c:\nasl\a.nasl

 

In the above code, we first open a socket to port 25, which is the SMTP port. Once again we must refresh your mind by stating that the Windows 2000 machine at IP address 70.0.0.2 has an SMTP server running on it. You can specify any IP address that has a SMTP server running.

 

The minute a SMTP server receives a connect packet it sends a number 220 denoting success along with a banner giving its version number and name. In order to capture this banner we use the recv_line function that takes two parameters named socket and length.

 

This function is called with the socket handle sock, that we just opened and the maximum length of the line, 1024 that is to be returned. The variable data will now contain the banner. The value is displayed using the display function.

 

sock = open_sock_tcp(21);

data = recv_line( socket:sock , length:1024);

display(data);

 

220 mach2 Microsoft FTP Service (Version 5.0).

The FTP protocol listens on the port 21. The rules remain the same as SMTP, i.e. after connecting to the ftp server, the server sends a welcome banner to the client.

 

abc();

 

c:\nasl\a.nasl(70.0.0.2): Undefined function 'abc'

 

We get an error as the abc function is undefined. The whole idea of having functions is to reuse that block of code. However, placing functions in the nasl file, kills the idea of sharing. Therefore it is wise to place all our function code in inc files. We create b.inc as

 

b.inc

function abc()

{

display("In abc\n");

}

 

a.nasl

include("c:\nasl\b.inc");

abc();

 

In abc

 

Then using the include function, the file is brought in and added to our code. The net result is that all code that is present in b.inc gets added to our nasl file. If we do not specify the full path name it looks for the file in another directory.

 

port = get_http_port(default:80);

display("port is ", port , "\n");

 

c:\nasl\a.nasl(70.0.0.2): Undefined function 'get_http_port'

 

The compiler very strongly states that it does not understand the function get_http_port. This function is not an in-built function but is present in a file called http_func.inc.

 

include("http_func.inc");

port = get_http_port(default:80);

display("port is ", port , "\n");

 

port is 80

 

This inc file http_func.inc is written by the smart guys who wrote nessus. It is found in the directory c:\program files\tenable\newt\plugins\scripts. There are over 37 such inc files. The function get_http_port takes one parameter that we assume, is the http port the system uses.

 

This function will detect the actual http port used by connecting to the http server on the target machine. We are aware of this because we have studied the code that comprises our function. Some time later we will explain the actual working of the function. Normally the http port is 80. It is better to use the above function to find out the http port on the target machine than assume what the port is.

 

include("http_func.inc");

sock = http_open_socket(80);

req = string("GET / HTTP/1.0\r\n","Accept: */*\r\n","\r\n");

send(socket:sock, data:req);

r = recv(socket:sock, length:4096);

display(r , "\n");

http_close_socket (sock);

 

HTTP/1.1 200 OK

Date: Mon, 04 Jul 2005 04:56:14 GMT

Server: Apache/1.3.17 (Win32)

Content-Location: index.html.en

Vary: negotiate,accept-language,accept-charset

TCN: choice

Last-Modified: Fri, 19 Jan 2001 08:09:48 GMT

ETag: "0-54a-3a67f64c;41e6035c"

Accept-Ranges: bytes

Content-Length: 1354

Connection: close

Content-Type: text/html

Content-Language: en

Expires: Mon, 04 Jul 2005 04:56:14 GMT

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">

<HTML>

 <HEAD>

  <TITLE>Test Page for Apache Installation</TITLE>

 </HEAD>

The above program reads an entire html file from the target machine. The http_open_socket function is called with the http port number to open a socket. We could have used the function open_sock_tcp instead, but opted for the above because the program we found on the net uses this function instead. Both do the same function, it returns a handle to the connection.

 

The string function comes handy to concatenate individual strings and us used to create our http request. A http request begins with the Get clause and then a slash  followed by the name of the file. No file by default references index.html in the apache world, default.htm in the IIS world. Then the send function is used to send the string across to the target. The send function needs two named arguments, the socket parameter is passed the sock variable, and the data parameter the req string. Then, instead of using the recv_line function that stops at a line, the more generic recv function is used, which takes the same parameters, a socket and a length and returns in r the data received from the socket.

 

The display function is finally used to display the file contents received and as good guys do it, we close the socket handle using http_close_socket.  The file received starts with a header and then the actual content. A header is a word that ends with a colon.

 

include("http_func.inc");

sock = open_sock_tcp(80);

req = string("GET / HTTP/1.0\r\n","Accept: */*\r\n","\r\n");

send(socket:sock, data:req);

r = recv(socket:sock, length:4096);

if("Server: Apache" >< r)

display("Apache Server running on host\n");

else if("Server: Microsoft-IIS" >< r)

display("IIS Server running on host\n");

http_close_socket(sock);

 

Apache Server running on host

IIS Server running on host

 

nasl -t 70.0.0.2 c:\nasl\a.nasl

nasl -t 70.0.0.10 c:\nasl\a.nasl

 

We have IIS running on host 70.0.0.10 and Apache running on host 70.0.0.2. Every web server adds a header called Server followed by its name, for e.g. in Apache we see Server: Apache. To extract the server name, all that is required is a search for the string ‘Server:’. Since this feature is used very often, NASL offers an operator  ><. This operator looks for a string specified before the >< in the string specified after. We search for  Server: Apache in the string r. If a match is found, a message is displayed. In the same vein, IIS uses the name Microsoft-IIS. This check is important as some exploits only work on certain servers with certain version numbers. Thus before running an exploit these prerequisite checks are a must so that the system meets with our conditions.

 

Now we venture into a few practical exploits and show you how they have coded. We will use the sample code supplied by the guys at nessus as the whole idea of this tutorial is to understand nasl code.

 

FTP exploits

 

sock = open_sock_tcp(21);

r = recv(socket:sock , length : 1024);

display(r , "\n");

req = string("USER anonymous\r\n");

send(socket:sock, data:req);

r = recv(socket:sock , length : 1024);

display(r , "\n");

req = string("PASS vijay@mukhi.com\r\n");

send(socket:sock, data:req);

r = recv(socket:sock , length : 1024);

display(r , "\n");

 

220 mach2 Microsoft FTP Service (Version 5.0).

331 Anonymous access allowed, send identity (e-mail name) as password.

230 Anonymous user logged in.

 

As always, a socket connection to port 21 is opened and in response to it the initial banner is received. The FTP rfc details the rules of ftp whereby the user name has to be sent across. For this purpose, the command USER is used followed by then the name of the user, anonymous. The response to this command is a statement clarifying that anonymous users are allowed to the server and an e-mail id is required as the password. To supply this information, the PASS command is used with the e-mail id. Any password is allowed. Once done, we are logged in.

 

sock = open_sock_tcp(21);

i = ftp_log_in(socket:sock, user:"anonymous" , pass: "vijay@mukhi.com" );

display("i = " , i ,"\n");

 

i = 1

 

Instead of writing all that code down, we could have used the function ftp_log_in instead which takes a socket, a user and pass parameters and does the internal plumbing.

 

sock = open_sock_tcp(21);

i = ftp_log_in(socket:sock, user:"anonymous1" , pass: "vijay@mukhi.com" );

display("i = " , i ,"\n");

 

I = 0

 

The user anonymous is special as he is the only user that can log in without a valid password. Change the user name to anonymous1 and the result will be a 0 as access is denied.

 

sock = open_sock_tcp(21);

r = recv(socket:sock , length : 1024);

display(r , "\n");

req = string("USER anonymous1\r\n");

send(socket:sock, data:req);

r = recv(socket:sock , length : 1024);

display(r , "\n");

req = string("PASS vijay@mukhi.com\r\n");

send(socket:sock, data:req);

r = recv(socket:sock , length : 1024);

display(r , "\n");

 

220 mach2 Microsoft FTP Service (Version 5.0).

331 Password required for anonymous1.

530 User anonymous1 cannot log in.

With the user name as anonymous1, a valid password is to be supplied. However, the user anonymous1 does not exist on host 70.0.0.2 and an error is generated.

 

Lets write a simple nasl script that tells us whether anonymous ftp access is allowed or not. This is one such check required on all ftp servers to ensure that anonymous logins are not permitted.

 

sock = open_sock_tcp(21);

i = ftp_log_in(socket:sock, user:"anonymous" , pass: "vijay@mukhi.com" );

if ( i == 1)

display("Anonymous ftp access is allowed. A bad thing\n");

else

display("Anonymous ftp access is not allowed. A good thing\n");

 

Anonymous ftp access is allowed. Not good!!!

 

sock = open_sock_tcp(21);

if ( ftp_log_in(socket:sock, user:"Administrator" , pass: "" ))

display("No password for Administrator\n");

else

display("Administrator has a valid password\n");

 

Administrator has a valid password

 

Many systems has a user called Administrator with no password. The above script tries to logon as Administrator with a blank password. When the if statement is true, an error message is displayed.

 

include("ftp_func.inc");

sock1 = open_sock_tcp(21);

ftp_log_in(socket:sock1, user:"anonymous",pass:"aa@bb.com");

data = string("MKD /vijay\r\n");

send(socket:sock1, data:data);

r = recv_line(socket:sock1, length:1024);

display("sock1: " , r , "\n");

 

sock1: 257 "/vijay" directory created.

 

We once again anonymously log in and then use the MKD command to create a directory on the host running the ftp server, 70.0.0.2. The Microsoft’s ftp server root directory is c;\inetpub\ftproot therefore the directory vijay will be created within it. The recv_line function declares whether the above command has executed successfully.

 

include("ftp_func.inc");

sock1 = open_sock_tcp(21);

ftp_log_in(socket:sock1, user:"anonymous",pass:"aa@bb.com");

send(socket:sock1, data:"MKD /vijay2\r\n");

r = recv_line(socket:sock1, length:1024);

display("sock1: " , r , "\n");

 

The above program fails because all ftp, smtp, http commands end with a \r\n. It is only the double inverted commas or the string function that evaluate a \r\n to be an enter. Thus it is safe to use the string function or a single inverted whenever the \r\n are required. The display command first calls the string function and then displays the text. In the above case the system waits for an enter from our side.

 

send(socket:sock1, data:'MKD /vijay2\r\n');

 

The correct way to do it is use the single inverted commas.

 

include("ftp_func.inc");

sock1 = open_sock_tcp(21);

ftp_log_in(socket:sock1, user:"anonymous",pass:"aa@bb.com");

data = string("CWD /\r\n");

send(socket:sock1, data:data);

r = recv_line(socket:sock1, length:1024);

display("sock1: " , r , "\n");

pasv = ftp_get_pasv_port (socket:sock1);

display("second port is ", pasv , "\n");

sock2 = open_sock_tcp(pasv);

data = string("RETR a.txt\r\n");

send(socket:sock1, data:data);

r1 = ftp_recv_line(socket:sock2);

display("File Contents: " , r1 , "\n");

r = ftp_recv_line(socket:sock1);

display("sock1: " ,  r , "\n");

 

sock1: 250 CWD command successful.

second port is 1677

File Contents: vijay mukhi

sock1: 125 Data connection already open; Transfer starting.

We now proceed further to write a program that will retrieve an entire file from the server. The fourth line is not really necessary, it just shows us one more command that can be used within the ftp world, CWD is used to change the current working directory to another one. Here the directory is changed to / or the root which is the default. The recv_line function discloses the success of this command.

 

The ftp protocol uses 2 ports when transferring data. Port 21 is used for passing commands across like MKD, CWD etc whereas file transfers take another port no called the data port. The function ftp_get_pasv_port is used to negotiate a second port address from the ftp server. We get a port number larger than 1024 and in our case it is 1677. do bear in mind that it is this port no that will receive the contents of the file but it will keep changing with every file. Thus we are creating two channels, one 21 for sending commands, the other for sending the actual data across, the data channel. Once done, a socket connection is opened on this port 1677 and the string RETR is used with the file name and sent to the first port or control channel. Once the send function sends the string across, we wait on sock2 to receive the contents of the file. Simultaneously, sock1 keeps us informed about the file transfer. This may get a little confusing as the file is transferred using sock2, whereas sock1 is used to control the transfer.

 

We hope that you created a file called a.txt in the ftproot directory. The function ftp_get_pasv_port simply sends the command PASV over to the server. The server sends the string Entering Passive mode and in round brackets its IP address in a dotted decimal format, but separated by commas and not by dots. This is followed by 2 numbers, the first to be multiplied by 256 and the second by 1 and then added up to give us the port number of the data channel. Once the transfer ends, the server sends a message ‘226 Transfer complete\r\n’. Some Linux daemons allow the users to log in a as user NULL and password NULL. This check can be performed too. Finally, to close a ftp connection gracefully, the Quit command is used. 

We must admit that this is the simplest ftp client we have written, using nasl.

 

There is an attack called the ftp glob overflow which crashes a server by creating too many directories and then listing them.

 

include("ftp_func.inc");

soc = open_sock_tcp(21);

ftp_log_in(socket:soc, user:"anonymous", pass:"aa@bb.com");

port2 = ftp_get_pasv_port(socket:soc);

soc2 = open_sock_tcp(port2);

send(socket:soc, data:'NLST *\r\n');

b = ftp_recv_line(socket:soc);

display(b);

b = recv(socket:soc2, length:1024);

display(b);

 

125 Data connection already open; Transfer starting.

a.txt

b.txt

One more program that displays the contents of a directory. The data channel port number is in port2, so this channel is opened first. Then the command NLST is sent with a wild card which lists files in a directory. At present, there are two files a.txt and b.txt so * will list both rgwaw files. If the command used is NLIST a*, only files beginning with a would have been listed. The data channel list the files and the soc channel keeps us updated with the transfer. RMD then removes the directory.

 

include("ftp_func.inc");

soc = open_sock_tcp(21);

ftp_log_in(socket:soc, user:"anonymous", pass:"aa@bb.com");

port2 = ftp_get_pasv_port(socket:soc);

soc2 = open_sock_tcp(port2);

send(socket:soc, data:'NLST *\r\n');

b = recv(socket:soc2, length:1024);

display(b);

if ( '.avi' >< b )

display("Sound Files found\n");

 

a.txt

c.avi

Sound Files found

 

Many people are concerned whether certain sound files, video files are present on their servers. The NLST command can be used to loop through all the files and then check whether certain files with certain extensions are there on the ftp server. Nevertheless it becomes a lot more complex. Also a check is required to track the banner for a fake ftp server. The KIBUV.B worm installs a fake ftp server on port 7955. It has a banner 220 fuckFtpd 0wns j0 and is also found on ports 14920 and 42260.

 

Certain login combinations also need to be scrutinized for eg, user name being admin and password being password. There are a large number of tests for different user name and passwords combinations. Also earlier versions of sql server can have a blank password for user sa. Further, one can crash a novell ftp server by sending it nulls.

 

soc = open_sock_tcp(21);

req = crap(15);

ftp_log_in(socket:soc, user: req, pass:"aa@bb.com");

 

It is also possible to crash an ftp server by sending it huge chunks of data or by using too large user name. The crap function creates such a large string for us. In this case we get 15 X’s. A buffer overflow exploit uses the crap function to locate the position when the buffer overflows.

In the above case we are sending a command USER XXXXXXXXXXXXXXX, 15 X’s in all. At times the buffer can be overflowed using 100 k of data. This is where the crap function is really useful.

 

There is a test wherein every FTP command is used with large amount of data along with the crap function and sent across.

 

soc = open_sock_tcp(21);

req = crap(data: "ab" , length:5);

ftp_log_in(socket:soc, user: req, pass:"aa@bb.com");

 

The crap function takes two parameters, the data variable that contains the string to be replicated, and the total length of the string in the length variable. As the length is five, the user name send across will be ababa.

 

soc = open_sock_tcp(21);

ftp_log_in(socket:soc, user: "anonymous", pass:"aa@bb.com");

for(i=0;i<40000;i=i+1)

{

port2 = ftp_get_pasv_port(socket:soc);

soc1 = open_sock_tcp(port2);

display("Port no is " , port2 , "\n");

}

 

In the above program, we are creating 40000 data channels. This is one method used extensively to create a denial of service where resources get used up unnecessary. As per our understanding, we thought that the system would create for us 40,000 data channels. But we were wrong, as after some time the ftp server kept giving us the same port numbers. The software knows that a DOS is being attempted and allows other legitimate users access. It keeps sending the port number up to 5000 and then restarts from 1027.

 

display("hi\n");

exit(0);

display("bye");

 

hi

 

The exit function does it job of exiting. Anytime to exit from our code, the exit function is called. Normally the banner for a certain version of the product is checked. If the version does match, we immediately exit out. The EXIT command is used to gracefully quit the ftp connection. Many exploits send commands with very very long argument. A buffer overflow occurs and the server shuts down.

 

soc = open_sock_tcp(21);

ftp_log_in(socket:soc, user:"anonymous",pass:"nessus@");

send(socket:soc, data:string("CWD ", rand(), "-", rand(), "\r\n"));

r = recv(socket:soc, length:1024);

display(r);

 

550 8482-15703: The system cannot find the file specified.

550 9808-21412: The system cannot find the file specified.

In the past, the ftp servers when asked to change to an unknown directory would leak out the full path name of the root directory. This is not an exploit but an information leak. Now days these leaks have been plugged by most ftp servers.

 

The rand function returns a random number. The program when executed twice gives different numbers. The minus sign is to separate the two random numbers. But still, our ftp server does not leak any directory names.

 

 

IIS exploits

 

include("http_func.inc");

req = http_get(item:"/NULL.printer", port:80);

display(req,"\n");

 

GET /NULL.printer HTTP/1.1

Connection: Close

Host: MACH2

Pragma: no-cache

User-Agent: Mozilla/4.75 [en] (X11, U; Nessus)

Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */*

Accept-Language: en

Accept-Charset: iso-8859-1,*,utf-8

We start the script with some http helper functions by including the file http_func.inc. The http_get function takes two parameters, the first is the name of the file and the second the port. It then creates the actual request that is to be sent across. This avoids the mugging up required on the headers that an http server requires. Our file name is /NULL.printer. The function deciphers the other headers of which some are optional, but why bother as we do not have to write them.

 

include("http_func.inc ");

req = http_get(item:"/NULL.printer", port:80);

soc = http_open_socket(80);

send(socket:soc, data:req);

r = http_recv(socket:soc);

http_close_socket(soc);

display(r , "\n");

if("Error in web printer install" >< r)       

display("IIS bug found\n");

 

HTTP/1.1 500 13

Server: Microsoft-IIS/5.0

Date: Tue, 05 Jul 2005 04:57:57 GMT

X-Powered-By: ASP.NET

Connection: close

Content-Type: text/html

<b> Error in web printer install.</b>

IIS bug found

 

The Internet Information Server IIS supports the Internet Printing Protocol IPP from version 5 onwards. This protocol is implemented as an ISAPI extension. There has been a buffer overflow found in this extension and therefore it is recommended to disable this feature if not used. To disable it, choose the web server in the internet services manager, right mouse button, properties, Home directories tab, Configuration. Choose .printer in the extensions column and then remove.

 

The request is first sent out and then function http_recv is used to receive data from the http server. If this contains the string Error in web printer install, then we know that the IIS has the IPP protocol installed. This could lead to a security breach.

 

include("http_func.inc");

include("http_keepalive.inc");

req = http_get(item:"/b.html", port:80);

res = http_keepalive_send_recv (data:req, port:80);

display(res);

 

HTTP/1.1 200 OK

Server: Microsoft-IIS/5.0

Date: Tue, 05 Jul 2005 06:31:13 GMT

Content-Type: text/html

Accept-Ranges: bytes

Last-Modified: Sun, 03 Apr 2005 01:11:30 GMT

ETag: "08d2d11ea37c51:8f6"

Content-Length: 6

hi 1

With include files, we can create our own functions that shorten the code we have to write. The above example demonstrates this fact. The http_get functions, creates the request to be sent across and the http_keepalive_send_recv functions sends the request and waits for an answer. These two functions are all that we need to pick up a file on the server.

 

include("http_func.inc");

data = string("GET ../../\r\n");

soc = open_sock_tcp(80);

send(socket:soc, data:data);

sleep(2);

soc2 = open_sock_tcp(80);

if(!soc2)

display("server down");

else

display("server up exploit does not work\n");

 

server up exploit does not work

 

Earlier, a request like GET ../../ to IIS was allowed which eventually would result in IIS crashing. The above request simply tells IIS to go one directory above and then again one level up. This would result in a denial of service as IIS would go beyond its directory structure. Today IIS simply comes back stating it to be a bad request. We sent a similar request over and then used the sleep function to sleep for 2 seconds. Then once again we connected to IIS. Since we received a valid socket it only signifies that the server is yet up and running.

 

Lets take a break from writing nasl code and instead write an actual pen test. Nessus installs itself in the directory c:\program files\tenable\newt. All the exe files are stored in this directory. Within it is a directory plugins that has a scripts subdirectory with all the nasl scripts. There are over 8000 files here. We moved all of them to another directory. SO in a sense we deleted all the nasl files. You heard us right, deleted all nasl files. Now create a file v.nasl as follows.

 

v.nasl

if (description)

{

 script_id (11420);

 script_bugtraq_id (420);

script_name(english:"Vijay Mukhi is the script name ");

desc["english"] = "This is a long description";

script_description (english:desc["english"]);

script_summary (english:"This is a script summary");

script_category (ACT_GATHER_INFO);

script_family (english:"This is a script family");

script_copyright(english:"This script is Copyright (C) 2005 Vijay Mukhi");

exit(0);

}

security_warning (port:81, data:"Vijay Mukhi security warning");

 

We now run the exe file build.exe in the newt directory. This program goes into the plugins\scripts directory and reads all the available nasl scripts. It then builds a file, plugins.xml with the details of the plugins installed. In our case we have 3 plugins, one of ours, two of the existing ones. If there were 8000 plugins, the build would take a very long time to create this file.

 

Now start nessus or the newt security scanner, which is a icon on your desktop. We choose the first option new scan task and specify the IP address of our class C network 70.0.0.2. Then click on next. The window has four radio buttons to chose from which we choose the last one as we would like to specify our own plugins. Newt runs all the pen tests it can lay its hands on.Click on next gives a screen with two columns. The first is entitled families and has three entries. The third entry says This is script family.

 

Coming back to v.nasl, the description variable is true only when the script is executed through the newt security scanner. Thus running it though nasl will not activate the above code as the description variable will be false. The function script_family has a parameter english that specifies a family name. It is in this manner that the pen tests are grouped together.  The minute we click on it, the check box goes on and all the plugins belonging to this family is displayed on the right. We are shown just one with the parameter we have given to the function script_name and parameter english. By using the language name as the parameter, the same function can be used for registering different languages.  We select it using the link. This brings about a dialog box where other useful information about the pen test can be given. The first is the id of the script specified by using the script_id function. Do not give too large a number or a error results. This is only used for documentation. Then we have the Plugin Name which is the repeat of the script_name function. This is followed by the name of the plugin file v.nasl.

 

Finally we have the summary which is specified using the function script_summary. Then we have a long text area for description using the desc array with english as a parameter. We could have called the variable desc1 but all the nasl code we have seen calls the array desc. Arrays in nasl are like arrays in any programming language. This desc is specified with the script_description function. For some reason we first need to create an array variable and pass this variable to the description function. All other functions directly pass a string. Here we do it in a round about manner.

 

We then click on scan on and if  we get an error, start again. We are given a report that starts with a start time and end time and Ip address of host scanned and the number of warnings, errors, holes etc. We get only a warning and the following output.

 

hosts2-ns (81/tcp)

 

Vijay Mukhi security warning
BID : 420
Plugin ID : 11420

 

The only line of code we have is the function security warning. This takes a port number and a description. The port number 81 is displayed in the first column and the description or data in the second column. The function script_bugtraq_id was supplied a number 420 which was the bugtraq data base id and this is followed by our plugin id. The bugtraq id is for our reference only. The function script_category is important as it specifies the type of the script to the In our case we say that we are simply gathering information only. There are about half a dozen such categories.

 

security_hole(port:81, data:"Vijay Mukhi security warning");

exit(0);

 

If we change the second last line to security_hole, the icon changes to a X and the number of hole increases by 1, not the warnings. We do not need to re run the build program as the changes are being made in the code and not in things like the name of the script.

 

security_note(port:81, data:"Vijay Mukhi security warning");

 

Finally changing the function to security note allows us to once again add to the notes, we see a different icon too. There is a way of also running exe files which are given an extension of nes. This is how we can combine our knowledge of nasl and create our own plugins.

 

Creating our own packets

 

ip = forge_ip_packet( ip_hl: 5, ip_v : 4, ip_tos : 0, ip_len : 20, ip_id:12, ip_off : 0,

     ip_ttl : 255, ip_p:2, ip_src : 70.0.0.4);

display(this_host() , "\n");

send_packet(ip,pcap_active:FALSE);

 

70.0.0.10

 

Let us now create our own packets. The ip packet can be created using the function forge_ip_packet. This function takes up a large number of parameters. We start in the order they appear in the IP header. The first four bits is the version of ip used, 4 and we set this value as the ip_v parameter. The next four bits are the length of the ip header and in our case as we are not adding anything to ip, it is 5., This number is to be multiplied by 4 and that is how we get 20. The length of the ip header can vary from minimum 20 to maximum 60 as four bits hold a number from 0 to 15. The parameter name is ip_hl.  Then we have the type of service which signifies the importance of packets to the routers. Unfortunately most routers ignore this field called ip_tos.

 

Then there are two bytes that give the total length of the packet. The field ip_len is set to 20 as our packet is only an ip packet. The ip_id field holds the id of the packet. Most of the time this field is ignored. Our packet has an id of 12.

 

The maximum length of an ip packet is 65536 but Ethernet networks cannot carry a packet larger than 1500 bytes. Thus if a packet is 3000 bytes large it has to be send as two packets or fragments. The field ip_off talks about the fragmentation. 0 refers to no fragments. Then is a field ip_ttl or time to live that decides on the number of routers our packet can cross before it is to be killed or dropped. We choose the maximum value of 255 but even 20 is too large.

 

The job of ip is simply to deliver a packet to the destination. IP never travels alone, there is some other protocol like TCP or ICMP or UDP following. The field ip_p is the protocol following ip for which we have given some non existent protocol 2. Then we have 2 bytes of the ip checksum which is calculated by an internal function. The next four bytes are the source ip address where we write 70.0.0.4 even though our ip address is 70.0.0.10. The field ip_src takes the ip address in a dotted decimal notation. The last four bytes are the destination ip address. The field ip_ttl is an optional field. If we give a value other than the ip address specified by the –t option, the system emits an error. Which means that we can forge all the bytes of an  ip packet but need to keep the –t and the ip_ttl fields the same.

 

The function this_host returns our ip address. Thereafter, we call a function send_packet with the ip packet to send across a packet. The pcap_active parameter will be explained later.

 

Prior to sending the packet, i.e running the program, run snort as in snort –dev and the following packet was captured.

 

Snort Output

07/06-10:06:34.097968 0:0:E8:DF:A4:66 -> 0:0:E8:D7:5E:7C type:0x800 len:0x22

70.0.0.4 -> 70.0.0.2 PROTO002 TTL:255 TOS:0x0 ID:12 IpLen:20 DgmLen:20

 

The first line gives us a date and time when the packet capture happened and the Ethernet addresses of the source and destination. Then the ip address of the source and destination is shown. This is followed by the protocol, ttl, tos, id , length of ip and the total length of the datagram.

 

ip = forge_ip_packet(ip_hl: 5, ip_v : 4,ip_tos : 1, ip_len : 40,ip_id:12,ip_off : 0,

     ip_ttl : 5, ip_p: IPPROTO_TCP, ip_src : this_host());

tcp = forge_tcp_packet (ip:ip, th_sport:70, th_dport:71, th_seq:4,

      th_ack:5, th_off:5, th_flags:TH_ACK, th_win:0x1000, th_x2:0, th_urp:0);

send_packet(tcp, pcap_active:FALSE);

 

Now lets send a ip-tcp packet over. We first use the forge_ip_packet function where for the protocol we do not use 2 but the value for tcp that is IPPROTO_TCP. If we do not for some reason like using constants use the number 6 which stands for tcp. In the same vein 1 stands for the icmp protocol. The length of the packet is 40, 20 for ip, 20 for tcp. The function forge_tcp_packet takes a parameter ip that is the ip packet just created. We then start with the source and destination ports 70 and 71 using the parameters th_sport and th_dport. These fields are two bytes large. This is followed by 2 four byte fields, the sequence number or th_seq that is given a value of 4 and th_ack which is the acknowledgement number that has a value of 5. Then we have 6 flags of which we set only one TH_ACK or the ack flag. The th_off field is the length of the tcp header multiplied by 5, the same as ip. Then we have the window size or th_win which is  the amount of data that can be send without waiting for an acknowledgement. The last field is the th_urp field or the urgent pointer. This field is always zero as it is a way to tell the other side, some urgent data is in the packet please read. The check sum as usual is optional and the th_x2 field are the 6 unused bytes that are always zero. We use the same send_packet function with the tcp packet and not ip.

 

Snort output

07/06-11:47:43.474595 0:0:E8:DF:A4:66 -> 0:0:E8:D7:5E:7C type:0x800 len:0x36

70.0.0.10:70 -> 70.0.0.2:71 TCP TTL:5 TOS:0x1 ID:12 IpLen:20 DgmLen:40

***A**** Seq: 0x4  Ack: 0x5  Win: 0x1000  TcpLen: 20

 

The tcp portion of snort shows the Ack flag on, the sequence and ack numbers, the window size and tcp length.

 

07/06-12:17:42.909187 0:0:E8:DF:A4:66 -> 0:0:E8:D7:5E:7C type:0x800 len:0x36

70.0.0.10:1080 -> 70.0.0.2:80 TCP TTL:5 TOS:0x1 ID:12 IpLen:20 DgmLen:40

******S* Seq: 0x4  Ack: 0x5  Win: 0x1000  TcpLen: 20

07/06-12:17:42.909705 0:0:E8:D7:5E:7C -> 0:0:E8:DF:A4:66 type:0x800 len:0x3C

70.0.0.2:80 -> 70.0.0.10:1080 TCP TTL:128 TOS:0x0 ID:58331 IpLen:20 DgmLen:44 DF

***A**S* Seq: 0xBDD4A946  Ack: 0x5  Win: 0x40E8  TcpLen: 24

TCP Options (1) => MSS: 1460

07/06-12:17:42.909743 0:0:E8:DF:A4:66 -> 0:0:E8:D7:5E:7C type:0x800 len:0x36

70.0.0.10:1080 -> 70.0.0.2:80 TCP TTL:128 TOS:0x0 ID:8881 IpLen:20 DgmLen:40

*****R** Seq: 0x5  Ack: 0x5  Win: 0x0  TcpLen: 20

 

ip = forge_ip_packet(ip_hl: 5, ip_v : 4,ip_tos : 1, ip_len : 40,ip_id:12,ip_off : 0,

     ip_ttl : 5, ip_p:6, ip_src : this_host());

tcp = forge_tcp_packet(ip:ip, th_sport:1080, th_dport:80, th_seq:4,

      th_ack:5, th_off:5, th_flags:TH_SYN, th_win:0x1000, th_x2:0, th_urp:0);

send_packet(tcp, pcap_active:FALSE);

 

Lets test out our code by sending a syn packet to Ip address 70.0.0.2 which will reply with a syn ack packet. We set the syn flag by using the enumeration TH_SYN. This is the first packet in the snort output. The machine 70.0.0.2 sends us a packet with two flags SYN and ACK set.  We send a sequence number of 4, the return packet increased it by 1 to 5 as the ACK number. The seq number from 70.0.0.2 is a large number 0xBDD4A946. When our copy of windows receives such a packet it sends back a RST packet. For a rst packet the seq no and ack no are not meaningful. Thus we successfully send a syn packet, received a syn ack which windows took objection to and send back a rst. All this happened because we are connecting to a web server running on the target machine on port 80. This is the value of the destination port field, the port on the other side.

 

ip = forge_ip_packet(ip_hl: 5, ip_v : 4,ip_tos : 1, ip_len : 40,ip_id:12,ip_off : 0,

     ip_ttl : 5, ip_p:6, ip_src : this_host());

tcp = forge_tcp_packet(ip:ip, th_sport:80, th_dport:1080, th_seq:4,

      th_ack:5, th_off:5, th_flags:TH_SYN, th_win:0x1000, th_x2:0, th_urp:0);

send_packet(tcp, pcap_active:FALSE);

 

07/06-13:33:37.354257 0:0:E8:DF:A4:66 -> 0:0:E8:D7:5E:7C type:0x800 len:0x36

70.0.0.10:80 -> 70.0.0.2:1080 TCP TTL:5 TOS:0x1 ID:12 IpLen:20 DgmLen:40

******S* Seq: 0x4  Ack: 0x5  Win: 0x1000  TcpLen: 20

07/06-13:33:37.354678 0:0:E8:D7:5E:7C -> 0:0:E8:DF:A4:66 type:0x800 len:0x3C

70.0.0.2:1080 -> 70.0.0.10:80 TCP TTL:128 TOS:0x0 ID:58567 IpLen:20 DgmLen:40

***A*R** Seq: 0x0  Ack: 0x5  Win: 0x0  TcpLen: 20

 

We now send a syn packet to port 1080. We know that we have no server running on this machine at that port. According to the rfc, if we send a syn packet on a port where no server is accepting packets, the os must send a RST packet. This is exactly what happens. This is how a port scanner works, it keeps sending syn packets and if it receives a Ack, the port is open, rst no port open, no packet, a firewall is between us and the target.

 

ip = forge_ip_packet(ip_hl: 5,ip_v : 4,    ip_tos : 0,ip_len : 100,ip_id:4,ip_off : 0,

    ip_ttl : 0xff,ip_p:0x03,ip_src : this_host());

ipo = insert_ip_options (ip:ip, code:0xE4, length:2, value:raw_string(0x03, 0x04));

ipo += string("ABCDEFG");

send_packet(ipo, pcap_active:FALSE);

 

00 00 e8 d7 5e 7c 00 00 e8 df a4 66 08 00

46 00  00 1f 00 04 00 00 ff 03 47 c5 46 00 00 0a 46 00  00 20

e4 02 03 04

41 42 43 44 45 46 47          

 

We can change everything about the ip header whether it is logical or not. We now have used ethereal another network sniffer to show you the bytes send by nessus. After installing ethereal, we choose the menu Capture, Options, choose the network card and then capture. We then click again on Capture, Start and then click on Stop after we have captured our packets. By default we get 3 windows, the first with all the packets, the second with a higher level view of one packet, three the individual bytes. If we click on a certain english header name in the second window we get the actual bytes in the third. The first line is the Ethernet header. The second line that contains the IP packet starts with 46 and not 45. This is because our header is now 24 bytes large and not 20 bytes. We set the length of the packet to 100 bytes but the system overrode us and made it 31 bytes or 1f instead. The ip packet ends with the source and destination addresses and then begins our IP options. Remember the IP header can be up to 60 bytes large. Normally these extra 40 bytes are used to let routers place their IP addresses to act as a path used by the packet. The function insert_ip_options first takes an ip packet just created ip, the code of the ip option that is specified in the rfc, This is a one byte value, we use a non standard value of e4. Then we have the length of the data following 2 bytes and the actual bytes. The function raw_string is always used whenever we want our packet to contain specific bytes. This gives us a new packet ipo whose size is now 24 bytes. We then add a string to this ip packet which gets concatenated to the end. This adds another 7 bytes to our ip packet giving it a length of 31. This is how we can create our own packets to simulate say a buffer overflow.

 

ip = forge_ip_packet(ip_hl :5,ip_v :4,ip_tos :0,ip_len :20,ip_id :3,ip_off :0,ip_ttl :64,

 ip_p :IPPROTO_UDP,ip_src  :this_host());

udp = forge_udp_packet(ip:ip, uh_sport:1025,uh_dport:1026,uh_ulen :8);

send_packet(udp,pcap_active:FALSE);

 

00 00 e8 d7 5e 7c 00 00 e8 df a4 66 08 00

45 00 00 1c 00 03 00 00 40 11 ee c2 46 00 00 0a 46 00 00 02

04 01 04 02 00 08 6b cf

 

00 00 e8 df a4 66 00 00 e8 d7 5e 7c 08 00

45 00 00 38 0e 94 00 00 80 01 a0 25 46 00 00 02 46 00 00 0a

03 03 89 22 00 00 00 00 45 00 00 1c 00 03 00 00 40 11 ee c2 46 00 00 0a 46 00 00 02 04 0104 02 00 08 6b cf