Skip to content

Commit 37ceda6

Browse files
authored
Report cookies task (#44)
* Adds code to report cookies Signed-off-by: karthikuj <[email protected]>
1 parent 0832654 commit 37ceda6

File tree

7 files changed

+224
-5
lines changed

7 files changed

+224
-5
lines changed

.eslintrc.json

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"project": [
99
"./tsconfig.json",
1010
"./test/ContentScript/tsconfig.json",
11+
"./test/Background/tsconfig.json",
1112
"./__mocks__/tsconfig.json"
1213
],
1314
"sourceType": "module"

__mocks__/webextension-polyfill.ts

+24
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,36 @@ const Browser = {
2727
onMessage: {
2828
addListener: jest.fn(),
2929
},
30+
onInstalled: {
31+
addListener: jest.fn(),
32+
},
3033
},
3134
storage: {
3235
sync: {
3336
get: mockStorageSyncGet,
3437
},
3538
},
39+
cookies: {
40+
onChanged: {
41+
addListener: jest.fn(),
42+
},
43+
set: jest.fn().mockImplementation(() =>
44+
Promise.resolve({
45+
name: 'ZAP',
46+
value: 'Proxy',
47+
path: '/',
48+
domain: 'example.com',
49+
})
50+
),
51+
},
52+
action: {
53+
onClicked: {
54+
addListener: jest.fn(),
55+
},
56+
},
57+
tabs: {
58+
query: jest.fn(),
59+
},
3660
};
3761

3862
export default Browser;

source/Background/index.ts

+116-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
* limitations under the License.
1919
*/
2020
import 'emoji-log';
21-
import Browser, {Runtime} from 'webextension-polyfill';
21+
import Browser, {Cookies, Runtime} from 'webextension-polyfill';
2222
import {ReportedStorage} from '../types/ReportedModel';
2323
import {ZestScript, ZestScriptMessage} from '../types/zestScript/ZestScript';
2424

@@ -39,6 +39,105 @@ function zapApiUrl(zapurl: string, action: string): string {
3939
return `${zapurl}JSON/client/action/${action}/`;
4040
}
4141

