(一)
馬上做一個分布式漏洞掃描與攻擊的項目,這段時間一直選技術路線以及做大量的demo。這篇是記錄我在主控端與各個漏洞掃描節點協調通訊上的一個demo代碼。我選擇使用類似於WebService的技術,即各個節點暴露WebService介面,主控端去調用並且拿到回調。WebService基於SOAP協議通訊我覺得太麻煩,因為我的需求是主控端分發任務隊列給節點,節點執行,完成之後回調,主控端進行處理,非同步呼叫,邏輯簡單。最終我試了一下基於GET方法的類似於WebService的方法,自己寫互動的過程,這樣編碼也比較方便: - )。 這裡我不禁要吐槽下Python開發一個WebService配置運行環境實在是蛋疼,但是調用WebService倒是很容易: - (。
(二)
我使用了BaseHTTPServer,在主控端和各個節點上都維護一個多線程Server進行互動,這樣編碼也比較簡單,可以把精力主要投放在設計好通訊細節以及處理各種突發事件即可。
這個過程的邏輯如下:
主控端接收外部調用
判斷調用合法性,並重新組裝請求並發給掃描節點
節點收到請求,進行身分識別驗證,並提取出資訊,根據資訊(服務名、參數)開啟背景工作執行緒。
節點執行完進行回調。
整個過程非同步實現,畫了張圖如下:
(三)
使用Python的BaseHTTPServer實現,這個模組可以輕易實現一個HTTP伺服器,使用裡面的do_GET回呼函數執行具體的邏輯,識別是外部調用請求還是回調請求,並對請求進行解析以及處理異常情況。
由於只是探測階段,所以只是簡單做了一個小demo,證明這種處理方式的可行性。下面就是貼代碼了:
控制端:
#coding=utf-8'''控制節點邏輯:1.接收外部介面的調用,格式為:如控制節點1,執行其內部的print_job任務http://127.0.0.1:8000/1/print_job2.接收節點的Callback如收到:http://127.0.0.1:8001/SUCCESS/NODE_ONE表示節點1的任務完成如果是:http://127.0.0.1:8001/ERROR/NODE_ONE表示節點1執行出錯3.轉寄給節點各個節點分析URLhttp://127.0.0.1:8001/1/print_job表示執行方法為print_job'''from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServerfrom SocketServer import ThreadingMixInimport urllibimport reimport threadingNODE_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): passif __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, HTTPServerfrom SocketServer import ThreadingMixInimport urllibimport reCONTROLLER = "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): passif __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, HTTPServerfrom SocketServer import ThreadingMixInimport urllibimport reCONTROLLER = "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): passif __name__ == '__main__': serveraddr = ('127.0.0.1',8002) node1 = ThreadingHTTPServer(serveraddr,MyRequestHandler) node1.serve_forever()
(四)
以上代碼經過測試,解譯器順利執行,過程如下:
(1)向主控端發送調用資訊:
(2)主控端接收訊息:
(3)節點1執行: