Skip to content

Instantly share code, notes, and snippets.

@nruth
Last active September 14, 2015 19:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nruth/e6f052ea3349c80731fe to your computer and use it in GitHub Desktop.
Save nruth/e6f052ea3349c80731fe to your computer and use it in GitHub Desktop.
ruby/rails user signup email screening with google safebrowsing api -- catch phishing domain typos in user email addresses
---
http_interactions:
- request:
method: get
uri: https://sb-ssl.google.com/safebrowsing/api/lookup?appver=1&client=your-app-name-here&key=abc123fakekey&pver=3.1&url=gmai.com
body:
encoding: US-ASCII
string: ''
headers:
Accept:
- "*/*; q=0.5, application/xml"
Accept-Encoding:
- gzip, deflate
Timeout:
- '4'
User-Agent:
- Ruby
response:
status:
code: 200
message: OK
headers:
Content-Type:
- application/octet-stream
P3p:
- CP="This is not a P3P policy! See http://www.google.com/support/accounts/bin/answer.py?hl=en&answer=151657
for more info."
X-Content-Type-Options:
- nosniff
Date:
- Wed, 05 Aug 2015 22:59:16 GMT
Server:
- HTTP server (unknown)
Content-Length:
- '7'
X-Xss-Protection:
- 1; mode=block
X-Frame-Options:
- SAMEORIGIN
Set-Cookie:
- NID=70=RqpUA10yjTPamIiNtmDoFuI7HMSg0TNISCuaU1SgRBpKY9ScNcBL6fHN7jaeynrEXevBm9WFlAgO3DRUQ1BOABzUAu6fZ-A-aGnzzmw8OAqdh-mi9ZlhmnqR8_XE7pF4;
expires=Thu, 04-Feb-2016 22:59:16 GMT; path=/; domain=.google.com; HttpOnly
- PREF=ID=1111111111111111:TM=1438815556:LM=1438815556:V=1:S=QbXwVGLqYBhLAblQ;
expires=Fri, 04-Aug-2017 22:59:16 GMT; path=/; domain=.google.com
Alternate-Protocol:
- 443:quic,p=1
Expires:
- Wed, 05 Aug 2015 22:59:16 GMT
Cache-Control:
- private
body:
encoding: UTF-8
string: malware
http_version:
recorded_at: Wed, 05 Aug 2015 22:59:16 GMT
recorded_with: VCR 2.9.3
require 'rest-client'
# sometimes users give the wrong email address
# moreso since smartphone keyboards, errant auto-correct, and big players mixing their domains inconsistently by country.
# even worse for some apps, they might type in a domain that's incorrect, but has a phishing operator sat on it running an MX server to intercept any email
# help your users out: warn them if they've done something that looks supicious
# e.g. if they registered with someguy@gmai.com instead of gmail.com, chances are it's an error
# You may find confirmation emails solve this problem, but your users may get pretty confused waiting for ones that will never arrive, losing you sales.
class EmailScreener
attr_reader :email
def initialize(email)
@email = email
end
# is the given email address probably a data-entry error
# e.g. algo@gmai.com instead of algo@gmail.com
def suspects_domain_error?
!domain_exists? || phishing?
end
# return the address if it could be understood
def parseable?
begin
Mail::Address.new(email).address
rescue Mail::Field::ParseError
false
end
end
def username
@username ||= Mail::Address.new(email).local
end
def domain
@domain ||= Mail::Address.new(email).domain
end
# is there a mail exchange (MX) record (DNS) for this domain?
def domain_exists?
ValidateEmail.mx_valid?(email)
end
def warnings
@warnings ||= if %w(hotmail.com hotmail.co.uk).include?(domain)
'Please double-check whether your address is with hotmail.com or hotmail.co.uk'
end
end
# how does google safebrowsing consider this domain?
# Is it a gmail-typo-phishing-site? e.g. gmai.com
def phishing?
unless safebrowsing_api_key.present?
Rails.logger.error "Safebrowsing API key missing"
return false
end
begin
response = RestClient.get(
'https://sb-ssl.google.com/safebrowsing/api/lookup',
timeout: safebrowsing_timeout,
params: {
key: safebrowsing_api_key,
appver: '1',
client: 'your-app-name-here',
pver:'3.1',
url: domain
}
)
# 200 for malware or phishing, 204 not malware
# https://developers.google.com/safe-browsing/lookup_guide
response.code == 200
rescue RestClient::Exception
Rails.logger.info "SafeBrowsing email check returned error #{e.code} for #{email}"
# can't tell, so just approve
false
rescue
Rails.logger.error "SafeBrowsing lookup unknown error #{e.message}"
# can't tell, so just approve
false
end
end
private
def safebrowsing_timeout
ENV['GOOGLE_SAFEBROWSING_TIMEOUT'] || 4
end
def safebrowsing_api_key
ENV['GOOGLE_SAFEBROWSING_API_KEY']
end
end
# -*- encoding : utf-8 -*-
require 'spec_helper'
describe "EmailScreener" do
describe 'phishing' do
it 'returns false when no API key present' do
expect(EmailScreener.new('foo@gmai.com')).not_to be_phishing
end
it 'suspects gmai.com when response says so (vcr/webmock)' do
original_key = ENV['GOOGLE_SAFEBROWSING_API_KEY']
begin
ENV['GOOGLE_SAFEBROWSING_API_KEY'] = 'abc123fakekey'
VCR.use_cassette('email-screener-google-safebrowsing', record: :once) do
screener = EmailScreener.new('foo@gmai.com')
expect(screener).to be_phishing
end
ensure
ENV['GOOGLE_SAFEBROWSING_API_KEY'] = original_key
end
end
end
describe 'suspects_domain_error?' do
it 'true for phishing' do
screener = EmailScreener.new('foo@gmai.com')
allow(screener).to receive(:phishing?).and_return true
expect(screener.suspects_domain_error?).to be true
end
it 'true for mx not found' do
expect(EmailScreener.new('foo@gmaix2kdlkjf.co.uk').suspects_domain_error?).to be true
end
end
describe 'parseable?' do
it 'returns the address if it could be read' do
expect(EmailScreener.new('foo@bar.net').parseable?).to eq('foo@bar.net')
end
it 'returns false if it couldnt understand the address' do
expect(EmailScreener.new('foo@bar.').parseable?).to be false
end
end
describe 'warnings' do
it 'warns hotmail users to check .com or .co.uk' do
hotmail_warning = 'Please double-check whether your address is with hotmail.com or hotmail.co.uk'
expect(EmailScreener.new('foo@hotmail.com').warnings).to eq(hotmail_warning)
expect(EmailScreener.new('foo@hotmail.co.uk').warnings).to eq(hotmail_warning)
end
end
describe 'username' do
it 'returns the username before @' do
expect(EmailScreener.new('foo@hotmail.com').username).to eq('foo')
end
it 'fails when address not parsable' do
end
end
describe 'domain' do
it 'returns the mailhost after @' do
expect(EmailScreener.new('foo@hotmail.com').domain).to eq('hotmail.com')
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment