模块开发
需要在 ExpDepos 中运行的模块都必须继承自 ExpDepos.libs.core.base.ExploitBase.ExploitBase 类,且需要重写父类 _exploit 和 _verify 函数。在 ExploitBase 模块中不仅定义了 ExploitBase 类本身,还引入了模块运行时所依赖的其他模块。因此在模块开发时候可以一次性引入所有模块,如下所示:
from ExpDepos.libs.core.base.ExploitBase import *
备注
为方便快速开发模块,可以 点击这里 获取完整模块模板,新的模块文件需要根据分类放到 ExpDepos/modules 目录下。为更好的分类存放模块文件,用户也可以选择新建目录用于存放同一类模块,例如 ExpDepos/modules/exploits/webapp/ 下的 tdoa_exploits 。
模块定义
模块类型定义请遵循与模块文件同名原则(该规则类似 java 的类型定义),否则将无法正确加载定义的模块。且类型继承自 ExploitBase,并重写父类 _exploit 和 _verify 函数。如下所示创建一个名为 example.py 的模块文件,其定义如下:
from ExpDepos.libs.core.base.ExploitBase import *
class example(ExploitBase):
def _verify(self):
pass
def _exploit(self):
pass
小技巧
如果不想编写模块的 verify 模式,可以在 _verify 函数中直接返回 _exploit 函数的调用,反之亦然。例如 return self._exploit() 或者在 _exploit 函数中 return self._verify() 。
_init()
在模块调用 _verify() 或 _exploit() 函数之前默认会先调用初始化函数 _init(),如果有需要在 _verify() 或 _exploit() 函数调用之前准备的业务逻辑,可以重写该函数并在其中实现,例如准备模块所需参数变量值等。
基本属性
模块类型静态属性用于定义模块基本信息,例如模块名称、开发作者、版本号等。这些属性都是固定的名称,开发过程中只允许覆盖这些值来定义模块的基本信息。下面展示了位于 ExploitBase 类中所有基本属性的默认定义:
class ExploitBase(object):
Name = None
Alias = None
Author = None
VulType = None
Category = None
Create_Date = None
Update_Date = None
Rank = None
AppPowerLink = None
AppName = None
AppVersion = None
References = []
Desc = """
"""
Description = """
"""
def _verify(self):
pass
def _exploit(self):
pass
属性名称对应用途如下:
Name模块名称
Alias模块别名,用于-M选项或是交互式Shell的use命令
Author模块作者,多个请使用list列表
VulType漏洞类型,详情定义请参考 漏洞类型定义
Category漏洞分类,详情定义请参考 漏洞分类定义
Create_Date模块开发时间
Update_Date模块更新时间
Rank模块效果分级(可选:RANK.Excellent RANK.Great RANK.Good RANK.Normal RANK.Average RANK.Low)
AppPowerLink漏洞厂商主页地址
AppName漏洞应用名称
AppVersion漏洞影响版本
References参考链接,多个请使用list列表
Desc漏洞描述,使用一句话描述该Exploit模块的主要功能(不支持换行或Markdown语法)
Description漏洞详情,支持Markdown语法
参数设置
要为模块设置参数选项需要重写父类的 _options 函数,并在该函数中返回一个以键值作为参数名的字典。其值有多种类型可选,如下代码所示:
def _options(self):
options = dict()
options["name"] = OptString("admin", description="字符类型示例", require=True)
options["Age"] = OptInteger(20, description="整型参数示例", require=True)
options["require"] = OptBoolean(False, description="非必选项参数示例", require=False)
return options
在 ExpDepos 中为模块参数内置了下列基本数据类型可供选择:
OptString类型用于存储值为str类型的模块参数
OptInteger类型用于存储值为int类型的模块参数
OptFloat类型用于存储值为float类型的模块参数
OptBoolean类型用于存储值为bool类型的模块参数
OptHost类型用于存储值为HOST数据的模块参数
OptPayload类型用于存储其他payload类型的模块参数
OptEncoder类型用于存储值为encoder类型的模块参数
这些内置的基本类型都继承自 ExpDepos.libs.core.base.OptionsBase.Option 类,他们构造函数如下所示:
- ExpDepos.libs.core.base.OptionsBase.Option.__init__(self, default='', description='', require=False, choices=[])
- 参数
default -- 参数默认值
description -- 参数描述信息
require -- 是否必选参数
choices -- 可选值列表
小技巧
所有内置基本类型的默认值同时也可以使用 str 类型数据,这将会根据字面值转换成对应的数据类型。如果转换失败则会抛出异常,例如: options["require"] = OptBoolean("False", description="非必选项参数示例")。
参数获取
要在模块中获取用户在命令行使用 -P 或者 --options 为模块提供的参数值,需要在模块中调用 self.get_option() 函数。该函数参数接收一个字符串作为参数名(不区分大小写),如果参数不存在将抛出异常。此外还可以使用 self.get_options() 函数获取全部参数,该函数返回一个包含所有参数及参数值的字典类型。
get_option 函数定义如下:
- ExpDepos.libs.core.base.ExploitBase.ExploitBase.get_option(self, key, default=None)
返回模块用户自定义参数值
- 参数
key -- 需要获取的参数名
default -- 默认值
- 返回
参数名对应值
输出/输入
消息输出
ExpDepos 采用 rich 模块作为整体的消息输出工具,在该模块基础上重新定义了 ExpDepos.libs.core.common.Console.Console 模块。并在全局环境中实例化为 console 的引用,您可以在模块的任意地方直接使用该引用来输出不同级别的消息,各级别消息定义如下所示:
EXCEPTION向终端输出输出异常类消息。
ERROR向终端输出错误类消息。
WARNING向终端输出告警消息。
FAILED向终端输出失败消息,用于漏洞利用失败的情况。
SUCCESS向终端输出成功消息,用于漏洞利用成功的情况。
INFO向终端输出常规消息。
DEBUG向终端输出调试消息。
PROGRESS用于输出进度条过程中的消息(使用是需使用formatPgString函数格式化)。
代码示例
def _verify(self):
console.info("这是在_verify中输出的INFO信息,即将跳转至_exploit执行。")
return self._exploit()
def _exploit(self):
# 在模块中使用统一的消息输出
console.info("测试INFO级别消息输出")
console.debug("测试 [bold green]DEBUG[bold green] 级别消息输出")
console.warning("测试 [bold yellow]WARNING[/bold yellow] 级别消息输出")
try:
1 / 0
except Exception as e:
console.exception("测试 [bold red]EXCEPTION[bold red] 级别消息输出:{0}".format(e.args[0]))
with Progress() as progress:
task = progress.add_task(formatPgString("测试 [green]PROGRESS[green] 级别消息输出..."), total=10)
for i in range(10):
progress.print(formatPgString("progress {0} done.".format(i)))
progress.update(task, advance=1)
time.sleep(1)
数据输入
在 console 中另外一个函数 input 用于接收用户输入的数据,该函数使用方式和 Python 的 input 一样。下列代码展示了如何让用户做出规定的选择:
choose = ''
while choose.lower() != 'y' and choose.lower() != 'n':
choose = console.input("[bold yellow]是否继续演示其他示例?[bold yellow] [bold green]Y/N:[/bold green]")
if choose == 'n':
return
console.info("example 将继续演示其他示例")
备注
console 引用的对象是对 rich.Console 模块的拓展和封装,该类提供的函数均可使用 rich 语法为消息进行渲染。且输出输入函数与 Python 内置的 print 和 input 一样。需要输出不带时间前缀的消息可以使用 console.print() 函数,更多有关 rich 的用法请参考 rich官方文档 。
HTTP请求
要在模块中发起HTTP请求可以使用模块基类已实例化的引用 self.request,该引用为一个继承了 httpx.Client 类的 Request 对象。所以可以像使用 requests 模块一样来发起HTTP请求,如下列代码所示:
# HTTP 请求测试
console.info("正在请求:" + str(self.request.base_url) + "tongda.ico")
response = self.request.get("/tongda.ico")
if response.status_code == 200:
console.info({"status": response.status_code,
"md5": response.md5sum,
"hash": response.hash,
"server": response.server,
"base64": response.base64})
备注
httpx 的使用类似我们熟悉的 requests 模块,同样可以自定义请求方式和请求参数。需要了解更多 httpx 的使用请参阅 httpx官方文档 。
Response
同样的我们也为 HTTP 的 Response 对象做了扩展,就像上诉代码中看到的一样,可以使用该对象的 md5sum 属性来获取返回内容的 MD5 值。以下列出的是 Response 对象的全部扩展属性:
md5sum返回HTTP Response内容的MD5值。
hash返回HTTP Response内容的mmh3值,类似fofa等网络搜索引擎的favicon.ico文件搜索功能都基于该算法。
base64返回HTTP Response内容的base64值。
segmentBase64返回HTTP Response内容以每76个字符加入换行的base64值。
server返回自动识别到的服务器常用中间件名称和版本。
除此之 Response 对象额外还具有以下几个函数用于常用的内容查找:
- ExpDepos.libs.core.Response.Response.contains(self, keyword: str) bool
检查response.text中是否包含keyword
- 参数
keyword -- 需要查找的关键字
- 返回
bool 是否含有关键字keyword
- ExpDepos.libs.core.Response.Response.bcontains(self, keyword: bytes) bool
检查response.content中是否包含二进制内容keyword
- 参数
keyword -- 需要查找的关键字
- 返回
bool 是否含有关键字keyword
- ExpDepos.libs.core.Response.Response.search(self, regex: str) match
在response.text中正则匹配regex
- 参数
regex -- 需要匹配的正则表达式
- 返回
re.match 匹配对象
异步请求
选择使用 httpx 作为 ExpDepos 内置的HTTP请求模块,主要原因还是 httpx 支持异步请求,这对编写一些高性能模块有着关键性作用。要在模块中使用异步请求接口我们需要用到在基类中已实例化的 self.asyncRequest 引用,该引用的对象为一个继承了 httpx.AsyncClient 的 Request 对象。以下是一个测试使用异步请求发起 1000 次HTTP请求,并统计其耗时的代码示例:
async def _request(self):
response = await self.asyncRequest.get("/")
assert response.status_code == 200
@asyncTimeit
async def _testAsyncRequest(self, times):
"""
测试异步请求性能
:param times: 测试请求次数
:return:
"""
task_list = list()
for x in range(times):
req = self._request()
if sys.version_info < (3, 7):
task = asyncio.ensure_future(req)
else:
task = asyncio.create_task(req)
task_list.append(task)
await asyncio.gather(*task_list)
def _exploit(self):
# HTTP 异步请求测试
console.info("正在执行异步请求测试...")
loop = None
try:
loop = asyncio.get_event_loop()
except RuntimeError as e:
if "There is no current event loop in thread" in str(e):
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
asyncio.get_event_loop().run_until_complete(self._testAsyncRequest(1000))
指纹识别
在 ExpDepos 中内置了 WhatWeb 增强版 最新的 8000+ 指纹模块。要在模块中使用这些指纹对目标进行识别可以直接调用 self.fpMatches() 函数,并传入被识别目标的 HTTP Response 对象和模式即可,以下是 self.fpMatches() 的函数定义:
- ExpDepos.libs.core.base.ExploitBase.ExploitBase.fpMatches(self, response: <module 'ExpDepos.libs.core.Response' from '/home/docs/checkouts/readthedocs.org/user_builds/expdepos/checkouts/latest/ExpDepos/libs/core/Response.py'>, mode='ALL', host='', fp_filter={}, progress=None) list
指纹识别
- 参数
response -- Response 对象
mode -- 识别模式 可选ALL、PASS, AGGR, EXP
fp_filter -- 指纹过滤器 用于过滤包含指定过滤器的指纹
- @param host
指定特定的host
- @param progress
进度条对象
- 返回
list
为了在指纹识别过程中尽可能少的发送HTTP请求,我们将指纹识别分为 aggressive (主动模式)和 passive (被动模式)。 主动模式即是需要对目标主动发起HTTP请求特定URL的方式,被动模式则不需要主动请求特定URL,它只需要从HTTP响应包中进行关键词识别。以下是对几种模式的详细说明:
AGGR主动模式,该模式将提取所有指纹中需要主动请求特定URL的匹配项进行识别(该模式会发送大量的HTTP 请求)。
PASS被动模式,该模式将提取所有指纹中需要被动识别的匹配项对HTTP Response内容进行匹配。
EXPExploit模式,该模式只提取在ExpDepos模块中注册的指纹进行识别(有关在模块中编写指纹请参阅 指纹编写 章节)。
ALL使用以上全部模式(这也是默认模式)。
fpFilter 参数过滤了那些只有符合过滤条件的指纹才进行匹配,该参数接收一个字典类型作为值。指纹过滤将从 platform (操作系统平台)、middleware (中间件)、language (脚本语言)、type``(指纹分类) 四个维度进行过滤,每一个维度使用一个 ``list 作为过滤值。
当前模块指纹
如果为当前模块编写了指纹,要使用当前指纹而不是匹配全部指纹库,需要先获取当前指纹属性 self.fingerprint 来获取一个 Fingerprint 对象,并调用他的 Matches() 函数进行识别,以下是 Matches() 的函数定义:
- ExpDepos.libs.core.base.Fingerprint.Fingerprint.Matches(self, moduleObj, responseObj: Optional[Response] = None, mode='ALL') list
并发匹配指纹
- 参数
moduleObj -- 已实例化的模块对象
responseObj -- 已实例化的ExpDepos.libs.core.Response对象
mode -- 匹配模式 可选ALL、PASS, AGGR, EXP
- 返回
list 匹配成功列表
调用 Matches() 函数需要将模块对象传入第一个参数,第二个参数为一个HTTP Response对象,如下代码展示了全部指纹的主动模式匹配和当前模块指纹的ALL模式匹配:
def _fingers(self):
"""
当前模块指纹定义
:return: dict
"""
fingerprint = {
"name": self.AppName, # 漏洞应用名作为指纹名称
"author": self.Author, # 作者
"version": "1.0", # 版本号
"type": FINGERPRINT.FP_TYPE.WEBAPP, # 指纹类型 详情请参考指纹类型表
"logic": "or", # 匹配逻辑 默认为 or
"description": "指纹描述信息", # 描述信息
"website": "https://www.tongda2000.com/",
"filters": { # 过滤属性,用于从操作系统平台、中间件、和脚本语言3个维度进行过滤
"platform": ['windows', 'Unix'],
"middleware": ['apache', 'nginx'],
"language": ['PHP']
},
"matches": [{"url": "/tongda.ico?r={randstr()}", "hash": -759108386, "certainty": 100, "status": 200},
{"search": "headers", "keyword": "X-Powered-By: PHP/7.2.24-0ubuntu0.18.04.8"},
{"search": "headers[set-cookie]", "regex": "(aa)", "offset": 1, "version": "2.2",
"aim": "version"},
{"name": "matchName", "keyword": "test <||> referer"},
{"status": 200}],
"sets": { # 主动式匹配的一些HTTP Request设置
"headers": {"testHeader": "testHeader{randstr(10)}"},
"cookies": {"cname": "cValue{randstr(5,true)}"},
"params": {"test": "bbbb"},
"data": ""
}
}
return fingerprint
def _exploit(self):
# 指纹识别测试
response = self.request.get("")
console.info("被动指纹匹配结果: ")
console.info(self.fpMatches(response, "PASS"))
console.info("当前模块指纹匹配结果:")
console.info(self.fingerprint.Matches(self, response))
小技巧
第二参数Response对象为可选参数,如果未提供将不进行被动模式匹配。
获取Payload
要在模块中获取用户设置的 Payload 可以使用 self.payload 直接获取,这将返回 Payload 的字符串类型值(如果用户设置了 encoder 则 payload 将经过对应编码器编码后返回)。如果需要获取 bytes 类型的 payload 值则需使用 self.payload.bytes 获取。
执行结果
ExpDepos 作为一款运行高质量 Exploit 模块的框架,为了突出模块执行结果,我们专门为其提供了 Result 类用于存储执行结果相关的数据。在模块基类中已实例化为 self.result 引用,该引用可以在模块中直接使用。以下为 Result 模块的定义:
@Author: Castiel @Email: ca3tie1@gmail.com @Blog: https://ca3tie1.github.io @Git: https://github.com/ca3tie1 @Wechat: Ca5tie1 @Date: 2021/7/31 18:00
- class ExpDepos.libs.core.common.Result.Result(module)[源代码]
exploit模块执行结果类 用于存储exploit模块执行的各项结果数据
在模块开发过程中我们只需要调用 self.result 的 success() 或者 fail() 函数来设置模块执行结果,ExpDepos 将在模块执行完后根据 self.result 的结果使用 SUCCESS 还是 FAILED 级别的消息输出。 success() 和 fail() 函数接收一个字符串和一个字典作为参数传入,字符串作为成功或者失败的主消息使用。字典作为成功或者失败的数据存储,用户可根据实际情况存储任何数据。同时该字典中亦可使用 message 键值作为主消息,但优先级低于字符串消息。