mirror of
https://github.com/shmilylty/OneForAll.git
synced 2026-08-25 20:37:48 +08:00
v0.0.1
This commit is contained in:
+18
@@ -0,0 +1,18 @@
|
|||||||
|
# Changelog
|
||||||
|
OneForAll的所有值得注意的更改都将记录在此文件中。
|
||||||
|
|
||||||
|
OneForAll的更新日志格式基于[Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/)。
|
||||||
|
|
||||||
|
OneForAll遵守[语义化版本格式](https://semver.org/)。
|
||||||
|
|
||||||
|
## [Unreleased](https://github.com/shmilylty/onefroall/compare/v1.0.0...HEAD)
|
||||||
|
|
||||||
|
## [0.0.1](https://github.com/shmilylty/onefroall/releases/tag/v0.0.1) - 2019-08-01
|
||||||
|
### Added
|
||||||
|
- 添加检查crossdomain.xml收集子域功能
|
||||||
|
- 添加检查域名证书收集子域功能
|
||||||
|
- 添加检查内容安全策略头收集子域功能
|
||||||
|
- 添加域传送利用功能
|
||||||
|
- 添加子域收集功能(搜索引擎,DNS数据集,证书透明度,网上爬虫档案)
|
||||||
|
- 添加子域爆破功能
|
||||||
|
- 添加数据库导出功能
|
||||||
+51
-76
@@ -1,76 +1,51 @@
|
|||||||
# Contributor Covenant Code of Conduct
|
# 参与者公约
|
||||||
|
|
||||||
## Our Pledge
|
## 我们的承诺
|
||||||
|
|
||||||
In the interest of fostering an open and welcoming environment, we as
|
为建设开放友好的环境,我们贡献者和维护者承诺:不论年龄、体型、身体健全与否、民族、性征、性别认同与表征、经验水平、教育程度、社会地位、国籍、相貌、种族、信仰、性取向,我们项目和社区的参与者皆免于骚扰。
|
||||||
contributors and maintainers pledge to making participation in our project and
|
|
||||||
our community a harassment-free experience for everyone, regardless of age, body
|
## 我们的准则
|
||||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
|
||||||
level of experience, education, socio-economic status, nationality, personal
|
有助于创造积极环境的行为包括但不限于:
|
||||||
appearance, race, religion, or sexual identity and orientation.
|
|
||||||
|
* 措辞友好且包容
|
||||||
## Our Standards
|
* 尊重不同的观点和经验
|
||||||
|
* 耐心接受有益批评
|
||||||
Examples of behavior that contributes to creating a positive environment
|
* 关注对社区最有利的事情
|
||||||
include:
|
* 与社区其他成员友善相处
|
||||||
|
|
||||||
* Using welcoming and inclusive language
|
参与者不应采取的行为包括但不限于:
|
||||||
* Being respectful of differing viewpoints and experiences
|
|
||||||
* Gracefully accepting constructive criticism
|
* 发布与性有关的言论或图像、不受欢迎地献殷勤
|
||||||
* Focusing on what is best for the community
|
* 捣乱/煽动/造谣行为、侮辱/贬损的评论、人身及政治攻击
|
||||||
* Showing empathy towards other community members
|
* 公开或私下骚扰
|
||||||
|
* 未经明确授权便发布他人的资料,如住址、电子邮箱等
|
||||||
Examples of unacceptable behavior by participants include:
|
* 其他有理由认定为违反职业操守的不当行为
|
||||||
|
|
||||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
## 我们的义务
|
||||||
advances
|
|
||||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
项目维护者有义务诠释何谓“妥当行为”,并妥善公正地纠正已发生的不当行为。
|
||||||
* Public or private harassment
|
|
||||||
* Publishing others' private information, such as a physical or electronic
|
项目维护者有权利和义务去删除、编辑、拒绝违背本行为标准的评论(comments)、提交(commits)、代码、wiki 编辑、问题(issues)等贡献;项目维护者可暂时或永久地封禁任何他们认为行为不当、威胁、冒犯、有害的参与者。
|
||||||
address, without explicit permission
|
|
||||||
* Other conduct which could reasonably be considered inappropriate in a
|
## 适用范围
|
||||||
professional setting
|
|
||||||
|
本行为标准适用于本项目。当有人代表本项目或本社区时,本标准亦适用于此人所处的公共平台。
|
||||||
## Our Responsibilities
|
|
||||||
|
代表本项目或本社区的情形包括但不限于:使用项目的官方电子邮件、通过官方媒体账号发布消息、作为指定代表参与在线或线下活动等。
|
||||||
Project maintainers are responsible for clarifying the standards of acceptable
|
|
||||||
behavior and are expected to take appropriate and fair corrective action in
|
代表本项目的行为可由项目维护者进一步定义及解释。
|
||||||
response to any instances of unacceptable behavior.
|
|
||||||
|
## 贯彻落实
|
||||||
Project maintainers have the right and responsibility to remove, edit, or
|
|
||||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
可以致信[admin@hackfun.org],向项目团队举报滥用、骚扰及不当行为。
|
||||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
|
||||||
permanently any contributor for other behaviors that they deem inappropriate,
|
维护团队将审议并调查全部投诉,妥善地予以必要的回应。项目团队有义务保密举报者信息。具体执行方针或将另行发布。
|
||||||
threatening, offensive, or harmful.
|
|
||||||
|
未切实遵守或执行本行为标准的项目维护人员,经项目负责人或其他成员决议,可能被暂时或永久地剥夺参与本项目的资格。
|
||||||
## Scope
|
|
||||||
|
## 来源
|
||||||
This Code of Conduct applies both within project spaces and in public spaces
|
|
||||||
when an individual is representing the project or its community. Examples of
|
本行为标准改编自[参与者公约](https://www.contributor-covenant.org)的1.4版本,可[在此](https://www.contributor-covenant.org/zh-cn/version/1/4/code-of-conduct.html)查阅。
|
||||||
representing a project or community include using an official project e-mail
|
|
||||||
address, posting via an official social media account, or acting as an appointed
|
You can view the Contributor Covenant Translations [here](https://www.contributor-covenant.org/translations).
|
||||||
representative at an online or offline event. Representation of a project may be
|
|
||||||
further defined and clarified by project maintainers.
|
|
||||||
|
|
||||||
## Enforcement
|
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
||||||
reported by contacting the project team at admin@hackfun.org. All
|
|
||||||
complaints will be reviewed and investigated and will result in a response that
|
|
||||||
is deemed necessary and appropriate to the circumstances. The project team is
|
|
||||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
|
||||||
Further details of specific enforcement policies may be posted separately.
|
|
||||||
|
|
||||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
|
||||||
faith may face temporary or permanent repercussions as determined by other
|
|
||||||
members of the project's leadership.
|
|
||||||
|
|
||||||
## Attribution
|
|
||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
|
||||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
|
||||||
|
|
||||||
[homepage]: https://www.contributor-covenant.org
|
|
||||||
|
|
||||||
For answers to common questions about this code of conduct, see
|
|
||||||
https://www.contributor-covenant.org/faq
|
|
||||||
+42
-1
@@ -1 +1,42 @@
|
|||||||
|
# Contribution Guidelines
|
||||||
|
|
||||||
|
Before opening any issues or proposing any pull requests and to get the greatest chance of helpful responses, please observe the following notes.
|
||||||
|
|
||||||
|
## Good Bug Reports
|
||||||
|
|
||||||
|
Please be aware of the following things when filing bug reports:
|
||||||
|
|
||||||
|
1. Avoid raising duplicate issues. *Please* use the GitHub issue search feature
|
||||||
|
to check whether your bug report or feature request has been mentioned in
|
||||||
|
the past. Duplicate bug reports and feature requests are a huge maintenance
|
||||||
|
burden on the limited resources of the project. If it is clear from your
|
||||||
|
report that you would have struggled to find the original, that's ok, but
|
||||||
|
if searching for a selection of words in your issue title would have found
|
||||||
|
the duplicate then the issue will likely be closed extremely abruptly.
|
||||||
|
2. When filing bug reports about exceptions or tracebacks, please include the
|
||||||
|
*complete* traceback. Partial tracebacks, or just the exception text, are
|
||||||
|
not helpful. Issues that do not contain complete tracebacks may be closed
|
||||||
|
without warning.
|
||||||
|
3. Make sure you provide a suitable amount of information to work with. This
|
||||||
|
means you should provide:
|
||||||
|
|
||||||
|
- Guidance on **how to reproduce the issue**. Ideally, this should be a
|
||||||
|
*small* code sample that can be run immediately by the maintainers.
|
||||||
|
Failing that, let us know what you're doing, how often it happens, what
|
||||||
|
environment you're using, etc. Be thorough: it prevents us needing to ask
|
||||||
|
further questions.
|
||||||
|
- Tell us **what you expected to happen**. When we run your example code,
|
||||||
|
what are we expecting to happen? What does "success" look like for your
|
||||||
|
code?
|
||||||
|
- Tell us **what actually happens**. It's not helpful for you to say "it
|
||||||
|
doesn't work" or "it fails". Tell us *how* it fails: do you get an
|
||||||
|
exception? A hang? A non-200 status code? How was the actual result
|
||||||
|
different from your expected result?
|
||||||
|
- Tell us **what version of OneForAll you're using**, and
|
||||||
|
**how you installed it**. Different versions of Requests behave
|
||||||
|
differently and have different bugs, and some distributors of Requests
|
||||||
|
ship patches on top of the code we supply.
|
||||||
|
|
||||||
|
If you do not provide all of these things, it will take us much longer to
|
||||||
|
fix your problem. If we ask you to clarify these and you never respond, we
|
||||||
|
will close your issue without fixing it.
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# OneForAll贡献者
|
||||||
|
|
||||||
|
* **[Jing Ling](https://github.com/shmilylty)**
|
||||||
|
* 核心开发
|
||||||
|
|
||||||
|
* **[Black Star](https://github.com/shmilylty)**
|
||||||
|
* 模块贡献
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
[[source]]
|
||||||
|
name = "pypi"
|
||||||
|
url = "https://mirrors.aliyun.com/pypi/simple/"
|
||||||
|
verify_ssl = true
|
||||||
|
|
||||||
|
[dev-packages]
|
||||||
|
|
||||||
|
[packages]
|
||||||
|
aiodns = "*"
|
||||||
|
tqdm = "*"
|
||||||
|
aiomultiprocess = "*"
|
||||||
|
loguru = "*"
|
||||||
|
dnspython = "*"
|
||||||
|
requests = "*"
|
||||||
|
records = "*"
|
||||||
|
tldextract = "*"
|
||||||
|
exrex = "*"
|
||||||
|
aiohttp = "*"
|
||||||
|
fire = "*"
|
||||||
|
fake-useragent = "*"
|
||||||
|
bs4 = "*"
|
||||||
|
cchardet = "*"
|
||||||
|
lxml = "*"
|
||||||
|
brotlipy = "*"
|
||||||
|
pysocks = "*"
|
||||||
|
|
||||||
|
[requires]
|
||||||
|
python_version = "3.7"
|
||||||
Generated
+532
@@ -0,0 +1,532 @@
|
|||||||
|
{
|
||||||
|
"_meta": {
|
||||||
|
"hash": {
|
||||||
|
"sha256": "c2d9fe3a7303466070733472b7b06b491d35bbbcf3db8863152e721d797e9195"
|
||||||
|
},
|
||||||
|
"pipfile-spec": 6,
|
||||||
|
"requires": {
|
||||||
|
"python_version": "3.7"
|
||||||
|
},
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"name": "pypi",
|
||||||
|
"url": "https://mirrors.aliyun.com/pypi/simple/",
|
||||||
|
"verify_ssl": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"aiodns": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:815fdef4607474295d68da46978a54481dd1e7be153c7d60f9e72773cd38d77d",
|
||||||
|
"sha256:aaa5ac584f40fe778013df0aa6544bf157799bd3f608364b451840ed2c8688de"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==2.0.0"
|
||||||
|
},
|
||||||
|
"aiohttp": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:00d198585474299c9c3b4f1d5de1a576cc230d562abc5e4a0e81d71a20a6ca55",
|
||||||
|
"sha256:0155af66de8c21b8dba4992aaeeabf55503caefae00067a3b1139f86d0ec50ed",
|
||||||
|
"sha256:09654a9eca62d1bd6d64aa44db2498f60a5c1e0ac4750953fdd79d5c88955e10",
|
||||||
|
"sha256:199f1d106e2b44b6dacdf6f9245493c7d716b01d0b7fbe1959318ba4dc64d1f5",
|
||||||
|
"sha256:296f30dedc9f4b9e7a301e5cc963012264112d78a1d3094cd83ef148fdf33ca1",
|
||||||
|
"sha256:368ed312550bd663ce84dc4b032a962fcb3c7cae099dbbd48663afc305e3b939",
|
||||||
|
"sha256:40d7ea570b88db017c51392349cf99b7aefaaddd19d2c78368aeb0bddde9d390",
|
||||||
|
"sha256:629102a193162e37102c50713e2e31dc9a2fe7ac5e481da83e5bb3c0cee700aa",
|
||||||
|
"sha256:6d5ec9b8948c3d957e75ea14d41e9330e1ac3fed24ec53766c780f82805140dc",
|
||||||
|
"sha256:87331d1d6810214085a50749160196391a712a13336cd02ce1c3ea3d05bcf8d5",
|
||||||
|
"sha256:9a02a04bbe581c8605ac423ba3a74999ec9d8bce7ae37977a3d38680f5780b6d",
|
||||||
|
"sha256:9c4c83f4fa1938377da32bc2d59379025ceeee8e24b89f72fcbccd8ca22dc9bf",
|
||||||
|
"sha256:9cddaff94c0135ee627213ac6ca6d05724bfe6e7a356e5e09ec57bd3249510f6",
|
||||||
|
"sha256:a25237abf327530d9561ef751eef9511ab56fd9431023ca6f4803f1994104d72",
|
||||||
|
"sha256:a5cbd7157b0e383738b8e29d6e556fde8726823dae0e348952a61742b21aeb12",
|
||||||
|
"sha256:a97a516e02b726e089cffcde2eea0d3258450389bbac48cbe89e0f0b6e7b0366",
|
||||||
|
"sha256:acc89b29b5f4e2332d65cd1b7d10c609a75b88ef8925d487a611ca788432dfa4",
|
||||||
|
"sha256:b05bd85cc99b06740aad3629c2585bda7b83bd86e080b44ba47faf905fdf1300",
|
||||||
|
"sha256:c2bec436a2b5dafe5eaeb297c03711074d46b6eb236d002c13c42f25c4a8ce9d",
|
||||||
|
"sha256:cc619d974c8c11fe84527e4b5e1c07238799a8c29ea1c1285149170524ba9303",
|
||||||
|
"sha256:d4392defd4648badaa42b3e101080ae3313e8f4787cb517efd3f5b8157eaefd6",
|
||||||
|
"sha256:e1c3c582ee11af7f63a34a46f0448fca58e59889396ffdae1f482085061a2889"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==3.5.4"
|
||||||
|
},
|
||||||
|
"aiomultiprocess": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:fd9b616d5145ac2b01f315725277231ffad7e56eb6675885598f6fe861ef9fa3"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.6.0"
|
||||||
|
},
|
||||||
|
"async-timeout": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f",
|
||||||
|
"sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"
|
||||||
|
],
|
||||||
|
"version": "==3.0.1"
|
||||||
|
},
|
||||||
|
"attrs": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79",
|
||||||
|
"sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399"
|
||||||
|
],
|
||||||
|
"version": "==19.1.0"
|
||||||
|
},
|
||||||
|
"backports.csv": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:1277dfff73130b2e106bf3dd347adb3c5f6c4340882289d88f31240da92cbd6d",
|
||||||
|
"sha256:21f6e09bab589e6c1f877edbc40277b65e626262a86e69a70137db714eaac5ce"
|
||||||
|
],
|
||||||
|
"version": "==1.0.7"
|
||||||
|
},
|
||||||
|
"beautifulsoup4": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:05668158c7b85b791c5abde53e50265e16f98ad601c402ba44d70f96c4159612",
|
||||||
|
"sha256:25288c9e176f354bf277c0a10aa96c782a6a18a17122dba2e8cec4a97e03343b",
|
||||||
|
"sha256:f040590be10520f2ea4c2ae8c3dae441c7cfff5308ec9d58a0ec0c1b8f81d469"
|
||||||
|
],
|
||||||
|
"version": "==4.8.0"
|
||||||
|
},
|
||||||
|
"brotlipy": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:07194f4768eb62a4f4ea76b6d0df6ade185e24ebd85877c351daa0a069f1111a",
|
||||||
|
"sha256:091b299bf36dd6ef7a06570dbc98c0f80a504a56c5b797f31934d2ad01ae7d17",
|
||||||
|
"sha256:09ec3e125d16749b31c74f021aba809541b3564e5359f8c265cbae442810b41a",
|
||||||
|
"sha256:0be698678a114addcf87a4b9496c552c68a2c99bf93cf8e08f5738b392e82057",
|
||||||
|
"sha256:0fa6088a9a87645d43d7e21e32b4a6bf8f7c3939015a50158c10972aa7f425b7",
|
||||||
|
"sha256:1ea4e578241504b58f2456a6c69952c88866c794648bdc74baee74839da61d44",
|
||||||
|
"sha256:2699945a0a992c04fc7dc7fa2f1d0575a2c8b4b769f2874a08e8eae46bef36ae",
|
||||||
|
"sha256:2a80319ae13ea8dd60ecdc4f5ccf6da3ae64787765923256b62c598c5bba4121",
|
||||||
|
"sha256:2e5c64522364a9ebcdf47c5744a5ddeb3f934742d31e61ebfbbc095460b47162",
|
||||||
|
"sha256:36def0b859beaf21910157b4c33eb3b06d8ce459c942102f16988cca6ea164df",
|
||||||
|
"sha256:3a3e56ced8b15fbbd363380344f70f3b438e0fd1fcf27b7526b6172ea950e867",
|
||||||
|
"sha256:3c1d5e2cf945a46975bdb11a19257fa057b67591eb232f393d260e7246d9e571",
|
||||||
|
"sha256:50ca336374131cfad20612f26cc43c637ac0bfd2be3361495e99270883b52962",
|
||||||
|
"sha256:5de6f7d010b7558f72f4b061a07395c5c3fd57f0285c5af7f126a677b976a868",
|
||||||
|
"sha256:637847560d671657f993313ecc6c6c6666a936b7a925779fd044065c7bc035b9",
|
||||||
|
"sha256:653faef61241bf8bf99d73ca7ec4baa63401ba7b2a2aa88958394869379d67c7",
|
||||||
|
"sha256:786afc8c9bd67de8d31f46e408a3386331e126829114e4db034f91eacb05396d",
|
||||||
|
"sha256:79aaf217072840f3e9a3b641cccc51f7fc23037496bd71e26211856b93f4b4cb",
|
||||||
|
"sha256:7e31f7adcc5851ca06134705fcf3478210da45d35ad75ec181e1ce9ce345bb38",
|
||||||
|
"sha256:8b39abc3256c978f575df5cd7893153277216474f303e26f0e43ba3d3969ef96",
|
||||||
|
"sha256:9448227b0df082e574c45c983fa5cd4bda7bfb11ea6b59def0940c1647be0c3c",
|
||||||
|
"sha256:96bc59ff9b5b5552843dc67999486a220e07a0522dddd3935da05dc194fa485c",
|
||||||
|
"sha256:a07647886e24e2fb2d68ca8bf3ada398eb56fd8eac46c733d4d95c64d17f743b",
|
||||||
|
"sha256:af65d2699cb9f13b26ec3ba09e75e80d31ff422c03675fcb36ee4dabe588fdc2",
|
||||||
|
"sha256:b4c98b0d2c9c7020a524ca5bbff42027db1004c6571f8bc7b747f2b843128e7a",
|
||||||
|
"sha256:c6cc0036b1304dd0073eec416cb2f6b9e37ac8296afd9e481cac3b1f07f9db25",
|
||||||
|
"sha256:d2c1c724c4ac375feb2110f1af98ecdc0e5a8ea79d068efb5891f621a5b235cb",
|
||||||
|
"sha256:dc6c5ee0df9732a44d08edab32f8a616b769cc5a4155a12d2d010d248eb3fb07",
|
||||||
|
"sha256:fd1d1c64214af5d90014d82cee5d8141b13d44c92ada7a0c0ec0679c6f15a471"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.7.0"
|
||||||
|
},
|
||||||
|
"bs4": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:36ecea1fd7cc5c0c6e4a1ff075df26d50da647b75376626cc186e2212886dd3a"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.0.1"
|
||||||
|
},
|
||||||
|
"cchardet": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:079aa02a14072874d943a671ba778a9def5b0e3cedc2ac9f59308526cfb31472",
|
||||||
|
"sha256:3e048a21688dcb4c797f40c8deb3600887bcaf435620256fd8becd4252012750",
|
||||||
|
"sha256:41fced7a6f05ef859fe3eac89fc2120aca3cbbfd2b6c803bed3ee4bf02956903",
|
||||||
|
"sha256:440903d5dca3d326f4b841e7fa760b6af1be4f950ead1a6ff77b76eaa46f0cd3",
|
||||||
|
"sha256:50170f346527c5df4d3cb94648ca187c666e61c0db6e510b984e867c44709d8b",
|
||||||
|
"sha256:6c55a6e7bc7337671c9f1ad90746c0efb2b2979ff4305c7ca1d7d381f05174c1",
|
||||||
|
"sha256:7f581ea172b252034f745dfd49733966b73b73907bdef0b47ad5f2008b797d54",
|
||||||
|
"sha256:80f7b087198827e60c81574c321b12f89188eae626ae1567d66808928be42f88",
|
||||||
|
"sha256:8ba753ff73ca2f3554999a0e027eab9450f6ffdb7e92e1b4e13b52be89995349",
|
||||||
|
"sha256:9ad8f61d6d1ca37bd4b954ad92d461ea4f58d0dc413b0790a5abed7c09e54996",
|
||||||
|
"sha256:a35bd23cedbaa87cc9300af1dd10bb03fda41894045fbca7bfdf1d350b813f25",
|
||||||
|
"sha256:a8feb9a7def2310e18c27e485a21a38669abe8c2e36b93c6ce1a1363495d4cdf",
|
||||||
|
"sha256:aa9dd4cee8a5210a6d0a7b263b98dc50637e00401fc4a5ad3ce2dbef54fdfa02",
|
||||||
|
"sha256:ab9858a0673262e467619df91f425cfef0590dcf5deef5c0c7945e9dc4dbd7d8",
|
||||||
|
"sha256:b09a488bbb35be95f82845e3c4312be9025e8377975b027eee67e0b39445e070",
|
||||||
|
"sha256:b2893d558761b3534cddf5a49ba8d77df3d8f964d7b14680b925f4a85fc13476",
|
||||||
|
"sha256:b5a8f9b229a30cd2432572d15e169483bc47c24418772ff58d0585050631c2fd",
|
||||||
|
"sha256:bded54eeccd5f810bc69e076b3d9a35819a92e5e0559ad274b9ae9061b1b881d",
|
||||||
|
"sha256:cbc206061e69561af6e4cba11f99abd928346c6b5bcdc83eb32ae40e9fc23a5f",
|
||||||
|
"sha256:cc9745e0400da4cfb49f075e7819f22473b66443f953427058fee2c7b9547cc0",
|
||||||
|
"sha256:db30bf3825702c07fc55a290d41663fd8151f870642a15667bbabf81fff21e0b",
|
||||||
|
"sha256:eeeb1b95bb5851dda93ee522860a0e6066d47921cb1d540cb778346e37e5a524",
|
||||||
|
"sha256:f1c3919fb71ac5da3aeee42c5b731c99dcd2beed71db7fdc28ca993c173f0402"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==2.1.4"
|
||||||
|
},
|
||||||
|
"certifi": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939",
|
||||||
|
"sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695"
|
||||||
|
],
|
||||||
|
"version": "==2019.6.16"
|
||||||
|
},
|
||||||
|
"cffi": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:041c81822e9f84b1d9c401182e174996f0bae9991f33725d059b771744290774",
|
||||||
|
"sha256:046ef9a22f5d3eed06334d01b1e836977eeef500d9b78e9ef693f9380ad0b83d",
|
||||||
|
"sha256:066bc4c7895c91812eff46f4b1c285220947d4aa46fa0a2651ff85f2afae9c90",
|
||||||
|
"sha256:066c7ff148ae33040c01058662d6752fd73fbc8e64787229ea8498c7d7f4041b",
|
||||||
|
"sha256:2444d0c61f03dcd26dbf7600cf64354376ee579acad77aef459e34efcb438c63",
|
||||||
|
"sha256:300832850b8f7967e278870c5d51e3819b9aad8f0a2c8dbe39ab11f119237f45",
|
||||||
|
"sha256:34c77afe85b6b9e967bd8154e3855e847b70ca42043db6ad17f26899a3df1b25",
|
||||||
|
"sha256:46de5fa00f7ac09f020729148ff632819649b3e05a007d286242c4882f7b1dc3",
|
||||||
|
"sha256:4aa8ee7ba27c472d429b980c51e714a24f47ca296d53f4d7868075b175866f4b",
|
||||||
|
"sha256:4d0004eb4351e35ed950c14c11e734182591465a33e960a4ab5e8d4f04d72647",
|
||||||
|
"sha256:4e3d3f31a1e202b0f5a35ba3bc4eb41e2fc2b11c1eff38b362de710bcffb5016",
|
||||||
|
"sha256:50bec6d35e6b1aaeb17f7c4e2b9374ebf95a8975d57863546fa83e8d31bdb8c4",
|
||||||
|
"sha256:55cad9a6df1e2a1d62063f79d0881a414a906a6962bc160ac968cc03ed3efcfb",
|
||||||
|
"sha256:5662ad4e4e84f1eaa8efce5da695c5d2e229c563f9d5ce5b0113f71321bcf753",
|
||||||
|
"sha256:59b4dc008f98fc6ee2bb4fd7fc786a8d70000d058c2bbe2698275bc53a8d3fa7",
|
||||||
|
"sha256:73e1ffefe05e4ccd7bcea61af76f36077b914f92b76f95ccf00b0c1b9186f3f9",
|
||||||
|
"sha256:a1f0fd46eba2d71ce1589f7e50a9e2ffaeb739fb2c11e8192aa2b45d5f6cc41f",
|
||||||
|
"sha256:a2e85dc204556657661051ff4bab75a84e968669765c8a2cd425918699c3d0e8",
|
||||||
|
"sha256:a5457d47dfff24882a21492e5815f891c0ca35fefae8aa742c6c263dac16ef1f",
|
||||||
|
"sha256:a8dccd61d52a8dae4a825cdbb7735da530179fea472903eb871a5513b5abbfdc",
|
||||||
|
"sha256:ae61af521ed676cf16ae94f30fe202781a38d7178b6b4ab622e4eec8cefaff42",
|
||||||
|
"sha256:b012a5edb48288f77a63dba0840c92d0504aa215612da4541b7b42d849bc83a3",
|
||||||
|
"sha256:d2c5cfa536227f57f97c92ac30c8109688ace8fa4ac086d19d0af47d134e2909",
|
||||||
|
"sha256:d42b5796e20aacc9d15e66befb7a345454eef794fdb0737d1af593447c6c8f45",
|
||||||
|
"sha256:dee54f5d30d775f525894d67b1495625dd9322945e7fee00731952e0368ff42d",
|
||||||
|
"sha256:e070535507bd6aa07124258171be2ee8dfc19119c28ca94c9dfb7efd23564512",
|
||||||
|
"sha256:e1ff2748c84d97b065cc95429814cdba39bcbd77c9c85c89344b317dc0d9cbff",
|
||||||
|
"sha256:ed851c75d1e0e043cbf5ca9a8e1b13c4c90f3fbd863dacb01c0808e2b5204201"
|
||||||
|
],
|
||||||
|
"version": "==1.12.3"
|
||||||
|
},
|
||||||
|
"chardet": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
||||||
|
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
||||||
|
],
|
||||||
|
"version": "==3.0.4"
|
||||||
|
},
|
||||||
|
"colorama": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d",
|
||||||
|
"sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"
|
||||||
|
],
|
||||||
|
"markers": "sys_platform == 'win32'",
|
||||||
|
"version": "==0.4.1"
|
||||||
|
},
|
||||||
|
"defusedxml": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93",
|
||||||
|
"sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5"
|
||||||
|
],
|
||||||
|
"version": "==0.6.0"
|
||||||
|
},
|
||||||
|
"dnspython": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:36c5e8e38d4369a08b6780b7f27d790a292b2b08eea01607865bf0936c558e01",
|
||||||
|
"sha256:f69c21288a962f4da86e56c4905b49d11aba7938d3d740e80d9e366ee4f1632d"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==1.16.0"
|
||||||
|
},
|
||||||
|
"docopt": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"
|
||||||
|
],
|
||||||
|
"version": "==0.6.2"
|
||||||
|
},
|
||||||
|
"et-xmlfile": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:614d9722d572f6246302c4491846d2c393c199cfa4edc9af593437691683335b"
|
||||||
|
],
|
||||||
|
"version": "==1.0.1"
|
||||||
|
},
|
||||||
|
"exrex": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:3fb8b18fd9832eaff8b13dc042a4f63b13c5d684ee069f70a23ddfc6bcb708f3"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.10.5"
|
||||||
|
},
|
||||||
|
"fake-useragent": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:c104998b750eb097eefc28ae28e92d66397598d2cf41a31aa45d5559ef1adf35"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.1.11"
|
||||||
|
},
|
||||||
|
"fire": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:cdae28ea728d6cad487c900d534426d2cd09ddd2993287a885ff7c8accb097c8"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.2.0"
|
||||||
|
},
|
||||||
|
"idna": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
|
||||||
|
"sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
|
||||||
|
],
|
||||||
|
"version": "==2.8"
|
||||||
|
},
|
||||||
|
"jdcal": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:1abf1305fce18b4e8aa248cf8fe0c56ce2032392bc64bbd61b5dff2a19ec8bba",
|
||||||
|
"sha256:472872e096eb8df219c23f2689fc336668bdb43d194094b5cc1707e1640acfc8"
|
||||||
|
],
|
||||||
|
"version": "==1.4.1"
|
||||||
|
},
|
||||||
|
"loguru": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:b6fad0d7aed357b5c147edcc6982606b933754338950b72d8123f48c150c5a4f",
|
||||||
|
"sha256:e3138bfdee5f57481a2a6e078714be20f8c71ab1ff3f07f8fb1cfa25191fed2a"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.3.2"
|
||||||
|
},
|
||||||
|
"lxml": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:06e5599b9c54f797a3c0f384c67705a0d621031007aa2400a6c7d17300fdb995",
|
||||||
|
"sha256:092237cfe4ece074401b75001a2e525fa6e1fb9d40fee8b7b132b1947d3bd2f8",
|
||||||
|
"sha256:0b6d49d0a26fe8207df8dd27c40b75be4deb2277173903aa76ec3e82df77cbe7",
|
||||||
|
"sha256:0f77061c20b4f32b1cf39e8f661c74e966344084c996e7b23c3a94e472461df0",
|
||||||
|
"sha256:0fef86edfa2f146b4b0ae2c6c05c3e4a8f3388b3655eafbc4aab3247f4dabb24",
|
||||||
|
"sha256:2f163c8844db4ed06a230ef092e2461ad01830972a896b8f3cf8b5bac70ae85d",
|
||||||
|
"sha256:350333190052bbfbc3222b1805b59b7979d7276e57af2257367e15a2db27082d",
|
||||||
|
"sha256:3b57dc5ed7b6a7d852c961f2389ca99404c2b59fd2088baec6fbaca02f688be4",
|
||||||
|
"sha256:3e86e5df4a8edd6f725f3c76f1d45e046d4f3aa40478092e4f5f373ad1f526e2",
|
||||||
|
"sha256:43dac60d10341d3e56be089cd0798b70e70d45ce32279f4c3190d8cbd71350e4",
|
||||||
|
"sha256:4665ee84ac8ba11d58f1ed517e29ea8536b4ae4e0c6fb6c7d3dce70abcd279f0",
|
||||||
|
"sha256:5033cf606a7cb559db967689b1b2e743994000f783607ba4c484e90917395ad7",
|
||||||
|
"sha256:75d731af05bf40f808d7716e0d26b4b02913402f861c032ce8c36efca350ae72",
|
||||||
|
"sha256:7720174604c7647e357566ac9e4d135c137caed5e7b01223551a4c81c8dc8b9a",
|
||||||
|
"sha256:b33ec641309bcea40c76c1b105f988e4e8f9a2f1ee1486aa5c0eeef33956c9bb",
|
||||||
|
"sha256:d1135dc0ac197242028ede085b693ba1f2bff7f0f9b91080e2540348312bfa53",
|
||||||
|
"sha256:d5a61e9c2322b45f259909a02b76bc98c4641214e22a37191d00c151aa9cdb9a",
|
||||||
|
"sha256:da22c4b17bc17dad9c8faf6d94c8fe568ac71c867a56631ab874da418fc7f8f7",
|
||||||
|
"sha256:da5c48ec9f8d8b5df42d328b6d1fb8d9413cd664a2367ef4f6f7cc48ee5b82c0",
|
||||||
|
"sha256:db2794bad21b7b30b6849b4e1537171cae8a7087711d958d69c233470dc612e7",
|
||||||
|
"sha256:f1c2f67df727034f94ccb590142d1d110f3dd38f638a4f1567fdd9f39892ba05",
|
||||||
|
"sha256:f840dddded8b046edc774c88ed8d2442cdb231a68894c42c74e3a809450fae76"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==4.4.0"
|
||||||
|
},
|
||||||
|
"multidict": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:024b8129695a952ebd93373e45b5d341dbb87c17ce49637b34000093f243dd4f",
|
||||||
|
"sha256:041e9442b11409be5e4fc8b6a97e4bcead758ab1e11768d1e69160bdde18acc3",
|
||||||
|
"sha256:045b4dd0e5f6121e6f314d81759abd2c257db4634260abcfe0d3f7083c4908ef",
|
||||||
|
"sha256:047c0a04e382ef8bd74b0de01407e8d8632d7d1b4db6f2561106af812a68741b",
|
||||||
|
"sha256:068167c2d7bbeebd359665ac4fff756be5ffac9cda02375b5c5a7c4777038e73",
|
||||||
|
"sha256:148ff60e0fffa2f5fad2eb25aae7bef23d8f3b8bdaf947a65cdbe84a978092bc",
|
||||||
|
"sha256:1d1c77013a259971a72ddaa83b9f42c80a93ff12df6a4723be99d858fa30bee3",
|
||||||
|
"sha256:1d48bc124a6b7a55006d97917f695effa9725d05abe8ee78fd60d6588b8344cd",
|
||||||
|
"sha256:31dfa2fc323097f8ad7acd41aa38d7c614dd1960ac6681745b6da124093dc351",
|
||||||
|
"sha256:34f82db7f80c49f38b032c5abb605c458bac997a6c3142e0d6c130be6fb2b941",
|
||||||
|
"sha256:3d5dd8e5998fb4ace04789d1d008e2bb532de501218519d70bb672c4c5a2fc5d",
|
||||||
|
"sha256:4a6ae52bd3ee41ee0f3acf4c60ceb3f44e0e3bc52ab7da1c2b2aa6703363a3d1",
|
||||||
|
"sha256:4b02a3b2a2f01d0490dd39321c74273fed0568568ea0e7ea23e02bd1fb10a10b",
|
||||||
|
"sha256:4b843f8e1dd6a3195679d9838eb4670222e8b8d01bc36c9894d6c3538316fa0a",
|
||||||
|
"sha256:5de53a28f40ef3c4fd57aeab6b590c2c663de87a5af76136ced519923d3efbb3",
|
||||||
|
"sha256:61b2b33ede821b94fa99ce0b09c9ece049c7067a33b279f343adfe35108a4ea7",
|
||||||
|
"sha256:6a3a9b0f45fd75dc05d8e93dc21b18fc1670135ec9544d1ad4acbcf6b86781d0",
|
||||||
|
"sha256:76ad8e4c69dadbb31bad17c16baee61c0d1a4a73bed2590b741b2e1a46d3edd0",
|
||||||
|
"sha256:7ba19b777dc00194d1b473180d4ca89a054dd18de27d0ee2e42a103ec9b7d014",
|
||||||
|
"sha256:7c1b7eab7a49aa96f3db1f716f0113a8a2e93c7375dd3d5d21c4941f1405c9c5",
|
||||||
|
"sha256:7fc0eee3046041387cbace9314926aa48b681202f8897f8bff3809967a049036",
|
||||||
|
"sha256:8ccd1c5fff1aa1427100ce188557fc31f1e0a383ad8ec42c559aabd4ff08802d",
|
||||||
|
"sha256:8e08dd76de80539d613654915a2f5196dbccc67448df291e69a88712ea21e24a",
|
||||||
|
"sha256:c18498c50c59263841862ea0501da9f2b3659c00db54abfbf823a80787fde8ce",
|
||||||
|
"sha256:c49db89d602c24928e68c0d510f4fcf8989d77defd01c973d6cbe27e684833b1",
|
||||||
|
"sha256:ce20044d0317649ddbb4e54dab3c1bcc7483c78c27d3f58ab3d0c7e6bc60d26a",
|
||||||
|
"sha256:d1071414dd06ca2eafa90c85a079169bfeb0e5f57fd0b45d44c092546fcd6fd9",
|
||||||
|
"sha256:d3be11ac43ab1a3e979dac80843b42226d5d3cccd3986f2e03152720a4297cd7",
|
||||||
|
"sha256:db603a1c235d110c860d5f39988ebc8218ee028f07a7cbc056ba6424372ca31b"
|
||||||
|
],
|
||||||
|
"version": "==4.5.2"
|
||||||
|
},
|
||||||
|
"odfpy": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:596021f0519623ca8717331951c95e3b8d7b21e86edc7efe8cb650a0d0f59a2b"
|
||||||
|
],
|
||||||
|
"version": "==1.4.0"
|
||||||
|
},
|
||||||
|
"openpyxl": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:626d38647c063d55803ef4971c4d43226538d4e95cb6260c094e363ee33e10c7"
|
||||||
|
],
|
||||||
|
"version": "==2.4.11"
|
||||||
|
},
|
||||||
|
"pycares": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:2ca080db265ea238dc45f997f94effb62b979a617569889e265c26a839ed6305",
|
||||||
|
"sha256:6f79c6afb6ce603009db2042fddc2e348ad093ece9784cbe2daa809499871a23",
|
||||||
|
"sha256:70918d06eb0603016d37092a5f2c0228509eb4e6c5a3faacb4184f6ab7be7650",
|
||||||
|
"sha256:755187d28d24a9ea63aa2b4c0638be31d65fbf7f0ce16d41261b9f8cb55a1b99",
|
||||||
|
"sha256:7baa4b1f2146eb8423ff8303ebde3a20fb444a60db761fba0430d104fe35ddbf",
|
||||||
|
"sha256:90b27d4df86395f465a171386bc341098d6d47b65944df46518814ae298f6cc6",
|
||||||
|
"sha256:9e090dd6b2afa65cb51c133883b2bf2240fd0f717b130b0048714b33fb0f47ce",
|
||||||
|
"sha256:a11b7d63c3718775f6e805d6464cb10943780395ab042c7e5a0a7a9f612735dd",
|
||||||
|
"sha256:b253f5dcaa0ac7076b79388a3ac80dd8f3bd979108f813baade40d3a9b8bf0bd",
|
||||||
|
"sha256:c7f4f65e44ba35e35ad3febc844270665bba21cfb0fb7d749434e705b556e087",
|
||||||
|
"sha256:cdb342e6a254f035bd976d95807a2184038fc088d957a5104dcaab8be602c093",
|
||||||
|
"sha256:cf08e164f8bfb83b9fe633feb56f2754fae6baefcea663593794fa0518f8f98c",
|
||||||
|
"sha256:df9bc694cf03673878ea8ce674082c5acd134991d64d6c306d4bd61c0c1df98f"
|
||||||
|
],
|
||||||
|
"version": "==3.0.0"
|
||||||
|
},
|
||||||
|
"pycparser": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"
|
||||||
|
],
|
||||||
|
"version": "==2.19"
|
||||||
|
},
|
||||||
|
"pysocks": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:15d38914b60dbcb231d276f64882a20435c049450160e953ca7d313d1405f16f",
|
||||||
|
"sha256:32238918ac0f19e9fd870a8692ac9bd14f5e8752b3c62624cda5851424642210",
|
||||||
|
"sha256:d9031ea45fdfacbe59a99273e9f0448ddb33c1580fe3831c1b09557c5718977c"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==1.7.0"
|
||||||
|
},
|
||||||
|
"pyyaml": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9",
|
||||||
|
"sha256:01adf0b6c6f61bd11af6e10ca52b7d4057dd0be0343eb9283c878cf3af56aee4",
|
||||||
|
"sha256:5124373960b0b3f4aa7df1707e63e9f109b5263eca5976c66e08b1c552d4eaf8",
|
||||||
|
"sha256:5ca4f10adbddae56d824b2c09668e91219bb178a1eee1faa56af6f99f11bf696",
|
||||||
|
"sha256:7907be34ffa3c5a32b60b95f4d95ea25361c951383a894fec31be7252b2b6f34",
|
||||||
|
"sha256:7ec9b2a4ed5cad025c2278a1e6a19c011c80a3caaac804fd2d329e9cc2c287c9",
|
||||||
|
"sha256:87ae4c829bb25b9fe99cf71fbb2140c448f534e24c998cc60f39ae4f94396a73",
|
||||||
|
"sha256:9de9919becc9cc2ff03637872a440195ac4241c80536632fffeb6a1e25a74299",
|
||||||
|
"sha256:a5a85b10e450c66b49f98846937e8cfca1db3127a9d5d1e31ca45c3d0bef4c5b",
|
||||||
|
"sha256:b0997827b4f6a7c286c01c5f60384d218dca4ed7d9efa945c3e1aa623d5709ae",
|
||||||
|
"sha256:b631ef96d3222e62861443cc89d6563ba3eeb816eeb96b2629345ab795e53681",
|
||||||
|
"sha256:bf47c0607522fdbca6c9e817a6e81b08491de50f3766a7a0e6a5be7905961b41",
|
||||||
|
"sha256:f81025eddd0327c7d4cfe9b62cf33190e1e736cc6e97502b3ec425f574b3e7a8"
|
||||||
|
],
|
||||||
|
"version": "==5.1.2"
|
||||||
|
},
|
||||||
|
"records": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:47e4874096f4a8f4b5bcad8c7c7cf512be36186e6e263ff3dfd750b05ff0d3c4",
|
||||||
|
"sha256:cdbacf52c61b4a3bc10fef1286a24a63ae95255a2e7b4e8ccb1e1f96737231ed"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.5.3"
|
||||||
|
},
|
||||||
|
"requests": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4",
|
||||||
|
"sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==2.22.0"
|
||||||
|
},
|
||||||
|
"requests-file": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:75c175eed739270aec3c5279ffd74e6527dada275c5c0d76b5817e9c86bb7dea",
|
||||||
|
"sha256:8f04aa6201bacda0567e7ac7f677f1499b0fc76b22140c54bc06edf1ba92e2fa"
|
||||||
|
],
|
||||||
|
"version": "==1.4.3"
|
||||||
|
},
|
||||||
|
"six": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
|
||||||
|
"sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
|
||||||
|
],
|
||||||
|
"version": "==1.12.0"
|
||||||
|
},
|
||||||
|
"soupsieve": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:72b5f1aea9101cf720a36bb2327ede866fd6f1a07b1e87c92a1cc18113cbc946",
|
||||||
|
"sha256:e4e9c053d59795e440163733a7fec6c5972210e1790c507e4c7b051d6c5259de"
|
||||||
|
],
|
||||||
|
"version": "==1.9.2"
|
||||||
|
},
|
||||||
|
"sqlalchemy": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:217e7fc52199a05851eee9b6a0883190743c4fb9c8ac4313ccfceaffd852b0ff"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.0'",
|
||||||
|
"version": "==1.3.6"
|
||||||
|
},
|
||||||
|
"tablib": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0f88a9cebdaa1a2cc29ae57387082ee81015d1149ecd34e48a8c8d3b4dd21670",
|
||||||
|
"sha256:5f33c079b07eb10cf9c4b4696add2ecf32c89db7729240546ecdcd5c92f67e13"
|
||||||
|
],
|
||||||
|
"version": "==0.13.0"
|
||||||
|
},
|
||||||
|
"termcolor": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"
|
||||||
|
],
|
||||||
|
"version": "==1.1.0"
|
||||||
|
},
|
||||||
|
"tldextract": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:2c1c5d9d454f79734b4f3da0d603856dd9f820753410a3e9abf0a0c9fde33e97",
|
||||||
|
"sha256:b72bef6013de67c7fa181250bc2c2e089a994d259c09ca95a9771f2f97e29ed1"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==2.2.1"
|
||||||
|
},
|
||||||
|
"tqdm": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:14a285392c32b6f8222ecfbcd217838f88e11630affe9006cd0e94c7eff3cb61",
|
||||||
|
"sha256:25d4c0ea02a305a688e7e9c2cdc8f862f989ef2a4701ab28ee963295f5b109ab"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==4.32.2"
|
||||||
|
},
|
||||||
|
"urllib3": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1",
|
||||||
|
"sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232"
|
||||||
|
],
|
||||||
|
"version": "==1.25.3"
|
||||||
|
},
|
||||||
|
"win32-setctime": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:568fd636c68350bcc54755213fe01966fe0a6c90b386c0776425944a0382abef",
|
||||||
|
"sha256:b47e5023ec7f0b4962950902b15bc56464a380d869f59d27dbf9ab423b23e8f9"
|
||||||
|
],
|
||||||
|
"markers": "sys_platform == 'win32'",
|
||||||
|
"version": "==1.0.1"
|
||||||
|
},
|
||||||
|
"xlrd": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:546eb36cee8db40c3eaa46c351e67ffee6eeb5fa2650b71bc4c758a29a1b29b2",
|
||||||
|
"sha256:e551fb498759fa3a5384a94ccd4c3c02eb7c00ea424426e212ac0c57be9dfbde"
|
||||||
|
],
|
||||||
|
"version": "==1.2.0"
|
||||||
|
},
|
||||||
|
"xlwt": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:a082260524678ba48a297d922cc385f58278b8aa68741596a87de01a9c628b2e",
|
||||||
|
"sha256:c59912717a9b28f1a3c2a98fd60741014b06b043936dcecbc113eaaada156c88"
|
||||||
|
],
|
||||||
|
"version": "==1.3.0"
|
||||||
|
},
|
||||||
|
"yarl": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:024ecdc12bc02b321bc66b41327f930d1c2c543fa9a561b39861da9388ba7aa9",
|
||||||
|
"sha256:2f3010703295fbe1aec51023740871e64bb9664c789cba5a6bdf404e93f7568f",
|
||||||
|
"sha256:3890ab952d508523ef4881457c4099056546593fa05e93da84c7250516e632eb",
|
||||||
|
"sha256:3e2724eb9af5dc41648e5bb304fcf4891adc33258c6e14e2a7414ea32541e320",
|
||||||
|
"sha256:5badb97dd0abf26623a9982cd448ff12cb39b8e4c94032ccdedf22ce01a64842",
|
||||||
|
"sha256:73f447d11b530d860ca1e6b582f947688286ad16ca42256413083d13f260b7a0",
|
||||||
|
"sha256:7ab825726f2940c16d92aaec7d204cfc34ac26c0040da727cf8ba87255a33829",
|
||||||
|
"sha256:b25de84a8c20540531526dfbb0e2d2b648c13fd5dd126728c496d7c3fea33310",
|
||||||
|
"sha256:c6e341f5a6562af74ba55205dbd56d248daf1b5748ec48a0200ba227bb9e33f4",
|
||||||
|
"sha256:c9bb7c249c4432cd47e75af3864bc02d26c9594f49c82e2a28624417f0ae63b8",
|
||||||
|
"sha256:e060906c0c585565c718d1c3841747b61c5439af2211e185f6739a9412dfbde1"
|
||||||
|
],
|
||||||
|
"version": "==1.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"develop": {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# OneForAll后续开发计划
|
||||||
|
|
||||||
|
## 下一步计划
|
||||||
|
|
||||||
|
- [ ] 子域收集模块优化
|
||||||
|
- [ ] 子域接管功能实现
|
||||||
|
- [ ] 子域收集爬虫实现
|
||||||
|
- [ ] 操作强大交互人性的前端界面实现
|
||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# coding=utf-8
|
||||||
@@ -0,0 +1,275 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
import signal
|
||||||
|
import pathlib
|
||||||
|
import asyncio
|
||||||
|
import fire
|
||||||
|
import tqdm
|
||||||
|
import exrex
|
||||||
|
import secrets
|
||||||
|
import aiomultiprocess
|
||||||
|
import config
|
||||||
|
from config import logger
|
||||||
|
from common import utils, database, resolve
|
||||||
|
from common.module import Module
|
||||||
|
|
||||||
|
|
||||||
|
def init_worker():
|
||||||
|
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||||
|
|
||||||
|
|
||||||
|
def get_wordlist(name):
|
||||||
|
return config.data_storage_path.joinpath(name)
|
||||||
|
|
||||||
|
|
||||||
|
def detect_wildcard(domain):
|
||||||
|
"""
|
||||||
|
探测域名是否使用泛解析
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:return: 如果没有使用泛解析返回False 使用返回泛解析的IP集合和ttl整型值
|
||||||
|
"""
|
||||||
|
logger.log('INFOR', f'正在探测{domain}是否使用泛解析')
|
||||||
|
token = secrets.token_hex(16)
|
||||||
|
random_subdomain = f'{token}.{domain}'
|
||||||
|
try:
|
||||||
|
answers = resolve.dns_query_a(random_subdomain)
|
||||||
|
except Exception as e: # 如果查询随机域名A记录出错 说明不存在随机子域的A记录 即没有开启泛解析
|
||||||
|
logger.log('DEBUG', e)
|
||||||
|
logger.log('INFOR', f'{domain}没有使用泛解析')
|
||||||
|
return False, None, None
|
||||||
|
ttl = answers.ttl
|
||||||
|
ips = {item.address for item in answers}
|
||||||
|
logger.log('ALERT', f'{domain}使用了泛解析')
|
||||||
|
logger.log('ALERT', f'{random_subdomain} 解析到IP: {ips} TTL: {ttl}')
|
||||||
|
return True, ips, ttl
|
||||||
|
|
||||||
|
|
||||||
|
def wildcard_by_compare(ips, ttl, wildcard_ips, wildcard_ttl):
|
||||||
|
"""
|
||||||
|
通过与泛解析返回的IP集合和判TTL值进行对比判断发现的子域是否是泛解析子域
|
||||||
|
|
||||||
|
:param set ips: 子域A记录查询出的IP集合
|
||||||
|
:param int ttl: 子域A记录查询出的TTL整型值
|
||||||
|
:param set wildcard_ips: 泛解析的IP集合
|
||||||
|
:param int wildcard_ttl: 泛解析的TTL整型值
|
||||||
|
:return: 判断结果
|
||||||
|
:rtype bool
|
||||||
|
"""
|
||||||
|
# 参考:http://sh3ll.me/archives/201704041222.txt
|
||||||
|
if not ips.issubset(wildcard_ips):
|
||||||
|
return False
|
||||||
|
if ttl != wildcard_ttl and ttl % 60 == 0 and wildcard_ttl % 60 == 0:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def wildcard_by_times(ips, ips_times):
|
||||||
|
"""
|
||||||
|
对ips出现次数进行判断泛解析
|
||||||
|
|
||||||
|
:param set ips: 发现子域的IP集合
|
||||||
|
:param ips_times: 子域IP集合出现次数统计字典
|
||||||
|
:return: 判断结果
|
||||||
|
"""
|
||||||
|
times = ips_times.get(str(ips))
|
||||||
|
if times > config.ips_appear_maximum:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def gen_fuzz_domains(domain, rule):
|
||||||
|
domains = set()
|
||||||
|
if '{fuzz}' not in domain:
|
||||||
|
logger.log('FATAL', f'没有指定fuzz位置')
|
||||||
|
return domains
|
||||||
|
if not rule:
|
||||||
|
logger.log('FATAL', f'没有指定fuzz规则')
|
||||||
|
return domains
|
||||||
|
fuzz_count = exrex.count(rule)
|
||||||
|
if fuzz_count > 2000000:
|
||||||
|
logger.log('FATAL', f'fuzz规则范围太大:{fuzz_count} > 2000000')
|
||||||
|
return domains
|
||||||
|
logger.log('INFOR', f'fuzz字典大小:{fuzz_count}')
|
||||||
|
for i in range(3):
|
||||||
|
random_domain = domain.replace('{fuzz}', exrex.getone(rule))
|
||||||
|
logger.log('ALERT', f'请注意检查随机生成的{random_domain}是否正确')
|
||||||
|
logger.log('ALERT', f'你有10秒检查时间退出使用`CTRL+C`')
|
||||||
|
try:
|
||||||
|
time.sleep(5)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logger.log('INFOR', '爆破终止')
|
||||||
|
exit(0)
|
||||||
|
parts = domain.split('{fuzz}')
|
||||||
|
for fuzz in exrex.generate(rule):
|
||||||
|
fuzz_domain = parts[0] + fuzz + parts[1]
|
||||||
|
domains.add(fuzz_domain)
|
||||||
|
return domains
|
||||||
|
|
||||||
|
|
||||||
|
def gen_brute_domains(domain, path):
|
||||||
|
domains = set()
|
||||||
|
with open(path) as file:
|
||||||
|
for line in file:
|
||||||
|
brute_domain = line.strip() + '.' + domain
|
||||||
|
domains.add(brute_domain)
|
||||||
|
logger.log('INFOR', f'爆破字典大小:{len(domains)}')
|
||||||
|
return domains
|
||||||
|
|
||||||
|
|
||||||
|
class AIOBrute(Module):
|
||||||
|
"""
|
||||||
|
多进程多协程异步子域爆破
|
||||||
|
|
||||||
|
:param str target: 单个域名或者每行一个域名的文件路径
|
||||||
|
:param int processes: 爆破的进程数(默认CPU核心数)
|
||||||
|
:param int coroutine: 每个爆破进程下的协程数(默认16)
|
||||||
|
:param str wordlist: 指定爆破所使用的字典路径(默认使用config.py配置)
|
||||||
|
:param bool recursive: 是否使用递归爆破(默认禁用)
|
||||||
|
:param int depth: 递归爆破的深度(默认2)
|
||||||
|
:param str namelist: 指定递归爆破所使用的字典路径(默认使用config.py配置)
|
||||||
|
:param bool fuzz: 是否使用fuzz模式进行爆破(默认禁用,开启必须指定fuzz正则规则)
|
||||||
|
:param str rule: fuzz模式使用的正则规则(默认使用config.py配置)
|
||||||
|
|
||||||
|
Example:
|
||||||
|
python aiobrute.py --target example.com run
|
||||||
|
python aiobrute.py --target ./domains.txt run
|
||||||
|
python aiobrute.py --target example.com --processes 4 --coroutine 64 --wordlist data/subdomains.txt run
|
||||||
|
python aiobrute.py --target example.com --recursive True --depth 2 --namelist data/next_subdomains.txt run
|
||||||
|
python aiobrute.py --target www.{fuzz}.example.com --fuzz True --rule [a-z][0-9] run
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, target, processes=None, coroutine=64, wordlist=None,
|
||||||
|
recursive=False, depth=2, namelist=None, fuzz=False, rule=None):
|
||||||
|
Module.__init__(self)
|
||||||
|
self.domains = set()
|
||||||
|
self.domain = str()
|
||||||
|
self.module = 'Brute'
|
||||||
|
self.source = 'AIOBrute'
|
||||||
|
self.target = target
|
||||||
|
self.processes = processes or config.brute_processes_num or os.cpu_count()
|
||||||
|
self.coroutine = coroutine or config.brute_coroutine_num
|
||||||
|
self.wordlist = wordlist or config.brute_wordlist_path or get_wordlist('subdomains.txt')
|
||||||
|
self.recursive_brute = recursive or config.enable_recursive_brute
|
||||||
|
self.recursive_depth = depth or config.brute_recursive_depth
|
||||||
|
self.recursive_namelist = namelist or config.recursive_namelist_path or get_wordlist('next_subdomains.txt')
|
||||||
|
self.fuzz = fuzz or config.enable_fuzz
|
||||||
|
self.rule = rule or config.fuzz_rule
|
||||||
|
self.nameservers = config.resolver_nameservers
|
||||||
|
self.ips_times = dict() # IP集合出现次数
|
||||||
|
self.enable_wildcard = False # 当前域名是否使用泛解析
|
||||||
|
self.wildcard_ips = set() # 泛解析IP集合
|
||||||
|
self.wildcard_ttl = int() # 泛解析TTL整型值
|
||||||
|
|
||||||
|
def gen_tasks(self, domain):
|
||||||
|
logger.log('INFOR', f'正在生成{domain}的字典')
|
||||||
|
if self.domain != domain: # 如果domain不是self.domain,而是self.domain的子域 生成递归爆破字典
|
||||||
|
domains = gen_brute_domains(domain, self.recursive_namelist)
|
||||||
|
elif self.fuzz and self.rule: # 开启fuzz模式并指定了fuzz正则规则
|
||||||
|
domains = gen_fuzz_domains(domain, self.rule)
|
||||||
|
else:
|
||||||
|
domains = gen_brute_domains(domain, self.wordlist)
|
||||||
|
domains = list(domains)
|
||||||
|
return utils.split_list(domains, 500) # 分割任务组 500个子域为一组任务
|
||||||
|
|
||||||
|
def deal_results(self, results):
|
||||||
|
for result in results:
|
||||||
|
if isinstance(result, Exception):
|
||||||
|
# logger.log('DEBUG', f'爆破{subdomain}时出错 {str(answers)}')
|
||||||
|
continue
|
||||||
|
if isinstance(result, tuple):
|
||||||
|
subdomain, answers = result
|
||||||
|
ips = {record.host for record in answers}
|
||||||
|
value = self.ips_times.setdefault(str(ips), 0) # 取值 如果是首次出现的IP集合 出现次数先赋值0
|
||||||
|
self.ips_times[str(ips)] = value + 1
|
||||||
|
ttl = answers[0].ttl
|
||||||
|
if self.enable_wildcard:
|
||||||
|
if wildcard_by_compare(ips, ttl, self.wildcard_ips, self.wildcard_ttl):
|
||||||
|
continue
|
||||||
|
if wildcard_by_times(ips, self.ips_times):
|
||||||
|
continue
|
||||||
|
logger.log('INFOR', f'发现{self.domain}的子域: {subdomain} 解析IP: {ips} TTL: {ttl}')
|
||||||
|
self.subdomains.add(subdomain)
|
||||||
|
self.records[subdomain] = str(ips)
|
||||||
|
|
||||||
|
async def main(self, domain, rx_queue):
|
||||||
|
if not self.fuzz: # fuzz模式不探测域名是否使用泛解析
|
||||||
|
self.enable_wildcard, self.wildcard_ips, self.wildcard_ttl = detect_wildcard(domain)
|
||||||
|
tasks = self.gen_tasks(domain)
|
||||||
|
logger.log('INFOR', f'正在爆破{domain}的域名')
|
||||||
|
for task in tqdm.tqdm(tasks, desc='Progress', smoothing=1.0, ncols=True):
|
||||||
|
async with aiomultiprocess.Pool(processes=self.processes, initializer=init_worker,
|
||||||
|
childconcurrency=self.coroutine) as pool:
|
||||||
|
try:
|
||||||
|
results = await pool.map(resolve.aiodns_query_a, task)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logger.log('ALERT', '爆破终止正在退出')
|
||||||
|
pool.terminate() # 关闭pool,结束工作进程,不在处理未完成的任务。
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
self.deal_results(results)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
|
||||||
|
def run(self, rx_queue=None):
|
||||||
|
self.domains = utils.get_domains(self.target)
|
||||||
|
while self.domains:
|
||||||
|
self.domain = self.domains.pop()
|
||||||
|
start = time.time()
|
||||||
|
db_conn = database.connect_db()
|
||||||
|
table_name = self.domain.replace('.', '_')
|
||||||
|
database.create_table(db_conn, table_name)
|
||||||
|
if not rx_queue:
|
||||||
|
rx_queue = queue.Queue()
|
||||||
|
logger.log('INFOR', f'开始执行{self.source}模块爆破域名{self.domain}')
|
||||||
|
logger.log('INFOR', f'{self.source}模块使用{self.processes}个进程乘{self.coroutine}个协程')
|
||||||
|
# logger.log('INFOR', f'{self.source}模块使用个进程乘{self.coroutine}个协程')
|
||||||
|
if self.recursive_brute and not self.fuzz: # fuzz模式不使用递归爆破
|
||||||
|
logger.log('INFOR', f'开始递归爆破{self.domain}的第1层子域')
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
asyncio.set_event_loop(loop)
|
||||||
|
loop.run_until_complete(self.main(self.domain, rx_queue))
|
||||||
|
|
||||||
|
# 递归爆破下一层的子域
|
||||||
|
if self.recursive_brute and not self.fuzz: # fuzz模式不使用递归爆破
|
||||||
|
for layer_num in range(1, self.recursive_depth): # 之前已经做过1层子域爆破 当前实际递归层数是layer+1
|
||||||
|
logger.log('INFOR', f'开始递归爆破{self.domain}的第{layer_num+1}层子域')
|
||||||
|
for subdomain in self.subdomains.copy():
|
||||||
|
if subdomain.count('.') - self.domain.count('.') == layer_num: # 进行下一层子域爆破的限制条件
|
||||||
|
loop.run_until_complete(self.main(subdomain, rx_queue))
|
||||||
|
|
||||||
|
|
||||||
|
while not rx_queue.empty(): # 队列不空就一直取数据存数据库
|
||||||
|
database.save_db(db_conn, table_name, rx_queue.get()) # 将结果存入数据库中
|
||||||
|
database.copy_table(db_conn, table_name)
|
||||||
|
database.deduplicate_subdomain(db_conn, table_name)
|
||||||
|
database.remove_invalid(db_conn, table_name)
|
||||||
|
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
logger.log('INFOR', f'结束执行{self.source}模块爆破域名{self.domain}')
|
||||||
|
logger.log('INFOR', f'{self.source}模块耗时{self.elapsed}秒发现{self.domain}的域名{len(self.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{self.source}模块发现{self.domain}的的域名 {self.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, result): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param result: 结果集队列
|
||||||
|
"""
|
||||||
|
brute = AIOBrute(domain)
|
||||||
|
brute.run(result)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# fire.Fire(AIOBrute)
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
被动收集类
|
||||||
|
"""
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
import threading
|
||||||
|
import importlib
|
||||||
|
import config
|
||||||
|
import dbexport
|
||||||
|
from common import database
|
||||||
|
|
||||||
|
|
||||||
|
class Collect(object):
|
||||||
|
"""
|
||||||
|
收集子域名类
|
||||||
|
"""
|
||||||
|
def __init__(self, domain, export=True):
|
||||||
|
self.domain = domain
|
||||||
|
self.elapsed = 0.0
|
||||||
|
self.modules = []
|
||||||
|
self.collect_func = []
|
||||||
|
self.path = None
|
||||||
|
self.export = export
|
||||||
|
self.format = 'xlsx'
|
||||||
|
|
||||||
|
def get_mod(self):
|
||||||
|
"""
|
||||||
|
获取要运行的模块
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
if config.enable_all_module:
|
||||||
|
# modules = ['brute', 'certificates', 'crawl', 'datasets', 'intelligence', 'search']
|
||||||
|
modules = ['certificates', 'check', 'datasets', 'dnsquery', 'intelligence', 'search'] # crawl模块还有点问题
|
||||||
|
# modules = ['intelligence'] # crawl模块还有点问题
|
||||||
|
for module in modules:
|
||||||
|
module_path = config.oneforall_module_path.joinpath(module)
|
||||||
|
for path in module_path.rglob('*.py'):
|
||||||
|
import_module = ('modules.' + module, path.stem) # 需要导入的类
|
||||||
|
self.modules.append(import_module)
|
||||||
|
else:
|
||||||
|
self.modules = config.enable_partial_module
|
||||||
|
|
||||||
|
def import_func(self):
|
||||||
|
"""
|
||||||
|
导入脚本的do函数
|
||||||
|
"""
|
||||||
|
for package, name in self.modules:
|
||||||
|
import_object = importlib.import_module('.'+name, package)
|
||||||
|
self.collect_func.append(getattr(import_object, 'do'))
|
||||||
|
|
||||||
|
def run(self, rx_queue=None):
|
||||||
|
"""
|
||||||
|
类运行入口
|
||||||
|
"""
|
||||||
|
start = time.time()
|
||||||
|
self.get_mod()
|
||||||
|
self.import_func()
|
||||||
|
|
||||||
|
if not rx_queue:
|
||||||
|
rx_queue = queue.Queue(maxsize=len(self.collect_func)) # 结果集队列
|
||||||
|
threads = []
|
||||||
|
# 创建多个子域收集线程
|
||||||
|
for collect_func in self.collect_func:
|
||||||
|
thread = threading.Thread(target=collect_func, args=(self.domain, rx_queue), daemon=True)
|
||||||
|
threads.append(thread)
|
||||||
|
# 启动所有线程
|
||||||
|
for thread in threads:
|
||||||
|
thread.start()
|
||||||
|
# 等待所有线程完成
|
||||||
|
for thread in threads:
|
||||||
|
thread.join()
|
||||||
|
|
||||||
|
db_conn = database.connect_db()
|
||||||
|
table_name = self.domain.replace('.', '_')
|
||||||
|
database.create_table(db_conn, table_name)
|
||||||
|
database.copy_table(db_conn, table_name)
|
||||||
|
database.deduplicate_subdomain(db_conn, table_name)
|
||||||
|
database.remove_invalid(db_conn, table_name)
|
||||||
|
db_conn.close()
|
||||||
|
# 数据库导出
|
||||||
|
if self.export:
|
||||||
|
if not self.path:
|
||||||
|
self.path = config.result_save_path.joinpath(f'{self.domain}.{self.format}')
|
||||||
|
dbexport.export(table_name, path=self.path, format=self.format)
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
a = Collect('example.com')
|
||||||
|
a.run()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# coding=utf-8
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
from .module import Module
|
||||||
|
|
||||||
|
|
||||||
|
class Crawl(Module):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
Module.__init__(self)
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# coding=utf-8
|
||||||
|
|
||||||
|
"""
|
||||||
|
SQLite数据库初始化和操作
|
||||||
|
"""
|
||||||
|
|
||||||
|
import records
|
||||||
|
import config
|
||||||
|
from records import Connection
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
def connect_db(db_path=None):
|
||||||
|
"""
|
||||||
|
获取数据库对象
|
||||||
|
|
||||||
|
:param db_path: 数据库连接或路径
|
||||||
|
:return: SQLite数据库
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'正在获取数据库连接')
|
||||||
|
if isinstance(db_path, Connection):
|
||||||
|
return db_path
|
||||||
|
protocol = 'sqlite:///'
|
||||||
|
if not db_path: # 数据库路径为空连接默认数据库
|
||||||
|
db_path = f'{protocol}{config.result_save_path}/result.sqlite3'
|
||||||
|
else:
|
||||||
|
db_path = protocol + db_path
|
||||||
|
db = records.Database(db_path) # 不存在数据库时会新建一个数据库
|
||||||
|
logger.log('DEBUG', f'使用数据库: {db_path}')
|
||||||
|
return db.get_connection()
|
||||||
|
|
||||||
|
|
||||||
|
def create_table(db_conn, table_name):
|
||||||
|
"""
|
||||||
|
初始化数据库
|
||||||
|
|
||||||
|
:param db_conn: 数据库连接
|
||||||
|
:param str table_name: 要创建的表名
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'正在创建{table_name}表')
|
||||||
|
try:
|
||||||
|
db_conn.query(f'create table if not exists {table_name} ('
|
||||||
|
f'id integer primary key,'
|
||||||
|
f'url text,'
|
||||||
|
f'subdomain text,'
|
||||||
|
f'port int,'
|
||||||
|
f'ips text,'
|
||||||
|
f'status int,'
|
||||||
|
f'reason text,'
|
||||||
|
f'valid int,'
|
||||||
|
f'title text,'
|
||||||
|
f'banner text,'
|
||||||
|
f'module text,'
|
||||||
|
f'source text,'
|
||||||
|
f'elapsed float,'
|
||||||
|
f'count int)')
|
||||||
|
except Exception as e:
|
||||||
|
logger.log('ERROR', e)
|
||||||
|
|
||||||
|
|
||||||
|
def save_db(db_conn, table_name, results, module_name=None):
|
||||||
|
"""
|
||||||
|
将各模块结果存入数据库
|
||||||
|
|
||||||
|
:param db_conn: 数据库连接
|
||||||
|
:param str table_name: 表名
|
||||||
|
:param list results: 结果列表
|
||||||
|
:param str module_name: 模块名
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'正在将{module_name}模块发现{table_name}的子域结果存入数据库')
|
||||||
|
if results:
|
||||||
|
try:
|
||||||
|
db_conn.bulk_query(f'insert into {table_name} (id, url, subdomain, port, ips, status,'
|
||||||
|
f'reason, valid, title, banner, module, source, elapsed, count)'
|
||||||
|
f'values (:id, :url, :subdomain, :port, :ips, :status, :reason, :valid,'
|
||||||
|
f':title, :banner, :module, :source, :elapsed, :count)', results)
|
||||||
|
except Exception as e:
|
||||||
|
logger.log('ERROR', e)
|
||||||
|
|
||||||
|
|
||||||
|
def copy_table(db_conn, table_name):
|
||||||
|
"""
|
||||||
|
复制表创建备份
|
||||||
|
|
||||||
|
:param db_conn: 数据库连接
|
||||||
|
:param str table_name: 表名
|
||||||
|
"""
|
||||||
|
new_table_name = table_name + '_bak'
|
||||||
|
logger.log('DEBUG', f'正在将{table_name}表复制到{new_table_name}新表')
|
||||||
|
try:
|
||||||
|
db_conn.query(f'drop table if exists {new_table_name}')
|
||||||
|
db_conn.query(f'create table {new_table_name} as select * from {table_name}')
|
||||||
|
except Exception as e:
|
||||||
|
logger.log('ERROR', e)
|
||||||
|
|
||||||
|
|
||||||
|
def clear_table(db_conn, table_name):
|
||||||
|
"""
|
||||||
|
清空表中数据
|
||||||
|
|
||||||
|
:param db_conn: 数据库连接
|
||||||
|
:param str table_name: 表名
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'正在清空{table_name}表中的数据')
|
||||||
|
try:
|
||||||
|
db_conn.query(f'delete from {table_name}')
|
||||||
|
except Exception as e:
|
||||||
|
logger.log('ERROR', e)
|
||||||
|
|
||||||
|
|
||||||
|
def deduplicate_subdomain(db_conn, table_name):
|
||||||
|
"""
|
||||||
|
去重表中的子域并删除空值和无效值
|
||||||
|
|
||||||
|
:param db_conn: 数据库连接
|
||||||
|
:param str table_name: 表名
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'正在去重{table_name}表中的子域')
|
||||||
|
try:
|
||||||
|
db_conn.query(f'delete from {table_name} where id not in (select min(id) from {table_name} group by subdomain)')
|
||||||
|
except Exception as e:
|
||||||
|
logger.log('ERROR', e)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_invalid(db_conn, table_name):
|
||||||
|
"""
|
||||||
|
去除表中的空值或无效子域
|
||||||
|
|
||||||
|
:param db_conn: 数据库连接
|
||||||
|
:param str table_name: 表名
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'正在去除{table_name}表中的无效子域')
|
||||||
|
try:
|
||||||
|
db_conn.query(f'delete from {table_name} where subdomain is null or valid == 0')
|
||||||
|
except Exception as e:
|
||||||
|
logger.log('ERROR', e)
|
||||||
|
|
||||||
|
|
||||||
|
def get_data(db_conn, table_name):
|
||||||
|
"""
|
||||||
|
获取表中的所有数据
|
||||||
|
|
||||||
|
:param db_conn: 数据库连接
|
||||||
|
:param str table_name: 表名
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'获取{table_name}表中的所有数据')
|
||||||
|
try:
|
||||||
|
rows = db_conn.query(f'select * from {table_name}')
|
||||||
|
except Exception as e:
|
||||||
|
logger.log('ERROR', e)
|
||||||
|
else:
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
def get_subdomain(db_conn, table_name, valid):
|
||||||
|
"""
|
||||||
|
获取表中的子域数据
|
||||||
|
|
||||||
|
:param db_conn: 数据库连接
|
||||||
|
:param str table_name: 表名
|
||||||
|
:param int valid: 是否有效
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'获取{table_name}表中的所有数据')
|
||||||
|
try:
|
||||||
|
rows = db_conn.query(f'select * from {table_name} where valid = {valid}')
|
||||||
|
except Exception as e:
|
||||||
|
logger.log('ERROR', e)
|
||||||
|
else:
|
||||||
|
return rows
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import re
|
||||||
|
import tldextract
|
||||||
|
import config
|
||||||
|
|
||||||
|
|
||||||
|
class Domain(object):
|
||||||
|
"""
|
||||||
|
域名处理类
|
||||||
|
|
||||||
|
:param str string: 传入的字符串
|
||||||
|
"""
|
||||||
|
def __init__(self, string):
|
||||||
|
self.string = str(string)
|
||||||
|
self.regexp = r'\b((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}\b'
|
||||||
|
self.domain = None
|
||||||
|
|
||||||
|
def match(self):
|
||||||
|
"""
|
||||||
|
域名匹配
|
||||||
|
|
||||||
|
:return: 匹配结果
|
||||||
|
"""
|
||||||
|
result = re.search(self.regexp, self.string, re.I)
|
||||||
|
if result:
|
||||||
|
return result.group()
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def extract(self):
|
||||||
|
"""
|
||||||
|
域名导出
|
||||||
|
|
||||||
|
>>>d = Domain('www.example.com')
|
||||||
|
<domain.Domain object>
|
||||||
|
>>>d.extract()
|
||||||
|
ExtractResult(subdomain='www', domain='example', suffix='com')
|
||||||
|
|
||||||
|
:return: 导出结果
|
||||||
|
"""
|
||||||
|
extract_cache_file = config.data_storage_path.joinpath('public_suffix_list.dat')
|
||||||
|
tldext = tldextract.TLDExtract(extract_cache_file)
|
||||||
|
result = self.match()
|
||||||
|
if result:
|
||||||
|
return tldext(result)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def registered(self):
|
||||||
|
"""
|
||||||
|
获取注册域名
|
||||||
|
|
||||||
|
>>>d = Domain('www.example.com')
|
||||||
|
<domain.Domain object>
|
||||||
|
>>>d.registered()
|
||||||
|
example.com
|
||||||
|
|
||||||
|
:return: 注册域名
|
||||||
|
"""
|
||||||
|
result = self.extract()
|
||||||
|
if result:
|
||||||
|
return result.registered_domain
|
||||||
|
else:
|
||||||
|
return None
|
||||||
@@ -0,0 +1,191 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
模块基类
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import threading
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import config
|
||||||
|
from config import logger
|
||||||
|
from . import utils
|
||||||
|
from .domain import Domain
|
||||||
|
from common import database
|
||||||
|
|
||||||
|
|
||||||
|
lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
|
class Module(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.module = 'Module'
|
||||||
|
self.source = 'BaseModule'
|
||||||
|
self.cookie = None
|
||||||
|
self.header = dict()
|
||||||
|
self.proxy = None
|
||||||
|
self.delay = config.request_delay # 请求睡眠时延
|
||||||
|
self.timeout = config.request_timeout # 请求超时时间
|
||||||
|
self.verify = config.request_verify # 请求SSL验证
|
||||||
|
self.domain = '' # 要进行子域名收集的域名
|
||||||
|
self.subdomains = set() # 存放发现的子域
|
||||||
|
self.records = dict() # 存放子域解析记录
|
||||||
|
self.results = list() # 存放模块结果
|
||||||
|
self.elapsed = 0.0 # 模块执行耗时
|
||||||
|
|
||||||
|
def get(self, url, params=None, **kwargs):
|
||||||
|
"""
|
||||||
|
自定义get请求
|
||||||
|
|
||||||
|
:param str url: 请求地址
|
||||||
|
:param dict params: 请求参数
|
||||||
|
:param kwargs: 其他参数
|
||||||
|
:return: requests响应对象
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
resp = requests.get(url, params=params, cookies=self.cookie, headers=self.header,
|
||||||
|
proxies=self.proxy, timeout=self.timeout, verify=self.verify, **kwargs)
|
||||||
|
except Exception as e:
|
||||||
|
logger.log('ERROR', e)
|
||||||
|
return None
|
||||||
|
if resp.status_code != 200:
|
||||||
|
logger.log('ALERT', f'GET {resp.url} {resp.status_code} - {resp.reason} {len(resp.content)}')
|
||||||
|
content_type = resp.headers.get('Content-Type')
|
||||||
|
if content_type:
|
||||||
|
if 'json' in content_type:
|
||||||
|
logger.log('ALERT', resp.json())
|
||||||
|
return None
|
||||||
|
logger.log('DEBUG', f'GET {resp.url} {resp.status_code} - {resp.reason} {len(resp.content)}')
|
||||||
|
return resp
|
||||||
|
|
||||||
|
def post(self, url, data=None, **kwargs):
|
||||||
|
"""
|
||||||
|
自定义post请求
|
||||||
|
|
||||||
|
:param str url: 请求地址
|
||||||
|
:param dict data: 请求数据
|
||||||
|
:param kwargs: 其他参数
|
||||||
|
:return: requests响应对象
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
resp = requests.post(url, data=data, cookies=self.cookie, headers=self.header,
|
||||||
|
proxies=self.proxy, timeout=self.timeout, verify=self.verify, **kwargs)
|
||||||
|
except Exception as e:
|
||||||
|
logger.log('ERROR', e)
|
||||||
|
return None
|
||||||
|
if resp.status_code != 200:
|
||||||
|
content_type = resp.headers.get('Content-Type')
|
||||||
|
if content_type:
|
||||||
|
if 'json' in content_type:
|
||||||
|
logger.log('ALERT', resp.json())
|
||||||
|
return None
|
||||||
|
logger.log('DEBUG', f'POST {resp.url} {resp.status_code} - {resp.reason} {len(resp.content)}')
|
||||||
|
return resp
|
||||||
|
|
||||||
|
def get_header(self):
|
||||||
|
"""
|
||||||
|
获取请求头
|
||||||
|
|
||||||
|
:return: 请求头
|
||||||
|
"""
|
||||||
|
# logger.log('DEBUG', f'获取请求头')
|
||||||
|
if config.enable_fake_header:
|
||||||
|
return utils.gen_fake_header()
|
||||||
|
else:
|
||||||
|
return self.header
|
||||||
|
|
||||||
|
def get_proxy(self, module):
|
||||||
|
"""
|
||||||
|
获取代理
|
||||||
|
|
||||||
|
:param str module: 模块名
|
||||||
|
:return: 代理字典
|
||||||
|
"""
|
||||||
|
if not config.enable_proxy:
|
||||||
|
logger.log('DEBUG', f'所有模块不使用代理')
|
||||||
|
return self.proxy
|
||||||
|
if config.proxy_all_module:
|
||||||
|
logger.log('DEBUG', f'{module}模块使用代理')
|
||||||
|
return utils.get_random_proxy()
|
||||||
|
if module in config.proxy_partial_module:
|
||||||
|
logger.log('DEBUG', f'{module}模块使用代理')
|
||||||
|
return utils.get_random_proxy()
|
||||||
|
else:
|
||||||
|
logger.log('DEBUG', f'{module}模块不使用代理')
|
||||||
|
return self.proxy
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def match(domain, html, distinct=True):
|
||||||
|
"""
|
||||||
|
正则匹配出子域
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param str html: 要匹配的html响应体
|
||||||
|
:param bool distinct: 匹配结果去除
|
||||||
|
:return: 匹配出的子域集合或列表
|
||||||
|
:rtype: set or list
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'正则匹配响应体中的子域')
|
||||||
|
regexp = r'(?:\>|\"|\'|\=|\,)(?:http\:\/\/|https\:\/\/)?(?:[a-z0-9](?:[a-z0-9\-]{0,61}[a-z0-9])?\.){0,}' \
|
||||||
|
+ domain.replace('.', r'\.')
|
||||||
|
result = re.findall(regexp, html, re.I)
|
||||||
|
if not result:
|
||||||
|
return set()
|
||||||
|
deal = map(lambda s: re.sub(r'(?:http://|https://)', '', s[1:].lower(), flags=re.I), result)
|
||||||
|
if distinct:
|
||||||
|
return set(deal)
|
||||||
|
else:
|
||||||
|
return list(deal)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def register(domain):
|
||||||
|
"""
|
||||||
|
获取注册域名
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:return: 注册域名
|
||||||
|
"""
|
||||||
|
return Domain(domain).registered()
|
||||||
|
|
||||||
|
def save_json(self):
|
||||||
|
"""
|
||||||
|
将各模块结果保存为json文件
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'将{self.source}模块发现的子域结果保存为json文件')
|
||||||
|
if config.save_module_result:
|
||||||
|
dirpath = config.result_save_path.joinpath(self.domain, self.module)
|
||||||
|
dirpath.mkdir(parents=True, exist_ok=True)
|
||||||
|
name = self.source + '.json'
|
||||||
|
path = dirpath.joinpath(name)
|
||||||
|
with open(path, mode='w', encoding='utf-8') as file:
|
||||||
|
result = {'domain': self.domain, 'name': self.module, 'source': self.source, 'elapsed': self.elapsed,
|
||||||
|
'count': len(self.subdomains), 'subdomains': list(self.subdomains), 'records': self.records}
|
||||||
|
json.dump(result, file, ensure_ascii=False, indent=4)
|
||||||
|
|
||||||
|
def gen_result(self):
|
||||||
|
results = list()
|
||||||
|
if not len(self.subdomains): # 一个子域都没有发现的情况
|
||||||
|
result = {'id': None, 'url': None, 'subdomain': None, 'port': None, 'ips': None, 'status': None,
|
||||||
|
'reason': None, 'valid': 1, 'title': None, 'banner': None, 'module': self.module,
|
||||||
|
'source': self.source, 'elapsed': self.elapsed, 'count': 0}
|
||||||
|
results.append(result)
|
||||||
|
self.results = (self.source, results)
|
||||||
|
else:
|
||||||
|
for subdomain in self.subdomains:
|
||||||
|
url = 'http://' + subdomain
|
||||||
|
ips = self.records.get(subdomain)
|
||||||
|
result = {'id': None, 'url': url, 'subdomain': subdomain, 'port': None, 'ips': ips, 'status': None,
|
||||||
|
'reason': None, 'valid': 1, 'title': None, 'banner': None, 'module': self.module,
|
||||||
|
'source': self.source, 'elapsed': self.elapsed, 'count': len(self.subdomains)}
|
||||||
|
results.append(result)
|
||||||
|
self.results = (self.source, results)
|
||||||
|
|
||||||
|
def save_db(self):
|
||||||
|
lock.acquire()
|
||||||
|
db_conn = database.connect_db()
|
||||||
|
table_name = self.domain.replace('.', '_')
|
||||||
|
database.create_table(db_conn, table_name)
|
||||||
|
source, results = self.results
|
||||||
|
database.save_db(db_conn, table_name, results, source) # 将结果存入数据库中
|
||||||
|
lock.release()
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
from .module import Module
|
||||||
|
|
||||||
|
|
||||||
|
class Query(Module):
|
||||||
|
"""
|
||||||
|
查询基类
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
Module.__init__(self)
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import functools
|
||||||
|
import aiohttp
|
||||||
|
from aiohttp import ClientSession
|
||||||
|
from aiohttp.resolver import AsyncResolver
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
import config
|
||||||
|
from common import utils
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
def get_ports(port):
|
||||||
|
logger.log('INFOR', f'正在获取请求端口范围')
|
||||||
|
ports = set()
|
||||||
|
if isinstance(port, set):
|
||||||
|
ports = port
|
||||||
|
elif isinstance(port, str):
|
||||||
|
if port not in {'small', 'medium', 'large', 'xlarge'}:
|
||||||
|
logger.log('ERROR', f'不存在{port}等端口范围')
|
||||||
|
port = 'medium'
|
||||||
|
ports = config.ports.get(port)
|
||||||
|
logger.log('INFOR', f'使用{port}等端口范围')
|
||||||
|
if not ports: # 意外情况 ports_range为空使用使用中等端口范围
|
||||||
|
logger.log('ALERT', f'使用medium等端口范围')
|
||||||
|
ports = config.ports.get('medium')
|
||||||
|
return ports
|
||||||
|
|
||||||
|
|
||||||
|
def gen_new_datas(datas, ports):
|
||||||
|
logger.log('INFOR', f'正在生成请求地址')
|
||||||
|
new_datas = []
|
||||||
|
protocols = ['http://', 'https://']
|
||||||
|
for data in datas:
|
||||||
|
if data.get('valid'): # 有效的子域才进行http请求探测
|
||||||
|
subdomain = data.get('subdomain')
|
||||||
|
for port in ports:
|
||||||
|
for protocol in protocols:
|
||||||
|
url = f'{protocol}{subdomain}:{port}'
|
||||||
|
data['id'] = None
|
||||||
|
data['url'] = url
|
||||||
|
data['port'] = port
|
||||||
|
new_datas.append(data)
|
||||||
|
data = dict(data) # 需要生成一个新的字典对象
|
||||||
|
return new_datas
|
||||||
|
|
||||||
|
|
||||||
|
async def fetch(session, url, semaphore):
|
||||||
|
"""
|
||||||
|
请求
|
||||||
|
|
||||||
|
:param session: session对象
|
||||||
|
:param url: url地址
|
||||||
|
:param semaphore: 同步对象(控制并发量)
|
||||||
|
:return: 响应对象和响应文本
|
||||||
|
"""
|
||||||
|
timeout = aiohttp.ClientTimeout(total=config.get_timeout)
|
||||||
|
async with semaphore:
|
||||||
|
async with session.get(url, allow_redirects=config.get_redirects,
|
||||||
|
timeout=timeout, proxy=config.get_proxy) as resp:
|
||||||
|
text = await resp.text()
|
||||||
|
return resp, text
|
||||||
|
|
||||||
|
|
||||||
|
def request_callback(future, index, datas):
|
||||||
|
try:
|
||||||
|
resp, text = future.result()
|
||||||
|
except Exception as e:
|
||||||
|
logger.log('DEBUG', e.args)
|
||||||
|
datas[index]['reason'] = str(e.args)
|
||||||
|
datas[index]['valid'] = 0
|
||||||
|
else:
|
||||||
|
datas[index]['reason'] = resp.reason
|
||||||
|
datas[index]['status'] = resp.status
|
||||||
|
if resp.status == 400 or resp.status >= 500:
|
||||||
|
datas[index]['valid'] = 0
|
||||||
|
else:
|
||||||
|
headers = resp.headers
|
||||||
|
banner = str({'Server': headers.get('Server'), 'Via': headers.get('Via'),
|
||||||
|
'X-Powered-By': headers.get('X-Powered-By')})
|
||||||
|
datas[index]['banner'] = banner
|
||||||
|
soup = BeautifulSoup(text, 'lxml')
|
||||||
|
title = soup.title
|
||||||
|
head = soup.head
|
||||||
|
if title:
|
||||||
|
datas[index]['title'] = title.text
|
||||||
|
elif head:
|
||||||
|
datas[index]['title'] = head.text
|
||||||
|
else:
|
||||||
|
datas[index]['title'] = text
|
||||||
|
|
||||||
|
|
||||||
|
async def bulk_get_request(datas, port):
|
||||||
|
logger.log('INFOR', f'正在异步进行子域的GET请求')
|
||||||
|
ports = get_ports(port)
|
||||||
|
new_datas = gen_new_datas(datas, ports)
|
||||||
|
header = None
|
||||||
|
if config.fake_header:
|
||||||
|
header = utils.gen_fake_header()
|
||||||
|
resolver = AsyncResolver(nameservers=config.resolver_nameservers) # 使用异步域名解析器 自定义域名服务器
|
||||||
|
conn = aiohttp.TCPConnector(verify_ssl=config.verify_ssl, limit=config.limit_open_conn,
|
||||||
|
limit_per_host=config.limit_per_host, resolver=resolver)
|
||||||
|
semaphore = asyncio.Semaphore(utils.get_semaphore())
|
||||||
|
async with ClientSession(connector=conn, headers=header) as session:
|
||||||
|
tasks = []
|
||||||
|
for i, data in enumerate(new_datas):
|
||||||
|
url = data.get('url')
|
||||||
|
task = asyncio.ensure_future(fetch(session, url, semaphore))
|
||||||
|
task.add_done_callback(functools.partial(request_callback, index=i, datas=new_datas))
|
||||||
|
tasks.append(task)
|
||||||
|
if tasks: # 任务列表里有任务不空时才进行解析
|
||||||
|
await asyncio.wait(tasks) # 等待所有task完成
|
||||||
|
logger.log('INFOR', f'完成异步进行子域的GET请求')
|
||||||
|
return new_datas
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import asyncio
|
||||||
|
import functools
|
||||||
|
|
||||||
|
import dns.resolver
|
||||||
|
import aiodns
|
||||||
|
import config
|
||||||
|
from common import utils
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
def dns_resolver():
|
||||||
|
"""
|
||||||
|
dns解析器
|
||||||
|
"""
|
||||||
|
resolver = dns.resolver.Resolver()
|
||||||
|
resolver.nameservers = config.resolver_nameservers
|
||||||
|
resolver.timeout = config.resolver_timeout
|
||||||
|
resolver.lifetime = config.resolver_lifetime
|
||||||
|
return resolver
|
||||||
|
|
||||||
|
|
||||||
|
def dns_query_a(hostname):
|
||||||
|
"""
|
||||||
|
查询A记录
|
||||||
|
|
||||||
|
:param str hostname: 主机名
|
||||||
|
:return: 查询结果
|
||||||
|
"""
|
||||||
|
resolver = dns_resolver()
|
||||||
|
return resolver.query(hostname, 'A')
|
||||||
|
|
||||||
|
|
||||||
|
def aiodns_resolver():
|
||||||
|
"""
|
||||||
|
异步dns解析器
|
||||||
|
"""
|
||||||
|
return aiodns.DNSResolver(nameservers=config.resolver_nameservers,
|
||||||
|
timeout=config.resolver_timeout)
|
||||||
|
|
||||||
|
|
||||||
|
async def aiodns_query_a(hostname, semaphore):
|
||||||
|
"""
|
||||||
|
异步查询A记录
|
||||||
|
|
||||||
|
:param str hostname: 主机名
|
||||||
|
:param semaphore: 并发查询数量
|
||||||
|
:return: 主机名或查询结果或查询异常
|
||||||
|
"""
|
||||||
|
async with semaphore:
|
||||||
|
resolver = aiodns_resolver()
|
||||||
|
answers = await resolver.query(hostname, 'A')
|
||||||
|
return hostname, answers
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_callback(future, index, datas):
|
||||||
|
try:
|
||||||
|
result = future.result()
|
||||||
|
except aiodns.error.DNSError as e:
|
||||||
|
datas[index]['ips'] = str(e.args)
|
||||||
|
datas[index]['valid'] = 0
|
||||||
|
else:
|
||||||
|
if isinstance(result, tuple):
|
||||||
|
_, answers = result
|
||||||
|
ips = {record.host for record in answers}
|
||||||
|
datas[index]['ips'] = str(ips)
|
||||||
|
|
||||||
|
|
||||||
|
async def bulk_query_a(datas):
|
||||||
|
"""
|
||||||
|
批量查询A记录
|
||||||
|
|
||||||
|
:param datas: 待查的数据集
|
||||||
|
:return: 查询过得到的结果集
|
||||||
|
"""
|
||||||
|
logger.log('INFOR', '正在异步查询子域的A记录')
|
||||||
|
tasks = []
|
||||||
|
semaphore = asyncio.Semaphore(utils.get_semaphore())
|
||||||
|
for i, data in enumerate(datas):
|
||||||
|
if not data.get('ips'):
|
||||||
|
subdomain = data.get('subdomain')
|
||||||
|
task = asyncio.ensure_future(aiodns_query_a(subdomain, semaphore))
|
||||||
|
task.add_done_callback(functools.partial(resolve_callback, index=i, datas=datas)) # 回调
|
||||||
|
tasks.append(task)
|
||||||
|
if tasks: # 任务列表里有任务不空时才进行解析
|
||||||
|
await asyncio.wait(tasks) # 等待所有task完成
|
||||||
|
logger.log('INFOR', '完成异步查询子域的A记录')
|
||||||
|
return datas
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import requests
|
||||||
|
import config
|
||||||
|
from .module import Module
|
||||||
|
from . import utils
|
||||||
|
|
||||||
|
|
||||||
|
class Search(Module):
|
||||||
|
"""
|
||||||
|
搜索基类
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
Module.__init__(self)
|
||||||
|
self.page_num = 0 # 要显示搜索起始条数
|
||||||
|
self.per_page_num = 50 # 每页显示搜索条数
|
||||||
|
self.recursive_search = config.enable_recursive_search
|
||||||
|
self.recursive_times = config.search_recursive_times
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def filter(domain, subdomain):
|
||||||
|
"""
|
||||||
|
生成搜索过滤语句
|
||||||
|
使用搜索引擎支持的-site:语法过滤掉搜索页面较多的子域以发现新域
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param set subdomain: 子域名集合
|
||||||
|
:return: 过滤语句
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
statements_list = []
|
||||||
|
subdomains_temp = set(map(lambda x: x + '.' + domain, config.subdomains_common))
|
||||||
|
subdomains_temp = list(subdomain.intersection(subdomains_temp))
|
||||||
|
for i in range(0, len(subdomains_temp), 2): # 同时排除2个子域
|
||||||
|
statements_list.append(''.join(set(map(lambda s: ' -site:' + s, subdomains_temp[i:i + 2]))))
|
||||||
|
return statements_list
|
||||||
|
|
||||||
|
def match_location(self, domain, url):
|
||||||
|
"""
|
||||||
|
匹配跳转之后的url
|
||||||
|
针对部分搜索引擎(如百度搜索)搜索展示url时有显示不全的情况
|
||||||
|
此函数会向每条结果的链接发送head请求获取响应头的location值并做子域匹配
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param str url: 展示结果的url链接
|
||||||
|
:return: 匹配的子域
|
||||||
|
:rtype set
|
||||||
|
"""
|
||||||
|
resp = requests.head(url, headers=self.header, proxies=self.proxy, timeout=self.timeout, allow_redirects=False)
|
||||||
|
location = resp.headers.get('location')
|
||||||
|
return set(utils.match_subdomain(domain, location))
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import re
|
||||||
|
import pathlib
|
||||||
|
import random
|
||||||
|
import ipaddress
|
||||||
|
import platform
|
||||||
|
import config
|
||||||
|
from fake_useragent import UserAgent
|
||||||
|
from common.domain import Domain
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
def match_subdomain(domain, text, distinct=True):
|
||||||
|
"""
|
||||||
|
匹配text中domain的子域名
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param str text: 响应文本
|
||||||
|
:param bool distinct: 结果去重
|
||||||
|
:return: 匹配结果
|
||||||
|
:rtype: set or list
|
||||||
|
"""
|
||||||
|
regexp = r'(?:[a-z0-9](?:[a-z0-9\-]{0,61}[a-z0-9])?\.){0,}' + domain.replace('.', r'\.')
|
||||||
|
result = re.findall(regexp, text, re.I)
|
||||||
|
if not result:
|
||||||
|
return set()
|
||||||
|
deal = map(lambda s: s.lower(), result)
|
||||||
|
if distinct:
|
||||||
|
return set(deal)
|
||||||
|
else:
|
||||||
|
return list(deal)
|
||||||
|
|
||||||
|
|
||||||
|
def gen_random_ip():
|
||||||
|
"""
|
||||||
|
生成随机的点分十进制的IP字符串
|
||||||
|
"""
|
||||||
|
while True:
|
||||||
|
ip = ipaddress.IPv4Address(random.randint(0, 2 ** 32 - 1))
|
||||||
|
if ip.is_global:
|
||||||
|
return ip.exploded
|
||||||
|
|
||||||
|
|
||||||
|
def gen_fake_header():
|
||||||
|
"""
|
||||||
|
生成伪造请求头
|
||||||
|
"""
|
||||||
|
ua = UserAgent()
|
||||||
|
ip = gen_random_ip()
|
||||||
|
headers = {
|
||||||
|
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||||
|
'Accept-Encoding': 'gzip, deflate, br',
|
||||||
|
'Accept-Language': 'en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7',
|
||||||
|
'Cache-Control': 'max-age=0',
|
||||||
|
'Connection': 'keep-alive',
|
||||||
|
'DNT': '1',
|
||||||
|
'Referer': 'https://www.google.com/',
|
||||||
|
'Upgrade-Insecure-Requests': '1',
|
||||||
|
'User-Agent': ua.random,
|
||||||
|
'X-Forwarded-For': ip,
|
||||||
|
'X-Real-IP': ip
|
||||||
|
}
|
||||||
|
return headers
|
||||||
|
|
||||||
|
|
||||||
|
def get_random_proxy():
|
||||||
|
"""
|
||||||
|
获取随机代理
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return random.choice(config.proxy_pool)
|
||||||
|
except IndexError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def split_list(ls, size):
|
||||||
|
"""
|
||||||
|
将ls列表按size大小划分并返回新的划分结果列表
|
||||||
|
|
||||||
|
:param list ls: 要划分的列表
|
||||||
|
:param int size: 划分大小
|
||||||
|
:return 划分结果
|
||||||
|
|
||||||
|
>>> split_list([1, 2, 3, 4], 3)
|
||||||
|
[[1, 2, 3], [4]]
|
||||||
|
"""
|
||||||
|
if size == 0:
|
||||||
|
return ls
|
||||||
|
return [ls[i:i+size] for i in range(0, len(ls), size)]
|
||||||
|
|
||||||
|
|
||||||
|
def get_domains(target):
|
||||||
|
"""
|
||||||
|
获取域名
|
||||||
|
|
||||||
|
:param set or str target:
|
||||||
|
:return: 域名集合
|
||||||
|
"""
|
||||||
|
domains = set()
|
||||||
|
logger.log('INFOR', f'正在获取域名')
|
||||||
|
if isinstance(target, set):
|
||||||
|
domains = target
|
||||||
|
elif isinstance(target, str):
|
||||||
|
path = pathlib.Path(target)
|
||||||
|
if path.is_file():
|
||||||
|
with open(target) as file:
|
||||||
|
for line in file:
|
||||||
|
domain = Domain(line.strip()).match()
|
||||||
|
if domain:
|
||||||
|
domains.add(domain)
|
||||||
|
if Domain(target).match():
|
||||||
|
domains = {target}
|
||||||
|
logger.log('INFOR', f'获取到{len(domains)}个域名')
|
||||||
|
return domains
|
||||||
|
|
||||||
|
|
||||||
|
def get_semaphore():
|
||||||
|
"""
|
||||||
|
获取查询并发值
|
||||||
|
|
||||||
|
:return: 并发整型值
|
||||||
|
"""
|
||||||
|
system = platform.system()
|
||||||
|
if system == 'Windows':
|
||||||
|
return 500
|
||||||
|
elif system == 'Linux':
|
||||||
|
return 1000
|
||||||
|
elif system == 'Darwin':
|
||||||
|
return 1000
|
||||||
|
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
OneForAll配置
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import pathlib
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
# 路径设置
|
||||||
|
oneforall_relpath = pathlib.Path(__file__).parent # oneforall代码相对路径
|
||||||
|
oneforall_abspath = oneforall_relpath.resolve() # oneforall代码绝对路径
|
||||||
|
oneforall_module_path = oneforall_relpath.joinpath('modules') # oneforall模块目录
|
||||||
|
data_storage_path = oneforall_relpath.joinpath('data') # 数据存放目录
|
||||||
|
result_save_path = oneforall_relpath.joinpath('results') # 结果保存目录
|
||||||
|
|
||||||
|
# 模块设置
|
||||||
|
save_module_result = True # 保存模块中各脚本结果 默认保存
|
||||||
|
enable_all_module = True # 启用所有模块 默认启用
|
||||||
|
enable_partial_module = [] # 启用部分模块 必须禁用enable_all_module才能生效
|
||||||
|
# enable_partial_module = [('modules.search', 'google')] # 启用部分模块 必须禁用enable_all_module才能生效
|
||||||
|
|
||||||
|
|
||||||
|
# 爆破模块设置
|
||||||
|
enable_brute_module = False # 使用爆破模块(默认禁用)
|
||||||
|
enable_wildcard_check = True # 开启泛解析检测 会去掉泛解析的子域
|
||||||
|
brute_processes_num = None # 爆破时使用的进程数(根据系统中CPU数量情况设置 不宜大于CPU数量 默认None为系统中的CPU数量)
|
||||||
|
brute_coroutine_num = 128 # 爆破时每个进程下的协程数(不宜大于1000)
|
||||||
|
brute_wordlist_path = None # 爆破所使用的字典路径 默认data/subdomains.dict
|
||||||
|
use_general_wordlist = False # 是否使用通用字典(默认禁用)
|
||||||
|
enable_recursive_brute = False # 是否使用递归爆破(默认禁用)
|
||||||
|
brute_recursive_depth = 2 # 递归爆破深度(默认2层)
|
||||||
|
recursive_namelist_path = None # 爆破下一层子域所使用的字典路径 默认data/next_subdomains.dict
|
||||||
|
enable_fuzz = False # 是否使用fuzz模式枚举域名
|
||||||
|
fuzz_rule = '' # fuzz域名的正则 示例:[a-z][0-9] 第一位是字母 第二位是数字
|
||||||
|
ips_appear_maximum = 10 # 同一IP集合出现次数超过10认为是泛解析
|
||||||
|
|
||||||
|
# 代理设置
|
||||||
|
enable_proxy = True # 是否使用代理 全局开关
|
||||||
|
proxy_all_module = False # 代理所有模块
|
||||||
|
proxy_partial_module = ['GoogleQuery', 'AskSearch', 'DuckDuckGoSearch', 'GoogleAPISearch',
|
||||||
|
'GoogleSearch', 'YahooSearch', 'YandexSearch'] # 代理自定义的模块
|
||||||
|
proxy_pool = [{'http': 'http://127.0.0.1:1080', 'https': 'https://127.0.0.1:1080'}] # 代理池
|
||||||
|
# proxy_pool = [{'http': 'socks5://127.0.0.1:10808', 'https': 'socks5://127.0.0.1:10808'}] # 代理池
|
||||||
|
|
||||||
|
|
||||||
|
# 网络请求设置
|
||||||
|
enable_fake_header = True # 启用伪造请求头
|
||||||
|
request_delay = 1 # 请求时延
|
||||||
|
request_timeout = 30 # 请求超时(AskSearch和YahooSearch比较慢)
|
||||||
|
request_verify = True # 请求SSL验证
|
||||||
|
|
||||||
|
# 搜索模块设置
|
||||||
|
enable_recursive_search = False # 递归搜索子域
|
||||||
|
search_recursive_times = 2 # 递归搜索层数
|
||||||
|
|
||||||
|
# DNS解析设置
|
||||||
|
resolver_nameservers = [
|
||||||
|
'119.29.29.29', '182.254.116.116', # DNSPod
|
||||||
|
'180.76.76.76', # Baidu DNS
|
||||||
|
'223.5.5.5', '223.6.6.6', # AliDNS
|
||||||
|
'114.114.114.114', '114.114.115.115' # 114DNS
|
||||||
|
# '8.8.8.8', '8.8.4.4', # Google DNS
|
||||||
|
# '1.0.0.1', '1.1.1.1' # CloudFlare DNS
|
||||||
|
# '208.67.222.222', '208.67.220.220' # OpenDNS
|
||||||
|
] # 指定查询的DNS域名服务器
|
||||||
|
resolver_timeout = 5.0 # 解析超时时间
|
||||||
|
resolver_lifetime = 30.0 # 解析存活时间
|
||||||
|
|
||||||
|
# http探测设置
|
||||||
|
small_ports = {80, 443}
|
||||||
|
medium_ports = {80, 443, 8000, 8080, 8443} # 默认使用
|
||||||
|
large_ports = {80, 81, 443, 591, 2082, 2087, 2095, 2096, 3000, 8000, 8001,
|
||||||
|
8008, 8080, 8083, 8443, 8834, 8888}
|
||||||
|
xlarge_ports = {80, 81, 300, 443, 591, 593, 832, 981, 1010, 1311, 2082,
|
||||||
|
2087, 2095, 2096, 2480, 3000, 3128, 3333, 4243, 4567, 4711,
|
||||||
|
4712, 4993, 5000, 5104, 5108, 5800, 6543, 7000, 7396, 7474,
|
||||||
|
8000, 8001, 8008, 8014, 8042, 8069, 8080, 8081, 8088, 8090,
|
||||||
|
8091, 8118, 8123, 8172, 8222, 8243, 8280, 8281, 8333, 8443,
|
||||||
|
8500, 8834, 8880, 8888, 8983, 9000, 9043, 9060, 9080, 9090,
|
||||||
|
9091, 9200, 9443, 9800, 9981, 12443, 16080, 18091, 18092,
|
||||||
|
20720, 28017}
|
||||||
|
ports = {'small': small_ports, 'medium': medium_ports, 'large': large_ports, 'xlarge': xlarge_ports}
|
||||||
|
verify_ssl = False
|
||||||
|
get_proxy = None # aiohttp 支持 HTTP/HTTPS形式的代理 proxy="http://user:pass@some.proxy.com"
|
||||||
|
get_timeout = 10 # http请求探测总超时时间 None或者0则表示不检测超时
|
||||||
|
get_redirects = True # 允许请求跳转
|
||||||
|
fake_header = True # 使用伪造请求头
|
||||||
|
limit_open_conn = 100 # 限制同一时间打开的连接数(默认100),0表示不限制
|
||||||
|
limit_per_host = 0 # 限制同一时间在同一个端点((host, port, is_ssl) 3者都一样的情况)打开的连接数(默认0表示不限制)
|
||||||
|
|
||||||
|
|
||||||
|
# 模块API配置
|
||||||
|
# Censys可以免费注册获取API:https://censys.io/api
|
||||||
|
censys_api_id = ''
|
||||||
|
censys_api_secret = ''
|
||||||
|
|
||||||
|
# Binaryedge可以免费注册获取API:https://app.binaryedge.io/account/api
|
||||||
|
# 免费的API有效期只有1个月,到期之后可以再次生成,每月可以查询250次。
|
||||||
|
binaryedge_api = ''
|
||||||
|
|
||||||
|
# Binaryedge可以免费注册获取API:http://api.chinaz.com/ApiDetails/Alexa
|
||||||
|
chinaz_api = ''
|
||||||
|
|
||||||
|
# Bing可以免费注册获取API:https://azure.microsoft.com/zh-cn/services/cognitive-services/bing-web-search-api/#web-json
|
||||||
|
bing_api_id = ''
|
||||||
|
bing_api_key = ''
|
||||||
|
|
||||||
|
# SecurityTrails可以免费注册获取API:https://securitytrails.com/corp/api
|
||||||
|
securitytrails_api = ''
|
||||||
|
|
||||||
|
# https://fofa.so/api
|
||||||
|
fofa_api_email = '' # fofa用户邮箱
|
||||||
|
fofa_api_key = '' # fofa用户key
|
||||||
|
|
||||||
|
# Google可以免费注册获取API: https://developers.google.com/custom-search/v1/overview
|
||||||
|
# 免费的API只能查询前100条结果
|
||||||
|
google_api_key = '' # Google API搜索key
|
||||||
|
google_api_cx = '' # Google API搜索cx
|
||||||
|
|
||||||
|
# https://api.passivetotal.org/api/docs/
|
||||||
|
riskiq_api_username = ''
|
||||||
|
riskiq_api_key = ''
|
||||||
|
|
||||||
|
# Shodan可以免费注册获取API: https://account.shodan.io/register
|
||||||
|
# 免费的API限速1秒查询1次
|
||||||
|
shodan_api_key = ''
|
||||||
|
# ThreatBook API 查询子域名需要收费 https://x.threatbook.cn/nodev4/vb4/myAPI
|
||||||
|
threatbook_api_key = ''
|
||||||
|
|
||||||
|
# VirusTotal可以免费注册获取API: https://developers.virustotal.com/reference
|
||||||
|
virustotal_api_key = ''
|
||||||
|
|
||||||
|
# https://www.zoomeye.org/doc?channel=api
|
||||||
|
zoomeye_api_username = ''
|
||||||
|
zoomeye_api_password = ''
|
||||||
|
|
||||||
|
# Certdb可以免费注册获取API: https://spyse.com/
|
||||||
|
certdb_api_token = ''
|
||||||
|
|
||||||
|
# https://www.circl.lu/services/passive-dns/
|
||||||
|
circl_api_username = ''
|
||||||
|
circl_api_password = ''
|
||||||
|
|
||||||
|
# https://www.dnsdb.info/
|
||||||
|
dnsdb_api_key = ''
|
||||||
|
|
||||||
|
# ipv4info可以免费注册获取API: http://ipv4info.com/tools/api/
|
||||||
|
# 免费的API有效期只有2天,到期之后可以再次生成,每天可以查询50次。
|
||||||
|
ipv4info_api_key = ''
|
||||||
|
|
||||||
|
subdomains_common = {'i', 'w', 'm', 'en', 'us', 'zh', 'w3', 'app', 'bbs', 'web', 'www', 'job', 'docs', 'news', 'blog',
|
||||||
|
'data', 'help', 'live', 'mall', 'blogs', 'files', 'forum', 'store', 'mobile'}
|
||||||
|
|
||||||
|
# 日志配置
|
||||||
|
log_fmt = '<light-green>{time:HH:mm:ss,SSS}</light-green> ' \
|
||||||
|
'[<level>{level: <5}</level>] ' \
|
||||||
|
'<cyan>{process.name}</cyan>:<cyan>{thread.name: <10}</cyan> | ' \
|
||||||
|
'<blue>{module}</blue>.<blue>{function}</blue>:<blue>{line}</blue> - ' \
|
||||||
|
'<level>{message}</level>'
|
||||||
|
|
||||||
|
log_path = result_save_path.joinpath('oneforall.log')
|
||||||
|
|
||||||
|
logger.remove()
|
||||||
|
logger.level(name='TRACE', no=5, color='<cyan><bold>', icon='✏️')
|
||||||
|
logger.level(name='DEBUG', no=10, color='<blue><bold>', icon='🐞 ')
|
||||||
|
logger.level(name='INFOR', no=20, color='<green><bold>', icon='ℹ️')
|
||||||
|
logger.level(name='ALERT', no=30, color='<yellow><bold>', icon='⚠️')
|
||||||
|
logger.level(name='ERROR', no=40, color='<red><bold>', icon='❌️')
|
||||||
|
logger.level(name='FATAL', no=50, color='<RED><bold>', icon='☠️')
|
||||||
|
|
||||||
|
logger.add(sys.stdout, level='INFOR', format=log_fmt, enqueue=True)
|
||||||
|
logger.add(log_path, level='TRACE', format=log_fmt, enqueue=True, encoding='utf-8')
|
||||||
|
|
||||||
|
# 调试模式
|
||||||
|
# import urllib3
|
||||||
|
# urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||||
|
# request_proxy = [{'http': 'http://127.0.0.1:8080', 'https': 'https://127.0.0.1:8080'}]
|
||||||
|
# request_verify = False
|
||||||
|
# enable_all_module = False # 启用所有模块 默认启用
|
||||||
|
# enable_partial_module = [('modules.certificates', 'censys_api')] # 启用部分模块 必须禁用enable_all_module才能生效
|
||||||
+104008
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,163 @@
|
|||||||
|
test
|
||||||
|
test2
|
||||||
|
t
|
||||||
|
dev
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
s1
|
||||||
|
s2
|
||||||
|
s3
|
||||||
|
admin
|
||||||
|
adm
|
||||||
|
a
|
||||||
|
b
|
||||||
|
c
|
||||||
|
m
|
||||||
|
ht
|
||||||
|
adminht
|
||||||
|
webht
|
||||||
|
web
|
||||||
|
gm
|
||||||
|
sys
|
||||||
|
system
|
||||||
|
manage
|
||||||
|
manager
|
||||||
|
mgr
|
||||||
|
passport
|
||||||
|
bata
|
||||||
|
wei
|
||||||
|
weixin
|
||||||
|
wechat
|
||||||
|
wx
|
||||||
|
wiki
|
||||||
|
upload
|
||||||
|
ftp
|
||||||
|
pic
|
||||||
|
jira
|
||||||
|
zabbix
|
||||||
|
nagios
|
||||||
|
bug
|
||||||
|
bugzilla
|
||||||
|
sql
|
||||||
|
mysql
|
||||||
|
db
|
||||||
|
stmp
|
||||||
|
pop
|
||||||
|
imap
|
||||||
|
mail
|
||||||
|
zimbra
|
||||||
|
exchange
|
||||||
|
forum
|
||||||
|
bbs
|
||||||
|
list
|
||||||
|
count
|
||||||
|
counter
|
||||||
|
img
|
||||||
|
img01
|
||||||
|
img02
|
||||||
|
img03
|
||||||
|
img04
|
||||||
|
api
|
||||||
|
cache
|
||||||
|
js
|
||||||
|
css
|
||||||
|
app
|
||||||
|
apps
|
||||||
|
wap
|
||||||
|
sms
|
||||||
|
zip
|
||||||
|
monitor
|
||||||
|
proxy
|
||||||
|
update
|
||||||
|
upgrade
|
||||||
|
stat
|
||||||
|
stats
|
||||||
|
data
|
||||||
|
portal
|
||||||
|
blog
|
||||||
|
autodiscover
|
||||||
|
en
|
||||||
|
search
|
||||||
|
so
|
||||||
|
oa
|
||||||
|
database
|
||||||
|
home
|
||||||
|
sso
|
||||||
|
help
|
||||||
|
vip
|
||||||
|
s
|
||||||
|
w
|
||||||
|
down
|
||||||
|
download
|
||||||
|
downloads
|
||||||
|
dl
|
||||||
|
svn
|
||||||
|
git
|
||||||
|
log
|
||||||
|
staff
|
||||||
|
vpn
|
||||||
|
sslvpn
|
||||||
|
ssh
|
||||||
|
scanner
|
||||||
|
sandbox
|
||||||
|
ldap
|
||||||
|
lab
|
||||||
|
go
|
||||||
|
demo
|
||||||
|
console
|
||||||
|
cms
|
||||||
|
auth
|
||||||
|
crm
|
||||||
|
erp
|
||||||
|
res
|
||||||
|
static
|
||||||
|
old
|
||||||
|
new
|
||||||
|
beta
|
||||||
|
image
|
||||||
|
service
|
||||||
|
login
|
||||||
|
3g
|
||||||
|
docs
|
||||||
|
it
|
||||||
|
e
|
||||||
|
live
|
||||||
|
library
|
||||||
|
files
|
||||||
|
i
|
||||||
|
d
|
||||||
|
cp
|
||||||
|
connect
|
||||||
|
gateway
|
||||||
|
lib
|
||||||
|
preview
|
||||||
|
backup
|
||||||
|
share
|
||||||
|
status
|
||||||
|
assets
|
||||||
|
user
|
||||||
|
vote
|
||||||
|
bugs
|
||||||
|
cas
|
||||||
|
feedback
|
||||||
|
id
|
||||||
|
edm
|
||||||
|
survey
|
||||||
|
union
|
||||||
|
ceshi
|
||||||
|
dev1
|
||||||
|
updates
|
||||||
|
phpmyadmin
|
||||||
|
pma
|
||||||
|
edit
|
||||||
|
master
|
||||||
|
xml
|
||||||
|
control
|
||||||
|
profile
|
||||||
|
zhidao
|
||||||
|
tool
|
||||||
|
toolbox
|
||||||
|
boss
|
||||||
|
activity
|
||||||
|
www
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,102 @@
|
|||||||
|
[
|
||||||
|
"_afpovertcp._tcp.",
|
||||||
|
"_aix._tcp.",
|
||||||
|
"_autodiscover._tcp.",
|
||||||
|
"_caldav._tcp.",
|
||||||
|
"_certificates._tcp.",
|
||||||
|
"_client._smtp.",
|
||||||
|
"_cmp._tcp.",
|
||||||
|
"_crls._tcp.",
|
||||||
|
"_crl._tcp.",
|
||||||
|
"_finger._tcp.",
|
||||||
|
"_ftp._tcp.",
|
||||||
|
"_gc._tcp.",
|
||||||
|
"_h323be._tcp.",
|
||||||
|
"_h323be._udp.",
|
||||||
|
"_h323cs._tcp.",
|
||||||
|
"_h323cs._udp.",
|
||||||
|
"_h323ls._tcp.",
|
||||||
|
"_h323ls._udp.",
|
||||||
|
"_h323rs._tcp.",
|
||||||
|
"_hkps._tcp.",
|
||||||
|
"_hkp._tcp.",
|
||||||
|
"_http._tcp.",
|
||||||
|
"_iax.udp.",
|
||||||
|
"_imaps._tcp.",
|
||||||
|
"_imap._tcp.",
|
||||||
|
"_jabber-client._tcp.",
|
||||||
|
"_jabber-client._udp.",
|
||||||
|
"_jabber._tcp.",
|
||||||
|
"_jabber._udp.",
|
||||||
|
"_kerberos-adm._tcp.",
|
||||||
|
"_kerberos._tcp.",
|
||||||
|
"_kerberos._tcp.dc._msdcs.",
|
||||||
|
"_kerberos._udp.",
|
||||||
|
"_kpasswd._tcp.",
|
||||||
|
"_kpasswd._udp.",
|
||||||
|
"_ldap._tcp.",
|
||||||
|
"_ldap._tcp.dc._msdcs.",
|
||||||
|
"_ldap._tcp.gc._msdcs.",
|
||||||
|
"_ldap._tcp.pdc._msdcs.",
|
||||||
|
"_msdcs.",
|
||||||
|
"_mysqlsrv._tcp.",
|
||||||
|
"_nntp._tcp.",
|
||||||
|
"_ntp._udp.",
|
||||||
|
"_ocsp._tcp.",
|
||||||
|
"_pgpkeys._tcp.",
|
||||||
|
"_pgprevokations._tcp.",
|
||||||
|
"_PKIXREP._tcp.",
|
||||||
|
"_pop3s._tcp.",
|
||||||
|
"_pop3._tcp.",
|
||||||
|
"_sipfederationtls._tcp.",
|
||||||
|
"_sipinternal._tcp.",
|
||||||
|
"_sipinternaltls._tcp.",
|
||||||
|
"_sips._tcp.",
|
||||||
|
"_sip._tcp.",
|
||||||
|
"_sip._tls.",
|
||||||
|
"_sip._udp.",
|
||||||
|
"_smtp._tcp.",
|
||||||
|
"_ssh._tcp.",
|
||||||
|
"_stun._tcp.",
|
||||||
|
"_stun._udp.",
|
||||||
|
"_svcp._tcp.",
|
||||||
|
"_tcp.",
|
||||||
|
"_telnet._tcp.",
|
||||||
|
"_test._tcp.",
|
||||||
|
"_tls.",
|
||||||
|
"_udp.",
|
||||||
|
"_vlmcs._tcp.",
|
||||||
|
"_vlmcs._udp.",
|
||||||
|
"_whois._tcp.",
|
||||||
|
"_wpad._tcp.",
|
||||||
|
"_xmpp-client._tcp.",
|
||||||
|
"_xmpp-client._udp.",
|
||||||
|
"_xmpp-server._tcp.",
|
||||||
|
"_xmpp-server._udp.",
|
||||||
|
"_https._tcp.",
|
||||||
|
"_imap.tcp.",
|
||||||
|
"_kerberos.tcp.dc._msdcs.",
|
||||||
|
"_ldap._tcp.ForestDNSZones.",
|
||||||
|
"_submission._tcp.",
|
||||||
|
"_caldavs._tcp.",
|
||||||
|
"_carddav._tcp.",
|
||||||
|
"_carddavs._tcp.",
|
||||||
|
"_x-puppet._tcp.",
|
||||||
|
"_x-puppet-ca._tcp.",
|
||||||
|
"_domainkey.",
|
||||||
|
"_pkixrep._tcp.",
|
||||||
|
"_cisco-phone-http.",
|
||||||
|
"_cisco-phone-tftp.",
|
||||||
|
"_cisco-uds._tcp.",
|
||||||
|
"_ciscowtp._tcp.",
|
||||||
|
"_collab-edge._tls.",
|
||||||
|
"_cuplogin._tcp.",
|
||||||
|
"_client._smtp._tcp.",
|
||||||
|
"_sftp._tcp.",
|
||||||
|
"_h323rs._udp.",
|
||||||
|
"_sql._tcp.",
|
||||||
|
"_sip._tcp.internal.",
|
||||||
|
"_snmp._udp.",
|
||||||
|
"_rdp._tcp.",
|
||||||
|
"_xmpp-server._udp."
|
||||||
|
]
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,53 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
|
||||||
|
import fire
|
||||||
|
from common import database
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
def export(table, db=None, valid=None, path=None, format='xlsx', output=False):
|
||||||
|
"""
|
||||||
|
将数据库导出为指定格式文件
|
||||||
|
|
||||||
|
:param str table: 要导出的表
|
||||||
|
:param str db: 要导出的数据库路径(默认为results/result.sqlite3)
|
||||||
|
:param int valid: 导出子域的有效性(默认None)
|
||||||
|
:param str format: 导出格式(默认xlsx)
|
||||||
|
:param str path: 导出路径(默认None)
|
||||||
|
:param bool output: 是否将导出数据输出到终端(默认False)
|
||||||
|
|
||||||
|
Note:
|
||||||
|
参数valid可选值1,0,None,分别表示导出有效,无效,全部子域
|
||||||
|
参数format可选格式:'csv','tsv','json','yaml','html','xls','xlsx','dbf','latex','ods'
|
||||||
|
参数path为None会根据format参数和域名名称在项目结果目录生成相应文件
|
||||||
|
|
||||||
|
Example:
|
||||||
|
python dbexport.py --db result.db --table name --format csv --output False
|
||||||
|
python dbexport.py --db result.db --table name --format csv --path= ./result.csv
|
||||||
|
"""
|
||||||
|
db_conn = database.connect_db(db)
|
||||||
|
if valid is None:
|
||||||
|
rows = database.get_data(db_conn, table)
|
||||||
|
elif isinstance(valid, int):
|
||||||
|
rows = database.get_subdomain(db_conn, table, valid)
|
||||||
|
else:
|
||||||
|
rows = database.get_data(db_conn, table) # 意外情况导出全部子域
|
||||||
|
if output:
|
||||||
|
print(rows.dataset)
|
||||||
|
if not path:
|
||||||
|
path = 'export.' + format
|
||||||
|
logger.log('INFOR', f'正在将数据库中{table}表导出到{path}')
|
||||||
|
try:
|
||||||
|
with open(path, 'w') as file:
|
||||||
|
file.write(rows.export(format))
|
||||||
|
logger.log('INFOR', '成功完成导出')
|
||||||
|
except TypeError:
|
||||||
|
with open(path, 'wb') as file:
|
||||||
|
file.write(rows.export(format))
|
||||||
|
logger.log('INFOR', '成功完成导出')
|
||||||
|
except Exception as e:
|
||||||
|
logger.log('ERROR', e)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
fire.Fire(export)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
example.com
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# coding=utf-8
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
import config
|
||||||
|
from common.query import Query
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class CensysAPI(Query):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Query.__init__(self)
|
||||||
|
self.domain = self.register(domain)
|
||||||
|
self.module = 'Certificate'
|
||||||
|
self.source = "CensysAPIQuery"
|
||||||
|
self.addr = 'https://www.censys.io/api/v1/search/certificates'
|
||||||
|
self.id = config.censys_api_id
|
||||||
|
self.secret = config.censys_api_secret
|
||||||
|
self.delay = 3.0 # Censys 接口查询速率限制 最快2.5秒查1次
|
||||||
|
|
||||||
|
def query(self):
|
||||||
|
"""
|
||||||
|
向接口查询子域并做子域匹配
|
||||||
|
"""
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
data = {
|
||||||
|
'query': 'parsed.names: owasp.org',
|
||||||
|
'page': 1,
|
||||||
|
'fields': ['parsed.subject_dn'],
|
||||||
|
'flatten': True}
|
||||||
|
|
||||||
|
resp = self.post(self.addr, json=data, auth=(self.id, self.secret))
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
resp_json = resp.json()
|
||||||
|
status = resp_json.get('status')
|
||||||
|
if status != 'ok':
|
||||||
|
logger.log('ALERT', status)
|
||||||
|
return
|
||||||
|
subdomains_find = self.match(self.domain, str(resp_json))
|
||||||
|
self.subdomains = self.subdomains.union(subdomains_find)
|
||||||
|
pages = resp_json.get('metadata').get('pages')
|
||||||
|
for page in range(2, pages+1):
|
||||||
|
time.sleep(self.delay)
|
||||||
|
data['page'] = page
|
||||||
|
resp = self.post(self.addr, json=data, auth=(self.id, self.secret))
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
subdomains_find = self.match(self.domain, str(resp.json()))
|
||||||
|
self.subdomains = self.subdomains.union(subdomains_find)
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
if not (self.id and self.secret):
|
||||||
|
logger.log('ERROR', f'{self.source}模块API配置错误')
|
||||||
|
logger.log('ALERT', f'不执行{self.source}模块')
|
||||||
|
return
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.query()
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
query = CensysAPI(domain)
|
||||||
|
query.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{query.source}模块耗时{query.elapsed}秒发现{query.domain}的子域{len(query.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{query.source}模块发现{query.domain}的子域 {query.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
import config
|
||||||
|
from common.query import Query
|
||||||
|
from common import utils
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class CertDBAPI(Query):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Query.__init__(self)
|
||||||
|
self.domain = domain
|
||||||
|
self.module = 'Certificate'
|
||||||
|
self.source = 'CertDBQuery'
|
||||||
|
self.addr = 'https://api.spyse.com/v1/subdomains'
|
||||||
|
self.token = config.certdb_api_token
|
||||||
|
|
||||||
|
def query(self):
|
||||||
|
"""
|
||||||
|
向接口查询子域并做子域匹配
|
||||||
|
"""
|
||||||
|
page_num = 1
|
||||||
|
while True:
|
||||||
|
time.sleep(self.delay)
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
params = {'domain': self.domain, 'api_token': self.token, 'page': page_num}
|
||||||
|
resp = self.get(self.addr, params)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
resp_json = resp.json()
|
||||||
|
subdomains_find = utils.match_subdomain(self.domain, str(resp_json))
|
||||||
|
self.subdomains = self.subdomains.union(subdomains_find) # 合并搜索子域名搜索结果
|
||||||
|
page_num += 1
|
||||||
|
if resp_json.get('count') < 30: # 默认每次查询最多返回30条 当前条数小于30条说明已经查完
|
||||||
|
break
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
if not self.token:
|
||||||
|
logger.log('ERROR', f'{self.source}模块API配置错误')
|
||||||
|
logger.log('ALERT', f'不执行{self.source}模块')
|
||||||
|
return
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.query()
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
query = CertDBAPI(domain)
|
||||||
|
query.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{query.source}模块耗时{query.elapsed}秒发现{query.domain}的子域{len(query.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{query.source}模块发现{query.domain}的子域 {query.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
from common.query import Query
|
||||||
|
from common import utils
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class CertSpotter(Query):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Query.__init__(self)
|
||||||
|
self.domain = domain
|
||||||
|
self.module = 'Certificate'
|
||||||
|
self.source = 'CertSpotterQuery'
|
||||||
|
self.addr = 'https://api.certspotter.com/v1/issuances'
|
||||||
|
|
||||||
|
def query(self):
|
||||||
|
"""
|
||||||
|
向接口查询子域并做子域匹配
|
||||||
|
"""
|
||||||
|
time.sleep(self.delay)
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
params = {'domain': self.domain, 'include_subdomains': 'true', 'expand': 'dns_names'}
|
||||||
|
resp = self.get(self.addr, params)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
subdomains_find = utils.match_subdomain(self.domain, str(resp.json()))
|
||||||
|
self.subdomains = self.subdomains.union(subdomains_find) # 合并搜索子域名搜索结果
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.query()
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
query = CertSpotter(domain)
|
||||||
|
query.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{query.source}模块耗时{query.elapsed}秒发现{query.domain}的子域{len(query.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{query.source}模块发现{query.domain}的子域 {query.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
from common.query import Query
|
||||||
|
from common import utils
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class Crtsh(Query):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Query.__init__(self)
|
||||||
|
self.domain = self.register(domain)
|
||||||
|
self.module = 'Certificate'
|
||||||
|
self.source = 'CrtshQuery'
|
||||||
|
self.addr = 'https://crt.sh/'
|
||||||
|
|
||||||
|
def query(self):
|
||||||
|
"""
|
||||||
|
向接口查询子域并做子域匹配
|
||||||
|
"""
|
||||||
|
time.sleep(self.delay)
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
params = {'q': f'%.{self.domain}', 'output': 'json'}
|
||||||
|
resp = self.get(self.addr, params)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
subdomains_find = utils.match_subdomain(self.domain, str(resp.json()))
|
||||||
|
self.subdomains = self.subdomains.union(subdomains_find)
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.query()
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
query = Crtsh(domain)
|
||||||
|
query.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{query.source}模块耗时{query.elapsed}秒发现{query.domain}的子域{len(query.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{query.source}模块发现{query.domain}的子域 {query.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
from common.query import Query
|
||||||
|
from common import utils
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class Entrust(Query):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Query.__init__(self)
|
||||||
|
self.domain = self.register(domain)
|
||||||
|
self.module = 'Certificate'
|
||||||
|
self.source = 'EntrustQuery'
|
||||||
|
self.addr = 'https://ctsearch.entrust.com/api/v1/certificates'
|
||||||
|
|
||||||
|
def query(self):
|
||||||
|
"""
|
||||||
|
向接口查询子域并做子域匹配
|
||||||
|
"""
|
||||||
|
time.sleep(self.delay)
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
params = {'fields': 'subjectDN', 'domain': self.domain, 'includeExpired': 'true'}
|
||||||
|
resp = self.get(self.addr, params)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
subdomains_find = utils.match_subdomain(self.domain, str(resp.json()))
|
||||||
|
self.subdomains = self.subdomains.union(subdomains_find)
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.query()
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
query = Entrust(domain)
|
||||||
|
query.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{query.source}模块耗时{query.elapsed}秒发现{query.domain}的子域{len(query.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{query.source}模块发现{query.domain}的子域 {query.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
from common.query import Query
|
||||||
|
from common import utils
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class Google(Query):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Query.__init__(self)
|
||||||
|
self.domain = self.register(domain)
|
||||||
|
self.module = 'Certificate'
|
||||||
|
self.source = 'GoogleQuery'
|
||||||
|
self.addr = 'https://transparencyreport.google.com/transparencyreport/api/v3/httpsreport/ct/certsearch'
|
||||||
|
|
||||||
|
def query(self):
|
||||||
|
"""
|
||||||
|
向接口查询子域并做子域匹配
|
||||||
|
"""
|
||||||
|
time.sleep(self.delay)
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
params = {'include_expired': 'true', 'include_subdomains': 'true', 'domain': self.domain}
|
||||||
|
resp = self.get(self.addr, params)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
subdomains_find = utils.match_subdomain(self.domain, resp.text)
|
||||||
|
self.subdomains = self.subdomains.union(subdomains_find) # 合并搜索子域名搜索结果
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.query()
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
query = Google(domain)
|
||||||
|
query.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{query.source}模块耗时{query.elapsed}秒发现{query.domain}的子域{len(query.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{query.source}模块发现{query.domain}的子域 {query.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
查询域名的NS记录(域名服务器记录,记录该域名由哪台域名服务器解析),
|
||||||
|
检查查出的域名服务器是否开启DNS域传送,如果开启且没做访问控制和身份验证便加以利用获取域名的所有记录
|
||||||
|
|
||||||
|
DNS域传送(DNS zone transfer)指的是一台备用域名服务器使用来自主域名服务器的数据刷新自己的域数据库,
|
||||||
|
目的是为了做冗余备份,防止主域名服务器出现故障时 dns 解析不可用。
|
||||||
|
当主服务器开启DNS域传送同时又对来请求的备用服务器未作访问控制和身份验证便可以利用此漏洞获取某个域的所有记录。
|
||||||
|
"""
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
import dns.resolver
|
||||||
|
import dns.zone
|
||||||
|
from config import logger
|
||||||
|
from common import utils, resolve
|
||||||
|
from common.module import Module
|
||||||
|
|
||||||
|
|
||||||
|
class CheckAXFR(Module):
|
||||||
|
"""
|
||||||
|
DNS域传送漏洞检查类
|
||||||
|
"""
|
||||||
|
def __init__(self, domain: str):
|
||||||
|
Module.__init__(self)
|
||||||
|
self.domain = self.register(domain)
|
||||||
|
self.module = 'Check'
|
||||||
|
self.source = 'AXFRCheck'
|
||||||
|
self.nsservers = []
|
||||||
|
self.results = []
|
||||||
|
|
||||||
|
def check(self):
|
||||||
|
"""
|
||||||
|
正则匹配响应头中的内容安全策略字段以发现子域名
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
resolver = resolve.dns_resolver()
|
||||||
|
try:
|
||||||
|
answers = resolver.query(self.domain, "NS")
|
||||||
|
except Exception as e:
|
||||||
|
logger.log('ERROR', e)
|
||||||
|
return
|
||||||
|
self.nsservers = [str(answer) for answer in answers]
|
||||||
|
if not len(self.nsservers):
|
||||||
|
logger.log('ALERT', f'没有找到{self.domain}的NS域名服务器记录')
|
||||||
|
return
|
||||||
|
for nsserver in self.nsservers:
|
||||||
|
logger.log('DEBUG', f'正在尝试对{self.domain}的域名服务器{nsserver}进行域传送')
|
||||||
|
try:
|
||||||
|
xfr = dns.query.xfr(nsserver, self.domain)
|
||||||
|
zone = dns.zone.from_xfr(xfr)
|
||||||
|
except Exception as e:
|
||||||
|
logger.log('DEBUG', str(e))
|
||||||
|
logger.log('INFOR', f'对{self.domain}的域名服务器{nsserver}进行域传送失败')
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
names = zone.nodes.keys()
|
||||||
|
for name in names:
|
||||||
|
subdomain = utils.match_subdomain(self.domain, str(name)+'.'+self.domain)
|
||||||
|
self.subdomains = self.subdomains.union(subdomain)
|
||||||
|
record = zone[name].to_text(name)
|
||||||
|
self.results.append(record)
|
||||||
|
if self.results:
|
||||||
|
logger.log('INFOR', f'发现{self.domain}在{nsserver}上的域传送记录')
|
||||||
|
logger.log('DEBUG', '\n'.join(self.results))
|
||||||
|
self.results = []
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}检查{self.domain}的域传送漏洞')
|
||||||
|
start = time.time()
|
||||||
|
self.check()
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}检查{self.domain}的域传送漏洞')
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
check = CheckAXFR(domain)
|
||||||
|
check.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{check.source}模块耗时{check.elapsed}秒发现子域{len(check.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{check.source}模块发现的子域 {check.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# do('ZoneTransfer.me')
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
检查crossdomain.xml文件收集子域名
|
||||||
|
"""
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
from config import logger
|
||||||
|
from common.utils import match_subdomain
|
||||||
|
from common.module import Module
|
||||||
|
|
||||||
|
|
||||||
|
class CheckCDX(Module):
|
||||||
|
"""
|
||||||
|
检查crossdomain.xml文件收集子域名
|
||||||
|
"""
|
||||||
|
def __init__(self, domain: str):
|
||||||
|
Module.__init__(self)
|
||||||
|
self.domain = self.register(domain)
|
||||||
|
self.module = 'Check'
|
||||||
|
self.source = "CrossDomainXml"
|
||||||
|
|
||||||
|
def check(self):
|
||||||
|
"""
|
||||||
|
检查crossdomain.xml收集子域名
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
url = f'http://{self.domain}/crossdomain.xml'
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
resp = self.get(url)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
self.subdomains = match_subdomain(self.domain, resp.text)
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}检查{self.domain}域的crossdomain.xml')
|
||||||
|
start = time.time()
|
||||||
|
self.check()
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}检查{self.domain}域的crossdomain.xml')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
:param domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
check = CheckCDX(domain)
|
||||||
|
check.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{check.source}模块耗时{check.elapsed}秒发现子域{len(check.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{check.source}模块发现的子域 {check.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
do('163.com')
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# coding=utf-8
|
||||||
|
|
||||||
|
"""
|
||||||
|
检查域名证书收集子域名
|
||||||
|
"""
|
||||||
|
import ssl
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
import socket
|
||||||
|
from config import logger
|
||||||
|
from common import utils
|
||||||
|
from common.module import Module
|
||||||
|
|
||||||
|
|
||||||
|
class CheckCert(Module):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Module.__init__(self)
|
||||||
|
self.domain = self.register(domain)
|
||||||
|
self.port = 443 # ssl port
|
||||||
|
self.module = 'Check'
|
||||||
|
self.source = 'CertInfo'
|
||||||
|
|
||||||
|
def check(self):
|
||||||
|
"""
|
||||||
|
获取域名证书并匹配证书中的子域名
|
||||||
|
"""
|
||||||
|
ctx = ssl.create_default_context()
|
||||||
|
sock = ctx.wrap_socket(socket.socket(), server_hostname=self.domain)
|
||||||
|
try:
|
||||||
|
sock.connect((self.domain, self.port))
|
||||||
|
cert_dict = sock.getpeercert()
|
||||||
|
except Exception as e:
|
||||||
|
logger.log('ERROR', e)
|
||||||
|
return
|
||||||
|
subdomains_find = utils.match_subdomain(self.domain, str(cert_dict))
|
||||||
|
self.subdomains = self.subdomains.union(subdomains_find)
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}检查{self.domain}域的证书中的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.check()
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}检查{self.domain}域的证书中的子域')
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
check = CheckCert(domain)
|
||||||
|
check.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{check.source}模块耗时{check.elapsed}秒发现子域{len(check.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{check.source}模块发现的子域 {check.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
检查内容安全策略收集子域名收集子域名
|
||||||
|
"""
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
from config import logger
|
||||||
|
from common import utils
|
||||||
|
from common.module import Module
|
||||||
|
|
||||||
|
|
||||||
|
class CheckCSP(Module):
|
||||||
|
"""
|
||||||
|
检查内容安全策略收集子域名
|
||||||
|
"""
|
||||||
|
def __init__(self, domain, header):
|
||||||
|
Module.__init__(self)
|
||||||
|
self.domain = self.register(domain)
|
||||||
|
self.module = 'Check'
|
||||||
|
self.source = 'Content-Security-Policy'
|
||||||
|
self.header = header
|
||||||
|
|
||||||
|
def check(self):
|
||||||
|
"""
|
||||||
|
正则匹配响应头中的内容安全策略字段以发现子域名
|
||||||
|
"""
|
||||||
|
if not self.header:
|
||||||
|
url = f'http://www.{self.domain}'
|
||||||
|
resp = self.get(url)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
self.header = resp.headers
|
||||||
|
csp = self.header.get('Content-Security-Policy')
|
||||||
|
if not csp:
|
||||||
|
logger.log('DEBUG', f'{self.domain}域的响应头不存在内容安全策略字段')
|
||||||
|
return
|
||||||
|
logger.log('DEBUG', f'{self.domain}域的响应头存在内容安全策略字段')
|
||||||
|
self.subdomains = utils.match_subdomain(self.domain, csp)
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}检查{self.domain}域响应头中的内容安全策略字段')
|
||||||
|
start = time.time()
|
||||||
|
self.check()
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}检查{self.domain}域响应头中的内容安全策略字段')
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue, header=None): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
:param dict or None header: 响应头
|
||||||
|
"""
|
||||||
|
check = CheckCSP(domain, header)
|
||||||
|
check.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{check.source}模块耗时{check.elapsed}秒发现子域{len(check.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{check.source}模块发现的子域 {check.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import requests
|
||||||
|
# resp = requests.get('https://content-security-policy.com/')
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
resp = requests.get('https://www.baidu.com/')
|
||||||
|
do('google-analytics.com', result_queue, resp.headers)
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
import cdx_toolkit
|
||||||
|
from common.crawl import Crawl
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class ArchiveCrawl(Crawl):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Crawl.__init__(self)
|
||||||
|
self.domain = domain
|
||||||
|
self.module = 'Crawl'
|
||||||
|
self.source = 'ArchiveCrawl'
|
||||||
|
|
||||||
|
def crawl(self, domain, limit):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:param domain:
|
||||||
|
:param limit:
|
||||||
|
"""
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
cdx = cdx_toolkit.CDXFetcher(source='ia')
|
||||||
|
url = f'*.{domain}/*'
|
||||||
|
size = cdx.get_size_estimate(url)
|
||||||
|
logger.log('DEBUG', f'{url} ArchiveCrawl size estimate {size}')
|
||||||
|
|
||||||
|
for resp in cdx.iter(url, limit=limit):
|
||||||
|
if resp.data.get('status') not in ['301', '302']:
|
||||||
|
url = resp.data.get('url')
|
||||||
|
subdomains_find = self.match(self.register(domain), url + resp.text)
|
||||||
|
self.subdomains = self.subdomains.union(subdomains_find) # 合并搜索子域名搜索结果
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.crawl(self.domain, 50)
|
||||||
|
|
||||||
|
# 爬取已发现的子域以发现新的子域
|
||||||
|
for subdomain in self.subdomains:
|
||||||
|
if subdomain != self.domain:
|
||||||
|
self.crawl(subdomain, 10)
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
crawl = ArchiveCrawl(domain)
|
||||||
|
crawl.run(result)
|
||||||
|
logger.log('INFOR', f'{crawl.source}模块耗时{crawl.elapsed}秒发现{crawl.domain}的子域{len(crawl.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{crawl.source}模块发现{crawl.domain}的子域 {crawl.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
from tqdm import tqdm
|
||||||
|
import cdx_toolkit
|
||||||
|
from common.crawl import Crawl
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class CommonCrawl(Crawl):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Crawl.__init__(self)
|
||||||
|
self.domain = domain
|
||||||
|
self.module = 'Crawl'
|
||||||
|
self.source = 'CommonCrawl'
|
||||||
|
|
||||||
|
def crawl(self, domain, limit):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:param domain:
|
||||||
|
:param limit:
|
||||||
|
"""
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
cdx = cdx_toolkit.CDXFetcher()
|
||||||
|
url = f'*.{domain}/*'
|
||||||
|
size = cdx.get_size_estimate(url)
|
||||||
|
print(url, 'CommonCrawl size estimate', size)
|
||||||
|
|
||||||
|
for resp in tqdm(cdx.iter(url, limit=limit), total=limit):
|
||||||
|
if resp.data.get('status') not in ['301', '302']:
|
||||||
|
subdomains_find = self.match(self.register(domain), resp.text)
|
||||||
|
self.subdomains = self.subdomains.union(subdomains_find) # 合并搜索子域名搜索结果
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.crawl(self.domain, 50)
|
||||||
|
|
||||||
|
# 爬取已发现的子域以发现新的子域
|
||||||
|
for subdomain in self.subdomains:
|
||||||
|
if subdomain != self.domain:
|
||||||
|
self.crawl(subdomain, 10)
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
crawl = CommonCrawl(domain)
|
||||||
|
crawl.run(result)
|
||||||
|
logger.log('INFOR', f'{crawl.source}模块耗时{crawl.elapsed}秒发现{crawl.domain}的子域{len(crawl.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{crawl.source}模块发现{crawl.domain}的子域 {crawl.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
import config
|
||||||
|
from common.query import Query
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class BinaryEdgeAPI(Query):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Query.__init__(self)
|
||||||
|
self.domain = self.register(domain)
|
||||||
|
self.module = 'Dataset'
|
||||||
|
self.source = 'BinaryEdgeAPIQuery'
|
||||||
|
self.addr = 'https://api.binaryedge.io/v2/query/domains/subdomain/'
|
||||||
|
self.api = config.binaryedge_api
|
||||||
|
|
||||||
|
def query(self):
|
||||||
|
"""
|
||||||
|
向接口查询子域并做子域匹配
|
||||||
|
"""
|
||||||
|
time.sleep(self.delay)
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.header.update({'X-Key': self.api})
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
url = self.addr + self.domain
|
||||||
|
resp = self.get(url)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
subdomains_find = self.match(self.domain, str(resp.json()))
|
||||||
|
self.subdomains = self.subdomains.union(subdomains_find)
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
if not self.api:
|
||||||
|
logger.log('ERROR', f'{self.source}模块API配置错误')
|
||||||
|
logger.log('ALERT', f'不执行{self.source}模块')
|
||||||
|
return
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.query()
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
query = BinaryEdgeAPI(domain)
|
||||||
|
query.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{query.source}模块耗时{query.elapsed}秒发现{query.domain}的子域{len(query.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{query.source}模块发现{query.domain}的子域 {query.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
from common.query import Query
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class BufferOver(Query):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Query.__init__(self)
|
||||||
|
self.domain = self.register(domain)
|
||||||
|
self.module = 'Dataset'
|
||||||
|
self.source = 'BufferOverQuery'
|
||||||
|
self.addr = 'https://dns.bufferover.run/dns'
|
||||||
|
|
||||||
|
def query(self):
|
||||||
|
"""
|
||||||
|
向接口查询子域并做子域匹配
|
||||||
|
"""
|
||||||
|
time.sleep(self.delay)
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
params = {'q': self.domain}
|
||||||
|
resp = self.get(self.addr, params)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
subdomains_find = self.match(self.domain, resp.text)
|
||||||
|
self.subdomains = self.subdomains.union(subdomains_find) # 合并搜索子域名搜索结果
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.query()
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
query = BufferOver(domain)
|
||||||
|
query.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{query.source}模块耗时{query.elapsed}秒发现{query.domain}的子域{len(query.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{query.source}模块发现{query.domain}的子域 {query.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
from common.query import Query
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class Chinaz(Query):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Query.__init__(self)
|
||||||
|
self.domain = self.register(domain)
|
||||||
|
self.module = 'Dataset'
|
||||||
|
self.source = 'ChinazQuery'
|
||||||
|
self.addr = 'https://alexa.chinaz.com/'
|
||||||
|
|
||||||
|
def query(self):
|
||||||
|
"""
|
||||||
|
向接口查询子域并做子域匹配
|
||||||
|
"""
|
||||||
|
time.sleep(self.delay)
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
self.addr = self.addr + self.domain
|
||||||
|
resp = self.get(self.addr)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
subdomains_find = self.match(self.domain, resp.text)
|
||||||
|
self.subdomains = self.subdomains.union(subdomains_find) # 合并搜索子域名搜索结果
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.query()
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
query = Chinaz(domain)
|
||||||
|
query.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{query.source}模块耗时{query.elapsed}秒发现{query.domain}的子域{len(query.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{query.source}模块发现{query.domain}的子域 {query.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
import config
|
||||||
|
from common.query import Query
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class ChinazAPI(Query):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Query.__init__(self)
|
||||||
|
self.domain = self.register(domain)
|
||||||
|
self.module = 'Dataset'
|
||||||
|
self.source = 'ChinazAPIQuery'
|
||||||
|
self.addr = 'https://apidata.chinaz.com/CallAPI/Alexa'
|
||||||
|
self.api = config.chinaz_api
|
||||||
|
|
||||||
|
def query(self):
|
||||||
|
"""
|
||||||
|
向接口查询子域并做子域匹配
|
||||||
|
"""
|
||||||
|
time.sleep(self.delay)
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
params = {'key': self.api, 'domainName': self.domain}
|
||||||
|
resp = self.get(self.addr, params)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
subdomains_find = self.match(self.domain, str(resp.json()))
|
||||||
|
self.subdomains = self.subdomains.union(subdomains_find) # 合并搜索子域名搜索结果
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
if not self.api:
|
||||||
|
logger.log('ERROR', f'{self.source}模块API配置错误')
|
||||||
|
logger.log('ALERT', f'不执行{self.source}模块')
|
||||||
|
return
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.query()
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
query = ChinazAPI(domain)
|
||||||
|
query.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{query.source}模块耗时{query.elapsed}秒发现{query.domain}的子域{len(query.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{query.source}模块发现{query.domain}的子域 {query.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
import config
|
||||||
|
from common.query import Query
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class CirclAPI(Query):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Query.__init__(self)
|
||||||
|
self.domain = self.register(domain)
|
||||||
|
self.module = 'Dataset'
|
||||||
|
self.source = 'CirclAPIQuery'
|
||||||
|
self.addr = 'https://www.circl.lu/pdns/query/'
|
||||||
|
self.user = config.circl_api_username
|
||||||
|
self.pwd = config.circl_api_password
|
||||||
|
|
||||||
|
def query(self):
|
||||||
|
"""
|
||||||
|
向接口查询子域并做子域匹配
|
||||||
|
"""
|
||||||
|
time.sleep(self.delay)
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
resp = self.get(self.addr + self.domain, auth=(self.user, self.pwd))
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
subdomains_find = self.match(self.domain, str(resp.json()))
|
||||||
|
self.subdomains = self.subdomains.union(subdomains_find) # 合并搜索子域名搜索结果
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
if self.user and self.pwd:
|
||||||
|
start = time.time()
|
||||||
|
self.query()
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
query = CirclAPI(domain)
|
||||||
|
query.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{query.source}模块耗时{query.elapsed}秒发现{query.domain}的子域{len(query.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{query.source}模块发现{query.domain}的子域 {query.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import random
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
from common.query import Query
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class DNSdb(Query):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Query.__init__(self)
|
||||||
|
self.domain = self.register(domain)
|
||||||
|
self.module = 'Dataset'
|
||||||
|
self.source = 'DNSdbQuery'
|
||||||
|
self.addr = 'https://www.dnsdb.org/'
|
||||||
|
|
||||||
|
def query(self):
|
||||||
|
"""
|
||||||
|
向接口查询子域并做子域匹配
|
||||||
|
"""
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.header.update({'Referer': self.addr})
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
url = self.addr + self.domain + '/'
|
||||||
|
resp = self.get(url)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
if resp.status_code == 200:
|
||||||
|
if 'index' in resp.text:
|
||||||
|
soup = BeautifulSoup(resp.text, features='lxml')
|
||||||
|
index_urls = set(map(lambda x: self.addr + self.domain + x.text, soup.find_all('a')))
|
||||||
|
for url in index_urls:
|
||||||
|
self.delay = random.randint(2, 5) # 休眠绕过CloudFlare的DDoS保护
|
||||||
|
time.sleep(self.delay)
|
||||||
|
resp = self.get(url)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
subdomains_find = self.match(self.domain, resp.text)
|
||||||
|
self.subdomains = self.subdomains.union(subdomains_find) # 合并搜索子域名搜索结果
|
||||||
|
else:
|
||||||
|
subdomains_find = self.match(self.domain, resp.text)
|
||||||
|
self.subdomains = self.subdomains.union(subdomains_find) # 合并搜索子域名搜索结果
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.query()
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
query = DNSdb(domain)
|
||||||
|
query.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{query.source}模块耗时{query.elapsed}秒发现{query.domain}的子域{len(query.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{query.source}模块发现{query.domain}的子域 {query.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
import config
|
||||||
|
from common.query import Query
|
||||||
|
from common import utils
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class DNSdbAPI(Query):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Query.__init__(self)
|
||||||
|
self.domain = self.register(domain)
|
||||||
|
self.module = 'Dataset'
|
||||||
|
self.source = 'DNSdbAPIQuery'
|
||||||
|
self.addr = 'https://api.dnsdb.info/lookup/rrset/name/'
|
||||||
|
self.api = config.dnsdb_api_key
|
||||||
|
|
||||||
|
def query(self):
|
||||||
|
"""
|
||||||
|
向接口查询子域并做子域匹配
|
||||||
|
"""
|
||||||
|
time.sleep(self.delay)
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.header.update({'X-API-Key': self.api})
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
url = f'{self.addr}*.{self.domain}'
|
||||||
|
resp = self.get(url)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
if resp.status_code == 200:
|
||||||
|
subdomains_find = utils.match_subdomain(self.domain, resp.text)
|
||||||
|
self.subdomains = self.subdomains.union(subdomains_find) # 合并搜索子域名搜索结果
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
if not self.api:
|
||||||
|
logger.log('ERROR', f'{self.source}模块API配置错误')
|
||||||
|
logger.log('ALERT', f'不执行{self.source}模块')
|
||||||
|
return
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.query()
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
query = DNSdbAPI(domain)
|
||||||
|
query.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{query.source}模块耗时{query.elapsed}秒发现{query.domain}的子域{len(query.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{query.source}模块发现{query.domain}的子域 {query.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
from common.query import Query
|
||||||
|
from common import utils
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class DNSdumpster(Query):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
def __init__(self, domain):
|
||||||
|
Query.__init__(self)
|
||||||
|
self.domain = self.register(domain)
|
||||||
|
self.module = 'Dataset'
|
||||||
|
self.source = "DNSdumpsterQuery"
|
||||||
|
self.addr = 'https://dnsdumpster.com/'
|
||||||
|
|
||||||
|
def query(self):
|
||||||
|
"""
|
||||||
|
向接口查询子域并做子域匹配
|
||||||
|
"""
|
||||||
|
time.sleep(self.delay)
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.header.update({'Referer': 'https://dnsdumpster.com'})
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
resp = self.get(self.addr)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
self.cookie = resp.cookies
|
||||||
|
data = {'csrfmiddlewaretoken': self.cookie.get('csrftoken'), 'targetip': self.domain}
|
||||||
|
resp = self.post(self.addr, data)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
subdomains_find = utils.match_subdomain(self.domain, resp.text)
|
||||||
|
if subdomains_find:
|
||||||
|
self.subdomains = self.subdomains.union(subdomains_find) # 合并搜索子域名搜索结果
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.query()
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
query = DNSdumpster(domain)
|
||||||
|
query.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{query.source}模块耗时{query.elapsed}秒发现{query.domain}的子域{len(query.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{query.source}模块发现{query.domain}的子域 {query.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
from common.query import Query
|
||||||
|
from common import utils
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class HackerTarget(Query):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Query.__init__(self)
|
||||||
|
self.domain = self.register(domain)
|
||||||
|
self.module = 'Dataset'
|
||||||
|
self.source = "HackerTargetQuery"
|
||||||
|
self.addr = 'https://api.hackertarget.com/hostsearch/'
|
||||||
|
|
||||||
|
def query(self):
|
||||||
|
"""
|
||||||
|
向接口查询子域并做子域匹配
|
||||||
|
"""
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
params = {'q': self.domain}
|
||||||
|
resp = self.get(self.addr, params)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
if resp.status_code == 200:
|
||||||
|
subdomains_find = utils.match_subdomain(self.domain, resp.text)
|
||||||
|
if subdomains_find:
|
||||||
|
self.subdomains = self.subdomains.union(subdomains_find) # 合并搜索子域名搜索结果
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.query()
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
query = HackerTarget(domain)
|
||||||
|
query.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{query.source}模块耗时{query.elapsed}秒发现{query.domain}的子域{len(query.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{query.source}模块发现{query.domain}的子域 {query.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
import config
|
||||||
|
from common.query import Query
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class IPv4InfoAPI(Query):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Query.__init__(self)
|
||||||
|
self.domain = self.register(domain)
|
||||||
|
self.module = 'Dataset'
|
||||||
|
self.source = 'IPv4InfoAPIQuery'
|
||||||
|
self.addr = ' http://ipv4info.com/api_v1/'
|
||||||
|
self.api = config.ipv4info_api_key
|
||||||
|
|
||||||
|
def query(self):
|
||||||
|
"""
|
||||||
|
向接口查询子域并做子域匹配
|
||||||
|
"""
|
||||||
|
page = 0
|
||||||
|
while True:
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
params = {'type': 'SUBDOMAINS', 'key': self.api, 'value': self.domain, 'page': page}
|
||||||
|
resp = self.get(self.addr, params)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
if resp.status_code != 200:
|
||||||
|
break # 请求不正常通常网络是有问题,不再继续请求下去
|
||||||
|
resp_json = resp.json()
|
||||||
|
subdomains_find = self.match(self.domain, str(resp_json))
|
||||||
|
if not subdomains_find:
|
||||||
|
break
|
||||||
|
self.subdomains = self.subdomains.union(subdomains_find) # 合并搜索子域名搜索结果
|
||||||
|
subdomains = resp_json.get('Subdomains') # 不直接使用subdomains是因为可能里面会出现不符合标准的子域名
|
||||||
|
if len(subdomains) < 300: # ipv4info子域查询接口每次最多返回300个 用来判断是否还有下一页
|
||||||
|
break
|
||||||
|
page += 1
|
||||||
|
if page >= 50: # ipv4info子域查询接口最多允许查询50页
|
||||||
|
break
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.query()
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
query = IPv4InfoAPI(domain)
|
||||||
|
query.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{query.source}模块耗时{query.elapsed}秒发现{query.domain}的子域{len(query.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{query.source}模块发现{query.domain}的子域 {query.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
import hashlib
|
||||||
|
from urllib import parse
|
||||||
|
from common.query import Query
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class NetCraft(Query):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Query.__init__(self)
|
||||||
|
self.domain = self.register(domain)
|
||||||
|
self.module = 'Dataset'
|
||||||
|
self.source = 'NetCraftQuery'
|
||||||
|
self.init = 'https://searchdns.netcraft.com/'
|
||||||
|
self.addr = 'https://searchdns.netcraft.com/?restriction=site+contains'
|
||||||
|
self.page_num = 1
|
||||||
|
self.per_page_num = 20
|
||||||
|
|
||||||
|
def bypass_verification(self):
|
||||||
|
"""
|
||||||
|
绕过NetCraft的JS验证
|
||||||
|
"""
|
||||||
|
self.header = self.get_header() # Netcraft会检查User-Agent
|
||||||
|
self.cookie = self.get(self.init).cookies
|
||||||
|
cookie_value = self.cookie['netcraft_js_verification_challenge']
|
||||||
|
verify_taken = hashlib.sha1(parse.unquote(cookie_value).encode('utf-8')).hexdigest()
|
||||||
|
self.cookie['netcraft_js_verification_response'] = verify_taken
|
||||||
|
|
||||||
|
def query(self):
|
||||||
|
"""
|
||||||
|
向接口查询子域并做子域匹配
|
||||||
|
"""
|
||||||
|
self.bypass_verification()
|
||||||
|
last = ''
|
||||||
|
while True:
|
||||||
|
time.sleep(self.delay)
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
params = {'host': '*.' + self.domain, 'from': self.page_num}
|
||||||
|
resp = self.get(self.addr + last, params)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
subdomains_find = self.match(self.domain, resp.text)
|
||||||
|
if not subdomains_find: # 搜索没有发现子域名则停止搜索
|
||||||
|
break
|
||||||
|
self.subdomains = self.subdomains.union(subdomains_find) # 合并搜索子域名搜索结果
|
||||||
|
if 'Next page' not in resp.text: # 搜索页面没有出现下一页时停止搜索
|
||||||
|
break
|
||||||
|
last = re.search(r'&last=.*' + self.domain, resp.text).group(0)
|
||||||
|
self.page_num += self.per_page_num
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.query()
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
query = NetCraft(domain)
|
||||||
|
query.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{query.source}模块耗时{query.elapsed}秒发现{query.domain}的子域{len(query.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{query.source}模块发现{query.domain}的子域 {query.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
import random
|
||||||
|
from common.query import Query
|
||||||
|
from common import utils
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class PTRArchive(Query):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Query.__init__(self)
|
||||||
|
self.domain = self.register(domain)
|
||||||
|
self.module = 'Dataset'
|
||||||
|
self.source = "PTRArchiveQuery"
|
||||||
|
self.addr = 'http://ptrarchive.com/tools/search3.htm'
|
||||||
|
|
||||||
|
def query(self):
|
||||||
|
"""
|
||||||
|
向接口查询子域并做子域匹配
|
||||||
|
"""
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
self.cookie = {'pa_id': str(random.randint(0, 1000000000))} # 绕过主页前端JS验证
|
||||||
|
params = {'label': self.domain, 'date': 'ALL'}
|
||||||
|
resp = self.get(self.addr, params)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
if resp.status_code == 200:
|
||||||
|
subdomains_find = utils.match_subdomain(self.domain, resp.text)
|
||||||
|
if subdomains_find:
|
||||||
|
self.subdomains = self.subdomains.union(subdomains_find) # 合并搜索子域名搜索结果
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.query()
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
query = PTRArchive(domain)
|
||||||
|
query.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{query.source}模块耗时{query.elapsed}秒发现{query.domain}的子域{len(query.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{query.source}模块发现{query.domain}的子域 {query.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
from common.query import Query
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class Riddler(Query):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Query.__init__(self)
|
||||||
|
self.domain = self.register(domain)
|
||||||
|
self.module = 'Dataset'
|
||||||
|
self.source = 'RiddlerQuery'
|
||||||
|
self.addr = 'https://riddler.io/search'
|
||||||
|
|
||||||
|
def query(self):
|
||||||
|
"""
|
||||||
|
向接口查询子域并做子域匹配
|
||||||
|
"""
|
||||||
|
time.sleep(self.delay)
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
params = {'q': 'pld:' + self.domain}
|
||||||
|
resp = self.get(self.addr, params)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
subdomains_find = self.match(self.domain, resp.text)
|
||||||
|
self.subdomains = self.subdomains.union(subdomains_find) # 合并搜索子域名搜索结果
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.query()
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
query = Riddler(domain)
|
||||||
|
query.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{query.source}模块耗时{query.elapsed}秒发现{query.domain}的子域{len(query.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{query.source}模块发现{query.domain}的子域 {query.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
import json
|
||||||
|
from common.query import Query
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class Robtex(Query):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Query.__init__(self)
|
||||||
|
self.domain = self.register(domain)
|
||||||
|
self.module = 'Dataset'
|
||||||
|
self.source = "RobtexQuery"
|
||||||
|
self.addr = 'https://freeapi.robtex.com/pdns/'
|
||||||
|
|
||||||
|
def query(self):
|
||||||
|
"""
|
||||||
|
向接口查询子域并做子域匹配
|
||||||
|
"""
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
url = self.addr + 'forward/' + self.domain
|
||||||
|
resp = self.get(url)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
text_list = resp.text.splitlines()
|
||||||
|
text_json = list(map(lambda x: json.loads(x), text_list))
|
||||||
|
for record in text_json:
|
||||||
|
if record.get('rrtype') in ['A', 'AAAA']:
|
||||||
|
time.sleep(self.delay) # Robtex有查询频率限制
|
||||||
|
ip = record.get('rrdata')
|
||||||
|
url = self.addr + 'reverse/' + ip
|
||||||
|
resp = self.get(url)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
subdomains_find = self.match(self.domain, resp.text)
|
||||||
|
if subdomains_find:
|
||||||
|
self.subdomains = self.subdomains.union(subdomains_find) # 合并搜索子域名搜索结果
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.query()
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
query = Robtex(domain)
|
||||||
|
query.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{query.source}模块耗时{query.elapsed}秒发现{query.domain}的子域{len(query.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{query.source}模块发现{query.domain}的子域 {query.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
import config
|
||||||
|
from common.query import Query
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class SecurityTrailsAPI(Query):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Query.__init__(self)
|
||||||
|
self.domain = self.register(domain)
|
||||||
|
self.module = 'Dataset'
|
||||||
|
self.source = 'SecurityTrailsQuery'
|
||||||
|
self.addr = 'https://api.securitytrails.com/v1/domain/'
|
||||||
|
self.api = config.securitytrails_api
|
||||||
|
self.delay = 2 # SecurityTrails查询时延至少2秒
|
||||||
|
|
||||||
|
def query(self):
|
||||||
|
"""
|
||||||
|
向接口查询子域并做子域匹配
|
||||||
|
"""
|
||||||
|
time.sleep(self.delay)
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
params = {'apikey': self.api}
|
||||||
|
url = f'{self.addr}{self.domain}/subdomains'
|
||||||
|
resp = self.get(url, params)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
subdomains_prefix = resp.json()['subdomains']
|
||||||
|
subdomains_find = [f'{prefix}.{self.domain}' for prefix in subdomains_prefix]
|
||||||
|
if subdomains_find:
|
||||||
|
self.subdomains = self.subdomains.union(subdomains_find) # 合并搜索子域名搜索结果
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
if not self.api:
|
||||||
|
logger.log('ERROR', f'{self.source}模块API配置错误')
|
||||||
|
logger.log('ALERT', f'不执行{self.source}模块')
|
||||||
|
return
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.query()
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
query = SecurityTrailsAPI(domain)
|
||||||
|
query.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{query.source}模块耗时{query.elapsed}秒发现{query.domain}的子域{len(query.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{query.source}模块发现{query.domain}的子域 {query.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
from common.query import Query
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class SiteDossier(Query):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Query.__init__(self)
|
||||||
|
self.domain = self.register(domain)
|
||||||
|
self.module = 'Dataset'
|
||||||
|
self.source = 'SiteDossierQuery'
|
||||||
|
self.addr = 'http://www.sitedossier.com/parentdomain/'
|
||||||
|
self.delay = 2
|
||||||
|
self.page_num = 1
|
||||||
|
self.per_page_num = 100
|
||||||
|
|
||||||
|
def query(self):
|
||||||
|
"""
|
||||||
|
向接口查询子域并做子域匹配
|
||||||
|
"""
|
||||||
|
while True:
|
||||||
|
time.sleep(self.delay)
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
url = f'{self.addr}{self.domain}/{self.page_num}'
|
||||||
|
resp = self.get(url)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
subdomains_find = self.match(self.domain, resp.text)
|
||||||
|
if not subdomains_find: # 搜索没有发现子域名则停止搜索
|
||||||
|
break
|
||||||
|
self.subdomains = self.subdomains.union(subdomains_find) # 合并搜索子域名搜索结果
|
||||||
|
if 'Show next 100 items' not in resp.text: # 搜索页面没有出现下一页时停止搜索
|
||||||
|
break
|
||||||
|
self.page_num += self.per_page_num
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.query()
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
query = SiteDossier(domain)
|
||||||
|
query.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{query.source}模块耗时{query.elapsed}秒发现{query.domain}的子域{len(query.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{query.source}模块发现{query.domain}的子域 {query.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# coding=utf-8
|
||||||
|
|
||||||
|
"""
|
||||||
|
通过枚举域名常见的SRV记录并做查询来发现子域
|
||||||
|
"""
|
||||||
|
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
import json
|
||||||
|
import asyncio
|
||||||
|
import aiodns
|
||||||
|
from common.module import Module
|
||||||
|
from common import utils
|
||||||
|
from config import logger, data_storage_path, resolver_nameservers
|
||||||
|
|
||||||
|
|
||||||
|
class BruteSRV(Module):
|
||||||
|
def __init__(self, domain: str):
|
||||||
|
Module.__init__(self)
|
||||||
|
self.domain = self.register(domain)
|
||||||
|
self.module = 'dnsquery'
|
||||||
|
self.source = "BruteSRV"
|
||||||
|
self.loop = asyncio.new_event_loop()
|
||||||
|
self.nameservers = resolver_nameservers
|
||||||
|
self.resolver = aiodns.DNSResolver(self.nameservers, self.loop)
|
||||||
|
|
||||||
|
async def query(self, name):
|
||||||
|
"""
|
||||||
|
查询域名的SRV记录
|
||||||
|
:param str name: SRV记录
|
||||||
|
:return: 查询结果
|
||||||
|
"""
|
||||||
|
logger.log('TRACE', f'尝试查询{name}的SRV记录')
|
||||||
|
try:
|
||||||
|
answers = await self.resolver.query(name, 'SRV')
|
||||||
|
except Exception as e:
|
||||||
|
logger.log('TRACE', e)
|
||||||
|
logger.log('TRACE', f'查询{name}的SRV记录失败')
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
logger.log('TRACE', f'查询{name}的SRV记录成功')
|
||||||
|
return answers
|
||||||
|
|
||||||
|
def brute(self):
|
||||||
|
"""
|
||||||
|
枚举域名的SRV记录
|
||||||
|
"""
|
||||||
|
names_path = data_storage_path.joinpath('srv_names.json')
|
||||||
|
with open(names_path) as fp:
|
||||||
|
names_dict = json.load(fp)
|
||||||
|
query_map = map(lambda name: name + self.domain, names_dict)
|
||||||
|
|
||||||
|
tasks = []
|
||||||
|
for query in query_map:
|
||||||
|
tasks.append(self.query(query))
|
||||||
|
task_group = asyncio.gather(*tasks, loop=self.loop)
|
||||||
|
self.loop.run_until_complete(asyncio.gather(task_group))
|
||||||
|
self.loop.close()
|
||||||
|
results = task_group.result()
|
||||||
|
for result in results:
|
||||||
|
if result:
|
||||||
|
for answer in result:
|
||||||
|
subdomain = utils.match_subdomain(self.domain, answer.host)
|
||||||
|
if subdomain:
|
||||||
|
self.subdomains = self.subdomains.union(subdomain)
|
||||||
|
else:
|
||||||
|
logger.log('DEBUG', f'{answer.host}不是{self.domain}的子域')
|
||||||
|
if not len(self.subdomains):
|
||||||
|
logger.log('DEBUG', f'没有找到{self.domain}的SRV记录')
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'开始枚举{self.domain}域的SRV记录')
|
||||||
|
start = time.time()
|
||||||
|
self.brute()
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束枚举{self.domain}域的SRV记录')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
brute = BruteSRV(domain)
|
||||||
|
brute.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{brute.source}模块耗时{brute.elapsed}秒发现{brute.domain}的子域{len(brute.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{brute.source}模块发现{brute.domain}的子域 {brute.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
import config
|
||||||
|
from common.query import Query
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class RiskIQ(Query):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Query.__init__(self)
|
||||||
|
self.domain = self.register(domain)
|
||||||
|
self.module = 'Intelligence'
|
||||||
|
self.source = 'RiskIQQuery'
|
||||||
|
self.addr = 'https://api.passivetotal.org/v2/enrichment/subdomains'
|
||||||
|
self.username = config.riskiq_api_username
|
||||||
|
self.key = config.riskiq_api_key
|
||||||
|
|
||||||
|
def query(self):
|
||||||
|
"""
|
||||||
|
向接口查询子域并做子域匹配
|
||||||
|
"""
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
params = {'query': self.domain}
|
||||||
|
resp = self.get(url=self.addr, params=params, auth=(self.username, self.key))
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
resp_json = resp.json()
|
||||||
|
subdomains_find = resp_json.get('subdomains')
|
||||||
|
if subdomains_find:
|
||||||
|
self.subdomains = set(map(lambda x: x + '.' + self.domain, subdomains_find))
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.query()
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
return
|
||||||
|
query = RiskIQ(domain)
|
||||||
|
query.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{query.source}模块耗时{query.elapsed}秒发现{query.domain}的子域{len(query.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{query.source}模块发现{query.domain}的子域 {query.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
import config
|
||||||
|
from common.query import Query
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class ThreatBookAPI(Query):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Query.__init__(self)
|
||||||
|
self.domain = self.register(domain)
|
||||||
|
self.module = 'Intelligence'
|
||||||
|
self.source = 'ThreatBookAPIQuery'
|
||||||
|
self.addr = 'https://x.threatbook.cn/api/v1/domain/query'
|
||||||
|
self.key = config.threatbook_api_key
|
||||||
|
|
||||||
|
def query(self, domain):
|
||||||
|
"""
|
||||||
|
向接口查询子域并做子域匹配
|
||||||
|
"""
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
params = {'apikey': self.key, 'domain': domain, 'field': 'sub_domains'}
|
||||||
|
resp = self.post(self.addr, params)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
subdomain_find = self.match(self.domain, str(resp.json()))
|
||||||
|
self.subdomains = self.subdomains.union(subdomain_find)
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
if not self.key:
|
||||||
|
logger.log('ERROR', f'{self.source}模块API配置错误')
|
||||||
|
logger.log('ALERT', f'不执行{self.source}模块')
|
||||||
|
return
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.query(self.domain)
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
query = ThreatBookAPI(domain)
|
||||||
|
query.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{query.source}模块耗时{query.elapsed}秒发现{query.domain}的子域{len(query.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{query.source}模块发现{query.domain}的子域 {query.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
from common.query import Query
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class ThreatMiner(Query):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Query.__init__(self)
|
||||||
|
self.domain = self.register(domain)
|
||||||
|
self.module = 'Intelligence'
|
||||||
|
self.source = 'ThreatMinerQuery'
|
||||||
|
self.addr = 'https://www.threatminer.org/getData.php'
|
||||||
|
|
||||||
|
def query(self):
|
||||||
|
"""
|
||||||
|
向接口查询子域并做子域匹配
|
||||||
|
"""
|
||||||
|
time.sleep(self.delay)
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
params = {'e': 'subdomains_container', 'q': self.domain, 't': 0, 'rt': 10}
|
||||||
|
resp = self.get(self.addr, params)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
subdomains_find = self.match(self.domain, resp.text)
|
||||||
|
self.subdomains = self.subdomains.union(subdomains_find) # 合并搜索子域名搜索结果
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.query()
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
query = ThreatMiner(domain)
|
||||||
|
query.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{query.source}模块耗时{query.elapsed}秒发现{query.domain}的子域{len(query.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{query.source}模块发现{query.domain}的子域 {query.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
from common.query import Query
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
最多查询100条
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
class VirusTotal(Query):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Query.__init__(self)
|
||||||
|
self.source = 'VirusTotalQuery'
|
||||||
|
self.module = 'Intelligence'
|
||||||
|
self.addr = 'https://www.virustotal.com/ui/domains/{}/subdomains'
|
||||||
|
self.domain = self.register(domain)
|
||||||
|
|
||||||
|
def query(self, domain):
|
||||||
|
"""
|
||||||
|
向接口查询子域并做子域匹配
|
||||||
|
"""
|
||||||
|
next_cursor = ''
|
||||||
|
while True:
|
||||||
|
time.sleep(self.delay)
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.header.update({'Referer': 'https://www.virustotal.com/',
|
||||||
|
'TE': 'Trailers'})
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
params = {'limit': '40', 'cursor': next_cursor}
|
||||||
|
resp = self.get(url=self.addr.format(domain), params=params)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
resp_json = resp.json()
|
||||||
|
subdomain_find = set()
|
||||||
|
datas = resp_json.get('data')
|
||||||
|
|
||||||
|
if datas:
|
||||||
|
for data in datas:
|
||||||
|
subdomain = data.get('id')
|
||||||
|
if subdomain:
|
||||||
|
subdomain_find.add(subdomain)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
self.subdomains = self.subdomains.union(subdomain_find)
|
||||||
|
meta = resp_json.get('meta')
|
||||||
|
if meta:
|
||||||
|
next_cursor = meta.get('cursor')
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.query(self.domain)
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
query = VirusTotal(domain)
|
||||||
|
query.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{query.source}模块耗时{query.elapsed}秒发现{query.domain}的子域{len(query.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{query.source}模块发现{query.domain}的子域 {query.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
import config
|
||||||
|
from common.query import Query
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class VirusTotalAPI(Query):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Query.__init__(self)
|
||||||
|
self.domain = self.register(domain)
|
||||||
|
self.module = 'Intelligence'
|
||||||
|
self.source = 'VirusTotalAPIQuery'
|
||||||
|
self.addr = 'https://www.virustotal.com/vtapi/v2/domain/report'
|
||||||
|
self.key = config.virustotal_api_key
|
||||||
|
|
||||||
|
def query(self, domain):
|
||||||
|
"""
|
||||||
|
向接口查询子域并做子域匹配
|
||||||
|
"""
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
params = {'apikey': self.key, 'domain': domain}
|
||||||
|
resp = self.get(self.addr, params)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
resp_json = resp.json()
|
||||||
|
subdomain_find = set(resp_json.get('subdomains'))
|
||||||
|
self.subdomains = self.subdomains.union(subdomain_find)
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
if not self.key:
|
||||||
|
logger.log('ERROR', f'{self.source}模块API配置错误')
|
||||||
|
logger.log('ALERT', f'不执行{self.source}模块')
|
||||||
|
return
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.query(self.domain)
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块查询{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
return
|
||||||
|
query = VirusTotalAPI(domain)
|
||||||
|
query.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{query.source}模块耗时{query.elapsed}秒发现{query.domain}的子域{len(query.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{query.source}模块发现{query.domain}的子域 {query.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
from common.search import Search
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class Ask(Search):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Search.__init__(self)
|
||||||
|
self.domain = domain
|
||||||
|
self.module = 'Search'
|
||||||
|
self.source = 'AskSearch'
|
||||||
|
self.addr = 'https://www.search.ask.com/web'
|
||||||
|
self.limit_num = 200 # 限制搜索条数
|
||||||
|
self.per_page_num = 10 # 默认每页显示10页
|
||||||
|
|
||||||
|
def search(self, domain, filtered_subdomain='', full_search=False):
|
||||||
|
"""
|
||||||
|
发送搜索请求并做子域匹配
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param str filtered_subdomain: 过滤的子域
|
||||||
|
:param bool full_search: 全量搜索
|
||||||
|
"""
|
||||||
|
self.page_num = 1
|
||||||
|
while True:
|
||||||
|
time.sleep(self.delay)
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
query = 'site:' + domain + filtered_subdomain
|
||||||
|
params = {'q': query, 'page': self.page_num}
|
||||||
|
resp = self.get(self.addr, params)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
subdomain_find = self.match(domain, resp.text)
|
||||||
|
if not subdomain_find:
|
||||||
|
break
|
||||||
|
if not full_search:
|
||||||
|
if subdomain_find.issubset(self.subdomains): # 搜索中发现搜索出的结果有完全重复的结果就停止搜索
|
||||||
|
break
|
||||||
|
self.subdomains = self.subdomains.union(subdomain_find)
|
||||||
|
self.page_num += 1
|
||||||
|
if '>Next<' not in resp.text:
|
||||||
|
break
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块搜索{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.search(self.domain, full_search=True)
|
||||||
|
|
||||||
|
# 排除同一子域搜索结果过多的子域以发现新的子域
|
||||||
|
for statement in self.filter(self.domain, self.subdomains):
|
||||||
|
self.search(self.domain, filtered_subdomain=statement)
|
||||||
|
|
||||||
|
# 递归搜索下一层的子域
|
||||||
|
if self.recursive_search:
|
||||||
|
for layer_num in range(1, self.recursive_times): # 从1开始是之前已经做过1层子域搜索了,当前实际递归层数是layer+1
|
||||||
|
for subdomain in self.subdomains:
|
||||||
|
if subdomain.count('.') - self.domain.count('.') == layer_num: # 进行下一层子域搜索的限制条件
|
||||||
|
self.search(subdomain)
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块搜索{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
search = Ask(domain)
|
||||||
|
search.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{search.source}模块耗时{search.elapsed}秒发现{search.domain}的子域{len(search.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{search.source}模块发现{search.domain}的子域 {search.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
from common.search import Search
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class Baidu(Search):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Search.__init__(self)
|
||||||
|
self.module = 'Search'
|
||||||
|
self.source = 'BaiduSearch'
|
||||||
|
self.init = 'https://www.baidu.com/'
|
||||||
|
self.addr = 'https://www.baidu.com/s'
|
||||||
|
self.domain = domain
|
||||||
|
self.limit_num = 750 # 限制搜索条数
|
||||||
|
|
||||||
|
def redirect_match(self, domain, html):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:param domain:
|
||||||
|
:param html:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
bs = BeautifulSoup(html, features='lxml')
|
||||||
|
subdomains_all = set()
|
||||||
|
for find_res in bs.find_all('a', {'class': 'c-showurl'}): # 获取搜索结果中所有的跳转URL地址
|
||||||
|
url = find_res.get('href')
|
||||||
|
subdomain = self.match_location(domain, url)
|
||||||
|
subdomains_all = subdomains_all.union(subdomain)
|
||||||
|
return subdomains_all
|
||||||
|
|
||||||
|
def search(self, domain, filtered_subdomain='', full_search=False):
|
||||||
|
"""
|
||||||
|
发送搜索请求并做子域匹配
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param str filtered_subdomain: 过滤的子域
|
||||||
|
:param bool full_search: 全量搜索
|
||||||
|
"""
|
||||||
|
self.page_num = 0 # 二次搜索重新置0
|
||||||
|
while True:
|
||||||
|
time.sleep(self.delay)
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
query = 'site:' + domain + filtered_subdomain
|
||||||
|
params = {'wd': query, 'pn': self.page_num, 'rn': self.per_page_num}
|
||||||
|
resp = self.get(self.addr, params)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
if len(domain) > 12: # 解决百度搜索结果中域名过长会显示不全的问题
|
||||||
|
subdomains_find = self.redirect_match(domain, resp.text) # 获取百度跳转URL响应头的Location字段获取直链
|
||||||
|
else:
|
||||||
|
subdomains_find = self.match(domain, resp.text)
|
||||||
|
if not subdomains_find: # 搜索没有发现子域名则停止搜索
|
||||||
|
break
|
||||||
|
if not full_search:
|
||||||
|
if subdomains_find.issubset(self.subdomains): # 搜索中发现搜索出的结果有完全重复的结果就停止搜索
|
||||||
|
break
|
||||||
|
self.subdomains = self.subdomains.union(subdomains_find) # 合并搜索子域名搜索结果
|
||||||
|
self.page_num += self.per_page_num
|
||||||
|
if '&pn={next_pn}&'.format(next_pn=self.page_num) not in resp.text: # 搜索页面没有出现下一页时停止搜索
|
||||||
|
break
|
||||||
|
if self.page_num >= self.limit_num: # 搜索条数限制
|
||||||
|
break
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块搜索{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.search(self.domain, full_search=True)
|
||||||
|
|
||||||
|
# 排除同一子域搜索结果过多的子域以发现新的子域
|
||||||
|
for statement in self.filter(self.domain, self.subdomains):
|
||||||
|
self.search(self.domain, filtered_subdomain=statement)
|
||||||
|
|
||||||
|
# 递归搜索下一层的子域
|
||||||
|
if self.recursive_search:
|
||||||
|
for layer_num in range(1, self.recursive_times): # 从1开始是之前已经做过1层子域搜索了,当前实际递归层数是layer+1
|
||||||
|
for subdomain in self.subdomains:
|
||||||
|
if subdomain.count('.') - self.domain.count('.') == layer_num: # 进行下一层子域搜索的限制条件
|
||||||
|
self.search(subdomain)
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块搜索{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
search = Baidu(domain)
|
||||||
|
search.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{search.source}模块耗时{search.elapsed}秒发现{search.domain}的子域{len(search.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{search.source}模块发现{search.domain}的子域 {search.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
from common.search import Search
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class Bing(Search):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Search.__init__(self)
|
||||||
|
self.domain = domain
|
||||||
|
self.module = 'Search'
|
||||||
|
self.source = 'BingSearch'
|
||||||
|
self.init = 'https://www.bing.com/'
|
||||||
|
self.addr = 'https://www.bing.com/search'
|
||||||
|
self.limit_num = 1000 # 限制搜索条数
|
||||||
|
|
||||||
|
def search(self, domain, filtered_subdomain='', full_search=False):
|
||||||
|
"""
|
||||||
|
发送搜索请求并做子域匹配
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param str filtered_subdomain: 过滤的子域
|
||||||
|
:param bool full_search: 全量搜索
|
||||||
|
"""
|
||||||
|
self.page_num = 0 # 二次搜索重新置0
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
resp = self.get(self.init)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
self.cookie = resp.cookies # 获取cookie bing在搜索时需要带上cookie
|
||||||
|
while True:
|
||||||
|
time.sleep(self.delay)
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
query = 'site:' + domain + filtered_subdomain
|
||||||
|
params = {'q': query, 'first': self.page_num, 'count': self.per_page_num}
|
||||||
|
resp = self.get(self.addr, params)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
subdomains_find = self.match(domain, resp.text)
|
||||||
|
if not subdomains_find: # 搜索没有发现子域名则停止搜索
|
||||||
|
break
|
||||||
|
if not full_search:
|
||||||
|
if subdomains_find.issubset(self.subdomains): # 搜索中发现搜索出的结果有完全重复的结果就停止搜索
|
||||||
|
break
|
||||||
|
self.subdomains = self.subdomains.union(subdomains_find) # 合并搜索子域名搜索结果
|
||||||
|
if '<div class="sw_next>' not in resp.text: # 搜索页面没有出现下一页时停止搜索
|
||||||
|
break
|
||||||
|
self.page_num += self.per_page_num
|
||||||
|
if self.page_num >= self.limit_num: # 搜索条数限制
|
||||||
|
break
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块搜索{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.search(self.domain, full_search=True)
|
||||||
|
|
||||||
|
# 排除同一子域搜索结果过多的子域以发现新的子域
|
||||||
|
for statement in self.filter(self.domain, self.subdomains):
|
||||||
|
self.search(self.domain, filtered_subdomain=statement)
|
||||||
|
|
||||||
|
# 递归搜索下一层的子域
|
||||||
|
if self.recursive_search:
|
||||||
|
for layer_num in range(1, self.recursive_times): # 从1开始是之前已经做过1层子域搜索了,当前实际递归层数是layer+1
|
||||||
|
for subdomain in self.subdomains:
|
||||||
|
if subdomain.count('.') - self.domain.count('.') == layer_num: # 进行下一层子域搜索的限制条件
|
||||||
|
self.search(subdomain)
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块搜索{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
search = Bing(domain)
|
||||||
|
search.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{search.source}模块耗时{search.elapsed}秒发现{search.domain}的子域{len(search.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{search.source}模块发现{search.domain}的子域 {search.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
import config
|
||||||
|
from common.search import Search
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class BingAPI(Search):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Search.__init__(self)
|
||||||
|
self.domain = domain
|
||||||
|
self.module = 'Search'
|
||||||
|
self.source = 'BingCustomSearch'
|
||||||
|
self.addr = 'https://api.cognitive.microsoft.com/bingcustomsearch/v7.0/search'
|
||||||
|
self.id = config.bing_api_id
|
||||||
|
self.key = config.bing_api_key
|
||||||
|
self.limit_num = 1000 # 必应同一个搜索关键词限制搜索条数
|
||||||
|
self.delay = 1 # 必应自定义搜索限制时延1秒
|
||||||
|
|
||||||
|
def search(self, domain, filtered_subdomain='', full_search=False):
|
||||||
|
"""
|
||||||
|
发送搜索请求并做子域匹配
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param str filtered_subdomain: 过滤的子域
|
||||||
|
:param bool full_search: 全量搜索
|
||||||
|
"""
|
||||||
|
self.page_num = 0 # 二次搜索重新置0
|
||||||
|
while True:
|
||||||
|
time.sleep(self.delay)
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.header = {'Ocp-Apim-Subscription-Key': self.key}
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
query = 'site:' + domain + filtered_subdomain
|
||||||
|
params = {'q': query, 'customconfig': self.id, 'safesearch': 'Off',
|
||||||
|
'count': self.per_page_num, 'offset': self.page_num}
|
||||||
|
resp = self.get(self.addr, params)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
subdomains_find = self.match(domain, str(resp.json()))
|
||||||
|
if not subdomains_find: # 搜索没有发现子域名则停止搜索
|
||||||
|
break
|
||||||
|
if not full_search:
|
||||||
|
if subdomains_find.issubset(self.subdomains): # 搜索中发现搜索出的结果有完全重复的结果就停止搜索
|
||||||
|
break
|
||||||
|
self.subdomains = self.subdomains.union(subdomains_find) # 合并搜索子域名搜索结果
|
||||||
|
self.page_num += self.per_page_num
|
||||||
|
if self.page_num >= self.limit_num: # 搜索条数限制
|
||||||
|
break
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
if not (self.id and self.key):
|
||||||
|
logger.log('ERROR', f'{self.source}模块API配置错误')
|
||||||
|
logger.log('ALERT', f'不执行{self.source}模块')
|
||||||
|
return
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块搜索{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.search(self.domain, full_search=True)
|
||||||
|
|
||||||
|
# 排除同一子域搜索结果过多的子域以发现新的子域
|
||||||
|
for statement in self.filter(self.domain, self.subdomains):
|
||||||
|
self.search(self.domain, filtered_subdomain=statement)
|
||||||
|
|
||||||
|
# 递归搜索下一层的子域
|
||||||
|
if self.recursive_search:
|
||||||
|
for layer_num in range(1, self.recursive_times): # 从1开始是之前已经做过1层子域搜索了,当前实际递归层数是layer+1
|
||||||
|
for subdomain in self.subdomains:
|
||||||
|
if subdomain.count('.') - self.domain.count('.') == layer_num: # 进行下一层子域搜索的限制条件
|
||||||
|
self.search(subdomain)
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块搜索{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
search = BingAPI(domain)
|
||||||
|
search.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{search.source}模块耗时{search.elapsed}秒发现{search.domain}的子域{len(search.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{search.source}模块发现{search.domain}的子域 {search.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
from common.search import Search
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class DuckDuckGO(Search):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Search.__init__(self)
|
||||||
|
self.domain = domain
|
||||||
|
self.module = 'Search'
|
||||||
|
self.source = 'DuckDuckGoSearch'
|
||||||
|
self.addr = 'https://duckduckgo.com/html/'
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.delay = 2
|
||||||
|
|
||||||
|
def search(self, domain, filtered_subdomain='', full_search=False):
|
||||||
|
"""
|
||||||
|
发送搜索请求并做子域匹配
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param str filtered_subdomain: 过滤的子域
|
||||||
|
:param bool full_search: 全量搜索
|
||||||
|
"""
|
||||||
|
query = 'site:' + domain + filtered_subdomain
|
||||||
|
data = {'q': query, 'kl': 'us-en', 'v': 'l'}
|
||||||
|
while True:
|
||||||
|
time.sleep(self.delay)
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
resp = self.post(self.addr, data)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
subdomain_find = self.match(domain, resp.text)
|
||||||
|
if not subdomain_find:
|
||||||
|
break
|
||||||
|
if not full_search:
|
||||||
|
if subdomain_find.issubset(self.subdomains): # 搜索中发现搜索出的结果有完全重复的结果就停止搜索
|
||||||
|
break
|
||||||
|
self.subdomains = self.subdomains.union(subdomain_find)
|
||||||
|
try:
|
||||||
|
s = re.findall(r'name="s" value="(\d.*)"', resp.text)[-1]
|
||||||
|
dc = re.findall(r'name="dc" value="(\d.*)"', resp.text)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e)
|
||||||
|
break
|
||||||
|
data.update({'s': s, 'nextParams': '', 'o': 'json', 'dc': dc, 'api': '/d.js'})
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块搜索{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.search(self.domain, full_search=True)
|
||||||
|
|
||||||
|
# 排除同一子域搜索结果过多的子域以发现新的子域
|
||||||
|
for statement in self.filter(self.domain, self.subdomains):
|
||||||
|
self.search(self.domain, filtered_subdomain=statement)
|
||||||
|
|
||||||
|
# 递归搜索下一层的子域
|
||||||
|
if self.recursive_search:
|
||||||
|
for layer_num in range(1, self.recursive_times): # 从1开始是之前已经做过1层子域搜索了,当前实际递归层数是layer+1
|
||||||
|
for subdomain in self.subdomains:
|
||||||
|
if subdomain.count('.') - self.domain.count('.') == layer_num: # 进行下一层子域搜索的限制条件
|
||||||
|
self.search(subdomain)
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块搜索{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
# return # 暂时还有点问题
|
||||||
|
search = DuckDuckGO(domain)
|
||||||
|
search.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{search.source}模块耗时{search.elapsed}秒发现{search.domain}的子域{len(search.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{search.source}模块发现{search.domain}的子域 {search.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
import random
|
||||||
|
from common.search import Search
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class Exalead(Search):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Search.__init__(self)
|
||||||
|
self.domain = domain
|
||||||
|
self.module = 'Search'
|
||||||
|
self.source = "ExaleadSearch"
|
||||||
|
self.addr = "http://www.exalead.com/search/web/results/"
|
||||||
|
self.per_page_num = 30
|
||||||
|
|
||||||
|
def search(self, domain, filtered_subdomain='', full_search=False):
|
||||||
|
"""
|
||||||
|
发送搜索请求并做子域匹配
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param str filtered_subdomain: 过滤的子域
|
||||||
|
:param bool full_search: 全量搜索
|
||||||
|
"""
|
||||||
|
self.page_num = 0
|
||||||
|
while True:
|
||||||
|
self.delay = random.randint(1, 5)
|
||||||
|
time.sleep(self.delay)
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
query = 'site:' + domain + filtered_subdomain
|
||||||
|
params = {'q': query, 'elements_per_page': '30', "start_index": self.page_num}
|
||||||
|
resp = self.get(url=self.addr, params=params)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
subdomain_find = self.match(domain, resp.text)
|
||||||
|
if not subdomain_find:
|
||||||
|
break
|
||||||
|
if not full_search:
|
||||||
|
if subdomain_find.issubset(self.subdomains):
|
||||||
|
break
|
||||||
|
self.subdomains = self.subdomains.union(subdomain_find)
|
||||||
|
self.page_num += self.per_page_num
|
||||||
|
if self.page_num > 1999:
|
||||||
|
break
|
||||||
|
if 'title="Go to the next page"' not in resp.text:
|
||||||
|
break
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块搜索{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.search(self.domain, full_search=True)
|
||||||
|
|
||||||
|
# 排除同一子域搜索结果过多的子域以发现新的子域
|
||||||
|
for statement in self.filter(self.domain, self.subdomains):
|
||||||
|
statement = statement.replace('-site', 'and -site')
|
||||||
|
self.search(self.domain, filtered_subdomain=statement)
|
||||||
|
|
||||||
|
# 递归搜索下一层的子域
|
||||||
|
if self.recursive_search:
|
||||||
|
for layer_num in range(1, self.recursive_times): # 从1开始是之前已经做过1层子域搜索了,当前实际递归层数是layer+1
|
||||||
|
for subdomain in self.subdomains:
|
||||||
|
if subdomain.count('.') - self.domain.count('.') == layer_num: # 进行下一层子域搜索的限制条件
|
||||||
|
self.search(subdomain)
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块搜索{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
search = Exalead(domain)
|
||||||
|
search.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{search.source}模块耗时{search.elapsed}秒发现{search.domain}的子域{len(search.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{search.source}模块发现{search.domain}的子域 {search.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
import json
|
||||||
|
import base64
|
||||||
|
import config
|
||||||
|
from common.search import Search
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class FoFa(Search):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Search.__init__(self)
|
||||||
|
self.domain = domain
|
||||||
|
self.module = 'Search'
|
||||||
|
self.source = 'FoFaSearch'
|
||||||
|
self.addr = 'https://fofa.so/api/v1/search/all'
|
||||||
|
self.delay = 1
|
||||||
|
self.email = config.fofa_api_email
|
||||||
|
self.key = config.fofa_api_key
|
||||||
|
|
||||||
|
def search(self):
|
||||||
|
"""
|
||||||
|
发送搜索请求并做子域匹配
|
||||||
|
"""
|
||||||
|
self.page_num = 1
|
||||||
|
query_base64 = base64.b64encode(f'domain={self.domain}'.encode('utf-8'))
|
||||||
|
while True:
|
||||||
|
time.sleep(self.delay)
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
query = {'email': self.email, 'key': self.key, 'qbase64': query_base64, 'page': self.page_num}
|
||||||
|
resp = self.get(self.addr, query)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
subdomain_find = self.match(self.domain, resp.text)
|
||||||
|
self.subdomains = self.subdomains.union(subdomain_find)
|
||||||
|
self.page_num += 1
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块搜索{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.search()
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块搜索{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
search = FoFa(domain)
|
||||||
|
search.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{search.source}模块耗时{search.elapsed}秒发现{search.domain}的子域{len(search.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{search.source}模块发现{search.domain}的子域 {search.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
import random
|
||||||
|
from common.search import Search
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class Google(Search):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Search.__init__(self)
|
||||||
|
self.domain = domain
|
||||||
|
self.module = 'Search'
|
||||||
|
self.source = 'GoogleSearch'
|
||||||
|
self.init = 'https://www.google.com/'
|
||||||
|
self.addr = 'https://www.google.com/search'
|
||||||
|
|
||||||
|
def search(self, domain, filtered_subdomain='', full_search=False):
|
||||||
|
"""
|
||||||
|
发送搜索请求并做子域匹配
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param str filtered_subdomain: 过滤的子域
|
||||||
|
:param bool full_search: 全量搜索
|
||||||
|
"""
|
||||||
|
page_num = 1
|
||||||
|
per_page_num = 50
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.header.update({'User-Agent': 'Googlebot',
|
||||||
|
'Referer': 'https://www.google.com'})
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
resp = self.get(self.init)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
self.cookie = resp.cookies
|
||||||
|
while True:
|
||||||
|
self.delay = random.randint(1, 5)
|
||||||
|
time.sleep(self.delay)
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
word = 'site:' + domain + filtered_subdomain
|
||||||
|
payload = {'q': word, 'start': page_num, 'num': per_page_num,
|
||||||
|
'filter': '0', 'btnG': 'Search', 'gbv': '1', 'hl': 'en'}
|
||||||
|
resp = self.get(url=self.addr, params=payload)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
subdomain_find = self.match(domain, resp.text)
|
||||||
|
if not subdomain_find:
|
||||||
|
break
|
||||||
|
if not full_search:
|
||||||
|
if subdomain_find.issubset(self.subdomains):
|
||||||
|
break
|
||||||
|
self.subdomains = self.subdomains.union(subdomain_find)
|
||||||
|
page_num += per_page_num
|
||||||
|
if 'start='+str(page_num) not in resp.text:
|
||||||
|
break
|
||||||
|
if '302 Moved' in resp.text:
|
||||||
|
break
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块搜索{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.search(self.domain, full_search=True)
|
||||||
|
|
||||||
|
# 排除同一子域搜索结果过多的子域以发现新的子域
|
||||||
|
for statement in self.filter(self.domain, self.subdomains):
|
||||||
|
self.search(self.domain, filtered_subdomain=statement)
|
||||||
|
|
||||||
|
# 递归搜索下一层的子域
|
||||||
|
if self.recursive_search:
|
||||||
|
for layer_num in range(1, self.recursive_times): # 从1开始是之前已经做过1层子域搜索了,当前实际递归层数是layer+1
|
||||||
|
for subdomain in self.subdomains:
|
||||||
|
if subdomain.count('.') - self.domain.count('.') == layer_num: # 进行下一层子域搜索的限制条件
|
||||||
|
self.search(subdomain)
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块搜索{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
search = Google(domain)
|
||||||
|
search.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{search.source}模块耗时{search.elapsed}秒发现{search.domain}的子域{len(search.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{search.source}模块发现{search.domain}的子域 {search.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
import config
|
||||||
|
from common.search import Search
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class GoogleAPI(Search):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Search.__init__(self)
|
||||||
|
self.domain = domain
|
||||||
|
self.module = 'Search'
|
||||||
|
self.source = 'GoogleAPISearch'
|
||||||
|
self.addr = 'https://www.googleapis.com/customsearch/v1'
|
||||||
|
self.delay = 1
|
||||||
|
self.key = config.google_api_key
|
||||||
|
self.cx = config.google_api_cx
|
||||||
|
self.per_page_num = 10 # 每次只能请求10个结果
|
||||||
|
|
||||||
|
def search(self, domain, filtered_subdomain='', full_search=False):
|
||||||
|
"""
|
||||||
|
发送搜索请求并做子域匹配
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param str filtered_subdomain: 过滤的子域
|
||||||
|
:param bool full_search: 全量搜索
|
||||||
|
"""
|
||||||
|
self.page_num = 1
|
||||||
|
while True:
|
||||||
|
word = 'site:' + domain + filtered_subdomain
|
||||||
|
time.sleep(self.delay)
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
params = {'key': self.key, 'cx': self.cx, 'q': word, 'fields': 'items/link',
|
||||||
|
'start': self.page_num, 'num': self.per_page_num}
|
||||||
|
resp = self.get(self.addr, params)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
subdomain_find = self.match(domain, str(resp.json()))
|
||||||
|
if not subdomain_find:
|
||||||
|
break
|
||||||
|
if not full_search:
|
||||||
|
if subdomain_find.issubset(self.subdomains):
|
||||||
|
break
|
||||||
|
self.subdomains = self.subdomains.union(subdomain_find)
|
||||||
|
self.page_num += self.per_page_num
|
||||||
|
if self.page_num > 100: # 免费的API只能查询前100条结果
|
||||||
|
break
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
if not (self.cx and self.key):
|
||||||
|
logger.log('ERROR', f'{self.source}模块API配置错误')
|
||||||
|
logger.log('ALERT', f'不执行{self.source}模块')
|
||||||
|
return
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块搜索{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.search(self.domain, full_search=True)
|
||||||
|
|
||||||
|
# 排除同一子域搜索结果过多的子域以发现新的子域
|
||||||
|
for statement in self.filter(self.domain, self.subdomains):
|
||||||
|
self.search(self.domain, filtered_subdomain=statement)
|
||||||
|
|
||||||
|
# 递归搜索下一层的子域
|
||||||
|
if self.recursive_search:
|
||||||
|
for layer_num in range(1, self.recursive_times): # 从1开始是之前已经做过1层子域搜索了,当前实际递归层数是layer+1
|
||||||
|
for subdomain in self.subdomains:
|
||||||
|
if subdomain.count('.') - self.domain.count('.') == layer_num: # 进行下一层子域搜索的限制条件
|
||||||
|
self.search(subdomain)
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块搜索{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
search = GoogleAPI(domain)
|
||||||
|
search.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{search.source}模块耗时{search.elapsed}秒发现{search.domain}的子域{len(search.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{search.source}模块发现{search.domain}的子域 {search.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
import queue
|
||||||
|
import config
|
||||||
|
# from shodan import Shodan
|
||||||
|
from common.search import Search
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class ShodanAPI(Search):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Search.__init__(self)
|
||||||
|
self.domain = self.register(domain)
|
||||||
|
self.module = 'Search'
|
||||||
|
self.source = 'ShodanSearch'
|
||||||
|
self.addr = 'https://api.shodan.io/shodan/host/search'
|
||||||
|
self.key = config.shodan_api_key
|
||||||
|
|
||||||
|
def search(self):
|
||||||
|
"""
|
||||||
|
发送搜索请求并做子域匹配
|
||||||
|
"""
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
query = 'hostname:.' + self.domain
|
||||||
|
page = 1
|
||||||
|
while True:
|
||||||
|
params = {'key': self.key, 'page': page, 'query': query, 'minify': True, 'facets': {'hostnames'}}
|
||||||
|
resp = self.get(self.addr, params)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
subdomain_find = self.match(self.domain, resp.text)
|
||||||
|
if subdomain_find:
|
||||||
|
self.subdomains = self.subdomains.union(subdomain_find)
|
||||||
|
page += 1
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
if not self.key:
|
||||||
|
logger.log('ERROR', f'{self.source}模块API配置错误')
|
||||||
|
logger.log('ALERT', f'不执行{self.source}模块')
|
||||||
|
return
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块搜索{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.search()
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块搜索{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
search = ShodanAPI(domain)
|
||||||
|
search.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{search.source}模块耗时{search.elapsed}秒发现{search.domain}的子域{len(search.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{search.source}模块发现{search.domain}的子域 {search.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
results = queue.Queue()
|
||||||
|
do('qq.com', results)
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
from common.search import Search
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class So(Search):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Search.__init__(self)
|
||||||
|
self.domain = domain
|
||||||
|
self.module = 'Search'
|
||||||
|
self.source = 'SoSearch'
|
||||||
|
self.addr = 'http://www.so.com/s'
|
||||||
|
self.limit_num = 640 # 限制搜索条数
|
||||||
|
self.per_page_num = 10 # 默认每页显示10页
|
||||||
|
|
||||||
|
def search(self, domain, filtered_subdomain='', full_search=False):
|
||||||
|
"""
|
||||||
|
发送搜索请求并做子域匹配
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param str filtered_subdomain: 过滤的子域
|
||||||
|
:param bool full_search: 全量搜索
|
||||||
|
"""
|
||||||
|
page_num = 1
|
||||||
|
while True:
|
||||||
|
time.sleep(self.delay)
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
word = 'site:' + domain + filtered_subdomain
|
||||||
|
payload = {'q': word, 'pn': page_num}
|
||||||
|
resp = self.get(url=self.addr, params=payload)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
subdomain_find = self.match(domain, resp.text)
|
||||||
|
if not subdomain_find:
|
||||||
|
break
|
||||||
|
if not full_search:
|
||||||
|
if subdomain_find.issubset(self.subdomains): # 搜索中发现搜索出的结果有完全重复的结果就停止搜索
|
||||||
|
break
|
||||||
|
self.subdomains = self.subdomains.union(subdomain_find)
|
||||||
|
page_num += 1
|
||||||
|
if '<a id="snext"' not in resp.text: # 搜索页面没有出现下一页时停止搜索
|
||||||
|
break
|
||||||
|
if self.page_num * self.per_page_num >= self.limit_num: # 搜索条数限制
|
||||||
|
break
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块搜索{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.search(self.domain, full_search=True)
|
||||||
|
|
||||||
|
# 排除同一子域搜索结果过多的子域以发现新的子域
|
||||||
|
for statement in self.filter(self.domain, self.subdomains):
|
||||||
|
self.search(self.domain, filtered_subdomain=statement)
|
||||||
|
|
||||||
|
# 递归搜索下一层的子域
|
||||||
|
if self.recursive_search:
|
||||||
|
for layer_num in range(1, self.recursive_times): # 从1开始是之前已经做过1层子域搜索了,当前实际递归层数是layer+1
|
||||||
|
for subdomain in self.subdomains:
|
||||||
|
if subdomain.count('.') - self.domain.count('.') == layer_num: # 进行下一层子域搜索的限制条件
|
||||||
|
self.search(subdomain)
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块搜索{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
search = So(domain)
|
||||||
|
search.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{search.source}模块耗时{search.elapsed}秒发现{search.domain}的子域{len(search.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{search.source}模块发现{search.domain}的子域 {search.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
from common.search import Search
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class Sogou(Search):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Search.__init__(self)
|
||||||
|
self.domain = domain
|
||||||
|
self.module = 'Search'
|
||||||
|
self.source = 'SogouSearch'
|
||||||
|
self.addr = 'https://www.sogou.com/web'
|
||||||
|
self.limit_num = 1000 # 限制搜索条数
|
||||||
|
|
||||||
|
def search(self, domain, filtered_subdomain='', full_search=False):
|
||||||
|
"""
|
||||||
|
发送搜索请求并做子域匹配
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param str filtered_subdomain: 过滤的子域
|
||||||
|
:param bool full_search: 全量搜索
|
||||||
|
"""
|
||||||
|
self.page_num = 1
|
||||||
|
while True:
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
word = 'site:' + domain + filtered_subdomain
|
||||||
|
payload = {'query': word, 'page': self.page_num, "num": self.per_page_num}
|
||||||
|
resp = self.get(self.addr, payload)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
subdomain_find = self.match(domain, resp.text)
|
||||||
|
if not subdomain_find:
|
||||||
|
break
|
||||||
|
if not full_search:
|
||||||
|
if subdomain_find.issubset(self.subdomains): # 搜索中发现搜索出的结果有完全重复的结果就停止搜索
|
||||||
|
break
|
||||||
|
self.subdomains = self.subdomains.union(subdomain_find)
|
||||||
|
self.page_num += 1
|
||||||
|
if '<a id="sogou_next"' not in resp.text: # 搜索页面没有出现下一页时停止搜索
|
||||||
|
break
|
||||||
|
if self.page_num * self.per_page_num >= self.limit_num: # 搜索条数限制
|
||||||
|
break
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块搜索{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.search(self.domain, full_search=True)
|
||||||
|
|
||||||
|
# 排除同一子域搜索结果过多的子域以发现新的子域
|
||||||
|
for statement in self.filter(self.domain, self.subdomains):
|
||||||
|
self.search(self.domain, filtered_subdomain=statement)
|
||||||
|
|
||||||
|
# 递归搜索下一层的子域
|
||||||
|
if self.recursive_search:
|
||||||
|
for layer_num in range(1, self.recursive_times): # 从1开始是之前已经做过1层子域搜索了,当前实际递归层数是layer+1
|
||||||
|
for subdomain in self.subdomains:
|
||||||
|
if subdomain.count('.') - self.domain.count('.') == layer_num: # 进行下一层子域搜索的限制条件
|
||||||
|
self.search(subdomain)
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块搜索{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
search = Sogou(domain)
|
||||||
|
search.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{search.source}模块耗时{search.elapsed}秒发现{search.domain}的子域{len(search.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{search.source}模块发现{search.domain}的子域 {search.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
from common.search import Search
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class Yahoo(Search):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Search.__init__(self)
|
||||||
|
self.domain = domain
|
||||||
|
self.module = 'Search'
|
||||||
|
self.source = 'YahooSearch'
|
||||||
|
self.init = 'https://hk.search.yahoo.com/'
|
||||||
|
self.addr = 'https://hk.search.yahoo.com/search'
|
||||||
|
self.limit_num = 1000 # 限制搜索条数
|
||||||
|
self.delay = 5
|
||||||
|
self.per_page_num = 40
|
||||||
|
|
||||||
|
def search(self, domain, filtered_subdomain='', full_search=False):
|
||||||
|
"""
|
||||||
|
发送搜索请求并做子域匹配
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param str filtered_subdomain: 过滤的子域
|
||||||
|
:param bool full_search: 全量搜索
|
||||||
|
"""
|
||||||
|
self.page_num = 0
|
||||||
|
resp = self.get(self.init)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
self.cookie = resp.cookies # 获取cookie Yahoo在搜索时需要带上cookie
|
||||||
|
while True:
|
||||||
|
time.sleep(self.delay)
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
query = 'site:' + domain + filtered_subdomain
|
||||||
|
params = {'q': query, 'b': self.page_num, 'n': self.per_page_num}
|
||||||
|
resp = self.get(self.addr, params)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
subdomains_find = self.match(domain, resp.text)
|
||||||
|
if not subdomains_find: # 搜索没有发现子域名则停止搜索
|
||||||
|
break
|
||||||
|
if not full_search:
|
||||||
|
if subdomains_find.issubset(self.subdomains): # 搜索中发现搜索出的结果有完全重复的结果就停止搜索
|
||||||
|
break
|
||||||
|
self.subdomains = self.subdomains.union(subdomains_find) # 合并搜索子域名搜索结果
|
||||||
|
if '>Next</a>' not in resp.text: # 搜索页面没有出现下一页时停止搜索
|
||||||
|
break
|
||||||
|
self.page_num += self.per_page_num
|
||||||
|
if self.page_num >= self.limit_num: # 搜索条数限制
|
||||||
|
break
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块搜索{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.search(self.domain, full_search=True)
|
||||||
|
|
||||||
|
# 排除同一子域搜索结果过多的子域以发现新的子域
|
||||||
|
for statement in self.filter(self.domain, self.subdomains):
|
||||||
|
self.search(self.domain, filtered_subdomain=statement)
|
||||||
|
|
||||||
|
# 递归搜索下一层的子域
|
||||||
|
if self.recursive_search:
|
||||||
|
for layer_num in range(1, self.recursive_times): # 从1开始是之前已经做过1层子域搜索了,当前实际递归层数是layer+1
|
||||||
|
for subdomain in self.subdomains:
|
||||||
|
if subdomain.count('.') - self.domain.count('.') == layer_num: # 进行下一层子域搜索的限制条件
|
||||||
|
self.search(subdomain)
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块搜索{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
search = Yahoo(domain)
|
||||||
|
search.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{search.source}模块耗时{search.elapsed}秒发现{search.domain}的子域{len(search.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{search.source}模块发现{search.domain}的子域 {search.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
from common.search import Search
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class Yandex(Search):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Search.__init__(self)
|
||||||
|
self.domain = domain
|
||||||
|
self.module = 'Search'
|
||||||
|
self.source = 'YandexSearch'
|
||||||
|
self.init = 'https://yandex.com/'
|
||||||
|
self.addr = 'https://yandex.com/search'
|
||||||
|
self.limit_num = 1000 # 限制搜索条数
|
||||||
|
self.delay = 5
|
||||||
|
|
||||||
|
def search(self, domain, filtered_subdomain='', full_search=False):
|
||||||
|
"""
|
||||||
|
发送搜索请求并做子域匹配
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param str filtered_subdomain: 过滤的子域
|
||||||
|
:param bool full_search: 全量搜索
|
||||||
|
"""
|
||||||
|
self.page_num = 0 # 二次搜索重新置0
|
||||||
|
self.cookie = self.get(self.init).cookies # 获取cookie bing在搜索时需要带上cookie
|
||||||
|
while True:
|
||||||
|
time.sleep(self.delay)
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
query = 'site:' + domain + filtered_subdomain
|
||||||
|
params = {'text': query, 'p': self.page_num, 'numdoc': self.per_page_num}
|
||||||
|
resp = self.get(self.addr, params)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
subdomains_find = self.match(domain, resp.text)
|
||||||
|
if not subdomains_find: # 搜索没有发现子域名则停止搜索
|
||||||
|
break
|
||||||
|
if not full_search:
|
||||||
|
if subdomains_find.issubset(self.subdomains): # 搜索中发现搜索出的结果有完全重复的结果就停止搜索
|
||||||
|
break
|
||||||
|
self.subdomains = self.subdomains.union(subdomains_find) # 合并搜索子域名搜索结果
|
||||||
|
if '>next</a>' not in resp.text: # 搜索页面没有出现下一页时停止搜索
|
||||||
|
break
|
||||||
|
self.page_num += 1
|
||||||
|
if self.page_num >= self.limit_num: # 搜索条数限制
|
||||||
|
break
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块搜索{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.search(self.domain, full_search=True)
|
||||||
|
|
||||||
|
# 排除同一子域搜索结果过多的子域以发现新的子域
|
||||||
|
for statement in self.filter(self.domain, self.subdomains):
|
||||||
|
self.search(self.domain, filtered_subdomain=statement)
|
||||||
|
|
||||||
|
# 递归搜索下一层的子域
|
||||||
|
if self.recursive_search:
|
||||||
|
for layer_num in range(1, self.recursive_times): # 从1开始是之前已经做过1层子域搜索了,当前实际递归层数是layer+1
|
||||||
|
for subdomain in self.subdomains:
|
||||||
|
if subdomain.count('.') - self.domain.count('.') == layer_num: # 进行下一层子域搜索的限制条件
|
||||||
|
self.search(subdomain)
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块搜索{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
search = Yandex(domain)
|
||||||
|
search.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{search.source}模块耗时{search.elapsed}秒发现{search.domain}的子域{len(search.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{search.source}模块发现{search.domain}的子域 {search.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
import config
|
||||||
|
from common.search import Search
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class ZoomEyeAPI(Search):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Search.__init__(self)
|
||||||
|
self.domain = domain
|
||||||
|
self.module = 'Search'
|
||||||
|
self.source = 'ZoomEyeAPISearch'
|
||||||
|
self.addr = 'https://api.zoomeye.org/web/search'
|
||||||
|
self.delay = 2
|
||||||
|
self.user = config.zoomeye_api_username
|
||||||
|
self.pwd = config.zoomeye_api_password
|
||||||
|
|
||||||
|
def login(self):
|
||||||
|
"""
|
||||||
|
登陆获取查询taken
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
url = 'https://api.zoomeye.org/user/login'
|
||||||
|
data = {'username': self.user, 'password': self.pwd}
|
||||||
|
resp = self.post(url=url, json=data)
|
||||||
|
if not resp:
|
||||||
|
logger.log('FETAL', f'登录失败无法获取{self.source}的访问token')
|
||||||
|
return
|
||||||
|
resp_json = resp.json()
|
||||||
|
if resp.status_code == 200:
|
||||||
|
# print('登陆成功')
|
||||||
|
return resp_json.get('access_token')
|
||||||
|
else:
|
||||||
|
logger.log('ALERT', resp_json.get('message'))
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
def search(self):
|
||||||
|
"""
|
||||||
|
发送搜索请求并做子域匹配
|
||||||
|
"""
|
||||||
|
page_num = 1
|
||||||
|
access_token = self.login()
|
||||||
|
while True:
|
||||||
|
time.sleep(self.delay)
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
self.header.update({'Authorization': 'JWT ' + access_token})
|
||||||
|
params = {'query': 'hostname:' + self.domain, 'page': page_num}
|
||||||
|
resp = self.get(self.addr, params)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
subdomain_find = self.match(self.domain, resp.text)
|
||||||
|
self.subdomains = self.subdomains.union(subdomain_find)
|
||||||
|
page_num += 1
|
||||||
|
if page_num > 500:
|
||||||
|
break
|
||||||
|
if resp.status_code == 403:
|
||||||
|
break
|
||||||
|
|
||||||
|
def run(self, rx_queue):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
if not (self.user and self.pwd):
|
||||||
|
logger.log('ERROR', f'{self.source}模块API配置错误')
|
||||||
|
logger.log('ALERT', f'不执行{self.source}模块')
|
||||||
|
return
|
||||||
|
logger.log('DEBUG', f'开始执行{self.source}模块搜索{self.domain}的子域')
|
||||||
|
start = time.time()
|
||||||
|
self.search()
|
||||||
|
end = time.time()
|
||||||
|
self.elapsed = round(end - start, 1)
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
rx_queue.put(self.results)
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块搜索{self.domain}的子域')
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain, rx_queue): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
:param rx_queue: 结果集队列
|
||||||
|
"""
|
||||||
|
search = ZoomEyeAPI(domain)
|
||||||
|
search.run(rx_queue)
|
||||||
|
logger.log('INFOR', f'{search.source}模块耗时{search.elapsed}秒发现{search.domain}的子域{len(search.subdomains)}个')
|
||||||
|
logger.log('DEBUG', f'{search.source}模块发现{search.domain}的子域 {search.subdomains}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
do('owasp.org', result_queue)
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
# coding=utf-8
|
||||||
|
|
||||||
|
"""
|
||||||
|
OneForAll是一款强大的子域收集神器
|
||||||
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import fire
|
||||||
|
import config
|
||||||
|
import dbexport
|
||||||
|
from config import logger
|
||||||
|
from collect import Collect
|
||||||
|
from aiobrute import AIOBrute
|
||||||
|
from common import utils, database, resolve, request
|
||||||
|
|
||||||
|
__author__ = 'Jing Ling, Black Star'
|
||||||
|
__contact__ = 'admin@hackfun.org'
|
||||||
|
__copyright__ = 'Copyright (c) 2019, Jing Ling. All rights reserved.'
|
||||||
|
__license__ = 'GNU General Public License v3.0'
|
||||||
|
__version__ = '0.0.1'
|
||||||
|
|
||||||
|
|
||||||
|
class OneForAll(object):
|
||||||
|
"""
|
||||||
|
OneForAll是一款强大的子域收集神器
|
||||||
|
|
||||||
|
Version: 0.0.1
|
||||||
|
Project: https://github.com/shmilylty/OneForAll/
|
||||||
|
|
||||||
|
:param str target: 单个域名或者每行一个域名的文件路径
|
||||||
|
:param bool brute: 是否使用爆破模块(默认禁用)
|
||||||
|
:param str port: HTTP请求验证的端口范围(默认medium)
|
||||||
|
:param int valid: 导出子域的有效性(默认1)
|
||||||
|
:param str format: 导出格式(默认xlsx)
|
||||||
|
:param str path: 导出路径(默认None)
|
||||||
|
:param bool output: 是否将导出数据输出到终端(默认False)
|
||||||
|
|
||||||
|
Note:
|
||||||
|
参数valid可选值有1,0,None,分别表示导出有效,无效,全部子域
|
||||||
|
参数port可选值有'small', 'medium', 'large', 'xlarge',详见config.py配置
|
||||||
|
参数format可选格式有'csv','tsv','json','yaml','html','xls','xlsx','dbf','latex','ods'
|
||||||
|
参数path为None会根据format参数和域名名称在项目结果目录生成相应文件
|
||||||
|
|
||||||
|
Example:
|
||||||
|
python oneforall.py --target example.com run
|
||||||
|
python oneforall.py --target example.com --brute True --port medium valid 1 run
|
||||||
|
python oneforall.py --target ./domains.txt --format csv --path= ./result.csv --output True run
|
||||||
|
"""
|
||||||
|
def __init__(self, target, brute=False, port='medium', valid=1, path=None, format='xlsx', output=False):
|
||||||
|
self.target = target
|
||||||
|
self.port = port
|
||||||
|
self.domains = set()
|
||||||
|
self.domain = ''
|
||||||
|
self.datas = list()
|
||||||
|
self.brute = brute or config.enable_brute_module
|
||||||
|
self.valid = valid
|
||||||
|
self.path = path
|
||||||
|
self.format = format
|
||||||
|
self.output = output
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
logger.log('INFOR', f'开始运行OneForAll')
|
||||||
|
self.domains = utils.get_domains(self.target)
|
||||||
|
if self.domains:
|
||||||
|
for self.domain in self.domains:
|
||||||
|
collect = Collect(self.domain, export=False)
|
||||||
|
collect.run()
|
||||||
|
if self.brute:
|
||||||
|
# 由于爆破会有大量dns解析请求 并发常常会导致其他任务中的网络请求超时
|
||||||
|
brute = AIOBrute(self.domain)
|
||||||
|
brute.run()
|
||||||
|
table_name = self.domain.replace('.', '_')
|
||||||
|
db_conn = database.connect_db()
|
||||||
|
self.datas = database.get_data(db_conn, table_name).as_dict()
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
asyncio.set_event_loop(loop)
|
||||||
|
self.datas = loop.run_until_complete(resolve.bulk_query_a(self.datas))
|
||||||
|
self.datas = loop.run_until_complete(request.bulk_get_request(self.datas, self.port))
|
||||||
|
loop.run_until_complete(asyncio.sleep(0.25)) # 在关闭事件循环前加入一小段延迟让底层连接得到关闭的缓冲时间
|
||||||
|
loop.close()
|
||||||
|
database.clear_table(db_conn, table_name)
|
||||||
|
database.save_db(db_conn, table_name, self.datas)
|
||||||
|
# database.deduplicate_subdomain(db_conn, table_name)
|
||||||
|
# database.remove_invalid(db_conn, table_name)
|
||||||
|
# 数据库导出
|
||||||
|
if not self.path:
|
||||||
|
self.path = config.result_save_path.joinpath(f'{self.domain}.{self.format}')
|
||||||
|
dbexport.export(table_name, db_conn, self.valid, self.path, self.format, self.output)
|
||||||
|
db_conn.close()
|
||||||
|
else:
|
||||||
|
logger.log('FATAL', f'获取域名失败')
|
||||||
|
logger.log('INFOR', f'结束运行OneForAll')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
fire.Fire(OneForAll)
|
||||||
|
# OneForAll('owasp.org').run()
|
||||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user