42+
function getUrlFromCookieDomain(domain: string): string {
43+
return domain.startsWith('.')
44+
? `http://${domain.substring(1)}`
45+
: `http://${domain}`;
46+
}
47+
48+
function getCookieTabUrl(cookie: Cookies.Cookie): Promise<string> {
49+
const getAllTabs = Browser.tabs.query({
50+
currentWindow: true,
51+
});
52+
return new Promise((resolve, reject) => {
53+
getAllTabs
54+
.then((allTabs) => {
55+
for (const tab of allTabs) {
56+
if (tab.url) {
57+
const getAllCookiesForTab = Browser.cookies.getAll({url: tab.url});
58+
getAllCookiesForTab.then((cookies) => {
59+
for (const c of cookies) {
60+
if (
61+
c.name === cookie.name &&
62+
c.value === cookie.value &&
63+
c.domain === cookie.domain &&
64+
c.storeId === cookie.storeId
65+
) {
66+
resolve(
67+
tab.url ? tab.url : getUrlFromCookieDomain(cookie.domain)
68+
);
69+
}
70+
}
71+
});
72+
}
73+
}
74+
})
75+
.catch((error) => {
76+
console.error(`Could not fetch tabs: ${error.message}`);
77+
reject(getUrlFromCookieDomain(cookie.domain));
78+
});
79+
});
80+
}
81+
82+
function reportCookies(
83+
cookie: Cookies.Cookie,
84+
zapurl: string,
85+
zapkey: string
86+
): boolean {
87+
let cookieString = `${cookie.name}=${cookie.value}; path=${cookie.path}; domain=${cookie.domain}`;
88+
if (cookie.expirationDate) {
89+
cookieString = cookieString.concat(
90+
`; expires=${new Date(cookie.expirationDate * 1000).toUTCString()}`
91+
);
92+
}
93+
if (cookie.secure) {
94+
cookieString = cookieString.concat(`; secure`);
95+
}
96+
if (cookie.sameSite === 'lax' || cookie.sameSite === 'strict') {
97+
cookieString = cookieString.concat(`; SameSite=${cookie.sameSite}`);
98+
}
99+
if (cookie.httpOnly) {
100+
cookieString = cookieString.concat(`; HttpOnly`);
101+
}
102+
103+
getCookieTabUrl(cookie)
104+
.then((cookieUrl) => {
105+
const repStorage = new ReportedStorage(
106+
'Cookies',
107+
'',
108+
cookie.name,
109+
'',
110+
cookieString,
111+
cookieUrl
112+
);
113+
const repStorStr: string = repStorage.toShortString();
114+
if (
115+
!reportedStorage.has(repStorStr) &&
116+
repStorage.url.startsWith('http')
117+
) {
118+
const body = `objectJson=${encodeURIComponent(
119+
repStorage.toString()
120+
)}&apikey=${encodeURIComponent(zapkey)}`;
121+
122+
fetch(zapApiUrl(zapurl, 'reportObject'), {
123+
method: 'POST',
124+
body,
125+
headers: {
126+
'Content-Type': 'application/x-www-form-urlencoded',
127+
},
128+
});
129+
130+
reportedStorage.add(repStorStr);
131+
}
132+
})
133+
.catch((error) => {
134+
console.log(error);
135+
return false;
136+
});
137+
138+
return true;
139+
}
140+
42141
function handleMessage(
43142
request: MessageEvent,
44143
zapurl: string,
@@ -134,10 +233,24 @@ async function onMessageHandler(
134233
return Promise.resolve(val);
135234
}
136235

236+
function cookieChangeHandler(
237+
changeInfo: Cookies.OnChangedChangeInfoType
238+
): void {
239+
Browser.storage.sync
240+
.get({
241+
zapurl: 'http://localhost:8080/',
242+
zapkey: 'not set',
243+
})
244+
.then((items) => {
245+
reportCookies(changeInfo.cookie, items.zapurl, items.zapkey);
246+
});
247+
}
248+
137249
Browser.action.onClicked.addListener((_tab: Browser.Tabs.Tab) => {
138250
Browser.runtime.openOptionsPage();
139251
});
140252

253+
Browser.cookies.onChanged.addListener(cookieChangeHandler);
141254
Browser.runtime.onMessage.addListener(onMessageHandler);
142255

143256
Browser.runtime.onInstalled.addListener((): void => {
@@ -147,3 +260,5 @@ Browser.runtime.onInstalled.addListener((): void => {
147260
zapkey: 'not set',
148261
});
149262
});
263+
264+
export {reportCookies};

source/manifest.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"short_name": "OWASP ZAP",
1616

1717
"permissions": [
18-
"activeTab",
18+
"tabs",
19+
"cookies",
1920
"storage"
2021
],
2122

source/types/ReportedModel.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,16 @@ class ReportedObject {
4141
tagName: string,
4242
id: string,
4343
nodeName: string,
44-
text: string | null
44+
text: string | null,
45+
url: string = window.location.href
4546
) {
4647
this.timestamp = Date.now();
4748
this.type = type;
4849
this.tagName = tagName;
4950
this.id = id;
5051
this.nodeName = nodeName;
5152
this.text = text;
52-
this.url = window.location.href;
53+
this.url = url;
5354
}
5455

5556
public toString(): string {
@@ -80,7 +81,12 @@ class ReportedObject {
8081
class ReportedStorage extends ReportedObject {
8182
public toShortString(): string {
8283
return JSON.stringify(this, function replacer(k: string, v: string) {
83-
if (k === 'xpath' || k === 'url' || k === 'href' || k === 'timestamp') {
84+
if (
85+
k === 'xpath' ||
86+
k === 'href' ||
87+
k === 'timestamp' ||
88+
(k === 'url' && !(this.type === 'cookies'))
89+
) {
8490
// Storage events are not time or URL specific
8591
return undefined;
8692
}

test/Background/tsconfig.json

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"extends": "../../tsconfig.json",
3+
"compilerOptions": {
4+
"esModuleInterop": true,
5+
"module": "commonjs",
6+
"outDir": "../dist"
7+
},
8+
"include": [
9+
"**/*.test.ts"
10+
]
11+
}

test/Background/unitTests.test.ts

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/**
2+
* @jest-environment jsdom
3+
*/
4+
/*
5+
* Zed Attack Proxy (ZAP) and its related source files.
6+
*
7+
* ZAP is an HTTP/HTTPS proxy for assessing web application security.
8+
*
9+
* Copyright 2023 The ZAP Development Team
10+
*
11+
* Licensed under the Apache License, Version 2.0 (the "License");
12+
* you may not use this file except in compliance with the License.
13+
* You may obtain a copy of the License at
14+
*
15+
* http://www.apache.org/licenses/LICENSE-2.0
16+
*
17+
* Unless required by applicable law or agreed to in writing, software
18+
* distributed under the License is distributed on an "AS IS" BASIS,
19+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20+
* See the License for the specific language governing permissions and
21+
* limitations under the License.
22+
*/
23+
import {TextEncoder, TextDecoder} from 'util';
24+
import * as src from '../../source/Background/index';
25+
26+
console.log(src);
27+
console.log(TextEncoder);
28+
console.log(TextDecoder);
29+
30+
jest.mock('webextension-polyfill');
31+
32+
// These lines must appear before the JSDOM import
33+
global.TextEncoder = TextEncoder;
34+
global.TextDecoder = TextDecoder as typeof global.TextDecoder;
35+
36+
// eslint-disable-next-line import/order,import/first
37+
import Browser from 'webextension-polyfill';
38+
39+
test('Report storage', () => {
40+
// Given
41+
42+
const setCookie = Browser.cookies.set({
43+
url: 'https://www.example.com/',
44+
name: 'ZAP',
45+
value: 'Proxy',
46+
domain: 'example.com',
47+
path: '/',
48+
});
49+
50+
setCookie.then((newCookie) => {
51+
console.log(newCookie);
52+
// When
53+
const success = src.reportCookies(
54+
newCookie,
55+
'http://localhost:8080/',
56+
'secretKey'
57+
);
58+
// Then
59+
expect(success).toBe(true);
60+
});
61+
});

0 commit comments

Comments
 (0)