FOFA是一款網絡空間搜索引擎,它通過進行網絡空間測繪,幫助研究人員或者企業迅速進行網絡資產匹配,例如進行漏洞影響范圍分析、應用分布統計、應用流行度等。

何為API?如果你在百度百科上搜索,你會得到如下結果:
API(Application Programming Interface,應用程序編程接口)是一些預先定義的函數,目的是提供應用程序與開發人員基於某軟件或硬件得以訪問一組例程的能力,而又無需訪問源碼,或理解內部工作機制的細節。
那么通俗一點來說,API是什么呢?從技術角度來說,API指的是應用程序編程接口。很多大型企業都會為自己的客戶建立應用程序編程接口,或供內部使用。
今天i春秋針對爬蟲FOFA里面的數據進行統計分析,即API的簡單利用,希望對大家有所幫助,本文閱讀用時約5分鍾。

環境
描述環境為文章中所用到的所有技術以及中間件並非全部安裝,可根據使用的規模進行調整使用。
語言:python2.7(必須)
模塊:requests(必須)
操作系統:MacOS 10.13.4(非必須)
分布式消息隊列管理:Celery(最后一節安裝)
日志記錄:logging(必須)
中間價:Redis(最后一節安裝)/MySQL(必須)
數據庫操作:pymysql(必須)/DBUtils(必須)
安裝環境
# 安裝requests
pip isntall requests
# 安裝celery Mac
pip install celery
# 安裝celery Windows
pip isntall celery=3.1.25
# 安裝 pymysql
pip install pymysql
# 安裝logging
pip install logging
# 安裝 DBUtils 數據庫連接池
pip install DBUtils
注:Windows安裝Celery時最好安裝3.1.25版本,不然可能會有意想不到的驚喜。
FOFA API
文檔:https://fofa.so/api
請求:https://fofa.so/api/v1/search/all
請求方式:GET
請求參數:

響應:

數據庫設計
設計數據庫存放FOFA爬蟲數據,方便統計查詢。

