热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

SSH介绍以及go客户端脚本实现

SSH协议介绍此处主要介绍一下SSH协议的结构以及其安全性。在日常使用中,SSH(SecureshellProtocol)是我们经常会用到的一个命令。通过它我们可以便捷的控制远端电

SSH 协议介绍

此处主要介绍一下SSH协议的结构以及其安全性。

在日常使用中,SSH (Secure shell Protocol) 是我们经常会用到的一个命令。通过它我们可以便捷的控制远端电脑。
同HTTPS作比较的话,相同点是它们都是用于客户机与服务器之间进行加密通信的一种机制。不同点在于,HTTPS协议是基于PKI(Public Key Infrastructor),也就是我们常说的CA,证书中心。用户在访问HTTPS站点之前会先向可信的CA请求该站点的数字证书,进而获取其公钥,之后与目标站点建立非对称加密连接。
而SSH的连接方式是这样的:(1)远程主机收到用户的登录请求,把自己的公钥发给用户。(2)用户使用这个公钥,将登录密码加密后,发送回来。(3)远程主机用自己的私钥,解密登录密码,如果密码正确,就同意用户登录。
这个过程存在几个风险:

  1. 如果有人截获了登录请求,然后冒充远程主机,将伪造的公钥发给用户。方法是第一次登陆的时候客户端会显示一段公钥指纹,这个时候可以在客户端选择相信或者不相信,如果相信的话则建立连接。
  2. 回放攻击,攻击者截获了公钥以及公钥加密的密码之后,便可以绕过用户直接与服务端通信。防范这个的方式是通过Diffie-Hellman算法。

经典Diffie-Hellman算法的计算步骤如下:

  1. 双方共同选择一个大值素数作为种子值(seed value)
  2. 双方共同选择一个加密生成器(通常是AES),用于后续的数值操作
  3. 双方分别各自选择一个素数,该素数的值对对方保密,用于生成本次通讯的私钥(与SSH身份认证私钥无关)
  4. 双方分别用各自的私钥、共同的加密生成器、和共同的素数生成各自的公钥
  5. 双方将各自的公钥共享给对方
  6. 双方用各自的私钥和对方发过来的公钥生成另一个密钥。根据该算法,双方各自计算出来的两个密钥是完全一样的,即“共同的秘密”
  7. 该密钥被用于本次通讯所有内容的加密

这样的话就能够保证此次通信仅限于这两个人之间,攻击者无法从中途插入。

除了使用账号密码登陆以外,用户还可以直接使用密钥登陆,具体方式就是使用ssh-keygen命令创建一对公私钥,然后使用ssh-copy-id命令将公钥上传到服务器,就可以免密码登陆了。

SSH介绍以及go客户端脚本实现

从图中可以看出,同一个SSH连接以内可以创建多个channel,既可以用于传输文本命令,也可以用于传输文件。这也就可以理解了我们常用的SSH登陆工具只用登陆一次就可以打开多个SSH窗口了。
对应着程序中,每次连接返回一个ssh.Client类型对象,使用该对象可以创建多个ssh.Session对象。

代码实现

GO 客户端实现

该程序能够打开一个ssh窗口,绑定标准输入输出,可以直接与远端通信。
由于terminal.MakeRaw()的存在所以无法在windows下运行。

package main

import ( 
	"fmt"
	"log"
	"os"
	"time"
	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/terminal"
	"net"
)
func main() { 
	session, err := connect("root", "Mao12345", "106.14.142.162", 22)
	if err != nil {
		log.Fatal(err)
	}
	defer session.Close()
	fd := int(os.Stdin.Fd())
	oldState, err := terminal.MakeRaw(fd)
	if err != nil {
		panic(err)
	}
	defer terminal.Restore(fd, oldState)

	
	session.Stdout = os.Stdout
	session.Stderr = os.Stderr
	session.Stdin = os.Stdin

	termWidth, termHeight, err := terminal.GetSize(fd)
	if err != nil {
		panic(err)
	}
	// Set up terminal modes
	modes := ssh.TerminalModes{
		ssh.ECHO:          1,     // enable echoing
		ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
		ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
	}
	// Request pseudo terminal
	if err := session.RequestPty("xterm-256color", termHeight, termWidth, modes); err != nil {
		log.Fatal(err)
	}
	session.Run("/bin/bash")
	// session.Run("top") 执行单个命令
	// session.Run("ls /; ls /abc") 执行单个命令
}


