Skip to content

Commit 01c3ca3

Browse files
authored
Merge pull request #409 from target/iqy_scanner
Adding IQY Scanner
2 parents ea46573 + cf04dee commit 01c3ca3

File tree

12 files changed

+336
-160
lines changed

12 files changed

+336
-160
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
# Changelog
22
Changes to the project will be tracked in this file via the date of change.
33

4+
## 2023-11-04
5+
- Added `ScanIqy` to target and extract network addressed from IQY (Internet Query) files
6+
- Added tests for `ScanIqy`
7+
- Fix for a `poetry` build issue
8+
- Fix for `ScanPcap` tests
9+
410
## 2023-10-25
511
- Changes to `ScanExiftool` scanner and tests
612
- Update `google.golang.org/grpc` dependency

build/python/backend/Dockerfile

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -153,15 +153,21 @@ RUN mkdir jtr && cd jtr && git init && git remote add origin https://github.com/
153153
chmod -R 777 /jtr && \
154154
chown -R $USER_UID:$USER_UID /jtr
155155

156-
# Poetry setup
157-
RUN curl -sSL https://install.python-poetry.org | python3 - && \
158-
export PATH="/root/.local/bin:$PATH" && \
159-
poetry config virtualenvs.create false
156+
# Install Poetry globally and copy project files
157+
RUN python3 -m pip install -U pip setuptools && \
158+
python3 -m pip install poetry && \
159+
rm -rf /root/.cache/pip
160160

161-
# Project setup
162-
COPY ./pyproject.toml ./poetry.lock /strelka/
161+
# Set the working directory and copy the project files
163162
WORKDIR /strelka/
164-
RUN /root/.local/bin/poetry install --no-dev
163+
COPY pyproject.toml poetry.lock ./
164+
165+
# Use Poetry to install the project dependencies globally
166+
# This step is after the COPY step because it is more likely to change,
167+
# and therefore should not be included in earlier layers that can be cached.
168+
RUN poetry config virtualenvs.create false && \
169+
poetry install --no-dev && \
170+
rm -rf /root/.cache/pypoetry
165171

166172
# Copy Strelka files
167173
COPY ./src/python/ /strelka/

configs/python/backend/backend.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,11 @@ scanners:
249249
priority: 5
250250
options:
251251
limit: 50
252+
'ScanIqy':
253+
- positive:
254+
flavors:
255+
- 'iqy_file'
256+
priority: 5
252257
'ScanJarManifest':
253258
- positive:
254259
flavors:

configs/python/backend/taste/taste.yara

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,23 @@ rule excel4_file
335335
(uint32be(0) == 0x504b0304 and $rels and $sheet and $xlsstr)
336336
}
337337

