mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[SECURITY_SOLUTION][ENDPOINT] Create Trusted Apps API changes to process user input (#78079)
* Convert new trusted app data to expected format for artifact * Renamed condition field `process.path` to `process.path.text` * determine hash type based on length of hash value * Convert `process.hash.[sha1|md5|sha256]` to `process.hash.*` for return on list api * Add test for conversion of ExceptionItem to TrustedApp Item
This commit is contained in:
parent
a537f9af50
commit
e8f3529828
6 changed files with 279 additions and 18 deletions
|
@ -76,7 +76,7 @@ describe('When invoking Trusted Apps Schema', () => {
|
|||
os: 'windows',
|
||||
entries: [
|
||||
{
|
||||
field: 'process.path',
|
||||
field: 'process.path.text',
|
||||
type: 'match',
|
||||
operator: 'included',
|
||||
value: 'c:/programs files/Anti-Virus',
|
||||
|
@ -194,7 +194,7 @@ describe('When invoking Trusted Apps Schema', () => {
|
|||
};
|
||||
expect(() => body.validate(bodyMsg2)).toThrow();
|
||||
|
||||
['process.hash.*', 'process.path'].forEach((field) => {
|
||||
['process.hash.*', 'process.path.text'].forEach((field) => {
|
||||
const bodyMsg3 = {
|
||||
...getCreateTrustedAppItem(),
|
||||
entries: [
|
||||
|
|
|
@ -26,7 +26,10 @@ export const PostTrustedAppCreateRequestSchema = {
|
|||
os: schema.oneOf([schema.literal('linux'), schema.literal('macos'), schema.literal('windows')]),
|
||||
entries: schema.arrayOf(
|
||||
schema.object({
|
||||
field: schema.oneOf([schema.literal('process.hash.*'), schema.literal('process.path')]),
|
||||
field: schema.oneOf([
|
||||
schema.literal('process.hash.*'),
|
||||
schema.literal('process.path.text'),
|
||||
]),
|
||||
type: schema.literal('match'),
|
||||
operator: schema.literal('included'),
|
||||
value: schema.string({ minLength: 1 }),
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
|
||||
/** API request params for retrieving a list of Trusted Apps */
|
||||
export type GetTrustedAppsListRequest = TypeOf<typeof GetTrustedAppsRequestSchema.query>;
|
||||
|
||||
export interface GetTrustedListAppsResponse {
|
||||
per_page: number;
|
||||
page: number;
|
||||
|
@ -21,12 +22,13 @@ export interface GetTrustedListAppsResponse {
|
|||
|
||||
/** API Request body for creating a new Trusted App entry */
|
||||
export type PostTrustedAppCreateRequest = TypeOf<typeof PostTrustedAppCreateRequestSchema.body>;
|
||||
|
||||
export interface PostTrustedAppCreateResponse {
|
||||
data: TrustedApp;
|
||||
}
|
||||
|
||||
export interface MacosLinuxConditionEntry {
|
||||
field: 'process.hash.*' | 'process.path';
|
||||
field: 'process.hash.*' | 'process.path.text';
|
||||
type: 'match';
|
||||
operator: 'included';
|
||||
value: string;
|
||||
|
|
|
@ -76,7 +76,7 @@ export const ConditionEntry = memo<ConditionEntryProps>(
|
|||
'xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.field.path',
|
||||
{ defaultMessage: 'Path' }
|
||||
),
|
||||
value: 'process.path',
|
||||
value: 'process.path.text',
|
||||
},
|
||||
];
|
||||
}, []);
|
||||
|
|
|
@ -26,7 +26,10 @@ import { ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../../../../lists/common/const
|
|||
import { EndpointAppContext } from '../../types';
|
||||
import { ExceptionListClient, ListClient } from '../../../../../lists/server';
|
||||
import { listMock } from '../../../../../lists/server/mocks';
|
||||
import { ExceptionListItemSchema } from '../../../../../lists/common/schemas/response';
|
||||
import {
|
||||
ExceptionListItemSchema,
|
||||
FoundExceptionListItemSchema,
|
||||
} from '../../../../../lists/common/schemas/response';
|
||||
import { DeleteTrustedAppsRequestParams } from './types';
|
||||
import { getExceptionListItemSchemaMock } from '../../../../../lists/common/schemas/response/exception_list_item_schema.mock';
|
||||
|
||||
|
@ -125,6 +128,97 @@ describe('when invoking endpoint trusted apps route handlers', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should map Exception List Item to Trusted App item', async () => {
|
||||
const request = createListRequest(10, 100);
|
||||
const emptyResponse: FoundExceptionListItemSchema = {
|
||||
data: [
|
||||
{
|
||||
_tags: ['os:windows'],
|
||||
_version: undefined,
|
||||
comments: [],
|
||||
created_at: '2020-09-21T19:43:48.240Z',
|
||||
created_by: 'test',
|
||||
description: '',
|
||||
entries: [
|
||||
{
|
||||
field: 'process.hash.sha256',
|
||||
operator: 'included',
|
||||
type: 'match',
|
||||
value: 'a4370c0cf81686c0b696fa6261c9d3e0d810ae704ab8301839dffd5d5112f476',
|
||||
},
|
||||
{
|
||||
field: 'process.hash.sha1',
|
||||
operator: 'included',
|
||||
type: 'match',
|
||||
value: 'aedb279e378bed6c2db3c9dc9e12ba635e0b391c',
|
||||
},
|
||||
{
|
||||
field: 'process.hash.md5',
|
||||
operator: 'included',
|
||||
type: 'match',
|
||||
value: '741462ab431a22233c787baab9b653c7',
|
||||
},
|
||||
],
|
||||
id: '1',
|
||||
item_id: '11',
|
||||
list_id: 'trusted apps test',
|
||||
meta: undefined,
|
||||
name: 'test',
|
||||
namespace_type: 'agnostic',
|
||||
tags: [],
|
||||
tie_breaker_id: '1',
|
||||
type: 'simple',
|
||||
updated_at: '2020-09-21T19:43:48.240Z',
|
||||
updated_by: 'test',
|
||||
},
|
||||
],
|
||||
page: 10,
|
||||
per_page: 100,
|
||||
total: 0,
|
||||
};
|
||||
|
||||
exceptionsListClient.findExceptionListItem.mockResolvedValue(emptyResponse);
|
||||
await routeHandler(context, request, response);
|
||||
|
||||
expect(response.ok).toHaveBeenCalledWith({
|
||||
body: {
|
||||
data: [
|
||||
{
|
||||
created_at: '2020-09-21T19:43:48.240Z',
|
||||
created_by: 'test',
|
||||
description: '',
|
||||
entries: [
|
||||
{
|
||||
field: 'process.hash.*',
|
||||
operator: 'included',
|
||||
type: 'match',
|
||||
value: 'a4370c0cf81686c0b696fa6261c9d3e0d810ae704ab8301839dffd5d5112f476',
|
||||
},
|
||||
{
|
||||
field: 'process.hash.*',
|
||||
operator: 'included',
|
||||
type: 'match',
|
||||
value: 'aedb279e378bed6c2db3c9dc9e12ba635e0b391c',
|
||||
},
|
||||
{
|
||||
field: 'process.hash.*',
|
||||
operator: 'included',
|
||||
type: 'match',
|
||||
value: '741462ab431a22233c787baab9b653c7',
|
||||
},
|
||||
],
|
||||
id: '1',
|
||||
name: 'test',
|
||||
os: 'windows',
|
||||
},
|
||||
],
|
||||
page: 10,
|
||||
per_page: 100,
|
||||
total: 0,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should log unexpected error if one occurs', async () => {
|
||||
exceptionsListClient.findExceptionListItem.mockImplementation(() => {
|
||||
throw new Error('expected error');
|
||||
|
@ -138,24 +232,26 @@ describe('when invoking endpoint trusted apps route handlers', () => {
|
|||
|
||||
describe('when creating a trusted app', () => {
|
||||
let routeHandler: RequestHandler<undefined, PostTrustedAppCreateRequest>;
|
||||
const createNewTrustedAppBody = (): PostTrustedAppCreateRequest => ({
|
||||
const createNewTrustedAppBody = (): {
|
||||
-readonly [k in keyof PostTrustedAppCreateRequest]: PostTrustedAppCreateRequest[k];
|
||||
} => ({
|
||||
name: 'Some Anti-Virus App',
|
||||
description: 'this one is ok',
|
||||
os: 'windows',
|
||||
entries: [
|
||||
{
|
||||
field: 'process.path',
|
||||
field: 'process.path.text',
|
||||
type: 'match',
|
||||
operator: 'included',
|
||||
value: 'c:/programs files/Anti-Virus',
|
||||
},
|
||||
],
|
||||
});
|
||||
const createPostRequest = () => {
|
||||
const createPostRequest = (body?: PostTrustedAppCreateRequest) => {
|
||||
return httpServerMock.createKibanaRequest<undefined, PostTrustedAppCreateRequest>({
|
||||
path: TRUSTED_APPS_LIST_API,
|
||||
method: 'post',
|
||||
body: createNewTrustedAppBody(),
|
||||
body: body ?? createNewTrustedAppBody(),
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -197,7 +293,7 @@ describe('when invoking endpoint trusted apps route handlers', () => {
|
|||
description: 'this one is ok',
|
||||
entries: [
|
||||
{
|
||||
field: 'process.path',
|
||||
field: 'process.path.text',
|
||||
operator: 'included',
|
||||
type: 'match',
|
||||
value: 'c:/programs files/Anti-Virus',
|
||||
|
@ -224,7 +320,7 @@ describe('when invoking endpoint trusted apps route handlers', () => {
|
|||
description: 'this one is ok',
|
||||
entries: [
|
||||
{
|
||||
field: 'process.path',
|
||||
field: 'process.path.text',
|
||||
operator: 'included',
|
||||
type: 'match',
|
||||
value: 'c:/programs files/Anti-Virus',
|
||||
|
@ -247,6 +343,134 @@ describe('when invoking endpoint trusted apps route handlers', () => {
|
|||
expect(response.internalError).toHaveBeenCalled();
|
||||
expect(endpointAppContext.logFactory.get('trusted_apps').error).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should trim trusted app entry name', async () => {
|
||||
const newTrustedApp = createNewTrustedAppBody();
|
||||
newTrustedApp.name = `\n ${newTrustedApp.name} \r\n`;
|
||||
const request = createPostRequest(newTrustedApp);
|
||||
await routeHandler(context, request, response);
|
||||
expect(exceptionsListClient.createExceptionListItem.mock.calls[0][0].name).toEqual(
|
||||
'Some Anti-Virus App'
|
||||
);
|
||||
});
|
||||
|
||||
it('should trim condition entry values', async () => {
|
||||
const newTrustedApp = createNewTrustedAppBody();
|
||||
newTrustedApp.entries.push({
|
||||
field: 'process.path.text',
|
||||
value: '\n some value \r\n ',
|
||||
operator: 'included',
|
||||
type: 'match',
|
||||
});
|
||||
const request = createPostRequest(newTrustedApp);
|
||||
await routeHandler(context, request, response);
|
||||
expect(exceptionsListClient.createExceptionListItem.mock.calls[0][0].entries).toEqual([
|
||||
{
|
||||
field: 'process.path.text',
|
||||
operator: 'included',
|
||||
type: 'match',
|
||||
value: 'c:/programs files/Anti-Virus',
|
||||
},
|
||||
{
|
||||
field: 'process.path.text',
|
||||
value: 'some value',
|
||||
operator: 'included',
|
||||
type: 'match',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should convert hash values to lowercase', async () => {
|
||||
const newTrustedApp = createNewTrustedAppBody();
|
||||
newTrustedApp.entries.push({
|
||||
field: 'process.hash.*',
|
||||
value: '741462AB431A22233C787BAAB9B653C7',
|
||||
operator: 'included',
|
||||
type: 'match',
|
||||
});
|
||||
const request = createPostRequest(newTrustedApp);
|
||||
await routeHandler(context, request, response);
|
||||
expect(exceptionsListClient.createExceptionListItem.mock.calls[0][0].entries).toEqual([
|
||||
{
|
||||
field: 'process.path.text',
|
||||
operator: 'included',
|
||||
type: 'match',
|
||||
value: 'c:/programs files/Anti-Virus',
|
||||
},
|
||||
{
|
||||
field: 'process.hash.md5',
|
||||
value: '741462ab431a22233c787baab9b653c7',
|
||||
operator: 'included',
|
||||
type: 'match',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should detect md5 hash', async () => {
|
||||
const newTrustedApp = createNewTrustedAppBody();
|
||||
newTrustedApp.entries = [
|
||||
{
|
||||
field: 'process.hash.*',
|
||||
value: '741462ab431a22233c787baab9b653c7',
|
||||
operator: 'included',
|
||||
type: 'match',
|
||||
},
|
||||
];
|
||||
const request = createPostRequest(newTrustedApp);
|
||||
await routeHandler(context, request, response);
|
||||
expect(exceptionsListClient.createExceptionListItem.mock.calls[0][0].entries).toEqual([
|
||||
{
|
||||
field: 'process.hash.md5',
|
||||
value: '741462ab431a22233c787baab9b653c7',
|
||||
operator: 'included',
|
||||
type: 'match',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should detect sha1 hash', async () => {
|
||||
const newTrustedApp = createNewTrustedAppBody();
|
||||
newTrustedApp.entries = [
|
||||
{
|
||||
field: 'process.hash.*',
|
||||
value: 'aedb279e378bed6c2db3c9dc9e12ba635e0b391c',
|
||||
operator: 'included',
|
||||
type: 'match',
|
||||
},
|
||||
];
|
||||
const request = createPostRequest(newTrustedApp);
|
||||
await routeHandler(context, request, response);
|
||||
expect(exceptionsListClient.createExceptionListItem.mock.calls[0][0].entries).toEqual([
|
||||
{
|
||||
field: 'process.hash.sha1',
|
||||
value: 'aedb279e378bed6c2db3c9dc9e12ba635e0b391c',
|
||||
operator: 'included',
|
||||
type: 'match',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should detect sha256 hash', async () => {
|
||||
const newTrustedApp = createNewTrustedAppBody();
|
||||
newTrustedApp.entries = [
|
||||
{
|
||||
field: 'process.hash.*',
|
||||
value: 'a4370c0cf81686c0b696fa6261c9d3e0d810ae704ab8301839dffd5d5112f476',
|
||||
operator: 'included',
|
||||
type: 'match',
|
||||
},
|
||||
];
|
||||
const request = createPostRequest(newTrustedApp);
|
||||
await routeHandler(context, request, response);
|
||||
expect(exceptionsListClient.createExceptionListItem.mock.calls[0][0].entries).toEqual([
|
||||
{
|
||||
field: 'process.hash.sha256',
|
||||
value: 'a4370c0cf81686c0b696fa6261c9d3e0d810ae704ab8301839dffd5d5112f476',
|
||||
operator: 'included',
|
||||
type: 'match',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when deleting a trusted app', () => {
|
||||
|
|
|
@ -10,7 +10,7 @@ import { NewTrustedApp, TrustedApp } from '../../../../common/endpoint/types';
|
|||
import { ExceptionListClient } from '../../../../../lists/server';
|
||||
import { ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../../../../lists/common/constants';
|
||||
|
||||
type NewExecptionItem = Parameters<ExceptionListClient['createExceptionListItem']>[0];
|
||||
type NewExceptionItem = Parameters<ExceptionListClient['createExceptionListItem']>[0];
|
||||
|
||||
/**
|
||||
* Map an ExcptionListItem to a TrustedApp item
|
||||
|
@ -23,7 +23,15 @@ export const exceptionItemToTrustedAppItem = (
|
|||
const { entries, description, created_by, created_at, name, _tags, id } = exceptionListItem;
|
||||
const os = osFromTagsList(_tags);
|
||||
return {
|
||||
entries,
|
||||
entries: entries.map((entry) => {
|
||||
if (entry.field.startsWith('process.hash')) {
|
||||
return {
|
||||
...entry,
|
||||
field: 'process.hash.*',
|
||||
};
|
||||
}
|
||||
return entry;
|
||||
}),
|
||||
description,
|
||||
created_at,
|
||||
created_by,
|
||||
|
@ -51,22 +59,46 @@ export const newTrustedAppItemToExceptionItem = ({
|
|||
entries,
|
||||
name,
|
||||
description = '',
|
||||
}: NewTrustedApp): NewExecptionItem => {
|
||||
}: NewTrustedApp): NewExceptionItem => {
|
||||
return {
|
||||
_tags: tagsListFromOs(os),
|
||||
comments: [],
|
||||
description,
|
||||
entries,
|
||||
// @ts-ignore
|
||||
entries: entries.map(({ value, ...newEntry }) => {
|
||||
let newValue = value.trim();
|
||||
|
||||
if (newEntry.field === 'process.hash.*') {
|
||||
newValue = newValue.toLowerCase();
|
||||
newEntry.field = `process.hash.${hashType(newValue)}`;
|
||||
}
|
||||
|
||||
return {
|
||||
...newEntry,
|
||||
value: newValue,
|
||||
};
|
||||
}),
|
||||
itemId: uuid.v4(),
|
||||
listId: ENDPOINT_TRUSTED_APPS_LIST_ID,
|
||||
meta: undefined,
|
||||
name,
|
||||
name: name.trim(),
|
||||
namespaceType: 'agnostic',
|
||||
tags: [],
|
||||
type: 'simple',
|
||||
};
|
||||
};
|
||||
|
||||
const tagsListFromOs = (os: NewTrustedApp['os']): NewExecptionItem['_tags'] => {
|
||||
const tagsListFromOs = (os: NewTrustedApp['os']): NewExceptionItem['_tags'] => {
|
||||
return [`os:${os}`];
|
||||
};
|
||||
|
||||
const hashType = (hash: string): 'md5' | 'sha256' | 'sha1' | undefined => {
|
||||
switch (hash.length) {
|
||||
case 32:
|
||||
return 'md5';
|
||||
case 40:
|
||||
return 'sha1';
|
||||
case 64:
|
||||
return 'sha256';
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue