mirror of
https://github.com/FlipsideCrypto/eth-phishing-detect.git
synced 2026-02-06 11:16:45 +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