Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 4 additions & 7 deletions app/controllers/crackme.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@

# Upload folder for crackmes
UPLOAD_FOLDER = 'tmp/crackme'
# Source archives for auto-validated crackmes. Never served: this directory sits
# Optional source archives. Never served: this directory sits
# outside static/ so the only way to read one is the reviewer download route.
SOURCE_UPLOAD_FOLDER = 'private/crackme_source'
MAX_FILE_SIZE = 10 * 1024 * 1024 # 10MB
Expand Down Expand Up @@ -318,8 +318,7 @@ def upload_crackme_post():
if is_single_file_archive(file_data):
return _upload_rejected('Archives containing only one file are not allowed. Please upload the file directly without wrapping it in an archive.')

# Auto-validation opt-in: the flag users will submit, plus the private source
# archive a reviewer needs to confirm that flag is actually the right one.
# Auto-validation and the private reviewer archive are independent choices.
flag = None
source_data = None
source_filename = None
Expand All @@ -338,10 +337,8 @@ def upload_crackme_post():
return _upload_rejected('Points must be a whole number from 100 to 600.')
official_difficulty = points / 100

source = request.files.get('source')
if source is None or source.filename == '':
return _upload_rejected('Auto-validation needs a source archive so reviewers can verify the flag.')

source = request.files.get('source')
if source is not None and source.filename:
source_data = source.read()
if len(source_data) > MAX_FILE_SIZE:
return _upload_rejected('The source archive is too large!')
Expand Down
4 changes: 2 additions & 2 deletions app/models/crackme.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,8 +316,8 @@ def crackme_create_prepare(name, info, username, lang, arch, platform, size, ori
Its presence is what marks a crackme as auto-validated -- there is
no separate flag to keep in sync. Stored in cleartext for reviewers;
never rendered on the public site.
source_original_filename: Filename of the private source archive that
accompanies an auto-validated submission (reviewers only).
source_original_filename: Filename of an optional private archive
available to reviewers, independent of auto-validation.
"""
if not check_connection():
raise ErrUnavailable("Database is unavailable")
Expand Down
24 changes: 15 additions & 9 deletions templates/crackme/create.html
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ <h3>Quick Rules</h3>
</div>
<div class="col-9 col-sm-12">
<textarea class="form-input" id="info" name="info" placeholder="Textarea" rows="3">{{ form.info|default('') }}</textarea>
<p class="form-input-hint">For a keygenme, name the required username here (for example, CrackMes1) so the accepted key is unique.</p>
</div>
</div>
<div class="form-group">
Expand All @@ -108,8 +109,8 @@ <h3>Quick Rules</h3>
</div>
<div class="col-9 col-sm-12">
<label class="form-checkbox">
<input type="checkbox" id="auto_validation" name="auto_validation"{{ '' if form and not form.auto_validation else ' checked' }}>
<i class="form-icon"></i> Optional. This crackme has a unique flag and can be auto-validated.
<input type="checkbox" id="auto_validation" name="auto_validation"{{ ' checked' if form.auto_validation|default(false) else '' }}>
<i class="form-icon"></i> Optional. Let solvers submit a unique flag for points.
</label>
<div id="auto-validation-fields" style="display: none;">
<p class="text-gray" style="font-size: 0.8rem; margin: 6px 0;">
Expand All @@ -119,14 +120,20 @@ <h3>Quick Rules</h3>
<label class="form-label" for="points" style="margin-top: 8px;">Points</label>
<input class="form-input" type="number" id="points" name="points" min="100" max="600" step="1" value="{{ form.points|default('') }}">
<p class="form-input-hint">Defaults to difficulty &times; 100. You may choose any whole number from 100 to 600.</p>
<p class="text-gray" style="font-size: 0.8rem; margin: 6px 0;">
Upload a ZIP containing source code, build files, a writeup, or anything else needed to verify the flag.
<b>Reviewers only</b> &mdash; it remains private.
</p>
<input class="form-input upload-btn" type="file" id="source" name="source">
</div>
</div>
</div>
<div class="form-group">
<div class="col-3 col-sm-12">
<label class="form-label" for="source">Source archive (optional)</label>
</div>
<div class="col-9 col-sm-12">
<p class="text-gray" style="font-size: 0.8rem; margin: 6px 0;">
You may upload a ZIP with source code or build notes for reviewers. This is private and independent of auto-validation.
</p>
<input class="form-input upload-btn" type="file" id="source" name="source">
</div>
</div>
<div class="form-group">
<div class="col-3 col-sm-12">
<label class="form-label">Labels</label>
Expand Down Expand Up @@ -156,8 +163,7 @@ <h3>Quick Rules</h3>
<script>LabelCheckboxes.init(document.getElementById('upload-form'));</script>
<script>
(function() {
// The flag and source inputs only mean anything once auto-validation is
// ticked, so keep them out of the way until then.
// Show flag and points only when auto-validation is selected.
var toggle = document.getElementById('auto_validation');
var fields = document.getElementById('auto-validation-fields');
if (!toggle || !fields) return;
Expand Down
7 changes: 2 additions & 5 deletions templates/faq/faq.html
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,8 @@ <h3 id="auto-validation-points">How do auto-validation and points work? <a href=
crackme, you can rate its quality.</p>
<div class="divider"></div>
<h3 id="auto-validation">How do I add a flag to my own crackme? <a href="#auto-validation" class="anchor-link">#</a></h3>
<p>Tick <b>Auto-validation</b> when you upload it, provide the flag and point value, and attach a ZIP with your source code and
build scripts. That archive is for reviewers only &mdash; it is never published or downloadable &mdash; and
it's how a reviewer confirms your flag is really the right one before your crackme goes live. Reviewers can
read your flag; nobody else can, and it is never rendered anywhere on the site. If it turns out to be wrong,
a reviewer can correct it.</p>
<p>Tick <b>Auto-validation</b> and provide the flag and point value. A source ZIP is optional and independent of auto-validation;
only reviewers can access it. Reviewers can read and correct the flag, but it is never shown publicly.</p>
<div class="divider"></div>
<h3 id="submit-writeup">How do I submit a writeup? <a href="#submit-writeup" class="anchor-link">#</a></h3>
<p>First, you must login or register for an account. Then, navigate to the crackme page and upload your writeup
Expand Down
9 changes: 4 additions & 5 deletions templates/rules/crackmerules.html
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,15 @@ <h3>Submission Details</h3>
</ul>

<h3>Auto-validation (optional)</h3>
<p>Tick <b>Auto-validation</b> if you want solvers to prove they beat your crackme and earn points for it. Two
things are needed:</p>
<p>Auto-validation and providing source code are separate, optional choices. Tick <b>Auto-validation</b> if you want solvers to submit a flag for points. It requires:</p>
<ul>
<li><b>The flag</b> your crackme prints when it is solved, in the format <code>CMO{...}</code> &mdash; no
spaces or braces inside. Reviewers can read it, because they need it to confirm your crackme is
solvable; it is never shown anywhere on the site.</li>
<li><b>A source archive</b> (zip) with your source code, build scripts and anything else needed to rebuild
the crackme. It is visible to <b>reviewers only</b>, never published or downloadable, and it is how a
reviewer confirms the flag you gave is really the right one.</li>
</ul>
<p>You may separately upload a private source ZIP for reviewers, with or without auto-validation. For a keygenme,
make the submitted flag correspond to a specific username, such as <code>CrackMes1</code>. State that username
in the description, or solvers may find many valid username-key pairs.</p>
<p>A reviewer assigns the crackme an official difficulty when approving it, and that fixes what a solve of it is
worth. Both it and the flag can be corrected later by a reviewer. Scoring is new and the exact numbers may
still change.</p>
Expand Down
24 changes: 19 additions & 5 deletions tests/test_solves.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ def test_upload_without_opting_in_stores_no_flag(
assert db.crackme.find_one({'name': 'Flagged Challenge'})['flag'] is None


def test_auto_validation_requires_a_well_formed_flag_and_a_source_archive(
def test_auto_validation_requires_a_well_formed_flag_but_no_source_archive(
alice_client, db, alice, tmp_path, monkeypatch):
bad_flag = _upload(alice_client, monkeypatch, tmp_path,
auto_validation='on', flag='not-a-flag',
Expand All @@ -156,8 +156,22 @@ def test_auto_validation_requires_a_well_formed_flag_and_a_source_archive(
auto_validation='on', flag=FLAG)

assert b'Invalid flag format' in bad_flag.data
assert b'needs a source archive' in no_source.data
assert db.crackme.count_documents({}) == 0
assert no_source.status_code == 200
stored = db.crackme.find_one({'name': 'Flagged Challenge'})
assert stored['flag'] == FLAG
assert stored['source_original_filename'] is None


def test_source_archive_without_auto_validation(
alice_client, db, alice, tmp_path, monkeypatch):
response = _upload(alice_client, monkeypatch, tmp_path,
source=(_zip_bytes(), 'source.zip'))

assert response.status_code == 200
stored = db.crackme.find_one({'name': 'Flagged Challenge'})
assert stored['flag'] is None
assert stored['source_original_filename'] == 'source.zip'
assert (tmp_path / 'source' / stored['hexid']).exists()


def test_auto_validation_requires_the_verification_file_to_be_a_zip(
Expand Down Expand Up @@ -972,10 +986,10 @@ def test_background_submits_get_the_confirmation_url_on_success(
assert alice_client.get('/upload/crackme/submitted').status_code == 302


def test_auto_validation_is_ticked_by_default_on_a_fresh_form(alice_client, alice):
def test_auto_validation_is_unticked_by_default_on_a_fresh_form(alice_client, alice):
body = alice_client.get('/upload/crackme').data.decode()

assert 'id="auto_validation" name="auto_validation" checked' in body
assert 'id="auto_validation" name="auto_validation" checked' not in body
assert 'id="points" name="points" min="100" max="600" step="1"' in body
# Labels sit at the end of the form, after the auto-validation block.
assert body.index('Auto-validation') < body.index('Select all anti-analysis')
Expand Down
Loading