fixed #1053 - prevent repeated consumption of the ticket uses within the same SSH session

This commit is contained in:
Eugene 2024-09-27 18:12:48 +02:00
parent 9b599ed1e2
commit 1f597a88a5
No known key found for this signature in database
GPG key ID: 5896FCBBDD1CF4F4

View file

@ -65,6 +65,11 @@ enum KeyboardInteractiveState {
WebAuthRequested(broadcast::Receiver<AuthResult>), WebAuthRequested(broadcast::Receiver<AuthResult>),
} }
struct CachedSuccessfulTicketAuth {
ticket: Secret<String>,
username: String,
}
pub struct ServerSession { pub struct ServerSession {
pub id: SessionId, pub id: SessionId,
username: Option<String>, username: Option<String>,
@ -90,6 +95,7 @@ pub struct ServerSession {
channel_writer: ChannelWriter, channel_writer: ChannelWriter,
auth_state: Option<Arc<Mutex<AuthState>>>, auth_state: Option<Arc<Mutex<AuthState>>>,
keyboard_interactive_state: KeyboardInteractiveState, keyboard_interactive_state: KeyboardInteractiveState,
cached_successful_ticket_auth: Option<CachedSuccessfulTicketAuth>,
} }
fn session_debug_tag(id: &SessionId, remote_address: &SocketAddr) -> String { fn session_debug_tag(id: &SessionId, remote_address: &SocketAddr) -> String {
@ -147,6 +153,7 @@ impl ServerSession {
channel_writer: ChannelWriter::new(), channel_writer: ChannelWriter::new(),
auth_state: None, auth_state: None,
keyboard_interactive_state: KeyboardInteractiveState::None, keyboard_interactive_state: KeyboardInteractiveState::None,
cached_successful_ticket_auth: None,
}; };
let mut so_rx = this.service_output.subscribe(); let mut so_rx = this.service_output.subscribe();
@ -1216,7 +1223,7 @@ impl ServerSession {
} }
let selector: AuthSelector = ssh_username.expose_secret().into(); let selector: AuthSelector = ssh_username.expose_secret().into();
match self.try_auth(&selector, None).await { match self.try_auth_lazy(&selector, None).await {
Ok(AuthResult::Need(kinds)) => russh::server::Auth::Reject { Ok(AuthResult::Need(kinds)) => russh::server::Auth::Reject {
proceed_with_methods: Some(self.get_remaining_auth_methods(kinds)), proceed_with_methods: Some(self.get_remaining_auth_methods(kinds)),
}, },
@ -1244,7 +1251,7 @@ impl ServerSession {
let mut result = Ok(AuthResult::Rejected); let mut result = Ok(AuthResult::Rejected);
for key in keys { for key in keys {
result = self result = self
.try_auth( .try_auth_lazy(
&selector, &selector,
Some(AuthCredential::PublicKey { Some(AuthCredential::PublicKey {
kind: key.name().to_string(), kind: key.name().to_string(),
@ -1283,7 +1290,7 @@ impl ServerSession {
info!("Password auth as {:?}", selector); info!("Password auth as {:?}", selector);
match self match self
.try_auth(&selector, Some(AuthCredential::Password(password))) .try_auth_lazy(&selector, Some(AuthCredential::Password(password)))
.await .await
{ {
Ok(AuthResult::Accepted { .. }) => russh::server::Auth::Accept, Ok(AuthResult::Accepted { .. }) => russh::server::Auth::Accept,
@ -1327,7 +1334,7 @@ impl ServerSession {
self.keyboard_interactive_state = KeyboardInteractiveState::None; self.keyboard_interactive_state = KeyboardInteractiveState::None;
match self.try_auth(&selector, cred).await { match self.try_auth_lazy(&selector, cred).await {
Ok(AuthResult::Accepted { .. }) => russh::server::Auth::Accept, Ok(AuthResult::Accepted { .. }) => russh::server::Auth::Accept,
Ok(AuthResult::Rejected) => russh::server::Auth::Reject { Ok(AuthResult::Rejected) => russh::server::Auth::Reject {
proceed_with_methods: None, proceed_with_methods: None,
@ -1448,7 +1455,38 @@ impl ServerSession {
} }
} }
async fn try_auth( /// As try_auth_lazy is called multiple times, this memoization prevents
/// consuming the ticket multiple times, depleting its uses.
async fn try_auth_lazy(
&mut self,
selector: &AuthSelector,
credential: Option<AuthCredential>,
) -> Result<AuthResult> {
if let AuthSelector::Ticket { secret } = selector {
if let Some(ref csta) = self.cached_successful_ticket_auth {
// Only if the client hasn't maliciously changed the username
// between auth attempts
if &csta.ticket == secret {
return Ok(AuthResult::Accepted {
username: csta.username.clone(),
});
}
}
let result = self.try_auth_eager(selector, credential).await?;
if let AuthResult::Accepted { ref username } = result {
self.cached_successful_ticket_auth = Some(CachedSuccessfulTicketAuth {
ticket: secret.clone(),
username: username.clone(),
});
}
return Ok(result);
}
self.try_auth_eager(selector, credential).await
}
async fn try_auth_eager(
&mut self, &mut self,
selector: &AuthSelector, selector: &AuthSelector,
credential: Option<AuthCredential>, credential: Option<AuthCredential>,