Mailing List Archive

Nessus 1.2.5 magic network read
Nessus plugins often run this kind of code:
blah = recv(socket: soc, length:4096);

The NASL recv() function calls the stream_read_connection() C function
which returns when:
- "length" bytes have been read
- the peer closed the connection (aka EOF)
- a timeout expired. By default, 20 s

If the peer does not close the connection, recv will return after
the timeout. And whatever the value is, it will always be too big
(slow) or too small (timeout on a slow network)
To have quick tests and avoid losing data, we have to find the right
value for plugins_read_timeout in nessusd.conf
That was the behaviour in version <= 1.2.4


In the "true life", especially telecommunications or real time
software, when you speak sme kind of protocol on whatever serial
medium (even some cases of broadcast), you read "messages" and you
have some "end of message" mark, whatever it is:
1. A token in the data flow says that you reached a message limit.
2. You read a well defined number of bytes, that you got from a header
or that you know from elsewhere (e.g., the messages have all the same
fixed size)
3. Hardware signal, or any other side channel
4. timeout (if your network delay cannot be longer than a known value,
whatver the situtation is, to avoid the "Three Miles Island syndrom")
5. etc. etc.

Our problem is that we want to have a generic read function.
We cannot code a function for every protocol: there are too many of
them.

For many clear text communication, we have the end of line as a
message separator. recv_line may reach a timeout too, but only if the
peer is really slow; in normal operation, this function returns as
soon as it got a full text line.

We might write a clean function for HTTP because it is so common --
but we have to beware of buggy web servers that would not respect the
protocol.
In many cases, we will get a EOF/EPIPE when the peer process
closes its connection. But this does not work for protocols like HTTPS
because the SSL connection is kept open (for performance reasons)

A clean solution would be that plugins do the real work as they
know the protocol. But we don't want our plugins or includes to become
as big as a novel from Dostoievsky

So we really need a "smart" read function, that will ease plugin
writing, speed up the tests and... work well in normal situations
(i.e. if the network is not overloaded)

Renaud tried this hack (in CVS, post 1.2.4): when reading starts,
loop while there is still data waiting on the socket and ends ASAP.
Unfortunately, the function sometimes returned too early (on Linux
2.4 at least), the buffer was truncated and some vulnerability missed.

I propose this:

1. Add a "min" parameter in recv()
If it is set, recv() will read at most "max" bytes, and will return
when the timeout occurs or when "min" bytes have been received

2. Implement a "smart" default for lazy people :-)
recv() starts reading and will time out after 20 s. However, if it
received at least one byte, it will retry reading with a 2 s timeout.
(my current code is rather brain damaged and use two 1 s timeout in
fact)

This is implemented in the CVS version. Feel free to play with this.
Keep in mind that the "min" parameter will be ignored by Nessus <=
1.2.4

We'd appreciate comments on this. If anybody has seen this problem
before and has a silver bullet, he is welcome.

--
mailto:arboi@bigfoot.com
GPG Public keys: http://michel.arboi.free.fr/pubkey.txt
http://michel.arboi.free.fr/ http://arboi.da.ru/
FAQNOPI de fr.comp.securite : http://faqnopi.da.ru/
Re: Nessus 1.2.5 magic network read [ In reply to ]
On Wed, 2002-08-21 at 12:30, Michel Arboi wrote:
> I propose this:
> 1. Add a "min" parameter in recv()
> 2. Implement a "smart" default for lazy people :-)
> recv() starts reading and will time out after 20 s. However, if it
> received at least one byte, it will retry reading with a 2 s timeout.

This could cause problems with, e.g., web servers or other protocols
that return a header or banner and then pause for several seconds before
sending anything else. This is most typical of servers that perform a
reverse-DNS lookup of the client IP (not usually a problem within an
intranet, but you never know).

