mirror of
https://github.com/zeek/zeek.git
synced 2025-10-14 12:38:20 +00:00
Merge remote-tracking branch 'origin/topic/jsiwek/gridftp'
* origin/topic/jsiwek/gridftp: Add memory leak unit test for GridFTP. Enable GridFTP detection by default. Track/log SSL client certs. Add analyzer for GSI mechanism of GSSAPI FTP AUTH method. Add an example of a GridFTP data channel detection script.
This commit is contained in:
commit
5e12a53ae5
27 changed files with 583 additions and 68 deletions
177
src/FTP.cc
177
src/FTP.cc
|
@ -8,6 +8,8 @@
|
|||
#include "FTP.h"
|
||||
#include "NVT.h"
|
||||
#include "Event.h"
|
||||
#include "SSL.h"
|
||||
#include "Base64.h"
|
||||
|
||||
FTP_Analyzer::FTP_Analyzer(Connection* conn)
|
||||
: TCP_ApplicationAnalyzer(AnalyzerTag::FTP, conn)
|
||||
|
@ -44,6 +46,14 @@ void FTP_Analyzer::Done()
|
|||
Weird("partial_ftp_request");
|
||||
}
|
||||
|
||||
static uint32 get_reply_code(int len, const char* line)
|
||||
{
|
||||
if ( len >= 3 && isdigit(line[0]) && isdigit(line[1]) && isdigit(line[2]) )
|
||||
return (line[0] - '0') * 100 + (line[1] - '0') * 10 + (line[2] - '0');
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
void FTP_Analyzer::DeliverStream(int length, const u_char* data, bool orig)
|
||||
{
|
||||
TCP_ApplicationAnalyzer::DeliverStream(length, data, orig);
|
||||
|
@ -93,16 +103,7 @@ void FTP_Analyzer::DeliverStream(int length, const u_char* data, bool orig)
|
|||
}
|
||||
else
|
||||
{
|
||||
uint32 reply_code;
|
||||
if ( length >= 3 &&
|
||||
isdigit(line[0]) && isdigit(line[1]) && isdigit(line[2]) )
|
||||
{
|
||||
reply_code = (line[0] - '0') * 100 +
|
||||
(line[1] - '0') * 10 +
|
||||
(line[2] - '0');
|
||||
}
|
||||
else
|
||||
reply_code = 0;
|
||||
uint32 reply_code = get_reply_code(length, line);
|
||||
|
||||
int cont_resp;
|
||||
|
||||
|
@ -143,19 +144,22 @@ void FTP_Analyzer::DeliverStream(int length, const u_char* data, bool orig)
|
|||
else
|
||||
line = end_of_line;
|
||||
|
||||
if ( auth_requested.size() > 0 &&
|
||||
(reply_code == 234 || reply_code == 335) )
|
||||
// Server accepted AUTH requested,
|
||||
// which means that very likely we
|
||||
// won't be able to parse the rest
|
||||
// of the session, and thus we stop
|
||||
// here.
|
||||
SetSkip(true);
|
||||
|
||||
cont_resp = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if ( reply_code == 334 && auth_requested.size() > 0 &&
|
||||
auth_requested == "GSSAPI" )
|
||||
{
|
||||
// Server wants to proceed with an ADAT exchange and we
|
||||
// know how to analyze the GSI mechanism, so attach analyzer
|
||||
// to look for that.
|
||||
SSL_Analyzer* ssl = new SSL_Analyzer(Conn());
|
||||
ssl->AddSupportAnalyzer(new FTP_ADAT_Analyzer(Conn(), true));
|
||||
ssl->AddSupportAnalyzer(new FTP_ADAT_Analyzer(Conn(), false));
|
||||
AddChildAnalyzer(ssl);
|
||||
}
|
||||
|
||||
vl->append(new Val(reply_code, TYPE_COUNT));
|
||||
vl->append(new StringVal(end_of_line - line, line));
|
||||
vl->append(new Val(cont_resp, TYPE_BOOL));
|
||||
|
@ -164,5 +168,140 @@ void FTP_Analyzer::DeliverStream(int length, const u_char* data, bool orig)
|
|||
}
|
||||
|
||||
ConnectionEvent(f, vl);
|
||||
|
||||
ForwardStream(length, data, orig);
|
||||
}
|
||||
|
||||
void FTP_ADAT_Analyzer::DeliverStream(int len, const u_char* data, bool orig)
|
||||
{
|
||||
// Don't know how to parse anything but the ADAT exchanges of GSI GSSAPI,
|
||||
// which is basically just TLS/SSL.
|
||||
if ( ! Parent()->GetTag() == AnalyzerTag::SSL )
|
||||
{
|
||||
Parent()->Remove();
|
||||
return;
|
||||
}
|
||||
|
||||
bool done = false;
|
||||
const char* line = (const char*) data;
|
||||
const char* end_of_line = line + len;
|
||||
|
||||
BroString* decoded_adat = 0;
|
||||
|
||||
if ( orig )
|
||||
{
|
||||
int cmd_len;
|
||||
const char* cmd;
|
||||
line = skip_whitespace(line, end_of_line);
|
||||
get_word(len, line, cmd_len, cmd);
|
||||
|
||||
if ( strncmp(cmd, "ADAT", cmd_len) == 0 )
|
||||
{
|
||||
line = skip_whitespace(line + cmd_len, end_of_line);
|
||||
StringVal encoded(end_of_line - line, line);
|
||||
decoded_adat = decode_base64(encoded.AsString());
|
||||
|
||||
if ( first_token )
|
||||
{
|
||||
// RFC 2743 section 3.1 specifies a framing format for tokens
|
||||
// that includes an identifier for the mechanism type. The
|
||||
// framing is supposed to be required for the initial context
|
||||
// token, but GSI doesn't do that and starts right in on a
|
||||
// TLS/SSL handshake, so look for that to identify it.
|
||||
const u_char* msg = decoded_adat->Bytes();
|
||||
int msg_len = decoded_adat->Len();
|
||||
|
||||
// Just check that it looks like a viable TLS/SSL handshake
|
||||
// record from the first byte (content type of 0x16) and
|
||||
// that the fourth and fifth bytes indicating the length of
|
||||
// the record match the length of the decoded data.
|
||||
if ( msg_len < 5 || msg[0] != 0x16 ||
|
||||
msg_len - 5 != ntohs(*((uint16*)(msg + 3))) )
|
||||
{
|
||||
// Doesn't look like TLS/SSL, so done analyzing.
|
||||
done = true;
|
||||
delete decoded_adat;
|
||||
decoded_adat = 0;
|
||||
}
|
||||
}
|
||||
|
||||
first_token = false;
|
||||
}
|
||||
|
||||
else if ( strncmp(cmd, "AUTH", cmd_len) == 0 )
|
||||
// Security state will be reset by a reissued AUTH.
|
||||
done = true;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
uint32 reply_code = get_reply_code(len, line);
|
||||
|
||||
switch ( reply_code ) {
|
||||
case 232:
|
||||
case 234:
|
||||
// Indicates security data exchange is complete, but nothing
|
||||
// more to decode in replies.
|
||||
done = true;
|
||||
break;
|
||||
|
||||
case 235:
|
||||
// Security data exchange complete, but may have more to decode
|
||||
// in the reply (same format at 334 and 335).
|
||||
done = true;
|
||||
|
||||
// Fall-through.
|
||||
|
||||
case 334:
|
||||
case 335:
|
||||
// Security data exchange still in progress, and there could be data
|
||||
// to decode in the reply.
|
||||
line += 3;
|
||||
if ( len > 3 && line[0] == '-' )
|
||||
line++;
|
||||
|
||||
line = skip_whitespace(line, end_of_line);
|
||||
|
||||
if ( end_of_line - line >= 5 && strncmp(line, "ADAT=", 5) == 0 )
|
||||
{
|
||||
line += 5;
|
||||
StringVal encoded(end_of_line - line, line);
|
||||
decoded_adat = decode_base64(encoded.AsString());
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 421:
|
||||
case 431:
|
||||
case 500:
|
||||
case 501:
|
||||
case 503:
|
||||
case 535:
|
||||
// Server isn't going to accept named security mechanism.
|
||||
// Client has to restart back at the AUTH.
|
||||
done = true;
|
||||
break;
|
||||
|
||||
case 631:
|
||||
case 632:
|
||||
case 633:
|
||||
// If the server is sending protected replies, the security
|
||||
// data exchange must have already succeeded. It does have
|
||||
// encoded data in the reply, but 632 and 633 are also encrypted.
|
||||
done = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( decoded_adat )
|
||||
{
|
||||
ForwardStream(decoded_adat->Len(), decoded_adat->Bytes(), orig);
|
||||
delete decoded_adat;
|
||||
}
|
||||
|
||||
if ( done )
|
||||
Parent()->Remove();
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue