Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions modules/feodo-tracker/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dist/
node_modules/
21 changes: 21 additions & 0 deletions modules/feodo-tracker/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 rissrice2105-agent

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
69 changes: 69 additions & 0 deletions modules/feodo-tracker/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Feodo Tracker

A defensive ThreatCrush module that polls the public
[abuse.ch Feodo Tracker](https://feodotracker.abuse.ch/) botnet C2 blocklist and
emits structured `ThreatEvent` records for newly observed command-and-control
servers.

## Features

- Uses the official Feodo Tracker CSV feed; no API key is required.
- Emits active C2 indicators by default and can optionally include offline ones.
- Persists indicator keys to avoid repeating events after a restart.
- Validates IP addresses and ports before emitting an event.
- Requires HTTPS for custom feed URLs and limits events per poll.
- Includes parser and event-mapping tests.

This module consumes threat intelligence only. It does not scan, contact, or
attempt to exploit any listed server.

## Install

```bash
threatcrush modules install feodo-tracker
```

For local development in the ThreatCrush monorepo:

```bash
pnpm install
pnpm --filter threatcrush-module-feodo-tracker build
pnpm --filter threatcrush-module-feodo-tracker test
```

## Configuration

Copy `config/example.conf.toml` into the ThreatCrush module configuration and
adjust these values if needed:

| Setting | Default | Description |
| --- | --- | --- |
| `poll_interval_seconds` | `900` | Poll interval, with a runtime minimum of 60 seconds. |
| `feed_url` | Official Feodo CSV URL | HTTPS feed endpoint. |
| `emit_offline` | `false` | Emit inactive historical C2 entries too. |
| `max_events_per_poll` | `100` | Maximum new events emitted in one poll. |

## Event shape

Active C2 servers produce a high-severity network event:

```json
{
"module": "feodo-tracker",
"category": "network",
"severity": "high",
"message": "Feodo Tracker: QakBot C2 203.0.113.8:443 is online",
"source_ip": "203.0.113.8",
"details": {
"destination_ip": "203.0.113.8",
"destination_port": 443,
"c2_status": "online",
"malware": "QakBot"
}
}
```

## Data source and license

The module code is MIT licensed. Feodo Tracker data remains subject to the
[abuse.ch terms of use](https://feodotracker.abuse.ch/blocklist/).
6 changes: 6 additions & 0 deletions modules/feodo-tracker/config/example.conf.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[modules.feodo-tracker]
enabled = true
poll_interval_seconds = 900
feed_url = "https://feodotracker.abuse.ch/downloads/ipblocklist.csv"
emit_offline = false
max_events_per_poll = 100
22 changes: 22 additions & 0 deletions modules/feodo-tracker/mod.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[module]
name = "feodo-tracker"
version = "0.1.0"
description = "Monitors the abuse.ch Feodo Tracker feed for active botnet command-and-control servers"
author = "rissrice2105-agent"
license = "MIT"
homepage = "https://github.com/profullstack/threatcrush/tree/master/modules/feodo-tracker"

[module.pricing]
type = "free"

[module.requirements]
threatcrush = ">=0.2.0"
os = ["linux", "darwin", "win32"]
capabilities = ["network:outbound", "threat-intel:feed"]

[module.config.defaults]
enabled = true
poll_interval_seconds = 900
feed_url = "https://feodotracker.abuse.ch/downloads/ipblocklist.csv"
emit_offline = false
max_events_per_poll = 100
45 changes: 45 additions & 0 deletions modules/feodo-tracker/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"name": "threatcrush-module-feodo-tracker",
"version": "0.1.0",
"description": "Emits defensive threat-intelligence events for active botnet C2 indicators from Feodo Tracker",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",
"author": "rissrice2105-agent",
"homepage": "https://threatcrush.com/store/feodo-tracker",
"repository": {
"type": "git",
"url": "git+https://github.com/profullstack/threatcrush.git",
"directory": "modules/feodo-tracker"
},
"keywords": [
"threatcrush",
"threatcrush-module",
"feodo-tracker",
"botnet",
"command-and-control",
"threat-intelligence"
],
"files": [
"dist",
"mod.toml",
"config",
"README.md",
"LICENSE"
],
"scripts": {
"build": "tsc -p tsconfig.json",
"test": "vitest run",
"test:watch": "vitest",
"clean": "rm -rf dist"
},
"dependencies": {
"@threatcrush/sdk": "workspace:*"
},
"devDependencies": {
"@types/node": "^22.0.0",
"typescript": "^5.9.3",
"vitest": "^3.0.0"
}
}
64 changes: 64 additions & 0 deletions modules/feodo-tracker/src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { describe, expect, it } from 'vitest';

import {
indicatorKey,
parseFeodoCsv,
toThreatEvent,
} from '../index.js';

const FEED = `
################################################################
# abuse.ch Feodo Tracker Botnet C2 IP Blocklist (CSV)
"first_seen_utc","dst_ip","dst_port","c2_status","last_online","malware"
"2026-02-10 12:00:00","203.0.113.8","443","online","2026-08-10","QakBot"
"2026-02-11 12:00:00","198.51.100.4","8080","offline","2026-08-01","Emotet"
# END 2 entries
`;

describe('parseFeodoCsv', () => {
it('parses valid data rows and skips metadata', () => {
const indicators = parseFeodoCsv(FEED);

expect(indicators).toHaveLength(2);
expect(indicators[0]).toEqual({
firstSeenUtc: '2026-02-10 12:00:00',
destinationIp: '203.0.113.8',
destinationPort: 443,
status: 'online',
lastOnline: '2026-08-10',
malware: 'QakBot',
});
});

it('rejects invalid IP addresses and ports', () => {
const invalid = `
"2026-02-10 12:00:00","999.0.0.1","443","online","2026-08-10","QakBot"
"2026-02-10 12:00:00","203.0.113.8","70000","online","2026-08-10","QakBot"
`;

expect(parseFeodoCsv(invalid)).toEqual([]);
});
});

describe('event mapping', () => {
it('builds a stable key for a re-observed endpoint', () => {
const [first] = parseFeodoCsv(FEED);

expect(indicatorKey(first)).toBe(
'203.0.113.8:443:2026-02-10 12:00:00',
);
});

it('creates a high-severity network event for active C2', () => {
const [first] = parseFeodoCsv(FEED);
const event = toThreatEvent(first);

expect(event).toMatchObject({
module: 'feodo-tracker',
category: 'network',
severity: 'high',
source_ip: '203.0.113.8',
});
expect(event.timestamp.toISOString()).toBe('2026-02-10T12:00:00.000Z');
});
});
Loading