Browse Source

Base prevention of replay attacks on proxy

tags/0.16
9seconds 7 years ago
parent
commit
33852ca481
10 changed files with 131 additions and 30 deletions
  1. 37
    0
      antireplay/cache.go
  2. 9
    0
      antireplay/hasher.go
  3. 2
    1
      client/client.go
  4. 9
    1
      client/direct.go
  5. 4
    2
      client/middle.go
  6. 23
    16
      config/config.go
  7. 4
    0
      go.mod
  8. 10
    0
      go.sum
  9. 16
    1
      main.go
  10. 17
    9
      proxy/proxy.go

+ 37
- 0
antireplay/cache.go View File

@@ -0,0 +1,37 @@
1
+package antireplay
2
+
3
+import (
4
+	"github.com/allegro/bigcache"
5
+	"github.com/juju/errors"
6
+
7
+	"github.com/9seconds/mtg/config"
8
+)
9
+
10
+// Cache defines storage for obfuscated2 handshake frames.
11
+type Cache struct {
12
+	cache *bigcache.BigCache
13
+}
14
+
15
+func (a Cache) Add(frame []byte) {
16
+	a.cache.Set(string(frame), nil) // nolint: errcheck
17
+}
18
+
19
+func (a Cache) Has(frame []byte) bool {
20
+	_, err := a.cache.Get(string(frame))
21
+
22
+	return err == nil
23
+}
24
+
25
+func NewCache(config *config.Config) (Cache, error) {
26
+	cache, err := bigcache.NewBigCache(bigcache.Config{
27
+		Shards:           1024,
28
+		LifeWindow:       config.AntiReplayEvictionTime,
29
+		Hasher:           hasher{},
30
+		HardMaxCacheSize: config.AntiReplayMaxSize,
31
+	})
32
+	if err != nil {
33
+		return Cache{}, errors.Annotate(err, "Cannot make cache")
34
+	}
35
+
36
+	return Cache{cache}, nil
37
+}

+ 9
- 0
antireplay/hasher.go View File

@@ -0,0 +1,9 @@
1
+package antireplay
2
+
3
+import "github.com/cespare/xxhash"
4
+
5
+type hasher struct{}
6
+
7
+func (h hasher) Sum64(value string) uint64 {
8
+	return xxhash.Sum64String(value)
9
+}

+ 2
- 1
client/client.go View File

