Skip to content

Latest commit

 

History

History
134 lines (99 loc) · 6.34 KB

File metadata and controls

134 lines (99 loc) · 6.34 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

This is the pwn.college challenge monorepo containing cybersecurity CTF challenges. Challenges are organized as directories under modules (e.g., web-security, etc.) and are built as Docker containers for deployment.

Key Commands

Dev Environment (Nix)

Preferred workflow is to use the repo's Nix flake dev shell:

nix develop
pwnshop test challenges/web-security/path-traversal-1

Requirements for nix develop: Linux (x86_64-linux), systemd, sudo, and Nix flakes enabled (experimental-features = nix-command flakes in ~/.config/nix/nix.conf or /etc/nix/nix.conf). See docs/development.md.

Challenge CLI

All workflows run through the pwnshop CLI (implemented in tools/pwnshop/src/pwnshop/commands and backed by shared helpers in tools/pwnshop/src/pwnshop/lib).

Each subcommand accepts either a direct path or a challenge slug (e.g., challenge/web-security/path-traversal-1). Slugs must contain the base path to all challenges ("challenge"), then the module and challenge.

Primary commands:

# Test a challenge end-to-end
pwnshop test challenges/MODULE_ID/CHALLENGE_ID

# Example: Test path-traversal-1
pwnshop test challenges/web-security/path-traversal-1

# Render a single template file for debugging
pwnshop render challenges/MODULE_ID/CHALLENGE_ID/path/to/file.j2 --output /tmp/rendered-file

# Build a challenge image without tests
pwnshop build challenges/MODULE_ID/CHALLENGE_ID

# List challenges grouped by key, optionally filtered by git history
pwnshop list --modified-since origin/main

# Drop into an interactive shell (use --user/--volume, or append a command)
pwnshop run --user 0 --volume /tmp/debug challenges/web-security/path-traversal-1 /bin/ls -la /challenge

DO NOT run these scripts without pwnshop: the dependencies are not installed in the host, and some of these challenges do permanent damage to their environment.

Architecture

Directory Structure

  • challenges/MODULE_ID/CHALLENGE_ID/challenge/: Challenge source code and artifacts. IF YOU PROVIDE A DOCKERFILE, PUT IT HERE
  • challenges/MODULE_ID/CHALLENGE_ID/tests_public/: Unencrypted functionality tests
  • challenges/MODULE_ID/CHALLENGE_ID/tests_private/: Encrypted exploitation tests
  • challenges/MODULE_ID/common/: Shared Jinja2 templates for the module

Templating System

  • Files ending in .j2 are Jinja2 templates rendered during build
  • Templates receive a challenge object with seeded RNG functions
  • Template permissions are preserved when rendering (make .j2 files executable if output should be executable)
  • Python templates are auto-formatted with Black
  • C templates are auto-formatted with astyle

Template Inheritance and Variables

  • Challenge templates should use {% extends %} not {% include %} when referencing shared templates from common/
  • Use {% block setup %} to set variables on the settings namespace
  • Call super() in setup blocks to preserve parent template initialization
  • The settings namespace is created by flask.py.j2 and passed through the template hierarchy
  • Random values (endpoints, parameters) are generated by random_names.j2 macro

Critical Template Details

  • Python scripts that need SUID should use shebang: #!/usr/bin/exec-suid -- /usr/bin/python3 -I
  • Web services (Flask apps) should set app.config['SERVER_NAME'] = "challenge.localhost:80" and run on port 80
  • Template variables in strings need double braces: {{variable}} not {variable}
  • When using randomization in generated code, use the random context variable (e.g., {{random.randrange()}}) to evaluate at template time
  • Binary challenges may not need templates at all - they can be compiled and placed directly in challenge/

Docker Build Process

  1. Every challenge must ship a Dockerfile.j2 (typically {% include "common/Dockerfile.j2" %}) — there is no automatic default.
  2. Copies challenge/ directory to /challenge in container
  3. Executes .setup script if present during build
  4. Executes .init script if present at container startup

Testing Framework

  • Tests run in temporary containers with a random flag at /flag
  • Test files must be named test_*.py or test_*.py.j2 and must be executable (chmod +x)
  • Tests receive FLAG environment variable
  • Public tests verify functionality
  • Private tests contain exploitation logic

Challenge Development Workflow

  1. Create challenge directory: challenges/MODULE_ID/CHALLENGE_ID/
  2. Create or extend common templates in challenges/MODULE_ID/common/ if needed
  3. Add challenge files to challenge/ directory (binaries, scripts, configs, etc.)
  4. If using templates, extend the appropriate common template and set variables in {% block setup %}
  5. Make executable files and templates executable: chmod +x challenges/MODULE_ID/CHALLENGE_ID/**/*.j2
  6. Write tests_public/test_*.py.j2 for functionality verification
  7. Write tests_private/test_*.py.j2 for exploitation verification
  8. Test with: pwnshop test MODULE_ID/CHALLENGE_ID

Example Challenge Template Structures

Web Challenge (Flask-based)

{%- extends "common/sqli-pw.py.j2" -%}

{% block setup %}
  {{- super() -}}
  {%- set settings.pw_name = "pin" -%}
  {%- set settings.guest_pw = 1337 -%}
  {%- set settings.admin_pw_code = "random.randrange(2**32, 2**63)" -%}
{% endblock %}

Binary Challenge

Can be a C program with or without templating, depending on randomization needs. Will need to be compiled in an executable .setup file or its custom dockerfile

Script Challenge

Can be a simple Python/Bash script with or without templating, depending on randomization needs

Important Notes

  • Docker is required for building and testing challenges
  • The exec-suid utility is automatically included for SUIDing interpreted programs
  • Common Dockerfile lives at challenges/common/Dockerfile.j2; include/extend it or provide a custom one as needed.
  • Challenge verification should be split between public (functionality) and private (exploitation) tests
  • The challenge object is available in templates with a seeded random attribute for deterministic randomization
  • Use existing common templates where possible (flask.py.j2, cmdi.py.j2, sqli-pw.py.j2, etc.)
  • Study existing challenges (cmdi-, path-traversal-) for patterns and conventions