前言
在掃描一個網站的時候,在掃描的生命周期的不同階段有一些信息是我們想要獲取的:比如在一個網站的基礎信息搜集之后,我們還想對它進行端口掃描;比如我們想要檢測這個網站是否存在WAF,WAF的版本,如果存在WAF的話后續的掃描就不用繼續執行了;又比如在獲取了一個網站中的動態URL之后,我們想要得到JS文件里面的所有接口信息等等。
同時這些需求也不是所有人都需要,因為功能越多掃描起來的速度就越慢。
它們並不是一個漏洞檢測POC,因為我們想要獲取的是一段探測的信息,並不只有True
,False
兩種狀態
所以我們很容易想到使用插件來實現這個功能
Python import() 函數
我們主要使用__import__()
函數來實現這個功能,__import__()
函數用於動態加載類和函數,如果一個模塊經常變化就可以使用 __import__()
來動態載入。
用法如下
a.py
文件
#!/usr/bin/env python
#encoding: utf-8
import os
print ('在 a.py 文件中 %s' % id(os))
test.py
文件
#!/usr/bin/env python
#encoding: utf-8
import sys
__import__('a') # 導入 a.py 模塊
執行test.py
文件,輸出結果為
在 a.py 文件中 4394716136
簡單來說,我們只需要將插件放置在某一個特定的目錄下,然后讀取該目錄下的全部插件,用__import__()
函數依次執行每個插件的運行函數即可,最后統一將結果返回存儲。
整體實現
先簡單實現了這個動態調用的功能,后期根據需要繼續改進插件部分的編寫
def scanPlugin(url,plugin,tid):
tempPlugin = __import__("plugins.{}".format(plugin), fromlist=[plugin])
result=tempPlugin.run(url)
saveExts(result, tid, plugin)
saveExts()
用來存儲掃描得到的信息,run()
函數是每個插件文件里面都需要寫的,用來執行插件主體邏輯
一個端口掃描的插件如下:
import re
import socket
import nmap
def run(host):
'''
this is portscan exts example :D
:param host:
:return:
'''
pattern = re.compile('^\d+\.\d+\.\d+\.\d+(:(\d+))?$')
content = ""
if not pattern.findall(host):
host = socket.gethostbyname(host)
if pattern.findall(host) and ":" in host:
host=host.split(":")[0]
nm = nmap.PortScanner()
try:
nm.scan(host, arguments='-Pn,-sS --host-timeout=50 --max-retries=3')
for proto in nm[host].all_protocols():
lport = list(nm[host][proto].keys())
for port in lport:
if nm[host][proto][port]['state'] == "open":
service = nm[host][proto][port]['name']
content += '[*] 主機 ' + host + ' 協議:' + proto + '\t開放端口號:' + str(port) + '\t端口服務:' + service + "\n"
return content
except Exception as e:
nmap.sys.exit(0)
pass
def test():
print('hi')
if __name__ == '__main__':
print(run("127.0.0.1"))
掃描本地127.0.0.1
之后得到的結果為:
可以看到掃描端口的結果是正確的,但隨之而來又存在一個新的問題,請看導入包這一部分
import re
import socket
import nmap
這里的nmap
包在我們本地的測試環境中是存在的,但是如果有用戶上傳的插件里面導入了一些我們沒有的包,運行插件的時候自然會報錯,導致插件導入之后也不能正常運行
考慮到很多編程語言都會有預處理
這個過程,我們可以也可以對掃描器插件加載進行一次預處理,在刷新插件的時候,把插件內部需要導入,但是python環境里面不存在的包下載下來
這里只考慮了python
腳本中按照import requests
這種形式的導入,沒有考慮變形的from xxx import xxx
或者from xxx import xxx as xxx
其正則匹配規則為pattern = re.compile("^import (.*?)$")
一個簡單的示例插件plugin1.py
import requests
import re
def run(url):
return "test {}".format(url)
def test():
print('hi')
if __name__ == '__main__':
test()
里面導入了requests
和re
包,實際上requests
在我本地已經下載了,re
是python
內置的包
在python
代碼里面下載包的代碼如下,這里直接用了清華源
from pip._internal import main
def install(package,source="https://pypi.tuna.tsinghua.edu.cn/simple"):
main(['install', package,'-i',source])
預處理的函數為
def getDepends(dir):
pattern = re.compile("^import (.*?)$")
moduleKeys=list(sys.modules.keys())
currdir = os.path.join(os.path.dirname(os.path.dirname(__file__)),dir)
for files in os.listdir(currdir):
if os.path.splitext(files)[1] == '.py' and not files.startswith("_"):
filename = os.path.splitext(files)[0]
filepath=currdir+"/"+filename+".py"
logging.info("{} is Checking".format(filepath))
with open(filepath, 'r') as f:
for line in f.readlines():
result=pattern.findall(line.strip())
if result:
name=result[0]
if name and checkLib(name,moduleKeys):
logging.info("{} Lib is Loading".format(name))
install(name)
else:
print("{} Lib is Loaded".format(name))
return
name
是我們獲得的包名,checkLib(name,moduleKeys)
函數用來檢測包是否下載,未下載則返回True
考慮到內置包和pip
下載包,所以在try
里面分了兩步進行
def checkLib(libName,moduleKeys):
try:
if libName in moduleKeys:
return False
importlib.import_module(libName)
return False
except Exception as e:
logging.warning(e)
return True
檢測結果為
使用pip uninstall
卸載掉requests
包,重新運行
下載成功,實現了預處理下載的功能
參考鏈接
https://www.jianshu.com/p/a472f44c7161
https://www.runoob.com/python/python-func-__import__.html
https://www.jb51.net/article/232964.htm
END
建了一個微信的安全交流群,歡迎添加我微信備注進群
,一起來聊天吹水哇,以及一個會發布安全相關內容的公眾號,歡迎關注 😃