func connect(user, password, host string, port int) (*ssh.Session, error) { 
	var (
		auth         []ssh.AuthMethod
		addr         string
		clientConfig *ssh.ClientConfig
		client       *ssh.Client
		session      *ssh.Session
		err          error
	)
	// get auth method
	auth = make([]ssh.AuthMethod, 0)
	auth = append(auth, ssh.Password(password))
 
	clientCOnfig= &ssh.ClientConfig{
		User:    user,
		Auth:    auth,
		Timeout: 30 * time.Second,
		//需要验证服务端,不做验证返回nil就可以,点击HostKeyCallback看源码就知道了
		HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
			return nil
		},
	}
 
	// connet to ssh
	addr = fmt.Sprintf("%s:%d", host, port)
 
	if client, err = ssh.Dial("tcp", addr, clientConfig); err != nil {
		return nil, err
	}
 
	// create session
	if session, err = client.NewSession(); err != nil {
		return nil, err
	}
 
	return session, nil
}

应用

实际上,自己去写一个GO的客户端或者服务器端并没有什么实用性,但是它可以帮助我们实现更多灵活的功能。之前有段时间我需要同时操作多台云主机进行网络实验,如果使用逐个SSH连接的话实在过于繁琐,而且容易出错。这个时候就可以用GO来写一个网络脚本了。
当然也有许多好用的集群SSH操作工具,比如cluster SSH,PSSH等等。它们的优点就是上手更快,并且具有回显,错误也比较少。但缺点可能就是不够灵活了,而且它们必须应用于一个节点都高度同质化的集群上,而自己写的脚本则可以写出很灵活的判断语句,这个大家也可以自行选择.
下面这个例子包含上传文件、执行命令、下载文件三步,为了更为清晰可读,对原代码进行了删减。

// 自动化执行服务器端的 cap.sh脚本
package main

import (
	"fmt"
	"log"
	"net"
	"os"
	"os/exec"
	"time"
	"sync"
	"strings"
	"path/filepath"
	"github.com/pkg/sftp"
	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/terminal"
)

var (
	wg sync.WaitGroup
)

func main() {

	// connecting
	client_A, err := connect("root", "Mao12345", "106.14.173.234", 22)
	if err != nil {
		log.Fatal(err)
		return
	}
	client_B, err := connect("root", "Lsc19940810", "106.14.9.185", 22)
	if err != nil {
		log.Fatal(err)
		return
	}
	fmt.Println("SSH connecting succeed!")


	// first, you need to upload scripts; 上传脚本
	wg.Add(1)
	go func(client *ssh.Client) {
		upload_file("/home/cap.sh", "/root/test.sh", client)
		wg.Done()
	}(client_A)
	wg.Add(1)
	go func(client *ssh.Client) {
		upload_file("/home/cap.sh", "/root/test.sh", client)
		wg.Done()
	}(client_B)

	wg.Wait()
	fmt.Println("Uploading completed!")

	// begin to send backgroung flow and capture pkgs 执行脚本,执行完成之后下载文件
	wg.Add(1)
	go func(client *ssh.Client) {
		exec_remote_cmd("sh /root/cap.sh", client, false, nil )
		download_file("/root/tmp.pcap", "/home/mao/pcapfiles/D-"+case_num+".pcap", client)
		wg.Done()
	}(client_D)
	wg.Add(1)
	go func(client *ssh.Client) {
		exec_remote_cmd("sh /root/cap.sh", client, false, nil )
		download_file("/root/tmp.pcap", "/home/mao/pcapfiles/A-"+case_num+".pcap", client)
		wg.Done()
	}(client_A)
	wg.Wait()
}

