Skip to content

Commit

Permalink
#1073 wip
Browse files Browse the repository at this point in the history
  • Loading branch information
Davide Arcuri committed Jun 11, 2024
1 parent dd68e37 commit 30e3535
Show file tree
Hide file tree
Showing 15 changed files with 287 additions and 133 deletions.
2 changes: 1 addition & 1 deletion compose/local/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
# We use the SemVer 2.0.0 versioning scheme
VERSION_MAJOR = 2 # Number of releases of the library with a breaking change
VERSION_MINOR = 7 # Number of changes that only add to the interface
VERSION_PATCH = 0 # Number of changes that do not change the interface
VERSION_PATCH = 1 # Number of changes that do not change the interface
VERSION_SUFFIX = ""

# TODO: At version 2.0.0, remove the symbol_shift feature
Expand Down
4 changes: 2 additions & 2 deletions compose/local/dask/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM daskdev/dask:2024.5.1-py3.12
FROM daskdev/dask:2024.5.2-py3.12
ENV DEBIAN_FRONTEND noninteractive

ARG local_folder=/uploads
Expand Down Expand Up @@ -47,7 +47,7 @@ RUN python setup.py build \
# Workers should have similar reqs as django
WORKDIR /
COPY ./requirements /requirements
RUN pip install uv==0.1.44 -e git+https://github.com/volatilityfoundation/volatility3.git@dc7a3878fa39156d89d567c3e823f1956675f192#egg=volatility3 \
RUN pip install uv==0.2.10 -e git+https://github.com/volatilityfoundation/volatility3.git@543a39485bdf57df47d731b55ab112e04f3033f0#egg=volatility3 \
&& uv pip install --no-cache --system -r /requirements/base.txt

COPY ./compose/local/dask/prepare.sh /usr/bin/prepare.sh
Expand Down
2 changes: 1 addition & 1 deletion compose/local/django/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ RUN /usr/local/go/bin/go build
FROM common-base
WORKDIR /
COPY ./requirements /requirements
RUN pip install uv==0.1.44 -e git+https://github.com/volatilityfoundation/volatility3.git@dc7a3878fa39156d89d567c3e823f1956675f192#egg=volatility3 \
RUN pip install uv==0.2.10 -e git+https://github.com/volatilityfoundation/volatility3.git@543a39485bdf57df47d731b55ab112e04f3033f0#egg=volatility3 \
&& uv pip install --no-cache --system -r /requirements/base.txt

COPY ./compose/local/__init__.py /src/volatility3/volatility3/framework/constants/__init__.py
Expand Down
2 changes: 2 additions & 0 deletions orochi/api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from orochi.api.routers.dumps import router as dumps_router
from orochi.api.routers.folders import router as folders_router
from orochi.api.routers.plugins import router as plugins_router
from orochi.api.routers.rules import router as rules_router
from orochi.api.routers.users import router as users_router
from orochi.api.routers.utils import router as utils_router

Expand All @@ -16,3 +17,4 @@
api.add_router("/plugins/", plugins_router, tags=["Plugins"])
api.add_router("/utils/", utils_router, tags=["Utils"])
api.add_router("/bookmarks/", bookmarks_router, tags=["Bookmarks"])
api.add_router("/rules/", rules_router, tags=["Rules"])
35 changes: 35 additions & 0 deletions orochi/api/filters.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from enum import Enum
from typing import List

from ninja import Schema
from pydantic import root_validator


class OPERATING_SYSTEM(str, Enum):
Expand All @@ -16,3 +18,36 @@ class OperatingSytemFilters(Schema):

class DumpFilters(Schema):
result: int = None


###################################################
# Rules
###################################################
class Search(Schema):
value: str = None
regex: bool = False


class Column(Schema):
data: int
name: str = None
searchable: bool = True
orderable: bool = True
search: Search = None


class Order(Schema):
column: int = 0
dir: str = "asc"


class RulesFilter(Schema):
start: int = 0
length: int = 10
columns: List[Column] = []
search: Search = None
order: List[Order] = []

