Skip to content

Set a maximum packet size of 32768 bytes for version and cleartext packets #49

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
21 changes: 19 additions & 2 deletions Sources/NIOSSH/SSHPacketParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ struct SSHPacketParser {

private var buffer: ByteBuffer
private var state: State
private let maximumPacketSize = 32768

/// Testing only: the number of bytes we can discard from this buffer.
internal var _discardableBytes: Int {
Expand Down Expand Up @@ -71,6 +72,10 @@ struct SSHPacketParser {
return nil
case .cleartextWaitingForLength:
if let length = self.buffer.getInteger(at: self.buffer.readerIndex, as: UInt32.self) {
if length >= self.maximumPacketSize {
throw NIOSSHError.invalidEncryptedPacketLength
}

if let message = try self.parsePlaintext(length: length) {
self.state = .cleartextWaitingForLength
return message
Expand Down Expand Up @@ -114,7 +119,13 @@ struct SSHPacketParser {
private mutating func readVersion() throws -> String? {
// Looking for a string ending with \r\n
let slice = self.buffer.readableBytesView
if let cr = slice.firstIndex(of: 13), cr.advanced(by: 1) < slice.endIndex, slice[cr.advanced(by: 1)] == 10 {

// Prevent the consumed bytes for a version from exceeding the maximum packet size
// In practice, if SSH version packets come anywhere near this it's already likely an attack
// More data cannot be blindly regarded as malicious though, since these might be multiple packets
let maximumVersionSize = min(slice.endIndex, self.maximumPacketSize)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ByteBufferView is not necessarily zero-indexed, so self.maximumPacketSize has to be translated into index-space first. I think this should be let maxIndex = slice.index(slice.startIndex, offsetBy: min(slice.count, self.maximumPacketSize)).


if let cr = slice.firstIndex(of: 13), cr.advanced(by: 1) < maximumVersionSize, slice[cr.advanced(by: 1)] == 10 {
let version = String(decoding: slice[slice.startIndex ..< cr], as: UTF8.self)
// read \r\n
self.buffer.moveReaderIndex(forwardBy: slice.startIndex.distance(to: cr).advanced(by: 2))
Expand All @@ -132,7 +143,13 @@ struct SSHPacketParser {
try protection.decryptFirstBlock(&self.buffer)

// This force unwrap is safe because we must have a block size, and a block size is always going to be more than 4 bytes.
return self.buffer.getInteger(at: self.buffer.readerIndex)! + UInt32(protection.macBytes)
let packetLength = self.buffer.getInteger(at: self.buffer.readerIndex, as: UInt32.self)!

if packetLength >= self.maximumPacketSize {
throw NIOSSHError.invalidEncryptedPacketLength
}

return packetLength + UInt32(protection.macBytes)
}

private mutating func parsePlaintext(length: UInt32) throws -> SSHMessage? {
Expand Down