شناسایی سرورهای کاربران ssh

اخیراً دوباره در مطالعه‌های روزانه‌ام به پدیده‌ای برخوردم که سال‌هاست به آن فکر می‌کنم. البته به زبان انگلیسی. پدیده درز یا نشت اطلاعات افرادی که از SSH استفاده می‌کنند. این نوع لو رفتن اطلاعات پدیده تازه‌ای نیست. من مدت‌هاست که در باره این خطر، به دوستانم که از SSH استفاده می‌کنند هشدار دادم. اما صدای من را فقط اندک افرادی شنیند. بنابراین تصمیم گرفتم که در این خصوص به زبان فارسی توضیحاتی را ارائه کنم. چون معتقدم که درک این خطر در جامعه IT ایران، مخصوصا در این شرایط ضروری است. از این رو، با این فرض که شما خواننده گرامی تجربه کارکردن به SSH را دارید ادامه می‌دهم.

کلیدهای نامتقارن SSH

پروتکل SSH از کریپتوگرافی نامتقارن استفاده می‌کند. به طور خلاصه یعنی برای رمزنگاری ارتباط، هم کاربر SSH و دستگاه مقصد هرکدام یک کلید عمومی و یک کلید خصوصی دارند که هنگام ارتباط، تنها کلید عمومی را با یکدیگر تبادل می‌کنند تا اطلاعات هر یک از طرفین با آن رمزنگاری شده و پس از تبادل اطلاعات، با کلید خصوصی رمزگشایی شود. SSH از همین شیوه رمزنگاری برای ssh public key authorization نیز استفاده می‌کند. در باور عموم این شیوه از password authentication امن تر است که البته این باور درستی است.

کاربر SSH برای اتصال ابتدا باید کلید عمومی را به دستگاه مقصد که معمولا یک سرور یا هاست Git است ارسال کند تا مجوز اتصال صادر شود. در صورت صدور این مجوز، امکان طی شدن باقی مراحل فراهم می شود تا کاربر به شکل کامل به دستگاه مقصد متصل شود.

گیتهاب, گیتلب و کلیدهای عمومی

درپاراگراف بالا از ارتباط با سیستم‌های Git از طریق SSH سخن گفتم. وبسایت GitHub یکی از چند وبسایت معروف Git است که کاربران ایرانی فراوانی دارد. GitHub اجازه کارکردن با دستور Git را از طریق ارائه کلید عمومی SSH به کاربران می‌دهد، اما مشکل اینجاست که سرورهایی مانند GitHub، کلید‌های کاربرانشان را به صورت عمومی منتشر می‌کند. با مراجعه به پیوند زیر، و جایگزین کردن نام کاربری خود می‌توانید جنبه‌ای از این اقدام جذاب، اما مضر GitHub را ببینید.

https://github.com/username.keys

یک فرد بد ذات و یا حکومتی سرکوبگر که در پی شنود شهروندان است، به سادگی می‌تواند با کرال (crawl) این کلید‌های SSH و ایجاد یک دیتابیس از نام و نام‌های کاربری، ارتباط آن را با کلید‌های عمومی کشف کرده و از آن برای شناسایی شهروندان استفاده کند. به عنوان مثال، شما همین حالا می‌توانید با دستور (ssh git@github.com) وجود کلید SSH خود را در GitHub بررسی کرده و در صورت وجود چنین کلیدی، نام کاربری گیت‌هاب خود را در خروجی دستور ببینید.

اگر شما واقعا کلید SSH دارید که در گیت‌هاب ثبت شده، باید بنا را بر این بگذارید که تمام کلید های SSH گیت‌هاب شما کرال شده و در دیتابیسی ذخیره شده، یک فرد شرور و یا حکومتی متخاصم می‌تواند کلید شما را به نام و یا نام کاربری شما مرتبط کرده و یا بلعکس، از طریق نام و یا نام کاربری، کلید شما را کشف کند.

کلید عمومی… خیلی عمومیه

برای این که درک کنید، کشف کلید عمومی کاربران SSH چگونه حریم خصوصی افراد (privacy) را به چالش می‌کشد، به آزمایش زیر توجه کنید:

من دو کلید SSH و یک سرور دارم که تنها یک کلید را از من قبول می‌کند. حالا تلاش می‌کنم که با هر دو کلید به سرور متصل شده و سپس لاگ‌ها را با فلگ verbose با یکدیگر مقایسه کنم. با این توضیح که من به دلیل تعداد بالای لاگ‌ها، برخی از خطوط نتیجه را حذف کردم.

$ ssh -v -o "IdentitiesOnly=yes" -i ~/.ssh/id_ed25519 root@10.2.10.5
OpenSSH_9.1p1, OpenSSL 3.0.7 1 Nov 2022
debug1: Connecting to 10.2.10.5 [10.2.10.5] port 22.
debug1: Connection established.
debug1: Authenticating to 10.2.10.5:22 as 'root'
debug1: Host '10.2.10.5' is known and matches the ED25519 host key.
debug1: Will attempt key: /home/mark/.ssh/id_ed25519
debug1: Authentications that can continue: publickey,password,keyboard-interactive
debug1: Next authentication method: publickey
debug1: Offering public key: /home/mark/.ssh/id_ed25519
debug1: Server accepts key: /home/mark/.ssh/id_ed25519
Authenticated to 10.2.10.5 ([10.2.10.5]:22) using "publickey".

