Internal enrichment connector for OpenCTI that queries the CIRCL Hashlookup API to identify known/legitimate files from their hashes (MD5, SHA-1, SHA-256).
When a StixFile or Artifact observable is created or enriched in OpenCTI, the connector:
- Extracts the hash (SHA-256 > SHA-1 > MD5 by priority)
- Queries the CIRCL Hashlookup API (free, no API key required)
- If found, enriches the observable with:
- 📝 STIX Note with file metadata (name, size, product, OS, trust)
- 🔗 External reference to Hashlookup
- 🏷️ Labels:
known-good,hashlookup:<source_db>,hashlookup:<trust_level> - 📊 Adjusted score: a hash with high trust gets its OpenCTI score lowered
The CIRCL Hashlookup API aggregates multiple sources:
- NSRL (National Software Reference Library) — Modern RDS, Legacy, Android, iOS
- Windows 10/11 builds (FR, NL, DE, UK, US)
- ~4.2 billion indexed hashes
The hashlookup:trust field ranges from 0 to 100:
| Score | Meaning |
|---|---|
| > 75 | Well-known file, appears in multiple sources → hashlookup:high-trust |
| 50-75 | Known file with moderate confidence → hashlookup:medium-trust |
| < 50 | Low confidence, needs verification → hashlookup:low-trust |
The HASHLOOKUP_TRUST_THRESHOLD setting (default: 50) determines the minimum trust level required to apply the known-good label.
Add to your OpenCTI docker-compose.yml:
connector-hashlookup:
build: ./hashlookup-connector
environment:
- OPENCTI_URL=http://opencti:8080
- OPENCTI_TOKEN=${OPENCTI_ADMIN_TOKEN}
- CONNECTOR_ID=<uuid4> # Generate with: python3 -c "import uuid; print(uuid.uuid4())"
- CONNECTOR_TYPE=INTERNAL_ENRICHMENT
- CONNECTOR_NAME=CIRCL Hashlookup
- CONNECTOR_SCOPE=StixFile,Artifact
- CONNECTOR_CONFIDENCE_LEVEL=80
- CONNECTOR_LOG_LEVEL=info
- CONNECTOR_AUTO=true
- CONNECTOR_UPDATE_EXISTING_DATA=true
- HASHLOOKUP_URL=https://hashlookup.circl.lu
- HASHLOOKUP_TRUST_THRESHOLD=50
- HASHLOOKUP_LABEL_KNOWN_GOOD=known-good
- HASHLOOKUP_MAX_TLP=TLP:AMBER
restart: always
depends_on:
- opencticd hashlookup-connector
pip install -r requirements.txt
cp src/config.yml.sample src/config.yml
# Edit config.yml with your settings
cd src && python3 main.py| Environment variable | config.yml | Description | Default |
|---|---|---|---|
OPENCTI_URL |
opencti.url |
OpenCTI instance URL | (required) |
OPENCTI_TOKEN |
opencti.token |
API token | (required) |
CONNECTOR_ID |
connector.id |
Connector UUID v4 | (required) |
CONNECTOR_AUTO |
connector.auto |
Auto-enrich on creation | true |
HASHLOOKUP_URL |
hashlookup.url |
Hashlookup API URL | https://hashlookup.circl.lu |
HASHLOOKUP_TRUST_THRESHOLD |
hashlookup.trust_threshold |
Min trust to label "known-good" | 50 |
HASHLOOKUP_LABEL_KNOWN_GOOD |
hashlookup.label_known_good |
Label name for known files | known-good |
HASHLOOKUP_MAX_TLP |
hashlookup.max_tlp |
Max TLP for enrichment | TLP:AMBER |
After enriching a SHA-1 hash found in Hashlookup:
Note added:
CIRCL Hashlookup Match
- File: openssl
- Size: 723944 bytes
- Source DB: nsrl_modern_rds
- Trust Level: 100/100
- Product: OpenSSL 1.1.1
- MD5: 34D827A288FA51B93297EF2A8A43B769
- SHA-1: 72F104BF11A12511154267328F069FE0541E841E
- SHA-256: 301C9EC7A9AADEE4D745E8FD...
Labels: known-good, hashlookup:nsrl_modern_rds, hashlookup:high-trust
Score: lowered from 50 → 10
If you don't want to send your lookups to the public CIRCL API, you can deploy your own instance:
- hashlookup-server
- A Bloom filter is also available for download (~700MB):
https://cra.circl.lu/hashlookup/hashlookup-full.bloom
Simply change HASHLOOKUP_URL to point to your local instance.
MIT