338+
rule iqy_file {
339+
meta:
340+
description = "Detects potential IQY (Excel Web Query) files with various protocols"
341+
author = "Paul Hutelmyer"
342+
date = "2023-11-02"
343+
strings:
344+
$iqy_header = /^WEB\n/ nocase
345+
$http = /http:\/\// nocase
346+
$https = /https:\/\// nocase
347+
$ftp = /ftp:\/\// nocase
348+
$ftps = /ftps:\/\// nocase
349+
$file = /file:\/\// nocase
350+
$smb = /smb:\/\// nocase
351+
condition:
352+
$iqy_header at 0 and ($http or $https or $ftp or $ftps or $file or $smb)
353+
}
354+
338355
rule onenote_file
339356
{
340357
meta:

docs/README.md

Lines changed: 72 additions & 71 deletions
Large diffs are not rendered by default.

pyproject.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22
name = "strelka-worker"
33
version = "0.23.10.19"
44
description = "Strelka's backend Python worker"
5-
authors = ["Paul.Hutelmyer@Target.com", "Ryan.Ohoro@Target.com", "Sara.Kalupa@Target.com"]
5+
authors = [
6+
"Paul Hutelmyer <Paul.Hutelmyer@Target.com>",
7+
"Ryan Ohoro <Ryan.Ohoro@Target.com>",
8+
"Sara Kalupa <Sara.Kalupa@Target.com>"
9+
]
610

711
[tool.poetry.dependencies]
812
python = ">=3.10,<=3.12"
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Description #
2+
# This scanner is looking for iqy files used with excel.
3+
#
4+
# author: Tasha Taylor
5+
# date: 10/30/2023
6+
7+
import re
8+
9+
from strelka import strelka
10+
11+
12+
class ScanIqy(strelka.Scanner):
13+
"""
14+
Extract URLs from IQY files.
15+
16+
IQY files, or Excel Web Query Internet Inquire files, are typically created from a VBA Web Query output.
17+
The following is a typical format:
18+
WEB
19+
1
20+
[URL]
21+
[optional parameters]
22+
Additional properties can be found at: https://learn.microsoft.com/en-us/office/vba/api/excel.querytable
23+
"""
24+
25+
def scan(self, data, file, options, expire_at):
26+
try:
27+
# Regular expression for detecting a URL-like pattern
28+
address_pattern = re.compile(
29+
r"\b(?:http|https|ftp|ftps|file|smb)://\S+|"
30+
r"\\{2}\w+\\(?:[\w$]+\\)*[\w$]+",
31+
re.IGNORECASE,
32+
)
33+
34+
# Attempt UTF-8 decoding first, fall back to latin-1 if necessary
35+
try:
36+
data = data.decode("utf-8")
37+
except UnicodeDecodeError:
38+
data = data.decode("latin-1")
39+
40+
# Split lines to review each record separately
41+
data_lines = data.splitlines()
42+
43+
addresses = set()
44+
# For each line, check if the line matches the address pattern.
45+
# In a typical IQY file, the "WEB" keyword is at the beginning of the file,
46+
# and what follows is usually just one URL with optional additional parameters.
47+
# However, because we are iterating lines anyway, lets check for additional addresses anyway.
48+
for entry in data_lines[1:]:
49+
match = address_pattern.search(entry)
50+
if match:
51+
address = match.group().strip()
52+
if address:
53+
addresses.add(address)
54+
55+
# Evaluate if any addresses were found and assign the boolean result.
56+
self.event["address_found"] = bool(addresses)
57+
58+
# Send all addresses to the IOC parser.
59+
self.add_iocs(list(addresses), self.type.url)
60+
61+
except UnicodeDecodeError as e:
62+
self.flags.append(f"Unicode decoding error: {e}")
63+
except Exception as e:
64+
self.flags.append(f"Unexpected exception: {e}")

src/python/strelka/tests/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ def run_test_scan(
4747
expire_at=datetime.date.today(),
4848
)
4949

50+
# If a scanner outputs IOCs, append them to the event for test coverage
51+
if scanner.iocs:
52+
scanner.event.update({"iocs": scanner.iocs})
53+
5054
return scanner.event
5155

5256

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
WEB
2+
1
3+
https://github.com/target/strelka/blob/master/docs/index.html // Test case: Valid HTTPS URL
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
from pathlib import Path
2+
from unittest import TestCase, mock
3+
4+
from strelka.scanners.scan_iqy import ScanIqy as ScanUnderTest
5+
from strelka.tests import run_test_scan
6+
7+
8+
def test_scan_iqy(mocker):
9+
"""
10+
Pass: Sample event matches output of scanner.
11+
Failure: Unable to load file or sample event fails to match.
12+
"""
13+
14+
test_scan_event = {
15+
"elapsed": mock.ANY,
16+
"flags": [],
17+
"address_found": True,
18+
"iocs": [
19+
{
20+
"description": "",
21+
"ioc": "github.com",
22+
"ioc_type": "domain",
23+
"scanner": "ScanIqy",
24+
},
25+
{
26+
"description": "",
27+
"ioc": "https://github.com/target/strelka/blob/master/docs/index.html",
28+
"ioc_type": "url",
29+
"scanner": "ScanIqy",
30+
},
31+
],
32+
}
33+
scanner_event = run_test_scan(
34+
mocker=mocker,
35+
scan_class=ScanUnderTest,
36+
fixture_path=Path(__file__).parent / "fixtures/test.iqy",
37+
)
38+
39+
TestCase.maxDiff = None
40+
TestCase().assertDictEqual(test_scan_event, scanner_event)

0 commit comments

Comments
 (0)