diff options
Diffstat (limited to 'checker/src/checker.py')
| -rw-r--r-- | checker/src/checker.py | 440 |
1 files changed, 205 insertions, 235 deletions
diff --git a/checker/src/checker.py b/checker/src/checker.py index 54820ee..54e0e46 100644 --- a/checker/src/checker.py +++ b/checker/src/checker.py @@ -42,6 +42,7 @@ extra_models = [] for path in os.listdir(f"{script_path}/models"): if path.endswith(".stl"): extra_models.append(f"{script_path}/models/{path}") +assert len(extra_models) > 0 wordlist = [w for w in open(f"{script_path}/wordlist.txt").read().split() if w != ""] prompt = b"\r$ " @@ -63,11 +64,11 @@ app = lambda: checker.app async def timed(promise: Any, logger: LoggerAdapter, ctx: str) -> Any: - logger.debug("Started: {}".format(ctx)) + logger.debug("START: {}".format(ctx)) start = time.time() result = await promise end = time.time() - logger.debug("Done: {} (took {:.3f} seconds)".format(ctx, end - start)) + logger.debug("DONE: {} (took {:.3f} seconds)".format(ctx, end - start)) return result @@ -80,16 +81,21 @@ class Session: self.closed = False async def __aenter__(self) -> "Session": - await timed(self.prepare(), self.logger, ctx="preparing session") + self.logger.debug("Preparing session") + await self.prepare() return self async def __aexit__(self, *args: list[Any], **kwargs: dict[str, Any]) -> None: - await timed(self.close(), self.logger, ctx="closing session") + self.logger.debug("Closing session") + await self.close() async def readuntil(self, target: bytes, ctx: Optional[str] = None) -> bytes: try: ctxstr = f"readuntil {target!r}" if ctx is None else ctx - return await timed(self.reader.readuntil(target), self.logger, ctx=ctxstr) + data = await timed(self.reader.readuntil(target), self.logger, ctx=ctxstr) + msg = f"read: {data[:100]!r}{'..' if len(data) > 100 else ''}" + self.logger.debug(msg) + return data except TimeoutError: self.logger.critical(f"Service timed out while waiting for {target!r}") raise MumbleException("Service took too long to respond") @@ -97,10 +103,13 @@ class Session: async def readline(self, ctx: Optional[str] = None) -> bytes: return await self.readuntil(b"\n", ctx=ctx) - async def read(self, n: int, ctx: Optional[str]) -> bytes: + async def read(self, n: int, ctx: Optional[str] = None) -> bytes: try: ctxstr = f"reading {n} bytes" if ctx is None else ctx - return await timed(self.reader.readexactly(n), self.logger, ctx=ctxstr) + data = await timed(self.reader.readexactly(n), self.logger, ctx=ctxstr) + msg = f"read: {data[:60]!r}{'..' if len(data) > 60 else ''}" + self.logger.debug(msg) + return data except TimeoutError: self.logger.critical(f"Service timed out while reading {n} bytes") raise MumbleException("Service took too long to respond") @@ -109,10 +118,8 @@ class Session: await self.writer.drain() def write(self, data: bytes) -> None: - if len(data) > 100: - self.logger.debug(f"Sending {data[:100]!r}..") - else: - self.logger.debug(f"Sending {data!r}") + msg = f"write: {data[:60]!r}{'..' if len(data) > 60 else ''}" + self.logger.debug(msg) self.writer.write(data) async def prepare(self) -> None: @@ -153,6 +160,16 @@ def includes_any(resp: bytes, targets: list[bytes]) -> bool: return False +def nnt(ino: Optional[tuple[bytes, bytes]]) -> tuple[bytes, bytes]: + assert ino is not None + return ino + + +def nnb(ino: Optional[bytes]) -> bytes: + assert ino is not None + return ino + + def randbool() -> bool: return rand.randint(0, 1) == 1 @@ -236,6 +253,9 @@ def assert_match(data: bytes, pattern: bytes, raiser: Any) -> bytes: def genfile_ascii(solidname: bytes, malformed: bool = None) -> bytes: + # Generate a valid ascii STL file or one that is malformed in a + # way that it can't be interpreted by the service, but might succeed + # with typical stl parsing libraries (as a means to identify them) indent = bytes([rand.choice(b"\t ") for i in range(rand.randint(1, 4))]) facet_count = rand.randint(4, 30) @@ -245,9 +265,9 @@ def genfile_ascii(solidname: bytes, malformed: bool = None) -> bytes: content = b"solid\n" for fi in range(facet_count): - # MALFORM 1: wrong keyword + # MALFORM 1: prefix keyword if malformed == 1: - content += indent * 1 + b"facet nornal " + content += indent * 1 + b"facet norm" else: content += indent * 1 + b"facet normal " @@ -257,9 +277,9 @@ def genfile_ascii(solidname: bytes, malformed: bool = None) -> bytes: content += " ".join([f"{v:.2f}" for v in norm]).encode() + b"\n" - # MALFORM 2: wrong keyword case + # MALFORM 2: too many spaces if malformed == 2: - content += indent * 2 + b"outer lOop\n" + content += indent * 2 + b"outer loop\n" else: content += indent * 2 + b"outer loop\n" @@ -274,17 +294,21 @@ def genfile_ascii(solidname: bytes, malformed: bool = None) -> bytes: content += indent * 2 + b"endloop\n" content += indent + b"endfacet\n" - # MALFORM 3: no endsolid keyword - if malformed != 3: - if solidname != b"": - content += b"endsolid " + solidname + b"\n" - else: - content += b"endsolid\n" + # MALFORM 3: different endsolid name + if malformed == 3: + content += b"endsolid end\n" + if solidname != b"": + content += b"endsolid " + solidname + b"\n" + else: + content += b"endsolid\n" return content def genfile_bin(solidname: bytes, malformed: bool = None) -> bytes: + # Generate a valid binary STL file or one that is malformed in a + # way that it can't be interpreted by the service, but might succeed + # with typical stl parsing libraries (as a means to identify them) facet_count = rand.randint(4, 30) if len(solidname) > 78: @@ -329,16 +353,34 @@ def genfile(solidname: bytes, filetype: str, malformed: Optional[Any] = None) -> return genfile_ascii(solidname, malformed=malformed) elif filetype == "bin": return genfile_bin(solidname, malformed=malformed) - elif filetype == "garbage-tiny": - return bytes([rand.choice(generic_alphabet) for i in range(rand.randint(3, 8))]) elif filetype == "garbage": - return bytes( - [rand.choice(generic_alphabet) for i in range(rand.randint(100, 300))] - ) + return bytes([rand.randint(0, 255) for i in range(rand.randint(100, 600))]) else: raise InternalErrorException("Invalid file type supplied") +def getfile(filetype: str) -> tuple[bytes, bytes]: + if filetype == "ascii": + if rand.randint(0, 20) >= 5: + solidname = fakeid() + stlfile = genfile(solidname, "ascii") + else: + model = rand.choice([v for v in extra_models if v.endswith("-ascii.stl")]) + stlfile = open(model, "rb").read() + solidname = fakeid() + stlfile = stlfile.replace(b"OpenSCAD_Model", solidname) + else: + if rand.randint(0, 20) >= 5: + solidname = fakeid() + stlfile = genfile(solidname, "ascii") + else: + model = rand.choice([v for v in extra_models if v.endswith("-bin.stl")]) + stlfile = open(model, "rb").read() + solidname = fakeid(minlen=15, maxlen=15) + stlfile = solidname + stlfile[15:] # replaces b"OpenSCAD Model\n" + return stlfile, solidname + + def parse_stlinfo(stlfile: bytes) -> Any: fakefile = BytesIO() fakefile.write(stlfile) @@ -388,17 +430,16 @@ async def do_auth( await session.drain() # Check for errors - resp = await session.readline(ctx="reading auth response (1)") - if b"ERR:" in resp: + resp = await session.readuntil(prompt, ctx="reading auth response") + if resp == b"ERR:": if check: session.logger.critical(f"Failed to login with {authstr!r}:\n{resp!r}") raise MumbleException("Authentication not working properly") return None # Also check success message - resp += await session.readuntil(prompt, ctx="reading auth response (2)") try: - userid = resp.split(b"Logged in with ID ")[1].split(b"!")[0] + userid = resp.split(b"!", 1)[0].split(b"Logged in with ID ", 1)[1] except: session.logger.critical(f"Login with pass {authstr!r} failed:\n{resp!r}") raise MumbleException("Authentication not working properly") @@ -414,15 +455,17 @@ async def do_auth( raise MumbleException("Authentication not working properly") return None + session.logger.debug(f"Logged in as user: {userid!r}") + return userid async def do_list(session: Session, check: bool = True) -> Optional[bytes]: session.write(b"list\n") await session.drain() - resp = await session.readuntil(prompt, ctx="reading list response") # Check for errors + resp = await session.readuntil(prompt, ctx="reading list response") if b"ERR:" in resp and b">> " not in resp: if check: session.logger.critical(f"Failed to list private files:\n{resp!r}") @@ -440,40 +483,45 @@ async def do_upload( ) -> Optional[bytes]: session.logger.debug(f"Uploading model with name {modelname!r}") - session.write(b"upload\n") - # enter name and check resp + # start upload and enter name + session.write(b"upload\n") session.write(modelname + b"\n") await session.drain() + + # check for error with name await session.readuntil(b"name: ") resp = await session.read(4, ctx="checking for err response") if resp == b"ERR:": + resp += await session.readuntil(prompt) if check: - resp = resp + await session.readline() session.logger.critical(f"Failed during name check: {resp!r}") raise MumbleException("File upload not working properly") return None - # enter size and check resp + # write size and contents in together await session.readuntil(b"size: ") session.write(f"{len(stlfile)}\n".encode()) + session.write(stlfile) await session.drain() + + # check for error with size resp = await session.read(4, ctx="checking for err response") if resp == b"ERR:": + resp += await session.readuntil(prompt) if check: - resp = resp + await session.readline() session.logger.critical(f"Failed during size check: {resp!r}") raise MumbleException("File upload not working properly") return None + # checker for error with file await session.readuntil(b"listening..\n") - session.write(stlfile) - await session.drain() resp = await session.readline() if b"ERR:" in resp: if check: session.logger.critical(f"Failed during stl parsing: {resp!r}") raise MumbleException("File upload not working properly") + await session.readuntil(prompt) return None # parse returned id @@ -482,12 +530,10 @@ async def do_upload( if modelid == b"": raise Exception except: - session.logger.critical( - f"Invalid file size during upload of {modelname!r}:\n{resp!r}" - ) + session.logger.critical(f"Invalid size during upload:\n{resp!r}") raise MumbleException("File upload not working properly") - session.logger.debug(f"Uploaded model id: {modelid!r}") + session.logger.debug(f"Uploaded model with id {modelid!r}") await session.readuntil(prompt, ctx="waiting for prompt") return modelid @@ -504,22 +550,37 @@ async def do_search( # get possible hashes session.write(b"search " + modelname + b"\n") await session.drain() - resp = await session.readline() - if b"ERR:" in resp: + + # chck for error with search query + resp = await session.read(4) + if resp == b"ERR:": + resp += await session.readuntil(prompt) if check: session.logger.critical( f"Failed to retrieve model {modelname!r}:\n{resp!r}" ) raise MumbleException("File search not working properly") return None - resp = resp + await session.readuntil(b"> ") - results = [l.strip() for l in resp[:-2].split(b"\n")] - # request first result - session.write(results[0] + b"\n") + # collect results + resp += await session.readuntil(b"quit]: ") + try: + resp = resp.split(b">")[0] + except: + session.logger.critical('Search response missing b">" delim') + raise MumbleException("File search not working properly") + results = [l.strip() for l in resp.split(b"\n") if l.strip() != b""] + + # request most recent result (later in index file) + session.write(results[-1] + b"\n") + session.write(b"y\n" if download else b"n\n") await session.drain() - resp = await session.readline() - if b"ERR:" in resp: + + # check for error in hash provided + resp = await session.read(4) + if resp == b"ERR:": + # cleanup download y/n interpreted as command + await session.readuntil(prompt) if check: session.logger.critical(f"Error selecting file: {results[0]!r}") raise MumbleException("File search not working properly") @@ -528,9 +589,6 @@ async def do_search( b"================== \n", ctx="reading stl info" ) - # download if requested - session.write(b"y\n" if download else b"n\n") - await session.drain() stlfile = b"" if download: # Parse file contents await session.readuntil(b"Here you go.. (", ctx="reading stl size (1)") @@ -555,8 +613,7 @@ async def do_search( async def check_listed(session: Session, includes: list[bytes]) -> bytes: - resp = await do_list(session, check=True) - assert resp is not None + resp = nnb(await do_list(session, check=True)) if not includes_all(resp, includes): session.logger.critical(f"Failed to find {includes} in listing:\n{resp!r}") raise MumbleException("File listing not working properly") @@ -590,8 +647,7 @@ async def check_in_search( includes: list[bytes], download: bool = False, ) -> tuple[bytes, bytes]: - resp = await do_search(session, modelname, download, check=True) - assert resp is not None + resp = nnt(await do_search(session, modelname, download, check=True)) if not includes_all(resp[0] + resp[1], includes): session.logger.critical( f"Retrieved info for {modelname!r} is missing {includes}: {resp[0]+resp[1]!r}" @@ -708,64 +764,24 @@ def check_stlinfo( # TEST METHODS # -async def test_good_upload( - di: DependencyInjector, filetype: str, register: bool -) -> None: - solidname, modelname, authstr = fakeids(3) - stlfile = genfile(solidname, filetype) - ref_info = parse_stlinfo(stlfile) +async def test_good_upload(di: DependencyInjector, filetype: str) -> None: + modelname = fakeid() + stlfile, solidname = getfile(filetype) + ref = parse_stlinfo(stlfile) - # Create new session, register and upload file + # Upload file, get it with search, verify returned info session = await di.get(Session) - if register: - await do_auth(session, authstr, check=True, newuser=True) - modelid = await do_upload(session, modelname, stlfile, check=True) - assert modelid is not None - check_hash(modelid) + modelid = nnb(await do_upload(session, modelname, stlfile, check=True)) expected = [modelname, solidname, stlfile, modelid] - info, stlfile = await check_in_search(session, modelname, expected, download=True) + info, stl = await check_in_search(session, modelname, expected, download=True) check_stlinfo( session.logger, info, - ref_info, + ref, ref_modelname=modelname, ref_modelid=modelid, ref_solidname=solidname, ) - if register: - await check_listed(session, [modelname, modelid + b"-"]) - - # Try getting file from a new session - session = await di.get(Session) - if register: - await check_not_in_search( - session, modelname, expected, download=True, fail=True - ) - await do_auth(session, authstr, check=True, newuser=False) - info, stlfile = await check_in_search( - session, modelname, expected, download=True - ) - check_stlinfo( - session.logger, - info, - ref_info, - ref_modelname=modelname, - ref_modelid=modelid, - ref_solidname=solidname, - ) - await check_listed(session, [modelname, modelid + b"-"]) - else: - info, stlfile = await check_in_search( - session, modelname, expected, download=True - ) - check_stlinfo( - session.logger, - info, - ref_info, - ref_modelname=modelname, - ref_modelid=modelid, - ref_solidname=solidname, - ) async def test_bad_upload(di: DependencyInjector, filetype: str, variant: int) -> None: @@ -780,19 +796,17 @@ async def test_bad_upload(di: DependencyInjector, filetype: str, variant: int) - raise MumbleException("Upload validation not working properly") -async def test_search(di: DependencyInjector, registered: bool = False) -> None: - solidname, modelname, authstr = fakeids(3) - - # Ensure searching for a file that doesnt exist causes an error +async def test_hash_collision(di: DependencyInjector) -> None: + # See if using a random string we hit a hash collision for search / auth session = await di.get(Session) - if registered: - await do_auth(session, authstr, check=True, newuser=True) - resp = await do_search(session, modelname, download=False, check=False) - if resp is not None: - session.logger.critical( - f"Search for file that shouldn't exist succeeded:\n{resp[0]+resp[1]!r}" - ) - raise MumbleException("File search not working properly") + sresp = await do_search(session, fakeid(), download=False, check=False) + if sresp is not None: + session.logger.critical("File search succeeded on random file") + raise MumbleException("Hash function not working properly") + aresp = await do_auth(session, fakeid(), check=False, newuser=False) + if aresp is not None: + session.logger.critical("Auth succeeded for user with random str") + raise MumbleException("Hash function not working properly") # CHECKER METHODS # @@ -801,13 +815,12 @@ async def test_search(di: DependencyInjector, registered: bool = False) -> None: @checker.putflag(0) async def putflag_guest(task: PutflagCheckerTaskMessage, di: DependencyInjector) -> str: modelname = fakeid() + stlfile = genfile(task.flag.encode(), "ascii") db = await di.get(ChainDB) # Generate a file with flag in solidname and upload it (unregistered, ascii) session = await di.get(Session) - stlfile = genfile(task.flag.encode(), "ascii") - modelid = await do_upload(session, modelname, stlfile, check=True) - assert modelid is not None + modelid = nnb(await do_upload(session, modelname, stlfile, check=True)) await db.set("info", (modelname, modelid)) return "Model {}.. is kinda sus".format(modelid[:10].decode()) @@ -823,219 +836,171 @@ async def putflag_private( # Generate a file with flag in solidname and upload it (registered, bin) session = await di.get(Session) - userid = await do_auth(session, authstr, check=True, newuser=True) - assert userid is not None - modelid = await do_upload(session, modelname, stlfile, check=True) - assert modelid is not None + userid = nnb(await do_auth(session, authstr, check=True, newuser=True)) + modelid = nnb(await do_upload(session, modelname, stlfile, check=True)) await db.set("info", (modelname, modelid, authstr)) return "User {}.. is kinda sus".format(userid[:10].decode()) @checker.getflag(0) -async def getflag_guest( +async def getflag_unregistered( task: GetflagCheckerTaskMessage, di: DependencyInjector ) -> None: db = await di.get(ChainDB) modelname, modelid = await getdb(db, "info") - # Retrieve flag file info via search and ensure flag's included + # Search for flag file and verify flag is included session = await di.get(Session) - resp = await do_search(session, modelname, download=True, check=True) - assert resp is not None - assert_in(task.flag.encode(), resp[0], "Flag is missing from stl info") - assert_in(task.flag.encode(), resp[1], "Flag is missing from stl file") + sresp = nnt(await do_search(session, modelname, download=True, check=True)) + assert_in(task.flag.encode(), sresp[0] + sresp[1], "Failed to retrieve flag") @checker.getflag(1) -async def getflag_private( +async def getflag_registered( task: GetflagCheckerTaskMessage, di: DependencyInjector ) -> None: db = await di.get(ChainDB) modelname, modelid, authstr = await getdb(db, "info") - # Retrieve private flag file info via search / list and ensure flag's included + # Authenticate, get flag via search and list session = await di.get(Session) await do_auth(session, authstr, check=True, newuser=False) - search_resp = await do_search(session, modelname, download=True, check=True) - assert search_resp is not None - assert_in(task.flag.encode(), search_resp[0], "Flag is missing from stl info") - assert_in(task.flag.encode(), search_resp[1], "Flag is missing from stl file") - list_resp = await do_list(session, check=True) - assert list_resp is not None - assert_in(task.flag.encode(), list_resp, "Flag is missing from list") + sresp = nnt(await do_search(session, modelname, download=True, check=True)) + assert_in(task.flag.encode(), sresp[0] + sresp[1], "Failed to retrieve flag") + lresp = nnb(await do_list(session, check=True)) + assert_in(task.flag.encode(), lresp, "Failed to retrieve flag") -@checker.putnoise(0, 1) -async def putnoise_guest( +@checker.putnoise(0) +async def putnoise_unregistered( task: PutnoiseCheckerTaskMessage, di: DependencyInjector ) -> None: modelname, solidname = fakeids(2) + stlfile, solidname = getfile("ascii" if randbool() else "bin") db = await di.get(ChainDB) - # Generate a random file and upload it (unregistered, bin / ascii) + # Upload file for later checking session = await di.get(Session) - stlfile = genfile(solidname, "ascii" if task.variant_id == 0 else "bin") modelid = await do_upload(session, modelname, stlfile, check=True) await db.set("info", (modelid, modelname, solidname, stlfile)) -@checker.putnoise(2, 3) -async def putnoise_priv( +@checker.putnoise(1) +async def putnoise_registered( task: PutnoiseCheckerTaskMessage, di: DependencyInjector ) -> None: modelname, solidname, authstr = fakeids(3) + stlfile, solidname = getfile("ascii" if randbool() else "bin") db = await di.get(ChainDB) - # Generate a random file and upload it (registered, bin / ascii) + # Upload private file for later checking session = await di.get(Session) - stlfile = genfile(solidname, "ascii" if task.variant_id == 0 else "bin") await do_auth(session, authstr, check=True, newuser=True) modelid = await do_upload(session, modelname, stlfile, check=True) await db.set("info", (modelid, modelname, solidname, stlfile, authstr)) -@checker.getnoise(0, 1) -async def getnoise_guest( +@checker.getnoise(0) +async def getnoise_unregistered( task: GetnoiseCheckerTaskMessage, di: DependencyInjector ) -> None: db = await di.get(ChainDB) modelid, modelname, solidname, stlfile = await getdb(db, "info") + ref = parse_stlinfo(stlfile) - # Retrieve noise file by name via search session = await di.get(Session) - await check_in_search( - session, - modelname, - [modelname, solidname, stlfile, modelid], - download=True, + # check that search works on persisted file + expected = [modelname, solidname, stlfile, modelid] + info, stl = await check_in_search(session, modelname, expected, download=True) + # check that persisted file info is still valid + check_stlinfo( + session.logger, + info, + ref, + ref_modelname=modelname, + ref_modelid=modelid, + ref_solidname=solidname, ) -@checker.getnoise(2, 3) -async def getnoise_priv( +@checker.getnoise(1) +async def getnoise_registered( task: GetnoiseCheckerTaskMessage, di: DependencyInjector ) -> None: db = await di.get(ChainDB) modelid, modelname, solidname, stlfile, authstr = await getdb(db, "info") + ref = parse_stlinfo(stlfile) - # Retrieve noise file by name via search and search (registered) session = await di.get(Session) + # check that auth works and tells us we are logging in again await do_auth(session, authstr, check=True, newuser=False) - await check_in_search( - session, - modelname, - [modelname, solidname, stlfile, modelid], - download=True, + # check that search works on persisted file + expected = [modelname, solidname, stlfile, modelid] + info, stl = await check_in_search(session, modelname, expected, download=True) + # check that persisted file info is still valid + check_stlinfo( + session.logger, + info, + ref, + ref_modelname=modelname, + ref_modelid=modelid, + ref_solidname=solidname, ) - await check_listed(session, [modelname, solidname, modelid]) + # check that list works on persisted file + await check_listed(session, [modelname, modelid + b"-", solidname]) @checker.havoc(0) -async def havoc_good_upload_guest_ascii(di: DependencyInjector) -> None: - await test_good_upload(di, filetype="ascii", register=False) +async def havoc_good_upload_ascii(di: DependencyInjector) -> None: + await test_good_upload(di, "ascii") @checker.havoc(1) -async def havoc_good_upload_guest_bin(di: DependencyInjector) -> None: - await test_good_upload(di, filetype="bin", register=False) - - -@checker.havoc(2) -async def havoc_good_upload_priv_ascii(di: DependencyInjector) -> None: - await test_good_upload(di, filetype="ascii", register=True) - - -@checker.havoc(3) -async def havoc_good_upload_priv_bin(di: DependencyInjector) -> None: - await test_good_upload(di, filetype="bin", register=True) - - -@checker.havoc(4) async def havoc_bad_upload_ascii_v1(di: DependencyInjector) -> None: await test_bad_upload(di, "ascii", 1) -@checker.havoc(5) +@checker.havoc(2) async def havoc_bad_upload_ascii_v2(di: DependencyInjector) -> None: await test_bad_upload(di, "ascii", 2) -@checker.havoc(6) +@checker.havoc(3) async def havoc_bad_upload_ascii_v3(di: DependencyInjector) -> None: await test_bad_upload(di, "ascii", 3) -@checker.havoc(7) +@checker.havoc(4) +async def havoc_good_upload_bin(di: DependencyInjector) -> None: + await test_good_upload(di, "bin") + + +@checker.havoc(5) async def havoc_bad_upload_bin_v1(di: DependencyInjector) -> None: await test_bad_upload(di, "bin", 1) -@checker.havoc(8) +@checker.havoc(6) async def havoc_bad_upload_bin_v2(di: DependencyInjector) -> None: await test_bad_upload(di, "bin", 2) -@checker.havoc(9) +@checker.havoc(7) async def havoc_bad_upload_bin_v3(di: DependencyInjector) -> None: await test_bad_upload(di, "bin", 3) -@checker.havoc(10) +@checker.havoc(8) async def havoc_bad_upload_garbage(di: DependencyInjector) -> None: await test_bad_upload(di, "garbage", 1) -@checker.havoc(11) -async def havoc_bad_upload_garbage_tiny(di: DependencyInjector) -> None: - await test_bad_upload(di, "garbage-tiny", 1) - - -@checker.havoc(12) -async def havoc_test_search_guest(di: DependencyInjector) -> None: - await test_search(di, registered=False) - - -@checker.havoc(13) -async def havoc_test_search_priv(di: DependencyInjector) -> None: - await test_search(di, registered=True) - - -@checker.havoc(14) -async def havoc_test_list_guest(di: DependencyInjector) -> None: - # Ensure that list does not work for unregistered users - session = await di.get(Session) - resp = await do_list(session, check=False) - if resp is not None: - session.logger.critical("Unregistered user can run list without ERR!") - raise MumbleException("User authentication not working properly") - - -@checker.havoc(15) -async def havoc_fluff_upload(di: DependencyInjector) -> None: - if len(extra_models) == 0: - return - model = rand.choice(extra_models) - stlfile = open(model, "rb").read() - modelname = fakeid() - if model.endswith("-bin.stl"): - solidname = fakeid(minlen=15, maxlen=15) - stlfile = solidname + stlfile[15:] # replaces "OpenSCAD Model\n" - else: - solidname = fakeid() - stlfile = stlfile.replace(b"OpenSCAD_Model", solidname) - - # Simple Upload - session = await di.get(Session) - if randbool(): - await do_auth(session, fakeid(), check=True, newuser=True) - modelid = await do_upload(session, modelname, stlfile, check=True) - assert modelid is not None - await check_in_search( - session, modelname, [modelname, solidname, modelid, stlfile], download=True - ) +@checker.havoc(9) +async def havoc_hash_collision(di: DependencyInjector) -> None: + await test_hash_collision(di) @checker.exploit(0) @@ -1099,6 +1064,7 @@ async def exploit_hash_overflow( task: ExploitCheckerTaskMessage, di: DependencyInjector ) -> None: searcher = await di.get(FlagSearcher) + logger = await di.get(LoggerAdapter) assert task.attack_info is not None target_prefix = task.attack_info.split()[1][:-2].encode() @@ -1124,6 +1090,9 @@ async def exploit_hash_overflow( if flag := searcher.search_flag(resp): return flag + logger.debug(f"Searching for user with prefix: {target_prefix!r}") + logger.debug(f"Possible users: {users!r}") + # Login as each private user for userhash in users: if not userhash.startswith(target_prefix): @@ -1134,6 +1103,7 @@ async def exploit_hash_overflow( # Authenticate and check if the user is new session = await di.get(Session) + logger.debug(f"Trying to login as user: {userhash!r}") if not await do_auth(session, authstr, check=False, newuser=False): await session.exit() # We dont raise an exception, because it could be that user dir was cleaned |
