forked from phpseclib/phpseclib
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
SSH2: make it so phpseclib initiates key re-exchange after 1GB
- Loading branch information
1 parent
2b3d219
commit 7f31a1e
Showing
1 changed file
with
118 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -123,6 +123,10 @@ | |
* Dumps the content real-time to a file | ||
*/ | ||
define('NET_SSH2_LOG_REALTIME_FILE', 4); | ||
/** | ||
* Dumps the message numbers real-time | ||
*/ | ||
define('NET_SSH2_LOG_REALTIME_SIMPLE', 5); | ||
/** | ||
* Make sure that the log never gets larger than this | ||
*/ | ||
|
@@ -1107,6 +1111,46 @@ class Net_SSH2 | |
*/ | ||
var $extra_packets; | ||
|
||
/** | ||
* Bytes Transferred Since Last Key Exchange | ||
* | ||
* Includes outbound and inbound totals | ||
* | ||
* @var int | ||
* @access private | ||
*/ | ||
var $bytesTransferredSinceLastKEX = 0; | ||
|
||
/** | ||
* After how many transferred byte should phpseclib initiate a key re-exchange? | ||
* | ||
* @var int | ||
* @access private | ||
*/ | ||
var $doKeyReexchangeAfterXBytes = 1024 * 1024 * 1024; | ||
|
||
/** | ||
* Has a key re-exchange been initialized? | ||
* | ||
* @var bool | ||
* @access private | ||
*/ | ||
var $keyExchangeInProgress = false; | ||
|
||
/** | ||
* KEX Buffer | ||
* | ||
* If we're in the middle of a key exchange we want to buffer any additional packets we get until | ||
* the key exchange is over | ||
* | ||
* @see self::_get_binary_packet() | ||
* @see self::_key_exchange() | ||
* @see self::exec() | ||
* @var array | ||
* @access private | ||
*/ | ||
var $kex_buffer = array(); | ||
|
||
/** | ||
* Default Constructor. | ||
* | ||
|
@@ -1492,8 +1536,13 @@ function _generate_identifier() | |
*/ | ||
function _key_exchange($kexinit_payload_server = false) | ||
{ | ||
$this->bytesTransferredSinceLastKEX = 0; | ||
|
||
$preferred = $this->preferred; | ||
$send_kex = true; | ||
// for the initial key exchange $send_kex is true (no key re-exchange has been started) | ||
// for phpseclib initiated key exchanges $send_kex is false | ||
$send_kex = !$this->keyExchangeInProgress; | ||
$this->keyExchangeInProgress = true; | ||
|
||
$kex_algorithms = isset($preferred['kex']) ? | ||
$preferred['kex'] : | ||
|
@@ -1579,22 +1628,29 @@ function _key_exchange($kexinit_payload_server = false) | |
0 | ||
); | ||
|
||
if ($kexinit_payload_server === false) { | ||
if ($kexinit_payload_server === false && $send_kex) { | ||
if (!$this->_send_binary_packet($kexinit_payload_client)) { | ||
return false; | ||
} | ||
|
||
$this->extra_packets = 0; | ||
$kexinit_payload_server = $this->_get_binary_packet(); | ||
if ($kexinit_payload_server === false) { | ||
$this->bitmap = 0; | ||
user_error('Connection closed by server'); | ||
return false; | ||
} | ||
while (true) { | ||
$kexinit_payload_server = $this->_get_binary_packet(); | ||
if ($kexinit_payload_server === false) { | ||
$this->bitmap = 0; | ||
user_error('Connection closed by server'); | ||
return false; | ||
} | ||
|
||
if (strlen($kexinit_payload_server)) { | ||
switch (ord($kexinit_payload_server[0])) { | ||
case NET_SSH2_MSG_KEXINIT: | ||
break 2; | ||
case NET_SSH2_MSG_DISCONNECT: | ||
return $this->_handleDisconnect($kexinit_payload_server); | ||
} | ||
} | ||
|
||
if (!strlen($kexinit_payload_server) || ord($kexinit_payload_server[0]) != NET_SSH2_MSG_KEXINIT) { | ||
user_error('Expected SSH_MSG_KEXINIT'); | ||
return false; | ||
$this->kex_buffer[] = $kexinit_payload_server; | ||
} | ||
|
||
$send_kex = false; | ||
|
@@ -1610,7 +1666,7 @@ function _key_exchange($kexinit_payload_server = false) | |
$temp = unpack('Nlength', $this->_string_shift($response, 4)); | ||
$this->kex_algorithms = explode(',', $this->_string_shift($response, $temp['length'])); | ||
if (in_array('[email protected]', $this->kex_algorithms)) { | ||
if ($this->session_id === false && $this->extra_packets) { | ||
if ($this->session_id === false && count($this->kex_buffer)) { | ||
user_error('Possible Terrapin Attack detected'); | ||
return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); | ||
} | ||
|
@@ -2001,6 +2057,8 @@ function _key_exchange($kexinit_payload_server = false) | |
return false; | ||
} | ||
|
||
$this->keyExchangeInProgress = false; | ||
|
||
if (in_array('[email protected]', $this->kex_algorithms)) { | ||
$this->get_seq_no = $this->send_seq_no = 0; | ||
} | ||
|
@@ -3606,6 +3664,10 @@ function _reset_connection($reason) | |
*/ | ||
function _get_binary_packet($skip_channel_filter = false) | ||
{ | ||
if (!$this->keyExchangeInProgress && count($this->kex_buffer)) { | ||
return $this->_filter(array_shift($this->kex_buffer), $skip_channel_filter); | ||
} | ||
|
||
if ($skip_channel_filter) { | ||
$read = array($this->fsock); | ||
$write = $except = null; | ||
|
@@ -3687,9 +3749,11 @@ function _get_binary_packet($skip_channel_filter = false) | |
|
||
$remaining_length = $packet_length + 4 - $this->decrypt_block_size; | ||
|
||
$this->bytesTransferredSinceLastKEX+= $packet_length + $padding_length + 5; | ||
|
||
// quoting <http://tools.ietf.org/html/rfc4253#section-6.1>, | ||
// "implementations SHOULD check that the packet length is reasonable" | ||
// PuTTY uses 0x9000 as the actual max packet size and so to shall we | ||
// PuTTY uses 0x9000 as the actual max packet size and so, too, shall we | ||
if ($remaining_length < -$this->decrypt_block_size || $remaining_length > 0x9000 || $remaining_length % $this->decrypt_block_size != 0) { | ||
if (!$this->bad_key_size_fix && $this->_bad_algorithm_candidate($this->decryptName) && !($this->bitmap & NET_SSH2_MASK_LOGIN)) { | ||
$this->bad_key_size_fix = true; | ||
|
@@ -3779,7 +3843,34 @@ function _get_binary_packet($skip_channel_filter = false) | |
$this->last_packet = $current; | ||
} | ||
|
||
return $this->_filter($payload, $skip_channel_filter); | ||
if ($this->bytesTransferredSinceLastKEX > $this->doKeyReexchangeAfterXBytes) { | ||
$this->_key_exchange(); | ||
} | ||
|
||
// don't filter if we're in the middle of a key exchange (since _filter might send out packets) | ||
return $this->keyExchangeInProgress ? $payload : $this->_filter($payload, $skip_channel_filter); | ||
} | ||
|
||
/** | ||
* Handle Disconnect | ||
* | ||
* Because some binary packets need to be ignored... | ||
* | ||
* @see self::_filter() | ||
* @see self::_key_exchange | ||
* @return boolean | ||
* @access private | ||
*/ | ||
function _handleDisconnect($payload) | ||
{ | ||
$this->_string_shift($payload, 1); | ||
if (strlen($payload) < 8) { | ||
return false; | ||
} | ||
extract(unpack('Nreason_code/Nlength', $this->_string_shift($payload, 8))); | ||
$this->errors[] = 'SSH_MSG_DISCONNECT: ' . $this->disconnect_reasons[$reason_code] . "\r\n" . $this->_string_shift($payload, $length); | ||
$this->bitmap = 0; | ||
return false; | ||
} | ||
|
||
/** | ||
|
@@ -3795,20 +3886,11 @@ function _filter($payload, $skip_channel_filter) | |
{ | ||
switch (ord($payload[0])) { | ||
case NET_SSH2_MSG_DISCONNECT: | ||
$this->_string_shift($payload, 1); | ||
if (strlen($payload) < 8) { | ||
return false; | ||
} | ||
extract(unpack('Nreason_code/Nlength', $this->_string_shift($payload, 8))); | ||
$this->errors[] = 'SSH_MSG_DISCONNECT: ' . $this->disconnect_reasons[$reason_code] . "\r\n" . $this->_string_shift($payload, $length); | ||
$this->bitmap = 0; | ||
return false; | ||
return $this->_handleDisconnect($payload); | ||
case NET_SSH2_MSG_IGNORE: | ||
$this->extra_packets++; | ||
$payload = $this->_get_binary_packet($skip_channel_filter); | ||
break; | ||
case NET_SSH2_MSG_DEBUG: | ||
$this->extra_packets++; | ||
$this->_string_shift($payload, 2); | ||
if (strlen($payload) < 4) { | ||
return false; | ||
|
@@ -3820,7 +3902,7 @@ function _filter($payload, $skip_channel_filter) | |
case NET_SSH2_MSG_UNIMPLEMENTED: | ||
return false; | ||
case NET_SSH2_MSG_KEXINIT: | ||
// this is here for key re-exchanges after the initial key exchange | ||
// this is here for server initiated key re-exchanges after the initial key exchange | ||
if ($this->session_id !== false) { | ||
$this->send_kex_first = false; | ||
if (!$this->_key_exchange($payload)) { | ||
|
@@ -4386,6 +4468,8 @@ function _send_binary_packet($data, $logged = null) | |
|
||
$packet.= $hmac; | ||
|
||
$this->bytesTransferredSinceLastKEX+= strlen($packet); | ||
|
||
$start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838 | ||
$result = strlen($packet) == @fputs($this->fsock, $packet); | ||
$stop = strtok(microtime(), ' ') + strtok(''); | ||
|
@@ -4399,6 +4483,10 @@ function _send_binary_packet($data, $logged = null) | |
$this->last_packet = $current; | ||
} | ||
|
||
if ($this->bytesTransferredSinceLastKEX > $this->doKeyReexchangeAfterXBytes) { | ||
$this->_key_exchange(); | ||
} | ||
|
||
return $result; | ||
} | ||
|
||
|
@@ -4476,6 +4564,10 @@ function _append_log($message_number, $message) | |
$this->realtime_log_wrap = true; | ||
} | ||
fputs($this->realtime_log_file, $entry); | ||
break; | ||
case NET_SSH2_LOG_REALTIME_SIMPLE: | ||
echo $message_number; | ||
echo PHP_SAPI == 'cli' ? "\r\n" : '<br>'; | ||
} | ||
} | ||
|
||
|