oneforall-kjl 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- OneForAll/__init__.py +15 -0
- OneForAll/brute.py +503 -0
- OneForAll/common/check.py +41 -0
- OneForAll/common/crawl.py +10 -0
- OneForAll/common/database.py +277 -0
- OneForAll/common/domain.py +63 -0
- OneForAll/common/ipasn.py +42 -0
- OneForAll/common/ipreg.py +139 -0
- OneForAll/common/lookup.py +28 -0
- OneForAll/common/module.py +369 -0
- OneForAll/common/query.py +9 -0
- OneForAll/common/records.py +363 -0
- OneForAll/common/request.py +264 -0
- OneForAll/common/resolve.py +173 -0
- OneForAll/common/search.py +78 -0
- OneForAll/common/similarity.py +138 -0
- OneForAll/common/tablib/__init__.py +0 -0
- OneForAll/common/tablib/format.py +89 -0
- OneForAll/common/tablib/tablib.py +360 -0
- OneForAll/common/tldextract.py +240 -0
- OneForAll/common/utils.py +789 -0
- OneForAll/config/__init__.py +17 -0
- OneForAll/config/api.py +94 -0
- OneForAll/config/default.py +255 -0
- OneForAll/config/log.py +38 -0
- OneForAll/config/setting.py +108 -0
- OneForAll/export.py +72 -0
- OneForAll/modules/altdns.py +216 -0
- OneForAll/modules/autotake/github.py +105 -0
- OneForAll/modules/certificates/censys_api.py +73 -0
- OneForAll/modules/certificates/certspotter.py +48 -0
- OneForAll/modules/certificates/crtsh.py +84 -0
- OneForAll/modules/certificates/google.py +48 -0
- OneForAll/modules/certificates/myssl.py +46 -0
- OneForAll/modules/certificates/racent.py +49 -0
- OneForAll/modules/check/axfr.py +97 -0
- OneForAll/modules/check/cdx.py +44 -0
- OneForAll/modules/check/cert.py +58 -0
- OneForAll/modules/check/csp.py +94 -0
- OneForAll/modules/check/nsec.py +58 -0
- OneForAll/modules/check/robots.py +44 -0
- OneForAll/modules/check/sitemap.py +44 -0
- OneForAll/modules/collect.py +70 -0
- OneForAll/modules/crawl/archivecrawl.py +59 -0
- OneForAll/modules/crawl/commoncrawl.py +59 -0
- OneForAll/modules/datasets/anubis.py +45 -0
- OneForAll/modules/datasets/bevigil.py +50 -0
- OneForAll/modules/datasets/binaryedge_api.py +50 -0
- OneForAll/modules/datasets/cebaidu.py +45 -0
- OneForAll/modules/datasets/chinaz.py +45 -0
- OneForAll/modules/datasets/chinaz_api.py +49 -0
- OneForAll/modules/datasets/circl_api.py +49 -0
- OneForAll/modules/datasets/cloudflare_api.py +130 -0
- OneForAll/modules/datasets/dnsdb_api.py +51 -0
- OneForAll/modules/datasets/dnsdumpster.py +52 -0
- OneForAll/modules/datasets/dnsgrep.py +44 -0
- OneForAll/modules/datasets/fullhunt.py +48 -0
- OneForAll/modules/datasets/hackertarget.py +45 -0
- OneForAll/modules/datasets/ip138.py +45 -0
- OneForAll/modules/datasets/ipv4info_api.py +73 -0
- OneForAll/modules/datasets/netcraft.py +66 -0
- OneForAll/modules/datasets/passivedns_api.py +51 -0
- OneForAll/modules/datasets/qianxun.py +61 -0
- OneForAll/modules/datasets/rapiddns.py +45 -0
- OneForAll/modules/datasets/riddler.py +45 -0
- OneForAll/modules/datasets/robtex.py +58 -0
- OneForAll/modules/datasets/securitytrails_api.py +56 -0
- OneForAll/modules/datasets/sitedossier.py +57 -0
- OneForAll/modules/datasets/spyse_api.py +62 -0
- OneForAll/modules/datasets/sublist3r.py +45 -0
- OneForAll/modules/datasets/urlscan.py +45 -0
- OneForAll/modules/datasets/windvane.py +92 -0
- OneForAll/modules/dnsquery/mx.py +35 -0
- OneForAll/modules/dnsquery/ns.py +35 -0
- OneForAll/modules/dnsquery/soa.py +35 -0
- OneForAll/modules/dnsquery/spf.py +35 -0
- OneForAll/modules/dnsquery/txt.py +35 -0
- OneForAll/modules/enrich.py +72 -0
- OneForAll/modules/finder.py +206 -0
- OneForAll/modules/intelligence/alienvault.py +50 -0
- OneForAll/modules/intelligence/riskiq_api.py +58 -0
- OneForAll/modules/intelligence/threatbook_api.py +50 -0
- OneForAll/modules/intelligence/threatminer.py +45 -0
- OneForAll/modules/intelligence/virustotal.py +60 -0
- OneForAll/modules/intelligence/virustotal_api.py +59 -0
- OneForAll/modules/iscdn.py +86 -0
- OneForAll/modules/search/ask.py +69 -0
- OneForAll/modules/search/baidu.py +96 -0
- OneForAll/modules/search/bing.py +79 -0
- OneForAll/modules/search/bing_api.py +78 -0
- OneForAll/modules/search/fofa_api.py +74 -0
- OneForAll/modules/search/gitee.py +71 -0
- OneForAll/modules/search/github_api.py +86 -0
- OneForAll/modules/search/google.py +83 -0
- OneForAll/modules/search/google_api.py +77 -0
- OneForAll/modules/search/hunter_api.py +72 -0
- OneForAll/modules/search/quake_api.py +72 -0
- OneForAll/modules/search/shodan_api.py +53 -0
- OneForAll/modules/search/so.py +75 -0
- OneForAll/modules/search/sogou.py +72 -0
- OneForAll/modules/search/wzsearch.py +68 -0
- OneForAll/modules/search/yahoo.py +81 -0
- OneForAll/modules/search/yandex.py +80 -0
- OneForAll/modules/search/zoomeye_api.py +73 -0
- OneForAll/modules/srv.py +75 -0
- OneForAll/modules/wildcard.py +319 -0
- OneForAll/oneforall.py +275 -0
- OneForAll/takeover.py +168 -0
- OneForAll/test.py +23 -0
- oneforall_kjl-0.1.1.dist-info/METADATA +18 -0
- oneforall_kjl-0.1.1.dist-info/RECORD +114 -0
- oneforall_kjl-0.1.1.dist-info/WHEEL +5 -0
- oneforall_kjl-0.1.1.dist-info/entry_points.txt +2 -0
- oneforall_kjl-0.1.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,363 @@
|
|
1
|
+
import os
|
2
|
+
from collections import OrderedDict
|
3
|
+
from inspect import isclass
|
4
|
+
|
5
|
+
from sqlalchemy import create_engine, exc, inspect, text
|
6
|
+
|
7
|
+
from .tablib import tablib
|
8
|
+
|
9
|
+
DATABASE_URL = os.environ.get('DATABASE_URL')
|
10
|
+
|
11
|
+
|
12
|
+
def is_exception(obj):
|
13
|
+
"""Given an object, return a boolean indicating whether it is an instance
|
14
|
+
or subclass of :py:class:`Exception`.
|
15
|
+
"""
|
16
|
+
if isinstance(obj, Exception):
|
17
|
+
return True
|
18
|
+
if isclass(obj) and issubclass(obj, Exception):
|
19
|
+
return True
|
20
|
+
return False
|
21
|
+
|
22
|
+
|
23
|
+
class Record(object):
|
24
|
+
"""A row, from a query, from a database."""
|
25
|
+
__slots__ = ('_keys', '_values')
|
26
|
+
|
27
|
+
def __init__(self, keys, values):
|
28
|
+
self._keys = keys
|
29
|
+
self._values = values
|
30
|
+
|
31
|
+
# Ensure that lengths match properly.
|
32
|
+
assert len(self._keys) == len(self._values)
|
33
|
+
|
34
|
+
def keys(self):
|
35
|
+
"""Returns the list of column names from the query."""
|
36
|
+
return self._keys
|
37
|
+
|
38
|
+
def values(self):
|
39
|
+
"""Returns the list of values from the query."""
|
40
|
+
return self._values
|
41
|
+
|
42
|
+
def __repr__(self):
|
43
|
+
return '<Record {}>'.format(self.export('json')[1:-1])
|
44
|
+
|
45
|
+
def __getitem__(self, key):
|
46
|
+
# Support for index-based lookup.
|
47
|
+
if isinstance(key, int):
|
48
|
+
return self.values()[key]
|
49
|
+
|
50
|
+
# Support for string-based lookup.
|
51
|
+
if key in self.keys():
|
52
|
+
i = self.keys().index(key)
|
53
|
+
if self.keys().count(key) > 1:
|
54
|
+
raise KeyError("Record contains multiple '{}' fields.".format(key))
|
55
|
+
return self.values()[i]
|
56
|
+
|
57
|
+
raise KeyError("Record contains no '{}' field.".format(key))
|
58
|
+
|
59
|
+
def __getattr__(self, key):
|
60
|
+
try:
|
61
|
+
return self[key]
|
62
|
+
except KeyError as e:
|
63
|
+
raise AttributeError(e)
|
64
|
+
|
65
|
+
def __dir__(self):
|
66
|
+
standard = dir(super(Record, self))
|
67
|
+
# Merge standard attrs with generated ones (from column names).
|
68
|
+
return sorted(standard + [str(k) for k in self.keys()])
|
69
|
+
|
70
|
+
def get(self, key, default=None):
|
71
|
+
"""Returns the value for a given key, or default."""
|
72
|
+
try:
|
73
|
+
return self[key]
|
74
|
+
except KeyError:
|
75
|
+
return default
|
76
|
+
|
77
|
+
def as_dict(self, ordered=False):
|
78
|
+
"""Returns the row as a dictionary, as ordered."""
|
79
|
+
items = zip(self.keys(), self.values())
|
80
|
+
|
81
|
+
return OrderedDict(items) if ordered else dict(items)
|
82
|
+
|
83
|
+
@property
|
84
|
+
def dataset(self):
|
85
|
+
"""A Tablib Dataset containing the row."""
|
86
|
+
data = tablib.Dataset()
|
87
|
+
data.headers = self.keys()
|
88
|
+
|
89
|
+
row = _reduce_datetimes(self.values())
|
90
|
+
data.append(row)
|
91
|
+
|
92
|
+
return data
|
93
|
+
|
94
|
+
def export(self, format, **kwargs):
|
95
|
+
"""Exports the row to the given format."""
|
96
|
+
return self.dataset.export(format, **kwargs)
|
97
|
+
|
98
|
+
|
99
|
+
class RecordCollection(object):
|
100
|
+
"""A set of excellent Records from a query."""
|
101
|
+
|
102
|
+
def __init__(self, rows):
|
103
|
+
self._rows = rows
|
104
|
+
self._all_rows = []
|
105
|
+
self.pending = True
|
106
|
+
|
107
|
+
def __repr__(self):
|
108
|
+
return '<RecordCollection size={} pending={}>'.format(len(self), self.pending)
|
109
|
+
|
110
|
+
def __iter__(self):
|
111
|
+
"""Iterate over all rows, consuming the underlying generator
|
112
|
+
only when necessary."""
|
113
|
+
i = 0
|
114
|
+
while True:
|
115
|
+
# Other code may have iterated between yields,
|
116
|
+
# so always check the cache.
|
117
|
+
if i < len(self):
|
118
|
+
yield self[i]
|
119
|
+
else:
|
120
|
+
# Throws StopIteration when done.
|
121
|
+
# Prevent StopIteration bubbling from generator,
|
122
|
+
# following https://www.python.org/dev/peps/pep-0479/
|
123
|
+
try:
|
124
|
+
yield next(self)
|
125
|
+
except StopIteration:
|
126
|
+
return
|
127
|
+
i += 1
|
128
|
+
|
129
|
+
def next(self):
|
130
|
+
return self.__next__()
|
131
|
+
|
132
|
+
def __next__(self):
|
133
|
+
try:
|
134
|
+
nextrow = next(self._rows)
|
135
|
+
self._all_rows.append(nextrow)
|
136
|
+
return nextrow
|
137
|
+
except StopIteration:
|
138
|
+
self.pending = False
|
139
|
+
raise StopIteration('RecordCollection contains no more rows.')
|
140
|
+
|
141
|
+
def __getitem__(self, key):
|
142
|
+
is_int = isinstance(key, int)
|
143
|
+
|
144
|
+
# Convert RecordCollection[1] into slice.
|
145
|
+
if is_int:
|
146
|
+
key = slice(key, key + 1)
|
147
|
+
|
148
|
+
while len(self) < key.stop or key.stop is None:
|
149
|
+
try:
|
150
|
+
next(self)
|
151
|
+
except StopIteration:
|
152
|
+
break
|
153
|
+
|
154
|
+
rows = self._all_rows[key]
|
155
|
+
if is_int:
|
156
|
+
return rows[0]
|
157
|
+
else:
|
158
|
+
return RecordCollection(iter(rows))
|
159
|
+
|
160
|
+
def __len__(self):
|
161
|
+
return len(self._all_rows)
|
162
|
+
|
163
|
+
def export(self, format, **kwargs):
|
164
|
+
"""Export the RecordCollection to a given format (courtesy of Tablib)."""
|
165
|
+
return self.dataset.export(format, **kwargs)
|
166
|
+
|
167
|
+
@property
|
168
|
+
def dataset(self):
|
169
|
+
"""A Tablib Dataset representation of the RecordCollection."""
|
170
|
+
# Create a new Tablib Dataset.
|
171
|
+
data = tablib.Dataset()
|
172
|
+
|
173
|
+
# If the RecordCollection is empty, just return the empty set
|
174
|
+
# Check number of rows by typecasting to list
|
175
|
+
if len(list(self)) == 0:
|
176
|
+
return data
|
177
|
+
|
178
|
+
# Set the column names as headers on Tablib Dataset.
|
179
|
+
first = self[0]
|
180
|
+
|
181
|
+
data.headers = first.keys()
|
182
|
+
for row in self.all():
|
183
|
+
row = _reduce_datetimes(row.values())
|
184
|
+
data.append(row)
|
185
|
+
|
186
|
+
return data
|
187
|
+
|
188
|
+
def all(self, as_dict=False, as_ordereddict=False):
|
189
|
+
"""Returns a list of all rows for the RecordCollection. If they haven't
|
190
|
+
been fetched yet, consume the iterator and cache the results."""
|
191
|
+
|
192
|
+
# By calling list it calls the __iter__ method
|
193
|
+
rows = list(self)
|
194
|
+
|
195
|
+
if as_dict:
|
196
|
+
return [r.as_dict() for r in rows]
|
197
|
+
elif as_ordereddict:
|
198
|
+
return [r.as_dict(ordered=True) for r in rows]
|
199
|
+
|
200
|
+
return rows
|
201
|
+
|
202
|
+
def as_dict(self, ordered=False):
|
203
|
+
return self.all(as_dict=not (ordered), as_ordereddict=ordered)
|
204
|
+
|
205
|
+
def first(self, default=None, as_dict=False, as_ordereddict=False):
|
206
|
+
"""Returns a single record for the RecordCollection, or `default`. If
|
207
|
+
`default` is an instance or subclass of Exception, then raise it
|
208
|
+
instead of returning it."""
|
209
|
+
|
210
|
+
# Try to get a record, or return/raise default.
|
211
|
+
try:
|
212
|
+
record = self[0]
|
213
|
+
except IndexError:
|
214
|
+
if is_exception(default):
|
215
|
+
raise default
|
216
|
+
return default
|
217
|
+
|
218
|
+
# Cast and return.
|
219
|
+
if as_dict:
|
220
|
+
return record.as_dict()
|
221
|
+
elif as_ordereddict:
|
222
|
+
return record.as_dict(ordered=True)
|
223
|
+
else:
|
224
|
+
return record
|
225
|
+
|
226
|
+
def one(self, default=None, as_dict=False, as_ordereddict=False):
|
227
|
+
"""Returns a single record for the RecordCollection, ensuring that it
|
228
|
+
is the only record, or returns `default`. If `default` is an instance
|
229
|
+
or subclass of Exception, then raise it instead of returning it."""
|
230
|
+
|
231
|
+
# Ensure that we don't have more than one row.
|
232
|
+
try:
|
233
|
+
return self[1]
|
234
|
+
except IndexError:
|
235
|
+
return self.first(default=default, as_dict=as_dict,
|
236
|
+
as_ordereddict=as_ordereddict)
|
237
|
+
else:
|
238
|
+
raise ValueError('RecordCollection contained more than one row. '
|
239
|
+
'Expects only one row when using '
|
240
|
+
'RecordCollection.one')
|
241
|
+
|
242
|
+
def scalar(self, default=None):
|
243
|
+
"""Returns the first column of the first row, or `default`."""
|
244
|
+
row = self.one()
|
245
|
+
return row[0] if row else default
|
246
|
+
|
247
|
+
|
248
|
+
class Database(object):
|
249
|
+
"""A Database. Encapsulates a url and an SQLAlchemy engine with a pool of
|
250
|
+
connections.
|
251
|
+
"""
|
252
|
+
|
253
|
+
def __init__(self, db_url=None, **kwargs):
|
254
|
+
# If no db_url was provided, fallback to $DATABASE_URL.
|
255
|
+
self.db_url = db_url or DATABASE_URL
|
256
|
+
|
257
|
+
if not self.db_url:
|
258
|
+
raise ValueError('You must provide a db_url.')
|
259
|
+
|
260
|
+
# Create an engine.
|
261
|
+
self._engine = create_engine(self.db_url, **kwargs)
|
262
|
+
self.open = True
|
263
|
+
|
264
|
+
def close(self):
|
265
|
+
"""Closes the Database."""
|
266
|
+
self._engine.dispose()
|
267
|
+
self.open = False
|
268
|
+
|
269
|
+
def __enter__(self):
|
270
|
+
return self
|
271
|
+
|
272
|
+
def __exit__(self, exc, val, traceback):
|
273
|
+
self.close()
|
274
|
+
|
275
|
+
def __repr__(self):
|
276
|
+
return '<Database open={}>'.format(self.open)
|
277
|
+
|
278
|
+
def get_table_names(self):
|
279
|
+
"""Returns a list of table names for the connected database."""
|
280
|
+
|
281
|
+
# Setup SQLAlchemy for Database inspection.
|
282
|
+
return inspect(self._engine).get_table_names()
|
283
|
+
|
284
|
+
def get_connection(self):
|
285
|
+
"""Get a connection to this Database. Connections are retrieved from a
|
286
|
+
pool.
|
287
|
+
"""
|
288
|
+
if not self.open:
|
289
|
+
raise exc.ResourceClosedError('Database closed.')
|
290
|
+
|
291
|
+
return Connection(self._engine.connect())
|
292
|
+
|
293
|
+
def query(self, query, fetchall=False, **params):
|
294
|
+
"""Executes the given SQL query against the Database. Parameters can,
|
295
|
+
optionally, be provided. Returns a RecordCollection, which can be
|
296
|
+
iterated over to get result rows as dictionaries.
|
297
|
+
"""
|
298
|
+
with self.get_connection() as conn:
|
299
|
+
return conn.query(query, fetchall, **params)
|
300
|
+
|
301
|
+
def bulk_query(self, query, *multiparams):
|
302
|
+
"""Bulk insert or update."""
|
303
|
+
|
304
|
+
with self.get_connection() as conn:
|
305
|
+
conn.bulk_query(query, *multiparams)
|
306
|
+
|
307
|
+
|
308
|
+
class Connection(object):
|
309
|
+
"""A Database connection."""
|
310
|
+
|
311
|
+
def __init__(self, connection):
|
312
|
+
self._conn = connection
|
313
|
+
self.open = not connection.closed
|
314
|
+
|
315
|
+
def close(self):
|
316
|
+
self._conn.close()
|
317
|
+
self.open = False
|
318
|
+
|
319
|
+
def __enter__(self):
|
320
|
+
return self
|
321
|
+
|
322
|
+
def __exit__(self, exc, val, traceback):
|
323
|
+
self.close()
|
324
|
+
|
325
|
+
def __repr__(self):
|
326
|
+
return '<Connection open={}>'.format(self.open)
|
327
|
+
|
328
|
+
def query(self, query, fetchall=False, **params):
|
329
|
+
"""Executes the given SQL query against the connected Database.
|
330
|
+
Parameters can, optionally, be provided. Returns a RecordCollection,
|
331
|
+
which can be iterated over to get result rows as dictionaries.
|
332
|
+
"""
|
333
|
+
|
334
|
+
# Execute the given query.
|
335
|
+
cursor = self._conn.execute(text(query), **params) # TODO: PARAMS GO HERE
|
336
|
+
|
337
|
+
# Row-by-row Record generator.
|
338
|
+
row_gen = (Record(cursor.keys(), row) for row in cursor)
|
339
|
+
|
340
|
+
# Convert psycopg2 results to RecordCollection.
|
341
|
+
results = RecordCollection(row_gen)
|
342
|
+
|
343
|
+
# Fetch all results if desired.
|
344
|
+
if fetchall:
|
345
|
+
results.all()
|
346
|
+
|
347
|
+
return results
|
348
|
+
|
349
|
+
def bulk_query(self, query, *multiparams):
|
350
|
+
"""Bulk insert or update."""
|
351
|
+
|
352
|
+
self._conn.execute(text(query), *multiparams)
|
353
|
+
|
354
|
+
|
355
|
+
def _reduce_datetimes(row):
|
356
|
+
"""Receives a row, converts datetimes to strings."""
|
357
|
+
|
358
|
+
row = list(row)
|
359
|
+
|
360
|
+
for i in range(len(row)):
|
361
|
+
if hasattr(row[i], 'isoformat'):
|
362
|
+
row[i] = row[i].isoformat()
|
363
|
+
return tuple(row)
|
@@ -0,0 +1,264 @@
|
|
1
|
+
import json
|
2
|
+
from threading import Thread
|
3
|
+
from queue import Queue
|
4
|
+
|
5
|
+
import tqdm
|
6
|
+
import requests
|
7
|
+
from bs4 import BeautifulSoup
|
8
|
+
|
9
|
+
from common import utils
|
10
|
+
from config.log import logger
|
11
|
+
from common.database import Database
|
12
|
+
from config import settings
|
13
|
+
|
14
|
+
|
15
|
+
def req_thread_count():
|
16
|
+
count = settings.request_thread_count
|
17
|
+
if isinstance(count, int):
|
18
|
+
count = max(16, count)
|
19
|
+
else:
|
20
|
+
count = utils.get_request_count()
|
21
|
+
logger.log('DEBUG', f'Number of request threads {count}')
|
22
|
+
return count
|
23
|
+
|
24
|
+
|
25
|
+
def get_port_seq(port):
|
26
|
+
logger.log('DEBUG', 'Getting port range')
|
27
|
+
ports = set()
|
28
|
+
if isinstance(port, (set, list, tuple)):
|
29
|
+
ports = port
|
30
|
+
elif isinstance(port, int):
|
31
|
+
if 0 <= port <= 65535:
|
32
|
+
ports = {port}
|
33
|
+
elif port in {'small', 'medium', 'large'}:
|
34
|
+
logger.log('DEBUG', f'{port} port range')
|
35
|
+
ports = settings.ports.get(port)
|
36
|
+
if not ports: # 意外情况
|
37
|
+
logger.log('ERROR', 'The specified request port range is incorrect')
|
38
|
+
ports = {80}
|
39
|
+
logger.log('INFOR', f'Port range:{ports}')
|
40
|
+
return set(ports)
|
41
|
+
|
42
|
+
|
43
|
+
def gen_req_url(domain, port):
|
44
|
+
if str(port).endswith('443'):
|
45
|
+
url = f'https://{domain}:{port}'
|
46
|
+
if port == 443:
|
47
|
+
url = f'https://{domain}'
|
48
|
+
return url
|
49
|
+
url = f'http://{domain}:{port}'
|
50
|
+
if port == 80:
|
51
|
+
url = f'http://{domain}'
|
52
|
+
return url
|
53
|
+
|
54
|
+
|
55
|
+
def gen_req_data(data, ports):
|
56
|
+
logger.log('INFOR', 'Generating request urls')
|
57
|
+
req_data = list()
|
58
|
+
req_urls = set()
|
59
|
+
for info in data:
|
60
|
+
resolve = info.get('resolve')
|
61
|
+
# 解析不成功的子域不进行http请求探测
|
62
|
+
if resolve != 1:
|
63
|
+
continue
|
64
|
+
subdomain = info.get('subdomain')
|
65
|
+
for port in ports:
|
66
|
+
tmp_info = info.copy()
|
67
|
+
tmp_info['port'] = port
|
68
|
+
url = gen_req_url(subdomain, port)
|
69
|
+
tmp_info['url'] = url
|
70
|
+
req_data.append(tmp_info)
|
71
|
+
req_urls.add(url)
|
72
|
+
return req_data, req_urls
|
73
|
+
|
74
|
+
|
75
|
+
def get_html_title(markup):
|
76
|
+
"""
|
77
|
+
获取标题
|
78
|
+
|
79
|
+
:param markup: html标签
|
80
|
+
:return: 标题
|
81
|
+
"""
|
82
|
+
soup = BeautifulSoup(markup, 'html.parser')
|
83
|
+
|
84
|
+
title = soup.title
|
85
|
+
if title:
|
86
|
+
return title.text
|
87
|
+
|
88
|
+
h1 = soup.h1
|
89
|
+
if h1:
|
90
|
+
return h1.text
|
91
|
+
|
92
|
+
h2 = soup.h2
|
93
|
+
if h2:
|
94
|
+
return h2.text
|
95
|
+
|
96
|
+
h3 = soup.h3
|
97
|
+
if h3:
|
98
|
+
return h3.text
|
99
|
+
|
100
|
+
desc = soup.find('meta', attrs={'name': 'description'})
|
101
|
+
if desc:
|
102
|
+
return desc['content']
|
103
|
+
|
104
|
+
word = soup.find('meta', attrs={'name': 'keywords'})
|
105
|
+
if word:
|
106
|
+
return word['content']
|
107
|
+
|
108
|
+
text = soup.text
|
109
|
+
if len(text) <= 200:
|
110
|
+
return repr(text)
|
111
|
+
|
112
|
+
return 'None'
|
113
|
+
|
114
|
+
|
115
|
+
def get_jump_urls(history):
|
116
|
+
urls = list()
|
117
|
+
for resp in history:
|
118
|
+
urls.append(str(resp.url))
|
119
|
+
return urls
|
120
|
+
|
121
|
+
|
122
|
+
def get_progress_bar(total):
|
123
|
+
bar = tqdm.tqdm()
|
124
|
+
bar.total = total
|
125
|
+
bar.desc = 'Request Progress'
|
126
|
+
bar.ncols = 80
|
127
|
+
return bar
|
128
|
+
|
129
|
+
|
130
|
+
def get_resp(url, session):
|
131
|
+
timeout = settings.request_timeout_second
|
132
|
+
redirect = settings.request_allow_redirect
|
133
|
+
proxy = utils.get_proxy()
|
134
|
+
try:
|
135
|
+
resp = session.get(url, timeout=timeout, allow_redirects=redirect, proxies=proxy)
|
136
|
+
except Exception as e:
|
137
|
+
logger.log('DEBUG', e.args)
|
138
|
+
resp = e
|
139
|
+
return resp
|
140
|
+
|
141
|
+
|
142
|
+
def request(urls_queue, resp_queue, session):
|
143
|
+
while not urls_queue.empty():
|
144
|
+
index, url = urls_queue.get()
|
145
|
+
resp = get_resp(url, session)
|
146
|
+
resp_queue.put((index, resp))
|
147
|
+
urls_queue.task_done()
|
148
|
+
|
149
|
+
|
150
|
+
def progress(bar, total, urls_queue):
|
151
|
+
while True:
|
152
|
+
remaining = urls_queue.qsize()
|
153
|
+
done = total - remaining
|
154
|
+
bar.n = done
|
155
|
+
bar.update()
|
156
|
+
if remaining == 0:
|
157
|
+
break
|
158
|
+
|
159
|
+
|
160
|
+
def get_session():
|
161
|
+
header = utils.gen_fake_header()
|
162
|
+
verify = settings.request_ssl_verify
|
163
|
+
redirect_limit = settings.request_redirect_limit
|
164
|
+
session = requests.Session()
|
165
|
+
session.trust_env = False
|
166
|
+
session.headers = header
|
167
|
+
session.verify = verify
|
168
|
+
session.max_redirects = redirect_limit
|
169
|
+
return session
|
170
|
+
|
171
|
+
|
172
|
+
def gen_new_info(info, resp):
|
173
|
+
if isinstance(resp, Exception):
|
174
|
+
info['reason'] = str(resp.args)
|
175
|
+
info['request'] = 0
|
176
|
+
info['alive'] = 0
|
177
|
+
return info
|
178
|
+
info['reason'] = resp.reason
|
179
|
+
code = resp.status_code
|
180
|
+
info['status'] = code
|
181
|
+
info['request'] = 1
|
182
|
+
if code == 400 or code >= 500:
|
183
|
+
info['alive'] = 0
|
184
|
+
else:
|
185
|
+
info['alive'] = 1
|
186
|
+
headers = resp.headers
|
187
|
+
if settings.enable_banner_identify:
|
188
|
+
info['banner'] = utils.get_sample_banner(headers)
|
189
|
+
info['header'] = json.dumps(dict(headers))
|
190
|
+
history = resp.history
|
191
|
+
info['history'] = json.dumps(get_jump_urls(history))
|
192
|
+
text = utils.decode_resp_text(resp)
|
193
|
+
title = get_html_title(text).strip()
|
194
|
+
info['title'] = utils.remove_invalid_string(title)
|
195
|
+
info['response'] = utils.remove_invalid_string(text)
|
196
|
+
return info
|
197
|
+
|
198
|
+
|
199
|
+
def save(name, total, req_data, resp_queue):
|
200
|
+
db = Database()
|
201
|
+
db.create_table(name)
|
202
|
+
i = 0
|
203
|
+
while True:
|
204
|
+
if not resp_queue.empty():
|
205
|
+
i += 1
|
206
|
+
index, resp = resp_queue.get()
|
207
|
+
old_info = req_data[index]
|
208
|
+
new_info = gen_new_info(old_info, resp)
|
209
|
+
db.insert_table(name, new_info)
|
210
|
+
resp_queue.task_done()
|
211
|
+
if i >= total: # 得存入完所有请求结果才能结束
|
212
|
+
break
|
213
|
+
db.close()
|
214
|
+
|
215
|
+
|
216
|
+
def bulk_request(domain, req_data, ret=False):
|
217
|
+
logger.log('INFOR', 'Requesting urls in bulk')
|
218
|
+
resp_queue = Queue()
|
219
|
+
urls_queue = Queue()
|
220
|
+
task_count = len(req_data)
|
221
|
+
for index, info in enumerate(req_data):
|
222
|
+
url = info.get('url')
|
223
|
+
urls_queue.put((index, url))
|
224
|
+
session = get_session()
|
225
|
+
thread_count = req_thread_count()
|
226
|
+
if task_count <= thread_count:
|
227
|
+
# 如果请求任务数很小不用创建很多线程了
|
228
|
+
thread_count = task_count
|
229
|
+
bar = get_progress_bar(task_count)
|
230
|
+
|
231
|
+
progress_thread = Thread(target=progress, name='ProgressThread',
|
232
|
+
args=(bar, task_count, urls_queue), daemon=True)
|
233
|
+
progress_thread.start()
|
234
|
+
|
235
|
+
for i in range(thread_count):
|
236
|
+
request_thread = Thread(target=request, name=f'RequestThread-{i}',
|
237
|
+
args=(urls_queue, resp_queue, session), daemon=True)
|
238
|
+
request_thread.start()
|
239
|
+
if ret:
|
240
|
+
urls_queue.join()
|
241
|
+
return resp_queue
|
242
|
+
save_thread = Thread(target=save, name=f'SaveThread',
|
243
|
+
args=(domain, task_count, req_data, resp_queue), daemon=True)
|
244
|
+
save_thread.start()
|
245
|
+
urls_queue.join()
|
246
|
+
save_thread.join()
|
247
|
+
|
248
|
+
|
249
|
+
def run_request(domain, data, port):
|
250
|
+
"""
|
251
|
+
HTTP request entrance
|
252
|
+
|
253
|
+
:param str domain: domain to be requested
|
254
|
+
:param list data: subdomains data to be requested
|
255
|
+
:param any port: range of ports to be requested
|
256
|
+
:return list: result
|
257
|
+
"""
|
258
|
+
logger.log('INFOR', f'Start requesting subdomains of {domain}')
|
259
|
+
data = utils.set_id_none(data)
|
260
|
+
ports = get_port_seq(port)
|
261
|
+
req_data, req_urls = gen_req_data(data, ports)
|
262
|
+
bulk_request(domain, req_data)
|
263
|
+
count = utils.count_alive(domain)
|
264
|
+
logger.log('INFOR', f'Found that {domain} has {count} alive subdomains')
|