Skip to content

Commit 1e4070f

Browse files
Add more advanced malware download test cases (#257)
* Add more advanced drive-by download techniques. * Add sample malware files to host. * Move delay to download route as arg. * eslint --fix * Fix download delay route. * Formatting + add file content links * Slightly modify all sample files.
1 parent 1d6ee6d commit 1e4070f

File tree

6 files changed

+160
-16
lines changed

6 files changed

+160
-16
lines changed

security/badware/malware-download.html

Lines changed: 103 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,92 @@
66
<title>Malware download page</title>
77
<script>
88
// eslint-disable-next-line no-unused-vars
9-
function run() {
10-
const url = "/security/badware/phishing-redirect/download";
9+
function getUrl() {
10+
const delayedCheckbox = document.getElementById('delayedCheckbox');
11+
return delayedCheckbox.checked ? "/security/badware/phishing-redirect/download?delay=5000" : "/security/badware/phishing-redirect/download";
12+
}
13+
14+
function linkHrefDownload() {
15+
const url = getUrl();
1116
const link = document.createElement('a');
1217
link.href = url;
1318
document.body.appendChild(link);
1419
link.click();
1520
document.body.removeChild(link);
1621
}
22+
23+
async function blobDownload() {
24+
const url = getUrl();
25+
const response = await fetch(url);
26+
const blob = await response.blob();
27+
const link = document.createElement('a');
28+
link.href = window.URL.createObjectURL(blob);
29+
link.download = 'example.exe';
30+
document.body.appendChild(link);
31+
link.click();
32+
document.body.removeChild(link);
33+
window.URL.revokeObjectURL(link.href);
34+
}
35+
36+
function xhrDownload() {
37+
const url = getUrl();
38+
const xhr = new XMLHttpRequest();
39+
xhr.open('GET', url, true);
40+
xhr.responseType = 'blob';
41+
xhr.onload = function () {
42+
const blob = new Blob([xhr.response], { type: 'application/octet-stream' });
43+
const link = document.createElement('a');
44+
link.href = window.URL.createObjectURL(blob);
45+
link.download = 'example.exe';
46+
document.body.appendChild(link);
47+
link.click();
48+
document.body.removeChild(link);
49+
window.URL.revokeObjectURL(link.href);
50+
};
51+
xhr.send();
52+
}
53+
54+
function iframeDownload() {
55+
const url = getUrl();
56+
const iframe = document.createElement('iframe');
57+
iframe.style.display = 'none';
58+
iframe.src = url;
59+
document.body.appendChild(iframe);
60+
setTimeout(() => {
61+
document.body.removeChild(iframe);
62+
}, 1000); // Clean up after a second
63+
}
64+
65+
function windowLocationDownload() {
66+
const url = getUrl();
67+
window.location.href = url;
68+
}
69+
70+
async function fetchStreamDownload() {
71+
const url = getUrl();
72+
const response = await fetch(url);
73+
const reader = response.body.getReader();
74+
const contentLength = +response.headers.get('Content-Length');
75+
let receivedLength = 0; // Received bytes
76+
const chunks = []; // Array of received binary chunks (comprises the body)
77+
78+
while (true) {
79+
const { done, value } = await reader.read();
80+
if (done) break;
81+
chunks.push(value);
82+
receivedLength += value.length;
83+
console.log(`Received ${receivedLength} of ${contentLength}`);
84+
}
85+
86+
const blob = new Blob(chunks);
87+
const link = document.createElement('a');
88+
link.href = window.URL.createObjectURL(blob);
89+
link.download = 'example.exe';
90+
document.body.appendChild(link);
91+
link.click();
92+
document.body.removeChild(link);
93+
window.URL.revokeObjectURL(link.href);
94+
}
1795
</script>
1896
</head>
1997

@@ -25,8 +103,29 @@ <h1>Malware download page</h1>
25103
<p>This is an example malware page that DuckDuckGo clients intend to block. If you arrive here by mistake; there's
26104
nothing to worry about, we just use this page to test if our client blocking is working.</p>
27105

28-
<button id="run" onclick="run()">Download Button</button>
29-
<a href="/security/badware/phishing-redirect/download">Download Link</a>
106+
<h2>Blocked URL Targets</h2>
107+
<p>Click the buttons below to test various malware detection techniques based on JS file download techniques:</p>
108+
<label>
109+
<input type="checkbox" id="delayedCheckbox"> Delay Download
110+
</label>
111+
<ul>
112+
<li><button id="run" onclick="linkHrefDownload()">Href Download Button</button></li>
113+
<li><button id="run" onclick="blobDownload()">Blob Download Button</button></li>
114+
<li><button id="run" onclick="xhrDownload()">XHR Download Button</button></li>
115+
<li><button id="run" onclick="iframeDownload()">Iframe Download Button</button></li>
116+
<li><button id="run" onclick="windowLocationDownload()">Window Location Download Button</button></li>
117+
<li><button id="run" onclick="fetchStreamDownload()">Fetch Stream Download Button</button></li>
118+
<li><a href="/security/badware/phishing-redirect/download">Download Link</a></li>
119+
</ul>
120+
121+
<h2>Blocked File Content</h2>
122+
<p>These links are to files where the file content should be blocked, as opposed to the file URL or JS technique:</p>
123+
<ul>
124+
<li><a href="/security/badware/phishing-redirect/files/bad_app_file_on_scan.exe">Uncommon, then "malicious" warning after deep scanning</a></li>
125+
<li><a href="/security/badware/phishing-redirect/files/unknown.apk">File warning on Android</a></li>
126+
<li><a href="/security/badware/phishing-redirect/files/content.exe">File warning on Desktop</a></li>
127+
<li><a href="/security/badware/phishing-redirect/files/zip_password_1234.zip">Password Protected ZIP</a></li>
128+
</ul>
30129
</body>
31130

32131
</html>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
bad file for app test
2+
bda64933-b495-4a56-831e-ecb86351f038
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
random_data_here_not_actually_an_exe_nor_malicious
2+
all_this_data_can_be_safely_ignored
3+
fghij12345klmno67890pqrstuvwxyz
4+
abcdefgHIJKLMNOpqrstuvwxyz09876
5+
sample_text_line_1: The quick brown fox jumps over the lazy dog.
6+
sample_text_line_2: 42 is the answer to life, the universe, and everything.
7+
sample_text_line_3: Random number: 9876543210
8+
sample_text_line_4: {"name": "Test User", "age": 30, "active": true}
9+
sample_text_line_5: 3.14159, 2.71828, 1.41421, 0.57721
10+
dummy_data_entry_1: This is a placeholder for testing purposes.
11+
dummy_data_entry_2: Another line of random data for this file.
12+
dummy_data_entry_3: Just some more text to fill up space.
13+
dummy_data_entry_4: 12345-ABCDE-67890-FGHIJ-KLMNOP
14+
dummy_data_entry_5: 2025-02-10T17:40:00Z
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
not_a_real_apk_just_text
629 Bytes
Binary file not shown.

security/badware/server/routes.js

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
const express = require('express');
2+
const path = require('path');
3+
const fs = require('fs');
24
const router = express.Router();
35

46
// Returns a 301 redirect to the main phishing test page
@@ -61,20 +63,46 @@ router.post('/form', (req, res) => {
6163
res.send('Form submitted');
6264
});
6365

64-
// Serves an arbitrary executable file to test download detection
66+
// Serves an arbitrary executable file to test download detection, with optional delay
6567
router.get('/download', (req, res) => {
66-
// Create a buffer with a minimal valid PE header
67-
const fileData = Buffer.alloc(64);
68-
// MZ header (magic bytes)
69-
const magicBytes = [0x4d, 0x5a];
70-
// DOS stub filled with zeros
71-
const dosStub = new Uint8Array(58).fill(0);
72-
fileData.set(magicBytes, 0);
73-
fileData.set(dosStub, 2);
68+
const returnFile = () => {
69+
// Create a buffer with a minimal valid PE header
70+
const fileData = Buffer.alloc(64);
71+
// MZ header (magic bytes)
72+
const magicBytes = [0x4d, 0x5a];
73+
// DOS stub filled with zeros
74+
const dosStub = new Uint8Array(58).fill(0);
75+
fileData.set(magicBytes, 0);
76+
fileData.set(dosStub, 2);
7477

75-
res.setHeader('Content-Type', 'application/octet-stream');
76-
res.setHeader('Content-Disposition', 'attachment; filename="test.exe"');
77-
res.send(fileData);
78+
res.setHeader('Content-Type', 'application/octet-stream');
79+
res.setHeader(
80+
'Content-Disposition',
81+
'attachment; filename="test.exe"'
82+
);
83+
res.send(fileData);
84+
};
85+
if (req.query.delay && !isNaN(req.query.delay)) {
86+
if (req.query.delay > 5000) {
87+
return res.status(400).send('Delay too long');
88+
}
89+
setTimeout(() => {
90+
returnFile();
91+
}, req.query.delay);
92+
} else {
93+
returnFile();
94+
}
95+
});
96+
97+
// serve ./files/
98+
router.get('/files/:filename', (req, res) => {
99+
const filePath = path.join(__dirname, 'files', req.params.filename);
100+
fs.access(filePath, fs.constants.F_OK, (err) => {
101+
if (err) {
102+
return res.status(404).send('File not found');
103+
}
104+
res.sendFile(filePath);
105+
});
78106
});
79107

80108
module.exports = router;

0 commit comments

Comments
 (0)