mirror of
https://github.com/FlipsideCrypto/eth-phishing-detect.git
synced 2026-02-06 03:06:43 +00:00
init commit
This commit is contained in:
commit
b67bcf233c
92
.gitignore
vendored
Normal file
92
.gitignore
vendored
Normal file
@ -0,0 +1,92 @@
|
||||
|
||||
# Created by https://www.gitignore.io/api/osx,node
|
||||
|
||||
### Node ###
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
|
||||
### OSX ###
|
||||
*.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
# End of https://www.gitignore.io/api/osx,node
|
||||
17
package.json
Normal file
17
package.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "eth-phishing-detect",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "node test"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"fast-levenshtein": "^2.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tape": "^4.8.0"
|
||||
}
|
||||
}
|
||||
89
src/config.json
Normal file
89
src/config.json
Normal file
@ -0,0 +1,89 @@
|
||||
{
|
||||
"whitelist": [
|
||||
"metamask.io",
|
||||
"myetherwallet.com",
|
||||
"myetheroll.com",
|
||||
"myetherapi.com",
|
||||
"ledgerwallet.com"
|
||||
],
|
||||
"blacklist": [
|
||||
"wallet-ethereum.net",
|
||||
"myelherwallel.com",
|
||||
"etherswap.org",
|
||||
"eos.ac",
|
||||
"uasfwallet.com",
|
||||
"ziber.io",
|
||||
"mvetherwallet.com",
|
||||
"etherswap.org",
|
||||
"myethewallet.net",
|
||||
"multiply-ethereum.info",
|
||||
"bittrex.comze.com",
|
||||
"karbon.vacau.com",
|
||||
"xn--myetherwallt-7db.com",
|
||||
"xn--myetherwallt-leb.com",
|
||||
"etherdelta.gitlhub.io",
|
||||
"etherdelta.glthub.io",
|
||||
"myethewallet.net",
|
||||
"myetherwillet.com",
|
||||
"digitaldevelopersfund.vacau.com",
|
||||
"myetherwallel.com",
|
||||
"myeltherwallet.com",
|
||||
"myelherwallet.com",
|
||||
"wwwmyetherwallet.com",
|
||||
"myethermwallet.com",
|
||||
"district-0x.io",
|
||||
"coin-dash.com",
|
||||
"coindash.ru",
|
||||
"myethervallet.com",
|
||||
"myetherwallet.com.gl",
|
||||
"myetherwallet.com.ua",
|
||||
"myÄ—therwallet.com",
|
||||
"myetherwallet.com.gl",
|
||||
"xn--mytherwallet-fvb.com",
|
||||
"district0x.net",
|
||||
"aragonproject.io",
|
||||
"coin-wallet.info",
|
||||
"coinswallet.info",
|
||||
"contribute-status.im",
|
||||
"secure-myetherwallet.com",
|
||||
"update-myetherwallet.com",
|
||||
"ether-api.com",
|
||||
"ether-wall.com",
|
||||
"mycoinwallet.net",
|
||||
"etherclassicwallet.com",
|
||||
"ethereumchamber.com",
|
||||
"ethereumchamber.net",
|
||||
"ethereumchest.com",
|
||||
"myethervvallet.com",
|
||||
"metherwallet.com",
|
||||
"mtetherwallet.com",
|
||||
"my-etherwallet.com",
|
||||
"my-etherwallet.in",
|
||||
"myeherwallet.com",
|
||||
"myetcwallet.com",
|
||||
"myetehrwallet.com",
|
||||
"myeterwallet.com",
|
||||
"myethe.rwallet.com",
|
||||
"myethereallet.com",
|
||||
"myetherieumwallet.com",
|
||||
"myetherswallet.com",
|
||||
"myetherw.allet.com",
|
||||
"myetherwal.let.com",
|
||||
"myetherwalet.com",
|
||||
"myetherwaliet.com",
|
||||
"myetherwall.et.com",
|
||||
"myetherwaller.com",
|
||||
"myetherwallett.com",
|
||||
"myetherwaillet.com",
|
||||
"myetherwalllet.com",
|
||||
"myetherweb.com.de",
|
||||
"myethetwallet.com",
|
||||
"myethewallet.com",
|
||||
"omg-omise.co",
|
||||
"omise-go.com",
|
||||
"tenx-tech.com",
|
||||
"tokensale-tenx.tech",
|
||||
"ubiqcoin.org",
|
||||
"metamask.com"
|
||||
]
|
||||
}
|
||||
73
src/detector.js
Normal file
73
src/detector.js
Normal file
@ -0,0 +1,73 @@
|
||||
const levenshtein = require('fast-levenshtein')
|
||||
const DEFAULT_TOLERANCE = 4
|
||||
|
||||
class PhishingDetector {
|
||||
|
||||
constructor (opts) {
|
||||
this.blacklist = processDomainList(opts.blacklist || [])
|
||||
this.whitelist = processDomainList(opts.whitelist || [])
|
||||
this.tolerance = ('tolerance' in opts) ? opts.tolerance : DEFAULT_TOLERANCE
|
||||
}
|
||||
|
||||
check (domain) {
|
||||
const source = domainToParts(domain)
|
||||
|
||||
// if source matches whitelist domain (or subdomain thereof), PASS
|
||||
const whitelistMatch = matchPartsAgainstList(source, this.whitelist)
|
||||
if (whitelistMatch) return { type: 'whitelist', result: false }
|
||||
|
||||
// if source matches blacklist domain (or subdomain thereof), FAIL
|
||||
const blacklistMatch = matchPartsAgainstList(source, this.blacklist)
|
||||
if (blacklistMatch) return { type: 'blacklist', result: true }
|
||||
|
||||
// check if near-match of whitelist domain, FAIL
|
||||
const fuzzyForm = domainPartsToFuzzyForm(source)
|
||||
const levenshteinMatched = this.whitelist.find((targetParts) => {
|
||||
const fuzzyTarget = domainPartsToFuzzyForm(targetParts)
|
||||
const distance = levenshtein.get(fuzzyForm, fuzzyTarget)
|
||||
return distance <= this.tolerance
|
||||
})
|
||||
if (levenshteinMatched) {
|
||||
const match = domainPartsToDomain(levenshteinMatched)
|
||||
return { type: 'fuzzy', result: true, match }
|
||||
}
|
||||
|
||||
// matched nothing, PASS
|
||||
return { type: 'all', result: false }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = PhishingDetector
|
||||
|
||||
// util
|
||||
|
||||
function processDomainList (list) {
|
||||
return list.map(domainToParts)
|
||||
}
|
||||
|
||||
function domainToParts (domain) {
|
||||
return domain.split('.').reverse()
|
||||
}
|
||||
|
||||
function domainPartsToDomain(domainParts) {
|
||||
return domainParts.slice().reverse().join('.')
|
||||
}
|
||||
|
||||
// for fuzzy search, drop TLD and re-stringify
|
||||
function domainPartsToFuzzyForm(domainParts) {
|
||||
return domainParts.slice(1).reverse().join('.')
|
||||
}
|
||||
|
||||
// match the target parts, ignoring extra subdomains on source
|
||||
// source: [io, metamask, xyz]
|
||||
// target: [io, metamask]
|
||||
// result: PASS
|
||||
function matchPartsAgainstList(source, list) {
|
||||
return list.some((target) => {
|
||||
// target domain has more parts than source, fail
|
||||
if (target.length > source.length) return false
|
||||
// source matches target or (is deeper subdomain)
|
||||
return target.every((part, index) => source[index] === part)
|
||||
})
|
||||
}
|
||||
11
src/index.js
Normal file
11
src/index.js
Normal file
@ -0,0 +1,11 @@
|
||||
const PhishingDetector = require('./detector')
|
||||
const config = require('./config.json')
|
||||
|
||||
const detector = new PhishingDetector(config)
|
||||
|
||||
module.exports = checkDomain
|
||||
|
||||
|
||||
function checkDomain(domain) {
|
||||
return detector.check(domain).result
|
||||
}
|
||||
186
test/index.js
Normal file
186
test/index.js
Normal file
@ -0,0 +1,186 @@
|
||||
const test = require('tape')
|
||||
const PhishingDetector = require('../src/detector')
|
||||
const config = require('../src/config.json')
|
||||
|
||||
const detector = new PhishingDetector(config)
|
||||
|
||||
|
||||
test('basic test', (t) => {
|
||||
|
||||
// blacklist
|
||||
|
||||
testDomain(t, {
|
||||
domain: 'metamask.com',
|
||||
type: 'blacklist',
|
||||
expected: true,
|
||||
})
|
||||
|
||||
testDomain(t, {
|
||||
domain: 'myetherwaillet.com',
|
||||
type: 'blacklist',
|
||||
expected: true,
|
||||
})
|
||||
|
||||
testDomain(t, {
|
||||
domain: 'myetherwaller.com',
|
||||
type: 'blacklist',
|
||||
expected: true,
|
||||
})
|
||||
|
||||
testDomain(t, {
|
||||
domain: 'myetherweb.com.de',
|
||||
type: 'blacklist',
|
||||
expected: true,
|
||||
})
|
||||
|
||||
testDomain(t, {
|
||||
domain: 'myeterwallet.com',
|
||||
type: 'blacklist',
|
||||
expected: true,
|
||||
})
|
||||
|
||||
// whitelist
|
||||
|
||||
testDomain(t, {
|
||||
domain: 'ledgerwallet.com',
|
||||
type: 'whitelist',
|
||||
expected: false,
|
||||
})
|
||||
|
||||
// whitelist subdomains
|
||||
|
||||
testDomain(t, {
|
||||
domain: 'www.metamask.io',
|
||||
type: 'whitelist',
|
||||
expected: false,
|
||||
})
|
||||
|
||||
testDomain(t, {
|
||||
domain: 'faucet.metamask.io',
|
||||
type: 'whitelist',
|
||||
expected: false,
|
||||
})
|
||||
|
||||
testDomain(t, {
|
||||
domain: 'zero.metamask.io',
|
||||
type: 'whitelist',
|
||||
expected: false,
|
||||
})
|
||||
|
||||
testDomain(t, {
|
||||
domain: 'zero-faucet.metamask.io',
|
||||
type: 'whitelist',
|
||||
expected: false,
|
||||
})
|
||||
|
||||
// fuzzy
|
||||
|
||||
testDomain(t, {
|
||||
domain: 'metmask.io',
|
||||
type: 'fuzzy',
|
||||
expected: true,
|
||||
})
|
||||
|
||||
testDomain(t, {
|
||||
domain: 'myetherwallet.cx',
|
||||
type: 'fuzzy',
|
||||
expected: true,
|
||||
})
|
||||
|
||||
testDomain(t, {
|
||||
domain: 'myetherwallet.aaa',
|
||||
type: 'fuzzy',
|
||||
expected: true,
|
||||
})
|
||||
|
||||
testDomain(t, {
|
||||
domain: 'myetherwallet.za',
|
||||
type: 'fuzzy',
|
||||
expected: true,
|
||||
})
|
||||
|
||||
testDomain(t, {
|
||||
domain: 'myetherwallet.z',
|
||||
type: 'fuzzy',
|
||||
expected: true,
|
||||
})
|
||||
|
||||
// no match
|
||||
|
||||
testDomain(t, {
|
||||
domain: 'example.com',
|
||||
type: 'all',
|
||||
expected: false,
|
||||
})
|
||||
|
||||
testDomain(t, {
|
||||
domain: 'etherscan.io',
|
||||
type: 'all',
|
||||
expected: false,
|
||||
})
|
||||
|
||||
testDomain(t, {
|
||||
domain: 'ethereum.org',
|
||||
type: 'all',
|
||||
expected: false,
|
||||
})
|
||||
|
||||
testDomain(t, {
|
||||
domain: 'etherid.org',
|
||||
type: 'all',
|
||||
expected: false,
|
||||
})
|
||||
|
||||
testDomain(t, {
|
||||
domain: 'ether.cards',
|
||||
type: 'all',
|
||||
expected: false,
|
||||
})
|
||||
|
||||
testDomain(t, {
|
||||
domain: 'easyeth.com',
|
||||
type: 'all',
|
||||
expected: false,
|
||||
})
|
||||
|
||||
testDomain(t, {
|
||||
domain: 'etherdomain.com',
|
||||
type: 'all',
|
||||
expected: false,
|
||||
})
|
||||
|
||||
testDomain(t, {
|
||||
domain: 'ethnews.com',
|
||||
type: 'all',
|
||||
expected: false,
|
||||
})
|
||||
|
||||
testDomain(t, {
|
||||
domain: 'cryptocompare.com',
|
||||
type: 'all',
|
||||
expected: false,
|
||||
})
|
||||
|
||||
testDomain(t, {
|
||||
domain: 'kraken.com',
|
||||
type: 'all',
|
||||
expected: false,
|
||||
})
|
||||
|
||||
testDomain(t, {
|
||||
domain: 'myetherwallet.groovehq.com',
|
||||
type: 'all',
|
||||
expected: false,
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
function testDomain(t, { domain, type, expected }) {
|
||||
const value = detector.check(domain)
|
||||
if (value.type === 'fuzzy') {
|
||||
t.comment(`"${domain}" fuzzy matches against "${value.match}"`)
|
||||
}
|
||||
t.equal(value.type, type, `type: "${domain}" should be "${type}"`)
|
||||
t.equal(value.result, expected, `result: "${domain}" should be match "${expected}"`)
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user