Skip to content
Open
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
17 changes: 15 additions & 2 deletions components/Export.vue
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@

<script setup lang="ts">
import { Dialog, DialogPanel, DialogTitle, TransitionChild, TransitionRoot } from "@headlessui/vue";
import { NessusCsvHeaders } from "~/types/nessus";

const props = defineProps({
boundaryId: {
Expand Down Expand Up @@ -525,11 +526,23 @@ const ssaDownload = async () => {
};

const nessusDownload = async () => {
const { data: currentUser } = await useFetch("/api/auth/currentUser");
const selectedHeaders = [
NessusCsvHeaders.PluginId,
NessusCsvHeaders.CVE,
NessusCsvHeaders.CvssV2,
NessusCsvHeaders.CvssV3,
NessusCsvHeaders.Risk,
NessusCsvHeaders.Host,
NessusCsvHeaders.Protocol,
NessusCsvHeaders.Port,
NessusCsvHeaders.Name,
NessusCsvHeaders.Description,
NessusCsvHeaders.PluginOutput,
] as const;

const queryParams = new URLSearchParams();
queryParams.append("BoundaryId", boundaryId);
queryParams.append("userEmail", currentUser.value.email);
queryParams.append("selectedHeaders", selectedHeaders);
await fetch(`/api/boundaries/nessus?${queryParams}`, {
method: "GET",
headers: { "Content-Type": "application/json" },
Expand Down
6 changes: 6 additions & 0 deletions db/models/nessusReportItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
type CreationOptional,
type ForeignKey,
type NonAttribute,
Association,
} from "sequelize";
import type { NessusPlugin } from "./nessusPlugin";
import type { NessusReport } from "./nessusReport";
Expand Down Expand Up @@ -49,6 +50,11 @@
declare lastUpdate: CreationOptional<string>;
declare creationDate: CreationOptional<string>;
declare NessusReport?: NonAttribute<NessusReport>;
declare NessusPlugin?: NonAttribute<NessusPlugin>;

declare static associations: {

Check warning on line 55 in db/models/nessusReportItem.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Make this public static property readonly.

See more on https://sonarcloud.io/project/issues?id=mitre_tir&issues=AZ_35-_ynnsrpcWkvjI9&open=AZ_35-_ynnsrpcWkvjI9&pullRequest=245
NessusPlugin: Association<NessusReportItem, NessusPlugin>;
};
}

NessusReportItem.init(
Expand Down
169 changes: 116 additions & 53 deletions server/api/boundaries/nessus.get.ts
Original file line number Diff line number Diff line change
@@ -1,67 +1,130 @@
import { Readable } from "node:stream";
import { sendStream } from "h3";
import { generateNessusCsv } from "../../utils/excelExport/nessusExport";
import { NessusOverride } from "~/db/models/nessusOverride";
import { NessusPlugin } from "~/db/models/nessusPlugin";
import { NessusReport } from "~/db/models/nessusReport";
import { NessusReportItem } from "~/db/models/nessusReportItem";
import { Boundary, BoundaryInterface, System } from "~/db/models";
import { NessusPluginTypes } from "~/types/nessus";
import { Boundary, System } from "~/db/models";
import { NessusCsvHeaders, type HeaderLabel } from "~/types/nessus";
import { Cve } from "~/db/models/cve";
import { NessusOverride } from "~/db/models/nessusOverride";

export default defineEventHandler(async (event) => {
const body = await getQuery(event);
const checkResult = await userCheck(event, undefined, body.BoundaryId?.toString(), undefined);
if (checkResult.BoundaryRoleId) {
const boundary = await Boundary.findOne({ where: { id: body.BoundaryId } });
if (!boundary) {
throw createError({
statusCode: 400,
statusMessage: `No Existing Boundary with specified ID found: \n` + body.BoundaryId,
});
}
const systems = await System.findAll({ where: { BoundaryId: body.BoundaryId } });
var reports: NessusReport[] = [];
for (const system of systems) {
console.log("Getting export for system", system.id);
const report = await NessusReport.findOne({
where: { SystemId: system.id },
include: [
{
model: NessusReportItem,
include: [
{
model: NessusPlugin,
},
],
},
],
});
if (report != null) {
reports.push(report);
} else {
console.log("No report data for system", system.id);
}
}
const nessusCSV = await generateNessusCsv(body.BoundaryId, reports);

const csvString = nessusCSV.map((row) => row.join(",")).join("\n");
const myBuffer = Buffer.from(csvString, "utf-8");
const stream = new Readable();
stream.push(myBuffer);
stream.push(null);

var fileName = "Boundary_" + boundary.name.replaceAll(" ", "") + "_NessusExport.csv";
setResponseHeader(event, "Content-Disposition", 'attachment; filename="' + fileName + '"');
setResponseHeader(event, "Content-Type", "application/octet-stream");
logger.info({
service: "Boundary",
message: `${body.userEmail} Downloaded Nessus Data for boundary ID: ${body.BoundaryId}`,
const query = getQuery(event);

if (!query.BoundaryId) {
throw createError({
statusCode: 400,
statusMessage: `BoundaryId required.`,
});
return sendStream(event, stream);
} else {
}

const BoundaryId = parseInt(query.BoundaryId?.toString(), 10);

Check warning on line 22 in server/api/boundaries/nessus.get.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `Number.parseInt` over `parseInt`.

See more on https://sonarcloud.io/project/issues?id=mitre_tir&issues=AZ_35-8hnnsrpcWkvjIy&open=AZ_35-8hnnsrpcWkvjIy&pullRequest=245

if (isNaN(BoundaryId)) {

Check warning on line 24 in server/api/boundaries/nessus.get.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `Number.isNaN` over `isNaN`.

See more on https://sonarcloud.io/project/issues?id=mitre_tir&issues=AZ_35-8hnnsrpcWkvjIz&open=AZ_35-8hnnsrpcWkvjIz&pullRequest=245
throw createError({
statusCode: 400,
statusMessage: `Invalid BoundaryId ${query.BoundaryId}`,
});
}

const checkResult = await userCheck(event, undefined, query.BoundaryId?.toString(), undefined);

if (!checkResult.BoundaryRoleId) {
throw createError({
statusCode: 401,
statusCode: 403,
statusMessage: "Insufficient Permissions.",
});
}
if (!query.selectedHeaders) {
throw createError({
statusCode: 400,
statusMessage: `No headers selected.`,
});
}

const values = Object.values(NessusCsvHeaders);
const selectedHeaders = query.selectedHeaders
.toString()
.split(",")
.map((header) => header.trim())
.filter((header): header is HeaderLabel => values.includes(header as HeaderLabel));

const factorOverrides = query.factorOverrides?.toString().toLowerCase() !== "false";

const boundary = await Boundary.findByPk(BoundaryId);
if (!boundary) {
throw createError({
statusCode: 404,
statusMessage: `No Existing Boundary with specified ID found: \n` + BoundaryId,
});
}

const overrideLookup: { [id: number]: NessusOverride[][] } = {};

const systems = await System.findAll({ where: { BoundaryId } });
const reports: NessusReport[] = [];
for (const system of systems) {
logger.info({
service: "Boundary Nessus Get",
message: `Getting nessus export content for: ${system.id} from boundary ${BoundaryId}.`,
});
const report = await NessusReport.findOne({
where: { SystemId: system.id },
include: [
{
model: NessusReportItem,
include: [
{
model: NessusPlugin,
include: [
{
model: Cve,
},
],
},
],
},
],
});
if (report != null) {
reports.push(report);
} else {
logger.info({
service: "Boundary Nessus Get",
message: `No report data for system: ${system.id} to be exported in boundary ${BoundaryId} Nessus export.`,
});
}

const overrides = await NessusOverride.findAll({
where: {
SystemId: system.id,
},
});

if (Array.isArray(overrides)) {
overrideLookup[system.id] = [overrides];
}
}

const nessusCSV = await generateNessusCsv(
reports,
overrideLookup,
selectedHeaders,
factorOverrides,
);

const myBuffer = Buffer.from(nessusCSV, "utf-8");
const stream = new Readable();
stream.push(myBuffer);
stream.push(null);

const fileName = "Boundary_" + boundary.name.replaceAll(" ", "") + "_NessusExport.csv";
setResponseHeader(event, "Content-Disposition", 'attachment; filename="' + fileName + '"');
setResponseHeader(event, "Content-Type", "application/octet-stream");
logger.info({
service: "Boundary",
message: `${checkResult.user.email} Downloaded Nessus Data for boundary ID: ${query.BoundaryId}`,
});
return sendStream(event, stream);
});
23 changes: 23 additions & 0 deletions server/utils/excelExport/excel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export function limitExcelCell(input: string): string {
if (!input) return "";

let hasWrappingQuotes = false;

if (input.startsWith('"') && input.endsWith('"')) {
hasWrappingQuotes = true;
input = input.slice(1, -1); // remove first and last quote
}

let truncated = input.slice(0, 32767);

const lines = truncated.split("\n");
if (lines.length > 254) {
truncated = lines.slice(0, 254).join("\n");
}

if (hasWrappingQuotes) {
return `"${truncated}"`;
}

return truncated;
}
Loading