Skip to content

Commit

Permalink
Merge pull request #1211 from akto-api-security/feature/improve-vulne…
Browse files Browse the repository at this point in the history
…rability-report

Feature/improve vulnerability report
  • Loading branch information
oren-akto authored Jun 24, 2024
2 parents 3a8227f + 671d329 commit 4e23022
Show file tree
Hide file tree
Showing 20 changed files with 995 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,7 @@ public String fetchVulnerableTestRunResults() {
return SUCCESS.toUpperCase();
}

private String getNodeResultLastMessage(String message) {
public static String getNodeResultLastMessage(String message) {
if (StringUtils.isEmpty(message) || "[]".equals(message)) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.akto.action.ExportSampleDataAction;
import com.akto.action.UserAction;
import com.akto.action.testing.StartTestAction;
import com.akto.dao.context.Context;
import com.akto.dao.demo.VulnerableRequestForTemplateDao;
import com.akto.dao.test_editor.YamlTemplateDao;
Expand All @@ -15,9 +16,7 @@
import com.akto.dto.test_editor.YamlTemplate;
import com.akto.dto.test_run_findings.TestingIssuesId;
import com.akto.dto.test_run_findings.TestingRunIssues;
import com.akto.dto.testing.GenericTestResult;
import com.akto.dto.testing.TestResult;
import com.akto.dto.testing.TestingRunResult;
import com.akto.dto.testing.*;
import com.akto.dto.testing.sources.TestSourceConfig;
import com.akto.log.LoggerMaker;
import com.akto.log.LoggerMaker.LogDb;
Expand All @@ -30,6 +29,7 @@
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.Sorts;
import com.mongodb.client.model.Updates;
import org.bouncycastle.util.test.Test;
import org.bson.conversions.Bson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -147,12 +147,38 @@ public String fetchVulnerableTestingRunResultsFromIssues() {
// todo: fix
for (TestingRunResult runResult: this.testingRunResults) {
List<GenericTestResult> testResults = new ArrayList<>();
WorkflowTest workflowTest = runResult.getWorkflowTest();
for (GenericTestResult tr : runResult.getTestResults()) {
TestResult testResult = (TestResult) tr;
if (testResult.isVulnerable()) {
testResults.add(testResult);
sampleDataVsCurlMap.put(testResult.getMessage(), ExportSampleDataAction.getCurl(testResult.getMessage()));
sampleDataVsCurlMap.put(testResult.getOriginalMessage(), ExportSampleDataAction.getCurl(testResult.getOriginalMessage()));
if (tr.isVulnerable()) {
if (tr instanceof TestResult) {
TestResult testResult = (TestResult) tr;
testResults.add(testResult);
sampleDataVsCurlMap.put(testResult.getMessage(), ExportSampleDataAction.getCurl(testResult.getMessage()));
sampleDataVsCurlMap.put(testResult.getOriginalMessage(), ExportSampleDataAction.getCurl(testResult.getOriginalMessage()));
} else if (tr instanceof MultiExecTestResult){
MultiExecTestResult testResult = (MultiExecTestResult) tr;
Map<String, WorkflowTestResult.NodeResult> nodeResultMap = testResult.getNodeResultMap();
for (String order : nodeResultMap.keySet()) {
WorkflowTestResult.NodeResult nodeResult = nodeResultMap.get(order);
String nodeResultLastMessage = StartTestAction.getNodeResultLastMessage(nodeResult.getMessage());
if (nodeResultLastMessage != null) {
nodeResult.setMessage(nodeResultLastMessage);
sampleDataVsCurlMap.put(nodeResultLastMessage,
ExportSampleDataAction.getCurl(nodeResultLastMessage));
}
}
}
}
if (workflowTest != null) {
Map<String, WorkflowNodeDetails> nodeDetailsMap = workflowTest.getMapNodeIdToWorkflowNodeDetails();
for (String nodeName: nodeDetailsMap.keySet()) {
if (nodeDetailsMap.get(nodeName) instanceof YamlNodeDetails) {
YamlNodeDetails details = (YamlNodeDetails) nodeDetailsMap.get(nodeName);
sampleDataVsCurlMap.put(details.getOriginalMessage(),
ExportSampleDataAction.getCurl(details.getOriginalMessage()));
}

}
}
}
runResult.setTestResults(testResults);
Expand Down
2 changes: 1 addition & 1 deletion apps/dashboard/src/main/java/com/akto/utils/Token.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public static String generateAccessToken(String username, String signedUp) throw
Calendar.MINUTE,
15
);

}

public Token(String refreshToken) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,5 @@ export default {
method: 'post',
data: {}
})
}

},
}
Original file line number Diff line number Diff line change
Expand Up @@ -594,8 +594,13 @@ const transform = {
resp.testSourceConfigs.forEach((x_1) => {
subCategoryFromSourceConfigMap[x_1.id] = x_1;
});
let categoryMap = {};
resp.categories.forEach((category) => {
categoryMap[category.name] = category;
});
PersistStore.getState().setSubCategoryMap(subCategoryMap);
PersistStore.getState().setSubCategoryFromSourceConfigMap(subCategoryFromSourceConfigMap);
PersistStore.getState().setCategoryMap(categoryMap);
},
prettifySummaryTable(summaries) {
summaries = summaries.map((obj) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { Box, Divider, Link, Text, VerticalStack } from "@shopify/polaris"
import ReportSummaryInfoCard from "./ReportSummaryInfoCard"
import Issue from "./Issue"

function Category({ index, categoryName, categoryIssues, categoryMap, categoryVsApisCountMap }) {

const categoryDisplayName = categoryMap?.[categoryName]?.displayName || ""

const categorySummaryItems = [
{
title: "Total issues",
data: categoryIssues.length,
},
{
title: "APIs affected",
data: categoryVsApisCountMap[categoryName]
}
]

return (
<Box id={categoryName} paddingBlockStart={6} paddingBlockEnd={8} paddingInlineStart={5} paddingInlineEnd={5}>
<VerticalStack gap="4">
<Text variant='headingMd'>
2.{index + 1} {categoryDisplayName}
</Text>

<ReportSummaryInfoCard summaryItems={categorySummaryItems} />

<Box>
<VerticalStack gap="2">
<Text variant='headingSm'>
Issues
</Text>
<VerticalStack gap="2">
{categoryIssues.length > 0 && categoryIssues.map((issueDetails, index) => {
const name = issueDetails?.name
const testName = issueDetails?.testName
return (
<Link key={index} url={`#${name}`} removeUnderline>
<Text variant='bodySm' color='subdued'>
{index + 1}. {testName}
</Text>
</Link>
)
})}
</VerticalStack>
</VerticalStack>
</Box>

<Divider/>

<Box>
{categoryIssues.map((issueDetails, index) => {
return (
<Issue key={index} issueDetails={issueDetails} index={index} isLast={index === categoryIssues.length - 1}/>
)
})}
</Box>

</VerticalStack>
</Box>
)
}

export default Category
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { Box, Divider, HorizontalStack, Link, Text, VerticalStack } from "@shopify/polaris"
import { useEffect, useState } from "react"
import GetPrettifyEndpoint from '@/apps/dashboard/pages/observe/GetPrettifyEndpoint';
import GithubSimpleTable from "@/apps/dashboard/components/tables/GithubSimpleTable";

function Issue({ index, issueDetails, isLast }) {
const [vulnerableApisState, setVulnerableApisState] = useState([])

const removeTrailingQuotes = (str) => {
return str.replace(/['"]+/g, '')
}

const getTestResultLink = () => {
if (issueDetails.vulnerableTestingRunResults.length === 0) {
return ''
}

const firstVulnerableApi = issueDetails.vulnerableTestingRunResults[0]
const testRunResultSummaryHexId = firstVulnerableApi.testRunHexId
const testRunId = firstVulnerableApi.hexId

return `/dashboard/testing/${testRunResultSummaryHexId}/result/${testRunId}`
}
const testResultLink = getTestResultLink()

const name = issueDetails?.name
const testName = issueDetails?.testName
const issueDescription = removeTrailingQuotes(issueDetails?.issueDescription)
const issueImpact = removeTrailingQuotes(issueDetails?.issueImpact)

const vulnerableApisCount = issueDetails?.vulnerableTestingRunResults.length

const resourceName = {
singular: 'API',
plural: 'APIs',
};

const apisHeader = (
<HorizontalStack align="space-between">
<Text variant="headingXs" fontWeight='bold'>{vulnerableApisCount} {vulnerableApisCount === 1 ? resourceName.singular : resourceName.plural} affected</Text>
<Link url={testResultLink} removeUnderline target="_blank">
<Text variant='bodyMd'>See in akto</Text>
</Link>
</HorizontalStack>

)
const headers = [
{
text: apisHeader,
title: apisHeader,
value: 'apiDetails',
textValue: 'apiDetailsText'
}
]

useEffect(() => {
let vulnerableApis = issueDetails?.vulnerableTestingRunResults.map((vulnerableApi, index) => {
const { url, method } = vulnerableApi.apiInfoKey

const apiDetails = (
<GetPrettifyEndpoint method={method} url={url} isNew={false} />
)

return {
key: index,
apiDetailsText: `${method} ${url}`,
apiDetails: apiDetails,
}
})

const VULNERABLE_APIS_LIMIT = 5

if (vulnerableApis.length > VULNERABLE_APIS_LIMIT) {
const moreApisText = `+ ${vulnerableApis.length - VULNERABLE_APIS_LIMIT} more`
vulnerableApis = vulnerableApis.slice(0, VULNERABLE_APIS_LIMIT)

vulnerableApis.push({
key: VULNERABLE_APIS_LIMIT + 1,
apiDetailsText: "+more",
apiDetails: <Text variant='bodySm' fontWeight="bold" color="subdued">{moreApisText}</Text>
})
}

setVulnerableApisState(vulnerableApis)
}, [])

return (
<Box id={name} paddingBlockStart={ index === 0 ? 0 : 4}>
<VerticalStack gap="4">
<Text variant='headingSm'>
{index + 1}. {testName}
</Text>

<Text variant='bodySm' color='subdued'>
{issueDescription}
</Text>

<GithubSimpleTable
key="table"
data={vulnerableApisState}
resourceName={resourceName}
headers={headers}
useNewRow={true}
condensedHeight={true}
hideQueryField={true}
headings={headers}
hidePagination={true}
/>

<VerticalStack gap="1">
<Text variant='headingXs'>
Why is this a problem?
</Text>
<Text variant='bodySm' color='subdued'>
{issueImpact}
</Text>
</VerticalStack>

{!isLast ? <Divider />: null}
</VerticalStack>
</Box>
)
}

export default Issue
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Box, Text, VerticalStack } from "@shopify/polaris";
import HighchartsReact from "highcharts-react-official";
import Highcharts from "highcharts"
import { useRef } from "react";

function IssuesGraph({ categoryIssues }) {

const issuesGraphRef = useRef(null)

let dataArr = [] ;
Object.values(categoryIssues).forEach((issuesList)=>{
dataArr.push(issuesList.length)
})

const issuesGraphOptions = {
chart: {
type: 'column',
height: '280px',
spacing: [5, 0, 0, 0],
},
credits: {
enabled: false,
},
title: {
text: '',
align: 'left',
margin: 20
},
xAxis: {
categories: Object.keys(categoryIssues),
crosshair: true,
accessibility: {
description: 'Categories'
}
},
yAxis: {
min: 0,
title: {
text: '# of issues'
}
},
legend: {
enabled: false
},
series: {
minPointLength: 0,
pointWidth: 40,
color: "#FDA29B",
cursor: 'pointer',
data: dataArr
}
}

return (
<VerticalStack gap={3}>
<Text variant="headingXs">Number of issues</Text>
<Box>
<HighchartsReact
highcharts={Highcharts}
options={issuesGraphOptions}
ref={issuesGraphRef}
/>
</Box>

</VerticalStack>
)
}

export default IssuesGraph
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Box, Text, VerticalStack } from "@shopify/polaris"

function ReportRecommendations({ title, sno, itemsList }) {
return (
<Box id="akto-recommendations" paddingBlockStart={6} paddingBlockEnd={8} paddingInlineStart={5} paddingInlineEnd={5}>
<VerticalStack gap="3">
<Text variant="headingLg">{sno}. {title}</Text>
{itemsList.map((item, index) => {
return (
<Box key={index}>
<Text variant='bodySm'> <b>{index + 1}. {item.title}: </b>{item.content}</Text>
</Box>
)
})}
</VerticalStack>
</Box>
)
}

export default ReportRecommendations
Loading

0 comments on commit 4e23022

Please sign in to comment.