From e2cb3acdaa3b2870dc7c234785d68366497f7bcc Mon Sep 17 00:00:00 2001 From: Christian Kandeler <christian.kandeler@digia.com> Date: Wed, 26 Sep 2012 10:57:19 +0200 Subject: [PATCH] SSH: Fix parsing of server id. The current implementation is a bit sloppy, so one can construct certain invalid strings that will be accepted as well as certain valid ones that will be rejected. The new checks are much more precise. Most importantly in practice, the version string "1.99" sent by some older servers is now accepted, as required by the RFC. Change-Id: Ib35c27d3a3bc4aea259b9a0dad66817435d95c0a Reviewed-by: Tobias Hunger <tobias.hunger@digia.com> --- src/libs/ssh/sshconnection.cpp | 78 ++++++++++++++++++++++++++-------- src/libs/ssh/sshconnection_p.h | 1 + 2 files changed, 61 insertions(+), 18 deletions(-) diff --git a/src/libs/ssh/sshconnection.cpp b/src/libs/ssh/sshconnection.cpp index ebf233d3b6d..7d748d02677 100644 --- a/src/libs/ssh/sshconnection.cpp +++ b/src/libs/ssh/sshconnection.cpp @@ -44,6 +44,7 @@ #include <QMutex> #include <QMutexLocker> #include <QNetworkProxy> +#include <QRegExp> #include <QTcpSocket> /*! @@ -320,7 +321,7 @@ void SshConnectionPrivate::handleIncomingData() qDebug("state = %d, remote data size = %d", m_state, m_incomingData.count()); #endif - if (m_state == SocketConnected) + if (m_serverId.isEmpty()) handleServerId(); handlePackets(); } catch (SshServerException &e) { @@ -335,39 +336,78 @@ void SshConnectionPrivate::handleIncomingData() } } +// RFC 4253, 4.2. void SshConnectionPrivate::handleServerId() { #ifdef CREATOR_SSH_DEBUG qDebug("%s: incoming data size = %d, incoming data = '%s'", Q_FUNC_INFO, m_incomingData.count(), m_incomingData.data()); #endif - const int idOffset = m_incomingData.indexOf("SSH-"); - if (idOffset == -1) + const int newLinePos = m_incomingData.indexOf('\n'); + if (newLinePos == -1) + return; // Not enough data yet. + + // Lines not starting with "SSH-" are ignored. + if (!m_incomingData.startsWith("SSH-")) { + m_incomingData.remove(0, newLinePos + 1); + m_serverHasSentDataBeforeId = true; return; - m_incomingData.remove(0, idOffset); - if (m_incomingData.size() < 7) - return; - const QByteArray &version = m_incomingData.mid(4, 3); - if (version != "2.0") { + } + + if (newLinePos > 255 - 1) { + throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR, + "Identification string too long.", + tr("Server identification string is %1 characters long, but the maximum " + "allowed length is 255.").arg(newLinePos + 1)); + } + + const bool hasCarriageReturn = m_incomingData.at(newLinePos - 1) == '\r'; + m_serverId = m_incomingData.left(newLinePos); + if (hasCarriageReturn) + m_serverId.chop(1); + m_incomingData.remove(0, newLinePos + 1); + + if (m_serverId.contains('\0')) { + throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR, + "Identification string contains illegal NUL character.", + tr("Server identification string contains illegal NUL character.")); + } + + // "printable US-ASCII characters, with the exception of whitespace characters + // and the minus sign" + QString legalString = QLatin1String("[]!\"#$!&'()*+,./0-9:;<=>?@A-Z[\\\\^_`a-z{|}~]+"); + const QRegExp versionIdpattern(QString::fromLatin1("SSH-(%1)-%1(?: .+)?").arg(legalString)); + if (!versionIdpattern.exactMatch(QString::fromLatin1(m_serverId))) { + throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR, + "Identification string is invalid.", + tr("Server Identification string '%1' is invalid.") + .arg(QString::fromLatin1(m_serverId))); + } + const QString serverProtoVersion = versionIdpattern.cap(1); + if (serverProtoVersion != QLatin1String("2.0") && serverProtoVersion != QLatin1String("1.99")) { throw SshServerException(SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED, "Invalid protocol version.", - tr("Invalid protocol version: Expected '2.0', got '%1'.") - .arg(SshPacketParser::asUserString(version))); + tr("Server protocol version is '%1', but needs to be 2.0 or 1.99.") + .arg(serverProtoVersion)); } - const int endOffset = m_incomingData.indexOf("\r\n"); - if (endOffset == -1) - return; - if (m_incomingData.at(7) != '-') { + + // Disable this check to accept older OpenSSH servers that do this wrong. + if (serverProtoVersion == QLatin1String("2.0") && !hasCarriageReturn) { + throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR, + "Identification string is invalid.", + tr("Server identification string is invalid (missing carriage return).")); + } + + if (serverProtoVersion == QLatin1String("1.99") && m_serverHasSentDataBeforeId) { throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR, - "Invalid server id.", tr("Invalid server id '%1'.") - .arg(SshPacketParser::asUserString(m_incomingData))); + "No extra data preceding identification string allowed for 1.99.", + tr("Server reports protocol version 1.99, but sends data " + "before the identification string, which is not allowed.")); } m_keyExchange.reset(new SshKeyExchange(m_sendFacility)); - m_serverId = m_incomingData.left(endOffset); m_keyExchange->sendKexInitPacket(m_serverId); m_keyExchangeState = KexInitSent; - m_incomingData.remove(0, endOffset + 2); } void SshConnectionPrivate::handlePackets() @@ -645,6 +685,8 @@ void SshConnectionPrivate::connectToHost() m_error = SshNoError; m_ignoreNextPacket = false; m_errorString.clear(); + m_serverId.clear(); + m_serverHasSentDataBeforeId = false; try { if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationByKey) diff --git a/src/libs/ssh/sshconnection_p.h b/src/libs/ssh/sshconnection_p.h index bdf0c26c960..797d7364374 100644 --- a/src/libs/ssh/sshconnection_p.h +++ b/src/libs/ssh/sshconnection_p.h @@ -165,6 +165,7 @@ private: SshConnection *m_conn; quint64 m_lastInvalidMsgSeqNr; QByteArray m_serverId; + bool m_serverHasSentDataBeforeId; }; } // namespace Internal -- GitLab