写在前面
终于终于,安全开发也练习一年半了,有时间完善一下项目,写写中间踩过的坑。
安全开发的系列全部都会上传至github
,欢迎使用和star
。
工具链接地址
https://github.com/SAY0l/my-crack
预览
My-Crack 工具介绍
更适合中国宝宝的弱口令扫描器
Help
NAME:
My-Crack - Weak password crack
USAGE:
main.exe [global options] command [command options] [arguments...]
VERSION:
1.1
AUTHOR:
sayol <github@sayol.com>
COMMANDS:
scan let's crack weak password
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
--debug, -d debug mode
--timeout value, -t value timeout (default: 3)
--scan_num value, -n value thread num (default: 5000)
--ip_list value, -i value ip_list (default: "./input/ip_list.txt")
--user_dict value, -u value user_dict (default: "./input/user.dic")
--pass_dict value, -p value pass_dict (default: "./input/pass.dic")
--output_file value, -o value scan result file (default: "./output/my_crack.txt")
--help, -h show help
--version, -v print the version
Use
当前核心支持ftp/mongodb/mysql/mssql/postgre/redis/ssh
的弱口令扫描
提供了编译后的版本。
后续版本开放代码
Other
思路借鉴于赵海峰老师的《白帽子安全开发实战》
顺带回答赵海峰老师的小问题:
1. 扫到一个弱口令后,如何取消相同IP\port和用户名请求,避免扫描效率低下
2. 对于FTP匿名访问,如何只记录一个密码,而不是把所有用户名都记录下来
3. 对于Redis这种没有用户名的服务,如何只记录一次密码,而不是记录所有的所有用户及正常的密码的组合
4. 对于不支持设置超时的扫描插件,如何统一设置超时时间
-
利用hash来完成的。在redis,ftp这种可以仅记录密码的就以
(ip-port-protocol)
进行hash,其他带用户名的以(ip-port-username)
进行hash,查hash来作为规避条件,一旦结果集的hash已经存在和当前扫描产生的hash值相同,就continue。 -
基于上述原理,就可以实现redis、ftp 只记录一个密码,不会把所有用户名都记录下来,因为是用ip-port-protocol 进行hash。
-
设计了一个
WaitTimeout
函数。c:= make(chan struct{})
go func(){
defer close(c)
wg.Wait()
}()
select{
case <-c:
return false //c仅仅作为一个flag,没有实际作用
case <-time.After(timeout):
return true
}
使用一个管道作为flag
,time.After(timeout)
也返回一个管道。一旦c
关闭,就会触发select
第一个case
,返回false
,表示正常关闭,wg.Wait
正常完成。一旦超时时间到达,会触发select
第二个case
,就会返回true
,表示已经超时。
Ver 1.1 优化点
- 连接核心中,更替了一些不能用的版本、第三方库,使用了维护性高,连接稳定的版本库。
例如mongodb的连接核心:
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
抛弃了之前的个人维护库。
-
修复了一些问题
ipListFile, err := os.Open(fileName)
if err != nil {
logger.Log.Fatalf("Open ip list file err, %v", err)
}defer ipListFile.Close()
减少大量代码冗余。上述代码为例,发生错误会直接终止,defer不会入栈,无需再做defer前的判断。
-
错误处理修复
之前版本大量复用err
,会出现err
覆盖问题,有必要的err
取消覆盖写法。
Ver 1.2 待优化点
代码上线
并发算法需要优化
更多服务的支持 smb,elastic ...
踩坑
初始化结构体什么时候用指针,什么时候传呢?
在Go语言中,当我们初始化一个结构体时,通常会使用取地址运算符&来创建一个指向该结构体的指针,然后将该指针赋值给一个变量。
这样做的主要原因有以下几点:
1.结构体是值类型:在Go语言中,结构体是值类型,也就是说当我们将一个结构体赋值给另一个结构体时,会进行值的拷贝。如果结构体较大,这将导致内存开销较大。而使用指针可以避免这种情况,因为指针只是保存了结构体的内存地址,赋值时只是复制了一个指针,而不是整个结构体。
2.修改结构体的字段:如果我们希望在函数中修改结构体的字段值,直接传递结构体的值是不会改变原结构体的。但如果传递结构体的指针,就可以通过指针来修改结构体的字段。
3.减少内存拷贝:在函数调用时,如果传递结构体的指针,避免了对整个结构体进行内存拷贝,提高了性能。特别是当结构体较大时,这种优化效果更为明显。
函数出口编写
无论是AI,还是我自己编写的,都是多出口写法,也就是函数弹出err时,检测err是否为nil,一旦出现错误,就弹出函数
从程序设计的角度来说,多出口是很繁琐,也很不严谨的,但是如果设置flag变量来检测,不仅占用内存也很麻烦
学习大佬编写时,提供了一个非常棒的思路,当你的true条件比较重要时,同时需要处理的err不算多,不需要保证每一次的错误精准处理时
使用单个err变量,判断err==nil时的下一步动作,一旦出现错误,直接走整个函数的总出口返回。如果没有错误,继续执行语句,新的err会覆盖旧的err,一旦出错仍然走总出口返回
这样,整个函数就只有一个总出口,重点就会放在err==nil
的正确情况下,不需要去繁琐的处理err
但是这样也会产生一个问题,由于err
是覆盖机制,一旦有两个语句同时发生错误,你并不能判断第一个语句是否真的发生了错误,它可能是被覆盖了,也可能是没有错误。所以上述写法在需要处理的err
较少的情况下比较好用。
defer与return
在常规情况下,defer
应该是写在return
的前面,保证函数在进行返回之前做出defer中的操作。
但是这并不能保证defer
一定能够达到你的逻辑,因为写的不好的函数中,函数会有多个出口,例如
if err!=nil{
return false
}
defer fmt.Println("i am defer")
这样的错误处理是很常规的,此时如果err没有问题,defer
仍然能够正常入栈,可以达到正常的逻辑,但是一旦err
发现错误,defer
就不能正常入栈了。
所以defer
一定要写在所有可能return
的前面