This repository will one day contain all core pwn.college challenges.
The basic idea is that challenges are just directories in here, similar to how they're specified in dojos currently. They'll be auto-built out of these directories, probably as docker files (though some nix stuff is another possibility), and deployed seamlessly.
The repository structure is:
./$MODULE_ID/$CHALLENGE_ID
: each challenge is a directory, and multiple challenge directories make up a module./$MODULE_ID/$CHALLENGE_ID/challenge
: this directory contains challenge artifacts (code, etc) that are used for building and deployment./$MODULE_ID/$CHALLENGE_ID/tests_public
: unencrypted tests used to test challenge functionality before deployment and also to test community contributions via CI./$MODULE_ID/$CHALLENGE_ID/tests_private
: encrypted tests used to ensure challenge solvability before deployment
There are a few options in the challenge building process, depending on the needs of the challenge.
Normally, when a challenge is built, its ./$MODULE_ID/$CHALLENGE_ID/challenge
is just deployed into /challenge in the default docker container (simple ubuntu:24.04
with some preinstalled packages and exec-suid
for SUIDing interpreted programs.
At the end of the container built process, if a ./$MODULE_ID/$CHALLENGE_ID/.setup
file exists, it will be executed in the docker build context.
When the container is launched for a user to attempt the challenge, if a ./$MODULE_ID/$CHALLENGE_ID/.init
file exists (built as /challenge/.init
in the resulting challenge image), it will be executed before as the challenge container starts up.
Tests are copied into a temporary directory in a running challenge container and executed with the FLAG
variable set to the value of the random flag generated to /flag
.
If a challenge needs build customization beyond .setup
, it can specify its own ./$MODULE_ID/$CHALLENGE_ID/challenge/Dockerfile
, which will be used instead of the default dockerfile.
Any file that ends in .j2
is automatically jinja2-formatted during challenge building.
Templates are passed in a challenge
parameter that has a few useful utility functions, mostly related to seeded RNG use at template time (e.g., {{challenge.random.randrange()}}
).
Examples abound around the repository.
Template variables in output strings need double braces: {{variable}}
.
This can be confusing in python code, because python f-strings use single braces ({variable}
), so keep that in mind.
There are base templates sprinkled around this repository in base_templates
subdirectories.
These templates typically {% block %}
areas that you can override in your child template.
Use {% extends %}
to extend a base template and tweak these blocks, not {% include %}
.
Most base templates included in this repository support various customizations via a settings
namespace that can be customized by child templates in a setup
block.
Set variables in {% block setup %}
blocks and call {{- super() -}}
to preserve parent initialization.
web_security/base_templates/flask.py.j2
- Base Flask application templateweb_security/base_templates/names.j2
- Macro for generating random endpoints and parameter namesdefault-dockerfile.j2
- Default Docker container template
Anything can be templated, including ./$MODULE_ID/$CHALLENGE_ID/challenge/Dockerfile.j2
(for example, to extend the default-dockerfile.j2
template with additional packages to install and so on).
Before extending the default template, consider if it would not be easier and more understandable to just make a full Dockerfile
.
Permissions are preserved when rendering a template.
If you want something executable, make the template executable (chmod +x *.j2
).
The repository contains all you need to build these challenges.
Install required Python packages in a virtual environment:
pip install jinja2 black pyastyle pwntools
# build and test
./build.py --test web_security/path-traversal-1
# build without testing into a directory to look at
./build.py web_security/path-traversal-1 --output-dir /tmp/output
# if you want to see a single file (for easier debugging)
./build.py web_security/path-traversal-1/tests_public/test_normal.py.j2
- All
.j2
template files that should produce executable output must be made executable first - Python scripts needing SUID should use the shebang:
#!/usr/bin/exec-suid -- /usr/bin/python3 -I
- Web services (if any) should run on
challenge.localhost
port 80 (tests are executed with--add-host challenge.localhost:127.0.0.1
for proper networking) - Not all challenges need templates - use them only when randomization or reuse is important.
FUTURE WORK.
This repository will contain various yamls files for different dojos.
These dojos will specify modules to include.
Each module will have its own module.yml
.
We are in the process of porting the pwnshop-based curriculum and the multi-repo based curriculum to this format.
The old model was: each dojo would be its own repository (e.g., intro-to-cybersecurity-dojo, program-security-dojo, systems-security-dojo, software-exploitation-dojo, etc) of ./$MODULE_ID/$CHALLENGE_ID
directories full of files to deploy into /challenge
.
Sometimes, these files were separately generated via pwnshop from the pwncollege-challenges repository, which has ./pwncollege_modules/$SLIGHTLY_DIFFERENT_MODULE_ID/__init__.py
files specifying challenge and verification logic.
These __init__.py
files also specify jinja2 templates, sprinkled around the pwncollege-challenges and pwnshop repositories.
The dojo repositories' module.yml
files would sometimes specify the pwnshop details for challenges.
The process of porting is:
- Identify the challenges to be ported by reading the
module.yml
file. - Determine any connections to the pwnshop templates by looking at
module.yml
. - Look at
pwncollege-modules/$WHATEVER_MODULE_ID/__init__.py
for the appropriate class and determine the template and challenge logic to port out, if any. - Create base templates in
./$MODULE_ID/base_templates/
if multiple challenges share patterns - Port challenge files to
./$MODULE_ID/$CHALLENGE_ID/challenge/
(binaries, scripts, configs, etc.) - If using templates, use
{% extends %}
and{% block setup %}
for customization - Ensure all executable files are marked as such:
chmod +x ./$MODULE_ID/$CHALLENGE_ID/**/*.j2
- Port verification logic to
./$MODULE_ID/$CHALLENGE_ID/tests_public
(functionality) and./$MODULE_ID/$CHALLENGE_ID/tests_private
(exploitation) - Test thoroughly:
./build.py --test $MODULE_ID/$CHALLENGE_ID
Some gaps we still need to fill:
- different architectures. Does docker cover all usecases?
- i have some doubts that this challenges-are-jinja-templates style will scale to all challenge complexities. yan85 has some hella spaghetti code. Maybe refactors will help, or maybe we'll need to have some other workarounds.
- should we follow symlinks during challege building? We do in the legacy case. Maybe only follow them if they point outside of the
challenge
dir? That might also be a PITA, though.