Skip to content

Commit e8f3529

Browse files
authored
[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
1 parent a537f9a commit e8f3529

File tree

6 files changed

+279
-18
lines changed

6 files changed

+279
-18
lines changed

x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ describe('When invoking Trusted Apps Schema', () => {
7676
os: 'windows',
7777
entries: [
7878
{
79-
field: 'process.path',
79+
field: 'process.path.text',
8080
type: 'match',
8181
operator: 'included',
8282
value: 'c:/programs files/Anti-Virus',
@@ -194,7 +194,7 @@ describe('When invoking Trusted Apps Schema', () => {
194194
};
195195
expect(() => body.validate(bodyMsg2)).toThrow();
196196

197-
['process.hash.*', 'process.path'].forEach((field) => {
197+
['process.hash.*', 'process.path.text'].forEach((field) => {
198198
const bodyMsg3 = {
199199
...getCreateTrustedAppItem(),
200200
entries: [

x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ export const PostTrustedAppCreateRequestSchema = {
2626
os: schema.oneOf([schema.literal('linux'), schema.literal('macos'), schema.literal('windows')]),
2727
entries: schema.arrayOf(
2828
schema.object({
29-
field: schema.oneOf([schema.literal('process.hash.*'), schema.literal('process.path')]),
29+
field: schema.oneOf([
30+
schema.literal('process.hash.*'),
31+
schema.literal('process.path.text'),
32+
]),
3033
type: schema.literal('match'),
3134
operator: schema.literal('included'),
3235
value: schema.string({ minLength: 1 }),

x-pack/plugins/security_solution/common/endpoint/types/trusted_apps.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212

1313
/** API request params for retrieving a list of Trusted Apps */
1414
export type GetTrustedAppsListRequest = TypeOf<typeof GetTrustedAppsRequestSchema.query>;
15+
1516
export interface GetTrustedListAppsResponse {
1617
per_page: number;
1718
page: number;
@@ -21,12 +22,13 @@ export interface GetTrustedListAppsResponse {
2122

2223
/** API Request body for creating a new Trusted App entry */
2324
export type PostTrustedAppCreateRequest = TypeOf<typeof PostTrustedAppCreateRequestSchema.body>;
25+
2426
export interface PostTrustedAppCreateResponse {
2527
data: TrustedApp;
2628
}
2729

2830
export interface MacosLinuxConditionEntry {
29-
field: 'process.hash.*' | 'process.path';
31+
field: 'process.hash.*' | 'process.path.text';
3032
type: 'match';
3133
operator: 'included';
3234
value: string;

x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/logical_condition/components/condition_entry.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export const ConditionEntry = memo<ConditionEntryProps>(
7676
'xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.field.path',
7777
{ defaultMessage: 'Path' }
7878
),
79-
value: 'process.path',
79+
value: 'process.path.text',
8080
},
8181
];
8282
}, []);

x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/trusted_apps.test.ts

+231-7
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ import { ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../../../../lists/common/const
2626
import { EndpointAppContext } from '../../types';
2727
import { ExceptionListClient, ListClient } from '../../../../../lists/server';
2828
import { listMock } from '../../../../../lists/server/mocks';
29-
import { ExceptionListItemSchema } from '../../../../../lists/common/schemas/response';
29+
import {
30+
ExceptionListItemSchema,
31+
FoundExceptionListItemSchema,
32+
} from '../../../../../lists/common/schemas/response';
3033
import { DeleteTrustedAppsRequestParams } from './types';
3134
import { getExceptionListItemSchemaMock } from '../../../../../lists/common/schemas/response/exception_list_item_schema.mock';
3235

@@ -125,6 +128,97 @@ describe('when invoking endpoint trusted apps route handlers', () => {
125128
});
126129
});
127130

131+
it('should map Exception List Item to Trusted App item', async () => {
132+
const request = createListRequest(10, 100);
133+
const emptyResponse: FoundExceptionListItemSchema = {
134+
data: [
135+
{
136+
_tags: ['os:windows'],
137+
_version: undefined,
138+
comments: [],
139+
created_at: '2020-09-21T19:43:48.240Z',
140+
created_by: 'test',
141+
description: '',
142+
entries: [
143+
{
144+
field: 'process.hash.sha256',
145+
operator: 'included',
146+
type: 'match',
147+
value: 'a4370c0cf81686c0b696fa6261c9d3e0d810ae704ab8301839dffd5d5112f476',
148+
},
149+
{
150+
field: 'process.hash.sha1',
151+
operator: 'included',
152+
type: 'match',
153+
value: 'aedb279e378bed6c2db3c9dc9e12ba635e0b391c',
154+
},
155+
{
156+
field: 'process.hash.md5',
157+
operator: 'included',
158+
type: 'match',
159+
value: '741462ab431a22233c787baab9b653c7',
160+
},
161+
],
162+
id: '1',
163+
item_id: '11',
164+
list_id: 'trusted apps test',
165+
meta: undefined,
166+
name: 'test',
167+
namespace_type: 'agnostic',
168+
tags: [],
169+
tie_breaker_id: '1',
170+
type: 'simple',
171+
updated_at: '2020-09-21T19:43:48.240Z',
172+
updated_by: 'test',
173+
},
174+
],
175+
page: 10,
176+
per_page: 100,
177+
total: 0,
178+
};
179+
180+
exceptionsListClient.findExceptionListItem.mockResolvedValue(emptyResponse);
181+
await routeHandler(context, request, response);
182+
183+
expect(response.ok).toHaveBeenCalledWith({
184+
body: {
185+
data: [
186+
{
187+
created_at: '2020-09-21T19:43:48.240Z',
188+
created_by: 'test',
189+
description: '',
190+
entries: [
191+
{
192+
field: 'process.hash.*',
193+
operator: 'included',
194+
type: 'match',
195+
value: 'a4370c0cf81686c0b696fa6261c9d3e0d810ae704ab8301839dffd5d5112f476',
196+
},
197+
{
198+
field: 'process.hash.*',
199+
operator: 'included',
200+
type: 'match',
201+
value: 'aedb279e378bed6c2db3c9dc9e12ba635e0b391c',
202+
},
203+
{
204+
field: 'process.hash.*',
205+
operator: 'included',
206+
type: 'match',
207+
value: '741462ab431a22233c787baab9b653c7',
208+
},
209+
],
210+
id: '1',
211+
name: 'test',
212+
os: 'windows',
213+
},
214+
],
215+
page: 10,
216+
per_page: 100,
217+
total: 0,
218+
},
219+
});
220+
});
221+
128222
it('should log unexpected error if one occurs', async () => {
129223
exceptionsListClient.findExceptionListItem.mockImplementation(() => {
130224
throw new Error('expected error');
@@ -138,24 +232,26 @@ describe('when invoking endpoint trusted apps route handlers', () => {
138232

139233
describe('when creating a trusted app', () => {
140234
let routeHandler: RequestHandler<undefined, PostTrustedAppCreateRequest>;
141-
const createNewTrustedAppBody = (): PostTrustedAppCreateRequest => ({
235+
const createNewTrustedAppBody = (): {
236+
-readonly [k in keyof PostTrustedAppCreateRequest]: PostTrustedAppCreateRequest[k];
237+
} => ({
142238
name: 'Some Anti-Virus App',
143239
description: 'this one is ok',
144240
os: 'windows',
145241
entries: [
146242
{
147-
field: 'process.path',
243+
field: 'process.path.text',
148244
type: 'match',
149245
operator: 'included',
150246
value: 'c:/programs files/Anti-Virus',
151247
},
152248
],
153249
});
154-
const createPostRequest = () => {
250+
const createPostRequest = (body?: PostTrustedAppCreateRequest) => {
155251
return httpServerMock.createKibanaRequest<undefined, PostTrustedAppCreateRequest>({
156252
path: TRUSTED_APPS_LIST_API,
157253
method: 'post',
158-
body: createNewTrustedAppBody(),
254+
body: body ?? createNewTrustedAppBody(),
159255
});
160256
};
161257

@@ -197,7 +293,7 @@ describe('when invoking endpoint trusted apps route handlers', () => {
197293
description: 'this one is ok',
198294
entries: [
199295
{
200-
field: 'process.path',
296+
field: 'process.path.text',
201297
operator: 'included',
202298
type: 'match',
203299
value: 'c:/programs files/Anti-Virus',
@@ -224,7 +320,7 @@ describe('when invoking endpoint trusted apps route handlers', () => {
224320
description: 'this one is ok',
225321
entries: [
226322
{
227-
field: 'process.path',
323+
field: 'process.path.text',
228324
operator: 'included',
229325
type: 'match',
230326
value: 'c:/programs files/Anti-Virus',
@@ -247,6 +343,134 @@ describe('when invoking endpoint trusted apps route handlers', () => {
247343
expect(response.internalError).toHaveBeenCalled();
248344
expect(endpointAppContext.logFactory.get('trusted_apps').error).toHaveBeenCalled();
249345
});
346+
347+
it('should trim trusted app entry name', async () => {
348+
const newTrustedApp = createNewTrustedAppBody();
349+
newTrustedApp.name = `\n ${newTrustedApp.name} \r\n`;
350+
const request = createPostRequest(newTrustedApp);
351+
await routeHandler(context, request, response);
352+
expect(exceptionsListClient.createExceptionListItem.mock.calls[0][0].name).toEqual(
353+
'Some Anti-Virus App'
354+
);
355+
});
356+
357+
it('should trim condition entry values', async () => {
358+
const newTrustedApp = createNewTrustedAppBody();
359+
newTrustedApp.entries.push({
360+
field: 'process.path.text',
361+
value: '\n some value \r\n ',
362+
operator: 'included',
363+
type: 'match',
364+
});
365+
const request = createPostRequest(newTrustedApp);
366+
await routeHandler(context, request, response);
367+
expect(exceptionsListClient.createExceptionListItem.mock.calls[0][0].entries).toEqual([
368+
{
369+
field: 'process.path.text',
370+
operator: 'included',
371+
type: 'match',
372+
value: 'c:/programs files/Anti-Virus',
373+
},
374+
{
375+
field: 'process.path.text',
376+
value: 'some value',
377+
operator: 'included',
378+
type: 'match',
379+
},
380+
]);
381+
});
382+
383+
it('should convert hash values to lowercase', async () => {
384+
const newTrustedApp = createNewTrustedAppBody();
385+
newTrustedApp.entries.push({
386+
field: 'process.hash.*',
387+
value: '741462AB431A22233C787BAAB9B653C7',
388+
operator: 'included',
389+
type: 'match',
390+
});
391+
const request = createPostRequest(newTrustedApp);
392+
await routeHandler(context, request, response);
393+
expect(exceptionsListClient.createExceptionListItem.mock.calls[0][0].entries).toEqual([
394+
{
395+
field: 'process.path.text',
396+
operator: 'included',
397+
type: 'match',
398+
value: 'c:/programs files/Anti-Virus',
399+
},
400+
{
401+
field: 'process.hash.md5',
402+
value: '741462ab431a22233c787baab9b653c7',
403+
operator: 'included',
404+
type: 'match',
405+
},
406+
]);
407+
});
408+
409+
it('should detect md5 hash', async () => {
410+
const newTrustedApp = createNewTrustedAppBody();
411+
newTrustedApp.entries = [
412+
{
413+
field: 'process.hash.*',
414+
value: '741462ab431a22233c787baab9b653c7',
415+
operator: 'included',
416+
type: 'match',
417+
},
418+
];
419+
const request = createPostRequest(newTrustedApp);
420+
await routeHandler(context, request, response);
421+
expect(exceptionsListClient.createExceptionListItem.mock.calls[0][0].entries).toEqual([
422+
{
423+
field: 'process.hash.md5',
424+
value: '741462ab431a22233c787baab9b653c7',
425+
operator: 'included',
426+
type: 'match',
427+
},
428+
]);
429+
});
430+
431+
it('should detect sha1 hash', async () => {
432+
const newTrustedApp = createNewTrustedAppBody();
433+
newTrustedApp.entries = [
434+
{
435+
field: 'process.hash.*',
436+
value: 'aedb279e378bed6c2db3c9dc9e12ba635e0b391c',
437+
operator: 'included',
438+
type: 'match',
439+
},
440+
];
441+
const request = createPostRequest(newTrustedApp);
442+
await routeHandler(context, request, response);
443+
expect(exceptionsListClient.createExceptionListItem.mock.calls[0][0].entries).toEqual([
444+
{
445+
field: 'process.hash.sha1',
446+
value: 'aedb279e378bed6c2db3c9dc9e12ba635e0b391c',
447+
operator: 'included',
448+
type: 'match',
449+
},
450+
]);
451+
});
452+
453+
it('should detect sha256 hash', async () => {
454+
const newTrustedApp = createNewTrustedAppBody();
455+
newTrustedApp.entries = [
456+
{
457+
field: 'process.hash.*',
458+
value: 'a4370c0cf81686c0b696fa6261c9d3e0d810ae704ab8301839dffd5d5112f476',
459+
operator: 'included',
460+
type: 'match',
461+
},
462+
];
463+
const request = createPostRequest(newTrustedApp);
464+
await routeHandler(context, request, response);
465+
expect(exceptionsListClient.createExceptionListItem.mock.calls[0][0].entries).toEqual([
466+
{
467+
field: 'process.hash.sha256',
468+
value: 'a4370c0cf81686c0b696fa6261c9d3e0d810ae704ab8301839dffd5d5112f476',
469+
operator: 'included',
470+
type: 'match',
471+
},
472+
]);
473+
});
250474
});
251475

252476
describe('when deleting a trusted app', () => {

0 commit comments

Comments
 (0)