enowars5-service-stldoctor

STL-Analyzing A/D Service for ENOWARS5 in 2021
git clone https://git.sinitax.com/sinitax/enowars5-service-stldoctor
Log | Files | Refs | README | LICENSE | sfeed.txt

commit dafdcc3547ec786fac85486cbb77909fffe1a4de
parent 130fc6d97bce96bc042bdaa6329b82d00f6316d5
Author: Louis Burda <quent.burda@gmail.com>
Date:   Mon, 17 May 2021 23:26:52 +0200

added basic checker flag and noise commands, still very basic, needs randomization and havoc

Diffstat:
Achecker/.dockerignore | 7+++++++
Achecker/.env | 1+
Achecker/.gitignore | 1+
Mchecker/Dockerfile | 1-
Dchecker/data/.gitkeep | 0
Mchecker/docker-compose.yml | 16+++++++---------
Mchecker/src/checker.py | 396+++++++++++++++++++++++++------------------------------------------------------
Mdocumentation/README.md | 6+++---
Mservice/entrypoint.sh | 2+-
Mservice/src/stldoctor | 0
Mservice/src/test.sh | 6+++---
11 files changed, 147 insertions(+), 289 deletions(-)

diff --git a/checker/.dockerignore b/checker/.dockerignore @@ -0,0 +1,6 @@ +data/ +Dockerfile +docker-compose.yml +.dockerignore +.gitignore +.env +\ No newline at end of file diff --git a/checker/.env b/checker/.env @@ -0,0 +1 @@ +COMPOSE_PROJECT_NAME=stldocker_checker diff --git a/checker/.gitignore b/checker/.gitignore @@ -0,0 +1 @@ +data/ diff --git a/checker/Dockerfile b/checker/Dockerfile @@ -13,5 +13,4 @@ RUN pip3 install -r requirements.txt # Copy all files into the container. COPY ./src/ /checker/ - ENTRYPOINT [ "/home/checker/.local/bin/gunicorn", "-c", "gunicorn.conf.py", "checker:app" ] diff --git a/checker/data/.gitkeep b/checker/data/.gitkeep diff --git a/checker/docker-compose.yml b/checker/docker-compose.yml @@ -1,23 +1,22 @@ version: '3' services: - # Give your container proper names! - n0t3b00k-checker: + stldoctor-checker: build: . # The checker runs a HTTP interfaces, so we need to map port 3031 to the outside (port 8000). ports: - 8000:3031 environment: - MONGO_ENABLED=1 - - MONGO_HOST=n0t3b00k-mongo + - MONGO_HOST=stldoctor-mongo - MONGO_PORT=27017 - - MONGO_USER=n0t3b00k_checker - - MONGO_PASSWORD=n0t3b00k_checker + - MONGO_USER=stldoctor_checker + - MONGO_PASSWORD=stldoctor_checker # The python checkerlib requires a mongo db! - n0t3b00k-mongo: + stldoctor-mongo: image: mongo volumes: - ./data:/data/db environment: - MONGO_INITDB_ROOT_USERNAME: n0t3b00k_checker - MONGO_INITDB_ROOT_PASSWORD: n0t3b00k_checker -\ No newline at end of file + MONGO_INITDB_ROOT_USERNAME: stldoctor_checker + MONGO_INITDB_ROOT_PASSWORD: stldoctor_checker diff --git a/checker/src/checker.py b/checker/src/checker.py @@ -5,17 +5,38 @@ import random import string #### Checker Tenets -# A checker SHOULD not be easily identified by the examination of network traffic => This one is not satisfied, because our usernames and notes are simple too random and easily identifiable. -# A checker SHOULD use unusual, incorrect or pseudomalicious input to detect network filters => This tenet is not satisfied, because we do not send common attack strings (i.e. for SQL injection, RCE, etc.) in our notes or usernames. +# A checker SHOULD not be easily identified by the examination of network traffic +# => satisfied, because checker uses regular user interface and picks strings from a wordlist +# to appear more human (TODO) +# A checker SHOULD use unusual, incorrect or pseudomalicious input to detect network filters +# => satisfied, send various garbage bytes for model name and file contents (TODO) #### - -class N0t3b00kChecker(BaseChecker): +samplestl = """ +solid + facet normal 1.0 0 0 + outer loop + vertex 0 1 0 + vertex 0 1 1 + vertex 0 0 1 + endloop + endfacet + facet normal 0 0 1.0 + outer loop + vertex 1 0 0 + vertex 1 1 0 + vertex 0 1 0 + endloop + endfacet +endsolid +""" + +class STLDoctorChecker(BaseChecker): """ Change the methods given here, then simply create the class and .run() it. Magic. A few convenient methods and helpers are provided in the BaseChecker. - ensure_bytes ans ensure_unicode to make sure strings are always equal. + ensure_bytes and ensure_unicode to make sure strings are always equal. As well as methods: self.connect() connects to the remote server. self.get and self.post request from http. @@ -28,30 +49,81 @@ class N0t3b00kChecker(BaseChecker): ##### EDIT YOUR CHECKER PARAMETERS flag_variants = 1 noise_variants = 1 - havoc_variants = 3 - service_name = "n0t3b00k" - port = 2323 # The port will automatically be picked up as default by self.connect and self.http. + havoc_variants = 0 + service_name = "stldoctor" + port = 9000 ##### END CHECKER PARAMETERS - def register_user(self, conn: SimpleSocket, username: str, password: str): - self.debug( - f"Sending command to register user: {username} with password: {password}" - ) - conn.write(f"reg {username} {password}\n") - conn.readline_expect( - b"User successfully registered", - read_until=b">", - exception_message="Failed to register user", - ) + def login_user(self, conn: SimpleSocket, password: str): + self.debug("Sending command to login.") + conn.write(f"login\n{password}\n") + conn.readline_expect(b"logged in!", read_until=b"$", exception_message="Failed to log in") + + def generate_file(self, filetype: str = "ascii", extra: str = ""): + if filetype == "ascii": + # TODO handle extra as solidname and gen randomly + return samplestl + elif filetype == "bin": + # TODO handle extra as header + return samplestl # TODO: this is not a binary STL! + else: + raise EnoException("Invalid file type supplied"); + + def putfile(self, conn: SimpleSocket, solidname = "TODO", modelname = "TODO"): + # Generate file contents + stlfile = samplestl + + # Upload file + self.debug("Sending command to submit file") + conn.write(f"submit\n{len(stlfile)}\n{stlfile}{modelname}\n") + conn.read_until(b"with ID ") + + # Parse ID + fileid = conn.read_until(b"!") + if fileid == b"": + raise BrokenServiceException("Unable to upload file!") + self.debug(f"Got ID {fileid}") + + conn.read_until(b"$") + + return stlfile, fileid + + def getfile(self, conn: SimpleSocket, modelname: str): + if modelname != "": + self.debug(f"Sending command to retrieve file with '{modelname}'") + conn.write(f"query\n{modelname}\n0\ny\n") + else: + self.debug(f"Sending command to retrieve file") + conn.write(f"query\n0\ny\n") + + resp = conn.read_until(b"$") + + return resp + + def querydb(self, *args): + self.debug("Querying db contents"); + vals = [] + for arg in args: + try: + val: str = self.chain_db[arg] + except IndexError as ex: + raise BrokenServiceException("Invalid db contents") + vals.append(val) + return vals - def login_user(self, conn: SimpleSocket, username: str, password: str): - self.debug(f"Sending command to login.") - conn.write(f"log {username} {password}\n") - conn.readline_expect( - b"Successfully logged in!", - read_until=b">", - exception_message="Failed to log in", - ) + def postdb(self, vdict): + self.chain_db = vdict + + def openconn(self): + self.debug("Connecting to service") + conn = self.connect() + conn.read_until("$") # ignore welcome + return conn + + def closeconn(self, conn: SimpleSocket): + self.debug("Sending exit command") + conn.write("exit\n") + conn.close() def putflag(self): # type: () -> None """ @@ -65,55 +137,11 @@ class N0t3b00kChecker(BaseChecker): the preferred way to report errors in the service is by raising an appropriate enoexception """ if self.variant_id == 0: - # First we need to register a user. So let's create some random strings. (Your real checker should use some funny usernames or so) - username: str = "".join( - random.choices(string.ascii_uppercase + string.digits, k=12) - ) - password: str = "".join( - random.choices(string.ascii_uppercase + string.digits, k=12) - ) - - # Log a message before any critical action that could raise an error. - self.debug(f"Connecting to service") - # Create a TCP connection to the service. - conn = self.connect() - welcome = conn.read_until(">") - - # Register a new user - self.register_user(conn, username, password) - - # Now we need to login - self.login_user(conn, username, password) - - # Finally, we can post our note! - self.debug(f"Sending command to set the flag") - conn.write(f"set {self.flag}\n") - conn.read_until(b"Note saved! ID is ") - - try: - # Try to retrieve the resulting noteId. Using rstrip() is hacky, you should probably want to use regular expressions or something more robust. - noteId = conn.read_until(b"!\n>").rstrip(b"!\n>").decode() - except Exception as ex: - self.debug(f"Failed to retrieve note: {ex}") - raise BrokenServiceException("Could not retrieve NoteId") - - assert_equals(len(noteId) > 0, True, message="Empty noteId received") - - self.debug(f"Got noteId {noteId}") - - # Exit! - self.debug(f"Sending exit command") - conn.write(f"exit\n") - conn.close() - - # Save the generated values for the associated getflag() call. - # This is not a real dictionary! You cannot update it (i.e., self.chain_db["foo"] = bar) and some types are converted (i.e., bool -> str.). See: https://github.com/enowars/enochecker/issues/27 - self.chain_db = { - "username": username, - "password": password, - "noteId": noteId, - } - + conn = self.openconn() + modelname = self.flag + stlfile, fileid = self.putfile(conn, modelname = modelname) + self.closeconn(conn) + self.chain_db = { "fileid": fileid, "modelname": modelname } else: raise EnoException("Wrong variant_id provided") @@ -128,34 +156,11 @@ class N0t3b00kChecker(BaseChecker): the preferred way to report errors in the service is by raising an appropriate enoexception """ if self.variant_id == 0: - # First we check if the previous putflag succeeded! - try: - username: str = self.chain_db["username"] - password: str = self.chain_db["password"] - noteId: str = self.chain_db["noteId"] - except IndexError as ex: - self.debug(f"error getting notes from db: {ex}") - raise BrokenServiceException("Previous putflag failed.") - - self.debug(f"Connecting to the service") - conn = self.connect() - welcome = conn.read_until(">") - - # Let's login to the service - self.login_user(conn, username, password) - - # Let´s obtain our note. - self.debug(f"Sending command to retrieve note: {noteId}") - conn.write(f"get {noteId}\n") - note = conn.read_until(">") - assert_in( - self.flag.encode(), note, "Resulting flag was found to be incorrect" - ) - - # Exit! - self.debug(f"Sending exit command") - conn.write(f"exit\n") - conn.close() + fileid, modelname = self.querydb("fileid", "modelname") + conn = self.openconn() + resp = self.getfile(conn, modelname) + assert_in(modelname.encode(), resp, "Resulting flag was found to be incorrect") + self.closeconn(conn) else: raise EnoException("Wrong variant_id provided") @@ -172,53 +177,11 @@ class N0t3b00kChecker(BaseChecker): the preferred way to report errors in the service is by raising an appropriate enoexception """ if self.variant_id == 0: - self.debug(f"Connecting to the service") - conn = self.connect() - welcome = conn.read_until(">") - - # First we need to register a user. So let's create some random strings. (Your real checker should use some better usernames or so [i.e., use the "faker¨ lib]) - username = "".join( - random.choices(string.ascii_uppercase + string.digits, k=12) - ) - password = "".join( - random.choices(string.ascii_uppercase + string.digits, k=12) - ) - randomNote = "".join( - random.choices(string.ascii_uppercase + string.digits, k=36) - ) - - # Register another user - self.register_user(conn, username, password) - - # Now we need to login - self.login_user(conn, username, password) - - # Finally, we can post our note! - self.debug(f"Sending command to save a note") - conn.write(f"set {randomNote}\n") - conn.read_until(b"Note saved! ID is ") - - try: - noteId = conn.read_until(b"!\n>").rstrip(b"!\n>").decode() - except Exception as ex: - self.debug(f"Failed to retrieve note: {ex}") - raise BrokenServiceException("Could not retrieve NoteId") - - assert_equals(len(noteId) > 0, True, message="Empty noteId received") - - self.debug(f"{noteId}") - - # Exit! - self.debug(f"Sending exit command") - conn.write(f"exit\n") - conn.close() - - self.chain_db = { - "username": username, - "password": password, - "noteId": noteId, - "note": randomNote, - } + conn = self.openconn() + modelname = "NOISE" # TODO + contents, fileid = self.putfile(conn, modelname = modelname) + self.closeconn(conn) + self.postdb({ "fileid": fileid, "modelname": modelname, "contents": contents }) else: raise EnoException("Wrong variant_id provided") @@ -235,35 +198,11 @@ class N0t3b00kChecker(BaseChecker): the preferred way to report errors in the service is by raising an appropriate enoexception """ if self.variant_id == 0: - try: - username: str = self.chain_db["username"] - password: str = self.chain_db["password"] - noteId: str = self.chain_db["noteId"] - randomNote: str = self.chain_db["note"] - except Exception as ex: - self.debug("Failed to read db {ex}") - raise BrokenServiceException("Previous putnoise failed.") - - self.debug(f"Connecting to service") - conn = self.connect() - welcome = conn.read_until(">") - - # Let's login to the service - self.login_user(conn, username, password) - - # Let´s obtain our note. - self.debug(f"Sending command to retrieve note: {noteId}") - conn.write(f"get {noteId}\n") - conn.readline_expect( - randomNote.encode(), - read_until=b">", - exception_message="Resulting flag was found to be incorrect" - ) - - # Exit! - self.debug(f"Sending exit command") - conn.write(f"exit\n") - conn.close() + fileid, modelname, contents = self.querydb("fileid", "modelname", "contents") + conn = self.openconn() + resp = self.getfile(conn, modelname) + assert_in(contents.encode(), resp, "Noise file content was found to be incorrect") + self.closeconn(conn) else: raise EnoException("Wrong variant_id provided") @@ -276,98 +215,11 @@ class N0t3b00kChecker(BaseChecker): If nothing is returned, the service status is considered okay. The preferred way to report Errors in the service is by raising an appropriate EnoException """ - self.debug(f"Connecting to service") - conn = self.connect() - welcome = conn.read_until(">") - - if self.variant_id == 0: - # In variant 1, we'll check if the help text is available - self.debug(f"Sending help command") - conn.write(f"help\n") - is_ok = conn.read_until(">") + return - for line in [ - "This is a notebook service. Commands:", - "reg USER PW - Register new account", - "log USER PW - Login to account", - "set TEXT..... - Set a note", - "user - List all users", - "list - List all notes", - "exit - Exit!", - "dump - Dump the database", - "get ID", - ]: - assert_in(line.encode(), is_ok, "Received incomplete response.") - - elif self.variant_id == 1: - # In variant 2, we'll check if the `user` command still works. - username = "".join( - random.choices(string.ascii_uppercase + string.digits, k=12) - ) - password = "".join( - random.choices(string.ascii_uppercase + string.digits, k=12) - ) - - # Register and login a dummy user - self.register_user(conn, username, password) - self.login_user(conn, username, password) - - self.debug(f"Sending user command") - conn.write(f"user\n") - ret = conn.readline_expect( - "User 0: ", - read_until=b">", - exception_message="User command does not return any users", - ) - - if username: - assert_in(username.encode(), ret, "Flag username not in user output") - - elif self.variant_id == 2: - # In variant 2, we'll check if the `list` command still works. - username = "".join( - random.choices(string.ascii_uppercase + string.digits, k=12) - ) - password = "".join( - random.choices(string.ascii_uppercase + string.digits, k=12) - ) - randomNote = "".join( - random.choices(string.ascii_uppercase + string.digits, k=36) - ) - - # Register and login a dummy user - self.register_user(conn, username, password) - self.login_user(conn, username, password) - - self.debug(f"Sending command to save a note") - conn.write(f"set {randomNote}\n") - conn.read_until(b"Note saved! ID is ") - - try: - noteId = conn.read_until(b"!\n>").rstrip(b"!\n>").decode() - except Exception as ex: - self.debug(f"Failed to retrieve note: {ex}") - raise BrokenServiceException("Could not retrieve NoteId") - - assert_equals(len(noteId) > 0, True, message="Empty noteId received") - - self.debug(f"{noteId}") - - self.debug(f"Sending list command") - conn.write(f"list\n") - conn.readline_expect( - noteId.encode(), - read_until=b'>', - exception_message="List command does not work as intended" - ) - - else: - raise EnoException("Wrong variant_id provided") - - # Exit! - self.debug(f"Sending exit command") - conn.write(f"exit\n") - conn.close() + # TODO! + conn = self.openconn() + self.closeconn(conn) def exploit(self): """ @@ -382,6 +234,6 @@ class N0t3b00kChecker(BaseChecker): pass -app = N0t3b00kChecker.service # This can be used for uswgi. +app = STLDoctorChecker.service # This can be used for uswgi. if __name__ == "__main__": - run(N0t3b00kChecker) + run(STLDoctorChecker) diff --git a/documentation/README.md b/documentation/README.md @@ -1,7 +1,7 @@ -PrintDoc -======== +STLDoctor +========= -PrintDoc is a plain-text protocol service, that allows users to upload files +STLDoctor is a plain-text protocol service, that allows users to upload STL files and generate reports that include information on the files.. - model name diff --git a/service/entrypoint.sh b/service/entrypoint.sh @@ -9,6 +9,6 @@ while [ 1 ]; do sleep 180 done & -servicecmd='socat -T30 -s TCP-LISTEN:9000,reuseaddr,fork EXEC:"/service/printdoc",raw,pty,echo=0,stderr' +servicecmd='socat -T30 -s TCP-LISTEN:9000,reuseaddr,fork EXEC:"/service/stldoctor",raw,pty,echo=0,stderr' su -s /bin/sh -c "$servicecmd" service diff --git a/service/src/stldoctor b/service/src/stldoctor Binary files differ. diff --git a/service/src/test.sh b/service/src/test.sh @@ -2,7 +2,7 @@ set -e -RUN_REMOTE=1 +# RUN_REMOTE=1 export RESULTDIR="../data/scans" export ECHO_INPUT=1 @@ -22,7 +22,7 @@ print() } checkleaks() { - valgrind --leak-check=full ./printdoc 2>&1 | tee /tmp/testlog + valgrind --leak-check=full ./stldoctor 2>&1 | tee /tmp/testlog if [ -z "$(grep "no leaks are possible" /tmp/testlog)" ]; then echo "Valgrind exited with errors!" exit 1 @@ -33,7 +33,7 @@ connect() { if [ $RUN_REMOTE ]; then nc localhost 9000 else - ./printdoc + ./stldoctor fi }