diff --git a/temp.py b/challenge/__init__.py similarity index 100% rename from temp.py rename to challenge/__init__.py diff --git a/challenge/admin.py b/challenge/admin.py new file mode 100644 index 000000000..f913d9b22 --- /dev/null +++ b/challenge/admin.py @@ -0,0 +1,16 @@ +from django.contrib import admin +from .models import Challenge, UserChallenge +# Register your models here. + +@admin.register(Challenge) +class ChallengeAdmin(admin.ModelAdmin): + list_display = ('name', 'docker_image', 'start_port', 'end_port', 'point') + search_fields = ('name', 'docker_image') + empty_value_display = '-empty-' + + +@admin.register(UserChallenge) +class UserChallengeAdmin(admin.ModelAdmin): + list_display = ('user', 'challenge', 'container_id', 'port', 'no_of_attempt', 'is_solved') + search_fields = ('user__username', 'challenge__name') + empty_value_display = '-empty-' \ No newline at end of file diff --git a/challenge/apps.py b/challenge/apps.py new file mode 100644 index 000000000..ad63d6650 --- /dev/null +++ b/challenge/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ChallengeConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'challenge' diff --git a/challenge/challenge.json b/challenge/challenge.json new file mode 100644 index 000000000..b92c182af --- /dev/null +++ b/challenge/challenge.json @@ -0,0 +1,12 @@ +[ + { + "name":"do-it-fast", + "description":"You want flag ?\nThis webside gives you ?\nyeah, just need to login and grab fast", + "docker_image": "rupak2001/do-it-fast", + "docker_port": 5050, + "start_port" : 5000, + "end_port" : 5500, + "flag": "flag{do_it_fast}", + "point" : 100 + } +] \ No newline at end of file diff --git a/challenge/management/commands/populate_challenge.py b/challenge/management/commands/populate_challenge.py new file mode 100644 index 000000000..62ab6330d --- /dev/null +++ b/challenge/management/commands/populate_challenge.py @@ -0,0 +1,22 @@ +import json +from django.core.management.base import BaseCommand, CommandError +from challenge.models import Challenge + + +class Command(BaseCommand): + help = "Populates the database with some test data" + + def handle(self, *args, **options): + try: + filename = "challenge/challenge.json" + with open(filename, "r") as f: + data = json.load(f) + for challenge in data: + try: + Challenge.objects.create(**challenge).save() + except: + pass + except FileNotFoundError: + raise CommandError("File not found: {}".format(filename)) + + diff --git a/challenge/migrations/0001_initial.py b/challenge/migrations/0001_initial.py new file mode 100644 index 000000000..16e775aa2 --- /dev/null +++ b/challenge/migrations/0001_initial.py @@ -0,0 +1,47 @@ +# Generated by Django 4.0.4 on 2023-03-03 06:33 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Challenge', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=100)), + ('description', models.TextField()), + ('docker_image', models.CharField(max_length=100)), + ('start_port', models.IntegerField()), + ('end_port', models.IntegerField()), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('flag', models.CharField(max_length=100)), + ('point', models.IntegerField()), + ], + ), + migrations.CreateModel( + name='UserChallenge', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('container_id', models.CharField(max_length=100)), + ('port', models.IntegerField()), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('is_live', models.BooleanField(default=False)), + ('no_of_attempt', models.IntegerField(default=0)), + ('is_solved', models.BooleanField(default=False)), + ('challenge', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='challenge.challenge')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/challenge/migrations/0002_challenge_docker_port.py b/challenge/migrations/0002_challenge_docker_port.py new file mode 100644 index 000000000..bf8fb3aee --- /dev/null +++ b/challenge/migrations/0002_challenge_docker_port.py @@ -0,0 +1,19 @@ +# Generated by Django 4.0.4 on 2023-03-03 06:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('challenge', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='challenge', + name='docker_port', + field=models.IntegerField(default=8000), + preserve_default=False, + ), + ] diff --git a/challenge/migrations/0003_alter_challenge_docker_image_alter_challenge_id_and_more.py b/challenge/migrations/0003_alter_challenge_docker_image_alter_challenge_id_and_more.py new file mode 100644 index 000000000..79c7e8164 --- /dev/null +++ b/challenge/migrations/0003_alter_challenge_docker_image_alter_challenge_id_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 4.0.4 on 2023-03-03 12:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('challenge', '0002_challenge_docker_port'), + ] + + operations = [ + migrations.AlterField( + model_name='challenge', + name='docker_image', + field=models.CharField(max_length=100, unique=True), + ), + migrations.AlterField( + model_name='challenge', + name='id', + field=models.AutoField(primary_key=True, serialize=False, unique=True), + ), + migrations.AlterField( + model_name='challenge', + name='name', + field=models.CharField(max_length=100, unique=True), + ), + ] diff --git a/challenge/migrations/__init__.py b/challenge/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/challenge/models.py b/challenge/models.py new file mode 100644 index 000000000..95442f220 --- /dev/null +++ b/challenge/models.py @@ -0,0 +1,46 @@ +from django.db import models +from django.contrib.auth.models import User + +# Create your models here +class Challenge(models.Model): + id = models.AutoField(primary_key=True, unique=True) + name = models.CharField(max_length=100, unique=True) + description = models.TextField() + docker_image = models.CharField(max_length=100, unique=True) + docker_port = models.IntegerField() + start_port = models.IntegerField() + end_port = models.IntegerField() + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + flag = models.CharField(max_length=100) + point = models.IntegerField() + + def __str__(self): + return self.name + + # overwriting default save method + def save(self, *args, **kwargs): + if self.start_port > self.end_port: + raise Exception("Start port should be less than end port") + # Here flag need to be hashed + super(Challenge, self).save(*args, **kwargs) + +class UserChallenge(models.Model): + """ + This is a mapping of user to challenge with proper progress tracking + This also allows us to reuse the created container for the user + """ + id = models.AutoField(primary_key=True) + user = models.ForeignKey(User, on_delete=models.CASCADE) + challenge = models.ForeignKey(Challenge, on_delete=models.CASCADE) + container_id = models.CharField(max_length=100) + port = models.IntegerField() + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + is_live = models.BooleanField(default=False) + no_of_attempt = models.IntegerField(default=0) + is_solved = models.BooleanField(default=False) + port = models.IntegerField() + + def __str__(self): + return f"{self.user.username} - {self.challenge.name}" \ No newline at end of file diff --git a/challenge/templates/chal-not-found.html b/challenge/templates/chal-not-found.html new file mode 100644 index 000000000..1bacba43b --- /dev/null +++ b/challenge/templates/chal-not-found.html @@ -0,0 +1,8 @@ +{% extends "introduction/base.html" %} +{% block content %} +
+ +