$ ssh -v -o "IdentitiesOnly=yes" -i ~/.ssh/id_rsa root@10.2.10.5
OpenSSH_9.1p1, OpenSSL 3.0.7 1 Nov 2022
debug1: Connecting to 10.2.10.5 [10.2.10.5] port 22.
debug1: Connection established.
debug1: Authenticating to 10.2.10.5:22 as 'root'
debug1: Host '10.2.10.5' is known and matches the ED25519 host key.
debug1: Will attempt key: /home/mark/.ssh/id_rsa
debug1: Authentications that can continue: publickey,password,keyboard-interactive
debug1: Next authentication method: publickey
debug1: Offering public key: /home/mark/.ssh/id_rsa
debug1: Authentications that can continue: publickey,password,keyboard-interactive
debug1: Next authentication method: keyboard-interactive
(root@10.2.10.5) Password:

نتیجه بالا نشان می‌دهد که در لاگ اول، کلید عمومی آفر و سرور آن را قبول می‌کند. اما در لاگ دوم، کلید عمومی پذیرفته نشده است. اما پرسش اینجاست که آیا واقعا با یک آفر کلید عمومی می‌توان تشخیص داد که این کلید اجازه اتصال به سرور را دارد و یا خیر؟ آیا از کلید خصوصی استفاده نشده؟ اینطور به نظر می‌آید، اما اجازه بدهید اثبات این فرضیه را مورد آزمایش قرار بدهیم.

برای آزمایش، من یک کد کوتاه نوشتم و یک کلید عمومی را روی سرور هدف امتحان کردم. بدون اینکه از کلید خصوصی استفاده کنم.

package main

import (
	"fmt"
	"io"

	"golang.org/x/crypto/ssh"
)

const (
	username  = "root"
	server    = "10.2.10.5:22"
	publicKey = "ssh-ed25519 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
)

func main() {
	parsed, _, _, _, err := ssh.ParseAuthorizedKey([]byte(publicKey))
	if err != nil {
		panic(err)
	}

	signer := &DummySigner{PubKey: parsed}
	authMethod := []ssh.AuthMethod{ssh.PublicKeysCallback(
		func() ([]ssh.Signer, error) {
			return []ssh.Signer{signer}, nil
		},
	)}

	config := &ssh.ClientConfig{
		User:            username,
		Auth:            authMethod,
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
	}

	_, _ = ssh.Dial("tcp", server, config)

	if signer.Accepted {
		fmt.Println("Public key was accepted by server")
		return
	}

	fmt.Println("Public key was rejected by server")
}

type DummySigner struct {
	PubKey   ssh.PublicKey
	Accepted bool
}

func (signer *DummySigner) PublicKey() ssh.PublicKey {
	return signer.PubKey
}
func (signer *DummySigner) Sign(rand io.Reader, data []byte) (*ssh.Signature, error) {
	signer.Accepted = true
	return &ssh.Signature{Format: signer.PubKey.Type()}, nil
}

نتیجه:

Public key was accepted by server

همانطور که انتظار داشتم، کلید عمومی کار کرد.


خب؟

دیدیم که با چند خط کد، می‌توان یک کلید را روی یک سرور امتحان کرد. پس آنقدر هم آزمایش یک کلید روی یک رنج از آی پی سرورها نباید کار مشکلی باشد. با این شیوه می‌توان به سادگی با یک بانک اطلاعات، حاوی کلید، نام و نام‌های کاربری، کلید عمومی افراد را کشف کرده و به همان سادگی سرورهای آنها را نیز شناسایی کرد.

این بحث، بحث هک سرور و یا اکانت گیت‌هاب نیست، بلکه بحث گرد‌اوری اطلاعات عمومی افراد و سازمان‌ها، و سوء استفاده از ارتباطات منبتی بر SSH است. پس هشیار باشید که اگر کلید عمومی شما لو رفته، و آن کلید را برای کاربر root و یا نام کاربری قابل حدس زدن نصب کرده‌اید، امکان شناسایی سرور شما فراوان است. توصیه من این است که برای فعالیت‌های مرتبط با Git و سرورتان از دو کلید جداگانه استفاده کرده و کلید‌هایی که برای سرورتان اختصاص داده‌اید را به هیچ عنوان جایی منتشر کنید.

همچنین در نظر داشته باشید که با آفر کردن کلید عمومی به سرور هدف، فقط یک مرحله از چندین مرحله لازم برای اتصال کامل به سرور هدف طی شده و همچنان برای اتصال کامل نیاز دارید کلید خصوصی را در اختیار داشته باشید.

منابع:

https://words.filippo.io/dispatches/whoami-updated

https://www.agwa.name/blog/post/whoarethey