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.
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 Microsofts 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 Xs. 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 Xs 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.
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.
|
|
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.
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