Skip to content

Commit

Permalink
Extendable replay attack protector
Browse files Browse the repository at this point in the history
- Disable replay attack detecting by default
  • Loading branch information
zonyitoo committed Sep 28, 2021
1 parent 1a3aac0 commit 9535f30
Show file tree
Hide file tree
Showing 21 changed files with 376 additions and 164 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ stream-cipher = ["shadowsocks-service/stream-cipher"]
# WARN: These non-standard AEAD ciphers are not officially supported by shadowsocks community
aead-cipher-extra = ["shadowsocks-service/aead-cipher-extra"]

# Enable detection against replay attack
replay-attack-detect = ["shadowsocks-service/replay-attack-detect"]

[dependencies]
log = "0.4"
log4rs = { version = "1.0", optional = true }
Expand Down
3 changes: 3 additions & 0 deletions crates/shadowsocks-service/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ stream-cipher = ["shadowsocks/stream-cipher"]
# WARN: These non-standard AEAD ciphers are not officially supported by shadowsocks community
aead-cipher-extra = ["shadowsocks/aead-cipher-extra"]

# Enable detection against replay attack
replay-attack-detect = ["shadowsocks/replay-attack-detect"]

[dependencies]
log = "0.4"
log4rs = "1.0"
Expand Down
56 changes: 55 additions & 1 deletion crates/shadowsocks-service/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ use serde::{Deserialize, Serialize};
#[cfg(any(feature = "local-tunnel", feature = "local-dns"))]
use shadowsocks::relay::socks5::Address;
use shadowsocks::{
config::{ManagerAddr, Mode, ServerAddr, ServerConfig, ServerWeight},
config::{ManagerAddr, Mode, ReplayAttackPolicy, ServerAddr, ServerConfig, ServerWeight},
crypto::v1::CipherKind,
plugin::PluginConfig,
};
Expand All @@ -83,6 +83,18 @@ enum SSDnsConfig {
TrustDns(ResolverConfig),
}

#[derive(Serialize, Deserialize, Debug, Default)]
struct SSSecurityConfig {
#[serde(skip_serializing_if = "Option::is_none")]
replay_attack: Option<SSSecurityReplayAttackConfig>,
}

#[derive(Serialize, Deserialize, Debug, Default)]
struct SSSecurityReplayAttackConfig {
#[serde(skip_serializing_if = "Option::is_none")]
policy: Option<String>,
}

#[derive(Serialize, Deserialize, Debug, Default)]
struct SSConfig {
#[serde(skip_serializing_if = "Option::is_none")]
Expand Down Expand Up @@ -134,6 +146,8 @@ struct SSConfig {
ipv6_first: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
fast_open: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
security: Option<SSSecurityConfig>,
}

#[derive(Serialize, Deserialize, Debug, Default)]
Expand Down Expand Up @@ -812,6 +826,17 @@ impl Default for DnsConfig {
}
}

/// Security Config
#[derive(Clone, Debug, Default)]
pub struct SecurityConfig {
pub replay_attack: SecurityReplayAttackConfig,
}

#[derive(Clone, Debug, Default)]
pub struct SecurityReplayAttackConfig {
pub policy: ReplayAttackPolicy,
}

/// Configuration
#[derive(Clone, Debug)]
pub struct Config {
Expand Down Expand Up @@ -888,6 +913,9 @@ pub struct Config {
/// Flow statistic report Unix socket path (only for Android)
#[cfg(feature = "local-flow-stat")]
pub stat_path: Option<PathBuf>,

/// Replay attack policy
pub security: SecurityConfig,
}

/// Configuration parsing error kind
Expand Down Expand Up @@ -990,6 +1018,8 @@ impl Config {

#[cfg(feature = "local-flow-stat")]
stat_path: None,

security: SecurityConfig::default(),
}
}

Expand Down Expand Up @@ -1505,6 +1535,21 @@ impl Config {
nconfig.ipv6_first = f;
}

// Security
if let Some(sec) = config.security {
if let Some(replay_attack) = sec.replay_attack {
if let Some(policy) = replay_attack.policy {
match policy.parse::<ReplayAttackPolicy>() {
Ok(p) => nconfig.security.replay_attack.policy = p,
Err(..) => {
let err = Error::new(ErrorKind::Invalid, "invalid replay attack policy", None);
return Err(err);
}
}
}
}
}

Ok(nconfig)
}

Expand Down Expand Up @@ -2027,6 +2072,15 @@ impl fmt::Display for Config {
jconf.ipv6_first = Some(self.ipv6_first);
}

// Security
if self.security.replay_attack.policy != ReplayAttackPolicy::default() {
jconf.security = Some(SSSecurityConfig {
replay_attack: Some(SSSecurityReplayAttackConfig {
policy: Some(self.security.replay_attack.policy.to_string()),
}),
});
}

write!(f, "{}", json5::to_string(&jconf).unwrap())
}
}
8 changes: 7 additions & 1 deletion crates/shadowsocks-service/src/local/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use shadowsocks::{
#[cfg(feature = "local-dns")]
use tokio::sync::Mutex;

use crate::{acl::AccessControl, net::FlowStat};
use crate::{acl::AccessControl, config::SecurityConfig, net::FlowStat};

/// Local Service Context
pub struct ServiceContext {
Expand Down Expand Up @@ -173,4 +173,10 @@ impl ServiceContext {
let context = Arc::get_mut(&mut self.context).expect("cannot set ipv6_first on a shared context");
context.set_ipv6_first(ipv6_first);
}

/// Set security config
pub fn set_security_config(&mut self, security: &SecurityConfig) {
let context = Arc::get_mut(&mut self.context).expect("cannot set security on a shared context");
context.set_replay_attack_policy(security.replay_attack.policy);
}
}
2 changes: 2 additions & 0 deletions crates/shadowsocks-service/src/local/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ pub async fn run(mut config: Config) -> io::Result<()> {
context.set_acl(acl);
}

context.set_security_config(&config.security);

assert!(!config.local.is_empty(), "no valid local server configuration");

let context = Arc::new(context);
Expand Down
11 changes: 10 additions & 1 deletion crates/shadowsocks-service/src/manager/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use tokio::{sync::Mutex, task::JoinHandle};

use crate::{
acl::AccessControl,
config::{ManagerConfig, ManagerServerHost},
config::{ManagerConfig, ManagerServerHost, SecurityConfig},
net::FlowStat,
server::Server,
};
Expand Down Expand Up @@ -57,6 +57,7 @@ pub struct Manager {
udp_capacity: Option<usize>,
acl: Option<Arc<AccessControl>>,
ipv6_first: bool,
security: SecurityConfig,
}

impl Manager {
Expand All @@ -77,6 +78,7 @@ impl Manager {
udp_capacity: None,
acl: None,
ipv6_first: false,
security: SecurityConfig::default(),
}
}

Expand Down Expand Up @@ -121,6 +123,11 @@ impl Manager {
self.ipv6_first = ipv6_first;
}

/// Set security config
pub fn set_security_config(&mut self, security: SecurityConfig) {
self.security = security;
}

/// Start serving
pub async fn run(self) -> io::Result<()> {
let mut listener = ManagerListener::bind(&self.context, &self.svr_cfg.addr).await?;
Expand Down Expand Up @@ -194,6 +201,8 @@ impl Manager {
server.set_ipv6_first(self.ipv6_first);
}

server.set_security_config(&self.security);

let server_port = server.config().addr().port();

let mut servers = self.servers.lock().await;
Expand Down
8 changes: 7 additions & 1 deletion crates/shadowsocks-service/src/server/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use shadowsocks::{
relay::Address,
};

use crate::{acl::AccessControl, net::FlowStat};
use crate::{acl::AccessControl, config::SecurityConfig, net::FlowStat};

/// Server Service Context
pub struct ServiceContext {
Expand Down Expand Up @@ -105,4 +105,10 @@ impl ServiceContext {
let context = Arc::get_mut(&mut self.context).expect("cannot set ipv6_first on a shared context");
context.set_ipv6_first(ipv6_first);
}

/// Set security config
pub fn set_security_config(&mut self, security: &SecurityConfig) {
let context = Arc::get_mut(&mut self.context).expect("cannot set security on a shared context");
context.set_replay_attack_policy(security.replay_attack.policy);
}
}
2 changes: 2 additions & 0 deletions crates/shadowsocks-service/src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ pub async fn run(config: Config) -> io::Result<()> {
server.set_ipv6_first(config.ipv6_first);
}

server.set_security_config(&config.security);

servers.push(server);
}

Expand Down
8 changes: 7 additions & 1 deletion crates/shadowsocks-service/src/server/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use shadowsocks::{
};
use tokio::time;

use crate::{acl::AccessControl, net::FlowStat};
use crate::{acl::AccessControl, config::SecurityConfig, net::FlowStat};

use super::{context::ServiceContext, tcprelay::TcpServer, udprelay::UdpServer};

Expand Down Expand Up @@ -109,6 +109,12 @@ impl Server {
context.set_ipv6_first(ipv6_first);
}

/// Set security config
pub fn set_security_config(&mut self, security: &SecurityConfig) {
let context = Arc::get_mut(&mut self.context).expect("cannot set security on a shared context");
context.set_security_config(security)
}

/// Start serving
pub async fn run(mut self) -> io::Result<()> {
let vfut = FuturesUnordered::new();
Expand Down
7 changes: 5 additions & 2 deletions crates/shadowsocks/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ stream-cipher = ["shadowsocks-crypto/v1-stream"]
# WARN: These non-standard AEAD ciphers are not officially supported by shadowsocks community
aead-cipher-extra = ["shadowsocks-crypto/v1-aead-extra"]

# Enable detection against replay attack
replay-attack-detect = ["bloomfilter", "spin"]

[dependencies]
log = "0.4"

Expand All @@ -41,9 +44,9 @@ byte_string = "1.0"
base64 = "0.13"
url = "2.2"
once_cell = "1.8"
spin = { version = "0.9", features = ["std"] }
spin = { version = "0.9", features = ["std"], optional = true }
pin-project = "1.0"
bloomfilter = "1.0.8"
bloomfilter = { version = "1.0.8", optional = true }
thiserror = "1.0"

serde = { version = "1.0", features = ["derive"] }
Expand Down
44 changes: 44 additions & 0 deletions crates/shadowsocks/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -689,3 +689,47 @@ impl From<PathBuf> for ManagerAddr {
ManagerAddr::UnixSocketAddr(p)
}
}

/// Policy for handling replay attack requests
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum ReplayAttackPolicy {
/// Ignore it completely
Ignore,
/// Try to detect replay attack and warn about it
Detect,
/// Try to detect replay attack and reject the request
Reject,
}

impl Default for ReplayAttackPolicy {
fn default() -> ReplayAttackPolicy {
ReplayAttackPolicy::Ignore
}
}

impl Display for ReplayAttackPolicy {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ReplayAttackPolicy::Ignore => f.write_str("ignore"),
ReplayAttackPolicy::Detect => f.write_str("detect"),
ReplayAttackPolicy::Reject => f.write_str("reject"),
}
}
}

/// Error while parsing ReplayAttackPolicy from string
#[derive(Debug, Clone, Copy)]
pub struct ReplayAttackPolicyError;

impl FromStr for ReplayAttackPolicy {
type Err = ReplayAttackPolicyError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"ignore" => Ok(ReplayAttackPolicy::Ignore),
"detect" => Ok(ReplayAttackPolicy::Detect),
"reject" => Ok(ReplayAttackPolicy::Reject),
_ => Err(ReplayAttackPolicyError),
}
}
}
Loading

0 comments on commit 9535f30

Please sign in to comment.