python插件式框架開發


前言

在掃描一個網站的時候,在掃描的生命周期的不同階段有一些信息是我們想要獲取的:比如在一個網站的基礎信息搜集之后,我們還想對它進行端口掃描;比如我們想要檢測這個網站是否存在WAF,WAF的版本,如果存在WAF的話后續的掃描就不用繼續執行了;又比如在獲取了一個網站中的動態URL之后,我們想要得到JS文件里面的所有接口信息等等。

同時這些需求也不是所有人都需要,因為功能越多掃描起來的速度就越慢。

它們並不是一個漏洞檢測POC,因為我們想要獲取的是一段探測的信息,並不只有TrueFalse兩種狀態

所以我們很容易想到使用插件來實現這個功能

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()

里面導入了requestsre包,實際上requests在我本地已經下載了,repython內置的包

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

建了一個微信的安全交流群,歡迎添加我微信備注進群,一起來聊天吹水哇,以及一個會發布安全相關內容的公眾號,歡迎關注 😃

GIF GIF


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM