域名預(yù)訂/競(jìng)價(jià),好“米”不錯(cuò)過(guò)
前言
前置關(guān)鍵詞:SSH 客戶端/服務(wù)器,Linux/Unix 系統(tǒng)的用戶賬戶,TCP/IP,Socket。
本文撰于 2022 年 9 月,若相關(guān)內(nèi)容有更新請(qǐng)按照引用鏈接內(nèi)的內(nèi)容為準(zhǔn)。
本文介紹了 SSH 協(xié)議在驗(yàn)證用戶身份過(guò)程中的實(shí)現(xiàn)細(xì)節(jié),想幫助讀者更加深入的了解 SSH 客戶端與服務(wù)器的工作過(guò)程。也因此,本文可能不適用于指導(dǎo) SSH 服務(wù)器或客戶端的配置。
本文在撰寫中參考了下列內(nèi)容。
SSH 架構(gòu) RFC 4251 The Secure Shell (SSH) Protocol Architecture
SSH 傳輸層協(xié)議 RFC 4253 The Secure Shell (SSH) Transport Layer Protocol
SSH 身份驗(yàn)證協(xié)議 RFC 4252 The Secure Shell (SSH) Authentication Protocol
SSH 連接協(xié)議 RFC 4254 The Secure Shell (SSH) Connection Protocol
SSH 交互式身份驗(yàn)證 RFC 4256 Generic Message Exchange Authentication for the Secure Shell Protocol (SSH)
SSH 協(xié)議的結(jié)構(gòu)
SSH 協(xié)議的基本框架
建立一個(gè) SSH 連接,將會(huì)經(jīng)過(guò)下面幾個(gè)過(guò)程。
(明文通信)建立 TCP 連接
(明文通信)協(xié)商 SSH 協(xié)議版本 (本質(zhì)上是相互發(fā)送包含版本號(hào)的字符串)
服務(wù)器將自己的 SSH 協(xié)議版本發(fā)送到客戶端,格式為:SSH-protoversion(版本號(hào))-softwareversion(自定義) SP(空格一個(gè),可選) comments(注釋,可選) CR(回車) LF(換行)
客戶端將自己的 SSH 協(xié)議版本發(fā)送到服務(wù)器,格式為:SSH-protoversion(版本號(hào))-softwareversion(自定義) SP(空格一個(gè),可選) comments(注釋,可選) CR(回車符) LF(換行符)
(明文通信)協(xié)商密鑰
服務(wù)器發(fā)送公鑰
客戶端和服務(wù)器相互發(fā)送支持的相關(guān)加密算法列表、MAC 算法列表
使用 D-H 算法 生成此后通訊中使用的對(duì)稱加密密鑰(會(huì)話密鑰)
(密文通信)身份認(rèn)證
(密文通信)正式進(jìn)入應(yīng)用 (如打開(kāi) Shell、SFTP、端口轉(zhuǎn)發(fā))
從上面的流程中可以看出,身份驗(yàn)證過(guò)程實(shí)際上發(fā)生在 SSH 連接建立之后,此后客戶端與服務(wù)器之間傳輸?shù)拿艽a、密鑰、信息都已經(jīng)受到 SSH 協(xié)議生成的「會(huì)話密鑰」加密。
關(guān)于「安全」
SSH 的全稱是「Secure Shell」,安全 Shell。其中的「安全」不是指使用 SSH 就能進(jìn)入一個(gè)絕對(duì)的安全世界,而是包含了傳輸安全和身份安全。
假設(shè),我正在打電話給身在公司的朋友,詢問(wèn)一些機(jī)要信息。此時(shí)攻擊者就可以剪斷我屋外的電話線,分別接上兩個(gè)電話聽(tīng)筒對(duì)聽(tīng)筒放在一起。我和我的朋友都不會(huì)注意到問(wèn)題,而攻擊者可以從中得知我們之間傳輸?shù)男畔ⅰ_@是網(wǎng)絡(luò)中存在的中間人攻擊。
SSH 避免了這個(gè)問(wèn)題,客戶端與服務(wù)器之間的數(shù)據(jù)傳輸經(jīng)過(guò)了上面的過(guò)程而加密(SSH 傳輸層協(xié)議 RFC 4253)。這個(gè)加密是無(wú)關(guān)于用戶的賬號(hào)密碼的,在事前就完成。無(wú)論是網(wǎng)絡(luò)中的交換機(jī)還是跳板機(jī)都無(wú)法直接獲取加密前的明文。SSH 保護(hù)了傳輸時(shí)的安全。
SSH 也同樣提供了驗(yàn)證用戶身份的方式,客戶端可以將用戶的密碼發(fā)送至服務(wù)器,以此讓服務(wù)器確認(rèn)用戶的身份,只允許被授權(quán)的訪問(wèn)連接到服務(wù)器。
SSH 的安全不是絕對(duì)的。比如攻擊者拿起剪斷的電話線(中間人攻擊),他依舊可以通過(guò)客戶端與服務(wù)器之間的通訊內(nèi)容推測(cè)出這是 SSH 連接。SSH 協(xié)議并不能保證連接不被偵測(cè)到特征。
身份驗(yàn)證方式
密碼(Password)
使用密碼登錄是常用且較為便捷的認(rèn)證方式。在配置文件 /etc/ssh/sshd_config 中添加 PasswordAuthentication yes 以開(kāi)啟密碼登錄。
使用 ssh 命令時(shí)提示輸入密碼
通常也認(rèn)為使用密碼登錄是一種較為脆弱的認(rèn)證方式。這不是說(shuō)使用密碼會(huì)造成傳輸時(shí)的不安全,而是對(duì)密碼本身保存的擔(dān)憂。一般的用戶密碼都是十位或數(shù)十位字符,可能被記錄與紙上或記事本中。即使不是如此,有意義的密碼也容易遭到社會(huì)工程攻擊而泄露。
在 /etc/ssh/sshd_config 文件中有時(shí)會(huì)設(shè)置 PermitRootLogin prohibit-password ,要求系統(tǒng)管理用戶 root 不得使用密碼登錄。
根據(jù) RFC 中的描述,用戶認(rèn)證過(guò)程是在連接握手之后的。此時(shí)客戶端與服務(wù)器之間已經(jīng)建立起了加密的連接。雙方都會(huì)使用握手時(shí)交換好的密鑰加密所有傳輸內(nèi)容。后文中的 SSH 數(shù)據(jù)樣式都是被加密傳輸?shù)摹?/p>
登錄時(shí),登錄請(qǐng)求由客戶端發(fā)起。一個(gè)名稱為 SSH_MSG_USERAUTH_REQUEST 的消息從客戶端發(fā)出,包含了登錄的用戶名與密碼。
C: byte SSH_MSG_USERAUTH_REQUEST
C: string user name
C: string service name
C: string "password"
C: boolean FALSE
C: string plaintext password in ISO-10646 UTF-8 encoding [RFC3629]
S: byte SSH_MSG_USERAUTH_SUCCESS
若登錄認(rèn)證失敗,服務(wù)器將回復(fù) SSH_MSG_USERAUTH_FAILURE。
基本上,這樣的消息往復(fù)就完成了平時(shí)常見(jiàn)的登錄過(guò)程。
除此之外,在 RFC 標(biāo)準(zhǔn)中還有一個(gè)用于服務(wù)器響應(yīng)密碼登錄請(qǐng)求的消息。
通常,服務(wù)器會(huì)成功或失敗地響應(yīng)此消息。但是,如果密碼已過(guò)期,服務(wù)器應(yīng)通過(guò) SSH_MSG_USERAUTH_PASSWD_CHANGEREQ 響應(yīng)來(lái)指示這一點(diǎn)。在任何情況下,服務(wù)器都不得允許使用過(guò)期密碼進(jìn)行身份驗(yàn)證。
后文介紹的 keyboard-interactive 登錄方式也可以做出密碼過(guò)期提示。
公鑰私鑰(Publickey)
在 RFC 標(biāo)準(zhǔn)中,公鑰驗(yàn)證方式是唯一必須實(shí)現(xiàn)的驗(yàn)證方式(The only REQUIRED authentication)。所有實(shí)現(xiàn)都必須(MUST, RFC2119)支持這種方法。在 /etc/ssh/sshd_config 文件中使用 PubkeyAuthentication yes 開(kāi)啟公鑰驗(yàn)證方式。
此驗(yàn)證方式需要用戶先準(zhǔn)備一個(gè)非對(duì)稱加密的密鑰對(duì),將公鑰保存至 SSH 服務(wù)器的 ~/.ssh/authorized_key 文件中??蛻舳说卿洉r(shí),在本地用私鑰加密某個(gè)信息,并將結(jié)果發(fā)送給服務(wù)器,服務(wù)器將通過(guò)公鑰驗(yàn)證收到的密文是否來(lái)自指定的用戶。
私鑰通常以加密的形式存儲(chǔ)在客戶主機(jī)上,用戶必須在生成簽名之前提供一個(gè)口令(passphrase)。 即使不是這樣,簽名操作也涉及一些昂貴的計(jì)算。 為了避免不必要的處理和用戶互動(dòng),提供以下信息來(lái)查詢使用 "公鑰 "方法的認(rèn)證是否可以接受。
C: byte SSH_MSG_USERAUTH_REQUEST
C: string user name in ISO-10646 UTF-8 encoding [RFC3629]
C: string service name in US-ASCII
C: string "publickey"
C: boolean FALSE
C: string public key algorithm name
C: string public key blob
任何公鑰算法都可以被提供給認(rèn)證使用,如果請(qǐng)求中的算法不被服務(wù)器支持,它必須直接拒絕該請(qǐng)求。
服務(wù)器必須以 SSH_MSG_USERAUTH_FAILURE 或以下方式回應(yīng)該消息。
S: byte SSH_MSG_USERAUTH_PK_OK
S: string public key algorithm name from the request
S: string public key blob from the request
之后,客戶端會(huì)使用私鑰加密一個(gè)消息(消息的構(gòu)成方式參見(jiàn) RFC 4252),將結(jié)果發(fā)送給服務(wù)器。下面消息中的 signature 即為加密運(yùn)算后的內(nèi)容。
C: byte SSH_MSG_USERAUTH_REQUEST
C: string user name
C: string service name
C: string "publickey"
C: boolean TRUE
C: string public key algorithm name
C: string public key to be used for authentication
C: string signature
使用公鑰方式登錄的優(yōu)點(diǎn)是,密鑰對(duì)基本不可能被寫在紙上(密鑰是很長(zhǎng)的隨機(jī)文本,社會(huì)工程攻擊中只能通過(guò)更困難的間接方式竊取這么長(zhǎng)的內(nèi)容);在網(wǎng)絡(luò)上傳輸、保存的通常是密鑰對(duì)的公鑰文件,而非私鑰文件。
更加顯而易見(jiàn)的好處是,私鑰文件是保存在客戶端的計(jì)算機(jī)上的。使用 SSH 命令時(shí)就無(wú)需再反復(fù)輸入密碼。因此網(wǎng)絡(luò)上很多教程使用此方式作為免密碼登錄的方式。與此同時(shí),因?yàn)樾枰A(yù)先將公鑰放在服務(wù)器上(通常是通過(guò)網(wǎng)絡(luò)上傳),其也確實(shí)不便于配置。
交互式(keyboard-interactive)
在 RFC 文檔中,這個(gè)驗(yàn)證方式被視作是前述方案的一種擴(kuò)展。允許 SSH 客戶端和服務(wù)器在獲取身份驗(yàn)證信息時(shí)進(jìn)行一些交互。如要啟用此方式需在 /etc/ssh/sshd_config 文件中添加 ChallengeResponseAuthentication yes 。通常情況下,這個(gè)驗(yàn)證模式會(huì)與系統(tǒng)內(nèi)的 PAM 模塊一同啟用。以此來(lái)支持谷歌驗(yàn)證器(多因素驗(yàn)證),或其他內(nèi)部身份校驗(yàn)?zāi)K。
NextSSH 在連接需要交互式驗(yàn)證的服務(wù)器時(shí)的提示
使用交互式驗(yàn)證可以允許用戶輸入更多的信息,獲得更多的提示內(nèi)容。
從 RFC 中來(lái)看,此驗(yàn)證模式也是從客戶端發(fā)起身份驗(yàn)證請(qǐng)求開(kāi)始。
C: byte SSH_MSG_USERAUTH_REQUEST
C: string user name (ISO-10646 UTF-8, as defined in [RFC-3629])
C: string service name (US-ASCII)
C: string "keyboard-interactive" (US-ASCII)
C: string language tag (as defined in [RFC-3066])
C: string submethods (ISO-10646 UTF-8)
當(dāng)服務(wù)器得知客戶端準(zhǔn)備使用 keyboard-interactive 為驗(yàn)證方式后,服務(wù)器會(huì)向客戶端發(fā)出用戶信息請(qǐng)求。在這個(gè)來(lái)自服務(wù)器的請(qǐng)求中,服務(wù)器將提供提示文本(instruction, prompt)并且為每一個(gè)字段(或者稱為詢問(wèn))標(biāo)記一個(gè)序號(hào)(num-prompts)。
S: byte SSH_MSG_USERAUTH_INFO_REQUEST
S: string name (ISO-10646 UTF-8)
S: string instruction (ISO-10646 UTF-8)
S: string language tag (as defined in [RFC-3066])
S: int num-prompts
S: string prompt[1] (ISO-10646 UTF-8)
S: boolean echo[1]
S: ...
S: string prompt[num-prompts] (ISO-10646 UTF-8)
S: boolean echo[num-prompts]
收到來(lái)自 SSH 服務(wù)器的請(qǐng)求后,客戶端即可開(kāi)始向用戶展示界面獲取信息。在使用 ssh 命令時(shí),通常會(huì)在終端內(nèi)等待用戶輸入,具有 GUI 的軟件將會(huì)展示提示界面。
使用 ssh 命令時(shí)提示輸入質(zhì)詢信息
當(dāng)用戶完成輸入后,客戶端即可發(fā)送用戶信息響應(yīng)。
C: byte SSH_MSG_USERAUTH_INFO_RESPONSE
C: int num-responses
C: string response[1] (ISO-10646 UTF-8)
C: ...
C: string response[num-responses] (ISO-10646 UTF-8)
這樣的過(guò)程(服務(wù)器請(qǐng)求-用戶輸入-客戶端響應(yīng))可能會(huì)重復(fù)多次。例如用戶輸入的密碼錯(cuò)誤,或者服務(wù)器依據(jù)情況請(qǐng)求了更多的信息。
下面是來(lái)自 RFC 文檔中的,客戶端和服務(wù)器之間的兩個(gè)交換例子。 第一個(gè)例子是用需處理的 Token 進(jìn)行驗(yàn)證的例子。這是一種其他認(rèn)證方法無(wú)法實(shí)現(xiàn)的方式。
C: byte SSH_MSG_USERAUTH_REQUEST
C: string "user23"
C: string "ssh-userauth"
C: string "keyboard-interactive"
C: string ""
C: string ""
S: byte SSH_MSG_USERAUTH_INFO_REQUEST
S: string "CRYPTOCard Authentication"
S: string "The challenge is ’14315716’"
S: string "en-US"
S: int 1
S: string "Response: "
S: boolean TRUE
[Client prompts user for password]
C: byte SSH_MSG_USERAUTH_INFO_RESPONSE
C: int 1
C: string "6d757575"
S: byte SSH_MSG_USERAUTH_SUCCESS
第二個(gè)例子是一個(gè)標(biāo)準(zhǔn)的密碼認(rèn)證。但在此例子中,用戶的密碼已經(jīng)過(guò)期。
C: byte SSH_MSG_USERAUTH_REQUEST
C: string "user23"
C: string "ssh-userauth"
C: string "keyboard-interactive"
C: string "en-US"
C: string ""
S: byte SSH_MSG_USERAUTH_INFO_REQUEST
S: string "Password Authentication"
S: string ""
S: string "en-US"
S: int 1
S: string "Password: "
S: boolean FALSE
[Client prompts user for password]
C: byte SSH_MSG_USERAUTH_INFO_RESPONSE
C: int 1
C: string "password"
S: byte SSH_MSG_USERAUTH_INFO_REQUEST
S: string "Password Expired"
S: string "Your password has expired."
S: string "en-US"
S: int 2
S: string "Enter new password: "
S: boolean FALSE
S: string "Enter it again: "
S: boolean FALSE
[Client prompts user for new password]
C: byte SSH_MSG_USERAUTH_INFO_RESPONSE
C: int 2
C: string "newpass"
C: string "newpass"
S: byte SSH_MSG_USERAUTH_INFO_REQUEST
S: string "Password changed"
S: string "Password successfully changed for user23."
S: string "en-US"
S: int 0
[Client displays message to user]
C: byte SSH_MSG_USERAUTH_INFO_RESPONSE
C: int 0
S: byte SSH_MSG_USERAUTH_SUCCESS
申請(qǐng)創(chuàng)業(yè)報(bào)道,分享創(chuàng)業(yè)好點(diǎn)子。點(diǎn)擊此處,共同探討創(chuàng)業(yè)新機(jī)遇!