@@ -4,6 +4,7 @@ import (
4 4
 	"context"
5 5
 	"net"
6 6
 
7
+	"github.com/9seconds/mtg/antireplay"
7 8
 	"github.com/9seconds/mtg/config"
8 9
 	"github.com/9seconds/mtg/mtproto"
9 10
 	"github.com/9seconds/mtg/wrappers"
@@ -11,4 +12,4 @@ import (
11 12
 
12 13
 // Init defines common method for initializing client connections.
13 14
 type Init func(context.Context, context.CancelFunc, net.Conn, string,
14
-	*config.Config) (wrappers.Wrap, *mtproto.ConnectionOpts, error)
15
+	antireplay.Cache, *config.Config) (wrappers.Wrap, *mtproto.ConnectionOpts, error)

+ 9
- 1
client/direct.go View File

@@ -7,6 +7,7 @@ import (
7 7
 
8 8
 	"github.com/juju/errors"
9 9
 
10
+	"github.com/9seconds/mtg/antireplay"
10 11
 	"github.com/9seconds/mtg/config"
11 12
 	"github.com/9seconds/mtg/mtproto"
12 13
 	"github.com/9seconds/mtg/obfuscated2"
@@ -18,7 +19,8 @@ const handshakeTimeout = 10 * time.Second
18 19
 // DirectInit initializes client connection for proxy which connects to
19 20
 // Telegram directly.
20 21
 func DirectInit(ctx context.Context, cancel context.CancelFunc, socket net.Conn,
21
-	connID string, conf *config.Config) (wrappers.Wrap, *mtproto.ConnectionOpts, error) {
22
+	connID string, antiReplayCache antireplay.Cache,
23
+	conf *config.Config) (wrappers.Wrap, *mtproto.ConnectionOpts, error) {
22 24
 	tcpSocket := socket.(*net.TCPConn)
23 25
 	if err := tcpSocket.SetNoDelay(false); err != nil {
24 26
 		return nil, nil, errors.Annotate(err, "Cannot disable NO_DELAY to client socket")
@@ -42,6 +44,12 @@ func DirectInit(ctx context.Context, cancel context.CancelFunc, socket net.Conn,
42 44
 	if err != nil {
43 45
 		return nil, nil, errors.Annotate(err, "Cannot parse obfuscated frame")
44 46
 	}
47
+
48
+	if antiReplayCache.Has([]byte(frame)) {
49
+		return nil, nil, errors.New("Replay attack is detected")
50
+	}
51
+	antiReplayCache.Add([]byte(frame))
52
+
45 53
 	connOpts.ConnectionProto = mtproto.ConnectionProtocolAny
46 54
 	connOpts.ClientAddr = conn.RemoteAddr()
47 55
 

+ 4
- 2
client/middle.go View File

@@ -4,6 +4,7 @@ import (
4 4
 	"context"
5 5
 	"net"
6 6
 
7
+	"github.com/9seconds/mtg/antireplay"
7 8
 	"github.com/9seconds/mtg/config"
8 9
 	"github.com/9seconds/mtg/mtproto"
9 10
 	"github.com/9seconds/mtg/wrappers"
@@ -12,8 +13,9 @@ import (
12 13
 // MiddleInit initializes client connection for proxy which has to
13 14
 // support promoted channels, connect to Telegram middle proxies etc.
14 15
 func MiddleInit(ctx context.Context, cancel context.CancelFunc, socket net.Conn,
15
-	connID string, conf *config.Config) (wrappers.Wrap, *mtproto.ConnectionOpts, error) {
16
-	conn, opts, err := DirectInit(ctx, cancel, socket, connID, conf)
16
+	connID string, antiReplayCache antireplay.Cache,
17
+	conf *config.Config) (wrappers.Wrap, *mtproto.ConnectionOpts, error) {
18
+	conn, opts, err := DirectInit(ctx, cancel, socket, connID, antiReplayCache, conf)
17 19
 	if err != nil {
18 20
 		return nil, nil, err
19 21
 	}

+ 23
- 16
config/config.go View File

@@ -6,6 +6,7 @@ import (
6 6
 	"fmt"
7 7
 	"net"
8 8
 	"strconv"
9
+	"time"
9 10
 
10 11
 	"github.com/juju/errors"
11 12
 	statsd "gopkg.in/alexcesaro/statsd.v2"
@@ -31,6 +32,9 @@ type Config struct {
31 32
 	PublicIPv6 net.IP
32 33
 	StatsIP    net.IP
33 34
 
35
+	AntiReplayMaxSize      int
36
+	AntiReplayEvictionTime time.Duration
37
+
34 38
 	StatsD struct {
35 39
 		Addr       net.Addr
36 40
 		Prefix     string
@@ -121,6 +125,7 @@ func NewConfig(debug, verbose bool, // nolint: gocyclo
121 125
 	statsdIP, statsdNetwork, statsdPrefix, statsdTagsFormat string,
122 126
 	statsdTags map[string]string, prometheusPrefix string,
123 127
 	secureOnly bool,
128
+	antiReplayMaxSize int, antiReplayEvictionTime time.Duration,
124 129
 	secret, adtag []byte) (*Config, error) {
125 130
 	secureMode := secureOnly
126 131
 	if bytes.HasPrefix(secret, []byte{0xdd}) && len(secret) == 17 {
@@ -160,22 +165,24 @@ func NewConfig(debug, verbose bool, // nolint: gocyclo
160 165
 	}
161 166
 
162 167
 	conf := &Config{
163
-		Debug:           debug,
164
-		Verbose:         verbose,
165
-		SecureOnly:      secureOnly,
166
-		BindIP:          bindIP,
167
-		BindPort:        bindPort,
168
-		PublicIPv4:      publicIPv4,
169
-		PublicIPv4Port:  publicIPv4Port,
170
-		PublicIPv6:      publicIPv6,
171
-		PublicIPv6Port:  publicIPv6Port,
172
-		StatsIP:         statsIP,
173
-		StatsPort:       statsPort,
174
-		Secret:          secret,
175
-		AdTag:           adtag,
176
-		SecureMode:      secureMode,
177
-		ReadBufferSize:  int(readBufferSize),
178
-		WriteBufferSize: int(writeBufferSize),
168
+		Debug:                  debug,
169
+		Verbose:                verbose,
170
+		SecureOnly:             secureOnly,
171
+		BindIP:                 bindIP,
172
+		BindPort:               bindPort,
173
+		PublicIPv4:             publicIPv4,
174
+		PublicIPv4Port:         publicIPv4Port,
175
+		PublicIPv6:             publicIPv6,
176
+		PublicIPv6Port:         publicIPv6Port,
177
+		StatsIP:                statsIP,
178
+		StatsPort:              statsPort,
179
+		Secret:                 secret,
180
+		AdTag:                  adtag,
181
+		SecureMode:             secureMode,
182
+		ReadBufferSize:         int(readBufferSize),
183
+		WriteBufferSize:        int(writeBufferSize),
184
+		AntiReplayMaxSize:      antiReplayMaxSize,
185
+		AntiReplayEvictionTime: antiReplayEvictionTime,
179 186
 	}
180 187
 	conf.Prometheus.Prefix = prometheusPrefix
181 188
 

+ 4
- 0
go.mod View File

@@ -3,8 +3,11 @@ module github.com/9seconds/mtg
3 3
 replace github.com/golang/lint => github.com/golang/lint v0.0.0-20190227174305-8f45f776aaf1
4 4
 
5 5
 require (
6
+	github.com/OneOfOne/xxhash v1.2.5 // indirect
7
+	github.com/allegro/bigcache v1.2.0
6 8
 	github.com/beevik/ntp v0.2.0
7 9
 	github.com/beorn7/perks v1.0.0 // indirect
10
+	github.com/cespare/xxhash v1.1.0
8 11
 	github.com/dustin/go-humanize v1.0.0
9 12
 	github.com/gofrs/uuid v3.2.0+incompatible
10 13
 	github.com/golang/protobuf v1.3.1 // indirect
@@ -17,6 +20,7 @@ require (
17 20
 	github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 // indirect
18 21
 	github.com/prometheus/common v0.3.0 // indirect
19 22
 	github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0 // indirect
23
+	github.com/spaolacci/murmur3 v1.1.0 // indirect
20 24
 	github.com/stretchr/testify v1.3.0
21 25
 	go.uber.org/atomic v1.3.2 // indirect
22 26
 	go.uber.org/multierr v1.1.0 // indirect

+ 10
- 0
go.sum View File

@@ -1,13 +1,20 @@
1
+github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
2
+github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI=
3
+github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
1 4
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
2 5
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
3 6
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
4 7
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
8
+github.com/allegro/bigcache v1.2.0 h1:qDaE0QoF29wKBb3+pXFrJFy1ihe5OT9OiXhg1t85SxM=
9
+github.com/allegro/bigcache v1.2.0/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
5 10
 github.com/beevik/ntp v0.2.0 h1:sGsd+kAXzT0bfVfzJfce04g+dSRfrs+tbQW8lweuYgw=
6 11
 github.com/beevik/ntp v0.2.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg=
7 12
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
8 13
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
9 14
 github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
10 15
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
16
+github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
17
+github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
11 18
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
12 19
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
13 20
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -65,6 +72,9 @@ github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R
65 72
 github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0 h1:c8R11WC8m7KNMkTv/0+Be8vvwo4I3/Ut9AC2FW8fX3U=
66 73
 github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
67 74
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
75
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
76
+github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
77
+github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
68 78
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
69 79
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
70 80
 github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=

+ 16
- 1
main.go View File

@@ -134,6 +134,17 @@ var (
134 134
 		Envar("MTG_SECURE_ONLY").
135 135
 		Bool()
136 136
 
137
+	antiReplayMaxSize = app.Flag("anti-replay-max-size",
138
+		"Max size of antireplay cache in megabytes.").
139
+		Envar("MTG_ANTIREPLAY_MAXSIZE").
140
+		Default("128").
141
+		Int()
142
+	antiReplayEvictionTime = app.Flag("anti-replay-eviction-time",
143
+		"Eviction time period for obfuscated2 handshakes").
144
+		Envar("MTG_ANTIREPLAY_EVICTIONTIME").
145
+		Default("168h").
146
+		Duration()
147
+
137 148
 	secret = app.Arg("secret", "Secret of this proxy.").Required().HexBytes()
138 149
 	adtag  = app.Arg("adtag", "ADTag of the proxy.").HexBytes()
139 150
 )
@@ -156,6 +167,7 @@ func main() { // nolint: gocyclo
156 167
 		*bindPort, *publicIPv4Port, *publicIPv6Port, *statsPort, *statsdPort,
157 168
 		*statsdIP, *statsdNetwork, *statsdPrefix, *statsdTagsFormat,
158 169
 		*statsdTags, *prometheusPrefix, *secureOnly,
170
+		*antiReplayMaxSize, *antiReplayEvictionTime,
159 171
 		*secret, *adtag,
160 172
 	)
161 173
 	if err != nil {
@@ -202,7 +214,10 @@ func main() { // nolint: gocyclo
202 214
 		panic(err)
203 215
 	}
204 216
 
205
-	server := proxy.NewProxy(conf)
217
+	server, err := proxy.NewProxy(conf)
218
+	if err != nil {
219
+		panic(err)
220
+	}
206 221
 	if err := server.Serve(); err != nil {
207 222
 		zap.S().Fatalw("Server stopped", "error", err)
208 223
 	}

+ 17
- 9
proxy/proxy.go View File

@@ -10,6 +10,7 @@ import (
10 10
 	"github.com/juju/errors"
11 11
 	"go.uber.org/zap"
12 12
 
13
+	"github.com/9seconds/mtg/antireplay"
13 14
 	"github.com/9seconds/mtg/client"
14 15
 	"github.com/9seconds/mtg/config"
15 16
 	"github.com/9seconds/mtg/mtproto"
@@ -20,9 +21,10 @@ import (
20 21
 
21 22
 // Proxy is a core of this program.
22 23
 type Proxy struct {
23
-	clientInit client.Init
24
-	tg         telegram.Telegram
25
-	conf       *config.Config
24
+	antiReplayCache antireplay.Cache
25
+	clientInit      client.Init
26
+	tg              telegram.Telegram
27
+	conf            *config.Config
26 28
 }
27 29
 
28 30
 // Serve runs TCP proxy server.
@@ -58,7 +60,7 @@ func (p *Proxy) accept(conn net.Conn) {
58 60
 
59 61
 	log.Infow("Client connected", "addr", conn.RemoteAddr())
60 62
 
61
-	clientConn, opts, err := p.clientInit(ctx, cancel, conn, connID, p.conf)
63
+	clientConn, opts, err := p.clientInit(ctx, cancel, conn, connID, p.antiReplayCache, p.conf)
62 64
 	if err != nil {
63 65
 		log.Errorw("Cannot initialize client connection", "error", err)
64 66
 		return
@@ -150,10 +152,15 @@ func (p *Proxy) directPipe(src wrappers.StreamReadCloser, dst io.Writer, wait *s
150 152
 }
151 153
 
152 154
 // NewProxy returns new proxy instance.
153
-func NewProxy(conf *config.Config) *Proxy {
155
+func NewProxy(conf *config.Config) (*Proxy, error) {
154 156
 	var clientInit client.Init
155 157
 	var tg telegram.Telegram
156 158
 
159
+	cache, err := antireplay.NewCache(conf)
160
+	if err != nil {
161
+		return nil, errors.Annotate(err, "Cannot make proxy")
162
+	}
163
+
157 164
 	if conf.UseMiddleProxy() {
158 165
 		clientInit = client.MiddleInit
159 166
 		tg = telegram.NewMiddleTelegram(conf)
@@ -163,8 +170,9 @@ func NewProxy(conf *config.Config) *Proxy {
163 170
 	}
164 171
 
165 172
 	return &Proxy{
166
-		conf:       conf,
167
-		clientInit: clientInit,
168
-		tg:         tg,
169
-	}
173
+		antiReplayCache: cache,
174
+		conf:            conf,
175
+		clientInit:      clientInit,
176
+		tg:              tg,
177
+	}, nil
170 178
 }

Loading…
Cancel
Save