diff --git a/cmd/interactsh-client/main.go b/cmd/interactsh-client/main.go index 424b12ca..e25843af 100644 --- a/cmd/interactsh-client/main.go +++ b/cmd/interactsh-client/main.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "encoding/json" "fmt" "os" "os/signal" @@ -10,7 +11,6 @@ import ( "strings" "time" - jsoniter "github.com/json-iterator/go" asnmap "github.com/projectdiscovery/asnmap/libs" "github.com/projectdiscovery/goflags" "github.com/projectdiscovery/gologger" @@ -266,7 +266,7 @@ func main() { } } } else { - b, err := jsoniter.Marshal(interaction) + b, err := json.Marshal(interaction) if err != nil { gologger.Error().Msgf("Could not marshal json output: %s\n", err) } else { diff --git a/go.mod b/go.mod index 02122c1e..f6ab0419 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,6 @@ require ( github.com/docker/go-units v0.5.0 github.com/goburrow/cache v0.1.4 github.com/google/uuid v1.6.0 - github.com/json-iterator/go v1.1.12 github.com/libdns/libdns v1.1.1 github.com/mackerelio/go-osstat v0.2.6 github.com/miekg/dns v1.1.68 @@ -87,6 +86,7 @@ require ( github.com/jcmturner/goidentity/v6 v6.0.1 // indirect github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect github.com/jcmturner/rpc/v2 v2.0.3 // indirect + github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.18.2 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/klauspost/pgzip v1.2.6 // indirect diff --git a/pkg/client/client.go b/pkg/client/client.go index 8f0979c4..bea9caa5 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -10,7 +10,9 @@ import ( "crypto/sha256" "crypto/x509" "encoding/base64" + "encoding/json" "encoding/pem" + "errors" "fmt" "io" "net" @@ -22,10 +24,7 @@ import ( "sync/atomic" "time" - "errors" - "github.com/google/uuid" - jsoniter "github.com/json-iterator/go" asnmap "github.com/projectdiscovery/asnmap/libs" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/interactsh/pkg/options" @@ -270,7 +269,7 @@ func encodeRegistrationRequest(publicKey, secretkey, correlationID string) ([]by CorrelationID: correlationID, } - data, err := jsoniter.Marshal(register) + data, err := json.Marshal(register) if err != nil { return nil, errkit.Wrap(err, "could not marshal register request") } @@ -457,7 +456,7 @@ func (c *Client) getInteractions(callback InteractionCallback) error { return fmt.Errorf("could not poll interactions: %s", string(data)) } response := &server.PollResponse{} - if err := jsoniter.NewDecoder(resp.Body).Decode(response); err != nil { + if err := json.NewDecoder(resp.Body).Decode(response); err != nil { gologger.Error().Msgf("Could not decode interactions: %v\n", err) return err } @@ -470,7 +469,7 @@ func (c *Client) getInteractions(callback InteractionCallback) error { } plaintext = bytes.TrimRight(plaintext, " \t\r\n") interaction := &server.Interaction{} - if err := jsoniter.Unmarshal(plaintext, interaction); err != nil { + if err := json.Unmarshal(plaintext, interaction); err != nil { gologger.Error().Msgf("Could not unmarshal interaction data interaction: %v\n", err) continue } @@ -479,7 +478,7 @@ func (c *Client) getInteractions(callback InteractionCallback) error { for _, plaintext := range response.Extra { interaction := &server.Interaction{} - if err := jsoniter.UnmarshalFromString(plaintext, interaction); err != nil { + if err := json.Unmarshal([]byte(plaintext), interaction); err != nil { gologger.Error().Msgf("Could not unmarshal interaction data interaction: %v\n", err) continue } @@ -492,7 +491,7 @@ func (c *Client) getInteractions(callback InteractionCallback) error { continue } interaction := &server.Interaction{} - if err := jsoniter.UnmarshalFromString(data, interaction); err != nil { + if err := json.Unmarshal([]byte(data), interaction); err != nil { gologger.Error().Msgf("Could not unmarshal interaction data interaction: %v\n", err) continue } @@ -564,7 +563,7 @@ func (c *Client) Close() error { CorrelationID: c.correlationID, SecretKey: c.secretKey, } - data, err := jsoniter.Marshal(register) + data, err := json.Marshal(register) if err != nil { return errkit.Wrap(err, "could not marshal deregister request") } @@ -634,7 +633,7 @@ func (c *Client) performRegistration(serverURL string, payload []byte) error { return fmt.Errorf("could not register to server: %s", string(data)) } response := make(map[string]interface{}) - if err := jsoniter.NewDecoder(resp.Body).Decode(&response); err != nil { + if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { return errkit.Wrap(err, "could not register to server") } message, ok := response["message"] diff --git a/pkg/server/ftp_server.go b/pkg/server/ftp_server.go index e43f466f..79a68f59 100644 --- a/pkg/server/ftp_server.go +++ b/pkg/server/ftp_server.go @@ -9,7 +9,7 @@ import ( "sync/atomic" "time" - jsoniter "github.com/json-iterator/go" + "encoding/json" "github.com/projectdiscovery/gologger" ftpserver "goftp.io/server/v2" "goftp.io/server/v2/driver/file" @@ -130,7 +130,7 @@ func (h *FTPServer) recordInteraction(remoteAddress, data string) { RawRequest: data, Timestamp: time.Now(), } - dataBytes, err := jsoniter.Marshal(interaction) + dataBytes, err := json.Marshal(interaction) if err != nil { gologger.Warning().Msgf("Could not encode ftp interaction: %s\n", err) } else { diff --git a/pkg/server/http_server.go b/pkg/server/http_server.go index 1acc5b23..d51b71a8 100644 --- a/pkg/server/http_server.go +++ b/pkg/server/http_server.go @@ -3,6 +3,7 @@ package server import ( "crypto/tls" "encoding/base64" + "encoding/json" "fmt" "log" "net" @@ -16,7 +17,6 @@ import ( "sync/atomic" "time" - jsoniter "github.com/json-iterator/go" "github.com/projectdiscovery/gologger" stringsutil "github.com/projectdiscovery/utils/strings" ) @@ -157,7 +157,7 @@ func (h *HTTPServer) logger(handler http.Handler) http.HandlerFunc { RemoteAddress: host, Timestamp: time.Now(), } - data, err := jsoniter.Marshal(interaction) + data, err := json.Marshal(interaction) if err != nil { gologger.Warning().Msgf("Could not encode root tld http interaction: %s\n", err) } else { @@ -217,7 +217,7 @@ func (h *HTTPServer) handleInteraction(r *http.Request, uniqueID, fullID, reqStr RemoteAddress: hostPort, Timestamp: time.Now(), } - data, err := jsoniter.Marshal(interaction) + data, err := json.Marshal(interaction) if err != nil { gologger.Warning().Msgf("Could not encode http interaction: %s\n", err) } else { @@ -382,7 +382,7 @@ type RegisterRequest struct { // registerHandler is a handler for client register requests func (h *HTTPServer) registerHandler(w http.ResponseWriter, req *http.Request) { r := &RegisterRequest{} - if err := jsoniter.NewDecoder(req.Body).Decode(r); err != nil { + if err := json.NewDecoder(req.Body).Decode(r); err != nil { gologger.Warning().Msgf("Could not decode json body: %s\n", err) jsonError(w, fmt.Sprintf("could not decode json body: %s", err), http.StatusBadRequest) return @@ -410,7 +410,7 @@ type DeregisterRequest struct { // deregisterHandler is a handler for client deregister requests func (h *HTTPServer) deregisterHandler(w http.ResponseWriter, req *http.Request) { r := &DeregisterRequest{} - if err := jsoniter.NewDecoder(req.Body).Decode(r); err != nil { + if err := json.NewDecoder(req.Body).Decode(r); err != nil { gologger.Warning().Msgf("Could not decode json body: %s\n", err) jsonError(w, fmt.Sprintf("could not decode json body: %s", err), http.StatusBadRequest) return @@ -476,7 +476,7 @@ func (h *HTTPServer) pollHandler(w http.ResponseWriter, req *http.Request) { } response := &PollResponse{Data: data, AESKey: aesKey, TLDData: tlddata, Extra: extradata} - if err := jsoniter.NewEncoder(w).Encode(response); err != nil { + if err := json.NewEncoder(w).Encode(response); err != nil { gologger.Warning().Msgf("Could not encode interactions for %s: %s\n", ID, err) jsonError(w, fmt.Sprintf("could not encode interactions: %s", err), http.StatusBadRequest) return @@ -505,7 +505,7 @@ func jsonBody(w http.ResponseWriter, key, value string, code int) { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("X-Content-Type-Options", "nosniff") w.WriteHeader(code) - _ = jsoniter.NewEncoder(w).Encode(map[string]interface{}{key: value}) + _ = json.NewEncoder(w).Encode(map[string]interface{}{key: value}) } func jsonError(w http.ResponseWriter, err string, code int) { @@ -540,5 +540,5 @@ func (h *HTTPServer) metricsHandler(w http.ResponseWriter, req *http.Request) { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("X-Content-Type-Options", "nosniff") - _ = jsoniter.NewEncoder(w).Encode(interactMetrics) + _ = json.NewEncoder(w).Encode(interactMetrics) } diff --git a/pkg/server/http_server_test.go b/pkg/server/http_server_test.go index 5a21e61b..a93f3e96 100644 --- a/pkg/server/http_server_test.go +++ b/pkg/server/http_server_test.go @@ -7,6 +7,7 @@ import ( "crypto/tls" "crypto/x509" "encoding/base64" + "encoding/json" "encoding/pem" "io" "net/http" @@ -17,7 +18,6 @@ import ( "time" "github.com/google/uuid" - jsoniter "github.com/json-iterator/go" "github.com/projectdiscovery/interactsh/pkg/storage" "github.com/rs/xid" "github.com/stretchr/testify/require" @@ -117,7 +117,7 @@ func TestSessionTotalMetric(t *testing.T) { secretKey := uuid.New().String() // --- Register --- - regBody, err := jsoniter.Marshal(&RegisterRequest{ + regBody, err := json.Marshal(&RegisterRequest{ PublicKey: pubB64, SecretKey: secretKey, CorrelationID: correlationID, @@ -132,7 +132,7 @@ func TestSessionTotalMetric(t *testing.T) { require.Equal(t, int64(1), atomic.LoadInt64(&stats.SessionsTotal), "sessions_total should be 1 after register") // --- Deregister --- - deregBody, err := jsoniter.Marshal(&DeregisterRequest{ + deregBody, err := json.Marshal(&DeregisterRequest{ SecretKey: secretKey, CorrelationID: correlationID, }) diff --git a/pkg/server/ldap_server.go b/pkg/server/ldap_server.go index 8e3bfdb4..66b7e75b 100644 --- a/pkg/server/ldap_server.go +++ b/pkg/server/ldap_server.go @@ -7,7 +7,7 @@ import ( "sync/atomic" "time" - jsoniter "github.com/json-iterator/go" + "encoding/json" "github.com/projectdiscovery/gologger" ldap "github.com/projectdiscovery/ldapserver" stringsutil "github.com/projectdiscovery/utils/strings" @@ -144,7 +144,7 @@ func (ldapServer *LDAPServer) handleInteraction(uniqueID, fullID, reqString, hos RemoteAddress: host, Timestamp: time.Now(), } - data, err := jsoniter.Marshal(interaction) + data, err := json.Marshal(interaction) if err != nil { gologger.Warning().Msgf("Could not encode ldap interaction: %s\n", err) } else { @@ -410,7 +410,7 @@ func (ldapServer *LDAPServer) logInteraction(interaction Interaction) { // Correlation id doesn't apply here, we skip encryption interaction.Protocol = "ldap" interaction.Timestamp = time.Now() - data, err := jsoniter.Marshal(interaction) + data, err := json.Marshal(interaction) if err != nil { gologger.Warning().Msgf("Could not encode ldap interaction: %s\n", err) } else { diff --git a/pkg/server/ntlm_capture.go b/pkg/server/ntlm_capture.go index 0b675cda..f1f199bc 100644 --- a/pkg/server/ntlm_capture.go +++ b/pkg/server/ntlm_capture.go @@ -4,6 +4,7 @@ import ( "context" "encoding/binary" "encoding/hex" + "encoding/json" "errors" "fmt" "sync/atomic" @@ -12,7 +13,6 @@ import ( "github.com/Mzack9999/goimpacket/pkg/ntlm" "github.com/Mzack9999/goimpacket/pkg/relay" "github.com/Mzack9999/goimpacket/pkg/utf16le" - jsoniter "github.com/json-iterator/go" "github.com/projectdiscovery/gologger" ) @@ -80,7 +80,7 @@ func runNTLMCapture(ctx context.Context, srv relay.ProtocolServer, protocolName RemoteAddress: auth.SourceAddr, Timestamp: time.Now(), } - data, err := jsoniter.Marshal(interaction) + data, err := json.Marshal(interaction) if err != nil { gologger.Warning().Msgf("Could not encode %s interaction: %s\n", protocolName, err) } else { diff --git a/pkg/server/util.go b/pkg/server/util.go index c91259c6..f7313bb3 100644 --- a/pkg/server/util.go +++ b/pkg/server/util.go @@ -1,11 +1,11 @@ package server import ( + "encoding/json" "net" "strconv" "strings" - jsoniter "github.com/json-iterator/go" "github.com/projectdiscovery/gologger" ) @@ -61,7 +61,7 @@ func formatAddress(host string, port int) string { // Each protocol handler builds its own protocol-specific Interaction and delegates the marshal/log/store // step here so every match is stored independently (see issue #1362). func (options *Options) storeInteraction(interaction *Interaction, correlationID string) { - data, err := jsoniter.Marshal(interaction) + data, err := json.Marshal(interaction) if err != nil { gologger.Warning().Msgf("Could not encode %s interaction: %s\n", interaction.Protocol, err) return @@ -75,7 +75,7 @@ func (options *Options) storeInteraction(interaction *Interaction, correlationID // storeRootTLDInteraction marshals interaction and persists it under id via Storage.AddInteractionWithId. // Used when RootTLD is enabled and the request targets a configured parent domain directly. func (options *Options) storeRootTLDInteraction(interaction *Interaction, id string) { - data, err := jsoniter.Marshal(interaction) + data, err := json.Marshal(interaction) if err != nil { gologger.Warning().Msgf("Could not encode root tld %s interaction: %s\n", interaction.Protocol, err) return diff --git a/pkg/storage/roundtrip_test.go b/pkg/storage/roundtrip_test.go index a867bbe2..e3d23b33 100644 --- a/pkg/storage/roundtrip_test.go +++ b/pkg/storage/roundtrip_test.go @@ -14,7 +14,7 @@ import ( "testing" "time" - jsoniter "github.com/json-iterator/go" + "encoding/json" "github.com/google/uuid" "github.com/rs/xid" "github.com/stretchr/testify/require" @@ -118,7 +118,7 @@ func TestFullRoundTripInMemory(t *testing.T) { RemoteAddress: "10.0.0.1", Timestamp: time.Now(), } - data, err := jsoniter.Marshal(inter) + data, err := json.Marshal(inter) require.NoError(t, err, "encode interaction %d", i) err = mem.AddInteraction(correlationID, data) @@ -134,7 +134,7 @@ func TestFullRoundTripInMemory(t *testing.T) { for i, d := range data { plaintext := clientDecrypt(t, priv, aesKey, d) result := &interaction{} - err = jsoniter.Unmarshal(plaintext, result) + err = json.Unmarshal(plaintext, result) require.NoError(t, err, "unmarshal interaction %d: plaintext=%q", i, string(plaintext[:min(len(plaintext), 100)])) require.Equal(t, "dns", result.Protocol) require.Equal(t, "abc123def456ghi", result.UniqueID) @@ -175,7 +175,7 @@ func TestFullRoundTripDisk(t *testing.T) { RemoteAddress: "10.0.0.1", Timestamp: time.Now(), } - data, err := jsoniter.Marshal(inter) + data, err := json.Marshal(inter) require.NoError(t, err, "encode interaction %d", i) err = db.AddInteraction(correlationID, data) @@ -191,7 +191,7 @@ func TestFullRoundTripDisk(t *testing.T) { for i, d := range data { plaintext := clientDecrypt(t, priv, aesKey, d) result := &interaction{} - err = jsoniter.Unmarshal(plaintext, result) + err = json.Unmarshal(plaintext, result) require.NoError(t, err, "unmarshal interaction %d: plaintext=%q", i, string(plaintext[:min(len(plaintext), 100)])) require.Equal(t, "dns", result.Protocol) require.Equal(t, "abc123def456ghi", result.UniqueID) @@ -233,7 +233,7 @@ func TestPollResponseRoundTrip(t *testing.T) { RemoteAddress: "10.0.0.1", Timestamp: time.Now(), } - interData, err := jsoniter.Marshal(inter) + interData, err := json.Marshal(inter) require.NoError(t, err) err = mem.AddInteraction(correlationID, interData) require.NoError(t, err) @@ -245,12 +245,12 @@ func TestPollResponseRoundTrip(t *testing.T) { // Simulate PollResponse JSON encoding/decoding (server→client HTTP) response := &PollResponse{Data: data, AESKey: aesKey} var responseBuf bytes.Buffer - err = jsoniter.NewEncoder(&responseBuf).Encode(response) + err = json.NewEncoder(&responseBuf).Encode(response) require.NoError(t, err) // Decode on client side receivedResponse := &PollResponse{} - err = jsoniter.NewDecoder(&responseBuf).Decode(receivedResponse) + err = json.NewDecoder(&responseBuf).Decode(receivedResponse) require.NoError(t, err) require.Len(t, receivedResponse.Data, 1) @@ -258,7 +258,7 @@ func TestPollResponseRoundTrip(t *testing.T) { // Decrypt and unmarshal plaintext := clientDecrypt(t, priv, receivedResponse.AESKey, receivedResponse.Data[0]) result := &interaction{} - err = jsoniter.Unmarshal(plaintext, result) + err = json.Unmarshal(plaintext, result) require.NoError(t, err, "unmarshal failed: plaintext[:100]=%q", string(plaintext[:min(len(plaintext), 100)])) require.Equal(t, "dns", result.Protocol) } @@ -273,7 +273,7 @@ func TestTrailingNewlineHandling(t *testing.T) { Timestamp: time.Now(), } buffer := &bytes.Buffer{} - err := jsoniter.NewEncoder(buffer).Encode(inter) + err := json.NewEncoder(buffer).Encode(inter) require.NoError(t, err) encoded := buffer.Bytes() @@ -283,15 +283,15 @@ func TestTrailingNewlineHandling(t *testing.T) { // Verify trailing newline is present require.Equal(t, byte('\n'), encoded[len(encoded)-1], "Encode() should append trailing newline") - // Verify jsoniter.Unmarshal handles trailing newline + // Verify json.Unmarshal handles trailing newline result := &interaction{} - err = jsoniter.Unmarshal(encoded, result) + err = json.Unmarshal(encoded, result) require.NoError(t, err, "Unmarshal should handle trailing newline") require.Equal(t, "dns", result.Protocol) } -// TestJsoniterControlCharacterEscaping verifies jsoniter properly escapes control characters -func TestJsoniterControlCharacterEscaping(t *testing.T) { +// TestControlCharacterEscaping verifies json properly escapes control characters +func TestControlCharacterEscaping(t *testing.T) { // DNS message String() output contains tabs and newlines inter := &interaction{ Protocol: "dns", @@ -302,7 +302,7 @@ func TestJsoniterControlCharacterEscaping(t *testing.T) { Timestamp: time.Now(), } buffer := &bytes.Buffer{} - err := jsoniter.NewEncoder(buffer).Encode(inter) + err := json.NewEncoder(buffer).Encode(inter) require.NoError(t, err) encoded := buffer.Bytes() @@ -316,7 +316,7 @@ func TestJsoniterControlCharacterEscaping(t *testing.T) { // Verify round-trip result := &interaction{} - err = jsoniter.Unmarshal(encoded, result) + err = json.Unmarshal(encoded, result) require.NoError(t, err) require.Equal(t, "line1\nline2\ttab\rcarriage", result.RawRequest) } @@ -354,7 +354,7 @@ func TestStaleDataCleanupOnReRegistration(t *testing.T) { RemoteAddress: "1.2.3.4", Timestamp: time.Now(), } - data1, err := jsoniter.Marshal(inter) + data1, err := json.Marshal(inter) require.NoError(t, err) err = db.AddInteraction(correlationID, data1) require.NoError(t, err) @@ -380,7 +380,7 @@ func TestStaleDataCleanupOnReRegistration(t *testing.T) { RemoteAddress: "5.6.7.8", Timestamp: time.Now(), } - data2, err := jsoniter.Marshal(inter2) + data2, err := json.Marshal(inter2) require.NoError(t, err) err = db.AddInteraction(correlationID, data2) require.NoError(t, err) @@ -395,7 +395,7 @@ func TestStaleDataCleanupOnReRegistration(t *testing.T) { // Decrypt and verify it's the second interaction plaintext := clientDecrypt(t, priv2, aesKey, interactions[0]) result := &interaction{} - err = jsoniter.Unmarshal(plaintext, result) + err = json.Unmarshal(plaintext, result) require.NoError(t, err, "should unmarshal successfully with new key") require.Equal(t, "second-registration", result.UniqueID) @@ -433,7 +433,7 @@ func TestCacheEvictionCleansLevelDB(t *testing.T) { RemoteAddress: "1.2.3.4", Timestamp: time.Now(), } - data, err := jsoniter.Marshal(inter) + data, err := json.Marshal(inter) require.NoError(t, err) err = db.AddInteraction(correlationID, data) require.NoError(t, err) diff --git a/pkg/storage/util.go b/pkg/storage/util.go index 8df6f62e..9dcc81c8 100644 --- a/pkg/storage/util.go +++ b/pkg/storage/util.go @@ -39,12 +39,20 @@ func ParseB64RSAPublicKeyFromPEM(pubPEM string) (*rsa.PublicKey, error) { return nil, errors.New("key type is not RSA") } +// maxEncryptMessageSize bounds the plaintext accepted by AESEncrypt. It guards +// the size computations below against integer overflow; interaction payloads are +// orders of magnitude smaller than this in practice. +const maxEncryptMessageSize = 256 << 20 // 256 MiB + // AESEncrypt encrypts a message using AES and puts IV at the beginning of ciphertext. func AESEncrypt(key []byte, message []byte) (string, error) { block, err := aes.NewCipher(key) if err != nil { return "", err } + if len(message) > maxEncryptMessageSize { + return "", errors.New("message too large to encrypt") + } // It's common to put IV at the beginning of the ciphertext. cipherText := make([]byte, aes.BlockSize+len(message)) iv := cipherText[:aes.BlockSize]