@root_validator(pre=True)
def extract_data(cls, v):
return v
19 changes: 19 additions & 0 deletions orochi/api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from orochi.website.defaults import OSEnum
from orochi.website.models import Bookmark, Dump, Folder, Plugin
from orochi.ya.models import Rule

###################################################
# Auth
Expand Down Expand Up @@ -233,3 +234,21 @@ class BookmarksInSchema(Schema):
icon: str = None
selected_plugin: str = None
query: Optional[str] = None


###################################################
# Rules
###################################################
class RuleBuildSchema(Schema):
rule_ids: List[int]
rulename: str


class RulesOutSchema(ModelSchema):
class Meta:
model = Rule
fields = ["id", "path", "enabled", "compiled", "ruleset", "created", "updated"]


class ListStr(Schema):
rule_ids: List[int]
153 changes: 153 additions & 0 deletions orochi/api/routers/rules.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import os
from typing import List

import yara
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from ninja import Query, Router
from ninja.security import django_auth

from orochi.api.filters import RulesFilter
from orochi.api.models import (
ErrorsOut,
ListStr,
RuleBuildSchema,
RulesOutSchema,
SuccessResponse,
)
from orochi.website.models import CustomRule
from orochi.ya.models import Rule

router = Router()


@router.get("/", response={200: List[RulesOutSchema]}, auth=django_auth)
def list_rules(request, filters: Query[RulesFilter]):
return Rule.objects.all()


@router.get("/{pk}/download", auth=django_auth)
def download(request, pk: int):
"""
Download a rule file by its primary key.
Args:
pk (int): The primary key of the rule to download.
Returns:
HttpResponse: The HTTP response object containing the downloaded rule file.
Raises:
Exception: If an error occurs during the process.
"""
try:
rule = Rule.objects.filter(pk=pk).filter(ruleset__enabled=True)
if rule.count() == 1:
rule = rule.first()
else:
return 400, {"errors": "Generic error"}
if os.path.exists(rule.path):
with open(rule.path, "rb") as f:
rule_data = f.read()

response = HttpResponse(
rule_data,
content_type="application/text",
)
response["Content-Disposition"] = (
f"attachment; filename={os.path.basename(rule.path)}"
)
return response
else:
return 400, {"errors": "Rule not found"}
except Exception as excp:
return 400, {"errors": str(excp)}


@router.delete(
"/",
auth=django_auth,
url_name="delete_rules",
response={200: SuccessResponse, 400: ErrorsOut},
)
def delete_rules(request, info: ListStr):
"""
Summary:
Delete rules based on the provided rule IDs.
Explanation:
This function deletes rules based on the specified rule IDs belonging to the authenticated user. It removes the rules from the database and returns a success message upon deletion.
Args:
- request: The request object.
- rule_ids: A list of integers representing the IDs of rules to be deleted.
Returns:
- Tuple containing status code and a message dictionary.
Raises:
- Any exception encountered during the process will result in a 400 status code with an error message.
"""
try:
rules = Rule.objects.filter(pk__in=info.rule_ids, ruleset__user=request.user)
rules.delete()
rules_count = rules.count()
if rules_count == 0:
return 200, {"message": f"{rules_count} rules deleted."}
else:
return 200, {"message": "Only rules in your ruleset can be deleted."}
except Exception as excp:
return 400, {
"errors": str(excp) if excp else "Generic error during rules deletion"
}


@router.post(
"/build",
response={200: SuccessResponse, 400: ErrorsOut},
url_name="rule_build",
auth=django_auth,
)
def build_rules(request, info: RuleBuildSchema):
"""
Summary:
Build rules based on the provided information.
Explanation:
This function builds rules using the provided information and saves them in a custom folder. It creates a new YARA rule file and stores it in the specified location.
Args:
- request: The request object.
- info: An instance of RuleBuildSchema containing rule information.
Returns:
- Tuple containing status code and a message dictionary.
Raises:
- Any exception encountered during the process will result in a 400 status code with an error message.
"""
try:
rules = Rule.objects.filter(pk__in=info.rule_ids)
rules_file = {f"{rule.ruleset.name}_{rule.pk}": rule.path for rule in rules}
rules = yara.compile(filepaths=rules_file)

