Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
M
mtproto_proxy
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
mtproto_proxy
Commits
24ef8277
Unverified
Commit
24ef8277
authored
May 26, 2019
by
Сергей Прохоров
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add another replay attack protection: filter error replies from downstream
parent
6768b7ba
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
77 additions
and
10 deletions
+77
-10
mtp_handler.erl
src/mtp_handler.erl
+37
-8
mtproto_proxy.app.src
src/mtproto_proxy.app.src
+12
-1
single_dc_SUITE.erl
test/single_dc_SUITE.erl
+28
-1
No files found.
src/mtp_handler.erl
View file @
24ef8277
...
...
@@ -28,6 +28,8 @@
-
define
(
MAX_UP_INIT_BUF_SIZE
,
1024
*
1024
).
%1mb
-
define
(
HEALTH_CHECK_INTERVAL
,
5000
).
% telegram server responds with "l\xfe\xff\xff" if client packet MTProto is invalid
-
define
(
SRV_ERROR
,
<<
108
,
254
,
255
,
255
>>
).
-
define
(
APP
,
mtproto_proxy
).
...
...
@@ -51,7 +53,8 @@
started_at
::
pos_integer
(),
timer_state
=
init
::
init
|
hibernate
|
stop
,
timer
::
gen_timeout
:
tout
(),
last_queue_check
::
integer
()}).
last_queue_check
::
integer
(),
srv_error_filter
::
first
|
on
|
off
}).
-
type
transport
()
::
module
().
-
type
stage
()
::
init
|
tunnel
.
...
...
@@ -99,6 +102,7 @@ init({Socket, Transport, [Name, Secret, Tag]}) ->
{
TimeoutKey
,
TimeoutDefault
}
=
state_timeout
(
init
),
Timer
=
gen_timeout
:
new
(
#
{
timeout
=>
{
env
,
?
APP
,
TimeoutKey
,
TimeoutDefault
}}),
Filter
=
application
:
get_env
(
?
APP
,
replay_check_server_error_filter
,
off
),
NowMs
=
erlang
:
system_time
(
millisecond
),
State
=
#state
{
sock
=
Socket
,
secret
=
unhex
(
Secret
),
...
...
@@ -108,7 +112,8 @@ init({Socket, Transport, [Name, Secret, Tag]}) ->
addr
=
{
Ip
,
Port
},
started_at
=
NowMs
,
timer
=
Timer
,
last_queue_check
=
NowMs
},
last_queue_check
=
NowMs
,
srv_error_filter
=
Filter
},
{
ok
,
State
};
{
error
,
Reason
}
->
mtp_metric
:
count_inc
([
?
APP
,
in_connection_closed
,
total
],
1
,
#
{
labels
=>
[
Name
]}),
...
...
@@ -120,11 +125,35 @@ handle_call(_Request, _From, State) ->
Reply
=
ok
,
{
reply
,
Reply
,
State
}.
handle_cast
({
proxy_ans
,
Down
,
Data
},
#state
{
down
=
Down
}
=
S
)
->
handle_cast
({
proxy_ans
,
Down
,
Data
},
#state
{
down
=
Down
,
srv_error_filter
=
off
}
=
S
)
->
%% telegram server -> proxy
%% srv_error_filter is 'off'
{
ok
,
S1
}
=
up_send
(
Data
,
S
),
ok
=
mtp_down_conn
:
ack
(
Down
,
1
,
iolist_size
(
Data
)),
maybe_check_health
(
bump_timer
(
S1
));
handle_cast
({
proxy_ans
,
Down
,
?
SRV_ERROR
=
Data
},
#state
{
down
=
Down
,
srv_error_filter
=
Filter
,
addr
=
{
Ip
,
_}}
=
S
)
when
Filter
=/=
off
->
%% telegram server -> proxy
%% Server replied with server error; it might be another kind of replay attack;
%% Don't send this packet to client so proxy won't be fingerprinted
ok
=
mtp_down_conn
:
ack
(
Down
,
1
,
iolist_size
(
Data
)),
?
log
(
warning
,
"
~s
: protocol_error srv_error_filtered"
,
[
inet
:
ntoa
(
Ip
)]),
mtp_metric
:
count_inc
([
?
APP
,
protocol_error
,
total
],
1
,
#
{
labels
=>
[
srv_error_filtered
]}),
case
Filter
of
first
->
S
#state
{
srv_error_filter
=
off
};
on
->
S
end
;
handle_cast
({
proxy_ans
,
Down
,
Data
},
#state
{
down
=
Down
,
srv_error_filter
=
Filter
}
=
S
)
when
Filter
=/=
off
->
%% telegram server -> proxy
%% Normal data packet
%% srv_error_filter is 'on' or srv_error_filter is 'first' and it's 1st server packet
{
ok
,
S1
}
=
up_send
(
Data
,
S
),
ok
=
mtp_down_conn
:
ack
(
Down
,
1
,
iolist_size
(
Data
)),
S2
=
case
Filter
of
first
->
S1
#state
{
srv_error_filter
=
off
};
on
->
S1
end
,
maybe_check_health
(
bump_timer
(
S2
));
handle_cast
({
close_ext
,
Down
},
#state
{
down
=
Down
,
sock
=
USock
,
transport
=
UTrans
}
=
S
)
->
?
log
(
debug
,
"asked to close connection by downstream"
),
ok
=
UTrans
:
close
(
USock
),
...
...
@@ -137,7 +166,7 @@ handle_cast(Other, State) ->
{
noreply
,
State
}.
handle_info
({
tcp
,
Sock
,
Data
},
#state
{
sock
=
Sock
,
transport
=
Transport
,
listener
=
Listener
}
=
S
)
->
listener
=
Listener
,
addr
=
{
Ip
,
_}
}
=
S
)
->
%% client -> proxy
Size
=
byte_size
(
Data
),
mtp_metric
:
count_inc
([
?
APP
,
received
,
upstream
,
bytes
],
Size
,
#
{
labels
=>
[
Listener
]}),
...
...
@@ -152,7 +181,7 @@ handle_info({tcp, Sock, Data}, #state{sock = Sock, transport = Transport,
{
stop
,
normal
,
S
}
catch
error
:{
protocol_error
,
Type
,
Extra
}
->
mtp_metric
:
count_inc
([
?
APP
,
protocol_error
,
total
],
1
,
#
{
labels
=>
[
Type
]}),
?
log
(
warning
,
"
protocol_error
~p
~p
"
,
[
Type
,
Extra
]),
?
log
(
warning
,
"
~s
: protocol_error
~p
~p
"
,
[
inet
:
ntoa
(
Ip
),
Type
,
Extra
]),
{
stop
,
normal
,
maybe_close_down
(
S
)}
end
;
handle_info
({
tcp_closed
,
Sock
},
#state
{
sock
=
Sock
}
=
S
)
->
...
...
@@ -268,11 +297,11 @@ handle_upstream_data(Bin, #state{stage = init, stage_state = Buf} = S) ->
maybe_check_replay
(
Packet
)
->
%% Check for session replay attack: attempt to connect with the same 1st 64byte packet
case
lists
:
member
(
mtp_session_storage
,
application
:
get_env
(
?
APP
,
replay_checks_enabled
,
[])
)
of
true
->
case
application
:
get_env
(
?
APP
,
replay_check_session_storage
,
off
)
of
on
->
(
new
==
mtp_session_storage
:
check_add
(
Packet
))
orelse
error
({
protocol_error
,
replay_session_detected
,
Packet
});
false
->
off
->
ok
end
.
...
...
src/mtproto_proxy.app.src
View file @
24ef8277
...
...
@@ -63,7 +63,18 @@
%% List of enabled replay-attack checks. See
%% https://habr.com/ru/post/452144/
{replay_checks_enabled, [mtp_session_storage]},
%% session_storage - store last used 1st client packets in special
%% storage, drop connections with same 1st packet
%% Values: on/off
%% Default: off
{replay_check_session_storage, on},
%% server_error_filter - drop server error responses.
%% Values:
%% first - drop server error only if it's 1st server packet
%% on - drop all server error packets
%% off - don't drop server errors
%% Default: off
{replay_check_server_error_filter, first},
%% Options for `mtp_session_storage` replay attack check
%% Those settings are not precise! They are checked not in realtime, but
...
...
test/single_dc_SUITE.erl
View file @
24ef8277
...
...
@@ -13,7 +13,8 @@
downstream_size_backpressure_case
/
1
,
downstream_qlen_backpressure_case
/
1
,
config_change_case
/
1
,
replay_attack_case
/
1
replay_attack_case
/
1
,
replay_attack_server_error_case
/
1
]).
-
export
([
set_env
/
2
,
...
...
@@ -346,6 +347,32 @@ replay_attack_case(Cfg) when is_list(Cfg) ->
?
assertEqual
(
1
,
ErrCount
()),
?
assertEqual
({
error
,
closed
},
mtp_test_client
:
recv_packet
(
Cli2
,
1000
)).
%% @doc test replay attack protection.
%% Server error responses are not proxied
replay_attack_server_error_case
({
pre
,
Cfg
})
->
setup_single
(
?
FUNCTION_NAME
,
10000
+
?
LINE
,
#
{},
Cfg
);
replay_attack_server_error_case
({
post
,
Cfg
})
->
stop_single
(
Cfg
);
replay_attack_server_error_case
(
Cfg
)
when
is_list
(
Cfg
)
->
DcId
=
?
config
(
dc_id
,
Cfg
),
Host
=
?
config
(
mtp_host
,
Cfg
),
Port
=
?
config
(
mtp_port
,
Cfg
),
Secret
=
?
config
(
mtp_secret
,
Cfg
),
ErrCount
=
fun
()
->
mtp_test_metric
:
get_tags
(
count
,
[
?
APP
,
protocol_error
,
total
],
[
srv_error_filtered
])
end
,
?
assertEqual
(
not_found
,
ErrCount
()),
Cli1
=
mtp_test_client
:
connect
(
Host
,
Port
,
Secret
,
DcId
,
mtp_secure
),
%% Let TG server echo error packet back, but packet will be filtered
_
Cli2
=
mtp_test_client
:
send
(
<<
108
,
254
,
255
,
255
>>
,
Cli1
),
?
assertEqual
(
ok
,
mtp_test_metric
:
wait_for_value
(
count
,
[
?
APP
,
protocol_error
,
total
],
[
srv_error_filtered
],
1
,
5000
),
{
mtp_session_storage
:
status
(),
sys
:
get_state
(
mtp_test_metric
)}),
?
assertEqual
(
1
,
ErrCount
()).
%% TODO: send a lot, not read, and then close - assert connection IDs are cleaned up
%% Helpers
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment