mirror of
https://github.com/zeek/zeek.git
synced 2025-10-14 12:38:20 +00:00
Add analyzer for GSI mechanism of GSSAPI FTP AUTH method.
GSI authentication involves an encoded TLS/SSL handshake over the FTP control session. Decoding the exchanged tokens and passing them to an SSL analyzer instance allows use of all the familiar script-layer events in inspecting the handshake (e.g. client/server certificats are available). For FTP sessions that attempt GSI authentication, the service field of the connection record will have both "ftp" and "ssl". One additional change is an FTP server's acceptance of an AUTH request no longer causes analysis of the connection to cease (because further analysis likely wasn't possible). This decision can be made more dynamically at the script-layer (plus there's now the fact that further analysis can be done at least on the GSSAPI AUTH method).
This commit is contained in:
parent
2ac6fab5fc
commit
49b8c7e390
10 changed files with 188 additions and 25 deletions
173
src/FTP.cc
173
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,136 @@ 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 = new StringVal(end_of_line - line, line);
|
||||
decoded_adat = decode_base64(encoded->AsString());
|
||||
delete encoded;
|
||||
|
||||
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;
|
||||
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 = new StringVal(end_of_line - line, line);
|
||||
decoded_adat = decode_base64(encoded->AsString());
|
||||
delete encoded;
|
||||
}
|
||||
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