Cybersecurity-Projects/PROJECTS/advanced/honeypot-network/internal/mysqld/handler.go

364 lines
6.8 KiB
Go

/*
©AngelaMos | 2026
handler.go
MySQL wire protocol handler for the honeypot
Implements the MySQL client-server protocol at the packet level:
greeting handshake, authentication parsing, and query response
generation. Builds valid MySQL packets without external dependencies
to give full control over what gets logged and how the honeypot
responds to automated exploitation tools.
*/
package mysqld
import (
"encoding/binary"
"fmt"
"io"
"net"
"strings"
)
const (
comQuit byte = 0x01
comInitDB byte = 0x02
comQuery byte = 0x03
comFieldList byte = 0x04
comPing byte = 0x0e
)
const (
clientProtocol41 uint32 = 1 << 9
clientSecureConn uint32 = 1 << 15
clientPluginAuth uint32 = 1 << 19
)
const statusAutocommit uint16 = 0x0002
func writePacket(
conn net.Conn, seq byte, data []byte,
) error {
header := make([]byte, 4)
header[0] = byte(len(data))
header[1] = byte(len(data) >> 8)
header[2] = byte(len(data) >> 16)
header[3] = seq
_, err := conn.Write(append(header, data...))
return err
}
func readPacket(
conn net.Conn,
) (byte, []byte, error) {
header := make([]byte, 4)
if _, err := io.ReadFull(conn, header); err != nil {
return 0, nil, err
}
length := int(header[0]) |
int(header[1])<<8 |
int(header[2])<<16
seq := header[3]
if length == 0 {
return seq, nil, nil
}
if length > 1<<20 {
return seq, nil, fmt.Errorf(
"packet too large: %d", length,
)
}
payload := make([]byte, length)
if _, err := io.ReadFull(conn, payload); err != nil {
return seq, nil, err
}
return seq, payload, nil
}
func buildGreeting(
version string, connID uint32,
) []byte {
salt1 := []byte{
0x52, 0x42, 0x33, 0x76,
0x7a, 0x26, 0x47, 0x72,
}
salt2 := []byte{
0x2b, 0x5a, 0x28, 0x40, 0x22, 0x37,
0x71, 0x41, 0x2d, 0x64, 0x34, 0x62, 0x00,
}
pluginName := "mysql_native_password"
caps := clientProtocol41 |
clientSecureConn |
clientPluginAuth
var buf []byte
buf = append(buf, 10)
buf = append(buf, version...)
buf = append(buf, 0)
id := make([]byte, 4)
binary.LittleEndian.PutUint32(id, connID)
buf = append(buf, id...)
buf = append(buf, salt1...)
buf = append(buf, 0)
lo := make([]byte, 2)
binary.LittleEndian.PutUint16(
lo, uint16(caps&0xFFFF),
)
buf = append(buf, lo...)
buf = append(buf, 0x21)
st := make([]byte, 2)
binary.LittleEndian.PutUint16(st, statusAutocommit)
buf = append(buf, st...)
hi := make([]byte, 2)
binary.LittleEndian.PutUint16(
hi, uint16((caps>>16)&0xFFFF),
)
buf = append(buf, hi...)
buf = append(buf, 21)
buf = append(buf, make([]byte, 10)...)
buf = append(buf, salt2...)
buf = append(buf, pluginName...)
buf = append(buf, 0)
return buf
}
func parseAuthUsername(data []byte) string {
if len(data) < 32 {
return ""
}
rest := data[32:]
for i, b := range rest {
if b == 0 {
return string(rest[:i])
}
}
return string(rest)
}
func okPacket() []byte {
return []byte{
0x00,
0x00,
0x00,
0x02, 0x00,
0x00, 0x00,
}
}
func errPacket(
code uint16, state, msg string,
) []byte {
var buf []byte
buf = append(buf, 0xFF)
ec := make([]byte, 2)
binary.LittleEndian.PutUint16(ec, code)
buf = append(buf, ec...)
buf = append(buf, '#')
padded := state
if len(padded) > 5 {
padded = padded[:5]
}
for len(padded) < 5 {
padded += " "
}
buf = append(buf, padded...)
buf = append(buf, msg...)
return buf
}
func eofPacket() []byte {
return []byte{0xFE, 0x00, 0x00, 0x02, 0x00}
}
func lenEncInt(n int) []byte {
if n < 251 {
return []byte{byte(n)}
}
if n < 1<<16 {
b := make([]byte, 3)
b[0] = 0xFC
binary.LittleEndian.PutUint16(b[1:], uint16(n))
return b
}
b := make([]byte, 4)
b[0] = 0xFD
b[1] = byte(n)
b[2] = byte(n >> 8)
b[3] = byte(n >> 16)
return b
}
func lenEncString(s string) []byte {
return append(lenEncInt(len(s)), s...)
}
func columnDef(name string) []byte {
var buf []byte
buf = append(buf, lenEncString("def")...)
buf = append(buf, lenEncString("")...)
buf = append(buf, lenEncString("")...)
buf = append(buf, lenEncString("")...)
buf = append(buf, lenEncString(name)...)
buf = append(buf, lenEncString(name)...)
buf = append(buf, 0x0C)
buf = append(buf, 0x21, 0x00)
buf = append(buf, 0x00, 0x01, 0x00, 0x00)
buf = append(buf, 0xFD)
buf = append(buf, 0x01, 0x00)
buf = append(buf, 0x00)
buf = append(buf, 0x00, 0x00)
return buf
}
type queryResult struct {
columns []string
rows [][]string
}
func handleQuery(query string) *queryResult {
upper := strings.ToUpper(strings.TrimSpace(query))
switch {
case strings.HasPrefix(upper, "SELECT @@VERSION"):
return &queryResult{
columns: []string{"@@version_comment"},
rows: [][]string{{"(Ubuntu)"}},
}
case strings.HasPrefix(upper, "SELECT DATABASE"):
return &queryResult{
columns: []string{"database()"},
rows: [][]string{{"mysql"}},
}
case strings.HasPrefix(upper, "SELECT USER"):
return &queryResult{
columns: []string{"user()"},
rows: [][]string{{"root@localhost"}},
}
case upper == "SHOW DATABASES":
return &queryResult{
columns: []string{"Database"},
rows: [][]string{
{"information_schema"},
{"mysql"},
{"performance_schema"},
{"sys"},
},
}
case upper == "SHOW TABLES":
return &queryResult{
columns: []string{"Tables_in_mysql"},
rows: [][]string{
{"columns_priv"},
{"db"},
{"event"},
{"func"},
{"general_log"},
{"help_category"},
{"user"},
},
}
case strings.Contains(upper, "INFORMATION_SCHEMA"):
return &queryResult{
columns: []string{"TABLE_NAME"},
rows: [][]string{},
}
case strings.HasPrefix(upper, "SELECT @@DATADIR"):
return &queryResult{
columns: []string{"@@datadir"},
rows: [][]string{{"/var/lib/mysql/"}},
}
case strings.HasPrefix(upper, "SELECT @@HOSTNAME"):
return &queryResult{
columns: []string{"@@hostname"},
rows: [][]string{{"ubuntu-server"}},
}
case strings.HasPrefix(upper, "SHOW VARIABLES"):
return &queryResult{
columns: []string{
"Variable_name", "Value",
},
rows: [][]string{
{"version", "5.7.42-0ubuntu0.18.04.1"},
{"datadir", "/var/lib/mysql/"},
{"hostname", "ubuntu-server"},
{"port", "3306"},
},
}
}
return nil
}
func writeResultSet(
conn net.Conn, seq byte, res *queryResult,
) error {
seq++
if err := writePacket(
conn, seq, lenEncInt(len(res.columns)),
); err != nil {
return err
}
for _, col := range res.columns {
seq++
if err := writePacket(
conn, seq, columnDef(col),
); err != nil {
return err
}
}
seq++
if err := writePacket(
conn, seq, eofPacket(),
); err != nil {
return err
}
for _, row := range res.rows {
seq++
var rowBuf []byte
for _, val := range row {
rowBuf = append(
rowBuf, lenEncString(val)...,
)
}
if err := writePacket(
conn, seq, rowBuf,
); err != nil {
return err
}
}
seq++
return writePacket(conn, seq, eofPacket())
}