本文内容:
巡风漏洞扫描系统资产搜集模块源码分析
框架目录 . ├── lib │ ├── __init__.py │ ├── cidr.py │ ├── common.py │ ├── icmp.py │ ├── log.py │ ├── mongo.py │ ├── scan.py │ └── start.py ├── nascan.py └── plugin └── masscan.py
主函数 读取配置信息 CONFIG_INI = get_config() def get_config () : config = {} config_info = mongo.na_db.Config.find_one({"type" : "nascan" }) for name in config_info['config' ]: if name in ['Discern_cms' , 'Discern_con' , 'Discern_lang' , 'Discern_server' ]: config[name] = format_config(name, config_info['config' ][name]['value' ]) else : config[name] = config_info['config' ][name]['value' ] return config
大概意思是从数据库里找到type
为nascan
的集合
{ "type" : "nascan" , "config" : { "Scan_list" : { "value" : "" , "info" : "网络资产探测列表(必填)" , "help" : "指定爬虫引擎探测范围,格式:192.168.1.1-192.168.1.254(修改会立刻触发资产扫描收集)" }, "Discern_cms" : { "value" : "phpmyadmin|file|index|pma_username\nphpmyadmin|file|/phpmyadmin|pma_username\nrouteros|file|index|<title>RouterOS router configuration page<\\/title>\ndestoon|file|index|Powered by DESTOON\ndestoon|file|index|DESTOON B2B SYSTEM\nU-mail|file|index|Power(ed)? by[^>]+U-Mail\nWinmail|file|index|Winmail Mail Server\nCoremail|file|index|Coremail[^>]+<\\/title>\nWinmail|header|Set-Cookie|magicwinmail\nWinmail|file|index|Powered by Winmail Server\nTurboMail|file|index|Powered by TurboMail \nXmail|file|index|\\d{4}-\\d{4}\\s*webmail.idccenter.net\noutlook|header|X-OWA-Version|.*?\noutlook|file|index|Outlook Web (Access|App)\\s*(?=<\\/title>)\nAnymacro|header|Server|AnyWebApp\nAnymacro|file|index|sec.anymacro.com\nExtMail|file|index|powered by.*?Extmail\nLotus|file|index|IBM Lotus iNotes[^>]+(?=<\\/title>)\nLotus|file|index|iwaredir.nsf\nSquirrelMail|file|index|SquirrelMail Project Team\nSquirrelMail|header|Set-Cookie|SQMSESSID\nSquirrelMail|file|index|SquirrelMail\neqmail|file|index|Powered by EQMail\nTMailer|file|index|TMailer Collaboration Suite Web Client \nzimbra|header|Set-Cookie|ZM_TEST\nzimbra|file|index|zimbra[^>]+(?=<\\/title>)\nzimbra|file|index|Zimbra,?\\s*Inc. All rights reserved.\nbxemail|file|index|[email protected] \nHorde|file|index|<title>[^>]+?Horde\nHorde|file|index|\\/themes\\/graphics\\/horde-power1.png\nAtmail|file|index|powered by Atmail\nIlohaMail|header|Set-Cookie|IMAIL_TEST_COOKIE\nIlohaMail|header|SESS_KEY|.*?\nIlohaMail|file|index|powered by[^>]+IlohaMail\nfangmail|file|index|fangmail\nRoundcube|file|index|Roundcube\nmailbase|header|Set-Cookie|\\s*(mb_lang|mb_ui_type|mb_cus_type)\nmailbase|file|index|MailBase[^<>]+(?=<\\/title>)\nKXmail|file|index|Powered By\\s?<[^>]+>\\s?KXmail\ntongda|file|index|href=\"/images/tongda\\.ico\"\ntrs_wcm|file|index|<title[^>]+>TRS WCM[^<]+</title>\ntrs_wcm|file|index|href=\"/wcm/console/auth/reg_newuser.jsp\"\nmymps|file|index|powered by[^&]+Mymps.*?\nmymps|file|index|wcontent=\"mymps\nmailgard|file|index|mailgard\\swebmail\ndiscuz|file|/robots.txt|discuz\ndiscuz|file|/robots.txt|discuz\nphpwind|file|/robots.txt|phpwind\nphpcms|file|/robots.txt|phpcms\nphp168|file|/robots.txt|php168\nqibosoft|file|/robots.txt|qibocms\nemlog|file|/robots.txt|robots.txt for emlog\nwecenter|file|/robots.txt|robots.txt for wecenter\nbbsmax|file|/robots.txt|bbsmax\nshopnc|file|/robots.txt|robots.txt for shopnc\nhdwike|file|/robots.txt|robots.txt for hdwiki\nphpdisk|file|/robots.txt|PHPDisk\ndedecms|file|/data/admin/ver.txt|20110812\ndedecms|file|/data/admin/ver.txt|20111111\ndedecms|file|/data/admin/ver.txt|20120709\ndedecms|file|/data/admin/ver.txt|20140814\ndedecms|file|/data/admin/verifies.txt|20081204\ndedecms|file|/data/admin/verifies.txt|20100324\ndedecms|file|/data/admin/verifies.txt|20100514\ndedecms|file|/data/admin/verifies.txt|20110216\nwordpress|file|/robots.txt|wordpress\nwordpress|file|/license.txt|wordpress\nwordpress|file|/readme.txt|wordpress\nwordpress|file|/help.txt|wordpress\nwordpress|file|/readme.html|wordpress\nwordpress|file|/wp-admin/css/colors-classic.css|wordpress\nwordpress|file|/wp-admin/js/media-upload.dev.js|wordpress\nwordpress|file|/wp-content/plugins/akismet/akismet.js|wordpress\nwordpress|file|/wp-content/themes/classic/rtl.css|wordpress\nwordpress|file|/wp-includes/css/buttons.css|wordpress\nz-blog|file|/license.txt|z-bolg\nz-blog|file|/SCRIPT/common.js|z-bolg\nsouthidc|file|/Ads/left.js|southidc\nsouthidc|file|/Css/Style.css|southidc\nsouthidc|file|/Images/ad.js|southidc\nsouthidc|file|/Script/Html.js|southidc\nsiteserver|file|/robots.txt|\\/SiteFiles\\/\nsiteserver|file|/SiteFiles/Inner/Register/script.js|stlUserRegister\nenableq|file|/License/index.php|<td>EnableQ\nenableq|file|/robots.txt|robots.txt for EnableQ\ntrs_wcm|file|/wcm/app/login.jsp|TRS WCM\ntrs_wcm|file|/wcm/app/login.jsp|href=\"/wcm/console/auth/reg_newuser.jsp\"\nmymps|file|/robots.txt|mymps\nigenus|file|/help/|igenus\nmailgard|file|/help/io_login.html|webmail" , "info" : "cms识别规则" , "help" : "用于识别WEB的CMS,格式:CMS名称|判断方式|判断对象|判断正则。识别信息保存于tag记录中,可使用tag:dedecms方式进行搜索。" }, "Discern_con" : { "value" : "jboss|header|X-Powered-By|jboss\njboss|file|jboss.css|youcandoit.jpg\njboss|file|is_test|JBossWeb\naxis|file|axis2|axis2-web/images/axis_l.jpg\nweblogic|file|is_test|Hypertext Transfer Protocol\nweblogic|file|console/css/login.css|Login_GC_LoginPage_Bg.gif\nglassfish|file|resource/js/cj.js|glassfish.dev.java.net\nglassfish|header|server|GlassFish\njenkins|header|X-Jenkins|.*?\njenkins|file|index|\\[Jenkins\\]\nresin|header|server|resin\ntomcat|file|is_test|Apache Tomcat\napache|header|server|apache\niis|header|server|iis\njetty|header|server|jetty\nnginx|header|server|nginx\ncisco|header|server|cisco\ncouchdb|header|server|couchdb\ntplink|header|WWW-Authenticate|TP-LINK\nh3c|header|WWW-Authenticate|h3c\nh3c|file|index|/web/device/login\nhuawei|header|WWW-Authenticate|huawei\nnetgear|header|WWW-Authenticate|netgear\nhikvision|header|server|DNVRS-Webs\nhikvision|header|server|App-webs\nhikvision|header|server|DVRDVS-Webs\nhikvision|header|server|Hikvision-Webs\ntengine|header|server|Tengine" , "info" : "组件容器识别规则" , "help" : "用于识别WEB的容器、中间件等组件信息,格式:组件名称|判断方式|判断对象|判断正则。识别信息保存于tag记录中,可使用tag:tomcat方式进行搜索。" }, "Discern_lang" : { "value" : "php|header|Server|php\nphp|header|X-Powered-By|php\nphp|header|Set-Cookie|PHPSSIONID\njsp|header|Set-Cookie|JSESSIONID\nasp|header|Set-Cookie|ASPSESSION\naspx|header|Set-Cookie|ASP.NET_SessionId\naspx|header|X-AspNet-Version|version\naspx|file|index|<input[^>]+name=\\\"__VIEWSTATE\naspx|file|index|<a[^>]*?href=('|\")[^http].*?\\.aspx(\\?|\\1)\nasp|file|index|<a[^>]*?href=('|\")[^http].*?\\.asp(\\?|\\1)\nphp|file|index|<a[^>]*?href=('|\")[^http].*?\\.php(\\?|\\1)\njsp|file|index|<a[^>]*?href=('|\")[^http].*?\\.jsp(\\?|\\1)" , "info" : "代码语言识别规则" , "help" : "用于识别WEB的开发语言,识别信息保存于tag记录中,可使用tag:php方式进行搜索。" }, "Discern_server" : { "value" : "ftp|21|banner|^220.*?ftp|^220-|^220 Service|^220 FileZilla\nssh|22|banner|^ssh-\ntelnet|23|banner|^\\xff[\\xfa-\\xfe]|^\\x54\\x65\\x6c|Telnet\nsmtp|25|banner|^220.*?smtp\ndns|53|default|\npop3|110|banner|\\+OK.*?pop3\nnetbios|139|default|\nimap|143|banner|^\\* OK.*?imap\nldap|389|default|\nsmb|445|default|\nsmtps|465|default|\nrsync|873|banner|^@RSYNCD|^@ERROR\nimaps|993|default|\npop3|995|banner|\\+OK\nproxy|1080|\\x05\\x01\\x00\\x01|^\\x05\\x00\npptp|1723|default|\nmssql|1433|\\x12\\x01\\x00\\x34\\x00\\x00\\x00\\x00\\x00\\x00\\x15\\x00\\x06\\x01\\x00\\x1b\\x00\\x01\\x02\\x00\\x1c\\x00\\x0c\\x03\\x00\\x28\\x00\\x04\\xff\\x08\\x00\\x01\\x55\\x00\\x00\\x00\\x4d\\x53\\x53\\x51\\x4c\\x53\\x65\\x72\\x76\\x65\\x72\\x00\\x48\\x0f\\x00\\x00|^\\x04\\x01\noracle|1521|\\x00\\x3a\\x00\\x00\\x01\\x00\\x00\\x00\\x01\\x39\\x01\\x2c\\x00\\x00\\x08\\x00\\x7f\\xff\\xc6\\x0e\\x00\\x00\\x01\\x00\\x00\\x00\\x00\\x3a\\x00\\x00\\x08\\x00\\x41\\x41\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00|\\x00Y\\x00\nmysql|3306|banner|^.\\0\\0\\0.*?mysql|^.\\0\\0\\0\\n|.*?MariaDB server\nrdp|3389|\\x03\\x00\\x00\\x13\\x0E\\xE0\\x00\\x00\\x00\\x00\\x00\\x01\\x00\\x08\\x00\\x03\\x00\\x00\\x00|\\x03\\x00\\x00\\x13\nsvn|3690|default|\npostgresql|5432|\\x00\\x00\\x00\\x54\\x00\\x03\\x00\\x00\\x75\\x73\\x65\\x72\\x00\\x70\\x6f\\x73\\x74\\x67\\x72\\x65\\x73\\x00\\x64\\x61\\x74\\x61\\x62\\x61\\x73\\x65\\x00\\x70\\x6f\\x73\\x74\\x67\\x72\\x65\\x73\\x00\\x61\\x70\\x70\\x6c\\x69\\x63\\x61\\x74\\x69\\x6f\\x6e\\x5f\\x6e\\x61\\x6d\\x65\\x00\\x70\\x73\\x71\\x6c\\x00\\x63\\x6c\\x69\\x65\\x6e\\x74\\x5f\\x65\\x6e\\x63\\x6f\\x64\\x69\\x6e\\x67\\x00\\x55\\x54\\x46\\x38\\x00\\x00|^R\\x00\\x00\\x00\nvnc|5900|banner|^RFB\nredis|6379|info\\r\\n|redis\nelasticsearch|9200|GET /_cat HTTP/1.1\\r\\n\\r\\n|/_cat/master\nmemcache|11211|\\x80\\x0b\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00|^\\x81\\x0b\nmongodb|27017|\\x00\\x00\\x00\\xa7A\\x00\\x00\\x00\\x00\\x00\\x00\\xd4\\x07\\x00\\x00\\x00\\x00\\x00\\x00admin.$cmd\\x00\\x00\\x00\\x00\\x00\\xff\\xff\\xff\\xff\\x13\\x00\\x00\\x00\\x10ismaster\\x00\\x01\\x00\\x00\\x00\\x00|ismaster\nzookeeper|2181|stat|Zookeeper version" , "info" : "服务类型识别规则" , "help" : "用于识别开放端口上所运行的服务信息,格式:服务名称|端口号|匹配模式|匹配正则,结果以正则匹配为优先,无正则内容时使用端口号进行默认匹配,再无结果时即主动发送探测包进行识别,识别结果保存于server记录中,可使用server:ftp方式进行搜索" }, "Port_list" : { "value" : "1|21\n22\n23\n25\n53\n80\n110\n139\n143\n389\n443\n445\n465\n873\n993\n995\n1080\n1311\n1723\n1433\n1521\n3000\n3001\n3002\n3306\n3389\n3690\n4000\n5432\n5900\n6379\n7001\n8000\n8001\n8080\n8081\n8888\n9200\n9300\n9080\n9090\n9999\n11211\n27017" , "info" : "端口探测列表(TCP探测)" , "help" : "默认探测端口列表,可开启ICMP,开启后只对存活的IP地址进行探测" }, "Masscan" : { "value" : "0|20000|/root/xunfeng/masscan/linux_64/masscan" , "info" : "启用MASSCAN" , "help" : "可启用MASSCAN(自行安装)代替默认的端口扫描方式,路径地址需配置全路径,MASSCAN探测端口范围为1-65535且强制进行ICMP存活检测,请根据网络实际情况设置发包速率。" }, "Timeout" : { "value" : "8" , "info" : "连接超时时间(TCP)" , "help" : "WEB请求的超时时间,socket连接超时为值的一半。" }, "Cycle" : { "value" : "1|9" , "info" : "资产探测周期" , "help" : "设置资产探测的扫描周期,格式:天数|小时,例如 5|16,即每5天的16点开始进行扫描。" }, "Thread" : { "value" : "200" , "info" : "最大线程数" , "help" : "爬虫引擎的最大线程数限制" }, "White_list" : { "value" : "" , "info" : "资产发现白名单" , "help" : "不对白名单内的IP列表进行资产发现。格式:x.x.x.x,以行分割" } } }
然后判断是否为['Discern_cms', 'Discern_con', 'Discern_lang', 'Discern_server']
这里面的集合
这里分类如下:
Discern_cms
:cms识别规则
Discern_con
:组件容器识别规则
Discern_lang
:代码语言识别规则
Discern_server
:服务类型识别规则
是的话就进行config
格式化处理:
def format_config (config_name, config_info) : mark_list = [] try : config_file = config_info.split('\n' ) if config_name == 'Discern_server' : for mark in config_file: name, port, mode, reg = mark.strip().split("|" , 3 ) mark_list.append([name, port, mode, reg]) else : for mark in config_file: name, location, key, value = mark.strip().split("|" , 3 ) mark_list.append([name.lower(), location, key, value]) except Exception, e: print e return mark_list
先每一行分开
如果是服务类型识别规则,就分割成name, port, mode, reg
不是服务类型识别规则的话,就分割成name, location, key, value
然后根据列表方式返回到一个新的列表,最后返回总列表
如果不在这几个识别类型里的话,就直接返回他的查询的value
,就比如:
"Timeout": { "value": "8", "info": "连接超时时间(TCP)", "help": "WEB请求的超时时间,socket连接超时为值的一半。" },
它实际上就是在设置这一块部分
读取统计信息 STATISTICS = get_statistics() def get_statistics () : date_ = datetime.datetime.now().strftime('%Y-%m-%d' ) now_stati = mongo.na_db.Statistics.find_one({"date" : date_}) if not now_stati: now_stati = {date_: {"add" : 0 , "update" : 0 , "delete" : 0 }} return now_stati else : return {date_: now_stati['info' ]}
根据年月日来读取,所以说他数据不能实时更新,只能每天更新一次统计信息
心跳检测 def monitor (CONFIG_INI, STATISTICS, NACHANGE) : while True : try : time_ = datetime.datetime.now() date_ = time_.strftime('%Y-%m-%d' ) mongo.na_db.Heartbeat.update({"name" : "heartbeat" }, {"$set" : {"up_time" : time_}}) if date_ not in STATISTICS: STATISTICS[date_] = {"add" : 0 , "update" : 0 , "delete" : 0 } mongo.na_db.Statistics.update({"date" : date_}, {"$set" : {"info" : STATISTICS[date_]}}, upsert=True ) new_config = get_config() if base64.b64encode(CONFIG_INI["Scan_list" ]) != base64.b64encode(new_config["Scan_list" ]):NACHANGE[0 ] = 1 CONFIG_INI.clear() CONFIG_INI.update(new_config) except Exception, e: print e time.sleep(30 )
它是对之前的配置信息和统计信息做心跳检测的(每隔30秒)
大概逻辑是判断是否扫描列表更新了,更新了的话就更新字典,设置NACHANGE[0]=1
并且把原先的设置清除了,然后重新获取数据
无效记录删除 thread.start_new_thread(cruise, (STATISTICS, MASSCAN_AC)) def cruise (STATISTICS,MASSCAN_AC) : while True : now_str = datetime.datetime.now() week = int(now_str.weekday()) hour = int(now_str.hour) if week >= 1 and week <= 5 and hour >= 9 and hour <= 18 : try : data = mongo.NA_INFO.find().sort("time" , 1 ) for history_info in data: while True : if MASSCAN_AC[0 ]: time.sleep(10 ) else : break ip = history_info['ip' ] port = history_info['port' ] try : sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((ip, int(port))) sock.close() except Exception, e: time_ = datetime.datetime.now() date_ = time_.strftime('%Y-%m-%d' ) mongo.NA_INFO.remove({"ip" : ip, "port" : port}) log.write('info' , None , 0 , '%s:%s delete' % (ip, port)) STATISTICS[date_]['delete' ] += 1 del history_info["_id" ] history_info['del_time' ] = time_ history_info['type' ] = 'delete' mongo.NA_HISTORY.insert(history_info) except : pass time.sleep(3600 )
大概逻辑就是判断是否为工作时间或者MASSCAN
是否在扫描,然后通过与这些ip和端口
尝试进行socket
连接来判断是否存活
通过异常捕捉来实现删除,然后写入删除记录
资产扫描 while True : now_time = time.localtime() now_hour = now_time.tm_hour now_day = now_time.tm_mday now_date = str(now_time.tm_year) + \ str(now_time.tm_mon) + str(now_day) cy_day, ac_hour = CONFIG_INI['Cycle' ].split('|' ) log.write('info' , None , 0 , u'扫描规则: ' + str(CONFIG_INI['Cycle' ])) if (now_hour == int(ac_hour) and now_day % int(cy_day) == 0 and now_date not in ac_data) or NACHANGE[0 ]: ac_data.append(now_date) NACHANGE[0 ] = 0 log.write('info' , None , 0 , u'开始扫描' ) s = start(CONFIG_INI) s.masscan_ac = MASSCAN_AC s.statistics = STATISTICS s.run() time.sleep(60 )
先是拼接当前时间,然后获取计划资产扫描的周期
然后写入log
当中
然后判断是否为当前计划扫描周期的时间或者有新的扫描任务(通过心跳检测来设置)
(now_hour == int(ac_hour) and now_day % int(cy_day) == 0 and now_date not in ac_data) or NACHANGE[0]
然后就把当前的时间加入ac_data
列表,把NACHANGE[0]
重置为0,写入日志当中,开始扫描
start.py s = start(CONFIG_INI) s.masscan_ac = MASSCAN_AC s.statistics = STATISTICS s.run()
这个类有点多,我们分开来看
__init__
def __init__ (self, config) : self.config_ini = config self.queue = Queue.Queue() self.thread = int(self.config_ini['Thread' ]) self.scan_list = self.config_ini['Scan_list' ].split('\n' ) self.mode = int(self.config_ini['Masscan' ].split('|' )[0 ]) self.icmp = int(self.config_ini['Port_list' ].split('|' )[0 ]) self.white_list = self.config_ini.get('White_list' , '' ).split('\n' )
读取了扫描的配置信息,然后设置了一个队列,读取了扫描线程、IP列表、Masscan配置信息、端口信息以及白名单
"Scan_list": { "value": "", "info": "网络资产探测列表(必填)", "help": "指定爬虫引擎探测范围,格式:192.168.1.1-192.168.1.254(修改会立刻触发资产扫描收集)" }, "Masscan": { "value": "0|20000|/root/xunfeng/masscan/linux_64/masscan", "info": "启用MASSCAN", "help": "可启用MASSCAN(自行安装)代替默认的端口扫描方式,路径地址需配置全路径,MASSCAN探测端口范围为1-65535且强制进行ICMP存活检测,请根据网络实际情况设置发包速率。" }, "Port_list": { "value": "1|21\n22\n23\n25\n53\n80\n110\n139\n143\n389\n443\n445\n465\n873\n993\n995\n1080\n1311\n1723\n1433\n1521\n3000\n3001\n3002\n3306\n3389\n3690\n4000\n5432\n5900\n6379\n7001\n8000\n8001\n8080\n8081\n8888\n9200\n9300\n9080\n9090\n9999\n11211\n27017", "info": "端口探测列表(TCP探测)", "help": "默认探测端口列表,可开启ICMP,开启后只对存活的IP地址进行探测" }, "White_list": { "value": "", "info": "资产发现白名单", "help": "不对白名单内的IP列表进行资产发现。格式:x.x.x.x,以行分割" }
run def run (self) : global AC_PORT_LIST all_ip_list = [] for ip in self.scan_list: if "/" in ip: ip = cidr.CIDR(ip) if not ip: continue
cidr.CIDR(ip)
这个我想了一会才发现这是子网掩码IP转IP段的
def CIDR (input) : try : ip = input.split('/' )[0 ] pos = int(input.split('/' )[1 ]) ipstr = '' for i in ip.split('.' ): ipstr = ipstr + bin(int(i)).replace('0b' , '' ).zfill(8 ) pstr = '1' * pos + '0' * (32 - pos) res = stringxor(ipstr, pstr) _ip = getip(res, 0 ), getip(res[0 :pos] + '1' * (32 - pos), 1 ) return _ip[0 ] + "-" + _ip[1 ] except : return
但这里我推荐使用IPy
这个库
重复造轮子会导致效率低下
然后根据白名单删除
for white_ip in self.white_list: if white_ip in ip_list: ip_list.remove(white_ip)
然后看是否启用Masscan
我们这里先看启用Masscan
的情况
if self.mode == 1 : masscan_path = self.config_ini['Masscan' ].split('|' )[2 ] masscan_rate = self.config_ini['Masscan' ].split('|' )[1 ] if self.icmp: ip_list = self.get_ac_ip(ip_list) self.masscan_ac[0 ] = 1 AC_PORT_LIST = self.masscan( ip_list, masscan_path, masscan_rate) if not AC_PORT_LIST: continue self.masscan_ac[0 ] = 0 for ip_str in AC_PORT_LIST.keys(): self.queue.put(ip_str) self.scan_start() else : all_ip_list.extend(ip_list)
读取了masscan
的路径以及发包速率
然后判断是否icmp
存活检测
ip_list = self.get_ac_ip(ip_list) def get_ac_ip (self, ip_list) : try : s = icmp.Nscan() ipPool = set(ip_list) return s.mPing(ipPool) except Exception, e: print 'The current user permissions unable to send icmp packets' return ip_list
这里看一下icmp
的扫描流程
icmp.py
class SendPingThr (threading.Thread) : def __init__ (self, ipPool, icmpPacket, icmpSocket, timeout=3 ) : threading.Thread.__init__(self) self.Sock = icmpSocket self.ipPool = ipPool self.packet = icmpPacket self.timeout = timeout self.Sock.settimeout(timeout + 1 ) def run (self) : for ip in self.ipPool: try : self.Sock.sendto(self.packet, (ip, 0 )) except socket.timeout: break except : pass time.sleep(self.timeout) class Nscan : def __init__ (self, timeout=3 ) : self.timeout = timeout self.__data = struct.pack('d' , time.time()) self.__id = os.getpid() if self.__id >= 65535 : self.__id = 65534 @property def __icmpSocket (self) : Sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.getprotobyname("icmp" )) return Sock def __inCksum (self, packet) : if len(packet) & 1 : packet = packet + '\0' words = array.array('h' , packet) sum = 0 for word in words: sum += (word & 0xffff ) sum = (sum >> 16 ) + (sum & 0xffff ) sum = sum + (sum >> 16 ) return (~sum) & 0xffff @property def __icmpPacket (self) : header = struct.pack('bbHHh' , 8 , 0 , 0 , self.__id, 0 ) packet = header + self.__data chkSum = self.__inCksum(packet) header = struct.pack('bbHHh' , 8 , 0 , chkSum, self.__id, 0 ) return header + self.__data def mPing (self, ipPool) : Sock = self.__icmpSocket Sock.settimeout(self.timeout) packet = self.__icmpPacket recvFroms = set() sendThr = SendPingThr(ipPool, packet, Sock, self.timeout) sendThr.start() while True : try : ac_ip = Sock.recvfrom(1024 )[1 ][0 ] if ac_ip not in recvFroms: log.write("active" , ac_ip, 0 , None ) recvFroms.add(ac_ip) except Exception: pass finally : if not sendThr.isAlive(): break return recvFroms & ipPool
大概流程就是使用原始套接字进行数据包构造
然后实例化一个SendPingThr
类进行多线程发包
然后通过set
的一个集合来去重,如果收到响应包,说明存活,最后判断线程是否存活,不存货则退出死循环,最终返回我们的扫描结果集合
然后回到我们的start.py
self.masscan_ac[0 ] = 1 AC_PORT_LIST = self.masscan( ip_list, masscan_path, masscan_rate) if not AC_PORT_LIST: continue self.masscan_ac[0 ] = 0 def masscan (self, ip, masscan_path, masscan_rate) : try : if len(ip) == 0 : return sys.path.append(sys.path[0 ] + "/plugin" ) m_scan = __import__("masscan" ) result = m_scan.run(ip, masscan_path, masscan_rate) return result except Exception, e: print e print 'No masscan plugin detected'
动态调用masscan
,然后启动扫描,masscan
放到最后看
然后把扫描结果加入到队列里
if not AC_PORT_LIST: continue self.masscan_ac[0 ] = 0 for ip_str in AC_PORT_LIST.keys(): self.queue.put(ip_str) self.scan_start()
看一下scan_start
函数
def scan_start (self) : for i in range(self.thread): t = ThreadNum(self.queue) t.setDaemon(True ) t.mode = self.mode t.config_ini = self.config_ini t.statistics = self.statistics t.start() self.queue.join() class ThreadNum (threading.Thread) : def __init__ (self, queue) : threading.Thread.__init__(self) self.queue = queue def run (self) : while True : try : task_host = self.queue.get(block=False ) except : break try : if self.mode: port_list = AC_PORT_LIST[task_host] else : port_list = self.config_ini['Port_list' ].split('|' )[ 1 ].split('\n' ) _s = scan.scan(task_host, port_list) _s.config_ini = self.config_ini _s.statistics = self.statistics _s.run() except Exception, e: print e finally : self.queue.task_done()
大概流程就是实例化了一个ThreadNum
类
里面从队列中调取IP地址,然后读取扫描的端口调用scan.scan
类
这个类文件很多,我们简单说一下流程
使用原始套接字进行端口扫描,讲扫到的端口写入Mongo
,合并历史扫描数据
服务识别,使用default
和banner
区分
如果识别的服务名为空,则尝试web访问
如果想调用masscan
进行扫描的话
import osdef run (ip_list,path,rate) : try : ip_file = open('target.log' ,'w' ) ip_file.write("\n" .join(ip_list)) ip_file.close() path = str(path).translate(None , ';|&`\n' ) rate = str(rate).translate(None , ';|&`\n' ) if not os.path.exists(path):return os.system("%s -p1-65535 -iL target.log -oL tmp.log --randomize-hosts --rate=%s" %(path,rate)) result_file = open('tmp.log' , 'r' ) result_json = result_file.readlines() result_file.close() del result_json[0 ] del result_json[-1 ] open_list = {} for res in result_json: try : ip = res.split()[3 ] port = res.split()[2 ] if ip in open_list: open_list[ip].append(port) else : open_list[ip] = [port] except :pass os.remove('target.log' ) os.remove('tmp.log' ) return open_list except : pass
很粗暴,通过读写文件存储中间结果,然后写入变量后删除临时文件
总结 大概流程就是这样吧,好多东西只有当自己亲手研究时候,才能发现其中的优点以及存在的问题
巡风我认为的问题
好多代码没有重用,就比如资产扫描和漏洞扫描里的get_code函数在两个模块写了两次
使用了urllib2,可以用request代替
心跳间距过长,可能导入配置后得最少30秒才能启动扫描
优点
使用两个模块的心跳检测来触发扫描,这样可以逻辑分离,并且可以通过前端修改配置来通过心跳检测来触发扫描
支持插件热更新,原理是通过心跳检测新版本然后下载,通过前端触发安装
模块分开编写,容易修改、重构
自定义payload方便,有固定的格式,并且可以通过心跳检测更新配置
支持调用masscan,把扫描目标写入target.log和tmp.log来临时保存,读取后删除文件