fixed GHSA-3cjp-w4cp-m9c8 - interpreting SSH public key offers as a successful authentication

This commit is contained in:
Eugene 2023-09-27 21:14:41 +02:00
parent 1cdd29b6b8
commit a4df7f7a21
No known key found for this signature in database
GPG key ID: 5896FCBBDD1CF4F4
4 changed files with 122 additions and 36 deletions

45
Cargo.lock generated
View file

@ -1473,9 +1473,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]] [[package]]
name = "form_urlencoded" name = "form_urlencoded"
version = "1.1.0" version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
dependencies = [ dependencies = [
"percent-encoding", "percent-encoding",
] ]
@ -2057,9 +2057,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]] [[package]]
name = "idna" name = "idna"
version = "0.3.0" version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
dependencies = [ dependencies = [
"unicode-bidi", "unicode-bidi",
"unicode-normalization", "unicode-normalization",
@ -2978,9 +2978,9 @@ dependencies = [
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.2.0" version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
[[package]] [[package]]
name = "phf" name = "phf"
@ -3625,9 +3625,9 @@ dependencies = [
[[package]] [[package]]
name = "russh" name = "russh"
version = "0.38.0" version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae0efcc0f4cd6c062c07e572ce4b806e3967fa029fcbfcc0aa98fb5910a37925" checksum = "7878311587d0353a854d5be954fbe68bdf6e77873933b484d1e45db12bb2f8cf"
dependencies = [ dependencies = [
"aes", "aes",
"aes-gcm", "aes-gcm",
@ -5162,9 +5162,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]] [[package]]
name = "url" name = "url"
version = "2.3.1" version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
dependencies = [ dependencies = [
"form_urlencoded", "form_urlencoded",
"idna", "idna",
@ -5256,7 +5256,7 @@ dependencies = [
[[package]] [[package]]
name = "warpgate" name = "warpgate"
version = "0.7.4" version = "0.8.0"
dependencies = [ dependencies = [
"ansi_term", "ansi_term",
"anyhow", "anyhow",
@ -5292,7 +5292,7 @@ dependencies = [
[[package]] [[package]]
name = "warpgate-admin" name = "warpgate-admin"
version = "0.7.4" version = "0.8.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -5321,7 +5321,7 @@ dependencies = [
[[package]] [[package]]
name = "warpgate-common" name = "warpgate-common"
version = "0.7.4" version = "0.8.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"argon2", "argon2",
@ -5357,7 +5357,7 @@ dependencies = [
[[package]] [[package]]
name = "warpgate-core" name = "warpgate-core"
version = "0.7.4" version = "0.8.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"argon2", "argon2",
@ -5397,7 +5397,7 @@ dependencies = [
[[package]] [[package]]
name = "warpgate-database-protocols" name = "warpgate-database-protocols"
version = "0.7.4" version = "0.8.0"
dependencies = [ dependencies = [
"bitflags 1.3.2", "bitflags 1.3.2",
"bytes", "bytes",
@ -5410,7 +5410,7 @@ dependencies = [
[[package]] [[package]]
name = "warpgate-db-entities" name = "warpgate-db-entities"
version = "0.7.4" version = "0.8.0"
dependencies = [ dependencies = [
"chrono", "chrono",
"poem-openapi", "poem-openapi",
@ -5423,7 +5423,7 @@ dependencies = [
[[package]] [[package]]
name = "warpgate-db-migrations" name = "warpgate-db-migrations"
version = "0.7.4" version = "0.8.0"
dependencies = [ dependencies = [
"async-std", "async-std",
"chrono", "chrono",
@ -5435,7 +5435,7 @@ dependencies = [
[[package]] [[package]]
name = "warpgate-protocol-http" name = "warpgate-protocol-http"
version = "0.7.4" version = "0.8.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -5456,6 +5456,7 @@ dependencies = [
"tokio", "tokio",
"tokio-tungstenite 0.17.2", "tokio-tungstenite 0.17.2",
"tracing", "tracing",
"url",
"uuid", "uuid",
"warpgate-admin", "warpgate-admin",
"warpgate-common", "warpgate-common",
@ -5467,7 +5468,7 @@ dependencies = [
[[package]] [[package]]
name = "warpgate-protocol-mysql" name = "warpgate-protocol-mysql"
version = "0.7.4" version = "0.8.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -5494,7 +5495,7 @@ dependencies = [
[[package]] [[package]]
name = "warpgate-protocol-ssh" name = "warpgate-protocol-ssh"
version = "0.7.4" version = "0.8.0"
dependencies = [ dependencies = [
"ansi_term", "ansi_term",
"anyhow", "anyhow",
@ -5519,7 +5520,7 @@ dependencies = [
[[package]] [[package]]
name = "warpgate-sso" name = "warpgate-sso"
version = "0.7.4" version = "0.8.0"
dependencies = [ dependencies = [
"bytes", "bytes",
"data-encoding", "data-encoding",
@ -5535,7 +5536,7 @@ dependencies = [
[[package]] [[package]]
name = "warpgate-web" name = "warpgate-web"
version = "0.7.4" version = "0.8.0"
dependencies = [ dependencies = [
"rust-embed", "rust-embed",
"serde", "serde",

View file

@ -12,7 +12,7 @@ bimap = "0.6"
bytes = "1.3" bytes = "1.3"
dialoguer = "0.10" dialoguer = "0.10"
futures = "0.3" futures = "0.3"
russh = { version = "0.38.0", features = ["vendored-openssl"] } russh = { version = "0.39.0", features = ["vendored-openssl"] }
# russh = { version = "0.35.0-beta.6", features = ["vendored-openssl"], path = "../../russh/russh"} # russh = { version = "0.35.0-beta.6", features = ["vendored-openssl"], path = "../../russh/russh"}
russh-keys = { version = "0.38.0", features = ["vendored-openssl"] } russh-keys = { version = "0.38.0", features = ["vendored-openssl"] }
# russh-keys = { version = "0.23.0-beta.1", features = ["vendored-openssl"], path = "../../russh/russh-keys" } # russh-keys = { version = "0.23.0-beta.1", features = ["vendored-openssl"], path = "../../russh/russh-keys" }

View file

@ -29,6 +29,7 @@ pub enum ServerHandlerEvent {
PtyRequest(ServerChannelId, PtyRequest, oneshot::Sender<()>), PtyRequest(ServerChannelId, PtyRequest, oneshot::Sender<()>),
ShellRequest(ServerChannelId, oneshot::Sender<bool>), ShellRequest(ServerChannelId, oneshot::Sender<bool>),
AuthPublicKey(Secret<String>, PublicKey, oneshot::Sender<Auth>), AuthPublicKey(Secret<String>, PublicKey, oneshot::Sender<Auth>),
AuthPublicKeyOffer(Secret<String>, PublicKey, oneshot::Sender<bool>),
AuthPassword(Secret<String>, Secret<String>, oneshot::Sender<Auth>), AuthPassword(Secret<String>, Secret<String>, oneshot::Sender<Auth>),
AuthKeyboardInteractive( AuthKeyboardInteractive(
Secret<String>, Secret<String>,
@ -178,6 +179,33 @@ impl russh::server::Handler for ServerHandler {
Ok((self, session)) Ok((self, session))
} }
async fn auth_publickey_offered(
self,
user: &str,
key: &russh_keys::key::PublicKey,
) -> Result<(Self, Auth), Self::Error> {
let user = Secret::new(user.to_string());
let (tx, rx) = oneshot::channel();
self.send_event(ServerHandlerEvent::AuthPublicKeyOffer(
user,
key.clone(),
tx,
))?;
let result = rx.await.unwrap_or(false);
Ok((
self,
if result {
Auth::Accept
} else {
Auth::Reject {
proceed_with_methods: None,
}
},
))
}
async fn auth_publickey( async fn auth_publickey(
self, self,
user: &str, user: &str,

View file

@ -466,6 +466,10 @@ impl ServerSession {
let _ = reply.send(self._auth_publickey(username, key).await); let _ = reply.send(self._auth_publickey(username, key).await);
} }
ServerHandlerEvent::AuthPublicKeyOffer(username, key, reply) => {
let _ = reply.send(self._auth_publickey_offer(username, key).await);
}
ServerHandlerEvent::AuthPassword(username, password, reply) => { ServerHandlerEvent::AuthPassword(username, password, reply) => {
let _ = reply.send(self._auth_password(username, password).await); let _ = reply.send(self._auth_password(username, password).await);
} }
@ -1149,19 +1153,7 @@ impl ServerSession {
.map_err(anyhow::Error::from) .map_err(anyhow::Error::from)
} }
async fn _auth_publickey( fn _get_public_keys_from_of(&self, key: PublicKey) -> Vec<PublicKey> {
&mut self,
ssh_username: Secret<String>,
key: PublicKey,
) -> russh::server::Auth {
let selector: AuthSelector = ssh_username.expose_secret().into();
info!(
"Public key auth as {:?} with key {}",
selector,
key.public_key_base64()
);
let mut keys = vec![key.clone()]; let mut keys = vec![key.clone()];
// Try all supported hash algorithms // Try all supported hash algorithms
if let PublicKey::RSA { key, hash } = &key { if let PublicKey::RSA { key, hash } = &key {
@ -1178,6 +1170,48 @@ impl ServerSession {
} }
} }
} }
keys
}
async fn _auth_publickey_offer(
&mut self,
ssh_username: Secret<String>,
key: PublicKey,
) -> bool {
let keys = self._get_public_keys_from_of(key);
let selector: AuthSelector = ssh_username.expose_secret().into();
for key in keys {
if let Ok(true) = self
.try_validate_public_key_offer(
&selector,
Some(AuthCredential::PublicKey {
kind: key.name().to_string(),
public_key_bytes: Bytes::from(key.public_key_bytes()),
}),
)
.await
{
return true;
}
}
false
}
async fn _auth_publickey(
&mut self,
ssh_username: Secret<String>,
key: PublicKey,
) -> russh::server::Auth {
let selector: AuthSelector = ssh_username.expose_secret().into();
info!(
"Public key auth as {:?} with key {}",
selector,
key.public_key_base64()
);
let keys = self._get_public_keys_from_of(key);
let mut result = Ok(AuthResult::Rejected); let mut result = Ok(AuthResult::Rejected);
for key in keys { for key in keys {
@ -1361,6 +1395,29 @@ impl ServerSession {
m m
} }
async fn try_validate_public_key_offer(
&mut self,
selector: &AuthSelector,
credential: Option<AuthCredential>,
) -> Result<bool> {
match selector {
AuthSelector::User { username, .. } => {
let cp = self.services.config_provider.clone();
if let Some(credential) = credential {
return Ok(cp
.lock()
.await
.validate_credential(username, &credential)
.await?);
}
Ok(false)
}
_ => Ok(false),
}
}
async fn try_auth( async fn try_auth(
&mut self, &mut self,
selector: &AuthSelector, selector: &AuthSelector,