This commit is contained in:
Jing Ling
2020-08-26 10:27:48 +08:00
40 changed files with 1061 additions and 1074 deletions
+1 -3
View File
@@ -13,15 +13,13 @@ requests = "*"
records = "*" records = "*"
tldextract = "*" tldextract = "*"
exrex = "*" exrex = "*"
aiohttp = "*"
fire = "*" fire = "*"
bs4 = "*" bs4 = "*"
pysocks = "*" pysocks = "*"
cloudscraper = "*" cloudscraper = "*"
tablib = "*" tablib = "*"
psutil = "*"
tenacity = "*" tenacity = "*"
ip2location = "*" treelib = "*"
[requires] [requires]
python_version = "3.8" python_version = "3.8"
Generated
+62 -152
View File
@@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "6556f720e7fb052c3f923f3e7986cc00c1cc230db729ad79685a127c02568c96" "sha256": "fde2c88929fdf63725faea24a4fc7d82d6792caaca6676c08e96a40227f47e18"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@@ -16,38 +16,6 @@
] ]
}, },
"default": { "default": {
"aiohttp": {
"hashes": [
"sha256:1e984191d1ec186881ffaed4581092ba04f7c61582a177b187d3a2f07ed9719e",
"sha256:259ab809ff0727d0e834ac5e8a283dc5e3e0ecc30c4d80b3cd17a4139ce1f326",
"sha256:2f4d1a4fdce595c947162333353d4a44952a724fba9ca3205a3df99a33d1307a",
"sha256:32e5f3b7e511aa850829fbe5aa32eb455e5534eaa4b1ce93231d00e2f76e5654",
"sha256:344c780466b73095a72c616fac5ea9c4665add7fc129f285fbdbca3cccf4612a",
"sha256:460bd4237d2dbecc3b5ed57e122992f60188afe46e7319116da5eb8a9dfedba4",
"sha256:4c6efd824d44ae697814a2a85604d8e992b875462c6655da161ff18fd4f29f17",
"sha256:50aaad128e6ac62e7bf7bd1f0c0a24bc968a0c0590a726d5a955af193544bcec",
"sha256:6206a135d072f88da3e71cc501c59d5abffa9d0bb43269a6dcd28d66bfafdbdd",
"sha256:65f31b622af739a802ca6fd1a3076fd0ae523f8485c52924a89561ba10c49b48",
"sha256:ae55bac364c405caa23a4f2d6cfecc6a0daada500274ffca4a9230e7129eac59",
"sha256:b778ce0c909a2653741cb4b1ac7015b5c130ab9c897611df43ae6a58523cb965"
],
"index": "pypi",
"version": "==3.6.2"
},
"async-timeout": {
"hashes": [
"sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f",
"sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"
],
"version": "==3.0.1"
},
"attrs": {
"hashes": [
"sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c",
"sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"
],
"version": "==19.3.0"
},
"beautifulsoup4": { "beautifulsoup4": {
"hashes": [ "hashes": [
"sha256:73cc4d115b96f79c7d77c1c7f7a0a8d4c57860d1041df407dd1aae7f07a77fd7", "sha256:73cc4d115b96f79c7d77c1c7f7a0a8d4c57860d1041df407dd1aae7f07a77fd7",
@@ -79,11 +47,11 @@
}, },
"cloudscraper": { "cloudscraper": {
"hashes": [ "hashes": [
"sha256:1d4e158de3be7a4e651083f13a14a25b75853186e989d9d97db1a08056b7a0b6", "sha256:793095bbc37aae84a5c38d9f66b56f3c83a00f0b1ff2d334f75a2b8f88b924af",
"sha256:c8c255aaa84e80d2cb5bf2c1ad1ffa000ce864595ca4cdb75ff7aab6f97befec" "sha256:a7ae28b5dc4e4ef0789ef608c42a4382bf6d3e3dd6b8117c2e3633103131ae43"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.2.42" "version": "==1.2.46"
}, },
"colorama": { "colorama": {
"hashes": [ "hashes": [
@@ -95,11 +63,11 @@
}, },
"dnspython": { "dnspython": {
"hashes": [ "hashes": [
"sha256:36c5e8e38d4369a08b6780b7f27d790a292b2b08eea01607865bf0936c558e01", "sha256:044af09374469c3a39eeea1a146e8cac27daec951f1f1f157b1962fc7cb9d1b7",
"sha256:f69c21288a962f4da86e56c4905b49d11aba7938d3d740e80d9e366ee4f1632d" "sha256:40bb3c24b9d4ec12500f0124288a65df232a3aa749bb0c39734b782873a2544d"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.16.0" "version": "==2.0.0"
}, },
"docopt": { "docopt": {
"hashes": [ "hashes": [
@@ -127,22 +95,19 @@
"index": "pypi", "index": "pypi",
"version": "==0.3.1" "version": "==0.3.1"
}, },
"future": {
"hashes": [
"sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"
],
"version": "==0.18.2"
},
"idna": { "idna": {
"hashes": [ "hashes": [
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
"sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.10" "version": "==2.10"
}, },
"ip2location": {
"hashes": [
"sha256:4a83f32098091b6aafaef63b393331bf973bbf54547db82de38f30108b5f079c",
"sha256:bbddbd5e3f79c18a9710afd43be09cb21724d7324557668017c545b5c5e62bc4"
],
"index": "pypi",
"version": "==8.4.1"
},
"jdcal": { "jdcal": {
"hashes": [ "hashes": [
"sha256:1abf1305fce18b4e8aa248cf8fe0c56ce2032392bc64bbd61b5dff2a19ec8bba", "sha256:1abf1305fce18b4e8aa248cf8fe0c56ce2032392bc64bbd61b5dff2a19ec8bba",
@@ -158,58 +123,17 @@
"index": "pypi", "index": "pypi",
"version": "==0.5.1" "version": "==0.5.1"
}, },
"multidict": {
"hashes": [
"sha256:1ece5a3369835c20ed57adadc663400b5525904e53bae59ec854a5d36b39b21a",
"sha256:275ca32383bc5d1894b6975bb4ca6a7ff16ab76fa622967625baeebcf8079000",
"sha256:3750f2205b800aac4bb03b5ae48025a64e474d2c6cc79547988ba1d4122a09e2",
"sha256:4538273208e7294b2659b1602490f4ed3ab1c8cf9dbdd817e0e9db8e64be2507",
"sha256:5141c13374e6b25fe6bf092052ab55c0c03d21bd66c94a0e3ae371d3e4d865a5",
"sha256:51a4d210404ac61d32dada00a50ea7ba412e6ea945bbe992e4d7a595276d2ec7",
"sha256:5cf311a0f5ef80fe73e4f4c0f0998ec08f954a6ec72b746f3c179e37de1d210d",
"sha256:6513728873f4326999429a8b00fc7ceddb2509b01d5fd3f3be7881a257b8d463",
"sha256:7388d2ef3c55a8ba80da62ecfafa06a1c097c18032a501ffd4cabbc52d7f2b19",
"sha256:9456e90649005ad40558f4cf51dbb842e32807df75146c6d940b6f5abb4a78f3",
"sha256:c026fe9a05130e44157b98fea3ab12969e5b60691a276150db9eda71710cd10b",
"sha256:d14842362ed4cf63751648e7672f7174c9818459d169231d03c56e84daf90b7c",
"sha256:e0d072ae0f2a179c375f67e3da300b47e1a83293c554450b29c900e50afaae87",
"sha256:f07acae137b71af3bb548bd8da720956a3bc9f9a0b87733e0899226a2317aeb7",
"sha256:fbb77a75e529021e7c4a8d4e823d88ef4d23674a202be4f5addffc72cbb91430",
"sha256:fcfbb44c59af3f8ea984de67ec7c306f618a3ec771c2843804069917a8f2e255",
"sha256:feed85993dbdb1dbc29102f50bca65bdc68f2c0c8d352468c25b54874f23c39d"
],
"markers": "python_version >= '3.5'",
"version": "==4.7.6"
},
"openpyxl": { "openpyxl": {
"hashes": [ "hashes": [
"sha256:626d38647c063d55803ef4971c4d43226538d4e95cb6260c094e363ee33e10c7" "sha256:626d38647c063d55803ef4971c4d43226538d4e95cb6260c094e363ee33e10c7"
], ],
"version": "==2.4.11" "version": "==2.4.11"
}, },
"psutil": {
"hashes": [
"sha256:1413f4158eb50e110777c4f15d7c759521703bd6beb58926f1d562da40180058",
"sha256:298af2f14b635c3c7118fd9183843f4e73e681bb6f01e12284d4d70d48a60953",
"sha256:60b86f327c198561f101a92be1995f9ae0399736b6eced8f24af41ec64fb88d4",
"sha256:685ec16ca14d079455892f25bd124df26ff9137664af445563c1bd36629b5e0e",
"sha256:73f35ab66c6c7a9ce82ba44b1e9b1050be2a80cd4dcc3352cc108656b115c74f",
"sha256:75e22717d4dbc7ca529ec5063000b2b294fc9a367f9c9ede1f65846c7955fd38",
"sha256:a02f4ac50d4a23253b68233b07e7cdb567bd025b982d5cf0ee78296990c22d9e",
"sha256:d008ddc00c6906ec80040d26dc2d3e3962109e40ad07fd8a12d0284ce5e0e4f8",
"sha256:d84029b190c8a66a946e28b4d3934d2ca1528ec94764b180f7d6ea57b0e75e26",
"sha256:e2d0c5b07c6fe5a87fa27b7855017edb0d52ee73b71e6ee368fae268605cc3f5",
"sha256:f344ca230dd8e8d5eee16827596f1c22ec0876127c28e800d7ae20ed44c4b310"
],
"index": "pypi",
"version": "==5.7.0"
},
"pyparsing": { "pyparsing": {
"hashes": [ "hashes": [
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
], ],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.4.7" "version": "==2.4.7"
}, },
"pysocks": { "pysocks": {
@@ -256,7 +180,6 @@
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.15.0" "version": "==1.15.0"
}, },
"soupsieve": { "soupsieve": {
@@ -264,42 +187,45 @@
"sha256:1634eea42ab371d3d346309b93df7870a88610f0725d47528be902a0d95ecc55", "sha256:1634eea42ab371d3d346309b93df7870a88610f0725d47528be902a0d95ecc55",
"sha256:a59dc181727e95d25f781f0eb4fd1825ff45590ec8ff49eadfd7f1a537cc0232" "sha256:a59dc181727e95d25f781f0eb4fd1825ff45590ec8ff49eadfd7f1a537cc0232"
], ],
"markers": "python_version >= '3.5'",
"version": "==2.0.1" "version": "==2.0.1"
}, },
"sqlalchemy": { "sqlalchemy": {
"hashes": [ "hashes": [
"sha256:0942a3a0df3f6131580eddd26d99071b48cfe5aaf3eab2783076fbc5a1c1882e", "sha256:072766c3bd09294d716b2d114d46ffc5ccf8ea0b714a4e1c48253014b771c6bb",
"sha256:0ec575db1b54909750332c2e335c2bb11257883914a03bc5a3306a4488ecc772", "sha256:107d4af989831d7b091e382d192955679ec07a9209996bf8090f1f539ffc5804",
"sha256:109581ccc8915001e8037b73c29590e78ce74be49ca0a3630a23831f9e3ed6c7", "sha256:15c0bcd3c14f4086701c33a9e87e2c7ceb3bcb4a246cd88ec54a49cf2a5bd1a6",
"sha256:16593fd748944726540cd20f7e83afec816c2ac96b082e26ae226e8f7e9688cf", "sha256:26c5ca9d09f0e21b8671a32f7d83caad5be1f6ff45eef5ec2f6fd0db85fc5dc0",
"sha256:427273b08efc16a85aa2b39892817e78e3ed074fcb89b2a51c4979bae7e7ba98", "sha256:276936d41111a501cf4a1a0543e25449108d87e9f8c94714f7660eaea89ae5fe",
"sha256:50c4ee32f0e1581828843267d8de35c3298e86ceecd5e9017dc45788be70a864", "sha256:3292a28344922415f939ee7f4fc0c186f3d5a0bf02192ceabd4f1129d71b08de",
"sha256:512a85c3c8c3995cc91af3e90f38f460da5d3cade8dc3a229c8e0879037547c9", "sha256:33d29ae8f1dc7c75b191bb6833f55a19c932514b9b5ce8c3ab9bc3047da5db36",
"sha256:57aa843b783179ab72e863512e14bdcba186641daf69e4e3a5761d705dcc35b1", "sha256:3bba2e9fbedb0511769780fe1d63007081008c5c2d7d715e91858c94dbaa260e",
"sha256:621f58cd921cd71ba6215c42954ffaa8a918eecd8c535d97befa1a8acad986dd", "sha256:465c999ef30b1c7525f81330184121521418a67189053bcf585824d833c05b66",
"sha256:6ac2558631a81b85e7fb7a44e5035347938b0a73f5fdc27a8566777d0792a6a4", "sha256:51064ee7938526bab92acd049d41a1dc797422256086b39c08bafeffb9d304c6",
"sha256:716754d0b5490bdcf68e1e4925edc02ac07209883314ad01a137642ddb2056f1", "sha256:5a49e8473b1ab1228302ed27365ea0fadd4bf44bc0f9e73fe38e10fdd3d6b4fc",
"sha256:736d41cfebedecc6f159fc4ac0769dc89528a989471dc1d378ba07d29a60ba1c", "sha256:618db68745682f64cedc96ca93707805d1f3a031747b5a0d8e150cfd5055ae4d",
"sha256:8619b86cb68b185a778635be5b3e6018623c0761dde4df2f112896424aa27bd8", "sha256:6547b27698b5b3bbfc5210233bd9523de849b2bb8a0329cd754c9308fc8a05ce",
"sha256:87fad64529cde4f1914a5b9c383628e1a8f9e3930304c09cf22c2ae118a1280e", "sha256:6557af9e0d23f46b8cd56f8af08eaac72d2e3c632ac8d5cf4e20215a8dca7cea",
"sha256:89494df7f93b1836cae210c42864b292f9b31eeabca4810193761990dc689cce", "sha256:73a40d4fcd35fdedce07b5885905753d5d4edf413fbe53544dd871f27d48bd4f",
"sha256:8cac7bb373a5f1423e28de3fd5fc8063b9c8ffe8957dc1b1a59cb90453db6da1", "sha256:8280f9dae4adb5889ce0bb3ec6a541bf05434db5f9ab7673078c00713d148365",
"sha256:8fd452dc3d49b3cc54483e033de6c006c304432e6f84b74d7b2c68afa2569ae5", "sha256:83469ad15262402b0e0974e612546bc0b05f379b5aa9072ebf66d0f8fef16bea",
"sha256:adad60eea2c4c2a1875eb6305a0b6e61a83163f8e233586a4d6a55221ef984fe", "sha256:860d0fe234922fd5552b7f807fbb039e3e7ca58c18c8d38aa0d0a95ddf4f6c23",
"sha256:c26f95e7609b821b5f08a72dab929baa0d685406b953efd7c89423a511d5c413", "sha256:883c9fb62cebd1e7126dd683222b3b919657590c3e2db33bdc50ebbad53e0338",
"sha256:cbe1324ef52ff26ccde2cb84b8593c8bf930069dfc06c1e616f1bfd4e47f48a3", "sha256:8afcb6f4064d234a43fea108859942d9795c4060ed0fbd9082b0f280181a15c1",
"sha256:d05c4adae06bd0c7f696ae3ec8d993ed8ffcc4e11a76b1b35a5af8a099bd2284", "sha256:96f51489ac187f4bab588cf51f9ff2d40b6d170ac9a4270ffaed535c8404256b",
"sha256:d98bc827a1293ae767c8f2f18be3bb5151fd37ddcd7da2a5f9581baeeb7a3fa1", "sha256:9e865835e36dfbb1873b65e722ea627c096c11b05f796831e3a9b542926e979e",
"sha256:da2fb75f64792c1fc64c82313a00c728a7c301efe6a60b7a9fe35b16b4368ce7", "sha256:aa0554495fe06172b550098909be8db79b5accdf6ffb59611900bea345df5eba",
"sha256:e4624d7edb2576cd72bb83636cd71c8ce544d8e272f308bd80885056972ca299", "sha256:b595e71c51657f9ee3235db8b53d0b57c09eee74dfb5b77edff0e46d2218dc02",
"sha256:e89e0d9e106f8a9180a4ca92a6adde60c58b1b0299e1b43bd5e0312f535fbf33", "sha256:b6ff91356354b7ff3bd208adcf875056d3d886ed7cef90c571aef2ab8a554b12",
"sha256:f11c2437fb5f812d020932119ba02d9e2bc29a6eca01a055233a8b449e3e1e7d", "sha256:b70bad2f1a5bd3460746c3fb3ab69e4e0eb5f59d977a23f9b66e5bdc74d97b86",
"sha256:f57be5673e12763dd400fea568608700a63ce1c6bd5bdbc3cc3a2c5fdb045274", "sha256:c7adb1f69a80573698c2def5ead584138ca00fff4ad9785a4b0b2bf927ba308d",
"sha256:fc728ece3d5c772c196fd338a99798e7efac7a04f9cb6416299a3638ee9a94cd" "sha256:c898b3ebcc9eae7b36bd0b4bbbafce2d8076680f6868bcbacee2d39a7a9726a7",
"sha256:e49947d583fe4d29af528677e4f0aa21f5e535ca2ae69c48270ebebd0d8843c0",
"sha256:eb1d71643e4154398b02e88a42fc8b29db8c44ce4134cf0f4474bfc5cb5d4dac",
"sha256:f2e8a9c0c8813a468aa659a01af6592f71cd30237ec27c4cc0683f089f90dcfc",
"sha256:fe7fe11019fc3e6600819775a7d55abc5446dda07e9795f5954fdbf8a49e1c37"
], ],
"markers": "python_version >= '3.0'", "markers": "python_version >= '3.0'",
"version": "==1.3.18" "version": "==1.3.19"
}, },
"tablib": { "tablib": {
"hashes": [ "hashes": [
@@ -325,27 +251,33 @@
}, },
"tldextract": { "tldextract": {
"hashes": [ "hashes": [
"sha256:16b2f7e81d89c2a5a914d25bdbddd3932c31a6b510db886c3ce0764a195c0ee7", "sha256:ab0e38977a129c72729476d5f8c85a8e1f8e49e9202e1db8dca76e95da7be9a8",
"sha256:9aa21a1f7827df4209e242ec4fc2293af5940ec730cde46ea80f66ed97bfc808" "sha256:c2a8a392edf3ea6fa8be80930f04c3ac29e91fa604cb2139bdf6a37fc1e1ac6d"
], ],
"index": "pypi", "index": "pypi",
"version": "==2.2.2" "version": "==2.2.3"
}, },
"tqdm": { "tqdm": {
"hashes": [ "hashes": [
"sha256:63ef7a6d3eb39f80d6b36e4867566b3d8e5f1fe3d6cb50c5e9ede2b3198ba7b7", "sha256:1a336d2b829be50e46b84668691e0a2719f26c97c62846298dd5ae2937e4d5cf",
"sha256:7810e627bcf9d983a99d9ff8a0c09674400fd2927eddabeadf153c14a2ec8656" "sha256:564d632ea2b9cb52979f7956e093e831c28d441c11751682f84c86fc46e4fd21"
], ],
"index": "pypi", "index": "pypi",
"version": "==4.47.0" "version": "==4.48.2"
},
"treelib": {
"hashes": [
"sha256:1cbfffb2d2b75ccac27d0200cee0507b6fbb0726e0afb9fae017ade5d2ce8788"
],
"index": "pypi",
"version": "==1.6.1"
}, },
"urllib3": { "urllib3": {
"hashes": [ "hashes": [
"sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527", "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a",
"sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115" "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", "version": "==1.25.10"
"version": "==1.25.9"
}, },
"win32-setctime": { "win32-setctime": {
"hashes": [ "hashes": [
@@ -354,28 +286,6 @@
], ],
"markers": "sys_platform == 'win32'", "markers": "sys_platform == 'win32'",
"version": "==1.0.1" "version": "==1.0.1"
},
"yarl": {
"hashes": [
"sha256:0c2ab325d33f1b824734b3ef51d4d54a54e0e7a23d13b86974507602334c2cce",
"sha256:0ca2f395591bbd85ddd50a82eb1fde9c1066fafe888c5c7cc1d810cf03fd3cc6",
"sha256:2098a4b4b9d75ee352807a95cdf5f10180db903bc5b7270715c6bbe2551f64ce",
"sha256:25e66e5e2007c7a39541ca13b559cd8ebc2ad8fe00ea94a2aad28a9b1e44e5ae",
"sha256:26d7c90cb04dee1665282a5d1a998defc1a9e012fdca0f33396f81508f49696d",
"sha256:308b98b0c8cd1dfef1a0311dc5e38ae8f9b58349226aa0533f15a16717ad702f",
"sha256:3ce3d4f7c6b69c4e4f0704b32eca8123b9c58ae91af740481aa57d7857b5e41b",
"sha256:58cd9c469eced558cd81aa3f484b2924e8897049e06889e8ff2510435b7ef74b",
"sha256:5b10eb0e7f044cf0b035112446b26a3a2946bca9d7d7edb5e54a2ad2f6652abb",
"sha256:6faa19d3824c21bcbfdfce5171e193c8b4ddafdf0ac3f129ccf0cdfcb083e462",
"sha256:944494be42fa630134bf907714d40207e646fd5a94423c90d5b514f7b0713fea",
"sha256:a161de7e50224e8e3de6e184707476b5a989037dcb24292b391a3d66ff158e70",
"sha256:a4844ebb2be14768f7994f2017f70aca39d658a96c786211be5ddbe1c68794c1",
"sha256:c2b509ac3d4b988ae8769901c66345425e361d518aecbe4acbfc2567e416626a",
"sha256:c9959d49a77b0e07559e579f38b2f3711c2b8716b8410b320bf9713013215a1b",
"sha256:d8cdee92bc930d8b09d8bd2043cedd544d9c8bd7436a77678dd602467a993080",
"sha256:e15199cdb423316e15f108f51249e44eb156ae5dba232cb73be555324a1d49c2"
],
"version": "==1.4.2"
} }
}, },
"develop": {} "develop": {}
+1 -16
View File
@@ -4,7 +4,7 @@
[![codecov](https://codecov.io/gh/shmilylty/OneForAll/branch/master/graph/badge.svg)](https://codecov.io/gh/shmilylty/OneForAll) [![codecov](https://codecov.io/gh/shmilylty/OneForAll/branch/master/graph/badge.svg)](https://codecov.io/gh/shmilylty/OneForAll)
[![Maintainability](https://api.codeclimate.com/v1/badges/1287668a6b4c72af683e/maintainability)](https://codeclimate.com/github/shmilylty/OneForAll/maintainability) [![Maintainability](https://api.codeclimate.com/v1/badges/1287668a6b4c72af683e/maintainability)](https://codeclimate.com/github/shmilylty/OneForAll/maintainability)
[![License](https://img.shields.io/github/license/shmilylty/OneForAll)](https://github.com/shmilylty/OneForAll/tree/master/LICENSE) [![License](https://img.shields.io/github/license/shmilylty/OneForAll)](https://github.com/shmilylty/OneForAll/tree/master/LICENSE)
[![python](https://img.shields.io/badge/python-3.8-blue)](https://github.com/shmilylty/OneForAll/tree/master/) [![python](https://img.shields.io/badge/python-3.6|3.7|3.8-blue)](https://github.com/shmilylty/OneForAll/tree/master/)
[![python](https://img.shields.io/badge/release-v0.3.0-brightgreen)](https://github.com/shmilylty/OneForAll/releases) [![python](https://img.shields.io/badge/release-v0.3.0-brightgreen)](https://github.com/shmilylty/OneForAll/releases)
👊**OneForAll是一款功能强大的子域收集工具** 📝[English Document](https://github.com/shmilylty/OneForAll/tree/master/docs/en-us/README.md) 👊**OneForAll是一款功能强大的子域收集工具** 📝[English Document](https://github.com/shmilylty/OneForAll/tree/master/docs/en-us/README.md)
@@ -15,21 +15,6 @@
📢 请务必花一点时间阅读此文档,有助于你快速熟悉OneForAll! 📢 请务必花一点时间阅读此文档,有助于你快速熟悉OneForAll!
<details>
<summary><b>🐍安装要求</b></summary>
OneForAll基于[Python 3.8.0]( https://www.python.org/downloads/release/python-380/ )开发和测试,请使用高于Python 3.8.0的稳定发行版本,其他版本可能会出现一些问题(Windows平台必须使用3.8.0以上版本),安装Python环境可以参考[Python 3 安装指南](https://pythonguidecn.readthedocs.io/zh/latest/starting/installation.html#python-3)。运行以下命令检查Python和pip3版本:
```bash
python -V
pip3 -V
```
如果你看到类似以下的输出便说明Python环境没有问题:
```bash
Python 3.8.0
pip 19.2.2 from C:\Users\shmilylty\AppData\Roaming\Python\Python38\site-packages\pip (python 3.8)
```
</details>
<details> <details>
<summary><b>✔安装步骤(git 版)</b></summary> <summary><b>✔安装步骤(git 版)</b></summary>
+113 -47
View File
@@ -10,7 +10,6 @@ OneForAll subdomain brute module
import gc import gc
import json import json
import time import time
import random
import secrets import secrets
import exrex import exrex
@@ -21,67 +20,137 @@ from dns.resolver import NXDOMAIN, YXDOMAIN, NoAnswer, NoNameservers
import dbexport import dbexport
from common import utils from common import utils
from common import similarity
from config import settings from config import settings
from common.module import Module from common.module import Module
from config.log import logger from config.log import logger
@tenacity.retry(stop=tenacity.stop_after_attempt(3)) def config_resolver(nameservers):
def do_query_a(domain, resolver): """
配置DNS解析器
:param nameservers: 名称解析服务器地址
"""
resolver = utils.dns_resolver()
resolver.nameservers = nameservers
resolver.rotate = True # 随机使用NS
resolver.cache = None # 不使用DNS缓存
return resolver
def gen_random_subdomains(domain, count):
"""
生成指定数量的随机子域域名列表
:param domain: 主域
:param count: 数量
"""
subdomains = set()
if count < 1:
return subdomains
for _ in range(count):
token = secrets.token_hex(4)
subdomains.add(f'{token}.{domain}')
return subdomains
def query_a_record(subdomain, resolver):
"""
查询子域A记录
:param subdomain: 子域
:param resolver: DNS解析器
"""
try: try:
answer = resolver.query(domain, 'A') answer = resolver.query(subdomain, 'A')
# If resolve random subdomain raise timeout error, try again
except Timeout as e:
logger.log('ALERT', f'DNS resolve timeout, retrying')
logger.log('DEBUG', e.args)
raise tenacity.TryAgain
# If resolve random subdomain raise NXDOMAIN, YXDOMAIN, NoAnswer, NoNameservers error
# It means that there is no A record of random subdomain and not use wildcard dns record
except (NXDOMAIN, YXDOMAIN, NoAnswer, NoNameservers) as e:
logger.log('DEBUG', e.args)
logger.log('INFOR', f'{domain} seems not use wildcard dns record')
return False
except Exception as e: except Exception as e:
logger.log('ALERT', f'Detect {domain} wildcard dns record error') logger.log('DEBUG', f'Query {subdomain} wildcard dns record error')
logger.log('FATAL', e.args) logger.log('DEBUG', e.args)
exit(1) return False
else:
if answer.rrset is None: if answer.rrset is None:
logger.log('ALERT', f'DNS resolve dont have result, retrying') return False
raise tenacity.TryAgain
ttl = answer.ttl ttl = answer.ttl
name = answer.name name = answer.name
ips = {item.address for item in answer} ips = {item.address for item in answer}
logger.log('ALERT', f'{domain} use wildcard dns record') logger.log('ALERT', f'{subdomain} resolve to: {name} '
logger.log('ALERT', f'{domain} resolve to: {name} '
f'IP: {ips} TTL: {ttl}') f'IP: {ips} TTL: {ttl}')
return True return True
def detect_wildcard(domain, authoritative_ns): def all_resolve_success(subdomains):
"""
判断是否所有子域都解析成功
:param subdomains: 子域列表
"""
resolver = utils.dns_resolver()
resolver.cache = None # 不使用DNS缓存
status = set()
for subdomain in subdomains:
status.add(query_a_record(subdomain, resolver))
return all(status)
def all_request_success(subdomains):
"""
判断是否所有子域都请求成功
:param subdomains: 子域列表
"""
result = list()
for subdomain in subdomains:
url = f'http://{subdomain}'
resp = utils.get_url_resp(url)
if resp:
logger.log('ALERT', f'Request: {url} Status: {resp.status_code} '
f'Size: {len(resp.content)}')
result.append(resp.text)
else:
result.append(resp)
return all(result), result
def any_similar_html(resp_list):
"""
判断是否有一组HTML页面结构相似
:param resp_list: 响应HTML页面
"""
html_doc1, html_doc2, html_doc3 = resp_list
if similarity.is_similar(html_doc1, html_doc2):
return True
if similarity.is_similar(html_doc1, html_doc3):
return True
if similarity.is_similar(html_doc2, html_doc3):
return True
return False
def detect_wildcard(domain):
""" """
Detect use wildcard dns record or not Detect use wildcard dns record or not
:param str domain: domain :param str domain: domain
:param list authoritative_ns: authoritative name server
:return bool use wildcard dns record or not :return bool use wildcard dns record or not
""" """
logger.log('INFOR', f'Detecting {domain} use wildcard dns record or not') logger.log('INFOR', f'Detecting {domain} use wildcard dns record or not')
token = secrets.token_hex(4) random_subdomains = gen_random_subdomains(domain, 3)
random_subdomain = f'{token}.{domain}' if not all_resolve_success(random_subdomains):
resolver = utils.dns_resolver()
resolver.nameservers = authoritative_ns
resolver.rotate = True
resolver.cache = None
try:
wildcard = do_query_a(random_subdomain, resolver)
except Exception as e:
logger.log('DEBUG', e.args)
logger.log('ALERT', f'Multiple detection errors, so temporarily {domain} '
f'does not use wildcard dns record')
return False return False
is_all_success, all_request_resp = all_request_success(random_subdomains)
if not is_all_success:
return True
return any_similar_html(all_request_resp)
def is_enable_wildcard(domain):
is_enable = detect_wildcard(domain)
if is_enable:
logger.log('ALERT', f'The domain {domain} enables wildcard')
else: else:
return wildcard logger.log('ALERT', f'The domain {domain} disables wildcard')
return is_enable
def gen_subdomains(expression, path): def gen_subdomains(expression, path):
@@ -229,9 +298,9 @@ def collect_wildcard_record(domain, authoritative_ns):
if not authoritative_ns: if not authoritative_ns:
return list(), int() return list(), int()
resolver = utils.dns_resolver() resolver = utils.dns_resolver()
resolver.nameservers = authoritative_ns resolver.nameservers = authoritative_ns # 使用权威名称服务器
resolver.rotate = True resolver.rotate = True # 随机使用NS
resolver.cache = None resolver.cache = None # 不使用DNS缓存
ips = set() ips = set()
ttl = int() ttl = int()
ips_stat = dict() ips_stat = dict()
@@ -464,14 +533,13 @@ class Brute(Module):
Example: Example:
brute.py --target domain.com --word True run brute.py --target domain.com --word True run
brute.py --targets ./domains.txt --word True run brute.py --targets ./domains.txt --word True run
brute.py --target domain.com --word True --process 1 run brute.py --target domain.com --word True --concurrent 2000 run
brute.py --target domain.com --word True --wordlist subnames.txt run brute.py --target domain.com --word True --wordlist subnames.txt run
brute.py --target domain.com --word True --recursive True --depth 2 run brute.py --target domain.com --word True --recursive True --depth 2 run
brute.py --target d.com --fuzz True --place m.*.d.com --rule '[a-z]' run brute.py --target d.com --fuzz True --place m.*.d.com --rule '[a-z]' run
brute.py --target d.com --fuzz True --place m.*.d.com --fuzzlist subnames.txt run brute.py --target d.com --fuzz True --place m.*.d.com --fuzzlist subnames.txt run
Note: Note:
--alive True/False Only export alive subdomains or not (default False)
--format rst/csv/tsv/json/yaml/html/jira/xls/xlsx/dbf/latex/ods (result format) --format rst/csv/tsv/json/yaml/html/jira/xls/xlsx/dbf/latex/ods (result format)
--path Result path (default None, automatically generated) --path Result path (default None, automatically generated)
@@ -479,7 +547,7 @@ class Brute(Module):
:param str target: One domain (target or targets must be provided) :param str target: One domain (target or targets must be provided)
:param str targets: File path of one domain per line :param str targets: File path of one domain per line
:param int process: Number of processes (default 1) :param int process: Number of processes (default 1)
:param int concurrent: Number of concurrent (default 10000) :param int concurrent: Number of concurrent (default 2000)
:param bool word: Use word mode generate dictionary (default False) :param bool word: Use word mode generate dictionary (default False)
:param str wordlist: Dictionary path used in word mode (default use ./config/default.py) :param str wordlist: Dictionary path used in word mode (default use ./config/default.py)
:param bool recursive: Use recursion (default False) :param bool recursive: Use recursion (default False)
@@ -524,8 +592,6 @@ class Brute(Module):
self.domain = str() # 当前正在进行爆破的域名 self.domain = str() # 当前正在进行爆破的域名
self.ips_times = dict() # IP集合出现次数 self.ips_times = dict() # IP集合出现次数
self.enable_wildcard = False # 当前域名是否使用泛解析 self.enable_wildcard = False # 当前域名是否使用泛解析
self.wildcard_check = settings.brute_wildcard_check
self.wildcard_deal = settings.brute_wildcard_deal
self.check_env = True self.check_env = True
self.quite = False self.quite = False
@@ -596,7 +662,7 @@ class Brute(Module):
wildcard_ttl = int() # 泛解析TTL整型值 wildcard_ttl = int() # 泛解析TTL整型值
ns_list = query_domain_ns(self.domain) ns_list = query_domain_ns(self.domain)
ns_ip_list = query_domain_ns_a(ns_list) # DNS权威名称服务器对应A记录列表 ns_ip_list = query_domain_ns_a(ns_list) # DNS权威名称服务器对应A记录列表
self.enable_wildcard = detect_wildcard(domain, ns_ip_list) self.enable_wildcard = is_enable_wildcard(domain)
if self.enable_wildcard: if self.enable_wildcard:
wildcard_ips, wildcard_ttl = collect_wildcard_record(domain, wildcard_ips, wildcard_ttl = collect_wildcard_record(domain,
+26
View File
@@ -0,0 +1,26 @@
from common.module import Module
class Check(Module):
"""
Check base class
"""
def __init__(self):
Module.__init__(self)
self.request_status = 1
def to_check(self, filenames):
urls = set()
for filename in filenames:
urls.update((f'http://{self.domain}/{filename}',
f'https://{self.domain}/{filename}',
f'http://www.{self.domain}/{filename}',
f'https://www.{self.domain}/{filename}'))
for url in urls:
self.header = self.get_header()
self.proxy = self.get_proxy(self.source)
resp = self.get(url, check=False, ignore=True)
self.subdomains = self.collect_subdomains(resp)
if self.subdomains:
break
+7 -7
View File
@@ -78,8 +78,8 @@ class Database(object):
f'cidr text,' f'cidr text,'
f'asn text,' f'asn text,'
f'org text,' f'org text,'
f'ip2region text,' f'addr text,'
f'ip2location text,' f'isp text,'
f'resolver text,' f'resolver text,'
f'module text,' f'module text,'
f'source text,' f'source text,'
@@ -103,13 +103,13 @@ class Database(object):
f'insert into "{table_name}" ' f'insert into "{table_name}" '
f'(id, alive, resolve, request, new, url, subdomain, port, level,' f'(id, alive, resolve, request, new, url, subdomain, port, level,'
f'cname, content, public, cdn, status, reason, title, banner, header,' f'cname, content, public, cdn, status, reason, title, banner, header,'
f'history, response, times, ttl, cidr, asn, org, ip2region,' f'history, response, times, ttl, cidr, asn, org, addr, isp, resolver,'
f'ip2location, resolver, module, source, elapse, find) ' f'module, source, elapse, find) '
f'values (:id, :alive, :resolve, :request, :new, :url,' f'values (:id, :alive, :resolve, :request, :new, :url,'
f':subdomain, :port, :level, :cname, :content, :public, :cdn,' f':subdomain, :port, :level, :cname, :content, :public, :cdn,'
f':status, :reason, :title, :banner, :header, :history, :response,' f':status, :reason, :title, :banner, :header, :history, :response,'
f':times, :ttl, :cidr, :asn, :org, :ip2region, :ip2location,' f':times, :ttl, :cidr, :asn, :org, :addr, :isp, :resolver, :module,'
f':resolver, :module, :source, :elapse, :find)', results) f':source, :elapse, :find)', results)
except Exception as e: except Exception as e:
logger.log('ERROR', e) logger.log('ERROR', e)
@@ -231,7 +231,7 @@ class Database(object):
table_name = table_name.replace('.', '_') table_name = table_name.replace('.', '_')
query = f'select id, new, alive, request, resolve, url, subdomain, level,' \ query = f'select id, new, alive, request, resolve, url, subdomain, level,' \
f'cname, content, public, cdn, port, status, reason, title, banner,' \ f'cname, content, public, cdn, port, status, reason, title, banner,' \
f'cidr, asn, org, ip2region, ip2location, source from "{table_name}"' f'cidr, asn, org, addr, isp, source from "{table_name}"'
if alive and limit: if alive and limit:
if limit in ['resolve', 'request']: if limit in ['resolve', 'request']:
where = f' where {limit} = 1' where = f' where {limit} = 1'
-1
View File
@@ -9,7 +9,6 @@ class Domain(object):
:param str string: input string :param str string: input string
""" """
def __init__(self, string): def __init__(self, string):
self.string = str(string) self.string = str(string)
self.regexp = r'\b((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}\b' self.regexp = r'\b((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}\b'
-13
View File
@@ -1,13 +0,0 @@
import zipfile
from IP2Location import IP2Location
from config.setting import data_storage_dir
class IpGeoInfo(IP2Location):
def __init__(self):
zip_path = data_storage_dir.joinpath("IP2LOCATION-LITE-DB3.BIN.ZIP")
bin_path = data_storage_dir.joinpath("IP2LOCATION-LITE-DB3.BIN")
if not bin_path.exists():
zf = zipfile.ZipFile(zip_path)
zf.extract('IP2LOCATION-LITE-DB3.BIN', data_storage_dir)
IP2Location.__init__(self, bin_path)
+35 -7
View File
@@ -10,6 +10,8 @@ import socket
import struct import struct
import sys import sys
from config import settings
class IpRegInfo(object): class IpRegInfo(object):
__INDEX_BLOCK_LENGTH = 12 __INDEX_BLOCK_LENGTH = 12
@@ -39,7 +41,8 @@ class IpRegInfo(object):
self.__dbBinStr = self.__f.read() # read all the contents in file self.__dbBinStr = self.__f.read() # read all the contents in file
self.__indexSPtr = self.get_long(self.__dbBinStr, 0) self.__indexSPtr = self.get_long(self.__dbBinStr, 0)
self.__indexLPtr = self.get_long(self.__dbBinStr, 4) self.__indexLPtr = self.get_long(self.__dbBinStr, 4)
self.__indexCount = int((self.__indexLPtr - self.__indexSPtr) / self.__INDEX_BLOCK_LENGTH) + 1 self.__indexCount = int((self.__indexLPtr - self.__indexSPtr) /
self.__INDEX_BLOCK_LENGTH) + 1
l, h, data_ptr = (0, self.__indexCount, 0) l, h, data_ptr = (0, self.__indexCount, 0)
while l <= h: while l <= h:
@@ -67,14 +70,16 @@ class IpRegInfo(object):
" binary search method " binary search method
" param: ip " param: ip
""" """
if not ip.isdigit(): ip = self.ip2long(ip) if not ip.isdigit():
ip = self.ip2long(ip)
if self.__indexCount == 0: if self.__indexCount == 0:
self.__f.seek(0) self.__f.seek(0)
super_block = self.__f.read(8) super_block = self.__f.read(8)
self.__indexSPtr = self.get_long(super_block, 0) self.__indexSPtr = self.get_long(super_block, 0)
self.__indexLPtr = self.get_long(super_block, 4) self.__indexLPtr = self.get_long(super_block, 4)
self.__indexCount = int((self.__indexLPtr - self.__indexSPtr) / self.__INDEX_BLOCK_LENGTH) + 1 self.__indexCount = int((self.__indexLPtr - self.__indexSPtr) /
self.__INDEX_BLOCK_LENGTH) + 1
l, h, data_ptr = (0, self.__indexCount, 0) l, h, data_ptr = (0, self.__indexCount, 0)
while l <= h: while l <= h:
@@ -208,10 +213,9 @@ class IpRegInfo(object):
self.__f.seek(data_ptr) self.__f.seek(data_ptr)
data = self.__f.read(data_len) data = self.__f.read(data_len)
return { info = {"city_id": self.get_long(data, 0),
"city_id": self.get_long(data, 0), "region": data[4:].decode('utf-8')}
"region": data[4:] return info
}
@staticmethod @staticmethod
def ip2long(ip): def ip2long(ip):
@@ -244,3 +248,27 @@ class IpRegInfo(object):
self.__dbBinStr = None self.__dbBinStr = None
self.__headerPtr = None self.__headerPtr = None
self.__headerSip = None self.__headerSip = None
class IpRegData(IpRegInfo):
def __init__(self):
path = settings.data_storage_dir.joinpath('ip2region.db')
IpRegInfo.__init__(self, path)
def query(self, ip, algorithm='memory'):
algorithms = ['memory', 'binary', 'btree']
if algorithm not in algorithms:
raise Exception(f"Only three query algorithms are supported: {algorithms}")
if algorithm == 'memory':
result = self.memory_search(ip)
elif algorithm == 'binary':
result = self.binary_search(ip)
else:
result = self.btree_search(ip)
addr_list = result.get('region').split('|')
addr = ''.join(filter(lambda x: x != '0', addr_list[:-1]))
isp = addr_list[-1]
if isp == '0':
isp = '未知'
info = {'addr': addr, 'isp': isp}
return info
+17 -13
View File
@@ -22,9 +22,9 @@ class Module(object):
self.cookie = None self.cookie = None
self.header = dict() self.header = dict()
self.proxy = None self.proxy = None
self.delay = settings.request_delay # 请求睡眠时延 self.delay = 1 # 请求睡眠时延
self.timeout = settings.request_timeout # 请求超时时间 self.timeout = settings.request_timeout_second # 请求超时时间
self.verify = settings.request_verify # 请求SSL验证 self.verify = settings.request_ssl_verify # 请求SSL验证
self.domain = str() # 当前进行子域名收集的主域 self.domain = str() # 当前进行子域名收集的主域
self.subdomains = set() # 存放发现的子域 self.subdomains = set() # 存放发现的子域
self.infos = dict() # 存放子域有关信息 self.infos = dict() # 存放子域有关信息
@@ -73,7 +73,7 @@ class Module(object):
:param dict params: request parameters :param dict params: request parameters
:param bool check: check response :param bool check: check response
:param kwargs: other params :param kwargs: other params
:return: requests's response object :return: response object
""" """
try: try:
resp = requests.head(url, resp = requests.head(url,
@@ -93,16 +93,20 @@ class Module(object):
return resp return resp
return None return None
def get(self, url, params=None, check=True, **kwargs): def get(self, url, params=None, check=True, ignore=False, **kwargs):
""" """
Custom get request Custom get request
:param str url: request url :param str url: request url
:param dict params: request parameters :param dict params: request parameters
:param bool check: check response :param bool check: check response
:param bool ignore: ignore error
:param kwargs: other params :param kwargs: other params
:return: requests's response object :return: response object
""" """
level = 'ERROR'
if ignore:
level = 'DEBUG'
try: try:
resp = requests.get(url, resp = requests.get(url,
params=params, params=params,
@@ -113,7 +117,7 @@ class Module(object):
verify=self.verify, verify=self.verify,
**kwargs) **kwargs)
except Exception as e: except Exception as e:
logger.log('ERROR', e.args) logger.log(level, e.args)
return None return None
if not check: if not check:
return resp return resp
@@ -129,7 +133,7 @@ class Module(object):
:param dict data: request data :param dict data: request data
:param bool check: check response :param bool check: check response
:param kwargs: other params :param kwargs: other params
:return: requests's response object :return: response object
""" """
try: try:
resp = requests.post(url, resp = requests.post(url,
@@ -195,7 +199,7 @@ class Module(object):
:param str module: module name :param str module: module name
:return: proxy :return: proxy
""" """
if not settings.enable_proxy: if not settings.enable_request_proxy:
logger.log('TRACE', f'All modules do not use proxy') logger.log('TRACE', f'All modules do not use proxy')
return self.proxy return self.proxy
if settings.proxy_all_module: if settings.proxy_all_module:
@@ -280,8 +284,8 @@ class Module(object):
'cidr': None, 'cidr': None,
'asn': None, 'asn': None,
'org': None, 'org': None,
'ip2region': None, 'addr': None,
'ip2location': None, 'isp': None,
'resolver': None, 'resolver': None,
'module': self.module, 'module': self.module,
'source': self.source, 'source': self.source,
@@ -331,8 +335,8 @@ class Module(object):
'cidr': info.get('cidr'), 'cidr': info.get('cidr'),
'asn': info.get('asn'), 'asn': info.get('asn'),
'org': info.get('org'), 'org': info.get('org'),
'ip2region': info.get('ip2region'), 'addr': info.get('addr'),
'ip2location': info.get('ip2location'), 'isp': info.get('isp'),
'resolver': info.get('resolver'), 'resolver': info.get('resolver'),
'module': self.module, 'module': self.module,
'source': self.source, 'source': self.source,
-1
View File
@@ -5,6 +5,5 @@ class Query(Module):
""" """
Query base class Query base class
""" """
def __init__(self): def __init__(self):
Module.__init__(self) Module.__init__(self)
+130 -185
View File
@@ -1,10 +1,10 @@
import json import json
import asyncio from threading import Thread
import functools from queue import Queue
import aiohttp
import tqdm import tqdm
from aiohttp import ClientSession import requests
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from common import utils from common import utils
@@ -12,17 +12,17 @@ from config.log import logger
from config import settings from config import settings
def get_limit_conn(): def req_thread_count():
count = settings.limit_open_conn count = settings.request_thread_count
if isinstance(count, int): if isinstance(count, int):
count = max(16, count) count = max(16, count)
else: else:
count = utils.get_coroutine_count() count = utils.get_request_count()
logger.log('DEBUG', f'Request coroutine {count}') logger.log('DEBUG', f'Number of request threads {count}')
return count return count
def get_ports(port): def get_port_seq(port):
logger.log('DEBUG', 'Getting port range') logger.log('DEBUG', 'Getting port range')
ports = set() ports = set()
if isinstance(port, (set, list, tuple)): if isinstance(port, (set, list, tuple)):
@@ -40,81 +40,33 @@ def get_ports(port):
return set(ports) return set(ports)
def gen_req_data(data, ports): def gen_req_url(domain, port):
if str(port).endswith('443'):
url = f'https://{domain}:{port}'
if port == 443:
url = f'https://{domain}'
return url
url = f'http://{domain}:{port}'
if port == 80:
url = f'http://{domain}'
return url
def gen_req_urls(data, ports):
logger.log('INFOR', 'Generating request urls') logger.log('INFOR', 'Generating request urls')
new_data = [] urls = set()
for data in data: for info in data:
resolve = data.get('resolve') resolve = info.get('resolve')
# 解析不成功的子域不进行http请求探测 # 解析不成功的子域不进行http请求探测
if resolve != 1: if resolve != 1:
continue continue
subdomain = data.get('subdomain') subdomain = info.get('subdomain')
for port in ports: for port in ports:
if str(port).endswith('443'): urls.add(gen_req_url(subdomain, port))
url = f'https://{subdomain}:{port}' return urls
if port == 443:
url = f'https://{subdomain}'
data['id'] = None
data['url'] = url
data['port'] = port
new_data.append(data)
data = dict(data) # 需要生成一个新的字典对象
else:
url = f'http://{subdomain}:{port}'
if port == 80:
url = f'http://{subdomain}'
data['id'] = None
data['url'] = url
data['port'] = port
new_data.append(data)
data = dict(data) # 需要生成一个新的字典对象
return new_data
async def fetch(session, method, url): def get_html_title(markup):
"""
请求
:param session: session对象
:param method: 请求方法
:param str url: url地址
:return: 响应对象和响应文本
"""
timeout = aiohttp.ClientTimeout(total=None,
connect=None,
sock_read=settings.sockread_timeout,
sock_connect=settings.sockconn_timeout)
try:
if method == 'HEAD':
async with session.head(url,
ssl=settings.verify_ssl,
allow_redirects=settings.allow_redirects,
timeout=timeout,
proxy=settings.aiohttp_proxy) as resp:
text = await resp.text()
else:
async with session.get(url,
ssl=settings.verify_ssl,
allow_redirects=settings.allow_redirects,
timeout=timeout,
proxy=settings.aiohttp_proxy) as resp:
try:
# 先尝试用utf-8解码
text = await resp.text(encoding='utf-8', errors='strict')
except UnicodeError:
try:
# 再尝试用gb18030解码
text = await resp.text(encoding='gb18030', errors='strict')
except UnicodeError:
# 最后尝试自动解码
text = await resp.text(encoding=None, errors='ignore')
return resp, text
except Exception as e:
return e, None
def get_title(markup):
""" """
获取标题 获取标题
@@ -161,107 +113,107 @@ def get_jump_urls(history):
return urls return urls
def request_callback(future, index, datas): def get(url, resp_queue, session):
resp, text = future.result() timeout = settings.request_timeout_second
if isinstance(resp, BaseException): redirect = settings.request_allow_redirect
exception = resp proxy = utils.get_proxy()
logger.log('TRACE', exception.args) try:
name = utils.get_classname(exception) resp = session.get(url, timeout=timeout, allow_redirects=redirect, proxies=proxy)
datas[index]['reason'] = name + ' ' + str(exception) except Exception as e:
datas[index]['request'] = 0 logger.log('DEBUG', e.args)
datas[index]['alive'] = 0 return
resp_queue.put(resp)
def request(urls_queue, resp_queue, session):
while not urls_queue.empty():
url = urls_queue.get()
get(url, resp_queue, session)
urls_queue.task_done()
def progress(urls, resp_queue):
bar = tqdm.tqdm()
bar.total = len(urls)
bar.desc = 'Request Progress'
bar.ncols = 80
while True:
done = resp_queue.qsize()
bar.n = done
bar.update()
if done == bar.total:
break
def get_session():
header = utils.gen_fake_header()
verify = settings.request_ssl_verify
redirect_limit = settings.request_redirect_limit
session = requests.Session()
session.headers = header
session.verify = verify
session.max_redirects = redirect_limit
return session
def bulk_request(urls):
logger.log('INFOR', 'Requesting urls in bulk')
resp_list = list()
urls_queue = Queue()
resp_queue = Queue()
for url in urls:
urls_queue.put(url)
session = get_session()
thread_count = req_thread_count()
progress_thread = Thread(target=progress, args=(urls, resp_queue))
progress_thread.start()
for _ in range(thread_count):
request_thread = Thread(target=request, args=(urls_queue, resp_queue, session))
request_thread.start()
urls_queue.join()
while not resp_queue.empty():
resp = resp_queue.get()
resp_list.append(resp)
return resp_list
def gen_new_info(info, resp):
port = resp.raw._pool.port
info['port'] = port
info['url'] = resp.url
info['reason'] = resp.reason
code = resp.status_code
info['status'] = code
info['request'] = 1
if code == 400 or code >= 500:
info['alive'] = 0
else: else:
datas[index]['reason'] = resp.reason info['alive'] = 1
datas[index]['status'] = resp.status
datas[index]['request'] = 1
if resp.status == 400 or resp.status >= 500:
datas[index]['alive'] = 0
else:
datas[index]['alive'] = 1
headers = resp.headers headers = resp.headers
if settings.enable_banner_identify: if settings.enable_banner_identify:
datas[index]['banner'] = utils.get_sample_banner(headers) info['banner'] = utils.get_sample_banner(headers)
datas[index]['header'] = json.dumps(dict(headers)) info['header'] = json.dumps(dict(headers))
history = resp.history history = resp.history
datas[index]['history'] = json.dumps(get_jump_urls(history)) info['history'] = json.dumps(get_jump_urls(history))
if isinstance(text, str): text = utils.decode_resp_text(resp)
title = get_title(text).strip() title = get_html_title(text).strip()
datas[index]['title'] = utils.remove_invalid_string(title) info['title'] = utils.remove_invalid_string(title)
datas[index]['response'] = utils.remove_invalid_string(text) info['response'] = utils.remove_invalid_string(text)
return info
def get_connector(): def gen_new_data(data, resp_list):
count = get_limit_conn() new_data = list()
return aiohttp.TCPConnector(ttl_dns_cache=300, for resp in resp_list:
ssl=settings.verify_ssl, subdomain = resp.raw._pool.host
limit=count, for info in data:
limit_per_host=settings.limit_per_host) if info.get('subdomain') == subdomain:
new_data.append(gen_new_info(info, resp))
return new_data
async def async_request(urls):
results = list()
connector = get_connector()
headers = utils.get_random_header()
session = ClientSession(connector=connector, headers=headers)
tasks = []
for i, url in enumerate(urls):
task = asyncio.ensure_future(fetch(session, 'GET', url))
tasks.append(task)
if tasks:
futures = asyncio.as_completed(tasks)
for future in tqdm.tqdm(futures,
total=len(tasks),
desc='Request Progress',
ncols=80):
result = await future
results.append(result)
await session.close()
return results
async def bulk_request(data, port):
ports = get_ports(port)
no_req_data = utils.get_filtered_data(data)
to_req_data = gen_req_data(data, ports)
method = settings.request_method.upper()
logger.log('INFOR', f'Use {method} method to request')
logger.log('INFOR', 'Async subdomains request in progress')
connector = get_connector()
headers = utils.get_random_header()
session = ClientSession(connector=connector, headers=headers)
tasks = []
for num, data in enumerate(to_req_data):
url = data.get('url')
task = asyncio.ensure_future(fetch(session, method, url))
task.add_done_callback(functools.partial(request_callback,
index=num,
datas=to_req_data))
tasks.append(task)
if tasks:
futures = asyncio.as_completed(tasks)
for future in tqdm.tqdm(futures,
total=len(tasks),
desc='Request Progress',
ncols=80):
await future
await session.close()
return to_req_data + no_req_data
def set_loop_policy():
try:
import uvloop
except ImportError:
pass
else:
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
def set_loop():
set_loop_policy()
loop = asyncio.get_event_loop()
asyncio.set_event_loop(loop)
return loop
def run_request(domain, data, port): def run_request(domain, data, port):
@@ -274,25 +226,18 @@ def run_request(domain, data, port):
:return list: result :return list: result
""" """
logger.log('INFOR', f'Start requesting subdomains of {domain}') logger.log('INFOR', f'Start requesting subdomains of {domain}')
loop = set_loop()
data = utils.set_id_none(data) data = utils.set_id_none(data)
request_coroutine = bulk_request(data, port) ports = get_port_seq(port)
data = loop.run_until_complete(request_coroutine) filtered_data = utils.get_filtered_data(data)
loop.run_until_complete(asyncio.sleep(0.25)) req_urls = gen_req_urls(data, ports)
resp_list = bulk_request(req_urls)
new_data = gen_new_data(data, resp_list)
data = new_data + filtered_data
count = utils.count_alive(data) count = utils.count_alive(data)
logger.log('INFOR', f'Found that {domain} has {count} alive subdomains') logger.log('INFOR', f'Found that {domain} has {count} alive subdomains')
return data return data
def urls_request(urls):
logger.log('INFOR', 'Start urls request module')
loop = set_loop()
request_coroutine = async_request(urls)
data = loop.run_until_complete(request_coroutine)
loop.run_until_complete(asyncio.sleep(0.25))
return data
def save_db(name, data): def save_db(name, data):
""" """
Save request results to database Save request results to database
+22 -30
View File
@@ -5,14 +5,11 @@ from config.log import logger
from config import settings from config import settings
from common import utils from common import utils
from common.ipasn import IPAsnInfo from common.ipasn import IPAsnInfo
from common.ipgeo import IpGeoInfo from common.ipreg import IpRegData
from common.ipreg import IpRegInfo
ip_asn = IPAsnInfo() ip_asn = IPAsnInfo()
ip_geo = IpGeoInfo() ip_reg = IpRegData()
db_path = settings.data_storage_dir.joinpath('ip2region.db')
ip_reg = IpRegInfo(db_path)
def filter_subdomain(data): def filter_subdomain(data):
@@ -82,12 +79,12 @@ def gen_infos(data, qname, info, infos):
cname = list() cname = list()
ips = list() ips = list()
public = list() public = list()
ttls = list() ttl = list()
cidrs = list() cidr = list()
asns = list() asn = list()
orgs = list() org = list()
locs = list() addr = list()
regs = list() isp = list()
answers = data.get('answers') answers = data.get('answers')
for answer in answers: for answer in answers:
if answer.get('type') == 'A': if answer.get('type') == 'A':
@@ -95,31 +92,26 @@ def gen_infos(data, qname, info, infos):
cname.append(answer.get('name')[:-1]) # 去除最右边的`.`点号 cname.append(answer.get('name')[:-1]) # 去除最右边的`.`点号
ip = answer.get('data') ip = answer.get('data')
ips.append(ip) ips.append(ip)
ttl = answer.get('ttl') ttl.append(str(answer.get('ttl')))
ttls.append(str(ttl)) public.append(str(utils.ip_is_public(ip)))
is_public = utils.ip_is_public(ip)
public.append(str(is_public))
asn_info = ip_asn.find(ip) asn_info = ip_asn.find(ip)
cidrs.append(asn_info.get('cidr')) cidr.append(asn_info.get('cidr'))
asns.append(asn_info.get('asn')) asn.append(asn_info.get('asn'))
orgs.append(asn_info.get('org')) org.append(asn_info.get('org'))
loc = f'{ip_geo.get_country_long(ip)} ' \ ip_info = ip_reg.query(ip)
f'{ip_geo.get_region(ip)} ' \ addr.append(ip_info.get('addr'))
f'{ip_geo.get_city(ip)}' isp.append(ip_info.get('isp'))
locs.append(loc)
reg = ip_reg.memory_search(ip).get('region').decode('utf-8')
regs.append(reg)
info['resolve'] = 1 info['resolve'] = 1
info['reason'] = 'OK' info['reason'] = 'OK'
info['cname'] = ','.join(cname) info['cname'] = ','.join(cname)
info['content'] = ','.join(ips) info['content'] = ','.join(ips)
info['public'] = ','.join(public) info['public'] = ','.join(public)
info['ttl'] = ','.join(ttls) info['ttl'] = ','.join(ttl)
info['cidr'] = ','.join(cidrs) info['cidr'] = ','.join(cidr)
info['asn'] = ','.join(asns) info['asn'] = ','.join(asn)
info['org'] = ','.join(orgs) info['org'] = ','.join(org)
info['ip2location'] = ','.join(locs) info['addr'] = ','.join(addr)
info['ip2region'] = ','.join(regs) info['isp'] = ','.join(isp)
infos[qname] = info infos[qname] = info
if not flag: if not flag:
info['alive'] = 0 info['alive'] = 0
+138
View File
@@ -0,0 +1,138 @@
"""
根据网页结构判断页面相似性(Determine page similarity based on HTML page structure)
判断方法:根据网页的DOM树确定网页的模板特征向量,对模板特征向量计算网页结构相似性。
来源地址:https://github.com/SPuerBRead/HTMLSimilarity
计算参考: https://patents.google.com/patent/CN101694668B/zh
"""
from treelib import Tree
from bs4 import BeautifulSoup
import bs4
class DOMTree(object):
def __init__(self, label, attrs):
self.label = label
self.attrs = attrs
class HTMLParser(object):
def __init__(self, html):
self.dom_id = 1
self.dom_tree = Tree()
self.bs_html = BeautifulSoup(html, 'html.parser')
def get_dom_structure_tree(self):
for content in self.bs_html.contents:
if isinstance(content, bs4.element.Tag):
self.bs_html = content
self.recursive_descendants(self.bs_html, 1)
return self.dom_tree
def recursive_descendants(self, descendants, parent_id):
if self.dom_id == 1:
self.dom_tree.create_node(descendants.name, self.dom_id,
data=DOMTree(descendants.name, descendants.attrs))
self.dom_id = self.dom_id + 1
for child in descendants.contents:
if isinstance(child, bs4.element.Tag):
self.dom_tree.create_node(child.name, self.dom_id, parent_id,
data=DOMTree(child.name, child.attrs))
self.dom_id = self.dom_id + 1
self.recursive_descendants(child, self.dom_id - 1)
class Converter(object):
def __init__(self, dom_tree, dimension):
self.dom_tree = dom_tree
self.node_info_list = []
self.dimension = dimension
self.initial_weight = 1
self.attenuation_ratio = 0.6
self.dom_eigenvector = {}.fromkeys(range(0, dimension), 0)
def get_eigenvector(self):
for node_id in range(1, self.dom_tree.size() + 1):
node = self.dom_tree.get_node(node_id)
node_feature = self.create_feature(node)
feature_hash = self.feature_hash(node_feature)
node_weight = self.calculate_weight(node, node_id, feature_hash)
self.construct_eigenvector(feature_hash, node_weight)
return self.dom_eigenvector
@staticmethod
def create_feature(node):
node_attr_list = []
node_feature = node.data.label + '|'
for attr in node.data.attrs.keys():
node_attr_list.append(attr + ':' + str(node.data.attrs[attr]))
node_feature += '|'.join(node_attr_list)
return node_feature
@staticmethod
def feature_hash(node_feature):
return abs(hash(node_feature)) % (10 ** 8)
def calculate_weight(self, node, node_id, feature_hash):
brother_node_count = 0
depth = self.dom_tree.depth(node)
for brother_node in self.dom_tree.siblings(node_id):
brother_node_feature = self.create_feature(brother_node)
brother_node_feature_hash = self.feature_hash(brother_node_feature)
if brother_node_feature_hash == feature_hash:
brother_node_count = brother_node_count + 1
if brother_node_count:
node_weight = self.initial_weight * self.attenuation_ratio ** depth \
* self.attenuation_ratio ** brother_node_count
else:
node_weight = self.initial_weight * self.attenuation_ratio ** depth
return node_weight
def construct_eigenvector(self, feature_hash, node_weight):
feature_hash = feature_hash % self.dimension
self.dom_eigenvector[feature_hash] += node_weight
def calc_pseudodistance(dom1_eigenvector, dom2_eigenvector, dimension):
a, b = 0, 0
for i in range(dimension):
a += dom1_eigenvector[i]-dom2_eigenvector[i]
if dom1_eigenvector[i] and dom2_eigenvector[i]:
b += dom1_eigenvector[i] + dom2_eigenvector[i]
pseudodistance = abs(a)/b
return pseudodistance
def get_pseudodistance(html_doc1, html_doc2, dimension=5000):
"""
获取html文档结构相似度
:param str html_doc1: html文档
:param str html_doc2: html文档
:param int dimension: 降维后的维数
:return 伪距离
"""
hp1 = HTMLParser(html_doc1)
html_doc1_dom_tree = hp1.get_dom_structure_tree()
hp2 = HTMLParser(html_doc2)
html_doc2_dom_tree = hp2.get_dom_structure_tree()
converter = Converter(html_doc1_dom_tree, dimension)
dom1_eigenvector = converter.get_eigenvector()
converter = Converter(html_doc2_dom_tree, dimension)
dom2_eigenvector = converter.get_eigenvector()
return calc_pseudodistance(dom1_eigenvector, dom2_eigenvector, dimension)
def is_similar(html_doc1, html_doc2, dimension=5000):
"""
根据计算出的伪距离来判断是否html页面结构相似
:param str html_doc1: html文档
:param str html_doc2: html文档
:param int dimension: 降维后的维数
:return 是否相似(伪距离value<0.2时相似,value>0.2时不相似)
"""
value = get_pseudodistance(html_doc1, html_doc2, dimension)
if value > 0.2:
return False
else:
return True
+36 -49
View File
@@ -10,7 +10,6 @@ import subprocess
from ipaddress import IPv4Address, ip_address from ipaddress import IPv4Address, ip_address
from stat import S_IXUSR from stat import S_IXUSR
import psutil
import tenacity import tenacity
import requests import requests
from pathlib import Path from pathlib import Path
@@ -49,10 +48,10 @@ def gen_fake_header():
""" """
Generate fake request headers Generate fake request headers
""" """
headers = settings.headers headers = settings.request_default_headers
if not isinstance(headers, dict): if not isinstance(headers, dict):
headers = dict() headers = dict()
if settings.random_user_agent: if settings.enable_random_ua:
ua = random.choice(user_agents) ua = random.choice(user_agents)
headers['User-Agent'] = ua headers['User-Agent'] = ua
headers['Accept-Encoding'] = 'gzip, deflate' headers['Accept-Encoding'] = 'gzip, deflate'
@@ -74,7 +73,7 @@ def get_random_proxy():
Get random proxy Get random proxy
""" """
try: try:
return random.choice(settings.proxy_pool) return random.choice(settings.request_proxy_pool)
except IndexError: except IndexError:
return None return None
@@ -83,7 +82,7 @@ def get_proxy():
""" """
Get proxy Get proxy
""" """
if settings.enable_proxy: if settings.enable_request_proxy:
return get_random_proxy() return get_random_proxy()
return None return None
@@ -112,26 +111,21 @@ def match_main_domain(domain):
def read_target_file(target): def read_target_file(target):
domains = set() domains = list()
with open(target, encoding='utf-8', errors='ignore') as file: with open(target, encoding='utf-8', errors='ignore') as file:
for line in file: for line in file:
domain = match_main_domain(line) domain = match_main_domain(line)
if not domain: if not domain:
continue continue
domain = get_main_domain(domain) domains.append(domain)
if not domain: sorted_domains = sorted(set(domains), key=domains.index)
continue return sorted_domains
domains.add(domain)
return domains
def get_from_target(target): def get_from_target(target):
domains = set() domains = set()
if isinstance(target, str): if isinstance(target, str):
domain = match_main_domain(target) domain = match_main_domain(target)
if not domain:
return domains
domain = get_main_domain(domain)
if not domain: if not domain:
return domains return domains
domains.add(domain) domains.add(domain)
@@ -507,27 +501,8 @@ def get_process_num():
return 1 return 1
def get_coroutine_count(): def get_request_count():
""" return os.cpu_count() * 10
根据内存大小获取并发数量
"""
mem = psutil.virtual_memory()
total_mem = mem.total
g_size = 1024 * 1024 * 1024
if total_mem < 1 * g_size:
return 16
elif total_mem < 2 * g_size:
return 32
elif total_mem < 4 * g_size:
return 64
elif total_mem < 8 * g_size:
return 128
elif total_mem < 16 * g_size:
return 256
elif total_mem < 32 * g_size:
return 512
else:
return 1024
def uniq_dict_list(dict_list): def uniq_dict_list(dict_list):
@@ -567,7 +542,6 @@ def check_net():
def check_pre(): def check_pre():
logger.log('INFOR', 'Checking dependent environment') logger.log('INFOR', 'Checking dependent environment')
system = platform.system()
implementation = platform.python_implementation() implementation = platform.python_implementation()
version = platform.python_version() version = platform.python_version()
if implementation != 'CPython': if implementation != 'CPython':
@@ -576,16 +550,6 @@ def check_pre():
if version < '3.6': if version < '3.6':
logger.log('FATAL', 'OneForAll requires Python 3.6 or higher') logger.log('FATAL', 'OneForAll requires Python 3.6 or higher')
exit(1) exit(1)
if system == 'Windows' and implementation == 'CPython' and version < '3.8':
logger.log('FATAL', 'OneForAll requires Python 3.8 '
'or higher when running on Windows')
exit(1)
if system in {"Linux", "Darwin"}:
try:
import uvloop
except ImportError:
logger.log('ALERT', f'Please install the uvloop library manually '
f'to accelerate subdomain requests')
def check_env(): def check_env():
@@ -604,8 +568,8 @@ def check_version(local):
api = 'https://api.github.com/repos/shmilylty/OneForAll/releases/latest' api = 'https://api.github.com/repos/shmilylty/OneForAll/releases/latest'
header = get_random_header() header = get_random_header()
proxy = get_proxy() proxy = get_proxy()
timeout = settings.request_timeout timeout = settings.request_timeout_second
verify = settings.request_verify verify = settings.request_ssl_verify
try: try:
resp = requests.get(url=api, headers=header, proxies=proxy, resp = requests.get(url=api, headers=header, proxies=proxy,
timeout=timeout, verify=verify) timeout=timeout, verify=verify)
@@ -613,7 +577,7 @@ def check_version(local):
latest = resp_json['tag_name'] latest = resp_json['tag_name']
except Exception as e: except Exception as e:
logger.log('ERROR', 'An error occurred while checking the latest version') logger.log('ERROR', 'An error occurred while checking the latest version')
logger.log('ERROR', e.args) logger.log('DEBUG', e.args)
return return
if latest > local: if latest > local:
change = resp_json.get("body") change = resp_json.get("body")
@@ -740,3 +704,26 @@ def check_random_subdomain(subdomains):
if subdomain: if subdomain:
logger.log('ALERT', f'Please check whether {subdomain} is correct or not') logger.log('ALERT', f'Please check whether {subdomain} is correct or not')
return True return True
def get_url_resp(url):
timeout = settings.request_timeout
verify = settings.request_verify
try:
resp = requests.get(url, params=None, timeout=timeout, verify=verify)
except Exception as e:
logger.log('DEBUG', f'Error request {url}')
logger.log('DEBUG', e.args)
return None
return resp
def decode_resp_text(resp):
try:
text = resp.text(encoding='utf-8', errors='strict') # 先尝试用utf-8严格解码
except UnicodeError:
try:
text = resp.text(encoding='gb18030', errors='strict') # 再尝试用gb18030严格解码
except UnicodeError:
text = resp.text(encoding=None, errors='ignore') # 最后尝试自动解码
return text
+31 -45
View File
@@ -6,6 +6,9 @@ OneForAll默认配置
import pathlib import pathlib
import warnings import warnings
# 禁用所有警告信息
warnings.filterwarnings("ignore")
# 路径设置 # 路径设置
relative_directory = pathlib.Path(__file__).parent.parent # OneForAll代码相对路径 relative_directory = pathlib.Path(__file__).parent.parent # OneForAll代码相对路径
module_dir = relative_directory.joinpath('modules') # OneForAll模块目录 module_dir = relative_directory.joinpath('modules') # OneForAll模块目录
@@ -40,7 +43,7 @@ enable_partial_module = [] # 启用部分收集模块 必须禁用enable_all_mo
# 只使用ask和baidu搜索引擎收集子域的示例 # 只使用ask和baidu搜索引擎收集子域的示例
# enable_partial_module = [('modules.search', 'ask') # enable_partial_module = [('modules.search', 'ask')
# ('modules.search', 'baidu')] # ('modules.search', 'baidu')]
module_thread_timeout = 120.0 # 每个收集模块线程超时时间(默认2分钟) module_thread_timeout = 90.0 # 每个收集模块线程超时时间(默认90秒)
# 爆破模块设置 # 爆破模块设置
enable_wildcard_check = True # 开启泛解析检测(默认True) enable_wildcard_check = True # 开启泛解析检测(默认True)
@@ -51,7 +54,7 @@ brute_status_format = 'ansi' # 爆破时状态输出格式(默认asni,可
brute_process_num = 1 # 默认1 brute_process_num = 1 # 默认1
brute_concurrent_num = 2000 # 并发查询数量(默认2000,最大推荐10000) brute_concurrent_num = 2000 # 并发查询数量(默认2000,最大推荐10000)
brute_socket_num = 1 # 爆破时每个进程下的socket数量 brute_socket_num = 1 # 爆破时每个进程下的socket数量
brute_resolve_num = 10 # 解析失败时尝试换名称服务器重查次数 brute_resolve_num = 15 # 解析失败时尝试换名称服务器重查次数
# 爆破所使用的字典路径 默认data/subdomains.txt # 爆破所使用的字典路径 默认data/subdomains.txt
brute_wordlist_path = data_storage_dir.joinpath('subnames.txt') brute_wordlist_path = data_storage_dir.joinpath('subnames.txt')
# 爆破所使用的字典路径 默认data/cn_nameservers.txt # 爆破所使用的字典路径 默认data/cn_nameservers.txt
@@ -79,24 +82,39 @@ ip_appear_maximum = 100 # 多个子域解析到同一IP次数超过100次则标
banner_process_number = 4 # 识别进程数量(默认4) banner_process_number = 4 # 识别进程数量(默认4)
# 代理设置 # 代理设置
enable_proxy = False # 是否使用代理(全局开关) enable_request_proxy = False # 是否使用代理(全局开关,默认False)
proxy_all_module = False # 代理所有模块 proxy_all_module = False # 代理所有模块
proxy_partial_module = ['GoogleQuery', 'AskSearch', 'DuckDuckGoSearch', proxy_partial_module = ['GoogleQuery', 'AskSearch', 'DuckDuckGoSearch',
'GoogleAPISearch', 'GoogleSearch', 'YahooSearch', 'GoogleAPISearch', 'GoogleSearch', 'YahooSearch',
'YandexSearch', 'CrossDomainXml', 'YandexSearch', 'CrossDomainXml',
'ContentSecurityPolicy'] # 代理自定义的模块 'ContentSecurityPolicy'] # 代理自定义的模块
proxy_pool = [{'http': 'http://127.0.0.1:1080', request_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': 'socks5h://127.0.0.1:10808', # request_proxy_pool = [{'http': 'socks5h://127.0.0.1:10808',
# 'https': 'socks5h://127.0.0.1:10808'}] # 代理池 # 'https': 'socks5h://127.0.0.1:10808'}] # 代理池
# 网络请求设置 # 请求设置
request_delay = 1 # 请求时延 request_thread_count = None # 请求线程数量(默认None,则根据内存大小设置)
request_timeout = 60 # 请求超时 request_timeout_second = 30 # 请求超时秒数(默认30秒)
request_verify = False # 请求SSL验证 request_ssl_verify = False # 请求SSL验证(默认False)
# 禁用所有警告信息 request_allow_redirect = True # 请求允许重定向(默认True)
warnings.filterwarnings("ignore") request_redirect_limit = 10 # 请求跳转限制(默认10次)
# 默认请求头 可以在headers里添加自定义请求头
request_default_headers = {
'Accept': 'text/html,application/xhtml+xml,'
'application/xml;q=0.9,*/*;q=0.8',
'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7',
'Cache-Control': 'max-age=0',
'DNT': '1',
'Referer': 'https://www.google.com/',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
'(KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36',
'Upgrade-Insecure-Requests': '1',
'X-Forwarded-For': '127.0.0.1'
}
enable_random_ua = True # 使用随机UA(默认True,开启可以覆盖request_default_headers的UA)
# 搜索模块设置 # 搜索模块设置
# 开启全量搜索会尽量去获取搜索引擎搜索的全部结果,不过搜索耗时可能会过长 # 开启全量搜索会尽量去获取搜索引擎搜索的全部结果,不过搜索耗时可能会过长
@@ -112,9 +130,8 @@ resolver_nameservers = [
'8.8.8.8', # Google DNS '8.8.8.8', # Google DNS
'1.1.1.1' # CloudFlare DNS '1.1.1.1' # CloudFlare DNS
] # 指定查询的DNS域名服务器 ] # 指定查询的DNS域名服务器
resolver_timeout = 5.0 # 解析超时时间 resolver_timeout = 5.0 # 解析超时时间(默认5.0秒)
resolver_lifetime = 60.0 # 解析存活时间 resolver_lifetime = 10.0 # 解析存活时间(默认10.0秒)
limit_resolve_conn = 500 # 限制同一时间解析的数量(默认500)
# 请求端口探测设置 # 请求端口探测设置
# 你可以在端口列表添加自定义端口 # 你可以在端口列表添加自定义端口
@@ -140,42 +157,11 @@ large_ports = [80, 81, 280, 300, 443, 591, 593, 832, 888, 901, 981, 1010, 1080,
18092, 20000, 20720, 24465, 28017, 28080, 30821, 43110, 61600] 18092, 20000, 20720, 24465, 28017, 28080, 30821, 43110, 61600]
ports = {'small': small_ports, 'medium': medium_ports, 'large': large_ports} ports = {'small': small_ports, 'medium': medium_ports, 'large': large_ports}
# aiohttp有关配置
verify_ssl = False
# aiohttp 支持 HTTP/HTTPS形式的代理
aiohttp_proxy = None # 示例 proxy="http://user:pass@some.proxy.com"
allow_redirects = True # 允许请求跳转
# 为了保证请求质量 请谨慎更改以下设置
# request_method只能是HEAD或GET,HEAD请求方法更快,但是不能获取响应体并提取从中title及banner
request_method = 'GET' # 使用请求方法,默认GET
sockread_timeout = 6 # 每个请求socket读取超时时间,默认6秒
sockconn_timeout = 3 # 每个请求socket连接超时时间,默认3秒
limit_open_conn = None # 限制同一时间打开的连接总数,默认None将根据系统内存大小自动设置
# 限制同一时间在同一个端点((host, port, is_ssl) 3者都一样的情况)打开的连接数
limit_per_host = 10 # 0表示不限制,默认10
common_subnames = {'i', 'w', 'm', 'en', 'us', 'zh', 'w3', 'app', 'bbs', common_subnames = {'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'}
# 请求头设置
# 可以在headers里添加自定义请求头
headers = {
'Accept': 'text/html,application/xhtml+xml,'
'application/xml;q=0.9,*/*;q=0.8',
'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7',
'Cache-Control': 'max-age=0',
'DNT': '1',
'Referer': 'https://www.google.com/',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
'(KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36',
'Upgrade-Insecure-Requests': '1',
'X-Forwarded-For': '127.0.0.1'
}
random_user_agent = True # 使用随机UA(默认True,开启可以覆盖header的设置)
# 模块API配置 # 模块API配置
# Censys可以免费注册获取API:https://censys.io/api # Censys可以免费注册获取API:https://censys.io/api
censys_api_id = '' censys_api_id = ''
+1 -1
View File
@@ -35,4 +35,4 @@ logger.level(name='FATAL', no=50, color='<RED><bold>', icon='☠️')
# 命令终端日志级别默认为INFOR # 命令终端日志级别默认为INFOR
logger.add(sys.stderr, level='INFOR', format=stdout_fmt, enqueue=True) logger.add(sys.stderr, level='INFOR', format=stdout_fmt, enqueue=True)
# 日志文件默认为级别为DEBUG # 日志文件默认为级别为DEBUG
logger.add(log_path, level='DEBUG', format=logfile_fmt, enqueue=True) logger.add(log_path, level='DEBUG', format=logfile_fmt, enqueue=True, encoding='utf-8')
+3 -24
View File
@@ -35,11 +35,8 @@ enable_partial_module = [] # 启用部分收集模块 必须禁用enable_all_mo
# 只使用ask和baidu搜索引擎收集子域的示例 # 只使用ask和baidu搜索引擎收集子域的示例
# enable_partial_module = [('modules.search', 'ask') # enable_partial_module = [('modules.search', 'ask')
# ('modules.search', 'baidu')] # ('modules.search', 'baidu')]
module_thread_timeout = 120.0 # 每个收集模块线程超时时间(默认2分钟)
# 爆破模块设置 # 爆破模块设置
brute_wildcard_check = True # 开启泛解析检测(默认True)
brute_wildcard_deal = True # 开启泛解析处理(默认True)
brute_concurrent_num = 2000 # 爆破时并发查询数量(默认2000,最大推荐10000) brute_concurrent_num = 2000 # 爆破时并发查询数量(默认2000,最大推荐10000)
# 爆破所使用的字典路径 默认data/subdomains.txt # 爆破所使用的字典路径 默认data/subdomains.txt
brute_wordlist_path = data_storage_dir.joinpath('subnames.txt') brute_wordlist_path = data_storage_dir.joinpath('subnames.txt')
@@ -66,37 +63,19 @@ fuzz_rule = None # fuzz域名的正则 示例:'[a-z][0-9]' 表示第一位是
brute_ip_blacklist = {'0.0.0.0', '0.0.0.1'} # IP黑名单 子域解析到IP黑名单则标记为非法子域 brute_ip_blacklist = {'0.0.0.0', '0.0.0.1'} # IP黑名单 子域解析到IP黑名单则标记为非法子域
ip_appear_maximum = 100 # 多个子域解析到同一IP次数超过100次则标记为非法(泛解析)子域 ip_appear_maximum = 100 # 多个子域解析到同一IP次数超过100次则标记为非法(泛解析)子域
# banner识别模块设置
banner_process_number = 4 # 识别进程数量(默认4)
# 代理设置 # 代理设置
enable_proxy = False # 是否使用代理(全局开关) enable_request_proxy = False # 是否使用代理(全局开关)
proxy_all_module = False # 代理所有模块 proxy_all_module = False # 代理所有模块
proxy_partial_module = ['GoogleQuery', 'AskSearch', 'DuckDuckGoSearch', proxy_partial_module = ['GoogleQuery', 'AskSearch', 'DuckDuckGoSearch',
'GoogleAPISearch', 'GoogleSearch', 'YahooSearch', 'GoogleAPISearch', 'GoogleSearch', 'YahooSearch',
'YandexSearch', 'CrossDomainXml', 'YandexSearch', 'CrossDomainXml',
'ContentSecurityPolicy'] # 代理自定义的模块 'ContentSecurityPolicy'] # 代理自定义的模块
proxy_pool = [{'http': 'http://127.0.0.1:1080', request_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': 'socks5h://127.0.0.1:10808', # request_proxy_pool = [{'http': 'socks5h://127.0.0.1:10808',
# 'https': 'socks5h://127.0.0.1:10808'}] # 代理池 # 'https': 'socks5h://127.0.0.1:10808'}] # 代理池
# 网络请求设置
request_delay = 1 # 请求时延
request_timeout = 60 # 请求超时
request_verify = False # 请求SSL验证
# 搜索模块设置 # 搜索模块设置
enable_recursive_search = False # 递归搜索子域 enable_recursive_search = False # 递归搜索子域
search_recursive_times = 2 # 递归搜索层数 search_recursive_times = 2 # 递归搜索层数
# aiohttp有关配置
# aiohttp 支持 HTTP/HTTPS形式的代理
aiohttp_proxy = None # 示例 proxy="http://user:pass@some.proxy.com"
# 为了保证请求质量 请谨慎更改以下设置
sockread_timeout = 6 # 每个请求socket读取超时时间,默认6秒
sockconn_timeout = 3 # 每个请求socket连接超时时间,默认3秒
limit_open_conn = None # 限制同一时间打开的连接总数,默认None将根据系统内存大小自动设置
# 限制同一时间在同一个端点((host, port, is_ssl) 3者都一样的情况)打开的连接数
limit_per_host = 10 # 0表示不限制,默认10
Binary file not shown.
-2
View File
@@ -29,7 +29,6 @@ def export(target, type='target', db=None, alive=False, limit=None, path=None, f
python3 dbexport.py --target table_name --tb True --show False python3 dbexport.py --target table_name --tb True --show False
Note: Note:
--alive True/False Only export alive subdomains or not (default False)
--format rst/csv/tsv/json/yaml/html/jira/xls/xlsx/dbf/latex/ods (result format) --format rst/csv/tsv/json/yaml/html/jira/xls/xlsx/dbf/latex/ods (result format)
--path Result directory (default directory is ./results) --path Result directory (default directory is ./results)
@@ -82,4 +81,3 @@ def export(target, type='target', db=None, alive=False, limit=None, path=None, f
if __name__ == '__main__': if __name__ == '__main__':
fire.Fire(export) fire.Fire(export)
# export('example.com')
+1 -18
View File
@@ -4,7 +4,7 @@
[![codecov](https://codecov.io/gh/shmilylty/OneForAll/branch/master/graph/badge.svg)](https://codecov.io/gh/shmilylty/OneForAll) [![codecov](https://codecov.io/gh/shmilylty/OneForAll/branch/master/graph/badge.svg)](https://codecov.io/gh/shmilylty/OneForAll)
[![Maintainability](https://api.codeclimate.com/v1/badges/1287668a6b4c72af683e/maintainability)](https://codeclimate.com/github/shmilylty/OneForAll/maintainability) [![Maintainability](https://api.codeclimate.com/v1/badges/1287668a6b4c72af683e/maintainability)](https://codeclimate.com/github/shmilylty/OneForAll/maintainability)
[![License](https://img.shields.io/github/license/shmilylty/OneForAll)](https://github.com/shmilylty/OneForAll/tree/master/LICENSE) [![License](https://img.shields.io/github/license/shmilylty/OneForAll)](https://github.com/shmilylty/OneForAll/tree/master/LICENSE)
[![python](https://img.shields.io/badge/python-3.8-blue)](https://github.com/shmilylty/OneForAll/tree/master/) [![python](https://img.shields.io/badge/python-3.6|3.7|3.8-blue)](https://github.com/shmilylty/OneForAll/tree/master/)
[![python](https://img.shields.io/badge/release-v0.3.0-brightgreen)](https://github.com/shmilylty/OneForAll/releases) [![python](https://img.shields.io/badge/release-v0.3.0-brightgreen)](https://github.com/shmilylty/OneForAll/releases)
👊**OneForAll is a powerful subdomain integration tool** 📝[中文文档](https://github.com/shmilylty/OneForAll/tree/master/README.md) 👊**OneForAll is a powerful subdomain integration tool** 📝[中文文档](https://github.com/shmilylty/OneForAll/tree/master/README.md)
@@ -15,23 +15,6 @@
📢 Please read this document to help you start quickly! 📢 Please read this document to help you start quickly!
<details>
<summary><b>🐍Installation requirements</b></summary>
OneForAll is developed and tested based on [Python 3.8.0](https://www.python.org/downloads/release/python-380/). Recommend use release higher than Python 3.8.0 (Windows platform must use Python 3.8.0 or later). For more information on installing the Python environment, please read [Python 3 installation Guide](https://pythonguidecn.readthedocs.io/zh/latest/starting/installation.html#python-3).
After installation python, run the following command to check the Python and pip3 versions:
```bash
python -V
pip3 -V
```
If you see the following output, there is no problem with the Python environment:
```bash
Python 3.8.0
pip 19.2.2 from C:\Users\shmilylty\AppData\Roaming\Python\Python37\site-packages\pip (python 3.8)
```
</details>
<details> <details>
<summary><b>✔Installation steps (for Git)</b></summary> <summary><b>✔Installation steps (for Git)</b></summary>
+111 -38
View File
@@ -39,7 +39,6 @@ The OneForAll command line interface is based on [Fire](https://github.com/googl
Note: Note:
--alive True/False Only export alive subdomains or not (default False)
--port small/medium/large See details in ./config/setting.py(default small) --port small/medium/large See details in ./config/setting.py(default small)
--format rst/csv/tsv/json/yaml/html/jira/xls/xlsx/dbf/latex/ods (result format) --format rst/csv/tsv/json/yaml/html/jira/xls/xlsx/dbf/latex/ods (result format)
--path Result path (default None, automatically generated) --path Result path (default None, automatically generated)
@@ -87,64 +86,138 @@ The OneForAll command line interface is based on [Fire](https://github.com/googl
```bash ```bash
python aiobrute.py --help python brute.py --help
``` ```
```bash ```bash
NAME NAME
aiobrute.py - OneForAll multi-process multi-correlation asynchronous subdomain blasting module brute.py - OneForAll subdomain brute module
SYNOPSIS SYNOPSIS
aiobrute.py --target=TARGET <flags> brute.py <flags>
DESCRIPTION DESCRIPTION
Example: Example:
python3 aiobrute.py --target example.com run brute.py --target domain.com --word True run
python3 aiobrute.py --target ./domains.txt run brute.py --targets ./domains.txt --word True run
python3 aiobrute.py --target example.com --process 4 --coroutine 64 run brute.py --target domain.com --word True --concurrent 2000 run
python3 aiobrute.py --target example.com --wordlist subdomains.txt run brute.py --target domain.com --word True --wordlist subnames.txt run
python3 aiobrute.py --target example.com --recursive True --depth 2 run brute.py --target domain.com --word True --recursive True --depth 2 run
python3 aiobrute.py --target m.{fuzz}.a.bz --fuzz True --rule [a-z] run brute.py --target d.com --fuzz True --place m.*.d.com --rule '[a-z]' run
brute.py --target d.com --fuzz True --place m.*.d.com --fuzzlist subnames.txt run
Note: Note:
Parameter valid optional value 1, 0, none indicates that the export is --format rst/csv/tsv/json/yaml/html/jira/xls/xlsx/dbf/latex/ods (result format)
valid, invalid, and all subdomains, respectively. --path Result path (default None, automatically generated)
Parameter format have optional values 'txt', 'rst', 'csv', 'tsv', 'json',
'yaml', 'html', 'jira', 'xls', 'xlsx', 'dbf', 'latex', 'ods'.
If the parameter path is None, the appropriate file is generated in the
project result directory based on the format parameter and the domain
name.
ARGUMENTS
TARGET
Single domain name or file path for one domain name per line (required)
FLAGS FLAGS
--target=TARGET
One domain (target or targets must be provided)
--targets=TARGETS
File path of one domain per line
--process=PROCESS --process=PROCESS
Number of processes blasted (default CPU core count) Number of processes (default 1)
--coroutine=COROUTINE --concurrent=CONCURRENT
Number of coroutines per blasting process (default 1024) Number of concurrent (default 2000)
--word=WORD
Use word mode generate dictionary (default False)
--wordlist=WORDLIST --wordlist=WORDLIST
Specify the dictionary path used for blasting (config.py is used by default) Dictionary path used in word mode (default use ./config/default.py)
--recursive=RECURSIVE --recursive=RECURSIVE
Whether to use recursive blasting (default False) Use recursion (default False)
--depth=DEPTH --depth=DEPTH
Depth of recursive blasting (default 2) Recursive depth (default 2)
--namelist=NAMELIST --nextlist=NEXTLIST
Specifies the dictionary path used by recursive blasting (configured by default using config.py) Dictionary file path used by recursive (default use ./config/default.py)
--fuzz=FUZZ --fuzz=FUZZ
Whether to use the fuzz mode for blasting (default False, you must specify the fuzz regular rule) Use fuzz mode generate dictionary (default False)
--place=PLACE
Designated fuzz position (required if use fuzz mode)
--rule=RULE --rule=RULE
Regular rules used by fuzz mode (configured by default using config.py) Specify the regexp rules used in fuzz mode (required if use fuzz mode)
--fuzzlist=FUZZLIST
Dictionary path used in fuzz mode (default use ./config/default.py)
--export=EXPORT --export=EXPORT
Whether to export the blast result (default True) Export the results (default True)
--valid=VALID --alive=ALIVE
Export validity of subdomains (default None) Only export alive subdomains (default False)
--format=FORMAT --format=FORMAT
Export format (default xls) Result format (default csv)
--path=PATH --path=PATH
Export path (default None) Result directory (default None)
```
3. **takeover.py help**
```bash
python takeover.py --help
```
```bash
NAME
takeover.py - OneForAll subdomain takeover module SYNOPSIS takeover.py <flags> DESCRIPTION
Example:
python3 takeover.py --target www.example.com --format csv run
python3 takeover.py --targets ./subdomains.txt --thread 10 run
Note:
--format rst/csv/tsv/json/yaml/html/jira/xls/xlsx/dbf/latex/ods (result format)
--path Result directory (default directory is ./results)
FLAGS
--target=TARGET
One domain (target or targets must be provided)
--targets=TARGETS
File path of one domain per line
--thread=THREAD
threads number (default 20)
--path=PATH
Result directory (default None)
--format=FORMAT
Result format (default csv)
```
4. **dbexport.py help**
```bash
python dbexport.py --help
```
```bash
NAME
dbexport.py - OneForAll export from database module
SYNOPSIS
dbexport.py TARGET <flags>
DESCRIPTION
Example:
python3 dbexport.py --target name --format csv --dir= ./result.csv
python3 dbexport.py --db result.db --target name --show False
python3 dbexport.py --target table_name --tb True --show False
Note:
--format rst/csv/tsv/json/yaml/html/jira/xls/xlsx/dbf/latex/ods (result format)
--path Result directory (default directory is ./results)
POSITIONAL ARGUMENTS
TARGET
Table to be exported
FLAGS
--type=TYPE
Type of target
--db=DB
Database path to be exported (default ./results/result.sqlite3)
--alive=ALIVE
Only export the results of alive subdomains (default False)
--limit=LIMIT
Export limit (default None)
--path=PATH
Result directory (default None)
--format=FORMAT
Result format (default csv)
--show=SHOW --show=SHOW
Terminal display exported data (default False) Displays the exported data in terminal (default False)
``` ```
+5 -5
View File
@@ -84,19 +84,19 @@ DNS解析返回的TTL值
### cidr ### cidr
IP2LOCATION-ASN库查询出的CIDR ip2location库查询出的CIDR
### asn ### asn
IP2LOCATION-ASN库查询出的ASN ip2location库查询出的ASN
### ip2region ### addr
ip2region库查询出的物理地址 ip2region库查询出的物理地址
### ip2location ### isp
ip2location库查询出的物理地址 ip2region库查询出的网络服务提供商
### resolver ### resolver
+34 -40
View File
@@ -38,7 +38,6 @@ OneForAll命令行界面基于[Fire](https://github.com/google/python-fire/)实
python3 oneforall.py --target example.com --show True run python3 oneforall.py --target example.com --show True run
Note: Note:
--alive True/False 只导出存活子域(默认False)
--port small/medium/large 详见./config/setting.py(默认small) --port small/medium/large 详见./config/setting.py(默认small)
--format rst/csv/tsv/json/yaml/html/jira/xls/xlsx/dbf/latex/ods (结果格式,默认CSV) --format rst/csv/tsv/json/yaml/html/jira/xls/xlsx/dbf/latex/ods (结果格式,默认CSV)
--path 结果路径(默认None,自动生成) --path 结果路径(默认None,自动生成)
@@ -90,63 +89,56 @@ OneForAll命令行界面基于[Fire](https://github.com/google/python-fire/)实
```bash ```bash
NAME NAME
aiobrute.py - OneForAll多进程多协程异步子域爆破模块 brute.py - OneForAll子域爆破模块
SYNOPSIS SYNOPSIS
aiobrute.py --target=TARGET <flags> brute.py <flags>
DESCRIPTION DESCRIPTION
Example: Example:
python3 aiobrute.py --target example.com run brute.py --target domain.com --word True run
python3 aiobrute.py --target ./domains.txt run brute.py --targets ./domains.txt --word True run
python3 aiobrute.py --target example.com --process 4 --coroutine 64 run brute.py --target domain.com --word True --coroutine 2000 run
python3 aiobrute.py --target example.com --wordlist subdomains.txt run brute.py --target domain.com --word True --wordlist subnames.txt run
python3 aiobrute.py --target example.com --recursive True --depth 2 run brute.py --target domain.com --word True --recursive True --depth 2 run
python3 aiobrute.py --target m.{fuzz}.a.bz --fuzz True --rule [a-z] run brute.py --target d.com --fuzz True --place m.*.d.com --rule '[a-z]' run
brute.py --target d.com --fuzz True --place m.*.d.com --fuzzlist subnames.txt run
Note: Note:
参数segment的设置受CPU性能,网络带宽,运营商限制等问题影响,默认设置500个子域为任务组, --format rst/csv/tsv/json/yaml/html/jira/xls/xlsx/dbf/latex/ods (结果格式,默认CSV)
当你觉得你的环境不受以上因素影响,当前爆破速度较慢,那么强烈建议根据字典大小调整大小: --path 导出路径(默认None,自动生成)
十万字典建议设置为5000,百万字典设置为50000
参数valid可选值1,0,None,分别表示导出有效,无效,全部子域
参数format可选格式:'csv', 'tsv', 'json', 'yaml', 'html', 'xls', 'xlsx',
'dbf', 'latex', 'ods'
参数path为None会根据format参数和域名名称在项目结果目录生成相应文件
ARGUMENTS
TARGET
单个域名或者每行一个域名的文件路径
FLAGS FLAGS
--target=TARGET
单个域名(必须提供target或targets参数)
--targets=TARGETS
每行一个域名的文件路径
--process=PROCESS --process=PROCESS
爆破的进程数(默认CPU核心数) 爆破的进程数(默认CPU核心数)
--coroutine=COROUTINE --coroutine=COROUTINE
每个爆破进程下的协程数(默认64) 每个爆破进程下的协程数(默认2000)
--wordlist=WORDLIST --wordlist=WORDLIST
指定爆破所使用的字典路径(默认使用config.py配置) 指定爆破所使用的字典路径(默认使用config.py配置)
--segment=SEGMENT
爆破任务分割(默认500)
--recursive=RECURSIVE --recursive=RECURSIVE
是否使用递归爆破(默认False) 是否使用递归爆破(默认False)
--depth=DEPTH --depth=DEPTH
递归爆破的深度(默认2) 递归爆破的深度(默认2)
--namelist=NAMELIST --nextlist=NEXTLIST
指定递归爆破所使用的字典路径(默认使用config.py配置) 指定递归爆破所使用的字典路径(默认使用config.py配置)
--fuzz=FUZZ --fuzz=FUZZ
是否使用fuzz模式进行爆破(默认False,开启须指定fuzz正则规则) 是否使用fuzz模式进行爆破(默认False)
--rule=RULE --rule=RULE
fuzz模式使用的正则规则(默认使用config.py配置) fuzz模式使用的正则规则(默认使用config.py配置)
--fuzzlist=FUZZLIST
指定fuzz模式所使用的字典路径(默认使用config.py配置)
--export=EXPORT --export=EXPORT
是否导出爆破结果(默认True) 是否导出爆破结果(默认True)
--valid=VALID --alive=ALIVE
导出子域的有效性(默认None) 只导出存活子域(默认False)
--format=FORMAT --format=FORMAT
导出格式(默认xlsx) 导出格式(默认csv)
--path=PATH --path=PATH
导出路径(默认None) 导出路径(默认None)
--show=SHOW
终端显示导出数据(默认False)
``` ```
@@ -200,24 +192,26 @@ OneForAll命令行界面基于[Fire](https://github.com/google/python-fire/)实
dbexport.py - OneForAll数据库导出模块 dbexport.py - OneForAll数据库导出模块
SYNOPSIS SYNOPSIS
dbexport.py TABLE <flags> dbexport.py TARGET <flags>
DESCRIPTION DESCRIPTION
Example: Example:
python3 dbexport.py --table name --format csv --path= ./result.csv python3 dbexport.py --target name --format csv --dir= ./result.csv
python3 dbexport.py --db result.db --table name --show False python3 dbexport.py --db result.db --target name --show False
python3 dbexport.py --target table_name --tb True --show False
Note: Note:
参数port可选值有'small', 'medium', 'large', 'xlarge',详见config.py配置 --type target/table 要导出的目标类型(默认target)
参数format可选格式有'csv', 'tsv', 'json', 'yaml', 'html', 'xls', 'xlsx', --format rst/csv/tsv/json/yaml/html/jira/xls/xlsx/dbf/latex/ods (结果格式,默认CSV)
'dbf', 'latex', 'ods' --path 结果路径(默认None,自动生成)
参数path为None会根据format参数和域名名称在项目结果目录生成相应文件
POSITIONAL ARGUMENTS POSITIONAL ARGUMENTS
TABLE TARGET
要导出的表 要导出的目标类型
FLAGS FLAGS
--type=TYPE
要导出的目标类型(默认target)
--db=DB --db=DB
要导出的数据库路径(默认为results/result.sqlite3) 要导出的数据库路径(默认为results/result.sqlite3)
--valid=VALID --valid=VALID
Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

+6 -10
View File
@@ -11,19 +11,15 @@ import dns.resolver
import dns.zone import dns.zone
from common import utils from common import utils
from common.module import Module from common.check import Check
from config.log import logger from config.log import logger
class CheckAXFR(Module): class AXFR(Check):
""" def __init__(self, domain):
DNS zone transfer vulnerability base class Check.__init__(self)
"""
def __init__(self, domain: str):
Module.__init__(self)
self.domain = domain self.domain = domain
self.module = 'Check' self.module = 'check'
self.source = 'AXFRCheck' self.source = 'AXFRCheck'
self.results = [] self.results = []
@@ -92,7 +88,7 @@ def run(domain):
:param str domain: 域名 :param str domain: 域名
""" """
check = CheckAXFR(domain) check = AXFR(domain)
check.run() check.run()
+9 -20
View File
@@ -1,33 +1,22 @@
""" """
检查crossdomain.xml文件收集子域名 检查crossdomain.xml文件收集子域名
""" """
from common.check import Check
from common.module import Module
class CheckCDX(Module): class CrossDomain(Check):
""" def __init__(self, domain):
检查crossdomain.xml文件收集子域名 Check.__init__(self)
"""
def __init__(self, domain: str):
Module.__init__(self)
self.domain = domain self.domain = domain
self.module = 'Check' self.module = 'check'
self.source = "CrossDomainXml" self.source = "CrossDomainCheck"
def check(self): def check(self):
""" """
检查crossdomain.xml收集子域名 检查crossdomain.xml收集子域名
""" """
urls = [f'http://{self.domain}/crossdomain.xml', filenames = {'crossdomain.xml'}
f'https://{self.domain}/crossdomain.xml', self.to_check(filenames)
f'http://www.{self.domain}/crossdomain.xml',
f'https://www.{self.domain}/crossdomain.xml']
for url in urls:
self.header = self.get_header()
self.proxy = self.get_proxy(self.source)
resp = self.get(url, check=False)
self.subdomains = self.collect_subdomains(resp)
def run(self): def run(self):
""" """
@@ -47,7 +36,7 @@ def run(domain):
:param domain: 域名 :param domain: 域名
""" """
check = CheckCDX(domain) check = CrossDomain(domain)
check.run() check.run()
+10 -12
View File
@@ -1,21 +1,18 @@
#!/usr/bin/env python3
""" """
检查域名证书收集子域名 检查域名证书收集子域名
""" """
import socket import socket
import ssl import ssl
from common.module import Module
from config.log import logger from config.log import logger
from common.check import Check
class CheckCert(Module): class CertInfo(Check):
def __init__(self, domain): def __init__(self, domain):
Module.__init__(self) Check.__init__(self)
self.domain = domain self.domain = domain
self.port = 443 # ssl port self.module = 'check'
self.module = 'Check'
self.source = 'CertInfo' self.source = 'CertInfo'
def check(self): def check(self):
@@ -24,10 +21,11 @@ class CheckCert(Module):
""" """
try: try:
ctx = ssl.create_default_context() ctx = ssl.create_default_context()
sock = ctx.wrap_socket(socket.socket(), sock = socket.socket()
server_hostname=self.domain) sock.settimeout(10)
sock.connect((self.domain, self.port)) wrap_sock = ctx.wrap_socket(sock, server_hostname=self.domain)
cert_dict = sock.getpeercert() wrap_sock.connect((self.domain, 443))
cert_dict = wrap_sock.getpeercert()
except Exception as e: except Exception as e:
logger.log('DEBUG', e.args) logger.log('DEBUG', e.args)
return return
@@ -52,7 +50,7 @@ def run(domain):
:param str domain: 域名 :param str domain: 域名
""" """
check = CheckCert(domain) check = CertInfo(domain)
check.run() check.run()
+7 -8
View File
@@ -3,19 +3,19 @@ Collect subdomains from ContentSecurityPolicy
""" """
import requests import requests
from common.module import Module
from config.log import logger from config.log import logger
from common.check import Check
class CheckCSP(Module): class CSP(Check):
""" """
Collect subdomains from ContentSecurityPolicy Collect subdomains from ContentSecurityPolicy
""" """
def __init__(self, domain, header): def __init__(self, domain, header):
Module.__init__(self) Check.__init__(self)
self.domain = domain self.domain = domain
self.module = 'Check' self.module = 'check'
self.source = 'ContentSecurityPolicy' self.source = 'CSPCheck'
self.csp_header = header self.csp_header = header
def grab_header(self): def grab_header(self):
@@ -34,8 +34,7 @@ class CheckCSP(Module):
self.proxy = self.get_proxy(self.source) self.proxy = self.get_proxy(self.source)
response = self.get(url, check=False) response = self.get(url, check=False)
if response: if response:
csp_header = response.headers return response.headers
break
return csp_header return csp_header
def check(self): def check(self):
@@ -73,7 +72,7 @@ def run(domain, header=None):
:param str domain: 域名 :param str domain: 域名
:param dict or None header: 响应头 :param dict or None header: 响应头
""" """
check = CheckCSP(domain, header) check = CSP(domain, header)
check.run() check.run()
+5 -5
View File
@@ -1,16 +1,16 @@
# https://www.icann.org/resources/pages/dnssec-what-is-it-why-important-2019-03-20-zh # https://www.icann.org/resources/pages/dnssec-what-is-it-why-important-2019-03-20-zh
# https://appsecco.com/books/subdomain-enumeration/active_techniques/zone_walking.html # https://appsecco.com/books/subdomain-enumeration/active_techniques/zone_walking.html
from common.module import Module
from common import utils from common import utils
from common.check import Check
class CheckNSEC(Module): class NSEC(Check):
def __init__(self, domain): def __init__(self, domain):
Module.__init__(self) Check.__init__(self)
self.domain = domain self.domain = domain
self.module = 'check' self.module = 'check'
self.source = "CheckNSEC" self.source = "NSECCheck"
def walk(self): def walk(self):
domain = self.domain domain = self.domain
@@ -47,7 +47,7 @@ def run(domain):
:param str domain: 域名 :param str domain: 域名
""" """
brute = CheckNSEC(domain) brute = NSEC(domain)
brute.run() brute.run()
+8 -18
View File
@@ -1,32 +1,22 @@
""" """
检查内容安全策略收集子域名收集子域名 检查内容安全策略收集子域名收集子域名
""" """
from common.module import Module from common.check import Check
class CheckRobots(Module): class Robots(Check):
"""
检查robots.txt收集子域名
"""
def __init__(self, domain): def __init__(self, domain):
Module.__init__(self) Check.__init__(self)
self.domain = domain self.domain = domain
self.module = 'Check' self.module = 'check'
self.source = 'Robots' self.source = 'RobotsCheck'
def check(self): def check(self):
""" """
正则匹配域名的robots.txt文件中的子域 正则匹配域名的robots.txt文件中的子域
""" """
urls = [f'http://{self.domain}/robots.txt', filenames = {'robots.txt'}
f'https://{self.domain}/robots.txt', self.to_check(filenames)
f'http://www.{self.domain}/robots.txt',
f'https://www.{self.domain}/robots.txt']
for url in urls:
self.header = self.get_header()
self.proxy = self.get_proxy(self.source)
resp = self.get(url, check=False, allow_redirects=False)
self.subdomains = self.collect_subdomains(resp)
def run(self): def run(self):
""" """
@@ -46,7 +36,7 @@ def run(domain):
:param str domain: 域名 :param str domain: 域名
""" """
check = CheckRobots(domain) check = Robots(domain)
check.run() check.run()
+8 -23
View File
@@ -1,37 +1,22 @@
""" """
检查内容安全策略收集子域名收集子域名 检查内容安全策略收集子域名收集子域名
""" """
from common.module import Module from common.check import Check
class CheckRobots(Module): class Sitemap(Check):
"""
检查sitemap收集子域名
"""
def __init__(self, domain): def __init__(self, domain):
Module.__init__(self) Check.__init__(self)
self.domain = domain self.domain = domain
self.module = 'Check' self.module = 'check'
self.source = 'Sitemap' self.source = 'SitemapCheck'
def check(self): def check(self):
""" """
正则匹配域名的sitemap文件中的子域 正则匹配域名的sitemap文件中的子域
""" """
urls = [f'http://{self.domain}/sitemap.xml', filenames = {'sitemap.xml', 'sitemap.txt', 'sitemap.html', 'sitemapindex.xml'}
f'http://www.{self.domain}/sitemap.xml', self.to_check(filenames)
f'http://{self.domain}/sitemap.txt',
f'http://www.{self.domain}/sitemap.txt',
f'http://{self.domain}/sitemap.html',
f'http://www.{self.domain}/sitemap.html',
f'http://{self.domain}/sitemap_index.xml',
f'http://www.{self.domain}/sitemap_index.xml']
for url in urls:
self.header = self.get_header()
self.proxy = self.get_proxy(self.source)
self.timeout = 10
resp = self.get(url, check=False)
self.subdomains = self.collect_subdomains(resp)
def run(self): def run(self):
""" """
@@ -51,7 +36,7 @@ def run(domain):
:param str domain: 域名 :param str domain: 域名
""" """
check = CheckRobots(domain) check = Sitemap(domain)
check.run() check.run()
+3 -28
View File
@@ -1,38 +1,24 @@
import time
import threading import threading
import importlib import importlib
import dbexport
from config.log import logger from config.log import logger
from config import settings from config import settings
from common import utils
class Collect(object): class Collect(object):
""" def __init__(self, domain):
Collect subdomains self.domain = domain
"""
def __init__(self, domain, export=True):
self.domain = utils.get_main_domain(domain)
self.elapse = 0.0
self.modules = [] self.modules = []
self.collect_funcs = [] self.collect_funcs = []
self.path = None
self.export = export
self.format = 'csv'
def get_mod(self): def get_mod(self):
""" """
Get modules Get modules
""" """
if settings.enable_all_module: if settings.enable_all_module:
# modules = ['brute', 'certificates', 'crawl',
# 'datasets', 'intelligence', 'search']
# The crawl module has some problems # The crawl module has some problems
modules = ['certificates', 'check', 'datasets', modules = ['certificates', 'check', 'datasets',
'dnsquery', 'intelligence', 'search'] 'dnsquery', 'intelligence', 'search']
# modules = ['certificates']
for module in modules: for module in modules:
module_path = settings.module_dir.joinpath(module) module_path = settings.module_dir.joinpath(module)
for path in module_path.rglob('*.py'): for path in module_path.rglob('*.py'):
@@ -55,7 +41,6 @@ class Collect(object):
""" """
Class entrance Class entrance
""" """
start = time.time()
logger.log('INFOR', f'Start collecting subdomains of {self.domain}') logger.log('INFOR', f'Start collecting subdomains of {self.domain}')
self.get_mod() self.get_mod()
self.import_func() self.import_func()
@@ -66,8 +51,7 @@ class Collect(object):
func_obj, func_name = collect_func func_obj, func_name = collect_func
thread = threading.Thread(target=func_obj, thread = threading.Thread(target=func_obj,
name=func_name, name=func_name,
args=(self.domain,), args=(self.domain,))
daemon=True)
threads.append(thread) threads.append(thread)
# Start all threads # Start all threads
for thread in threads: for thread in threads:
@@ -82,15 +66,6 @@ class Collect(object):
if thread.is_alive(): if thread.is_alive():
logger.log('ALERT', f'{thread.name} module thread timed out') logger.log('ALERT', f'{thread.name} module thread timed out')
# Export
if self.export:
if not self.path:
name = f'{self.domain}.{self.format}'
self.path = settings.result_save_dir.joinpath(name)
dbexport.export(self.domain, type='table', path=self.path, format=self.format)
end = time.time()
self.elapse = round(end - start, 1)
if __name__ == '__main__': if __name__ == '__main__':
collect = Collect('example.com') collect = Collect('example.com')
-95
View File
@@ -1,95 +0,0 @@
"""
通过枚举域名常见的SRV记录并做查询来发现子域
"""
import json
import queue
import threading
from common import utils
from common.module import Module
from config.setting import data_storage_dir
class BruteSRV(Module):
def __init__(self, domain):
Module.__init__(self)
self.domain = domain
self.module = 'dnsquery'
self.source = "BruteSRV"
self.qtype = 'SRV' # 利用的DNS记录的SRV记录查询子域
self.thread_num = 10
self.names_que = queue.Queue()
self.answers_que = queue.Queue()
def gen_names(self):
path = data_storage_dir.joinpath('srv_prefixes.json')
with open(path, encoding='utf-8', errors='ignore') as file:
prefixes = json.load(file)
names = map(lambda prefix: prefix + self.domain, prefixes)
for name in names:
self.names_que.put(name)
def brute(self):
"""
枚举域名的SRV记录
"""
self.gen_names()
for num in range(self.thread_num):
thread = BruteThread(self.names_que, self.answers_que)
thread.name = f'BruteThread-{num}'
thread.daemon = True
thread.start()
self.names_que.join()
while not self.answers_que.empty():
answer = self.answers_que.get()
if answer is None:
continue
for item in answer:
record = str(item)
subdomains = self.match_subdomains(record)
self.subdomains.update(subdomains)
def run(self):
"""
类执行入口
"""
self.begin()
self.brute()
self.finish()
self.save_json()
self.gen_result()
self.save_db()
class BruteThread(threading.Thread):
def __init__(self, names_que, answers_que):
threading.Thread.__init__(self)
self.names_que = names_que
self.answers_que = answers_que
def run(self):
while True:
name = self.names_que.get()
answer = utils.dns_query(name, 'SRV')
self.answers_que.put(answer)
self.names_que.task_done()
def run(domain):
"""
类统一调用入口
:param str domain: 域名
"""
brute = BruteSRV(domain)
brute.run()
if __name__ == '__main__':
run('zonetransfer.me')
# run('example.com')
+4 -3
View File
@@ -190,9 +190,10 @@ def find_subdomains(domain, data):
subdomains.update(find_in_resp(domain, req_url, rsp_html)) subdomains.update(find_in_resp(domain, req_url, rsp_html))
js_urls.update(find_js_urls(domain, req_url, rsp_html)) js_urls.update(find_js_urls(domain, req_url, rsp_html))
resp_data = request.urls_request(js_urls) resp_data = request.bulk_request(js_urls)
for resp, text in resp_data: for resp in resp_data:
if not text: if not resp:
continue continue
text = utils.decode_resp_text(resp)
subdomains.update(find_in_resp(domain, resp.url, text)) subdomains.update(find_in_resp(domain, resp.url, text))
return subdomains return subdomains
+75
View File
@@ -0,0 +1,75 @@
"""
通过枚举域名常见的SRV记录并做查询来发现子域
"""
import queue
import threading
from common import utils
from common.module import Module
from config.setting import data_storage_dir
class BruteSRV(Module):
def __init__(self, domain):
Module.__init__(self)
self.domain = domain
self.module = 'BruteSRV'
self.source = "BruteSRV"
self.qtype = 'SRV'
self.thread_num = 20
self.names_queue = queue.Queue()
self.answers_queue = queue.Queue()
def fill_queue(self):
path = data_storage_dir.joinpath('srv_prefixes.json')
prefixes = utils.load_json(path)
for prefix in prefixes:
self.names_queue.put(prefix + self.domain)
def do_brute(self):
for num in range(self.thread_num):
thread = BruteThread(self.names_queue, self.answers_queue)
thread.name = f'BruteThread-{num}'
thread.daemon = True
thread.start()
self.names_queue.join()
def deal_answers(self):
while not self.answers_queue.empty():
answer = self.answers_queue.get()
if answer is None:
continue
for item in answer:
record = str(item)
subdomains = self.match_subdomains(record)
self.subdomains.update(subdomains)
def run(self):
self.begin()
self.fill_queue()
self.do_brute()
self.deal_answers()
self.finish()
self.save_json()
self.gen_result()
self.save_db()
class BruteThread(threading.Thread):
def __init__(self, names_queue, answers_queue):
threading.Thread.__init__(self)
self.names_queue = names_queue
self.answers_queue = answers_queue
def run(self):
while True:
name = self.names_queue.get()
answer = utils.dns_query(name, 'SRV')
self.answers_queue.put(answer)
self.names_queue.task_done()
if __name__ == '__main__':
brute = BruteSRV('zonetransfer.me')
brute.run()
+7 -3
View File
@@ -17,6 +17,7 @@ from brute import Brute
from common import utils, resolve, request from common import utils, resolve, request
from common.database import Database from common.database import Database
from modules.collect import Collect from modules.collect import Collect
from modules.srv import BruteSRV
from modules.finder import Finder from modules.finder import Finder
from modules import iscdn from modules import iscdn
from config import settings from config import settings
@@ -65,7 +66,6 @@ class OneForAll(object):
python3 oneforall.py --target example.com --show True run python3 oneforall.py --target example.com --show True run
Note: Note:
--alive True/False Only export alive subdomains or not (default False)
--port small/medium/large See details in ./config/setting.py(default small) --port small/medium/large See details in ./config/setting.py(default small)
--format rst/csv/tsv/json/yaml/html/jira/xls/xlsx/dbf/latex/ods (result format) --format rst/csv/tsv/json/yaml/html/jira/xls/xlsx/dbf/latex/ods (result format)
--path Result path (default None, automatically generated) --path Result path (default None, automatically generated)
@@ -188,8 +188,12 @@ class OneForAll(object):
self.origin_table = self.domain + '_origin_result' self.origin_table = self.domain + '_origin_result'
self.resolve_table = self.domain + '_resolve_result' self.resolve_table = self.domain + '_resolve_result'
collect = Collect(self.domain, export=False) collect = Collect(self.domain)
collect.run() collect.run()
srv = BruteSRV(self.domain)
srv.run()
if self.brute: if self.brute:
# Due to there will be a large number of dns resolution requests, # Due to there will be a large number of dns resolution requests,
# may cause other network tasks to be error # may cause other network tasks to be error
@@ -240,7 +244,7 @@ class OneForAll(object):
# Scan subdomain takeover # Scan subdomain takeover
if self.takeover: if self.takeover:
subdomains = utils.get_subdomains(self.data) subdomains = utils.get_subdomains(self.data)
takeover = Takeover(subdomains) takeover = Takeover(targets=subdomains)
takeover.run() takeover.run()
return self.data return self.data
+8 -13
View File
@@ -1,24 +1,19 @@
aiohttp==3.6.2
async-timeout==3.0.1
attrs==19.3.0
beautifulsoup4==4.9.1 beautifulsoup4==4.9.1
bs4==0.0.1 bs4==0.0.1
certifi==2020.6.20 certifi==2020.6.20
chardet==3.0.4 chardet==3.0.4
cloudscraper==1.2.42 cloudscraper==1.2.46
colorama==0.4.3 colorama==0.4.3
dnspython==1.16.0 dnspython==2.0.0
docopt==0.6.2 docopt==0.6.2
et-xmlfile==1.0.1 et-xmlfile==1.0.1
exrex==0.10.5 exrex==0.10.5
fire==0.3.1 fire==0.3.1
future==0.18.2
idna==2.10 idna==2.10
IP2Location==8.4.1
jdcal==1.4.1 jdcal==1.4.1
loguru==0.5.1 loguru==0.5.1
multidict==4.7.6
openpyxl==2.4.11 openpyxl==2.4.11
psutil==5.7.0
pyparsing==2.4.7 pyparsing==2.4.7
PySocks==1.7.1 PySocks==1.7.1
records==0.5.3 records==0.5.3
@@ -27,12 +22,12 @@ requests-file==1.5.1
requests-toolbelt==0.9.1 requests-toolbelt==0.9.1
six==1.15.0 six==1.15.0
soupsieve==2.0.1 soupsieve==2.0.1
SQLAlchemy==1.3.18 SQLAlchemy==1.3.19
tablib==2.0.0 tablib==2.0.0
tenacity==6.2.0 tenacity==6.2.0
termcolor==1.1.0 termcolor==1.1.0
tldextract==2.2.2 tldextract==2.2.3
tqdm==4.47.0 tqdm==4.48.2
urllib3==1.25.9 treelib==1.6.1
urllib3==1.25.10
win32-setctime==1.0.1 win32-setctime==1.0.1
yarl==1.4.2
+12 -14
View File
@@ -20,7 +20,6 @@ from config.log import logger
from config import settings from config import settings
from common import utils from common import utils
from common.module import Module from common.module import Module
from common.domain import Domain
def get_fingerprint(): def get_fingerprint():
@@ -41,34 +40,32 @@ def get_cname(subdomain):
return answer.to_text() # 一个子域只有一个CNAME记录 return answer.to_text() # 一个子域只有一个CNAME记录
def get_maindomain(subdomain):
return Domain(subdomain).registered()
class Takeover(Module): class Takeover(Module):
""" """
OneForAll subdomain takeover module OneForAll subdomain takeover module
Example: Example:
python3 takeover.py --target www.example.com --format csv run python3 takeover.py --target www.example.com --format csv run
python3 takeover.py --target ./subdomains.txt --thread 10 run python3 takeover.py --targets ./subdomains.txt --thread 10 run
Note: Note:
--format rst/csv/tsv/json/yaml/html/jira/xls/xlsx/dbf/latex/ods (result format) --format rst/csv/tsv/json/yaml/html/jira/xls/xlsx/dbf/latex/ods (result format)
--path Result directory (default directory is ./results) --path Result directory (default directory is ./results)
:param any target: One domain or File path of one domain per line (required) :param str target: One domain (target or targets must be provided)
:param int thread: threads number (default 100) :param str targets: File path of one domain per line
:param int thread: threads number (default 20)
:param str format: Result format (default csv) :param str format: Result format (default csv)
:param str path: Result directory (default None) :param str path: Result directory (default None)
""" """
def __init__(self, target, thread=100, path=None, format='csv'): def __init__(self, target=None, targets=None, thread=20, path=None, format='csv'):
Module.__init__(self) Module.__init__(self)
self.subdomains = set() self.subdomains = set()
self.module = 'Check' self.module = 'Check'
self.source = 'Takeover' self.source = 'Takeover'
self.target = target self.target = target
self.targets = targets
self.thread = thread self.thread = thread
self.path = path self.path = path
self.format = format self.format = format
@@ -101,10 +98,10 @@ class Takeover(Module):
cname = get_cname(subdomain) cname = get_cname(subdomain)
if cname is None: if cname is None:
return return
maindomain = get_maindomain(cname) main_domain = utils.get_main_domain(cname)
for fingerprint in self.fingerprints: for fingerprint in self.fingerprints:
cnames = fingerprint.get('cname') cnames = fingerprint.get('cname')
if maindomain not in cnames: if main_domain not in cnames:
continue continue
responses = fingerprint.get('response') responses = fingerprint.get('response')
self.compare(subdomain, cname, responses) self.compare(subdomain, cname, responses)
@@ -116,7 +113,6 @@ class Takeover(Module):
self.subdomainq.task_done() self.subdomainq.task_done()
def progress(self): def progress(self):
# 设置进度
bar = tqdm() bar = tqdm()
bar.total = len(self.subdomains) bar.total = len(self.subdomains)
bar.desc = 'Check Progress' bar.desc = 'Check Progress'
@@ -127,12 +123,14 @@ class Takeover(Module):
bar.update() bar.update()
if done == bar.total: # 完成队列中所有子域的检查退出 if done == bar.total: # 完成队列中所有子域的检查退出
break break
# bar.close()
def run(self): def run(self):
start = time.time() start = time.time()
logger.log('INFOR', f'Start running {self.source} module') logger.log('INFOR', f'Start running {self.source} module')
self.subdomains = utils.get_domains(self.target) if isinstance(self.targets, set):
self.subdomains = self.targets
else:
self.subdomains = utils.get_domains(self.target, self.targets)
self.format = utils.check_format(self.format, len(self.subdomains)) self.format = utils.check_format(self.format, len(self.subdomains))
timestamp = utils.get_timestamp() timestamp = utils.get_timestamp()
name = f'takeover_check_result_{timestamp}' name = f'takeover_check_result_{timestamp}'
+1 -1
View File
@@ -8,7 +8,7 @@ Example
from oneforall import OneForAll from oneforall import OneForAll
if __name__ == '__main__': if __name__ == '__main__':
test = OneForAll(target='hackfun.org') test = OneForAll(target='mi.com')
test.brute = True test.brute = True
test.req = True test.req = True
test.takeover = True test.takeover = True