Skip to content

Commit 53bd9d3

Browse files
committedJan 17, 2021
complete sqli
1 parent c940705 commit 53bd9d3

20 files changed

+502
-7
lines changed
 

‎.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# virtualenv
22
core/.env/
33

4+
# volume
5+
core/data
6+
47
# django
58
core/**/*.log
69
core/**/*.pot

‎core/README.md

+49-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ need token
1212
**Response:**
1313
```javascript
1414
{
15-
"username" : string,
15+
"name" : string,
1616
"score" : int,
1717
"money" : int
1818
}
@@ -34,7 +34,7 @@ POST /register
3434

3535
### Login
3636
```http
37-
POST /Login
37+
POST /login
3838
```
3939
| Parameter | Type | Description |
4040
| :--- | :--- | :--- |
@@ -130,3 +130,50 @@ needs token
130130
- Already Bought : 409
131131
- Not Enough balance : 402
132132
- Success : 200
133+
134+
135+
## Flag
136+
### Auth
137+
```http
138+
POST /flag/
139+
```
140+
| Parameter | Type | Description |
141+
| :--- | :--- | :--- |
142+
| `flag` | `string` | |
143+
144+
needs token
145+
**Response:**
146+
- Invalid Form : 400
147+
- No Such Flag : 404
148+
- Success : 200
149+
```javascript
150+
{
151+
"score" : int
152+
}
153+
```
154+
155+
156+
## Sqli
157+
### Query
158+
```http
159+
POST /sqli/
160+
```
161+
| Parameter | Type | Description |
162+
| :--- | :--- | :--- |
163+
| `flag` | `string` | |
164+
165+
needs token
166+
**Response:**
167+
- Invalid Form : 400
168+
- "Attacked yourself", 400
169+
- "No Such Team", 404
170+
- "Too Long Query", 400
171+
- "Blocked by Regex", 400
172+
- Success : 200 -> with results!
173+
```javascript
174+
{
175+
'success': success,
176+
'message': result
177+
}
178+
```
179+

‎core/base/admin.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
from django.contrib import admin
2-
from .models import Team, SqliFilter, XssFilter, RegexRule, CspRule, LenRule
2+
from .models import Team, create_new_database, SqliFilter, XssFilter, RegexRule, CspRule, LenRule
3+
34

45
admin.site.register(Team)
6+
admin.site.add_action(create_new_database, "Create New Victim Database for SQL injection.")
57
admin.site.register(SqliFilter)
68
admin.site.register(XssFilter)
79
admin.site.register(RegexRule)

‎core/base/apps.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,5 @@ def get_latest_attack(team, category):
2323
return {
2424
"to_team": latest.to_team,
2525
"is_success": latest.succeed
26-
}
26+
}
27+

‎core/base/models.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,23 @@
22
from django.contrib.auth.models import AbstractUser
33

44

5-
65
class Team(AbstractUser):
76
balance = models.BigIntegerField(default=0)
87
score = models.BigIntegerField(default=0)
98

9+
actions = ['create_new_database', ]
10+
1011
class Meta:
1112
verbose_name = '팀'
1213
verbose_name_plural = '팀들'
1314

1415

16+
from sqli.apps import generate_db
17+
18+
def create_new_database(Team, request, queryset):
19+
for team in queryset:
20+
generate_db(team.username)
21+
1522

1623
class Rule(models.Model):
1724
title = models.CharField(max_length=50)

‎core/flag/__init__.py

Whitespace-only changes.

‎core/flag/admin.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from django.contrib import admin
2+
3+
# Register your models here.

‎core/flag/apps.py

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from django.apps import AppConfig
2+
from django.contrib.auth import get_user_model
3+
4+
from .models import Flag
5+
6+
7+
class FlagConfig(AppConfig):
8+
name = 'flag'
9+
10+
11+
Team = get_user_model()
12+
13+
14+
def check_flag(team: Team, flag_str: str):
15+
flag = Flag.objects.filter(flag=flag_str).exclude(teams__username=team.username)
16+
if not flag:
17+
return False, None
18+
flag = flag[0]
19+
20+
team.add_score(flag.score)
21+
team.save()
22+
flag.teams.add(team)
23+
24+
return True, flag

‎core/flag/migrations/0001_initial.py

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Generated by Django 3.1.5 on 2021-01-17 09:53
2+
3+
from django.conf import settings
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
initial = True
10+
11+
dependencies = [
12+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
13+
]
14+
15+
operations = [
16+
migrations.CreateModel(
17+
name='Flag',
18+
fields=[
19+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
20+
('flag', models.TextField(unique=True)),
21+
('score', models.IntegerField()),
22+
('category', models.CharField(choices=[('sqli', 'SQLi'), ('xss', 'XSS')], max_length=20)),
23+
('teams', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL)),
24+
],
25+
options={
26+
'verbose_name': 'Flag',
27+
'verbose_name_plural': 'Flag들',
28+
},
29+
),
30+
]

‎core/flag/migrations/__init__.py

Whitespace-only changes.

‎core/flag/models.py

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from django.db import models
2+
from django.contrib.auth import get_user_model
3+
from env.environ import CATEGORY
4+
5+
Team = get_user_model()
6+
7+
8+
class Flag(models.Model):
9+
flag = models.TextField(unique=True)
10+
score = models.IntegerField()
11+
category = models.CharField(max_length=20, choices=CATEGORY)
12+
teams = models.ManyToManyField(Team, blank=True)
13+
14+
class Meta:
15+
verbose_name = "Flag"
16+
verbose_name_plural = "Flag들"
17+
18+
def __str__(self):
19+
return '%s' % self.flag

‎core/flag/tests.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from django.test import TestCase
2+
3+
# Create your tests here.

‎core/flag/views.py

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#from django.shortcuts import render
2+
from django.http import JsonResponse, HttpResponse
3+
from django import forms
4+
from django.views import View
5+
from django.contrib.auth.mixins import LoginRequiredMixin
6+
from django.contrib.auth import authenticate, login
7+
import json
8+
from utils.validator import flag_format
9+
from .apps import check_flag
10+
11+
12+
class FlagAuthForm(forms.Form):
13+
flag = forms.CharField(validators=[flag_format])
14+
15+
16+
class FlagAuthView(LoginRequiredMixin, View):
17+
def post(self, request):
18+
form = FlagAuthForm(json.loads(request.body.decode("utf-8")))
19+
user = request.user
20+
21+
if not form.is_valid():
22+
return HttpResponse(status=400)
23+
24+
is_ok, flag = check_flag(user, form.cleaned_data['flag'])
25+
26+
if is_ok:
27+
return JsonResponse({
28+
'score': flag.score
29+
}, status=200)
30+
else:
31+
return HttpResponse(status=404)

‎core/plt/urls.py

+1
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@
2020
path('admin/', admin.site.urls),
2121
path('', include('base.urls')),
2222
path('shop/', include('shop.urls')),
23+
path('sqli/', include('sqli.urls')),
2324
]

‎core/sqli/apps.py

+220
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,225 @@
11
from django.apps import AppConfig
2+
from django.contrib.auth import get_user_model
3+
4+
import pymysql
5+
import random
6+
7+
from utils.generator import random_string, random_flag
8+
from utils.mysql import sqli_db
9+
from utils.mysql import raw_query
10+
11+
from flag.models import Flag
12+
from env.environ import ITEM_CATEGORY_SQLI
13+
from env.credential import MYSQL_PASS
14+
15+
16+
Team = get_user_model()
17+
218

319

420
class SqliConfig(AppConfig):
521
name = 'sqli'
22+
23+
24+
SCORE = 200
25+
26+
NUM_TEAM = 6
27+
NUM_TABLE_FLAG = 1
28+
NUM_COLUMN_FLAG = 2
29+
NUM_ELEMENT_FLAG = 7
30+
NUM_FLAG = NUM_TABLE_FLAG + NUM_ELEMENT_FLAG + NUM_COLUMN_FLAG
31+
32+
NUM_TABLE = 10
33+
NUM_COLUMN = 5
34+
NUM_ROW = 100
35+
36+
TABLE_SHOW_INTERVAL = 10
37+
38+
FLAG_MAX_LEN = 50
39+
40+
41+
class Element:
42+
def __init__(self):
43+
self.name: str = random_string()
44+
self.size: int = 1
45+
self.child: list = []
46+
47+
def insert_flag(self, flag: str):
48+
count = 0
49+
candidates = [i for i,value in enumerate(self.child) if not str(value).startswith("PLUS{")]
50+
if not candidates:
51+
return False
52+
self.child[random.choice(candidates)].name = flag
53+
return True
54+
55+
56+
class Column(Element):
57+
def __init__(self):
58+
super().__init__()
59+
self.elements: list = [Element() for _ in range(NUM_ROW)]
60+
self.child = self.elements
61+
self.size: int = NUM_ROW
62+
63+
def __repr__(self):
64+
return self.name
65+
66+
def __str__(self):
67+
return self.name
68+
69+
70+
class Table(Element):
71+
def __init__(self):
72+
super().__init__()
73+
self.columns: list = [Column() for _ in range(NUM_COLUMN)]
74+
self.child = self.columns
75+
self.size: int = NUM_COLUMN
76+
77+
names = []
78+
79+
for c in self.columns:
80+
while True:
81+
name = random_string()
82+
if name not in names:
83+
c.name = name
84+
names.append(name)
85+
break
86+
87+
def __repr__(self):
88+
return self.name
89+
90+
def __str__(self):
91+
return self.name
92+
93+
def show(self):
94+
print(self.name)
95+
print("-" * (NUM_COLUMN * 13))
96+
for i in self.columns:
97+
print("%10s |" % (i.name[:10]), end=' ')
98+
print()
99+
print("-" * (NUM_COLUMN * 13))
100+
for e_idx in range(NUM_ROW):
101+
for col in self.columns:
102+
print("{:10s} |".format(col.elements[e_idx].name[:10]), end=' ')
103+
print()
104+
print("-" * (NUM_COLUMN * 13))
105+
print()
106+
107+
def to_sql(self, db_name: str):
108+
result = "CREATE TABLE " + db_name + ".`" + self.name + "` (" + " VARCHAR(100), ".join("`"+c.name+"`" for c in self.columns) + " VARCHAR(100)); \n"
109+
cols = "`, `".join(c.name for c in self.columns)
110+
111+
for e_idx in range(NUM_ROW):
112+
data = "', '".join(c.elements[e_idx].name for c in self.columns)
113+
result += f"INSERT INTO {db_name}.`{self.name}` (`{cols}`) VALUES ('{data}'); \n"
114+
print(result)
115+
return result
116+
117+
118+
class DB(Element):
119+
def __init__(self, _name: str):
120+
super().__init__()
121+
self.name: str = _name
122+
self.size: int = NUM_TABLE
123+
self.tables: list = [Table() for _ in range(NUM_TABLE)]
124+
self.child = self.tables
125+
126+
def __repr__(self):
127+
return self.name
128+
129+
def __str__(self):
130+
return self.name
131+
132+
def show(self):
133+
print(f"[+] DB: {self.name}")
134+
for t in self.tables:
135+
t.show()
136+
137+
def to_sql(self):
138+
result = ""
139+
for table in self.tables:
140+
result += table.to_sql(self.name)
141+
return result
142+
143+
144+
145+
def add_flag(max_len):
146+
flag = random_flag(max_len)
147+
Flag.objects.create(flag=flag, score=SCORE, category=ITEM_CATEGORY_SQLI)
148+
return flag
149+
150+
151+
def get_flag_set(max_len):
152+
return [add_flag(FLAG_MAX_LEN) for _ in range(NUM_FLAG)]
153+
154+
155+
def generate_db(team_name: str):
156+
idx = 0
157+
db = DB(team_name)
158+
team_flag_set = get_flag_set(60)
159+
160+
for _ in range(NUM_TABLE_FLAG):
161+
db.insert_flag(team_flag_set[idx])
162+
idx += 1
163+
164+
for _ in range(NUM_COLUMN_FLAG):
165+
table = random.choice(db.tables)
166+
table.insert_flag(team_flag_set[idx])
167+
idx += 1
168+
169+
for _ in range(NUM_ELEMENT_FLAG):
170+
table = random.choice(db.tables)
171+
col = random.choice(table.columns)
172+
col.insert_flag(team_flag_set[idx])
173+
idx += 1
174+
175+
176+
query = f"DROP DATABASE IF EXISTS {team_name};\n"
177+
query += f"CREATE DATABASE {team_name};\n"
178+
query += f"CREATE USER {team_name}@'%' IDENTIFIED BY '{MYSQL_PASS}';\n"
179+
query += f"GRANT SELECT ON {team_name}.* TO {team_name}@'%';\n"
180+
query += f"FLUSH privileges;"
181+
182+
raw_query(sqli_db(), query)
183+
raw_query(sqli_db(), db.to_sql())
184+
185+
186+
187+
def query_sql(attack_team_name: str, target_team_name: str, query: str):
188+
if attack_team_name == target_team_name:
189+
return False, "Attacked yourself", 400
190+
191+
try:
192+
target_team = Team.objects.get(username=target_team_name)
193+
except model.DoesNotExist:
194+
return False, "No Such Team", 404
195+
196+
ok, message, status_code = is_valid_query(target_team, query)
197+
if not ok:
198+
return False, message, status_code
199+
200+
succeed, res = raw_query(sqli_db(target_team_name, MYSQL_PASS), query)
201+
202+
sqli_log = SqliLog.objects.create()
203+
sqli_log.from_team = attack_team_name
204+
sqli_log.to_team = target_team_name
205+
sqli_log.query = query
206+
sqli_log.succeed = succeed
207+
sqli_log.return_value = res
208+
sqli_log.save()
209+
210+
return succeed, res, 200
211+
212+
213+
214+
def is_valid_query(target_team: Team, query: str):
215+
max_len = target_team.sqli_filter.max_len
216+
if max_len < len(query):
217+
return False, "Too Long Query", 400
218+
219+
regex_filter_list = target_team.sqli_filter.regex_rule_list.all()
220+
for r in regex_filter_list:
221+
p = re.compile(r.regexp, re.I)
222+
if p.match(query):
223+
return False, "Blocked by Regex", 400
224+
225+
return True, "", 200

‎core/sqli/urls.py

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"""plt URL Configuration
2+
The `urlpatterns` list routes URLs to views. For more information please see:
3+
https://docs.djangoproject.com/en/3.0/topics/http/urls/
4+
Examples:
5+
Function views
6+
1. Add an import: from my_app import views
7+
2. Add a URL to urlpatterns: path('', views.home, name='home')
8+
Class-based views
9+
1. Add an import: from other_app.views import Home
10+
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
11+
Including another URLconf
12+
1. Import the include() function: from django.urls import include, path
13+
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
14+
"""
15+
from django.urls import path
16+
from . import views
17+
18+
urlpatterns = [
19+
path('', views.SqliView.as_view(), name='sqli'),
20+
]

‎core/sqli/views.py

+28-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,29 @@
1-
from django.shortcuts import render
1+
from django.http import JsonResponse, HttpResponse
2+
from django.contrib.auth.mixins import LoginRequiredMixin
3+
from django.views import View
4+
from django import forms
5+
from .apps import query_sql
6+
import json
27

3-
# Create your views here.
8+
9+
class SqlQueryForm(forms.Form):
10+
query = forms.CharField()
11+
team = forms.CharField()
12+
13+
14+
class SqliView(LoginRequiredMixin, View):
15+
def post(self, request):
16+
form = SqlQueryForm(json.loads(request.body.decode("utf-8")))
17+
18+
if not form.is_valid():
19+
return JsonResponse({
20+
'success': False,
21+
'message': 'Invalid Form'
22+
}, status=400)
23+
24+
success, result, status_code = query_sql(request.user.username, form.cleaned_data['team'], form.cleaned_data['query'])
25+
26+
return JsonResponse({
27+
'success': success,
28+
'message': result
29+
}, status=status_code)

‎core/utils/generator.py

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import random
2+
import string
3+
4+
def random_string(length=10):
5+
return ''.join(random.choice(string.ascii_letters) for i in range(length))
6+
7+
def random_flag(max_len=100):
8+
return 'PLUS{'+random_string(max_len-6)+'}'

‎core/utils/mysql.py

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import pymysql
2+
from pymysql.constants import CLIENT
3+
from env.credential import MYSQL_HOST, MYSQL_PORT, MYSQL_ROOT_USER, MYSQL_ROOT_PASS
4+
5+
6+
def sqli_db(user=MYSQL_ROOT_USER, password=MYSQL_ROOT_PASS) -> pymysql.connections.Connection:
7+
"""
8+
Make Connection to sqli mysql server
9+
:return: sqli; pymysql Connection Object
10+
"""
11+
return pymysql.connect(host=MYSQL_HOST,
12+
port=MYSQL_PORT,
13+
user=user,
14+
password=password,
15+
charset="utf8",
16+
client_flag=CLIENT.MULTI_STATEMENTS,)
17+
18+
19+
def raw_query(conn: pymysql.connections.Connection, query: str):
20+
"""
21+
Query Dangerously & Close DB Connection.
22+
:param conn: pymysql Connection Object
23+
:param query: Query Sentence
24+
:return: is_success, result
25+
"""
26+
try:
27+
c = conn.cursor()
28+
c.execute(query)
29+
return True, c.fetchall()
30+
except Exception as e:
31+
print("ERROROROROROROR!!!!!:",e)
32+
return False, e
33+
finally:
34+
conn.close()

‎core/utils/sqli.py

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from django.contrib.auth import get_user_model
2+
from env.sqli_flags import SQLI_FLAGS
3+
4+
5+
6+
def flag_match_list(flag_input: str):
7+
return [i for i in SQLI_FLAGS if i[0] is flag_input]
8+
9+
10+
def score(flag_input: str) -> int:
11+
result = flag_match_list(flag_input)
12+
13+
if len(result) == 0:
14+
return 0
15+
else:
16+
return result[0][1]

0 commit comments

Comments
 (0)
Please sign in to comment.