SQL語句
DROP TABLE IF EXISTS `fofa_spider`;
CREATE TABLE `fofa_spider` (
`id` INT (11) NOT NULL AUTO_INCREMENT,
`host` VARCHAR (255) NOT NULL,
`ip` VARCHAR (255) NOT NULL,
`port` VARCHAR (255) DEFAULT NULL,
`protocol` VARCHAR (255) NOT NULL,
`country_name` VARCHAR (255) DEFAULT NULL,
`region_name` VARCHAR (255) DEFAULT NULL,
`city_name` VARCHAR (255) DEFAULT NULL,
`fofa_sql` text NOT NULL,
`create_date` DATETIME NOT NULL,
`update_date` DATETIME NOT NULL,
PRIMARY KEY (`id`)
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;
數據庫SQL文件:https://github.com/0nise/scripts/blob/master/fofa_spider.sql
小試牛刀
本節主要講解可適用與一般的FOFA爬蟲,如果需要大批量數據爬蟲請您接着往下看。
環境
語言:python2.7
中間件:MySQL
第三方包:pymysql/requests/
場景:小規模爬蟲/一般爬蟲
通過查看FOFA API可以得知請求地址和參數,開局一句話功能全靠編。
請求中心
在發送大量的http請求時最好使用統一的HTTP請求中心,方便控制,代碼重復利用,提高效率。
session = requests.session()
# 請求頭
headers = {
'Upgrade-Insecure-Requests': '1',
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36'
}
'''
請求中心,控制程序所有HTTP請求,如果請求發生錯誤進行嘗試再次連接
@param url 請求連接
home.php?mod=space&uid=126298 請求響應結果
'''
def fofa_requests(url):
rs_content = ''
while True:
try:
rs = session.get(api_url, verify=False,headers=headers)
rs_text = rs.text
results = json.loads(rs_text)
total_size = results['size']
error = results
if results['error'] and 'None' not in results['error']:
info = u'fofa 錯誤:'+results['error']+u' 休眠30s'
logging.error(info)
time.sleep(30)
else:
rs_content = results
except Exception as e:
logging.error(u'fofa 錯誤:'+str(e.message)+u' 休眠30s')
traceback.print_exc()
time.sleep(30)
return rs_content
數據庫存儲
有了統一的請求中心接下來就該編寫入庫代碼,將爬蟲結果存入數據庫中。
'''
批量數據存入數據庫
@param results
@param page_no 當前頁數
@param page_total 總頁數
'''
def batch_insert_db(results,page_no,page_total,fofa_sql):
try:
Z = []
for result in results:
a = (str(result[0]),str(result[1]),str(result[2]),str(result[3]),str(result[4]),str(result[5]),str(result[6]),pymysql.escape_string(fofa_sql))
Z.append(a)
sql = "INSERT IGNORE INTO fofa_spider(id,host,ip,port,protocol,country_name,region_name,city_name,fofa_sql,create_date,update_date) VALUES(DEFAULT,%s,%s,%s,%s,%s,%s,%s,%s,NOW(),NOW())"
cursor.executemany(sql, Z)
connection.commit()
logging.info(u'存入數據庫ok,總數量為:'+str(len(Z))+u', page--> '+str(page_no)+'/'+str(page_total))
except Exception as e:
logging.error(u"存入數據庫錯誤,錯誤信息:"+e.message)
traceback.print_exc()
核心業務代碼
可以存入數據庫中就該寫核心的函數邏輯函數,輸入參數僅為FOFA檢索語句。
'''
fofa 爬蟲主函數
@param fofa_sql fofa查詢語句
'''
def main(fofa_sql):
base64_str = base64.b64encode(fofa_sql)
fields_str = ','.join(fields)
api_url = 'http://fofa.so/api/v1/search/all?email='+fofa_name+'&key='+fofa_key+'&fields='+fields_str+'&size='+str(page_size)+'&page='+str(page_start)+'&qbase64='+base64_str
rs = fofa_requests(api_url)
total_size = rs['size']
# 計算頁數
page_end = total_size / page_size + 1 if total_size % page_size != 0 else total_size / page_size
# 存入u 數據庫
batch_insert_db(rs['results'],page_start,page_end,fofa_sql)
for page_no in range(1,page_end+1):
api_url = 'http://fofa.so/api/v1/search/all?email='+fofa_name+'&key='+fofa_key+'&fields='+fields_str+'&size='+str(page_size)+'&page='+str(page_no)+'&qbase64='+base64_str
batch_insert_db(rs['results'],page_start,page_end,fofa_sql)
程序運行結果:

完整代碼地址:
https://github.com/0nise/scripts/blob/master/fofa_spider.py
注:運行腳本之前先配置相關配置信息(數據庫/FOFA信息)
進階
問題
針對一般的數據爬蟲,上述方法可以完美適應。但如果需要爬蟲的是為千萬級別規模的數據,上述方法就不適用了,解決方案一般有多線程/多進程/協程等。
思路
針對大規模數據爬蟲,很多人想到的是多線程/多進程/協程等方案,但是這些方案的可擴展並不是很強,如果需要調整工具需要停止程序修改程序等,這里我是使用生產者和消費的思路來處理。只需要對上述的代碼做輕微修改就可以完美的適應大規模數據爬蟲,這里我使用redis+celery的方式來實現。
Redis
Redis是一款開源的、高性能的鍵-值存儲(key-value store)。它常被稱作是一款數據結構服務器(data structure server)。
Redis的鍵值可以包括字符串(strings)類型,同時它還包括哈希(hashes)、列表(lists)、集合(sets)和 有序集合(sorted sets)等數據類型。 對於這些數據類型,你可以執行原子操作。例如:對字符串進行附加操作(append);遞增哈希中的值;向列表中增加元素;計算集合的交集、並集與差集等。
為了獲得優異的性能,Redis采用了內存中(in-memory)數據集(dataset)的方式。同時,Redis支持數據的持久化,你可以每隔一段時間將數據集轉存到磁盤上(snapshot),或者在日志尾部追加每一條操作命令(append only file,aof)。
Redis同樣支持主從復制(master-slave replication),並且具有非常快速的非阻塞首次同步( non-blocking first synchronization)、網絡斷開自動重連等功能。同時Redis還具有其它一些特性,其中包括簡單的事物支持、發布訂閱 ( pub/sub)、管道(pipeline)和虛擬內存(vm)等 。
Redis具有豐富的客戶端,支持現階段流行的大多數編程語言。
celery
簡介
Celery(芹菜)是一個簡單、靈活且可靠的,處理大量消息的分布式系統,並且提供維護這樣一個系統的必需工具。
任務隊列
任務隊列是一種在線程或機器間分發任務的機制。
消息隊列
消息隊列的輸入是工作的一個單元,稱為任務,獨立的職程(Worker)進程持續監視隊列中是否有需要處理的新任務。
Celery 用消息通信,通常使用中間人(Broker)在客戶端和職程間斡旋。這個過程從客戶端向隊列添加消息開始,之后中間人把消息派送給職程,職程對消息進行處理。如下圖所示:

Celery 系統可包含多個職程和中間人,以此獲得高可用性和橫向擴展能力。
架構
Celery的架構由三部分組成,消息中間件(message broker),任務執行單元(worker)和任務執行結果存儲(task result store)組成。
消息中間件
Celery本身不提供消息服務,但是可以方便的和第三方提供的消息中間件集成,包括,RabbitMQ,Redis,MongoDB等,這里我先去了解RabbitMQ,Redis。
任務執行單元
Worker是Celery提供的任務執行的單元,worker並發的運行在分布式的系統節點中。
任務結果存儲
Task result store用來存儲Worker執行的任務的結果,Celery支持以不同方式存儲任務的結果,包括Redis,MongoDB,Django ORM,AMQP等,這里我先不去看它是如何存儲的,就先選用Redis來存儲任務執行結果。
修改
添加celery配置信息
'''
Celery
'''
from celery import platforms,Celery
platforms.C_FORCE_ROOT = True
# Redis連接地址,如果為本機不需要做修改
broker = 'redis://127.0.0.1:6379/0'
app = Celery('fofa_spider',broker=broker)
添加核心函數
'''
celery 爬蟲
@param api_url 爬蟲URL
@param fofa_sql FOFA語句
'''
@app.task
def celery_spider(api_url,fofa_sql):
rs = fofa_requests(api_url)
batch_insert_db(rs['results'],fofa_sql)
修改業務邏輯代碼
'''
fofa 爬蟲主函數
@param fofa_sql fofa查詢語句
'''
def main(fofa_sql):
base64_str = base64.b64encode(fofa_sql)
fields_str = ','.join(fields)
api_url = 'http://fofa.so/api/v1/search/all?email='+fofa_name+'&key='+fofa_key+'&fields='+fields_str+'&size='+str(page_size)+'&page='+str(page_start)+'&qbase64='+base64_str
rs = fofa_requests(api_url)
total_size = rs['size']
# 計算頁數
page_end = total_size / page_size + 1 if total_size % page_size != 0 else total_size / page_size
# 存入數據庫
batch_insert_db(rs['results'],fofa_sql)
for page_no in range(1,page_end+1):
api_url = 'http://fofa.so/api/v1/search/all?email='+fofa_name+'&key='+fofa_key+'&fields='+fields_str+'&size='+str(page_size)+'&page='+str(page_no)+'&qbase64='+base64_str
logging.info('send task -->'+api_url)
celery_spider.delay(api_url,fofa_sql)
完整代碼地址:
https://github.com/0nise/scripts/blob/master/fofa_spider_ext.py
運行
運行python fofa_spider_ext.py發送需要爬蟲的任務信息
運行celery -A fofa_spider_ext worker -l info進行消費爬蟲
運行成功

數據庫信息
