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]

Reply via email to