PR #24384 opened by michaelni URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24384 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24384.patch
The sftp protocol connected and authenticated without checking the server host key, so credentials were sent to any or spoofed server. Verify against known_hosts (ssh_session_is_known_server, with a pre-0.8 fallback) before authenticating; add a 'verify' option (default on) to opt out. This changes the default behaviour: like OpenSSH with StrictHostKeyChecking=yes, sftp:// now refuses servers that are not in the known_hosts file or whose key differs from the recorded one. Fixes: credentials sent to unverified SSH servers (CWE-295) Fixes: ZmW0mOvhU0qN Found-by: Joshua Rogers <[email protected]> Signed-off-by: Michael Niedermayer <[email protected]> >From 7a4eed3e6b48d7e5d31df42fd2a82654460506e7 Mon Sep 17 00:00:00 2001 From: Joshua Rogers <[email protected]> Date: Sun, 6 Sep 2026 14:54:02 +0200 Subject: [PATCH] avformat/libssh: verify the SSH server host key The sftp protocol connected and authenticated without checking the server host key, so credentials were sent to any or spoofed server. Verify against known_hosts (ssh_session_is_known_server, with a pre-0.8 fallback) before authenticating; add a 'verify' option (default on) to opt out. This changes the default behaviour: like OpenSSH with StrictHostKeyChecking=yes, sftp:// now refuses servers that are not in the known_hosts file or whose key differs from the recorded one. Fixes: credentials sent to unverified SSH servers (CWE-295) Fixes: ZmW0mOvhU0qN Found-by: Joshua Rogers <[email protected]> Signed-off-by: Michael Niedermayer <[email protected]> --- doc/protocols.texi | 5 +++++ libavformat/libssh.c | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/doc/protocols.texi b/doc/protocols.texi index 4a82dae2ee..2dad4aaed6 100644 --- a/doc/protocols.texi +++ b/doc/protocols.texi @@ -1239,6 +1239,11 @@ truncating. Default value is 1. Specify the path of the file containing private key to use during authorization. By default libssh searches for keys in the @file{~/.ssh/} directory. +@item verify +Verify the server host key against the known_hosts file before +authenticating, if set to 1. Servers that are not in the file, or whose +key differs from the one recorded there, are refused. Default value is 1. + @end table Example: Play a file stored on remote server. diff --git a/libavformat/libssh.c b/libavformat/libssh.c index f71b89e438..886726b2e2 100644 --- a/libavformat/libssh.c +++ b/libavformat/libssh.c @@ -40,8 +40,42 @@ typedef struct { int rw_timeout; int trunc; char *priv_key; + int verify; } LIBSSHContext; +#if LIBSSH_VERSION_INT < SSH_VERSION_INT(0, 8, 0) +#define ssh_session_is_known_server ssh_is_server_known +#define SSH_KNOWN_HOSTS_OK SSH_SERVER_KNOWN_OK +#define SSH_KNOWN_HOSTS_CHANGED SSH_SERVER_KNOWN_CHANGED +#define SSH_KNOWN_HOSTS_OTHER SSH_SERVER_FOUND_OTHER +#define SSH_KNOWN_HOSTS_UNKNOWN SSH_SERVER_NOT_KNOWN +#define SSH_KNOWN_HOSTS_NOT_FOUND SSH_SERVER_FILE_NOT_FOUND +#endif + +static av_cold int libssh_verify_hostkey(LIBSSHContext *libssh) +{ + switch (ssh_session_is_known_server(libssh->session)) { + case SSH_KNOWN_HOSTS_OK: + return 0; + case SSH_KNOWN_HOSTS_CHANGED: + av_log(libssh, AV_LOG_ERROR, "Host key for server has changed; " + "possible man-in-the-middle attack, refusing to connect.\n"); + return AVERROR(EACCES); + case SSH_KNOWN_HOSTS_OTHER: + av_log(libssh, AV_LOG_ERROR, "Host key of a type different from the one " + "in the known_hosts file; refusing to connect.\n"); + return AVERROR(EACCES); + case SSH_KNOWN_HOSTS_UNKNOWN: + case SSH_KNOWN_HOSTS_NOT_FOUND: + av_log(libssh, AV_LOG_ERROR, "Server is not a known host; refusing to connect.\n"); + return AVERROR(EACCES); + default: + av_log(libssh, AV_LOG_ERROR, "Failed to check the server host key: %s\n", + ssh_get_error(libssh->session)); + return AVERROR(EIO); + } +} + static av_cold int libssh_create_ssh_session(LIBSSHContext *libssh, const char* hostname, unsigned int port) { static const int verbosity = SSH_LOG_NOLOG; @@ -67,6 +101,12 @@ static av_cold int libssh_create_ssh_session(LIBSSHContext *libssh, const char* return AVERROR(EIO); } + if (libssh->verify) { + int ret = libssh_verify_hostkey(libssh); + if (ret < 0) + return ret; + } + return 0; } @@ -477,6 +517,7 @@ static const AVOption options[] = { {"timeout", "set timeout of socket I/O operations", OFFSET(rw_timeout), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, D|E }, {"truncate", "Truncate existing files on write", OFFSET(trunc), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 1, E }, {"private_key", "set path to private key", OFFSET(priv_key), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D|E }, + {"verify", "verify the server host key against the known_hosts file", OFFSET(verify), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, D|E }, {NULL} }; -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
