forked from TrueCloudLab/neoneo-go
Merge pull request #3221 from nspcc-dev/json-limits
Anti-DOS RPC limitations
This commit is contained in:
commit
61d5b6eb0c
7 changed files with 109 additions and 20 deletions
|
@ -187,6 +187,8 @@ RPC:
|
|||
MaxFindResultItems: 100
|
||||
MaxFindStoragePageSize: 50
|
||||
MaxNEP11Tokens: 100
|
||||
MaxRequestBodyBytes: 5242880
|
||||
MaxRequestHeaderBytes: 1048576
|
||||
MaxWebSocketClients: 64
|
||||
SessionEnabled: false
|
||||
SessionExpirationTime: 15
|
||||
|
@ -225,6 +227,10 @@ where:
|
|||
- `MaxFindStoragePageSize` - the maximum number of elements for `findstorage` response per single page.
|
||||
- `MaxNEP11Tokens` - limit for the number of tokens returned from
|
||||
`getnep11balances` call.
|
||||
- `MaxRequestBodyBytes` - the maximum allowed HTTP request body size in bytes
|
||||
(5MB by default).
|
||||
- `MaxRequestHeaderBytes` - the maximum allowed HTTP request header size in bytes
|
||||
(1MB by default).
|
||||
- `MaxWebSocketClients` - the maximum simultaneous websocket client connection
|
||||
number (64 by default). Attempts to establish additional connections will
|
||||
lead to websocket handshake failures. Use "-1" to disable websocket
|
||||
|
|
|
@ -3,6 +3,7 @@ package config
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
|
@ -30,6 +31,12 @@ const (
|
|||
// DefaultMaxNEP11Tokens is the default maximum number of resulting NEP11 tokens
|
||||
// that can be traversed by `getnep11balances` JSON-RPC handler.
|
||||
DefaultMaxNEP11Tokens = 100
|
||||
// DefaultMaxRequestBodyBytes is the default maximum allowed size of HTTP
|
||||
// request body in bytes.
|
||||
DefaultMaxRequestBodyBytes = 5 * 1024 * 1024
|
||||
// DefaultMaxRequestHeaderBytes is the maximum permitted size of the headers
|
||||
// in an HTTP request.
|
||||
DefaultMaxRequestHeaderBytes = http.DefaultMaxHeaderBytes
|
||||
)
|
||||
|
||||
// Version is the version of the node, set at the build time.
|
||||
|
|
|
@ -16,6 +16,8 @@ type (
|
|||
MaxFindResultItems int `yaml:"MaxFindResultItems"`
|
||||
MaxFindStorageResultItems int `yaml:"MaxFindStoragePageSize"`
|
||||
MaxNEP11Tokens int `yaml:"MaxNEP11Tokens"`
|
||||
MaxRequestBodyBytes int `yaml:"MaxRequestBodyBytes"`
|
||||
MaxRequestHeaderBytes int `yaml:"MaxRequestHeaderBytes"`
|
||||
MaxWebSocketClients int `yaml:"MaxWebSocketClients"`
|
||||
SessionEnabled bool `yaml:"SessionEnabled"`
|
||||
SessionExpirationTime int `yaml:"SessionExpirationTime"`
|
||||
|
|
|
@ -115,6 +115,15 @@ func (s *SignerWithWitness) UnmarshalJSON(data []byte) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("not a signer: %w", err)
|
||||
}
|
||||
if len(aux.AllowedContracts) > transaction.MaxAttributes {
|
||||
return fmt.Errorf("invalid number of AllowedContracts: got %d, allowed %d at max", len(aux.AllowedContracts), transaction.MaxAttributes)
|
||||
}
|
||||
if len(aux.AllowedGroups) > transaction.MaxAttributes {
|
||||
return fmt.Errorf("invalid number of AllowedGroups: got %d, allowed %d at max", len(aux.AllowedGroups), transaction.MaxAttributes)
|
||||
}
|
||||
if len(aux.Rules) > transaction.MaxAttributes {
|
||||
return fmt.Errorf("invalid number of Rules: got %d, allowed %d at max", len(aux.Rules), transaction.MaxAttributes)
|
||||
}
|
||||
acc, err := util.Uint160DecodeStringLE(strings.TrimPrefix(aux.Account, "0x"))
|
||||
if err != nil {
|
||||
acc, err = address.StringToUint160(aux.Account)
|
||||
|
|
|
@ -2,10 +2,12 @@ package neorpc
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/internal/testserdes"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
@ -37,4 +39,54 @@ func TestSignerWithWitnessMarshalUnmarshalJSON(t *testing.T) {
|
|||
actual, err := json.Marshal(s)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected, string(actual))
|
||||
|
||||
t.Run("subitems overflow", func(t *testing.T) {
|
||||
checkSubitems := func(t *testing.T, bad any) {
|
||||
data, err := json.Marshal(bad)
|
||||
require.NoError(t, err)
|
||||
err = json.Unmarshal(data, &SignerWithWitness{})
|
||||
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), fmt.Sprintf("got %d, allowed %d at max", transaction.MaxAttributes+1, transaction.MaxAttributes))
|
||||
}
|
||||
|
||||
t.Run("groups", func(t *testing.T) {
|
||||
pk, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
bad := &SignerWithWitness{
|
||||
Signer: transaction.Signer{
|
||||
AllowedGroups: make([]*keys.PublicKey, transaction.MaxAttributes+1),
|
||||
},
|
||||
}
|
||||
for i := range bad.AllowedGroups {
|
||||
bad.AllowedGroups[i] = pk.PublicKey()
|
||||
}
|
||||
|
||||
checkSubitems(t, bad)
|
||||
})
|
||||
t.Run("contracts", func(t *testing.T) {
|
||||
bad := &SignerWithWitness{
|
||||
Signer: transaction.Signer{
|
||||
AllowedContracts: make([]util.Uint160, transaction.MaxAttributes+1),
|
||||
},
|
||||
}
|
||||
|
||||
checkSubitems(t, bad)
|
||||
})
|
||||
t.Run("rules", func(t *testing.T) {
|
||||
bad := &SignerWithWitness{
|
||||
Signer: transaction.Signer{
|
||||
Rules: make([]transaction.WitnessRule, transaction.MaxAttributes+1),
|
||||
},
|
||||
}
|
||||
for i := range bad.Rules {
|
||||
bad.Rules[i] = transaction.WitnessRule{
|
||||
Action: transaction.WitnessAllow,
|
||||
Condition: &transaction.ConditionScriptHash{},
|
||||
}
|
||||
}
|
||||
|
||||
checkSubitems(t, bad)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -394,7 +394,7 @@ func (p *Param) GetSignerWithWitness() (neorpc.SignerWithWitness, error) {
|
|||
// GetSignersWithWitnesses returns a slice of SignerWithWitness with CalledByEntry
|
||||
// scope from an array of Uint160 or an array of serialized transaction.Signer stored
|
||||
// in the parameter.
|
||||
func (p Param) GetSignersWithWitnesses() ([]transaction.Signer, []transaction.Witness, error) {
|
||||
func (p *Param) GetSignersWithWitnesses() ([]transaction.Signer, []transaction.Witness, error) {
|
||||
hashes, err := p.GetArray()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
|
|
@ -267,25 +267,6 @@ var rpcWsHandlers = map[string]func(*Server, params.Params, *subscriber) (any, *
|
|||
// untyped nil or non-nil structure implementing OracleHandler interface.
|
||||
func New(chain Ledger, conf config.RPC, coreServer *network.Server,
|
||||
orc OracleHandler, log *zap.Logger, errChan chan<- error) Server {
|
||||
addrs := conf.Addresses
|
||||
httpServers := make([]*http.Server, len(addrs))
|
||||
for i, addr := range addrs {
|
||||
httpServers[i] = &http.Server{
|
||||
Addr: addr,
|
||||
}
|
||||
}
|
||||
|
||||
var tlsServers []*http.Server
|
||||
if cfg := conf.TLSConfig; cfg.Enabled {
|
||||
addrs := cfg.Addresses
|
||||
tlsServers = make([]*http.Server, len(addrs))
|
||||
for i, addr := range addrs {
|
||||
tlsServers[i] = &http.Server{
|
||||
Addr: addr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protoCfg := chain.GetConfig().ProtocolConfiguration
|
||||
if conf.SessionEnabled {
|
||||
if conf.SessionExpirationTime <= 0 {
|
||||
|
@ -313,6 +294,14 @@ func New(chain Ledger, conf config.RPC, coreServer *network.Server,
|
|||
conf.MaxNEP11Tokens = config.DefaultMaxNEP11Tokens
|
||||
log.Info("MaxNEP11Tokens is not set or wrong, setting default value", zap.Int("MaxNEP11Tokens", config.DefaultMaxNEP11Tokens))
|
||||
}
|
||||
if conf.MaxRequestBodyBytes <= 0 {
|
||||
conf.MaxRequestBodyBytes = config.DefaultMaxRequestBodyBytes
|
||||
log.Info("MaxRequestBodyBytes is not set or wong, setting default value", zap.Int("MaxRequestBodyBytes", config.DefaultMaxRequestBodyBytes))
|
||||
}
|
||||
if conf.MaxRequestHeaderBytes <= 0 {
|
||||
conf.MaxRequestHeaderBytes = config.DefaultMaxRequestHeaderBytes
|
||||
log.Info("MaxRequestHeaderBytes is not set or wong, setting default value", zap.Int("MaxRequestHeaderBytes", config.DefaultMaxRequestHeaderBytes))
|
||||
}
|
||||
if conf.MaxWebSocketClients == 0 {
|
||||
conf.MaxWebSocketClients = defaultMaxWebSocketClients
|
||||
log.Info("MaxWebSocketClients is not set or wrong, setting default value", zap.Int("MaxWebSocketClients", defaultMaxWebSocketClients))
|
||||
|
@ -325,6 +314,28 @@ func New(chain Ledger, conf config.RPC, coreServer *network.Server,
|
|||
if conf.EnableCORSWorkaround {
|
||||
wsOriginChecker = func(_ *http.Request) bool { return true }
|
||||
}
|
||||
|
||||
addrs := conf.Addresses
|
||||
httpServers := make([]*http.Server, len(addrs))
|
||||
for i, addr := range addrs {
|
||||
httpServers[i] = &http.Server{
|
||||
Addr: addr,
|
||||
MaxHeaderBytes: conf.MaxRequestHeaderBytes,
|
||||
}
|
||||
}
|
||||
|
||||
var tlsServers []*http.Server
|
||||
if cfg := conf.TLSConfig; cfg.Enabled {
|
||||
addrs := cfg.Addresses
|
||||
tlsServers = make([]*http.Server, len(addrs))
|
||||
for i, addr := range addrs {
|
||||
tlsServers[i] = &http.Server{
|
||||
Addr: addr,
|
||||
MaxHeaderBytes: conf.MaxRequestHeaderBytes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Server{
|
||||
http: httpServers,
|
||||
https: tlsServers,
|
||||
|
@ -474,6 +485,8 @@ func (s *Server) SetOracleHandler(orc OracleHandler) {
|
|||
}
|
||||
|
||||
func (s *Server) handleHTTPRequest(w http.ResponseWriter, httpRequest *http.Request) {
|
||||
// Restrict request body before further processing.
|
||||
httpRequest.Body = http.MaxBytesReader(w, httpRequest.Body, int64(s.config.MaxRequestBodyBytes))
|
||||
req := params.NewRequest()
|
||||
|
||||
if httpRequest.URL.Path == "/ws" && httpRequest.Method == "GET" {
|
||||
|
|
Loading…
Reference in a new issue