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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ static/solution/*
config/config.json
users.json
.env
review/review_operations.log
review/users.json

# Testing
.pytest_cache/
Expand Down
69 changes: 69 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ The source code for [crackmes.one](https://crackmes.one), a platform for sharing

- Python 3.8+
- MongoDB 4.0+
- `zip` command (for creating password-protected archives when approving submissions)

## Installation

Expand Down Expand Up @@ -124,6 +125,12 @@ crackmesone_python/
│ ├── crackmesone.service # Systemd service file
│ ├── setup.sh # First-time setup script
│ └── deploy.sh # Deployment script
├── review/ # Reviewer tool (moderation interface)
│ ├── routes.py # Reviewer Flask blueprint
│ ├── users.json # Reviewer credentials
│ └── templates/ # Reviewer templates
├── script/ # Utility scripts
│ └── generate_reviewer_password_hash.py # Password hash generator
├── templates/ # Jinja2 templates
├── static/ # Static files (CSS, JS, images)
├── tmp/ # Upload staging area
Expand All @@ -142,6 +149,7 @@ crackmesone_python/
- Search functionality
- RSS feed
- Notifications
- Content moderation (reviewer tool for approving/rejecting submissions)

## Configuration

Expand All @@ -158,6 +166,67 @@ Edit `config/config.json`:
- **Recaptcha.Secret**: Your reCAPTCHA secret key
- **Discord.Enabled**: Enable/disable Discord notifications for new submissions
- **Discord.WebhookURL**: Your Discord webhook URL (get from Discord channel settings → Integrations → Webhooks)
- **Reviewer.Enabled**: Enable/disable the reviewer tool (for moderating submissions)
- **Reviewer.PasswordSalt**: Salt used for hashing reviewer passwords (change in production!)

### Reviewer Tool

The reviewer tool is a separate authentication system for site moderators to approve/reject crackme and solution submissions. It is accessed at `/review`.

#### Enabling the Reviewer Tool

1. Set `Reviewer.Enabled` to `true` in `config/config.json`
2. Set a secure random string for `Reviewer.PasswordSalt`

#### Reviewer Credentials (`review/users.json`)

Reviewer accounts are stored in `review/users.json` with the following format:

```json
{
"username": {
"password_hash": "sha256-hash-of-password-plus-salt",
"is_admin": false
}
}
```

- **password_hash**: SHA256 hash of the password concatenated with the `PasswordSalt` from config
Comment thread
xusheng6 marked this conversation as resolved.
- **is_admin**: If `true`, the user has admin privileges (can delete approved content, manage reviewers, delete users)

#### Creating Reviewer Accounts

Use the password hash generator script to create password hashes:

```bash
python script/generate_reviewer_password_hash.py <password>
```

Then add the username and hash to `review/users.json`:

```json
{
"newreviewer": {
"password_hash": "<output-from-script>",
"is_admin": false
}
}
```

Alternatively, an existing admin can add new reviewers through the web interface at `/review/managereviewers`.

#### Reviewer vs Admin Permissions

| Action | Reviewer | Admin |
|--------|----------|-------|
| Approve/reject pending crackmes | Yes | Yes |
| Approve/reject pending solutions | Yes | Yes |
| Delete approved crackmes | No | Yes |
| Delete approved solutions | No | Yes |
| Delete comments | No | Yes |
| Delete user accounts | No | Yes |
| Reset user passwords | No | Yes |
| Manage reviewer accounts | No | Yes |

## Previous Codebase

Expand Down
15 changes: 15 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,21 @@ def create_app(config_path=None):
from app.controllers import register_blueprints
register_blueprints(app)

# Register reviewer blueprint (separate authentication system)
reviewer_config = config.get('Reviewer', {})
if reviewer_config.get('Enabled', False):
from review.routes import reviewer_bp, init_reviewer
from review.logger import init_logger as init_reviewer_logger
app.config['REVIEWER_PASSWORD_SALT'] = reviewer_config.get('PasswordSalt', 'default_salt')
init_reviewer(app)
# Use private webhook for reviewer operation logs
discord_config = config.get('Discord', {})
private_webhook = discord_config.get('WebhookPrivate', '') if discord_config.get('Enabled', False) else None
init_reviewer_logger(discord_webhook=private_webhook)
app.register_blueprint(reviewer_bp)
# Exempt reviewer routes from main CSRF (reviewer has its own CSRF)
csrf.exempt(reviewer_bp)

# Register template context processors
@app.context_processor
def inject_globals():
Expand Down
11 changes: 9 additions & 2 deletions app/controllers/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@
login_bp = Blueprint('login', __name__)


def clear_main_auth():
"""Clear main site auth session keys only."""
session.pop('name', None)
session.pop('email', None)
session.pop('login_attempt', None)


@login_bp.route('/login', methods=['GET'])
@anonymous_required
def login_get():
Expand Down Expand Up @@ -46,7 +53,7 @@ def login_post():
# Check password
if match_string(user['password'], password):
# Login successful
session.clear()
clear_main_auth()
session['email'] = user['email']
session['name'] = user['name']
flash('Login successful!', FLASH_SUCCESS)
Expand All @@ -72,7 +79,7 @@ def login_post():
def logout():
"""Log out the user."""
if session.get('name'):
session.clear()
clear_main_auth()
flash('Goodbye!', 'alert-info')

return redirect('/')
64 changes: 51 additions & 13 deletions app/services/discord.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
"""
Discord webhook notification service.

Two webhooks are supported:
- WebhookPublic: Public channel for approved crackmes/solutions notifications
- WebhookPrivate: Private/admin channel for pending submissions and reviewer logs
"""

import requests
Expand All @@ -20,24 +24,26 @@ def is_enabled():
return discord_config.get('Enabled', False)


def get_webhook_url():
"""Get the Discord webhook URL."""
return discord_config.get('WebhookURL', '')
def get_public_webhook():
"""Get the public Discord webhook URL (for approved items)."""
return discord_config.get('WebhookPublic', '')


def get_private_webhook():
"""Get the private Discord webhook URL (for pending items and logs)."""
return discord_config.get('WebhookPrivate', '')

def send_notification(message: str) -> bool:
"""Send a notification to Discord via webhook.

def send_to_webhook(webhook_url: str, message: str) -> bool:
"""Send a message to a specific Discord webhook.

Args:
webhook_url: The webhook URL to send to
message: The message to send

Returns:
True if the notification was sent successfully, False otherwise
"""
if not is_enabled():
return True

webhook_url = get_webhook_url()
if not webhook_url:
return False

Expand All @@ -52,8 +58,38 @@ def send_notification(message: str) -> bool:
return False


def send_public_notification(message: str) -> bool:
"""Send a notification to the public Discord channel.

Args:
message: The message to send

Returns:
True if the notification was sent successfully, False otherwise
"""
if not is_enabled():
return True
return send_to_webhook(get_public_webhook(), message)


def send_private_notification(message: str) -> bool:
"""Send a notification to the private/admin Discord channel.

Args:
message: The message to send

Returns:
True if the notification was sent successfully, False otherwise
"""
if not is_enabled():
return True
return send_to_webhook(get_private_webhook(), message)


def notify_new_crackme(username: str, crackme_name: str) -> bool:
"""Send notification for a new crackme submission.
"""Send notification for a new crackme submission (pending review).

Sent to PRIVATE channel - only admins/reviewers need to see this.

Args:
username: The user who submitted the crackme
Expand All @@ -63,11 +99,13 @@ def notify_new_crackme(username: str, crackme_name: str) -> bool:
True if notification was sent successfully
"""
message = f"New crackme submission awaiting review: **{crackme_name}** by **{username}**"
return send_notification(message)
return send_private_notification(message)


def notify_new_solution(username: str, crackme_name: str) -> bool:
"""Send notification for a new solution submission.
"""Send notification for a new solution submission (pending review).

Sent to PRIVATE channel - only admins/reviewers need to see this.

Args:
username: The user who submitted the solution
Expand All @@ -77,4 +115,4 @@ def notify_new_solution(username: str, crackme_name: str) -> bool:
True if notification was sent successfully
"""
message = f"New solution submission awaiting review: Solution for **{crackme_name}** by **{username}**"
return send_notification(message)
return send_private_notification(message)
6 changes: 4 additions & 2 deletions app/services/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ def get_session():


def clear_session():
"""Clear all session values."""
flask_session.clear()
"""Clear main site auth session keys only."""
flask_session.pop('name', None)
flask_session.pop('email', None)
flask_session.pop('login_attempt', None)


def get_username():
Expand Down
7 changes: 6 additions & 1 deletion config/config.json.example
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@
},
"Discord": {
"Enabled": false,
"WebhookURL": "your-discord-webhook-url"
"WebhookPublic": "your-public-discord-webhook-url",
"WebhookPrivate": "your-private-discord-webhook-url"
},
"Reviewer": {
"Enabled": false,
"PasswordSalt": "change-this-to-a-secure-random-salt"
}
}
5 changes: 5 additions & 0 deletions review/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""
Reviewer Tool - Integrated as Flask Blueprint.

This module provides the reviewer functionality for managing crackme submissions.
"""
Loading