honeypot 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +31 -48
- data/Rakefile +4 -2
- data/VERSION +1 -1
- data/honeypot.gemspec +20 -12
- data/lib/honeypot.rb +48 -4
- data/lib/honeypot/ipaddr_ext.rb +18 -0
- data/lib/honeypot/rack.rb +17 -0
- data/lib/honeypot/railtie.rb +10 -0
- data/lib/{remote_host.rb → honeypot/remote_host.rb} +0 -9
- data/lib/{remote_request.rb → honeypot/remote_request.rb} +0 -2
- data/test/test_honeypot.rb +5 -2
- metadata +63 -18
- data/lib/remote_request_logger.rb +0 -36
data/README.rdoc
CHANGED
@@ -2,46 +2,59 @@
|
|
2
2
|
|
3
3
|
Catch bad guys when they stick their hands in the honey.
|
4
4
|
|
5
|
-
==
|
5
|
+
== rails 3 only
|
6
6
|
|
7
|
-
|
7
|
+
uses rack
|
8
8
|
|
9
|
-
==
|
9
|
+
== honeypot models
|
10
10
|
|
11
|
-
requestables
|
11
|
+
honeypots (aka requestables like User, Vote, etc.) should define #actor
|
12
12
|
|
13
13
|
class User < ActiveRecord::Base
|
14
|
-
|
14
|
+
has_many :votes
|
15
|
+
include Honeypot
|
15
16
|
def actor; self; end
|
16
17
|
end
|
17
18
|
|
18
19
|
class Vote < ActiveRecord::Base
|
19
20
|
belongs_to :user
|
20
|
-
include
|
21
|
+
include Honeypot
|
21
22
|
def actor; user; end
|
22
23
|
end
|
23
24
|
|
24
25
|
== usage in controllers
|
25
26
|
|
26
|
-
|
27
|
+
when somebody touches a honeypot, make sure to log it:
|
27
28
|
|
28
|
-
class
|
29
|
-
|
30
|
-
|
31
|
-
session
|
29
|
+
class UsersController < ApplicationController
|
30
|
+
def create
|
31
|
+
# [...]
|
32
|
+
@user.log_remote_request(session, request)
|
33
|
+
# [...]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class VotesController < ApplicationController
|
38
|
+
def create
|
39
|
+
# [...]
|
40
|
+
@vote.log_remote_request(session, request)
|
41
|
+
# [...]
|
32
42
|
end
|
33
|
-
prepend_before_filter :ensure_remote_ip
|
34
43
|
end
|
35
44
|
|
36
|
-
|
45
|
+
and be creative...
|
37
46
|
|
38
|
-
class
|
47
|
+
class SessionController < ApplicationController
|
48
|
+
# notice when a User logs in
|
39
49
|
def create
|
40
|
-
#
|
41
|
-
|
50
|
+
# [...]
|
51
|
+
current_user.log_remote_request(session, request)
|
52
|
+
# [...]
|
42
53
|
end
|
43
54
|
end
|
44
55
|
|
56
|
+
you pass both the session and the request so that you have the maximum chance of getting a real remote ip
|
57
|
+
|
45
58
|
== migration
|
46
59
|
|
47
60
|
create_table "remote_hosts" do |t|
|
@@ -69,39 +82,9 @@ then you invoke log_remote_request with both session and request
|
|
69
82
|
add_index "remote_requests", ["remote_host_id"], :name => "index_remote_requests_on_remote_host_id"
|
70
83
|
add_index "remote_requests", ["requestable_type", "requestable_id"], :name => "index_remote_requests_on_requestable"
|
71
84
|
|
72
|
-
==
|
73
|
-
|
74
|
-
def remote_requests_column(record)
|
75
|
-
str = '<ul>'
|
76
|
-
str << record.remote_requests.map { |x| content_tag :li, x.to_label }.join
|
77
|
-
str << '</ul>'
|
78
|
-
str
|
79
|
-
end
|
85
|
+
== Acknowledgements
|
80
86
|
|
81
|
-
|
82
|
-
|
83
|
-
class RemoteHostsController < ApplicationController
|
84
|
-
allow_access :admin
|
85
|
-
layout 'admin'
|
86
|
-
helper :remote_hosts
|
87
|
-
active_scaffold :remote_host do |config|
|
88
|
-
config.list.per_page = 100
|
89
|
-
config.actions.exclude :search, :create, :update, :delete
|
90
|
-
config.columns.add :remote_requests_count
|
91
|
-
config.columns[:remote_requests_count].sort_by :sql => "(SELECT COUNT(remote_requests.id) FROM remote_requests WHERE remote_requests.remote_host_id = remote_hosts.id)"
|
92
|
-
config.columns.add :last_remote_request_at
|
93
|
-
config.columns[:last_remote_request_at].sort_by :sql => "(SELECT MAX(remote_requests.updated_at) FROM remote_requests WHERE remote_requests.remote_host_id = remote_hosts.id)"
|
94
|
-
config.list.columns = %w{
|
95
|
-
hostname
|
96
|
-
ip_address
|
97
|
-
country_code
|
98
|
-
state_name
|
99
|
-
city
|
100
|
-
remote_requests_count
|
101
|
-
last_remote_request_at
|
102
|
-
}
|
103
|
-
end
|
104
|
-
end
|
87
|
+
in production use at http://brighterplanet.com
|
105
88
|
|
106
89
|
== Copyright
|
107
90
|
|
data/Rakefile
CHANGED
@@ -10,8 +10,10 @@ begin
|
|
10
10
|
gem.email = "seamus@abshere.net"
|
11
11
|
gem.homepage = "http://github.com/seamusabshere/honeypot"
|
12
12
|
gem.authors = ["Seamus Abshere"]
|
13
|
-
gem.add_dependency 'fast_timestamp', '0.0.4'
|
14
|
-
gem.add_dependency 'geokit', '1.5.0'
|
13
|
+
gem.add_dependency 'fast_timestamp', '>=0.0.4'
|
14
|
+
gem.add_dependency 'geokit', '>=1.5.0'
|
15
|
+
gem.add_dependency 'activesupport', '>=3.0.0beta2'
|
16
|
+
gem.add_dependency 'activerecord', '>=3.0.0beta2'
|
15
17
|
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
16
18
|
end
|
17
19
|
Jeweler::GemcutterTasks.new
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.2
|
data/honeypot.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{honeypot}
|
8
|
-
s.version = "0.0.
|
8
|
+
s.version = "0.0.2"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Seamus Abshere"]
|
12
|
-
s.date = %q{2010-
|
12
|
+
s.date = %q{2010-05-10}
|
13
13
|
s.description = %q{Catch bad guys when they stick their hands in the honey.}
|
14
14
|
s.email = %q{seamus@abshere.net}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -25,16 +25,18 @@ Gem::Specification.new do |s|
|
|
25
25
|
"VERSION",
|
26
26
|
"honeypot.gemspec",
|
27
27
|
"lib/honeypot.rb",
|
28
|
-
"lib/
|
29
|
-
"lib/
|
30
|
-
"lib/
|
28
|
+
"lib/honeypot/ipaddr_ext.rb",
|
29
|
+
"lib/honeypot/rack.rb",
|
30
|
+
"lib/honeypot/railtie.rb",
|
31
|
+
"lib/honeypot/remote_host.rb",
|
32
|
+
"lib/honeypot/remote_request.rb",
|
31
33
|
"test/helper.rb",
|
32
34
|
"test/test_honeypot.rb"
|
33
35
|
]
|
34
36
|
s.homepage = %q{http://github.com/seamusabshere/honeypot}
|
35
37
|
s.rdoc_options = ["--charset=UTF-8"]
|
36
38
|
s.require_paths = ["lib"]
|
37
|
-
s.rubygems_version = %q{1.3.
|
39
|
+
s.rubygems_version = %q{1.3.6}
|
38
40
|
s.summary = %q{Track remote requests to catch fraud.}
|
39
41
|
s.test_files = [
|
40
42
|
"test/helper.rb",
|
@@ -46,15 +48,21 @@ Gem::Specification.new do |s|
|
|
46
48
|
s.specification_version = 3
|
47
49
|
|
48
50
|
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
49
|
-
s.add_runtime_dependency(%q<fast_timestamp>, ["
|
50
|
-
s.add_runtime_dependency(%q<geokit>, ["
|
51
|
+
s.add_runtime_dependency(%q<fast_timestamp>, [">= 0.0.4"])
|
52
|
+
s.add_runtime_dependency(%q<geokit>, [">= 1.5.0"])
|
53
|
+
s.add_runtime_dependency(%q<activesupport>, [">= 3.0.0beta2"])
|
54
|
+
s.add_runtime_dependency(%q<activerecord>, [">= 3.0.0beta2"])
|
51
55
|
else
|
52
|
-
s.add_dependency(%q<fast_timestamp>, ["
|
53
|
-
s.add_dependency(%q<geokit>, ["
|
56
|
+
s.add_dependency(%q<fast_timestamp>, [">= 0.0.4"])
|
57
|
+
s.add_dependency(%q<geokit>, [">= 1.5.0"])
|
58
|
+
s.add_dependency(%q<activesupport>, [">= 3.0.0beta2"])
|
59
|
+
s.add_dependency(%q<activerecord>, [">= 3.0.0beta2"])
|
54
60
|
end
|
55
61
|
else
|
56
|
-
s.add_dependency(%q<fast_timestamp>, ["
|
57
|
-
s.add_dependency(%q<geokit>, ["
|
62
|
+
s.add_dependency(%q<fast_timestamp>, [">= 0.0.4"])
|
63
|
+
s.add_dependency(%q<geokit>, [">= 1.5.0"])
|
64
|
+
s.add_dependency(%q<activesupport>, [">= 3.0.0beta2"])
|
65
|
+
s.add_dependency(%q<activerecord>, [">= 3.0.0beta2"])
|
58
66
|
end
|
59
67
|
end
|
60
68
|
|
data/lib/honeypot.rb
CHANGED
@@ -1,4 +1,48 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
require 'ipaddr'
|
2
|
+
require 'set'
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_record'
|
5
|
+
require 'honeypot/ipaddr_ext'
|
6
|
+
require 'honeypot/remote_request'
|
7
|
+
require 'honeypot/remote_host'
|
8
|
+
require 'honeypot/rack'
|
9
|
+
|
10
|
+
require 'honeypot/railtie' if defined?(Rails::Railtie)
|
11
|
+
|
12
|
+
module Honeypot
|
13
|
+
def self.included(base)
|
14
|
+
base.class_eval do
|
15
|
+
has_many :remote_requests, :as => :requestable, :dependent => :destroy
|
16
|
+
has_many :remote_hosts, :through => :remote_requests, :uniq => true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def log_remote_request(session, request)
|
21
|
+
effective_ip_address = session['honeypot.last_known_remote_ip'].present? ? session['honeypot.last_known_remote_ip'] : request.remote_ip
|
22
|
+
remote_host = RemoteHost.find_or_create_by_ip_address effective_ip_address
|
23
|
+
remote_request = remote_requests.find_or_create_by_remote_host_id remote_host.id
|
24
|
+
remote_request.last_http_referer = request.referer if request.referer.present?
|
25
|
+
remote_request.last_request_uri = request.request_uri if request.request_uri.present?
|
26
|
+
remote_request.increment :hits
|
27
|
+
remote_request.save!
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
def related_requestables(seen_remote_host_ids = [])
|
32
|
+
set = Set.new
|
33
|
+
conditions = seen_remote_host_ids.present? ? [ "remote_hosts.id NOT IN (?)", seen_remote_host_ids ] : nil
|
34
|
+
remote_hosts.where(conditions).find_in_batches do |batch|
|
35
|
+
batch.each do |remote_host|
|
36
|
+
seen_remote_host_ids << remote_host.id
|
37
|
+
remote_host.remote_requests.all(:include => :requestable).each do |remote_request|
|
38
|
+
set << remote_request.requestable
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
if respond_to?(:actor) and actor != self
|
43
|
+
set += actor.related_requestables(seen_remote_host_ids)
|
44
|
+
end
|
45
|
+
set.delete self
|
46
|
+
set
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# http://codesnippets.joyent.com/posts/show/7546
|
2
|
+
class IPAddr
|
3
|
+
PRIVATE_RANGES = [
|
4
|
+
IPAddr.new('127.0.0.1/32'),
|
5
|
+
IPAddr.new('10.0.0.0/8'),
|
6
|
+
IPAddr.new('172.16.0.0/12'),
|
7
|
+
IPAddr.new('192.168.0.0/16')
|
8
|
+
]
|
9
|
+
|
10
|
+
def private?
|
11
|
+
return false unless self.ipv4?
|
12
|
+
PRIVATE_RANGES.any? { |ipr| ipr.include? self }
|
13
|
+
end
|
14
|
+
|
15
|
+
def public?
|
16
|
+
!private?
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# inspired by hoptoad_notifier
|
2
|
+
# http://charlesmaxwood.com/sessions-in-rack-and-rails-metal/
|
3
|
+
module Honeypot
|
4
|
+
# Middleware for Rack applications. Remote hosts will be tied together with remote requests.
|
5
|
+
class Rack
|
6
|
+
def initialize(app)
|
7
|
+
@app = app
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(env)
|
11
|
+
session = env['rack.session']
|
12
|
+
remote_ip = IPAddr.new env['action_dispatch.remote_ip'].to_s
|
13
|
+
session['honeypot.last_known_remote_ip'] = remote_ip.to_s if remote_ip.public?
|
14
|
+
@app.call env
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -1,15 +1,6 @@
|
|
1
1
|
class RemoteHost < ActiveRecord::Base
|
2
2
|
has_many :remote_requests, :dependent => :destroy
|
3
3
|
|
4
|
-
# start active-scaffold interface
|
5
|
-
def remote_requests_count
|
6
|
-
remote_requests.count
|
7
|
-
end
|
8
|
-
def last_remote_request_at
|
9
|
-
remote_requests.calculate :max, :updated_at
|
10
|
-
end
|
11
|
-
# end active-scaffold interface
|
12
|
-
|
13
4
|
include FastTimestamp
|
14
5
|
|
15
6
|
def lookup_hostname
|
data/test/test_honeypot.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
require 'helper'
|
2
2
|
|
3
3
|
class TestHoneypot < Test::Unit::TestCase
|
4
|
-
def
|
5
|
-
|
4
|
+
def test_ip_is_recognized_as_public_or_private
|
5
|
+
assert IPAddr.new('192.168.1.1').private?
|
6
|
+
assert IPAddr.new('10.0.0.2').private?
|
7
|
+
assert IPAddr.new('172.16.0.2').private?
|
8
|
+
assert IPAddr.new('88.122.122.122').public?
|
6
9
|
end
|
7
10
|
end
|
metadata
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: honeypot
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 2
|
9
|
+
version: 0.0.2
|
5
10
|
platform: ruby
|
6
11
|
authors:
|
7
12
|
- Seamus Abshere
|
@@ -9,29 +14,65 @@ autorequire:
|
|
9
14
|
bindir: bin
|
10
15
|
cert_chain: []
|
11
16
|
|
12
|
-
date: 2010-
|
17
|
+
date: 2010-05-10 00:00:00 -04:00
|
13
18
|
default_executable:
|
14
19
|
dependencies:
|
15
20
|
- !ruby/object:Gem::Dependency
|
16
21
|
name: fast_timestamp
|
17
|
-
|
18
|
-
|
19
|
-
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
24
|
requirements:
|
21
|
-
- - "
|
25
|
+
- - ">="
|
22
26
|
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
- 0
|
30
|
+
- 4
|
23
31
|
version: 0.0.4
|
24
|
-
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
25
34
|
- !ruby/object:Gem::Dependency
|
26
35
|
name: geokit
|
27
|
-
|
28
|
-
|
29
|
-
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
38
|
requirements:
|
31
|
-
- - "
|
39
|
+
- - ">="
|
32
40
|
- !ruby/object:Gem::Version
|
41
|
+
segments:
|
42
|
+
- 1
|
43
|
+
- 5
|
44
|
+
- 0
|
33
45
|
version: 1.5.0
|
34
|
-
|
46
|
+
type: :runtime
|
47
|
+
version_requirements: *id002
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: activesupport
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
segments:
|
56
|
+
- 3
|
57
|
+
- 0
|
58
|
+
- 0beta2
|
59
|
+
version: 3.0.0beta2
|
60
|
+
type: :runtime
|
61
|
+
version_requirements: *id003
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: activerecord
|
64
|
+
prerelease: false
|
65
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
segments:
|
70
|
+
- 3
|
71
|
+
- 0
|
72
|
+
- 0beta2
|
73
|
+
version: 3.0.0beta2
|
74
|
+
type: :runtime
|
75
|
+
version_requirements: *id004
|
35
76
|
description: Catch bad guys when they stick their hands in the honey.
|
36
77
|
email: seamus@abshere.net
|
37
78
|
executables: []
|
@@ -50,9 +91,11 @@ files:
|
|
50
91
|
- VERSION
|
51
92
|
- honeypot.gemspec
|
52
93
|
- lib/honeypot.rb
|
53
|
-
- lib/
|
54
|
-
- lib/
|
55
|
-
- lib/
|
94
|
+
- lib/honeypot/ipaddr_ext.rb
|
95
|
+
- lib/honeypot/rack.rb
|
96
|
+
- lib/honeypot/railtie.rb
|
97
|
+
- lib/honeypot/remote_host.rb
|
98
|
+
- lib/honeypot/remote_request.rb
|
56
99
|
- test/helper.rb
|
57
100
|
- test/test_honeypot.rb
|
58
101
|
has_rdoc: true
|
@@ -68,18 +111,20 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
68
111
|
requirements:
|
69
112
|
- - ">="
|
70
113
|
- !ruby/object:Gem::Version
|
114
|
+
segments:
|
115
|
+
- 0
|
71
116
|
version: "0"
|
72
|
-
version:
|
73
117
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
74
118
|
requirements:
|
75
119
|
- - ">="
|
76
120
|
- !ruby/object:Gem::Version
|
121
|
+
segments:
|
122
|
+
- 0
|
77
123
|
version: "0"
|
78
|
-
version:
|
79
124
|
requirements: []
|
80
125
|
|
81
126
|
rubyforge_project:
|
82
|
-
rubygems_version: 1.3.
|
127
|
+
rubygems_version: 1.3.6
|
83
128
|
signing_key:
|
84
129
|
specification_version: 3
|
85
130
|
summary: Track remote requests to catch fraud.
|
@@ -1,36 +0,0 @@
|
|
1
|
-
module RemoteRequestLogger
|
2
|
-
def self.included(base)
|
3
|
-
base.class_eval do
|
4
|
-
has_many :remote_requests, :as => :requestable, :dependent => :destroy
|
5
|
-
has_many :remote_hosts, :through => :remote_requests, :uniq => true
|
6
|
-
end
|
7
|
-
end
|
8
|
-
|
9
|
-
def log_remote_request(session, request)
|
10
|
-
effective_ip_address = session[:remote_ip].present? ? session[:remote_ip] : request.remote_ip
|
11
|
-
x = remote_requests.find_or_create_by_remote_host_id RemoteHost.find_or_create_by_ip_address(effective_ip_address).id
|
12
|
-
x.last_http_referer = request.referer if request.referer.present?
|
13
|
-
x.last_request_uri = request.request_uri if request.request_uri.present?
|
14
|
-
x.increment :hits
|
15
|
-
x.save!
|
16
|
-
true
|
17
|
-
end
|
18
|
-
|
19
|
-
def related_requestables(seen_remote_host_ids = [])
|
20
|
-
set = Set.new
|
21
|
-
conditions = seen_remote_host_ids.present? ? [ "remote_hosts.id NOT IN (?)", seen_remote_host_ids ] : nil
|
22
|
-
remote_hosts.scoped(:conditions => conditions).find_in_batches do |batch|
|
23
|
-
batch.each do |remote_host|
|
24
|
-
seen_remote_host_ids << remote_host.id
|
25
|
-
remote_host.remote_requests.all(:include => :requestable).each do |remote_request|
|
26
|
-
set << remote_request.requestable
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
if respond_to?(:actor) and actor != self
|
31
|
-
set += actor.related_requestables(seen_remote_host_ids)
|
32
|
-
end
|
33
|
-
set.delete self
|
34
|
-
set
|
35
|
-
end
|
36
|
-
end
|