commit a8f375bede6c397ae99558df3265a0f603f3dfd5
parent 14ac78e63dbd2233d3dd577a0684a3dd8566234a
Author: Louis Burda <quent.burda@gmail.com>
Date: Fri, 25 Jun 2021 17:13:56 +0200
large refactor of checker, added more havocs to test listing and search, added motd to service welcome banner
Diffstat:
15 files changed, 503 insertions(+), 278 deletions(-)
diff --git a/checker/src/checker.py b/checker/src/checker.py
@@ -12,20 +12,6 @@ rand = random.SystemRandom()
from faker import Faker
-# DEBUGING MEMORY ISSUES#
-import tracemalloc, signal
-
-tracemalloc.start()
-
-def handler(signum, frame):
- print("Received SIG!")
- snapshot = tracemalloc.take_snapshot()
- top_stats = snapshot.statistics('lineno')
- open(f"malloc-log-{os.getpid()}", "w+").write("\n".join([str(v) for v in top_stats[:10]]))
-
-signal.signal(signal.SIGALRM, handler)
-# END DEBUG #
-
evil_file = b"""
solid test\xff
facet normal 0 0 1.0
@@ -48,52 +34,60 @@ def ensure_bytes(v):
else:
raise BrokenServiceException("Tried to pass non str/bytes to bytes arg")
+def includes_all(resp, targets):
+ for m in targets:
+ if ensure_bytes(m) not in resp:
+ return False
+ return True
+
+def includes_any(resp, targets):
+ for m in targets:
+ if ensure_bytes(m) in resp:
+ return True
+ return False
+
+def fakeid():
+ fake = Faker(["en_US"])
+ idstr = bytes([ord(c) for c in fake.name().replace(" ","") if c in generic_alphabet][:12]).ljust(10, b".")
+ idstr += bytes([ord(rand.choice(generic_alphabet)) for i in range(8)])
+ return idstr
+
+def havocid():
+ idlen = rand.randint(10, 40)
+ return bytes([rand.randint(32, 127) for i in range(idlen)])
+
+def reverse_hash(hashstr):
+ if type(hashstr) is bytes:
+ hashstr = hashstr.decode()
+ data = subprocess.check_output([os.getenv("REVHASH_PATH"), hashstr])[:-1]
+ if data == b"":
+ raise BrokenServiceException(f"Failed to find hash preimage of {hashstr}")
+ return data
+
+def check_line(conn, context):
+ line = conn.recvline()
+ if b"ERR:" in line:
+ raise EnoException(f"{context}: Unexpected error message\n")
+ return line
+
+def parse_int(intstr):
+ try:
+ return int(intstr)
+ except:
+ return None
+
class STLDoctorChecker(BaseChecker):
service_name = "stldoctor"
port = 9090
flag_variants = 2
noise_variants = 2
- havoc_variants = 8
+ havoc_variants = 16
exploit_variants = 2
- prompt = b"$ "
-
- def openconn(self):
- conn = self.connect()
- resp = conn.recvuntil(self.prompt)
- return conn
-
- def closeconn(self, conn):
- self.debug("Sending exit command")
- conn.write("exit\n")
- # ensure it is a clean exit
- conn.recvuntil("bye!")
- conn.close()
-
- def fakeid(self):
- fake = Faker(["en_US"])
- idstr = "".join([c for c in fake.name().replace(' ','') if c in generic_alphabet][:12]).ljust(10, '.')
- idstr += "".join([rand.choice(generic_alphabet) for i in range(8)])
- return idstr
-
- def havocid(self):
- idlen = rand.randint(10, 40)
- return "".join([chr(rand.randint(32, 127)) for i in range(idlen)])
-
- def do_auth(self, conn, authstr):
- authstr = ensure_bytes(authstr)
- self.debug(f"Logging in with {authstr}")
- conn.write("auth\n")
- conn.write(authstr + b"\n")
- resp = conn.recvuntil(self.prompt)
- assert_in(b"Success!", resp, f"Login with pass {authstr} failed");
+ prompt = b"\r$ "
- def check_listed(self, conn, modelid):
- modelid = ensure_bytes(modelid)
- conn.write("list\n")
- resp = conn.recvuntil(self.prompt)
- assert_in(modelid, resp, f"Uploaded model {modelid} is missing from list command")
+ # HELPER FUNCS #
def querydb(self, *args):
vals = []
@@ -108,39 +102,43 @@ class STLDoctorChecker(BaseChecker):
def postdb(self, **kwdict):
self.chain_db = kwdict
- def reverse_hash(self, hashstr):
- return subprocess.check_output([os.getenv("REVHASH_PATH"), hashstr])[:-1]
-
- def genfile_ascii(self, solidname, malformed=False):
+ def genfile_ascii(self, solidname, malformed = None):
+ indent = bytes([rand.choice(b"\t ") for i in range(rand.randint(1, 4))])
solidname = ensure_bytes(solidname)
- randchoice = rand.randint(0,2)
+ facet_count = rand.randint(4, 30)
if len(solidname) != 0:
content = b"solid " + solidname + b"\n"
else:
content = b"solid\n"
- facet_count = rand.randint(4, 30)
- indent = bytes([rand.choice(b"\t ") for i in range(rand.randint(1, 4))])
+
for fi in range(facet_count):
- if malformed and randchoice == 0: # malformed by wrong keyword
+ # MALFORM 1: wrong keyword
+ if malformed == 1:
content += indent * 1 + b"facet nornal "
else:
content += indent * 1 + b"facet normal "
+
vs = [[rand.random() for i in range(3)] for k in range(3)]
norm = np.cross(np.subtract(vs[1], vs[0]), np.subtract(vs[2],vs[0]))
norm = norm / np.linalg.norm(norm)
+
content += " ".join([f"{v:.2f}" for v in norm]).encode() + b"\n"
- if malformed and randchoice == 1: # malformed wrong keyword case
+
+ # MALFORM 2: wrong keyword case
+ if malformed == 2:
content += indent * 2 + b"outer lOop\n"
else:
content += indent * 2 + b"outer loop\n"
+
for i in range(3):
content += indent * 3 + b"vertex " + " ".join([f"{v:.2f}" for v in vs[i]]).encode() + b"\n"
+
content += indent * 2 + b"endloop\n"
content += indent + b"endfacet\n"
- if malformed and randchoice == 2:
- content += b"" # malformed since no endsolid
- else:
+
+ # MALFORM 3: no endsolid keyword
+ if malformed != 3:
if solidname != b"":
content += b"endsolid " + solidname + b"\n"
else:
@@ -148,9 +146,9 @@ class STLDoctorChecker(BaseChecker):
return content
- def genfile_bin(self, solidname, malformed=False):
+ def genfile_bin(self, solidname, malformed = None):
solidname = ensure_bytes(solidname)
- randchoice = rand.randint(0, 3)
+ facet_count = rand.randint(4, 30)
if len(solidname) > 78:
raise EnoException("Solidname to embed in header is larger than header itself")
@@ -158,17 +156,20 @@ class STLDoctorChecker(BaseChecker):
content = b"#" + solidname.ljust(78, b"\x00") + b"\x00"
else:
content = b"#" + b"\x00" * 79
- facet_count = rand.randint(4, 30)
- if malformed and randchoice == 0: # malform by specifying more facets than are in the file
+
+ # MALFORM 1: specify more facets than are in the file
+ if malformed == 1:
content += struct.pack("<I", facet_count + rand.randint(3, 7))
else:
content += struct.pack("<I", facet_count)
+
for fi in range(facet_count):
vs = [[rand.random() for i in range(3)] for k in range(3)]
norm = np.cross(np.subtract(vs[1], vs[0]), np.subtract(vs[2],vs[0]))
- if malformed and randchoice == 2: # malform by setting invalid float in norm
- norm[rand.randint(0,2)] = math.inf
- elif malformed and randchoice == 3: # same malformation, but in vec
+
+ # MALFORM 2: invalid float for norm / vec
+ if malformed == 2:
+ norm[rand.randint(0,2)] = math.nan
vs[rand.randint(0,2)][rand.randint(0,2)] = math.inf
for i in range(3):
content += struct.pack("<f", norm[i])
@@ -176,229 +177,366 @@ class STLDoctorChecker(BaseChecker):
for i in range(3):
content += struct.pack("<f", vs[k][i])
content += b"\x00\x00"
- if malformed and randchoice == 1: # malform by adding extra data to the end of the file
+
+ # MALFORM 3: add extra data to the end of the file
+ if malformed == 3:
content += bytes([rand.randint(0, 255) for i in range(30)])
+
return content
- def genfile(self, filetype, solidname, malformed=False):
+ def genfile(self, solidname, filetype, malformed = None):
if filetype == "ascii":
- return self.genfile_ascii(solidname, malformed=malformed)
+ return self.genfile_ascii(solidname, malformed = malformed)
elif filetype == "bin":
- return self.genfile_bin(solidname, malformed=malformed)
+ return self.genfile_bin(solidname, malformed = malformed)
elif filetype == "garbage-tiny":
return ("".join([rand.choice(generic_alphabet) for i in range(rand.randint(3, 8))])).encode()
elif filetype == "garbage":
return ("".join([rand.choice(generic_alphabet) for i in range(rand.randint(100, 300))])).encode()
else:
- raise EnoException("Invalid file type supplied");
+ raise EnoException("Invalid file type supplied")
- def putfile(self, conn, modelname, solidname, filetype="ascii", stlfile=None):
- solidname = ensure_bytes(solidname)
- modelname = ensure_bytes(modelname)
+ def openconn(self):
+ conn = self.connect()
+ resp = conn.recvuntil(b'\n' + self.prompt)
+ return conn
- # Generate file contents
- if stlfile is None:
- stlfile = self.genfile(filetype, solidname)
+ def closeconn(self, conn):
+ self.debug("Sending exit command")
+ conn.write("exit\n")
+ conn.recvuntil("bye!") # ensure clean exit
+ conn.close()
+
+ def do_auth(self, conn, authstr, check = True):
+ authstr = ensure_bytes(authstr)
+ self.debug(f"Logging in with {authstr}")
+ conn.write("auth\n")
+ conn.write(authstr + b"\n")
+
+ # Check for errors
+ resp = conn.recvline()
+ if b"ERR:" in resp:
+ if check:
+ raise EnoException(f"Failed to login with {authstr}:\n{resp}")
+ return None
+
+ # Also check success message
+ resp += conn.recvuntil(self.prompt)
+ if b"Success!" not in resp:
+ raise EnoException(f"Login with pass {authstr} failed")
+ return b"Welcome back" in resp
+
+ def do_list(self, conn, check = True):
+ conn.write("list\n")
+ resp = conn.recvuntil(self.prompt)
+
+ # Check for errors
+ if b"ERR:" in resp and b">> " not in resp:
+ if check:
+ raise EnoException(f"Failed to list private files:\n{resp}")
+ return None
+
+ return resp
+
+ def do_upload(self, conn, modelname, stlfile, check = True):
+ modelname = ensure_bytes(modelname)
# Upload file
self.debug(f"Uploading model with name {modelname}")
conn.write("upload\n")
+ conn.write(modelname + b"\n")
conn.write(f"{len(stlfile)}\n")
conn.write(stlfile)
- conn.write(modelname + b"\n")
- # Parse ID
- _ = conn.recvline()
+ # Check for errors
+ _ = conn.recvline() # Modelname:
line = conn.recvline()
+ if b"ERR:" in line:
+ if check:
+ raise EnoException(f"Failed to upload model {modelname}:\n{line}")
+ conn.recvuntil(self.prompt)
+ return None
+
+ # Parse ID
try:
modelid = line.rsplit(b"!", 1)[0].split(b"with ID ", 1)[1]
if modelid == b"": raise Exception
except:
raise BrokenServiceException(f"Invalid response during upload of {modelname}:\n{line}")
- # Consume rest of data in this call
conn.recvuntil(self.prompt)
+ return modelid
- return stlfile, modelid
-
- def getfile(self, conn, modelname=None, download=True):
+ def do_search(self, conn, modelname, download = False, check = True):
modelname = ensure_bytes(modelname)
# Initiate download
self.debug(f"Retrieving model with name {modelname}")
- conn.write("search\n")
- conn.write(modelname + b"\n")
+ conn.write(b"search " + modelname + b"\n")
conn.write("0\n") # first result
- conn.write("y\n" if download else "\n")
+ conn.write("y\n" if download else "n\n")
conn.write("q\n") # quit
- # Wait for end of info box
- resp = conn.recvuntil("================== \n")
+ # Check if an error occured
+ line = conn.recvline()
+ if b"ERR:" in line:
+ if check:
+ raise EnoException(f"Failed to retrieve model {modelname}:\n{line}")
+ if b"Couldn't find a matching scan result" in line:
+ # collect all the invalid commands sent after (hacky)
+ conn.recvuntil(self.prompt)
+ conn.recvuntil(self.prompt)
+ conn.recvuntil(self.prompt)
+ conn.recvuntil(self.prompt)
+ return None
- # Ask for download if desired
- if download:
- resp += conn.recvuntil(b"Here you go.. (")
- try:
- size = int(conn.recvuntil(b"B)\n")[:-3])
- except:
- raise BrokenServiceException("Returned file content size for download is not a valid integer")
+ # Recv until end of info box
+ fileinfo = line + conn.recvuntil("================== \n")
+
+ stlfile = b""
+ if download: # Parse file contents
+ conn.recvuntil(b"Here you go.. (")
+ size = parse_int(conn.recvuntil(b"B)\n")[:-3])
+ if size is None:
+ raise BrokenServiceException(f"Received invalid download size, response:\n{resp}")
self.debug(f"Download size: {size}")
- contents = conn.recvn(size)
- self.debug("File contents:\n" + str(contents))
- resp += contents
+ stlfile = conn.recvn(size)
conn.recvuntil(self.prompt)
+ return fileinfo, stlfile
+
+ # CHECK WRAPPERS #
+
+ def check_listed(self, conn, includes):
+ resp = self.do_list(conn, check = True)
+ if not includes_all(resp, includes):
+ raise EnoException(f"Failed to find {includes} in listing:\n{resp}")
return resp
- def check_getfile(self, conn, modelname, solidname, contents, modelid = None):
- resp = self.getfile(conn, modelname = modelname)
- if modelid:
- assert_in(ensure_bytes(modelid), resp, f"Model id {modelid} not returned / correctly parsed")
- assert_in(ensure_bytes(modelname), resp, f"Model name {modelname} not returned / correctly parsed")
- assert_in(ensure_bytes(solidname), resp, f"Solid name {solidname} not returned / correctly parsed")
- assert_in(ensure_bytes(contents), resp, f"STL File contents not returned / correctly parsed")
+ def check_not_listed(self, conn, excludes, fail = False):
+ resp = self.do_list(conn, check = False)
+ if fail and resp:
+ raise EnoException(f"Expected list to fail, but returned:\n{resp}")
+ if not fail and not resp:
+ raise EnoException(f"List failed unexpectedly:\n{resp}")
+ if resp and includes_any(resp, excludes):
+ raise EnoException(f"Unexpectedly found one of {excludes} in listing:\n{resp}")
+ return resp
- def havoc_upload(self, filetype, register):
- # Cant be havocid with ascii since might mess with stl parsing
- solidname = self.fakeid() if filetype == 'ascii' else self.havocid()
- modelname = self.havocid()
- authstr = self.havocid()
+ def check_in_search(self, conn, modelname, includes, download = False):
+ info, stlfile = self.do_search(conn, modelname, download, check = True)
+ if not includes_all(info + stlfile, includes):
+ raise EnoException(f"Retrieved info for {modelname} is missing {includes}: {resp}")
+ return info, stlfile
+
+ def check_not_in_search(self, conn, modelname, excludes, download = False, fail = False):
+ resp = self.do_search(conn, modelname, download, check = False)
+ if resp:
+ combined = resp[0]+resp[1]
+ if fail and resp:
+ raise EnoException("Search for {modelname} succeeded unexpectedly:\n{combined}")
+ if not fail and not resp:
+ raise EnoException(f"Search for {modelname} failed unexpectedly:\n{resp}")
+ if resp and includes_any(resp[0] + resp[1], excludes):
+ raise EnoException(f"Unexpectedly {modelname} info contains one of {includes}: {combined}")
+ return resp
+
+ # TEST METHODS #
+
+ def test_good_upload(self, filetype, register):
+ # ASCII Solidname cant be havocid since it might mess with parsing
+ solidname = fakeid() if filetype == "ascii" else havocid()
+ modelname = havocid()
+ authstr = havocid()
+ stlfile = self.genfile(solidname, filetype)
# Create new session and user and upload file
conn = self.openconn()
if register:
self.do_auth(conn, authstr)
- contents, modelid = self.putfile(conn, modelname, solidname, filetype)
- self.check_getfile(conn, modelname, solidname, contents)
+ modelid = self.do_upload(conn, modelname, stlfile)
+ expected = [modelname, solidname, stlfile, modelid]
+ resp = self.check_in_search(conn, modelname, expected, download = True)
if register:
- self.check_listed(conn, modelid)
+ resp = self.check_listed(conn, [modelname, modelid])
self.closeconn(conn)
# Try getting file from a new session
conn = self.openconn()
if register:
+ self.check_not_in_search(conn, modelname, expected, download = True, fail = True)
self.do_auth(conn, authstr)
- self.check_getfile(conn, modelname, solidname, contents)
- if register:
- self.check_listed(conn, modelid)
+ self.check_in_search(conn, modelname, expected, download = True)
+ self.check_listed(conn, [modelid, modelname])
+ else:
+ self.check_in_search(conn, modelname, expected, download = True)
+
self.closeconn(conn)
- def malformed_upload(self, filetype):
+ def test_bad_upload(self, filetype, variant):
+ stlfile = self.genfile(fakeid(), filetype, malformed = variant)
+
conn = self.openconn()
- solidname = self.fakeid()
- modelname = self.fakeid()
- contents = self.genfile(filetype, solidname, malformed = True)
- conn.write("upload\n")
- conn.write(f"{len(contents)}\n")
- conn.write(contents)
- conn.write(modelname + "\n")
- if filetype == "garbage-tiny":
- conn.recvuntil("ERR: File too small")
- else:
- conn.recvuntil("ERR:")
- conn.recvuntil(self.prompt)
+ if self.do_upload(conn, fakeid(), stlfile, check = False):
+ raise EnoException(f"Able to upload malformed file:\n{stlfile}")
self.closeconn(conn)
- def putflag(self): # type: () -> None
- if self.variant_id == 0:
+ def test_search(self, registered = False):
+ solidname = fakeid()
+ modelname = fakeid()
+ modelname2 = fakeid()
+ authstr = fakeid()
+ stlfile = self.genfile(solidname, "bin")
+
+ conn = self.openconn()
+ if registered:
+ self.do_auth(conn, authstr)
+ modelid = self.do_upload(conn, modelname, stlfile)
+ self.check_not_in_search(conn, modelname2, [modelname, modelid],
+ download = True, fail = True)
+ self.check_in_search(conn, modelname, [modelname, modelid], download = True)
+ self.closeconn(conn)
+
+ def test_list(self, registered = False):
+ solidname = fakeid()
+ modelname = fakeid()
+ authstr = fakeid()
+ authstr2 = fakeid()
+ stlfile = self.genfile(solidname, "bin")
+
+ conn = self.openconn()
+ self.do_auth(conn, authstr)
+ modelid = self.do_upload(conn, modelname, stlfile)
+ self.check_listed(conn, [modelid, modelname])
+ self.closeconn(conn)
+
+ if registered:
conn = self.openconn()
- modelname = self.fakeid()
- stlfile, modelid = self.putfile(conn, modelname, self.flag, filetype = "ascii")
+ if self.do_auth(conn, authstr2):
+ raise EnoException("New authstr {authstr2} has user dir")
+ self.check_not_listed(conn, [modelid, modelname])
self.closeconn(conn)
- self.postdb(modelid=modelid, modelname=modelname)
- elif self.variant_id == 1:
+ else:
conn = self.openconn()
- modelname = self.fakeid()
- authstr = self.fakeid()
- self.do_auth(conn, authstr)
- stlfile, modelid = self.putfile(conn, modelname, self.flag, filetype = "bin")
+ self.check_not_listed(conn, [modelid, modelname], fail = True)
self.closeconn(conn)
- self.postdb(modelid=modelid, modelname=modelname, auth=authstr)
+
+ # CHECKER METHODS #
+
+ def putflag(self): # type: () -> None
+ if self.variant_id in (0, 1):
+ modelname = fakeid()
+ types = ["ascii", "bin"]
+ registered = (self.variant_id == 1)
+ stlfile = self.genfile(self.flag, types[self.variant_id])
+ authstr = fakeid() if registered else ""
+
+ conn = self.openconn()
+ if registered:
+ self.do_auth(conn, authstr)
+ modelid = self.do_upload(conn, modelname, stlfile)
+ self.closeconn(conn)
+
+ self.postdb(modelid = modelid, modelname = modelname, authstr = authstr)
else:
raise EnoException(f"Invalid putflag variant ({self.variant_id}) provided")
def getflag(self): # type: () -> None
- if self.variant_id == 0:
- modelid, modelname = self.querydb("modelid", "modelname")
- conn = self.openconn()
- resp = self.getfile(conn, modelname.encode())
- assert_in(self.flag.encode(), resp, "Flag not found in file info nor contents")
- self.closeconn(conn)
- elif self.variant_id == 1:
- modelid, modelname, authstr = self.querydb("modelid", "modelname", "auth")
+ if self.variant_id in (0, 1):
+ modelid, modelname, authstr = self.querydb("modelid", "modelname", "authstr")
+ registered = (self.variant_id == 1)
+
conn = self.openconn()
- self.do_auth(conn, authstr)
- resp = self.getfile(conn, modelname.encode())
- assert_in(self.flag.encode(), resp, "Flag not found in file info nor contents")
+ if registered:
+ self.do_auth(conn, authstr)
+ info, stlfile = self.do_search(conn, modelname)
+ if self.flag.encode() not in (info + stlfile):
+ raise EnoException(f"Flag {self.flag} not found in search:\n{info}\n{stlfile}")
self.closeconn(conn)
else:
raise EnoException(f"Invalid getflag variant ({self.variant_id}) provided")
def putnoise(self): # type: () -> None
- if self.variant_id == 0:
- conn = self.openconn()
- modelname = self.fakeid()
- solidname = self.fakeid()
- contents, modelid = self.putfile(conn, modelname, solidname, "bin")
- self.closeconn(conn)
- self.postdb(modelid=modelid, modelname=modelname, solidname=solidname, contents=contents)
- elif self.variant_id == 1:
+ if self.variant_id in (0, 1):
+ modelname = fakeid()
+ solidname = fakeid()
+ types = ["bin", "ascii"]
+ registered = (self.variant_id == 1)
+ authstr = fakeid() if registered else ""
+ stlfile = self.genfile(solidname, types[self.variant_id])
+
conn = self.openconn()
- authstr = self.fakeid()
- modelname = self.fakeid()
- solidname = self.fakeid()
- self.do_auth(conn, authstr)
- contents, modelid = self.putfile(conn, modelname, solidname, "ascii")
+ if registered:
+ self.do_auth(conn, authstr)
+ modelid = self.do_upload(conn, modelname, stlfile)
self.closeconn(conn)
- self.postdb(modelid=modelid, modelname=modelname, solidname=solidname, contents=contents, auth=authstr)
+
+ self.postdb(modelid = modelid, modelname = modelname,
+ solidname = solidname, stlfile = stlfile, authstr = authstr)
else:
raise EnoException(f"Invalid putnoise variant ({self.variant_id}) provided")
def getnoise(self): # type: () -> None
- if self.variant_id == 0:
- modelid, modelname, solidname, contents = self.querydb("modelid", "modelname", "solidname", "contents")
- conn = self.openconn()
- self.check_getfile(conn, modelname, solidname, contents, modelid)
- self.closeconn(conn)
- elif self.variant_id == 1:
- modelid, modelname, solidname, contents, authstr = self.querydb("modelid", "modelname", "solidname", "contents", "auth")
+ if self.variant_id in (0, 1):
+ modelid, modelname, solidname, stlfile, authstr \
+ = self.querydb("modelid", "modelname", "solidname", "stlfile", "authstr")
+ registered = (self.variant_id == 1)
+ expected = [modelname, solidname, stlfile, modelid]
+
conn = self.openconn()
- self.do_auth(conn, authstr)
- self.check_getfile(conn, modelname, solidname, contents, modelid)
+ if registered:
+ self.do_auth(conn, authstr)
+ self.check_in_search(conn, modelname, expected, download = True)
self.closeconn(conn)
else:
raise EnoException(f"Invalid noise variant ({self.variant_id}) provided")
def havoc(self): # type: () -> None
if self.variant_id == 0:
- self.havoc_upload('ascii', False)
+ self.test_good_upload("ascii", False)
elif self.variant_id == 1:
- self.havoc_upload('bin', False)
+ self.test_good_upload("bin", False)
elif self.variant_id == 2:
- self.havoc_upload('ascii', True)
+ self.test_good_upload("ascii", True)
elif self.variant_id == 3:
- self.havoc_upload('bin', True)
+ self.test_good_upload("bin", True)
elif self.variant_id == 4:
- self.malformed_upload('ascii')
+ self.test_bad_upload("ascii", variant = 1)
elif self.variant_id == 5:
- self.malformed_upload('bin')
+ self.test_bad_upload("ascii", variant = 2)
elif self.variant_id == 6:
- self.malformed_upload('garbage')
+ self.test_bad_upload("ascii", variant = 3)
elif self.variant_id == 7:
- self.malformed_upload('garbage-tiny')
+ self.test_bad_upload("bin", variant = 1)
+ elif self.variant_id == 8:
+ self.test_bad_upload("bin", variant = 2)
+ elif self.variant_id == 9:
+ self.test_bad_upload("bin", variant = 3)
+ elif self.variant_id == 10:
+ self.test_bad_upload("garbage", variant = 1)
+ elif self.variant_id == 11:
+ self.test_bad_upload("garbage-tiny", variant = 1)
+ elif self.variant_id == 12:
+ self.test_search(False)
+ elif self.variant_id == 13:
+ self.test_search(True)
+ elif self.variant_id == 14:
+ self.test_list(False)
+ elif self.variant_id == 15:
+ self.test_list(True)
else:
raise EnoException(f"Invalid havoc variant ({self.variant_id}) provided")
def exploit(self): # type: () -> None
if self.variant_id == 0:
- name = self.fakeid()
+ modelname = fakeid()
# Upload evil file for parse via search for hash truncation
+ self.debug(f"Uploading evil file for hash truncation")
conn = self.openconn()
- resp,mid = self.putfile(conn, name, name, stlfile=evil_file)
- self.debug(f"Uploaded evil file with id {mid}")
- resp = self.getfile(conn, name, download=False)
+ modelid = self.do_upload(conn, modelname, stlfile = evil_file)
+ info, contents = self.do_search(conn, modelname, download = False)
conn.write("search last\n")
filelist = [l.strip().split(b" : ")[1] for l in conn.recvuntil("? ").split(b"\n") if b" : " in l]
if len(filelist) == 0:
@@ -406,7 +544,7 @@ class STLDoctorChecker(BaseChecker):
# Use it to enumerate other files and grab contents
found = None
- self.debug("Targets:\n" + "\n".join([' - ' + l.decode('latin1') for l in filelist]))
+ self.debug("Targets:\n" + "\n".join([" - " + l.decode("latin1") for l in filelist]))
for i, fhash in enumerate(filelist):
self.debug(f"Retrieving file {fhash} at index {i}")
conn.write(f"{i}\nn\n")
@@ -416,6 +554,7 @@ class STLDoctorChecker(BaseChecker):
if found is not None or i == len(filelist) - 1:
break
+ # Done!
conn.write("q\n")
conn.recvuntil(self.prompt)
self.closeconn(conn)
@@ -431,8 +570,9 @@ class STLDoctorChecker(BaseChecker):
conn.write("auth\n")
assert_in(b"Already logged in!", conn.recvuntil(self.prompt),
"Exploit did not set 'loggedin' variable via overflow")
- conn.write("list\n")
- resp = conn.recvuntil(self.prompt)
+
+ # Get private user hashes via 'list'
+ resp = self.do_list(conn)
self.debug("List response: " + str(resp))
users = [l.split(b" .")[1] for l in resp.split(b"\n") if b">> ." in l]
if len(users) == 0:
@@ -442,27 +582,25 @@ class STLDoctorChecker(BaseChecker):
# Login as each private user
found = None
for u in users:
- conn = self.openconn()
+ # Find preimage of user hash
self.debug(f"Logging in as user with id {u}")
+ user = reverse_hash(u)
- user = self.reverse_hash(u.decode())
- if user == b"":
- raise BrokenServiceException("Failed to find hash preimage")
- conn.write(b"auth " + user + b"\n")
-
- resp = conn.recvuntil(self.prompt)
- if b"Welcome back" not in resp:
+ # Authenticate and check if the user is new
+ conn = self.openconn()
+ if not self.do_auth(conn, user):
self.closeconn(conn)
- continue
- # NOTE: dont raise an exception, could be that user dir was cleaned up just
- # before we logged in, not necessarily because of invalid prehash
+ # 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
# raise EnoException(f"Reversing of hash {u} returned invalid preimage {user}")
+ continue
- conn.write("list\n")
- resp = conn.recvuntil(self.prompt)
- names = b"\n".join([l.split(b": ", 1)[1] for l in resp.split(b"\n") if b"Solid Name: " in l])
+ # List all private files of user
+ resp = self.do_list(conn)
self.closeconn(conn)
+ # Search for flag in solid names
+ names = b"\n".join([l.split(b": ", 1)[1] for l in resp.split(b"\n") if b"Solid Name: " in l])
found = self.search_flag_bytes(names)
if found is not None:
break
diff --git a/checker/src/revhash/main.c b/checker/src/revhash/main.c
@@ -15,6 +15,7 @@ mhash(const char *str, size_t len)
int i, k, v;
char c, *bp;
+ if (!str || !*str) str = ".";
if (len <= 0) return EXIT_FAILURE;
for (v = 0, i = 0; i < len; i++) v += str[i];
diff --git a/checker/test.sh b/checker/test.sh
@@ -24,6 +24,7 @@ except:
" || nop
}
+taskid=""
try() {
cmd="$1"
pid=$BASHPID
@@ -34,7 +35,7 @@ try() {
else
variant=$2
fi
- taskid=$pid
+ taskid="$pid"
if [ ! -z "$REMOTE" ]; then
python3 enoreq.py -j True -A http://localhost:9091 -a $REMOTE \
--flag ENOTESTFLAG123= --flag_regex 'ENO.*=' -i $taskid \
@@ -57,11 +58,17 @@ try() {
docker-compose logs --tail=2000 | grep '"taskId": '$taskid | $ENOLOGMESSAGE_PARSER
fi
) > "$newfile"
+ echo -ne "Executing $cmd with variant $variant.. $res (TASK: $taskid)\n"
+ return 1
+ else
+ echo -ne "Executing $cmd with variant $variant.. $res (TASK: $taskid)\n"
+ return 0
fi
- echo -ne "Executing $cmd with variant $variant.. $res (TASK: $taskid)\n"
}
try-all() {
+ set -e
+
try putflag 0
try getflag 0
@@ -74,10 +81,9 @@ try-all() {
try putflag 1
try getflag 1
- try havoc 0
- try havoc 1
- try havoc 2
- try havoc 3
+ for i in $(seq 0 15); do
+ try havoc $i
+ done
try exploit 0
try exploit 1
@@ -92,6 +98,7 @@ one-of() {
if one-of "$1" putflag getflag putnoise getnoise havoc exploit; then
try $@
+ [ $? -ne 0 -a -e "$EDITOR" ] && "$EDITOR" "/tmp/checker-log-$taskid"
elif [ "$1" == "test-exploits" ]; then
try exploit 0
try exploit 1
diff --git a/service/src/main.c b/service/src/main.c
@@ -128,7 +128,7 @@ handle_download(const char *scandir)
fseek(f, 0, SEEK_END);
size = ftell(f);
fseek(f, 0, SEEK_SET);
- if (size > MAXFILESIZE) {
+ if (size >= MAXFILESIZE) {
ERR("File is too large!\n");
goto fail;
}
@@ -150,6 +150,31 @@ fail:
}
void
+motd()
+{
+ char linebuf[80];
+ int msgc, msgi, i;
+ FILE *f;
+
+ if (!(f = fopen("msgs/motd", "r"))) return;
+
+ if (!fgets(linebuf, sizeof(linebuf), f))
+ goto exit;
+
+ srand(time(NULL));
+ if ((msgc = atoi(linebuf))) {
+ msgi = rand() % msgc;
+ for (i = 0; i < msgi + 1; i++)
+ if (!fgets(linebuf, sizeof(linebuf), f))
+ return;
+ printf("%s\n", linebuf);
+ }
+
+exit:
+ fclose(f);
+}
+
+void
cat_cmd(const char *arg)
{
if (arg && !strncmp(arg, "flag", 4))
@@ -193,34 +218,41 @@ echo_cmd(const char *arg)
void
upload_cmd(const char *arg)
{
+ char *end, *contents, *modelname;
const char *resp;
- char *end, *contents;
size_t len;
+ modelname = checkp(strdup(ask("Enter a model name: ")));
+ if (!strlen(modelname)) {
+ ERR("Empty model names are not allowed");
+ goto exit;
+ }
+
resp = ask("How large is your file? ");
len = strtoul(resp, &end, 10);
if (len <= 0 || len >= MAXFILESIZE || *end) {
ERR("Invalid file length!\n");
- return;
+ goto exit;
}
printf("Ok! Im listening..\n");
contents = checkp(malloc(len + 1));
if (fread(contents, 1, len, stdin) != len) {
ERR("Not enough data received!\n");
- goto cleanup;
+ goto exit;
}
contents[len] = '\0';
- if ((cached.valid = parse_file(&cached, contents, len))) {
+ if ((cached.valid = parse_file(&cached, contents, len, &modelname))) {
if (save_submission(&cached, contents, len) != OK)
ERR("Failed to save your submission!\n");
else
printf("Your file was saved with ID %s!\n", cached.hash);
}
-cleanup:
+exit:
free(contents);
+ free(modelname);
}
void
@@ -264,7 +296,7 @@ search_cmd(const char *arg)
if (pathc == 0) {
ERR("Couldn't find a matching scan result!\n");
- goto cleanup;
+ goto exit;
}
while (1) {
@@ -273,15 +305,15 @@ search_cmd(const char *arg)
which = strtoul(resp, &end, 10);
if (which >= pathc || which < 0 || *end) {
ERR("Invalid index!\n");
- goto cleanup;
+ goto exit;
}
scandir = aprintf("%s/%s", resultdir, paths[which]);
- if (handle_download(scandir) != OK) goto cleanup;
+ if (handle_download(scandir) != OK) goto exit;
FREE(scandir);
}
-cleanup:
+exit:
FREE(scandir);
for (i = 0; i < pathc; i++) free(paths[i]);
@@ -355,7 +387,6 @@ auth_cmd(const char *arg)
void
cleanexit()
{
- printf("see you later!\n");
free_info(&cached);
free(resultdir);
}
@@ -364,8 +395,8 @@ int
main()
{
const char *cmd, *envstr;
- char *cp, *arg;
int exit, i, cmdlen;
+ char *cp, *arg;
if (!(envstr = getenv("RESULTDIR")))
die("RESULTDIR not defined\n");
@@ -378,12 +409,13 @@ main()
atexit(cleanexit);
- dump("msgs/welcome");
+ dump("msgs/banner");
+ motd();
exit = 0;
while (!exit) {
errno = 0;
- cmd = ask("$ ");
+ cmd = ask("\r$ ");
if (!*cmd && errno == EBADMSG) break;
if (!*cmd) continue;
diff --git a/service/src/msgs/banner b/service/src/msgs/banner
@@ -0,0 +1,4 @@
+ ┌─┐┌┬┐┬ ┌┬┐┌─┐┌─┐┌┬┐┌─┐┬─┐ ┬
+ └─┐ │ │ │││ ││ │ │ │├┬┘ ▓
+ └─┘ ┴ ┴─┘─┴┘└─┘└─┘ ┴ └─┘┴└─ │
+
diff --git a/service/src/msgs/motd b/service/src/msgs/motd
@@ -0,0 +1,15 @@
+13
+ We analyze your STL files!
+ STL: Standard Triangle Language
+ STL: Standard Template Library
+ STL: Solid Tesselation Language
+ STL: Standard Telegraph Level
+ A file upload service (now in 3D!)
+ Keep your files small, size matters!
+ Use 'search' to find models by name!
+ ENO.. is a popular solid name!
+ STeaLing flags since 2006!
+ C4n y0u r34d th1s? 🏁 ➡️ 📁
+ ( *ಥ ⌂ * ) dont pwn pls
+ *finds file* UwU whats this?
+
diff --git a/service/src/msgs/welcome b/service/src/msgs/welcome
@@ -1,6 +0,0 @@
- ┌─┐┌┬┐┬ ┌┬┐┌─┐┌─┐┌┬┐┌─┐┬─┐ ┬
- └─┐ │ │ │││ ││ │ │ │├┬┘ ▓
- └─┘ ┴ ┴─┘─┴┘└─┘└─┘ ┴ └─┘┴└─ │
-
- We analyze your STL files!
-
diff --git a/service/src/stlfile.c b/service/src/stlfile.c
@@ -310,7 +310,7 @@ fail:
}
int
-parse_file(struct parseinfo *info, char *buf, size_t len)
+parse_file(struct parseinfo *info, char *buf, size_t len, char **modelname)
{
int status;
const char *resp;
@@ -332,17 +332,11 @@ parse_file(struct parseinfo *info, char *buf, size_t len)
: parse_file_bin(info, buf, len);
if (status == FAIL) return FAIL;
- if (!info->modelname) {
- resp = ask("Please enter your model name: ");
- if (strlen(resp) < 4) {
- ERR("Model name is too short!\n");
- return FAIL;
- }
- info->modelname = checkp(strdup(resp));
- }
-
if (!info->solidname) info->solidname = checkp(strdup(""));
+ info->modelname = *modelname;
+ *modelname = NULL;
+
info->hash = checkp(strdup(mhash(info->modelname, -1)));
return OK;
diff --git a/service/src/stlfile.h b/service/src/stlfile.h
@@ -9,7 +9,7 @@
#include "util.h"
-#define FMT_ERR(...) ERR("FORMAT " __VA_ARGS__)
+#define FMT_ERR(...) printf("FORMAT ERR: " __VA_ARGS__)
enum {
KW_INVALID = -1,
@@ -47,7 +47,7 @@ struct parseinfo {
int type, valid;
};
-int parse_file(struct parseinfo *info, char *buf, size_t len);
+int parse_file(struct parseinfo *info, char *buf, size_t len, char **modelname);
int save_info(struct parseinfo *info, FILE *f);
int load_info(struct parseinfo *info, FILE *f);
void print_info(struct parseinfo *info);
diff --git a/src/main.c b/src/main.c
@@ -128,7 +128,7 @@ handle_download(const char *scandir)
fseek(f, 0, SEEK_END);
size = ftell(f);
fseek(f, 0, SEEK_SET);
- if (size > MAXFILESIZE) {
+ if (size >= MAXFILESIZE) {
ERR("File is too large!\n");
goto fail;
}
@@ -150,6 +150,31 @@ fail:
}
void
+motd()
+{
+ char linebuf[80];
+ int msgc, msgi, i;
+ FILE *f;
+
+ if (!(f = fopen("msgs/motd", "r"))) return;
+
+ if (!fgets(linebuf, sizeof(linebuf), f))
+ goto exit;
+
+ srand(time(NULL));
+ if ((msgc = atoi(linebuf))) {
+ msgi = rand() % msgc;
+ for (i = 0; i < msgi + 1; i++)
+ if (!fgets(linebuf, sizeof(linebuf), f))
+ return;
+ printf("%s\n", linebuf);
+ }
+
+exit:
+ fclose(f);
+}
+
+void
cat_cmd(const char *arg)
{
if (arg && !strncmp(arg, "flag", 4))
@@ -193,34 +218,41 @@ echo_cmd(const char *arg)
void
upload_cmd(const char *arg)
{
+ char *end, *contents, *modelname;
const char *resp;
- char *end, *contents;
size_t len;
+ modelname = checkp(strdup(ask("Enter a model name: ")));
+ if (!strlen(modelname)) {
+ ERR("Empty model names are not allowed");
+ goto exit;
+ }
+
resp = ask("How large is your file? ");
len = strtoul(resp, &end, 10);
if (len <= 0 || len >= MAXFILESIZE || *end) {
ERR("Invalid file length!\n");
- return;
+ goto exit;
}
printf("Ok! Im listening..\n");
contents = checkp(malloc(len + 1));
if (fread(contents, 1, len, stdin) != len) {
ERR("Not enough data received!\n");
- goto cleanup;
+ goto exit;
}
contents[len] = '\0';
- if ((cached.valid = parse_file(&cached, contents, len))) {
+ if ((cached.valid = parse_file(&cached, contents, len, &modelname))) {
if (save_submission(&cached, contents, len) != OK)
ERR("Failed to save your submission!\n");
else
printf("Your file was saved with ID %s!\n", cached.hash);
}
-cleanup:
+exit:
free(contents);
+ free(modelname);
}
void
@@ -264,7 +296,7 @@ search_cmd(const char *arg)
if (pathc == 0) {
ERR("Couldn't find a matching scan result!\n");
- goto cleanup;
+ goto exit;
}
while (1) {
@@ -273,15 +305,15 @@ search_cmd(const char *arg)
which = strtoul(resp, &end, 10);
if (which >= pathc || which < 0 || *end) {
ERR("Invalid index!\n");
- goto cleanup;
+ goto exit;
}
scandir = aprintf("%s/%s", resultdir, paths[which]);
- if (handle_download(scandir) != OK) goto cleanup;
+ if (handle_download(scandir) != OK) goto exit;
FREE(scandir);
}
-cleanup:
+exit:
FREE(scandir);
for (i = 0; i < pathc; i++) free(paths[i]);
@@ -355,7 +387,6 @@ auth_cmd(const char *arg)
void
cleanexit()
{
- printf("see you later!\n");
free_info(&cached);
free(resultdir);
}
@@ -364,8 +395,8 @@ int
main()
{
const char *cmd, *envstr;
- char *cp, *arg;
int exit, i, cmdlen;
+ char *cp, *arg;
if (!(envstr = getenv("RESULTDIR")))
die("RESULTDIR not defined\n");
@@ -378,12 +409,13 @@ main()
atexit(cleanexit);
- dump("msgs/welcome");
+ dump("msgs/banner");
+ motd();
exit = 0;
while (!exit) {
errno = 0;
- cmd = ask("$ ");
+ cmd = ask("\r$ ");
if (!*cmd && errno == EBADMSG) break;
if (!*cmd) continue;
diff --git a/src/msgs/banner b/src/msgs/banner
@@ -0,0 +1,4 @@
+ ┌─┐┌┬┐┬ ┌┬┐┌─┐┌─┐┌┬┐┌─┐┬─┐ ┬
+ └─┐ │ │ │││ ││ │ │ │├┬┘ ▓
+ └─┘ ┴ ┴─┘─┴┘└─┘└─┘ ┴ └─┘┴└─ │
+
diff --git a/src/msgs/motd b/src/msgs/motd
@@ -0,0 +1,15 @@
+13
+ We analyze your STL files!
+ STL: Standard Triangle Language
+ STL: Standard Template Library
+ STL: Solid Tesselation Language
+ STL: Standard Telegraph Level
+ A file upload service (now in 3D!)
+ Keep your files small, size matters!
+ Use 'search' to find models by name!
+ ENO.. is a popular solid name!
+ STeaLing flags since 2006!
+ C4n y0u r34d th1s? 🏁 ➡️ 📁
+ ( *ಥ ⌂ * ) dont pwn pls
+ *finds file* UwU whats this?
+
diff --git a/src/msgs/welcome b/src/msgs/welcome
@@ -1,6 +0,0 @@
- ┌─┐┌┬┐┬ ┌┬┐┌─┐┌─┐┌┬┐┌─┐┬─┐ ┬
- └─┐ │ │ │││ ││ │ │ │├┬┘ ▓
- └─┘ ┴ ┴─┘─┴┘└─┘└─┘ ┴ └─┘┴└─ │
-
- We analyze your STL files!
-
diff --git a/src/stlfile.c b/src/stlfile.c
@@ -310,7 +310,7 @@ fail:
}
int
-parse_file(struct parseinfo *info, char *buf, size_t len)
+parse_file(struct parseinfo *info, char *buf, size_t len, char **modelname)
{
int status;
const char *resp;
@@ -333,17 +333,12 @@ parse_file(struct parseinfo *info, char *buf, size_t len)
: parse_file_bin(info, buf, len);
if (status == FAIL) return FAIL;
- if (!info->modelname) {
- resp = ask("Please enter your model name: ");
- if (strlen(resp) < 4) {
- ERR("Model name is too short!\n");
- return FAIL;
- }
- info->modelname = checkp(strdup(resp));
- }
-
if (!info->solidname) info->solidname = checkp(strdup(""));
+ /* transfer ownership */
+ info->modelname = *modelname;
+ *modelname = NULL;
+
info->hash = checkp(strdup(mhash(info->modelname, -1)));
return OK;
diff --git a/src/stlfile.h b/src/stlfile.h
@@ -9,7 +9,7 @@
#include "util.h"
-#define FMT_ERR(...) ERR("FORMAT " __VA_ARGS__)
+#define FMT_ERR(...) fprintf(stderr, "FORMAT ERR: " __VA_ARGS__)
enum {
KW_INVALID = -1,
@@ -47,7 +47,7 @@ struct parseinfo {
int type, valid;
};
-int parse_file(struct parseinfo *info, char *buf, size_t len);
+int parse_file(struct parseinfo *info, char *buf, size_t len, char **modelname);
int save_info(struct parseinfo *info, FILE *f);
int load_info(struct parseinfo *info, FILE *f);
void print_info(struct parseinfo *info);