Skip to content

Commit 9c5a568

Browse files
authored
Merge pull request #275 from vulncheck-oss/fgfm-protocol
fgfm protocol
2 parents bf9d984 + 9bbd6fb commit 9c5a568

File tree

1 file changed

+86
-0
lines changed

1 file changed

+86
-0
lines changed

protocol/fortinet/fgfm.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Package fortinet is a very basic (and incomplete) implementation of Fortinet FGFM protocol
2+
package fortinet
3+
4+
import (
5+
"bytes"
6+
"crypto/tls"
7+
"encoding/binary"
8+
"net"
9+
10+
"github.com/vulncheck-oss/go-exploit/output"
11+
"github.com/vulncheck-oss/go-exploit/protocol"
12+
)
13+
14+
// Creates and sends a Fortinet FGFM message to a FortiManager.
15+
// The format is closed source, but research by BF, Watchtowr, and Rapid7 have helped uncover the basic message header structure:
16+
// [4 bytes of magic header]
17+
// [4 bytes of total request length]
18+
// [n bytes request body data].
19+
func SendFGFMMessage(conn net.Conn, payload string) bool {
20+
message := make([]byte, 0)
21+
// add magic header
22+
message = append(message, []byte("\x36\xe0\x11\x00")...)
23+
// build the total length field
24+
totalLengthField := make([]byte, 4)
25+
length := len(payload) + 8
26+
binary.BigEndian.PutUint32(totalLengthField, uint32(length))
27+
message = append(message, totalLengthField...)
28+
// add payload
29+
message = append(message, []byte(payload)...)
30+
31+
return protocol.TCPWrite(conn, message)
32+
}
33+
34+
// Reads response from a FortiManager.
35+
func ReadFGFMMessage(conn net.Conn) ([]byte, bool) {
36+
magic, ok := protocol.TCPReadAmount(conn, 4)
37+
if !ok || !bytes.Equal(magic, []byte("\x36\xe0\x11\x00")) {
38+
output.PrintFrameworkError("Failed to read server response with expected header")
39+
40+
return nil, false
41+
}
42+
size, ok := protocol.TCPReadAmount(conn, 4)
43+
if !ok {
44+
output.PrintFrameworkError("Failed to read server response length")
45+
46+
return nil, false
47+
}
48+
49+
readSize := int(binary.BigEndian.Uint32(size))
50+
data, ok := protocol.TCPReadAmount(conn, readSize-8)
51+
if !ok {
52+
output.PrintFrameworkError("Failed to read server response data")
53+
54+
return nil, false
55+
}
56+
57+
return data, true
58+
}
59+
60+
// Fortimanager requires a connecting Fortigate instance to have a cert.
61+
// SSL is optional here so you have the choice to sign the traffic from the go-exploit framework,
62+
// or so you can send the exploit network traffic through a proxy like socat to sign the traffic for you.
63+
// Benefits to this include being able to generate pcaps of the unencrypted traffic
64+
// between go-exploit and your proxy.
65+
// See CVE-2024-47575 for additional information.
66+
func Connect(host string, port int, ssl bool, certFile string, keyFile string) (net.Conn, bool) {
67+
if ssl {
68+
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
69+
if err != nil {
70+
output.PrintFrameworkError("Failed to load x509 Key Pair")
71+
output.PrintfFrameworkDebug("Failed to load x509 Key Pair with error: %s", err)
72+
73+
return nil, false
74+
}
75+
cfg := &tls.Config{Certificates: []tls.Certificate{cert}, InsecureSkipVerify: true}
76+
77+
conn, ok := protocol.TCPConnect(host, port)
78+
if !ok {
79+
return nil, false
80+
}
81+
82+
return tls.Client(conn, cfg), true
83+
}
84+
85+
return protocol.TCPConnect(host, port)
86+
}

0 commit comments

Comments
 (0)