So might I suggest this amendment:
Make the "smart" recv() read a byte with an initial 20s timeout; once
received, continue reading bytes in non-blocking mode, breaking out of
the loop when nothing is read (or "max" is reached), so as to queue up
as much data as was received in the received packet. Once out of this
inner loop, compare the length against "max" and return if "max" is
reached. If "max" is not reached but "min" is, then drop the timeout to
2s and continue. Add a strncmp() into the inner loop (bonus points:
make it PCRE instead) so that the NASL author can specify an
end-of-{line|code} sequence of interest.

Kris
Re: Nessus 1.2.5 magic network read [ In reply to ]
"Kristofer T. Karas" <ktk@enterprise.bidmc.harvard.edu> writes:

> Add a strncmp() into the inner loop (bonus points: make it PCRE
> instead) so that the NASL author can specify an end-of-{line|code}
> sequence of interest.

We thought about this. This is probably the way to go. I am afraid
that strings or simple Unix-like patterns (with * and ? wildcards) are
not enough and that we will have to implement regex.

An expensive way to do this would be, e.g. in NASL:
-----------------------------------------------------------------------------
function read_until_regex(socket, regex, icase, timeout)
{
__r = "";

__i=0;
while (__i < timeout)
{
__c = recv(socket: socket, length: 1, timeout: 1);
# Here, we could try to read more bytes at a time...
if (strlen(__c) >= 1)
{
__r=__r+__c;
if (ereg(string: __r, pattern: regex, icase: icase)) return (__r);
}
else
{
__i=__i+1;
}
}
return(__r); # or maybe return(0)?
}
-----------------------------------------------------------------------------

But we compile the regular expression at each byte, which is ugly.

A C function could compile the RE once, but will try to match it at
each byte; I think that there is a more efficient implementation.
Re: Nessus 1.2.5 magic network read [ In reply to ]
On 21 Aug 2002, Michel Arboi wrote:

> In the "true life", especially telecommunications or real time
> software, when you speak sme kind of protocol on whatever serial
> medium (even some cases of broadcast), you read "messages" and you
> have some "end of message" mark, whatever it is:
> 1. A token in the data flow says that you reached a message limit.
> 2. You read a well defined number of bytes, that you got from a header
> or that you know from elsewhere (e.g., the messages have all the same
> fixed size)
> 3. Hardware signal, or any other side channel
> 4. timeout (if your network delay cannot be longer than a known value,
> whatver the situtation is, to avoid the "Three Miles Island syndrom")
> 5. etc. etc.

I think 90+ % of real protocols are covered by (1) and (2).

> A clean solution would be that plugins do the real work as they
> know the protocol. But we don't want our plugins or includes to become
> as big as a novel from Dostoievsky

Hmm...the code needs to analyze to contents of received data anyway.

A crazy idea: recv() would return (almost) immediately and the result
would be a magical string: when you ask for data behind its current end,
it would (try to) received more data from the network. For instance, to
read a line, you would do something like:

line = recv(...);
for (i = 0; line[i] != '\n'; ++i) ;

Of course, there are some problems with this approach: you cannot call
strlen() on a magical string (and get any meaningful result), it is not
clear when a variable should stop being magical, and the most serious one:
it is difficult to deal with exceptional states (e.g. no newline in the
previous example).

Nevertheless, if you looking for a silver bullet, this might be one.

> Renaud tried this hack (in CVS, post 1.2.4): when reading starts,
> loop while there is still data waiting on the socket and ends ASAP.
> Unfortunately, the function sometimes returned too early (on Linux
> 2.4 at least), the buffer was truncated and some vulnerability missed.

A single lost packet would make it return "too early" on any platform.


--Pavel Kankovsky aka Peak
"Welcome to the Czech Republic. Bring your own lifeboats."
Re: Nessus 1.2.5 magic network read [ In reply to ]
"Pavel Kankovsky" <peak@argo.troja.mff.cuni.cz> writes:

> > 1. A token in the data flow says that you reached a message limit.
> > 2. You read a well defined number of bytes, that you got from a header
> > or that you know from elsewhere (e.g., the messages have all the same
> > fixed size)

> I think 90+ % of real protocols are covered by (1) and (2).

Yes, so do I.