func exec_remote_cmd(cmd string, client *ssh.Client, interactive bool, env map[string]string) {
	session, err := client.NewSession()
	if err != nil {
		log.Fatal(err)
		return
	}
	defer session.Close()
	session.Stdout = os.Stdout
	session.Stderr = os.Stderr

	for k, v := range env {
		fmt.Println("Set "+k+" to "+v)
		if err := session.Setenv(k, v); err != nil {
			log.Fatal(err)
		}
	}
	if interactive {
		session.Stdin = os.Stdin
		fd := int(os.Stdin.Fd())
		oldState, err := terminal.MakeRaw(fd)
		if err != nil {
			panic(err)
		}
		defer terminal.Restore(fd, oldState)
		termWidth, termHeight, err := terminal.GetSize(fd)
		if err != nil {
			panic(err)
		}
		// Set up terminal modes
		modes := ssh.TerminalModes{
			ssh.ECHO:          1,     // enable echoing
			ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
			ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
		}
		// Request pseudo terminal
		if err := session.RequestPty("xterm-256color", termHeight, termWidth, modes); err != nil {
			log.Fatal(err)
		}
			
	}
	session.Run(cmd)
}

func download_file(remote_file string, local_file string, client *ssh.Client) {
	sftpClient, err := sftp.NewClient(client)
	if err != nil {
		log.Fatal(err)
		return
	}
	defer sftpClient.Close()

	fmt.Println("Downloading files...")
	srcFile, err := sftpClient.Open(remote_file)
	if err != nil {
		log.Fatal(err)
	}
	defer srcFile.Close()

	// var local_file = path.Base(remote_file)
	dstFile, err := os.Create(local_file)
	if err != nil {
		log.Fatal(err)
	}
	defer dstFile.Close()

	if _, err = srcFile.WriteTo(dstFile); err != nil {
		log.Fatal(err)
	}

	fmt.Println("download: copy file from remote server finished!")
}

func upload_file(local_file string, remote_file string, client *ssh.Client) {
	sftpClient, err := sftp.NewClient(client)
	if err != nil {
		log.Fatal(err)
		return
	}
	// 用来测试的本地文件路径 和 远程机器上的文件夹
	srcFile, err := os.Open(local_file)
	if err != nil {
		log.Fatal(err)
	}
	defer srcFile.Close()

	dstFile, err := sftpClient.Create(remote_file)
	if err != nil {
		log.Fatal(err)
	}
	defer dstFile.Close()

	buf := make([]byte, 1024)
	for {
		n, _ := srcFile.Read(buf)
		if n == 0 {
	 		break
		}
		dstFile.Write(buf)
	}

	fmt.Println("upload: copy file to remote server finished!")
}

func connect(user, password, host string, port int) (*ssh.Client, error) {
	var (
		auth         []ssh.AuthMethod
		addr         string
		clientConfig *ssh.ClientConfig
		client       *ssh.Client
		err          error
	)
	// get auth method
	auth = make([]ssh.AuthMethod, 0)
	auth = append(auth, ssh.Password(password))

	clientCOnfig= &ssh.ClientConfig{
		User:    user,
		Auth:    auth,
		Timeout: 30 * time.Second,
		//需要验证服务端,不做验证返回nil就可以,点击HostKeyCallback看源码就知道了
		HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
			return nil
		},
	}

	// connet to ssh
	addr = fmt.Sprintf("%s:%d", host, port)

	if client, err = ssh.Dial("tcp", addr, clientConfig); err != nil {
		return nil, err
	}

	return client, nil

}

