Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding ScanVb Card #62

Merged
merged 2 commits into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 55 additions & 16 deletions app/services/insights.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,61 @@ def check_mime_type(strelka_data: dict) -> str:
str: Insight message or empty string.
"""
try:
file_name = strelka_data["file"]["name"]
if "." not in file_name:
# No file extension present
return ""

file_extension = strelka_data["file"]["name"].split(".")[-1]
mime_type = strelka_data["file"]["flavors"]["mime"][0]
expected_extensions = {
"application/x-dosexec": ["exe", "dll"],
"image/bmp": ["bmp"],
}
if (
mime_type in expected_extensions
and file_extension.lower() not in expected_extensions[mime_type]
):
return f"The file extension .{file_extension} does not match the expected extension for its MIME type ({mime_type})."
file_info = strelka_data.get("file", {})
file_name = file_info.get("name", "")
file_extension = (
file_name.rpartition(".")[2].lower() if "." in file_name else None
)

if file_extension:
mime_type = file_info.get("flavors", {}).get("mime", [None])[0]
expected_extensions = {
"application/x-dosexec": ["exe", "dll"],
"image/bmp": ["bmp"],
"image/jpeg": ["jpg", "jpeg"],
"image/png": ["png"],
"image/gif": ["gif"],
"text/html": ["html", "htm"],
"text/plain": ["txt"],
"application/pdf": ["pdf"],
"application/msword": ["doc"],
"application/vnd.ms-excel": ["xls"],
"application/vnd.ms-powerpoint": ["ppt"],
"application/zip": ["zip"],
"application/x-rar-compressed": ["rar"],
"application/x-tar": ["tar"],
"application/x-7z-compressed": ["7z"],
"application/x-bzip2": ["bz2"],
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": [
"docx"
],
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": [
"xlsx"
],
"application/vnd.openxmlformats-officedocument.presentationml.presentation": [
"pptx"
],
"audio/mpeg": ["mp3"],
"audio/ogg": ["ogg"],
"video/mp4": ["mp4"],
"video/mpeg": ["mpeg", "mpg"],
"application/javascript": ["js"],
"application/json": ["json"],
"application/xml": ["xml"],
"application/x-shockwave-flash": ["swf"],
"application/x-msdownload": ["exe", "msi"],
"application/x-font-ttf": ["ttf"],
"font/otf": ["otf"],
"application/x-font-woff": ["woff"],
"application/x-font-woff2": ["woff2"],
}
if (
mime_type in expected_extensions
and file_extension not in expected_extensions[mime_type]
):
return f"The file extension .{file_extension} does not match the expected extension for its MIME type ({mime_type})."

return ""
except Exception as e:
logging.warning(f"Error in MIME type check: {e}")
return ""
Expand Down
9 changes: 5 additions & 4 deletions ui/src/components/FileComponents/FileTypeOverviewCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const FileTypeOverviewCard = ({ data, onFileTypeSelect }) => {
const [selectedFileType, setSelectedFileType] = useState(null);
const [showMoreFileTypes, setShowMoreFileTypes] = useState(false);
const MAX_ITEMS_VISIBLE = 10;


// Handler for selecting or deselecting a file type
const selectFileType = (mimeType) => {
Expand All @@ -22,12 +23,12 @@ const FileTypeOverviewCard = ({ data, onFileTypeSelect }) => {

// Iterate over the strelka_response to populate mimeTypeDetails
data.strelka_response.forEach((response) => {
const mimeType = response.file.flavors.mime[0];
const mimeType = (response.file?.flavors?.yara && response.file.flavors?.yara[0]) || response.file.flavors.mime[0];
if (mimeTypeDetails[mimeType]) {
mimeTypeDetails[mimeType].count++;
mimeTypeDetails[mimeType].files.push(response.file.name); // Add filename to array
mimeTypeDetails[mimeType].files.push(response.file.name || response.file.scan?.hash.md5); // Add filename to array
} else {
mimeTypeDetails[mimeType] = { count: 1, files: [response.file.name] }; // Initialize with first filename
mimeTypeDetails[mimeType] = { count: 1, files: [response.file.name || response.scan?.hash.md5] }; // Initialize with first filename
}
});

Expand Down Expand Up @@ -122,4 +123,4 @@ const FileTypeOverviewCard = ({ data, onFileTypeSelect }) => {
);
};

export default FileTypeOverviewCard;
export default FileTypeOverviewCard;
77 changes: 77 additions & 0 deletions ui/src/components/FileComponents/VbOverviewCard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import React, { useState } from "react";
import { Input, Checkbox, Typography, Row, Col } from "antd";
import "../../styles/ExiftoolOverviewCard.css"

const { Text } = Typography;

const VbOverviewCard = ({ data }) => {
const [filter, setFilter] = useState("");
const [wrapText, setWrapText] = useState(false);
const [trimText, setTrimText] = useState(true);

// Function to process VB script data for display
const processVbData = (key) => {
return data.scan.vb[key]
.filter((value) => {
const searchTerm = filter.toLowerCase();
return !filter || value.toLowerCase().includes(searchTerm);
})
.map((value, index) => {
let processedValue = value;
if (trimText && typeof processedValue === "string") {
processedValue = processedValue.trim();
}
if (!wrapText) {
processedValue = typeof processedValue === "string" ? processedValue.replace(/\s+/g, " ") : processedValue;
}
return { line: index + 1, value: processedValue };
});
};

const renderVbSection = (title, key) => {
const vbData = processVbData(key);
return (
<div
className="exif-data-list"
style={{ maxHeight: "400px", overflow: "auto" }}
>
<Text strong style={{ fontSize: "14px", marginBottom: "10px" }}>{title}</Text>
{vbData.map(({ line, label, value }) => (
<Row key={line} className="exif-data-row">
<Col span={1} className="line-number">
<div style={{ fontSize: "12px" }}>{line}</div>
</Col>
<Col span={22} className="exif-data-value">
<Text code copyable style={{ fontSize: "12px" }}>
{typeof value === "string" ? value : JSON.stringify(value)}
</Text>
</Col>
</Row>
))}
</div>
);
};

return (
<div className="vb-overview">
<Row gutter={[16, 16]}>
<Col span={18}>
<Input placeholder="Filter" onChange={(e) => setFilter(e.target.value)} style={{ width: "100%" }} />
</Col>
<Col span={6}>
<Checkbox checked={wrapText} onChange={(e) => setWrapText(e.target.checked)}>Wrap</Checkbox>
<Checkbox checked={trimText} onChange={(e) => setTrimText(e.target.checked)}>Trim</Checkbox>
</Col>
</Row>

{renderVbSection("Tokens", "tokens")}
{renderVbSection("Comments", "comments")}
{renderVbSection("Functions", "functions")}
{renderVbSection("Names", "names")}
{renderVbSection("Operators", "operators")}
{renderVbSection("Strings", "strings")}
</div>
);
};

export default VbOverviewCard;
6 changes: 3 additions & 3 deletions ui/src/components/FileComponents/YaraTypeOverviewCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ const YaraTypeOverviewCard = ({ data, onFileYaraSelect }) => {
yaraMatches.forEach((match) => {
if (yaraCounts[match]) {
yaraCounts[match].count++;
yaraCounts[match].files.push(response.file.name);
yaraCounts[match].files.push(response.file.name || response.scan.hash.md5);
} else {
yaraCounts[match] = { count: 1, files: [response.file.name] };
yaraCounts[match] = { count: 1, files: [response.file.name || response.scan.hash.md5] };
}
});
});
Expand Down Expand Up @@ -199,4 +199,4 @@ const YaraTypeOverviewCard = ({ data, onFileYaraSelect }) => {
);
};

export default YaraTypeOverviewCard;
export default YaraTypeOverviewCard;
6 changes: 3 additions & 3 deletions ui/src/components/SubmissionTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -340,11 +340,11 @@ const SubmissionTable = () => {
) {
// Use the second record's MIME type if it exists
if (full.strelka_response.length > 1) {
mimeType = full.strelka_response[1].file.flavors.mime[0];
mimeType = full.strelka_response[1].file.flavors.yara[0] || full.strelka_response[1].file.flavors.mime[0];
}
} else {
// Use the first record's MIME type
mimeType = full.strelka_response[0].file.flavors.mime[0];
mimeType = full.strelka_response[0].file.flavors.yara[0] || full.strelka_response[0].file.flavors.mime[0];
}
}

Expand Down Expand Up @@ -483,4 +483,4 @@ const SubmissionTable = () => {
);
};

export default SubmissionTable;
export default SubmissionTable;
60 changes: 46 additions & 14 deletions ui/src/pages/SubmissionView.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import AuthCtx from "../contexts/auth";
import { fetchWithTimeout } from "../util";

import "../styles/IconContainer.css";
import VbOverviewCard from "../components/FileComponents/VbOverviewCard";

const { Text } = Typography;

Expand Down Expand Up @@ -78,12 +79,20 @@ const SubmissionsPage = (props) => {
}
}, [fileNameFilter, fileTypeFilter, fileYaraFilter]);


const getFileIcon = () => {
const mappingEntry = getIconConfig(
"strelka",
selectedNodeData["file"]["flavors"]["mime"][0].toLowerCase()
);
let flavorKey;
if (
selectedNodeData["file"]["flavors"]["yara"] &&
selectedNodeData["file"]["flavors"]["yara"].length > 0
) {
// Use YARA flavor if available
flavorKey = selectedNodeData["file"]["flavors"]["yara"][0].toLowerCase();
} else {
// Use MIME flavor if YARA is not available
flavorKey = selectedNodeData["file"]["flavors"]["mime"][0].toLowerCase();
}

const mappingEntry = getIconConfig("strelka", flavorKey);
const IconComponent = mappingEntry?.icon;
const bgColor = mappingEntry?.color || "defaultBackgroundColor";

Expand All @@ -105,12 +114,10 @@ const SubmissionsPage = (props) => {
if (virustotalPositives === -1) {
disposition = "Not Found on VirusTotal";
color = "default";
}
else if (virustotalPositives === -3) {
} else if (virustotalPositives === -3) {
disposition = "Exceeded VirusTotal Limit";
color = "warning";
}
else if (virustotalPositives === -2) {
color = "warning";
} else if (virustotalPositives === -2) {
disposition = "VirusTotal Not Enabled";
color = "default";
} else if (virustotalPositives > 5) {
Expand Down Expand Up @@ -266,7 +273,7 @@ const SubmissionsPage = (props) => {
defaultActiveKey={[1]}
style={{ width: "100%", marginBottom: "10px" }}
>
<Collapse.Panel header={<Text>File Mimetypes</Text>} key="1">
<Collapse.Panel header={<Text>File Types</Text>} key="1">
<FileTypeOverviewCard
data={data}
onFileTypeSelect={handleFileTypeSelect}
Expand Down Expand Up @@ -323,9 +330,13 @@ const SubmissionsPage = (props) => {
{getFileIcon()}
<div style={{ marginLeft: "8px" }}>
{" "}
<Text strong>{selectedNodeData.file.name}</Text>
<Text strong>
{selectedNodeData.file.name || "No Filename"}
</Text>
<div style={{ fontSize: "smaller", color: "#888" }}>
{selectedNodeData.file.flavors.mime[0]}
{(selectedNodeData.file.flavors?.yara &&
selectedNodeData.file.flavors.yara[0]) ||
selectedNodeData.file.flavors.mime[0]}
</div>
</div>
</div>
Expand Down Expand Up @@ -478,6 +489,27 @@ const SubmissionsPage = (props) => {
</Collapse.Panel>
</Collapse>
)}
{selectedNodeData && selectedNodeData.scan.vb && (
<Collapse
defaultActiveKey={[]}
style={{ width: "100%", marginBottom: "10px" }}
>
<Collapse.Panel
header={
<div style={{ marginLeft: "8px" }}>
<Text strong>Visual Basic</Text>
<div style={{ fontSize: "smaller", color: "#888" }}>
Script Length:{" "}
{selectedNodeData.scan.vb?.script_length_bytes} bytes
</div>
</div>
}
key="1"
>
<VbOverviewCard data={selectedNodeData} />
</Collapse.Panel>
</Collapse>
)}
{selectedNodeData && selectedNodeData.scan.exiftool && (
<Collapse
defaultActiveKey={[]}
Expand Down Expand Up @@ -593,4 +625,4 @@ const SubmissionsPage = (props) => {
);
};

export default SubmissionsPage;
export default SubmissionsPage;
Loading