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