参考链接 :

  • [1] SSH结构介绍 https://blog.gopheracademy.com/go-and-ssh/
  • [2] 抓包 https://notfound.cn/2017/08/20/golang-ssh-server-example/
  • [3] 上传下载文件 https://studygolang.com/articles/7645
  • [4] ssh原理与介绍 http://www.ruanyifeng.com/blog/2011/12/ssh_remote_login.html
  • [5] 加密过程介绍 http://blog.csdn.net/zstack_org/article/details/53100545

推荐阅读
  • 如何在服务器主机上实现文件共享的方法和工具
    本文介绍了在服务器主机上实现文件共享的方法和工具,包括Linux主机和Windows主机的文件传输方式,Web运维和FTP/SFTP客户端运维两种方式,以及使用WinSCP工具将文件上传至Linux云服务器的操作方法。此外,还介绍了在迁移过程中需要安装迁移Agent并输入目的端服务器所在华为云的AK/SK,以及主机迁移服务会收集的源端服务器信息。 ... [详细]
  • Linux服务器密码过期策略、登录次数限制、私钥登录等配置方法
    本文介绍了在Linux服务器上进行密码过期策略、登录次数限制、私钥登录等配置的方法。通过修改配置文件中的参数,可以设置密码的有效期、最小间隔时间、最小长度,并在密码过期前进行提示。同时还介绍了如何进行公钥登录和修改默认账户用户名的操作。详细步骤和注意事项可参考本文内容。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 本文介绍了高校天文共享平台的开发过程中的思考和规划。该平台旨在为高校学生提供天象预报、科普知识、观测活动、图片分享等功能。文章分析了项目的技术栈选择、网站前端布局、业务流程、数据库结构等方面,并总结了项目存在的问题,如前后端未分离、代码混乱等。作者表示希望通过记录和规划,能够理清思路,进一步完善该平台。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • 本文介绍了使用cacti监控mssql 2005运行资源情况的操作步骤,包括安装必要的工具和驱动,测试mssql的连接,配置监控脚本等。通过php连接mssql来获取SQL 2005性能计算器的值,实现对mssql的监控。详细的操作步骤和代码请参考附件。 ... [详细]
  • 集成电路企业在进行跨隔离网数据交换时面临着安全性问题,传统的数据交换方式存在安全性堪忧、效率低下等问题。本文以《Ftrans跨网文件安全交换系统》为例,介绍了如何通过丰富的审批流程来满足企业的合规要求,保障数据交换的安全性。 ... [详细]
  • 本文详细介绍了git常用命令及其操作方法,包括查看、添加、提交、删除、找回等操作,以及如何重置修改文件、抛弃工作区修改、将工作文件提交到本地暂存区、从版本库中删除文件等。同时还介绍了如何从暂存区恢复到工作文件、恢复最近一次提交过的状态,以及如何合并多个操作等。 ... [详细]
  • 背景应用安全领域,各类攻击长久以来都危害着互联网上的应用,在web应用安全风险中,各类注入、跨站等攻击仍然占据着较前的位置。WAF(Web应用防火墙)正是为防御和阻断这类攻击而存在 ... [详细]
  • LVS实现负载均衡的原理LVS负载均衡负载均衡集群是LoadBalance集群。是一种将网络上的访问流量分布于各个节点,以降低服务器压力,更好的向客户端 ... [详细]
  • 大坑|左上角_pycharm连接服务器同步写代码(图文详细过程)
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了pycharm连接服务器同步写代码(图文详细过程)相关的知识,希望对你有一定的参考价值。pycharm连接服务 ... [详细]
  • 一、修改注册表去掉桌面图标小箭头1按下win+R组合快捷键,打开windows10系统的“运行”窗口,输入“regedit”,打开注册表编辑器,找到HKEY_CLASSES_ROOT\lnkfi ... [详细]
  • hadoop1.2.1文档中这样写:Nowcheckthatyoucansshtothelocalhostwithoutapassphrase:$sshlocalhostIfyou ... [详细]
author-avatar
用户7krcbz6eb6
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有