From d4f18b13429b01832de935e25a7c5d2955a97374 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Sun, 12 Jun 2022 23:29:09 +0100 Subject: [PATCH] Fix DoS attack vulnerability in VMess Option Processing --- proxy/vmess/encoding/client.go | 48 +++++++++++++++++++------------- proxy/vmess/encoding/server.go | 48 +++++++++++++++++++------------- proxy/vmess/inbound/inbound.go | 11 ++++++-- proxy/vmess/outbound/outbound.go | 10 +++++-- 4 files changed, 72 insertions(+), 45 deletions(-) diff --git a/proxy/vmess/encoding/client.go b/proxy/vmess/encoding/client.go index 0b535d2a..ba08843e 100644 --- a/proxy/vmess/encoding/client.go +++ b/proxy/vmess/encoding/client.go @@ -136,31 +136,35 @@ func (c *ClientSession) EncodeRequestHeader(header *protocol.RequestHeader, writ return nil } -func (c *ClientSession) EncodeRequestBody(request *protocol.RequestHeader, writer io.Writer) buf.Writer { +func (c *ClientSession) EncodeRequestBody(request *protocol.RequestHeader, writer io.Writer) (buf.Writer, error) { var sizeParser crypto.ChunkSizeEncoder = crypto.PlainChunkSizeParser{} if request.Option.Has(protocol.RequestOptionChunkMasking) { sizeParser = NewShakeSizeParser(c.requestBodyIV[:]) } var padding crypto.PaddingLengthGenerator if request.Option.Has(protocol.RequestOptionGlobalPadding) { - padding = sizeParser.(crypto.PaddingLengthGenerator) + var ok bool + padding, ok = sizeParser.(crypto.PaddingLengthGenerator) + if !ok { + return nil, newError("invalid option: RequestOptionGlobalPadding") + } } switch request.Security { case protocol.SecurityType_NONE: if request.Option.Has(protocol.RequestOptionChunkStream) { if request.Command.TransferType() == protocol.TransferTypeStream { - return crypto.NewChunkStreamWriter(sizeParser, writer) + return crypto.NewChunkStreamWriter(sizeParser, writer), nil } auth := &crypto.AEADAuthenticator{ AEAD: new(NoOpAuthenticator), NonceGenerator: crypto.GenerateEmptyBytes(), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } - return crypto.NewAuthenticationWriter(auth, sizeParser, writer, protocol.TransferTypePacket, padding) + return crypto.NewAuthenticationWriter(auth, sizeParser, writer, protocol.TransferTypePacket, padding), nil } - return buf.NewWriter(writer) + return buf.NewWriter(writer), nil case protocol.SecurityType_LEGACY: aesStream := crypto.NewAesEncryptionStream(c.requestBodyKey[:], c.requestBodyIV[:]) cryptionWriter := crypto.NewCryptionWriter(aesStream, writer) @@ -170,10 +174,10 @@ func (c *ClientSession) EncodeRequestBody(request *protocol.RequestHeader, write NonceGenerator: crypto.GenerateEmptyBytes(), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } - return crypto.NewAuthenticationWriter(auth, sizeParser, cryptionWriter, request.Command.TransferType(), padding) + return crypto.NewAuthenticationWriter(auth, sizeParser, cryptionWriter, request.Command.TransferType(), padding), nil } - return &buf.SequentialWriter{Writer: cryptionWriter} + return &buf.SequentialWriter{Writer: cryptionWriter}, nil case protocol.SecurityType_AES128_GCM: aead := crypto.NewAesGcm(c.requestBodyKey[:]) auth := &crypto.AEADAuthenticator{ @@ -192,7 +196,7 @@ func (c *ClientSession) EncodeRequestBody(request *protocol.RequestHeader, write } sizeParser = NewAEADSizeParser(lengthAuth) } - return crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType(), padding) + return crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType(), padding), nil case protocol.SecurityType_CHACHA20_POLY1305: aead, err := chacha20poly1305.New(GenerateChacha20Poly1305Key(c.requestBodyKey[:])) common.Must(err) @@ -214,9 +218,9 @@ func (c *ClientSession) EncodeRequestBody(request *protocol.RequestHeader, write } sizeParser = NewAEADSizeParser(lengthAuth) } - return crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType(), padding) + return crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType(), padding), nil default: - panic("Unknown security type.") + return nil, newError("invalid option: Security") } } @@ -305,21 +309,25 @@ func (c *ClientSession) DecodeResponseHeader(reader io.Reader) (*protocol.Respon return header, nil } -func (c *ClientSession) DecodeResponseBody(request *protocol.RequestHeader, reader io.Reader) buf.Reader { +func (c *ClientSession) DecodeResponseBody(request *protocol.RequestHeader, reader io.Reader) (buf.Reader, error) { var sizeParser crypto.ChunkSizeDecoder = crypto.PlainChunkSizeParser{} if request.Option.Has(protocol.RequestOptionChunkMasking) { sizeParser = NewShakeSizeParser(c.responseBodyIV[:]) } var padding crypto.PaddingLengthGenerator if request.Option.Has(protocol.RequestOptionGlobalPadding) { - padding = sizeParser.(crypto.PaddingLengthGenerator) + var ok bool + padding, ok = sizeParser.(crypto.PaddingLengthGenerator) + if !ok { + return nil, newError("invalid option: RequestOptionGlobalPadding") + } } switch request.Security { case protocol.SecurityType_NONE: if request.Option.Has(protocol.RequestOptionChunkStream) { if request.Command.TransferType() == protocol.TransferTypeStream { - return crypto.NewChunkStreamReader(sizeParser, reader) + return crypto.NewChunkStreamReader(sizeParser, reader), nil } auth := &crypto.AEADAuthenticator{ @@ -328,10 +336,10 @@ func (c *ClientSession) DecodeResponseBody(request *protocol.RequestHeader, read AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } - return crypto.NewAuthenticationReader(auth, sizeParser, reader, protocol.TransferTypePacket, padding) + return crypto.NewAuthenticationReader(auth, sizeParser, reader, protocol.TransferTypePacket, padding), nil } - return buf.NewReader(reader) + return buf.NewReader(reader), nil case protocol.SecurityType_LEGACY: if request.Option.Has(protocol.RequestOptionChunkStream) { auth := &crypto.AEADAuthenticator{ @@ -339,10 +347,10 @@ func (c *ClientSession) DecodeResponseBody(request *protocol.RequestHeader, read NonceGenerator: crypto.GenerateEmptyBytes(), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } - return crypto.NewAuthenticationReader(auth, sizeParser, c.responseReader, request.Command.TransferType(), padding) + return crypto.NewAuthenticationReader(auth, sizeParser, c.responseReader, request.Command.TransferType(), padding), nil } - return buf.NewReader(c.responseReader) + return buf.NewReader(c.responseReader), nil case protocol.SecurityType_AES128_GCM: aead := crypto.NewAesGcm(c.responseBodyKey[:]) @@ -362,7 +370,7 @@ func (c *ClientSession) DecodeResponseBody(request *protocol.RequestHeader, read } sizeParser = NewAEADSizeParser(lengthAuth) } - return crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType(), padding) + return crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType(), padding), nil case protocol.SecurityType_CHACHA20_POLY1305: aead, _ := chacha20poly1305.New(GenerateChacha20Poly1305Key(c.responseBodyKey[:])) @@ -383,9 +391,9 @@ func (c *ClientSession) DecodeResponseBody(request *protocol.RequestHeader, read } sizeParser = NewAEADSizeParser(lengthAuth) } - return crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType(), padding) + return crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType(), padding), nil default: - panic("Unknown security type.") + return nil, newError("invalid option: Security") } } diff --git a/proxy/vmess/encoding/server.go b/proxy/vmess/encoding/server.go index be83bb31..4bdb6fb7 100644 --- a/proxy/vmess/encoding/server.go +++ b/proxy/vmess/encoding/server.go @@ -310,21 +310,25 @@ func (s *ServerSession) DecodeRequestHeader(reader io.Reader, isDrain bool) (*pr } // DecodeRequestBody returns Reader from which caller can fetch decrypted body. -func (s *ServerSession) DecodeRequestBody(request *protocol.RequestHeader, reader io.Reader) buf.Reader { +func (s *ServerSession) DecodeRequestBody(request *protocol.RequestHeader, reader io.Reader) (buf.Reader, error) { var sizeParser crypto.ChunkSizeDecoder = crypto.PlainChunkSizeParser{} if request.Option.Has(protocol.RequestOptionChunkMasking) { sizeParser = NewShakeSizeParser(s.requestBodyIV[:]) } var padding crypto.PaddingLengthGenerator if request.Option.Has(protocol.RequestOptionGlobalPadding) { - padding = sizeParser.(crypto.PaddingLengthGenerator) + var ok bool + padding, ok = sizeParser.(crypto.PaddingLengthGenerator) + if !ok { + return nil, newError("invalid option: RequestOptionGlobalPadding") + } } switch request.Security { case protocol.SecurityType_NONE: if request.Option.Has(protocol.RequestOptionChunkStream) { if request.Command.TransferType() == protocol.TransferTypeStream { - return crypto.NewChunkStreamReader(sizeParser, reader) + return crypto.NewChunkStreamReader(sizeParser, reader), nil } auth := &crypto.AEADAuthenticator{ @@ -332,9 +336,9 @@ func (s *ServerSession) DecodeRequestBody(request *protocol.RequestHeader, reade NonceGenerator: crypto.GenerateEmptyBytes(), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } - return crypto.NewAuthenticationReader(auth, sizeParser, reader, protocol.TransferTypePacket, padding) + return crypto.NewAuthenticationReader(auth, sizeParser, reader, protocol.TransferTypePacket, padding), nil } - return buf.NewReader(reader) + return buf.NewReader(reader), nil case protocol.SecurityType_LEGACY: aesStream := crypto.NewAesDecryptionStream(s.requestBodyKey[:], s.requestBodyIV[:]) @@ -345,9 +349,9 @@ func (s *ServerSession) DecodeRequestBody(request *protocol.RequestHeader, reade NonceGenerator: crypto.GenerateEmptyBytes(), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } - return crypto.NewAuthenticationReader(auth, sizeParser, cryptionReader, request.Command.TransferType(), padding) + return crypto.NewAuthenticationReader(auth, sizeParser, cryptionReader, request.Command.TransferType(), padding), nil } - return buf.NewReader(cryptionReader) + return buf.NewReader(cryptionReader), nil case protocol.SecurityType_AES128_GCM: aead := crypto.NewAesGcm(s.requestBodyKey[:]) @@ -367,7 +371,7 @@ func (s *ServerSession) DecodeRequestBody(request *protocol.RequestHeader, reade } sizeParser = NewAEADSizeParser(lengthAuth) } - return crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType(), padding) + return crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType(), padding), nil case protocol.SecurityType_CHACHA20_POLY1305: aead, _ := chacha20poly1305.New(GenerateChacha20Poly1305Key(s.requestBodyKey[:])) @@ -389,10 +393,10 @@ func (s *ServerSession) DecodeRequestBody(request *protocol.RequestHeader, reade } sizeParser = NewAEADSizeParser(lengthAuth) } - return crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType(), padding) + return crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType(), padding), nil default: - panic("Unknown security type.") + return nil, newError("invalid option: Security") } } @@ -453,21 +457,25 @@ func (s *ServerSession) EncodeResponseHeader(header *protocol.ResponseHeader, wr } // EncodeResponseBody returns a Writer that auto-encrypt content written by caller. -func (s *ServerSession) EncodeResponseBody(request *protocol.RequestHeader, writer io.Writer) buf.Writer { +func (s *ServerSession) EncodeResponseBody(request *protocol.RequestHeader, writer io.Writer) (buf.Writer, error) { var sizeParser crypto.ChunkSizeEncoder = crypto.PlainChunkSizeParser{} if request.Option.Has(protocol.RequestOptionChunkMasking) { sizeParser = NewShakeSizeParser(s.responseBodyIV[:]) } var padding crypto.PaddingLengthGenerator if request.Option.Has(protocol.RequestOptionGlobalPadding) { - padding = sizeParser.(crypto.PaddingLengthGenerator) + var ok bool + padding, ok = sizeParser.(crypto.PaddingLengthGenerator) + if !ok { + return nil, newError("invalid option: RequestOptionGlobalPadding") + } } switch request.Security { case protocol.SecurityType_NONE: if request.Option.Has(protocol.RequestOptionChunkStream) { if request.Command.TransferType() == protocol.TransferTypeStream { - return crypto.NewChunkStreamWriter(sizeParser, writer) + return crypto.NewChunkStreamWriter(sizeParser, writer), nil } auth := &crypto.AEADAuthenticator{ @@ -475,9 +483,9 @@ func (s *ServerSession) EncodeResponseBody(request *protocol.RequestHeader, writ NonceGenerator: crypto.GenerateEmptyBytes(), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } - return crypto.NewAuthenticationWriter(auth, sizeParser, writer, protocol.TransferTypePacket, padding) + return crypto.NewAuthenticationWriter(auth, sizeParser, writer, protocol.TransferTypePacket, padding), nil } - return buf.NewWriter(writer) + return buf.NewWriter(writer), nil case protocol.SecurityType_LEGACY: if request.Option.Has(protocol.RequestOptionChunkStream) { @@ -486,9 +494,9 @@ func (s *ServerSession) EncodeResponseBody(request *protocol.RequestHeader, writ NonceGenerator: crypto.GenerateEmptyBytes(), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } - return crypto.NewAuthenticationWriter(auth, sizeParser, s.responseWriter, request.Command.TransferType(), padding) + return crypto.NewAuthenticationWriter(auth, sizeParser, s.responseWriter, request.Command.TransferType(), padding), nil } - return &buf.SequentialWriter{Writer: s.responseWriter} + return &buf.SequentialWriter{Writer: s.responseWriter}, nil case protocol.SecurityType_AES128_GCM: aead := crypto.NewAesGcm(s.responseBodyKey[:]) @@ -508,7 +516,7 @@ func (s *ServerSession) EncodeResponseBody(request *protocol.RequestHeader, writ } sizeParser = NewAEADSizeParser(lengthAuth) } - return crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType(), padding) + return crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType(), padding), nil case protocol.SecurityType_CHACHA20_POLY1305: aead, _ := chacha20poly1305.New(GenerateChacha20Poly1305Key(s.responseBodyKey[:])) @@ -530,9 +538,9 @@ func (s *ServerSession) EncodeResponseBody(request *protocol.RequestHeader, writ } sizeParser = NewAEADSizeParser(lengthAuth) } - return crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType(), padding) + return crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType(), padding), nil default: - panic("Unknown security type.") + return nil, newError("invalid option: Security") } } diff --git a/proxy/vmess/inbound/inbound.go b/proxy/vmess/inbound/inbound.go index 6150edf2..00b07f14 100644 --- a/proxy/vmess/inbound/inbound.go +++ b/proxy/vmess/inbound/inbound.go @@ -184,8 +184,10 @@ func (h *Handler) RemoveUser(ctx context.Context, email string) error { func transferResponse(timer signal.ActivityUpdater, session *encoding.ServerSession, request *protocol.RequestHeader, response *protocol.ResponseHeader, input buf.Reader, output *buf.BufferedWriter) error { session.EncodeResponseHeader(response, output) - bodyWriter := session.EncodeResponseBody(request, output) - + bodyWriter, err := session.EncodeResponseBody(request, output) + if err != nil { + return newError("failed to start decoding response").Base(err) + } { // Optimize for small response packet data, err := input.ReadMultiBuffer() @@ -301,7 +303,10 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s requestDone := func() error { defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly) - bodyReader := svrSession.DecodeRequestBody(request, reader) + bodyReader, err := svrSession.DecodeRequestBody(request, reader) + if err != nil { + return newError("failed to start decoding").Base(err) + } if err := buf.Copy(bodyReader, link.Writer, buf.UpdateActivity(timer)); err != nil { return newError("failed to transfer request").Base(err) } diff --git a/proxy/vmess/outbound/outbound.go b/proxy/vmess/outbound/outbound.go index 5cf654ba..e7c6466e 100644 --- a/proxy/vmess/outbound/outbound.go +++ b/proxy/vmess/outbound/outbound.go @@ -158,7 +158,10 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte return newError("failed to encode request").Base(err).AtWarning() } - bodyWriter := session.EncodeRequestBody(request, writer) + bodyWriter, err := session.EncodeRequestBody(request, writer) + if err != nil { + return newError("failed to start encoding").Base(err) + } bodyWriter2 := bodyWriter if request.Command == protocol.RequestCommandMux && request.Port == 666 { bodyWriter = xudp.NewPacketWriter(bodyWriter, target) @@ -194,7 +197,10 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte } h.handleCommand(rec.Destination(), header.Command) - bodyReader := session.DecodeResponseBody(request, reader) + bodyReader, err := session.DecodeResponseBody(request, reader) + if err != nil { + return newError("failed to start encoding response").Base(err) + } if request.Command == protocol.RequestCommandMux && request.Port == 666 { bodyReader = xudp.NewPacketReader(&buf.BufferedReader{Reader: bodyReader}) }