add script hash field and calculate hash on script changes. Also removed storing scripts in DB as base64 strings. Should fix #634
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import base64
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
from logs.models import PendingAction
|
from logs.models import PendingAction
|
||||||
@@ -20,3 +21,12 @@ class Command(BaseCommand):
|
|||||||
for user in User.objects.filter(is_installer_user=True):
|
for user in User.objects.filter(is_installer_user=True):
|
||||||
user.block_dashboard_login = True
|
user.block_dashboard_login = True
|
||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
|
# convert script base64 field to text field
|
||||||
|
user_scripts = Script.objects.exclude(script_type="builtin").filter(script_body="")
|
||||||
|
for script in user_scripts:
|
||||||
|
# decode base64 string
|
||||||
|
script.script_body = base64.b64decode(script.code_base64.encode("ascii", "ignore")).decode(
|
||||||
|
"ascii", "ignore"
|
||||||
|
)
|
||||||
|
script.hash_script_body() # also saves script
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 3.2.9 on 2021-11-28 16:37
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('scripts', '0014_alter_script_filename'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='script',
|
||||||
|
name='script_body',
|
||||||
|
field=models.TextField(blank=True, default=''),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='script',
|
||||||
|
name='script_hash',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='script',
|
||||||
|
name='code_base64',
|
||||||
|
field=models.TextField(blank=True, default=''),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import base64
|
import base64
|
||||||
import re
|
import re
|
||||||
|
import hmac
|
||||||
|
import hashlib
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from django.contrib.postgres.fields import ArrayField
|
from django.contrib.postgres.fields import ArrayField
|
||||||
@@ -40,7 +42,9 @@ class Script(BaseAuditModel):
|
|||||||
syntax = TextField(null=True, blank=True)
|
syntax = TextField(null=True, blank=True)
|
||||||
favorite = models.BooleanField(default=False)
|
favorite = models.BooleanField(default=False)
|
||||||
category = models.CharField(max_length=100, null=True, blank=True)
|
category = models.CharField(max_length=100, null=True, blank=True)
|
||||||
code_base64 = models.TextField(null=True, blank=True, default="")
|
script_body = models.TextField(blank=True, default="")
|
||||||
|
script_hash = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
code_base64 = models.TextField(blank=True, default="") # deprecated
|
||||||
default_timeout = models.PositiveIntegerField(default=90)
|
default_timeout = models.PositiveIntegerField(default=90)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@@ -48,12 +52,7 @@ class Script(BaseAuditModel):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def code_no_snippets(self):
|
def code_no_snippets(self):
|
||||||
if self.code_base64:
|
return self.script_body if self.script_body else ""
|
||||||
return base64.b64decode(self.code_base64.encode("ascii", "ignore")).decode(
|
|
||||||
"ascii", "ignore"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def code(self):
|
def code(self):
|
||||||
@@ -78,6 +77,13 @@ class Script(BaseAuditModel):
|
|||||||
else:
|
else:
|
||||||
return code
|
return code
|
||||||
|
|
||||||
|
def hash_script_body(self):
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
msg = self.code.encode()
|
||||||
|
self.script_hash = hmac.new(settings.SECRET_KEY.encode(), msg, hashlib.sha256).hexdigest()
|
||||||
|
self.save()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load_community_scripts(cls):
|
def load_community_scripts(cls):
|
||||||
import json
|
import json
|
||||||
@@ -130,24 +136,8 @@ class Script(BaseAuditModel):
|
|||||||
i.filename = script["filename"] # type: ignore
|
i.filename = script["filename"] # type: ignore
|
||||||
|
|
||||||
with open(os.path.join(scripts_dir, script["filename"]), "rb") as f:
|
with open(os.path.join(scripts_dir, script["filename"]), "rb") as f:
|
||||||
script_bytes = (
|
i.script_body = f.read().decode('utf-8') # type: ignore
|
||||||
f.read().decode("utf-8").encode("ascii", "ignore")
|
i.hash_script_body() # also saves script
|
||||||
)
|
|
||||||
i.code_base64 = base64.b64encode(script_bytes).decode("ascii") # type: ignore
|
|
||||||
|
|
||||||
i.save( # type: ignore
|
|
||||||
update_fields=[
|
|
||||||
"name",
|
|
||||||
"description",
|
|
||||||
"category",
|
|
||||||
"default_timeout",
|
|
||||||
"code_base64",
|
|
||||||
"shell",
|
|
||||||
"args",
|
|
||||||
"filename",
|
|
||||||
"syntax",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
# check if script was added without a guid
|
# check if script was added without a guid
|
||||||
elif cls.objects.filter(
|
elif cls.objects.filter(
|
||||||
@@ -170,39 +160,17 @@ class Script(BaseAuditModel):
|
|||||||
with open(
|
with open(
|
||||||
os.path.join(scripts_dir, script["filename"]), "rb"
|
os.path.join(scripts_dir, script["filename"]), "rb"
|
||||||
) as f:
|
) as f:
|
||||||
script_bytes = (
|
s.script_body = f.read().decode('utf-8')
|
||||||
f.read().decode("utf-8").encode("ascii", "ignore")
|
s.hash_script_body() # also saves the script
|
||||||
)
|
|
||||||
s.code_base64 = base64.b64encode(script_bytes).decode(
|
|
||||||
"ascii"
|
|
||||||
)
|
|
||||||
|
|
||||||
s.save(
|
|
||||||
update_fields=[
|
|
||||||
"guid",
|
|
||||||
"name",
|
|
||||||
"description",
|
|
||||||
"category",
|
|
||||||
"default_timeout",
|
|
||||||
"code_base64",
|
|
||||||
"shell",
|
|
||||||
"args",
|
|
||||||
"filename",
|
|
||||||
"syntax",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print(f"Adding new community script: {script['name']}")
|
print(f"Adding new community script: {script['name']}")
|
||||||
|
|
||||||
with open(os.path.join(scripts_dir, script["filename"]), "rb") as f:
|
with open(os.path.join(scripts_dir, script["filename"]), "rb") as f:
|
||||||
script_bytes = (
|
script_body = f.read().decode('utf-8')
|
||||||
f.read().decode("utf-8").encode("ascii", "ignore")
|
|
||||||
)
|
|
||||||
code_base64 = base64.b64encode(script_bytes).decode("ascii")
|
|
||||||
|
|
||||||
cls(
|
new_script = cls(
|
||||||
code_base64=code_base64,
|
script_body=script_body,
|
||||||
guid=script["guid"],
|
guid=script["guid"],
|
||||||
name=script["name"],
|
name=script["name"],
|
||||||
description=script["description"],
|
description=script["description"],
|
||||||
@@ -213,7 +181,8 @@ class Script(BaseAuditModel):
|
|||||||
args=args,
|
args=args,
|
||||||
filename=script["filename"],
|
filename=script["filename"],
|
||||||
syntax=syntax,
|
syntax=syntax,
|
||||||
).save()
|
)
|
||||||
|
new_script.hash_script_body() # also saves script
|
||||||
|
|
||||||
# delete community scripts that had their name changed
|
# delete community scripts that had their name changed
|
||||||
cls.objects.filter(script_type="builtin", guid=None).delete()
|
cls.objects.filter(script_type="builtin", guid=None).delete()
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class ScriptTableSerializer(ModelSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class ScriptSerializer(ModelSerializer):
|
class ScriptSerializer(ModelSerializer):
|
||||||
|
script_hash = ReadOnlyField()
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Script
|
model = Script
|
||||||
fields = [
|
fields = [
|
||||||
@@ -32,7 +33,8 @@ class ScriptSerializer(ModelSerializer):
|
|||||||
"args",
|
"args",
|
||||||
"category",
|
"category",
|
||||||
"favorite",
|
"favorite",
|
||||||
"code_base64",
|
"script_body",
|
||||||
|
"script_hash",
|
||||||
"default_timeout",
|
"default_timeout",
|
||||||
"syntax",
|
"syntax",
|
||||||
"filename",
|
"filename",
|
||||||
@@ -41,10 +43,11 @@ class ScriptSerializer(ModelSerializer):
|
|||||||
|
|
||||||
class ScriptCheckSerializer(ModelSerializer):
|
class ScriptCheckSerializer(ModelSerializer):
|
||||||
code = ReadOnlyField()
|
code = ReadOnlyField()
|
||||||
|
script_hash = ReadOnlyField
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Script
|
model = Script
|
||||||
fields = ["code", "shell"]
|
fields = ["code", "shell", "script_hash"]
|
||||||
|
|
||||||
|
|
||||||
class ScriptSnippetSerializer(ModelSerializer):
|
class ScriptSnippetSerializer(ModelSerializer):
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import base64
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
@@ -37,6 +36,8 @@ class GetAddScripts(APIView):
|
|||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
obj = serializer.save()
|
obj = serializer.save()
|
||||||
|
|
||||||
|
obj.hash_script_body()
|
||||||
|
|
||||||
return Response(f"{obj.name} was added!")
|
return Response(f"{obj.name} was added!")
|
||||||
|
|
||||||
|
|
||||||
@@ -64,6 +65,8 @@ class GetUpdateDeleteScript(APIView):
|
|||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
obj = serializer.save()
|
obj = serializer.save()
|
||||||
|
|
||||||
|
obj.hash_script_body()
|
||||||
|
|
||||||
return Response(f"{obj.name} was edited!")
|
return Response(f"{obj.name} was edited!")
|
||||||
|
|
||||||
def delete(self, request, pk):
|
def delete(self, request, pk):
|
||||||
|
|||||||
@@ -82,7 +82,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<v-ace-editor
|
<v-ace-editor
|
||||||
v-model:value="code"
|
v-model:value="formScript.script_body"
|
||||||
class="col-8"
|
class="col-8"
|
||||||
:lang="formScript.shell === 'cmd' ? 'batchfile' : formScript.shell"
|
:lang="formScript.shell === 'cmd' ? 'batchfile' : formScript.shell"
|
||||||
:theme="$q.dark.isActive ? 'tomorrow_night_eighties' : 'tomorrow'"
|
:theme="$q.dark.isActive ? 'tomorrow_night_eighties' : 'tomorrow'"
|
||||||
@@ -111,7 +111,7 @@
|
|||||||
dense
|
dense
|
||||||
flat
|
flat
|
||||||
label="Test Script"
|
label="Test Script"
|
||||||
:disable="!agent || !code || !formScript.default_timeout"
|
:disable="!agent || !formScript.script_body || !formScript.default_timeout"
|
||||||
@click="openTestScriptModal"
|
@click="openTestScriptModal"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@@ -177,11 +177,10 @@ export default {
|
|||||||
|
|
||||||
// script form logic
|
// script form logic
|
||||||
const script = props.script
|
const script = props.script
|
||||||
? ref(Object.assign({}, props.script))
|
? ref(Object.assign({}, { ...props.script, script_body: "" }))
|
||||||
: ref({ shell: "powershell", default_timeout: 90, args: [] });
|
: ref({ shell: "powershell", default_timeout: 90, args: [], script_body: "" });
|
||||||
|
|
||||||
if (props.clone) script.value.name = `(Copy) ${script.value.name}`;
|
if (props.clone) script.value.name = `(Copy) ${script.value.name}`;
|
||||||
const code = ref("");
|
|
||||||
const maximized = ref(false);
|
const maximized = ref(false);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const agentLoading = ref(false);
|
const agentLoading = ref(false);
|
||||||
@@ -201,16 +200,13 @@ export default {
|
|||||||
// get code if editing or cloning script
|
// get code if editing or cloning script
|
||||||
if (props.script)
|
if (props.script)
|
||||||
downloadScript(script.value.id, { with_snippets: props.readonly }).then(r => {
|
downloadScript(script.value.id, { with_snippets: props.readonly }).then(r => {
|
||||||
code.value = r.code;
|
script.value.script_body = r.code;
|
||||||
});
|
});
|
||||||
|
|
||||||
async function submitForm() {
|
async function submitForm() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
let result = "";
|
let result = "";
|
||||||
try {
|
try {
|
||||||
// base64 encode the script text
|
|
||||||
script.value.code_base64 = btoa(code.value);
|
|
||||||
|
|
||||||
// edit existing script
|
// edit existing script
|
||||||
if (props.script && !props.clone) {
|
if (props.script && !props.clone) {
|
||||||
result = await editScript(script.value);
|
result = await editScript(script.value);
|
||||||
@@ -231,7 +227,7 @@ export default {
|
|||||||
$q.dialog({
|
$q.dialog({
|
||||||
component: TestScriptModal,
|
component: TestScriptModal,
|
||||||
componentProps: {
|
componentProps: {
|
||||||
script: { ...script.value, code: code.value },
|
script: { ...script.value },
|
||||||
agent: agent.value,
|
agent: agent.value,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -247,7 +243,6 @@ export default {
|
|||||||
return {
|
return {
|
||||||
// reactive data
|
// reactive data
|
||||||
formScript: script.value,
|
formScript: script.value,
|
||||||
code,
|
|
||||||
maximized,
|
maximized,
|
||||||
loading,
|
loading,
|
||||||
agentOptions,
|
agentOptions,
|
||||||
|
|||||||
@@ -118,12 +118,12 @@ export default {
|
|||||||
// base64 encode the script and delete file
|
// base64 encode the script and delete file
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onloadend = () => {
|
reader.onloadend = () => {
|
||||||
script.value.code_base64 = reader.result.replace(/^data:.+;base64,/, "");
|
script.value.script_body = reader.result;
|
||||||
};
|
};
|
||||||
|
|
||||||
reader.readAsDataURL(file.value);
|
reader.readAsText(file.value);
|
||||||
} else {
|
} else {
|
||||||
script.value.code_base64 = "";
|
script.value.script_body = "";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user