CHALLENGE OVERVIEW

Othernote is a basic note taking website that has the options to create and edit notes for a logged user. The backend is written in python. The main endpoints are:

  • /login where a user can login with his credentials
  • /signup where you can create your credentials for login
  • /notes to display all the notes of a logged user
  • /notes/create to create a new note
  • /notes/<string:note_id>/update to update a note based on its note_id
  • /admin which can only be accessed by admin user and contains the flag

VULNERABILITIES

The function responsible of retrieving the notes for a user is vulnerable to path traversals:

def load_user_notes(username):
	user_notes_file = os.path.join("user_notes", f"{username}.json")
	if os.path.exists(user_notes_file):
		with open(user_notes_file, 'r') as file:
			data = json.load(file)
			return {k: Note(v) for k, v in data.items()}

As you can see the username is used to get the json file containing the user’s notes. So a username like ./admin can make us read admin’s notes. Unfortunately this is not useful at all.

Another sus function in my opinion was merge:

def merge(src, dst):
	for k, v in src.items():
		if hasattr(dst, '__getitem__'):
			if dst.get(k) and type(v) == dict:
				merge(v, dst.get(k))
			else:
				dst[k] = v
		elif hasattr(dst, k) and type(v) == dict:
			merge(v, getattr(dst, k))
		else:
			setattr(dst, k, v)

It is used to merge two json dictionaries together and it is called when we update a note.

This function made me think of prototype pollution, but as I never exploited this vulnerability on a python backend I searched if it would be possible, and sure it was! I found this article on Hacktricks and basically copy-pasted the payload.


EXPLOITATION

The exploitation phase went as follows:

  • create an account and log in
  • create a note
  • update note making a PUT request with the following body
{"__class__":{"__init__":{"__globals__":{"session":{"username":"admin"}}}}}

this pollutes the session dict, making us admin

  • the server responds with a new session cookie
  • using that cookie we can GET /admin to get the flag

FLAG: codegate2024{78a5e12a3f3cdff9dfd8fc62215312abad910c78296d57003e5bf8b842b740aeb750eed0bfb54ddd30194baecfb5f2ebccd9be7bb4efa9}