init commit

This commit is contained in:
kumavis 2017-08-02 18:56:16 -07:00
commit b67bcf233c
6 changed files with 468 additions and 0 deletions

92
.gitignore vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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}"`)
}