使用Python完成控制主机与任务节点的交互 [Demo]



(一)

马上做一个分布式漏洞扫描与攻击的项目,这段时间一直选技术路线以及做大量的demo。这篇是记录我在主控端与各个漏洞扫描节点协调通信上的一个demo代码。我选择使用类似于WebService的技术,即各个节点暴露WebService接口,主控端去调用并且拿到回调。WebService基于SOAP协议通信我觉得太麻烦,因为我的需求是主控端分发任务队列给节点,节点执行,完成之后回调,主控端进行处理,异步调用,逻辑简单。最终我试了一下基于GET方法的类似于WebService的方法,自己写交互的过程,这样编码也比较方便: - )  这里我不禁要吐槽下Python开发一个WebService配置运行环境实在是蛋疼,但是调用WebService倒是很容易: - (



(二)


我使用了BaseHTTPServer,在主控端和各个节点上都维护一个多线程Server进行交互,这样编码也比较简单,可以把精力主要投放在设计好通信细节以及处理各种突发事件即可。


这个过程的逻辑如下:


  1. 主控端接收外部调用

  2. 判断调用合法性,并重新组装请求并发给扫描节点

  3. 节点收到请求,进行身份验证,并提取出信息,根据信息(服务名、参数)开启工作线程。

  4. 节点执行完进行回调。


整个过程异步实现,画了张图如下:



(三)


使用PythonBaseHTTPServer实现,这个模块可以轻易实现一个HTTP服务器,使用里面的do_GET回调函数执行具体的逻辑,识别是外部调用请求还是回调请求,并对请求进行解析以及处理异常情况。


由于只是探测阶段,所以只是简单做了一个小demo,证明这种处理方式的可行性。下面就是贴代码了:


控制端:


#coding=utf-8
'''
控制节点逻辑:
1.接收外部接口的调用,格式为:
如控制节点1,执行其内部的print_job任务
http://127.0.0.1:8000/1/print_job

2.接收节点的Callback
如收到:
http://127.0.0.1:8001/SUCCESS/NODE_ONE
表示节点1的任务完成
如果是:
http://127.0.0.1:8001/ERROR/NODE_ONE
表示节点1执行出错

3.转发给节点
各个节点分析URL
http://127.0.0.1:8001/1/print_job
表示执行方法为print_job

'''
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
from SocketServer import ThreadingMixIn
import urllib
import re
import threading

NODE_ONE = ('127.0.0.1','8001')
NODE_TWO = ('127.0.0.1','8002')

class MyRequestHandler(BaseHTTPRequestHandler):

    '''/1/print|1,2,3(判断url后面跟的参数列表)'''
    def getArguments(self,path):
        '''
        传递参数:/1/print|1,2,3  or  /1/print|
        '''
        if path.count('|') == 0:
            #无参数
            return []
        else:
            args = path[1:].split('/')[1].split('|')[1].split(',')
            return args
    def parseArgs(self,args):
        return str(args).replace('[','').replace(']','')
    def _writeheader(self,wtype):
        if wtype == 'OK':
            self.send_response(200)
        else:
            self.send_response(404)
        self.send_header('Content-type','text/html')
        self.end_headers()

    '''
    给任务节点分配任务(请求中转)
    http://127.0.0.1:8001/1/print_job|1,2,3
    '''
    def send_work(self,path):
        info = path[1:].split('/')
        node = info[0]  #识别节点
        job = info[1][:info[1].find('|')]   #调用节点的函数
        args = self.getArguments(path)
        print 'Node %s will do this: %s, args ====> %s' % (node,job,args)
        if node == '1': #如果是控制节点1
            #判断功能是否合法
            if job == 'print_job':
                self._writeheader('OK')
                url = 'http://%s:%s/%s/%s|%s' % (NODE_ONE[0],NODE_ONE[1],node,job,self.parseArgs(args))
                urllib.urlopen(url.replace("'",''))
                print 'Assign success!'
                return 'SENT_JOBS'
            elif job == 'xxx':   #添加节点可调用的服务列表
                '''write other functions'''
                pass
            else:
                return 'INVALID_JOB'
        elif node == '2':  #如果是节点2
            if job == 'print_job':
                self._writeheader('OK')
                url = 'http://%s:%s/%s/%s|%s' % (NODE_TWO[0],NODE_TWO[1],node,job,self.parseArgs(args))
                urllib.urlopen(url.replace("'",''))
                print 'Assign success!'
                return 'SENT_JOBS'
            else:
                self._writeheader('ERR')
                return 'INVALID_JOB'
        else:
            return 'NODE_NOT_EXISTS'

    def do_GET(self):
        '''callback:http://127.0.0.1:8001/SUCCESS/NODE_ONE'''
        pattern1 = re.compile(r'^/([A-Z]+)/(NODE_[A-Z]+)$')
        '''command:http://127.0.0.1:8002/1/print_job|1,2,3'''
        pattern2 = re.compile(r'^/(\d+)/([a-z_]+)\|([\w,]*?)$')

        callback = pattern1.findall(self.path)  #节点回调
        command = pattern2.findall(self.path)   #外部调用

        #收到callback
        if len(callback) == 1:
            status = callback[0][0]  #识别状态
            who = callback[0][1]   #识别节点
            if status == 'SUCCESS':  
                print '%s : %s Completed!' % (who,status)
                self._writeheader('OK')
            else:
                print '%s : %s Error!' % (who,status)
                self._writeheader('OK') 
                
        #如果收到指令,就分配任务
        elif len(command) == 1:
            self._writeheader('OK')
            job = command[0][0]   #具体的task
            who = command[0][1]   #指定节点
            print 'Send to %s : %s' % (who,job)
            ret = self.send_work(self.path)  #分配task
            print 'send %s' % ret
            
        else:
            #路径出错
            print 'Path: %s Error!' % self.path
            self._writeheader('ERR')

class ThreadingHTTPServer(ThreadingMixIn,HTTPServer):
    pass

if __name__ == '__main__':
    serveraddr = ('127.0.0.1',8000)
    myCtrl = ThreadingHTTPServer(serveraddr,MyRequestHandler)
    myCtrl.serve_forever()


节点1


#coding=utf-8

'''
工作节点:
提供的接口:
<a target=_blank href="http://127.0.0.1:8001/1/print_job">http://127.0.0.1:8001/1/print_job</a>
最好的方式:
把整个业务逻辑做成线程类

'''

from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
from SocketServer import ThreadingMixIn
import urllib
import re

CONTROLLER = "http://127.0.0.1:8000/"

class MyRequestHandler(BaseHTTPRequestHandler):
    
    '''身份验证(是否为主控节点)'''
    def IDConfirm(self,path):
        req_addr = self.client_address
        if req_addr[0] != '127.0.0.1':
            return False
        else:
            return True

    '''请求响应'''
    def _writeheader(self,wtype):
        if wtype == 'OK':
            self.send_response(200)
        elif wtype == 'INVLID_HOST':  #授权失败
            self.send_response(401)
        else:
            self.send_response(404)
        self.send_header('Content-type','text/html')
        self.end_headers()

    def getArguments(self,path):
        '''
        传递参数:/1/print|1,2,3  or  /1/print|
        '''
        if path.count('|') == 0:
            #无参数
            return []
        else:
            args = path[1:].split('/')[1].split('|')[1].split(',')
            return args


    '''解析请求参数'''
    def parseRequests(self,request):
        #/1/print_job|1,2,3
        info = request[1:].split('/')
        node = info[0]  #识别节点 
        job = info[1].split('|')[0]   #调用节点的函数
        args = self.getArguments(self.path)  #获取参数列表
        return node,job,args

    '''暴露的服务'''
    def print_job(self,name='NoneArgs'):
        print 'Node_ONE: receive jobs from controller,the args is %s' % name
        return 'SUCCESS'

    '''GET回调'''
    def do_GET(self):
        #授权失败,拒绝服务 
        if not self.IDConfirm(self.path):
            self._writeheader('INVLID_HOST')
        #授权成功,提供服务
        else:
            print self.path
            '''解析请求并执行服务'''
            (node,job,args) = self.parseRequests(self.path)
            print (node,job,args)
            if job == 'print_job':  #判断服务合法性
                self._writeheader('OK')
                print 'Print Job Working...'
                ret = self.print_job(args[0])
                urllib.urlopen('%s%s%s' % (CONTROLLER,ret,'/NODE_ONE'))
            elif job == 'xxx':
                pass
            else:
                self._writeheader('ERR')
                print 'Unknown Job...'
                urllib.urlopen('%s%s' % (CONTROLLER,"ERROR/NODE_ONE"))
        

class ThreadingHTTPServer(ThreadingMixIn,HTTPServer):
    pass

if __name__ == '__main__':
    serveraddr = ('127.0.0.1',8001)
    node1 = ThreadingHTTPServer(serveraddr,MyRequestHandler)
    node1.serve_forever()

节点2的代码和节点1的代码基本一致,除了端口不同,还是贴上去算了。

#coding=utf-8

'''
工作节点:
提供的接口:
http://127.0.0.1:8001/2/print_job
'''

from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
from SocketServer import ThreadingMixIn
import urllib
import re

CONTROLLER = "http://127.0.0.1:8000/"

class MyRequestHandler(BaseHTTPRequestHandler):
    
    '''身份验证(是否为主控节点)'''
    def IDConfirm(self,path):
        req_addr = self.client_address
        if req_addr[0] != '127.0.0.1':
            return False
        else:
            return True

    '''请求响应'''
    def _writeheader(self,wtype):
        if wtype == 'OK':
            self.send_response(200)
        elif wtype == 'INVLID_HOST':  #授权失败
            self.send_response(401)
        else:
            self.send_response(404)
        self.send_header('Content-type','text/html')
        self.end_headers()

    def getArguments(self,path):
        '''
        传递参数:/1/print|1,2,3  or  /1/print|
        '''
        if path.count('|') == 0:
            #无参数
            return []
        else:
            args = path[1:].split('/')[1].split('|')[1].split(',')
            return args


    '''解析请求参数'''
    def parseRequests(self,request):
        #/1/print_job|1,2,3
        info = request[1:].split('/')
        node = info[0]  #识别节点 
        job = info[1].split('|')[0]   #调用节点的函数
        args = self.getArguments(self.path)  #获取参数列表
        return node,job,args

    '''暴露的服务'''
    def print_job(self,name='NoneArgs'):
        print 'Node_TWO: receive jobs from controller,the args is %s' % name
        return 'SUCCESS'

    '''GET回调'''
    def do_GET(self):
        #授权失败,拒绝服务 
        if not self.IDConfirm(self.path):
            self._writeheader('INVLID_HOST')
        #授权成功,提供服务
        else:
            print self.path
            '''解析请求并执行服务'''
            (node,job,args) = self.parseRequests(self.path)
            print (node,job,args)
            if job == 'print_job':  #判断服务合法性
                self._writeheader('OK')
                print 'Print Job Working...'
                ret = self.print_job(args[0])
                urllib.urlopen('%s%s%s' % (CONTROLLER,ret,'/NODE_ONE'))
            elif job == 'xxx':
                pass
            else:
                self._writeheader('ERR')
                print 'Unknown Job...'
                urllib.urlopen('%s%s' % (CONTROLLER,"ERROR/NODE_ONE"))
        

class ThreadingHTTPServer(ThreadingMixIn,HTTPServer):
    pass

if __name__ == '__main__':
    serveraddr = ('127.0.0.1',8002)
    node1 = ThreadingHTTPServer(serveraddr,MyRequestHandler)
    node1.serve_forever()

(四)


以上代码经过测试,解释器顺利执行,过程如下:

1)向主控端发送调用信息:


2)主控端接收消息:


3)节点1执行:



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值