Challenge not found

+ +
+{% endblock content %} \ No newline at end of file diff --git a/challenge/templates/challenge.html b/challenge/templates/challenge.html new file mode 100644 index 000000000..f699f25a9 --- /dev/null +++ b/challenge/templates/challenge.html @@ -0,0 +1,81 @@ +{% extends "introduction/base.html" %} +{% block content %} +
+ +
+

{{ chal.name }}

+
+
+
{{ chal.description }}
+
+
+ + + +
+ + + + + +{% endblock content %} \ No newline at end of file diff --git a/challenge/tests.py b/challenge/tests.py new file mode 100644 index 000000000..7ce503c2d --- /dev/null +++ b/challenge/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/challenge/urls.py b/challenge/urls.py new file mode 100644 index 000000000..c9aac3b9f --- /dev/null +++ b/challenge/urls.py @@ -0,0 +1,6 @@ +from django.urls import path,include +from .views import * + +urlpatterns = [ + path('', DoItFast.as_view(), name='do-it-fast'), +] \ No newline at end of file diff --git a/challenge/utility.py b/challenge/utility.py new file mode 100644 index 000000000..d8ab3eb73 --- /dev/null +++ b/challenge/utility.py @@ -0,0 +1,10 @@ +import socket + +def get_free_port(START_PORT, END_PORT, HOST="localhost"): + for port in range(START_PORT, END_PORT): + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + result = s.connect_ex((HOST, port)) + if result == 111: + print(f"Port {port} is avilable") + return port + return None diff --git a/challenge/views.py b/challenge/views.py new file mode 100644 index 000000000..adb32a5ab --- /dev/null +++ b/challenge/views.py @@ -0,0 +1,88 @@ +from django.http import JsonResponse +from django.shortcuts import redirect, render +from django.views.generic import View +from django.views.decorators.csrf import csrf_exempt +import subprocess +from .utility import get_free_port +from .models import Challenge, UserChallenge +# Create your views here. + + +class DoItFast(View): + def get(self, request, challenge): + if not request.user.is_authenticated: + return redirect('login') + + try: + chal = Challenge.objects.get(name=challenge) + except Exception as e: + return render(request, 'chal-not-found.html') + + try: + user_chal = UserChallenge.objects.get(user=request.user, challenge=chal) + return render(request, 'challenge.html', {'chal': chal, 'user_chal': user_chal}) + except: + return render(request, 'challenge.html', {'chal': chal, 'user_chal': None}) + + def post(self, request, challenge): + user_chall_exists = False + if not request.user.is_authenticated: + return redirect('login') + + try: # checking the existance of challenge + chal = Challenge.objects.get(name=challenge) + except Exception as e: + return render(request, 'chal-not-found.html') + + try: # checking if he attempted it before or not, if yes then check if the container is live or not + user_chal = UserChallenge.objects.get(user=request.user, challenge=chal) + if user_chal.is_live: + return JsonResponse({'message':'already running', 'status': '200', 'endpoint': f'http://localhost:{user_chal.port}'}) + user_chall_exists = True + except: + pass + + port = get_free_port(8000, 8100) + if port == None: + return JsonResponse({'message': 'failed', 'status': '500', 'endpoint': 'None'}) + + command = f"docker run -d -p {port}:{chal.docker_port} {chal.docker_image}" + process = subprocess.Popen(command.split(" "), stdout=subprocess.PIPE) + output, error = process.communicate() + container_id = output.decode('utf-8').strip() + + if user_chall_exists: + # TODO : reuse the container instead of creaing the new one + user_chal.container_id = container_id + user_chal.port = port + user_chal.is_live = True + user_chal.save() + else: + user_chal = UserChallenge(user=request.user, challenge=chal, container_id=container_id, port=port) + user_chal.save() + # save the output in database for stoping the container + return JsonResponse({'message': 'success', 'status': '200', 'endpoint': f'http://localhost:{port}'}) + + + + def delete(self, request, challenge): + if not request.user.is_authenticated: + return redirect('login') + + try: + chal = Challenge.objects.get(name=challenge) + user_chal = UserChallenge.objects.get(user=request.user, challenge=chal) + except Exception as e: + return JsonResponse({'message': 'failed', 'status': '500'}) + + user_chal.is_live = False + user_chal.save() + command = f"docker stop {user_chal.container_id}" + process = subprocess.Popen(command.split(" "), stdout=subprocess.PIPE) + output, error = process.communicate() + return JsonResponse({'message': 'success', 'status': '200'}) + + def put(self, request, challange): + # TODO : implement flag checking + return "not implemented" + \ No newline at end of file diff --git a/db.sqlite3~f1cf11156c656314790387c2c9eb7f187a3d480e b/db.sqlite3~f1cf11156c656314790387c2c9eb7f187a3d480e deleted file mode 100644 index 5b7bde3cb..000000000 Binary files a/db.sqlite3~f1cf11156c656314790387c2c9eb7f187a3d480e and /dev/null differ diff --git a/introduction/static/css/dark-challenges.css b/introduction/static/css/dark-challenges.css new file mode 100644 index 000000000..28f8ae1ae --- /dev/null +++ b/introduction/static/css/dark-challenges.css @@ -0,0 +1,16 @@ +@import url('https://fonts.googleapis.com/css2?family=B612+Mono&display=swap'); + +body{ + font-family: 'B612 Mono', monospace; + background-color: rgb(0, 15, 4); +} + +.container{ + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 50%; + align-self: center; + color: rgb(162, 255, 0); +} \ No newline at end of file diff --git a/introduction/templates/introduction/base.html b/introduction/templates/introduction/base.html index 721354737..e54e425d3 100644 --- a/introduction/templates/introduction/base.html +++ b/introduction/templates/introduction/base.html @@ -613,13 +613,7 @@

PyGoat