# Manage duplicated file path
folder = f"/yara/customs/{request.user.username}"
os.makedirs(folder, exist_ok=True)
new_path = f"{folder}/{info.rulename}.yara"
filename, extension = os.path.splitext(new_path)
counter = 1
while os.path.exists(new_path):
new_path = f"{filename}{counter}{extension}"
counter += 1

rules.save(new_path)
CustomRule.objects.create(
user=request.user,
path=new_path,
name=info.rulename,
)

return 200, {"message": f"Rule {info.rulename} created"}
except Exception as excp:
return 400, {"errors": str(excp)}
4 changes: 2 additions & 2 deletions orochi/templates/users/user_bookmarks.html
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@
delay: 5000
});
},
error: function () {
error: function (data) {
$.toast({
title: 'Bookmark status!',
content: data.message,
Expand Down Expand Up @@ -217,7 +217,7 @@
delay: 5000
});
},
error: function () {
error: function (data) {
$.toast({
title: 'Bookmark status!',
content: data.message,
Expand Down
56 changes: 49 additions & 7 deletions orochi/templates/users/user_rules.html
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@
{
sortable: false,
render: function (data, type, row, meta) {
let down = `<a href='/ya/download_rule/${row[0]}' type='button' class='btn btn btn-outline' target='_blank'><i class='fa fa-download'></i></a>`;
let down = `<a href='/api/rules/${row[0]}/download' type='button' class='btn btn btn-outline' target='_blank'><i class='fa fa-download'></i></a>`;
down += `<button class='btn btn btn-outline btn-show' data-id='${row[0]}'><i class='fa fa-edit'></i></button>`;
return down;
}
Expand Down Expand Up @@ -204,15 +204,36 @@
if (rows_selected.length > 0) {
bootbox.confirm("Delete selected rules in your ruleset?", function (result) {
table.column(0).checkboxes.deselectAll();
let obj = {};
var items = [];
rows_selected.each(function (val) { items.push(val) });
obj['rule_ids'] = items;

$.ajaxSetup({
headers: { 'X-CSRFToken': $('input[name="csrfmiddlewaretoken"]').val() }
});

$.ajax({
url: "{% url 'ya:delete' %}",
method: 'get',
data: { 'rules': items },
url: "{% url 'api:delete_rules' %}",
method: 'delete',
data: JSON.stringify(obj),
dataType: 'json',
success: function (data) {
table.ajax.reload();
$.toast({
title: 'Rules Deleted!',
content: data.message,
type: 'success',
delay: 5000
});
},
error: function (data) {
$.toast({
title: 'Delete Rules error!',
content: data.message,
type: 'error',
delay: 5000
});
}
});
});
Expand All @@ -226,11 +247,17 @@
bootbox.prompt("Select name for compiled custom rule:", function (result) {
var items = [];
rows_selected.each(function (val) { items.push(val) });
var items_str = items.join(';');
let obj = {
'rule_ids': items,
'rulename': result
};
$.ajaxSetup({
headers: { 'X-CSRFToken': $('input[name="csrfmiddlewaretoken"]').val() }
});
$.ajax({
url: "{% url 'ya:build' %}",
url: "{% url 'api:rule_build' %}",
method: 'post',
data: { 'rules': items_str, 'rulename': result, 'csrfmiddlewaretoken': $("input[name=csrfmiddlewaretoken").val() },
data: JSON.stringify(obj),
dataType: 'json',
beforeSend: function () {
if (items.length > 50) {
Expand All @@ -246,6 +273,21 @@
if (items.length > 50) {
dialog.modal('hide');
}
$.toast({
title: 'Build Rule status!',
content: data.message,
type: 'success',
delay: 5000
});
},
error: function (data) {
$.toast({
title: 'Build Rule Error!',
content: data.message,
type: 'error',
delay: 5000
});
$("#modal-update").modal('hide');
}
});
});
Expand Down
Loading

0 comments on commit 30e3535

Please sign in to comment.