2018-02-12 15:42:45 +00:00
// Package mysql provides the mysql implementation of the zgrab2.Module.
// Grabs the HandshakePacket (or ERRPacket) that the server sends
// immediately upon connecting, and then if applicable negotiate an SSL
// connection.
package mysql
import (
"reflect"
log "github.com/sirupsen/logrus"
"github.com/zmap/zgrab2"
"github.com/zmap/zgrab2/lib/mysql"
)
// ScanResults contains detailed information about the scan.
type ScanResults struct {
// ProtocolVersion is the 8-bit unsigned integer representing the
// server's protocol version sent in the initial HandshakePacket from
// the server.
// This has been 10 for all MySQL versionssince 3.2.2 (from 1998).
ProtocolVersion byte ` json:"protocol_version" `
// ServerVersion is a null-terminated string giving the specific
// server version in the initial HandshakePacket. Often of the format
// x.y.z, but not always.
ServerVersion string ` json:"server_version" `
// ConnectionID is the server's internal identifier for this client's
// connection, sent in the initial HandshakePacket.
2018-04-03 21:15:20 +00:00
ConnectionID uint32 ` json:"connection_id,omitempty" zgrab:"debug" `
2018-02-12 15:42:45 +00:00
// AuthPluginData is optional plugin-specific data, whose meaning
// depends on the value of AuthPluginName. Returned in the initial
// HandshakePacket.
2018-04-03 21:15:20 +00:00
AuthPluginData [ ] byte ` json:"auth_plugin_data,omitempty" zgrab:"debug" `
2018-02-12 15:42:45 +00:00
// CharacterSet is the identifier for the character set the server is
// using. Returned in the initial HandshakePacket.
2018-04-03 21:15:20 +00:00
CharacterSet byte ` json:"character_set,omitempty" zgrab:"debug" `
2018-02-12 15:42:45 +00:00
// StatusFlags is the set of status flags the server returned in the
// initial HandshakePacket. Each true entry in the map corresponds to
// a bit set to 1 in the flags, where the keys correspond to the
// #defines in the MySQL docs.
StatusFlags map [ string ] bool ` json:"status_flags" `
// CapabilityFlags is the set of capability flags the server returned
// initial HandshakePacket. Each true entry in the map corresponds to
// a bit set to 1 in the flags, where the keys correspond to the
// #defines in the MySQL docs.
CapabilityFlags map [ string ] bool ` json:"capability_flags" `
// AuthPluginName is the name of the authentication plugin, returned
// in the initial HandshakePacket.
AuthPluginName string ` json:"auth_plugin_name,omitempty" zgrab:"debug" `
// ErrorCode is only set if there is an error returned by the server,
// for example if the scanner is not on the allowed hosts list.
ErrorCode * int ` json:"error_code,omitempty" `
// ErrorMessage is an optional string describing the error. Only set
// if there is an error.
ErrorMessage string ` json:"error_message,omitempty" `
// RawPackets contains the base64 encoding of all packets sent and
// received during the scan.
RawPackets [ ] string ` json:"raw_packets,omitempty" `
// TLSLog contains the usual shared TLS logs.
TLSLog * zgrab2 . TLSLog ` json:"tls,omitempty" `
}
// Convert the ConnectionLog into the output format.
func readResultsFromConnectionLog ( connectionLog * mysql . ConnectionLog ) * ScanResults {
ret := ScanResults { }
if connectionLog == nil {
return nil
}
// If we received neither a Handshake nor an Error message, then no
// MySQL service is detected.
if connectionLog . Handshake == nil && connectionLog . Error == nil {
return nil
}
if connectionLog . Handshake != nil {
ret . RawPackets = append ( ret . RawPackets , connectionLog . Handshake . Raw )
switch handshake := connectionLog . Handshake . Parsed . ( type ) {
case * mysql . HandshakePacket :
ret . ProtocolVersion = handshake . ProtocolVersion
ret . ServerVersion = handshake . ServerVersion
ret . ConnectionID = handshake . ConnectionID
len1 := len ( handshake . AuthPluginData1 )
ret . AuthPluginData = make ( [ ] byte , len1 + len ( handshake . AuthPluginData2 ) )
copy ( ret . AuthPluginData [ 0 : len1 ] , handshake . AuthPluginData1 )
copy ( ret . AuthPluginData [ len1 : ] , handshake . AuthPluginData2 )
ret . CharacterSet = handshake . CharacterSet
ret . StatusFlags = mysql . GetServerStatusFlags ( handshake . StatusFlags )
ret . CapabilityFlags = mysql . GetClientCapabilityFlags ( handshake . CapabilityFlags )
ret . AuthPluginName = handshake . AuthPluginName
default :
log . Fatalf ( "Unreachable code -- ConnectionLog.Handshake was set to a non-handshake packet: %v / %v" , connectionLog . Handshake . Parsed , reflect . TypeOf ( connectionLog . Handshake . Parsed ) )
}
}
if connectionLog . Error != nil {
ret . RawPackets = append ( ret . RawPackets , connectionLog . Error . Raw )
switch err := connectionLog . Error . Parsed . ( type ) {
case * mysql . ERRPacket :
temp := int ( err . ErrorCode )
ret . ErrorCode = & temp
ret . ErrorMessage = err . ErrorMessage
default :
temp := - 1
ret . ErrorCode = & temp
ret . ErrorMessage = "Unexpected packet type"
}
}
if connectionLog . SSLRequest != nil {
ret . RawPackets = append ( ret . RawPackets , connectionLog . SSLRequest . Raw )
}
return & ret
}
// Flags give the command-line flags for the MySQL module.
type Flags struct {
zgrab2 . BaseFlags
zgrab2 . TLSFlags
Verbose bool ` long:"verbose" description:"More verbose logging, include debug fields in the scan results" `
}
// Module is the implementation of the zgrab2.Module interface.
type Module struct {
}
// Scanner is the implementation of the zgrab2.Scanner interface.
type Scanner struct {
config * Flags
}
// RegisterModule is called by modules/mysql.go to register the scanner.
func RegisterModule ( ) {
var module Module
_ , err := zgrab2 . AddCommand ( "mysql" , "MySQL" , "Grab a MySQL handshake" , 3306 , & module )
if err != nil {
log . Fatal ( err )
}
}
// NewFlags returns a new default flags object.
func ( m * Module ) NewFlags ( ) interface { } {
return new ( Flags )
}
// NewScanner returns a new Scanner object.
func ( m * Module ) NewScanner ( ) zgrab2 . Scanner {
return new ( Scanner )
}
// Validate validates the flags and returns nil on success.
func ( f * Flags ) Validate ( args [ ] string ) error {
return nil
}
// Help returns the module's help string.
func ( f * Flags ) Help ( ) string {
return ""
}
// Init initializes the Scanner with the command-line flags.
func ( s * Scanner ) Init ( flags zgrab2 . ScanFlags ) error {
f , _ := flags . ( * Flags )
s . config = f
return nil
}
// InitPerSender does nothing in this module.
func ( s * Scanner ) InitPerSender ( senderID int ) error {
return nil
}
2018-03-12 17:36:11 +00:00
// Protocol returns the protocol identifer for the scanner.
func ( s * Scanner ) Protocol ( ) string {
return "mysql"
}
2018-02-12 15:42:45 +00:00
// GetName returns the name from the command line flags.
func ( s * Scanner ) GetName ( ) string {
return s . config . Name
}
// GetPort returns the port that is being scanned.
func ( s * Scanner ) GetPort ( ) uint {
return s . config . Port
}
// Scan probles the target for a MySQL server.
// 1. Connects and waits to receive the handshake packet.
// 2. If the server supports SSL, send an SSLRequest packet, then
// perform the standard TLS actions.
// 3. Process and return the results.
func ( s * Scanner ) Scan ( t zgrab2 . ScanTarget ) ( status zgrab2 . ScanStatus , result interface { } , thrown error ) {
var tlsConn * zgrab2 . TLSConnection
sql := mysql . NewConnection ( & mysql . Config { } )
defer func ( ) {
recovered := recover ( )
if recovered != nil {
thrown = recovered . ( error )
status = zgrab2 . TryGetScanStatus ( thrown )
// TODO FIXME: do more to distinguish errors
}
result = readResultsFromConnectionLog ( & sql . ConnectionLog )
if tlsConn != nil {
result . ( * ScanResults ) . TLSLog = tlsConn . GetLog ( )
}
} ( )
defer sql . Disconnect ( )
var err error
conn , err := t . Open ( & s . config . BaseFlags )
if err != nil {
panic ( err )
}
if err = sql . Connect ( conn ) ; err != nil {
panic ( err )
}
if sql . SupportsTLS ( ) {
if err = sql . NegotiateTLS ( ) ; err != nil {
panic ( err )
}
if tlsConn , err = s . config . TLSFlags . GetTLSConnection ( sql . Connection ) ; err != nil {
panic ( err )
}
if err = tlsConn . Handshake ( ) ; err != nil {
panic ( err )
}
// Replace sql.Connection to allow hypothetical future calls to go over the secure connection
sql . Connection = tlsConn
}
// If we made it this far, the scan was a success. The result will be grabbed in the defer block above.
return zgrab2 . SCAN_SUCCESS , nil , nil
}