> > A clean solution would be that plugins do the real work as they
> > know the protocol. But we don't want our plugins or includes to become
> > as big as a novel from Dostoievsky

> Hmm...the code needs to analyze to contents of received data anyway.

There is a difference between the current situation "wait for X bytes
or until Y second, then parse the data, and exit if it doesn't match
anything known" and a system that would exit as soon as the data
CANNOT match the protocol.

However, I suspect that there are not so many interesting protocols:
HTTP, SMB/NetBios, and a few line oriented protocols (SMTP, NNTP,
FTP...). We can afford to wait 15 seconds on a couple of plugins. We
just need to "optimize" the most common protocols.
The best way to do it is to look at the slowest plugins. Any comments?

> A crazy idea: recv() would return (almost) immediately and the result
> would be a magical string: when you ask for data behind its current end,
> it would (try to) received more data from the network.

A kind of stream? This looks like the diamond operator in Perl, but I
am not sure it would be very useful.

--
mailto:arboi@bigfoot.com
GPG Public keys: http://michel.arboi.free.fr/pubkey.txt
http://michel.arboi.free.fr/ http://arboi.da.ru/
FAQNOPI de fr.comp.securite : http://faqnopi.da.ru/
Re: Nessus 1.2.5 magic network read [ In reply to ]
On 12 Nov 2002, Michel Arboi wrote:

> > > A clean solution would be that plugins do the real work as they
> > > know the protocol. But we don't want our plugins or includes to become
> > > as big as a novel from Dostoievsky
>
> > Hmm...the code needs to analyze to contents of received data anyway.
>
> There is a difference between the current situation "wait for X bytes
> or until Y second, then parse the data, and exit if it doesn't match
> anything known" and a system that would exit as soon as the data
> CANNOT match the protocol.

Yes, there is a difference. This is why I proposed the magical strings
(see below) making it possible analyze incoming data without doing one big
and slow read (one extreme) or cluttering the code with numerous explicit
small reads (another extreme).

> However, I suspect that there are not so many interesting protocols:
> HTTP, SMB/NetBios, and a few line oriented protocols (SMTP, NNTP,
> FTP...). We can afford to wait 15 seconds on a couple of plugins. We
> just need to "optimize" the most common protocols.
> The best way to do it is to look at the slowest plugins. Any comments?

Well, as far as I can tell, the slowest plugin is check_ports.nasl (no
protocol handling at all) followed by other plugins testing all open ports
like generic_flood.nasl (? not sure about the name). :P

> > A crazy idea: recv() would return (almost) immediately and the result
> > would be a magical string: when you ask for data behind its current end,
> > it would (try to) received more data from the network.
>
> A kind of stream? This looks like the diamond operator in Perl, but I
> am not sure it would be very useful.

No. Let me show an example:

magic_read(socket:s, variable:magic_string);

length = ord(magic_string[0]);
if (length == 0 || length > 16) exit(0);
string = substr(magic_string, 1, length);

magic_read() would tie magic_string to the socket and return
immediately. magic_string[0] as well as every other reference to
magic_string[i] (including implicit references in substr()) would
check whether magic_string is at least i+1 characters long: if yes,
it would return the i-th character, if no, it would try reading more
data from the network.

Indeed, the idea is crazy but something similar might work.

--Pavel Kankovsky aka Peak [ Boycott Microsoft--http://www.vcnet.com/bms ]
"Resistance is futile. Open your source code and prepare for assimilation."
Re: Nessus 1.2.5 magic network read [ In reply to ]
"Pavel Kankovsky" <peak@argo.troja.mff.cuni.cz> writes:

> magic_read(socket:s, variable:magic_string);
> length = ord(magic_string[0]);
> if (length == 0 || length > 16) exit(0);
> string = substr(magic_string, 1, length);

Currently, it can be written like this:

l = recv(socket: s, length: 1);
length = ord(l);
if (length == 0 || length > 16) exit(0);
string = recv(socket: s, length: length);

Not longer, and not more complicated.