From a49dab2badc81658596311fd26425fb107d4b2e8 Mon Sep 17 00:00:00 2001 From: Manabu Niseki Date: Thu, 25 Apr 2019 09:11:02 +0900 Subject: [PATCH] v0.1.0 --- .gitignore | 10 ++-- .rspec | 3 + .travis.yml | 7 +++ Gemfile | 4 ++ README.md | 43 +++++++++++++- Rakefile | 6 ++ bin/console | 14 +++++ bin/setup | 8 +++ exe/rogue_one | 8 +++ lib/rogue_one.rb | 11 ++++ lib/rogue_one/cli.rb | 24 ++++++++ lib/rogue_one/data/top_100.yml | 101 +++++++++++++++++++++++++++++++++ lib/rogue_one/detector.rb | 68 ++++++++++++++++++++++ lib/rogue_one/resolver.rb | 47 +++++++++++++++ lib/rogue_one/version.rb | 5 ++ rogue_one.gemspec | 33 +++++++++++ spec/detector_spec.rb | 7 +++ spec/rogue_one_spec.rb | 21 +++++++ spec/spec_helper.rb | 25 ++++++++ 19 files changed, 439 insertions(+), 6 deletions(-) create mode 100644 .rspec create mode 100644 .travis.yml create mode 100644 Gemfile create mode 100644 Rakefile create mode 100755 bin/console create mode 100755 bin/setup create mode 100755 exe/rogue_one create mode 100644 lib/rogue_one.rb create mode 100644 lib/rogue_one/cli.rb create mode 100644 lib/rogue_one/data/top_100.yml create mode 100644 lib/rogue_one/detector.rb create mode 100644 lib/rogue_one/resolver.rb create mode 100644 lib/rogue_one/version.rb create mode 100644 rogue_one.gemspec create mode 100644 spec/detector_spec.rb create mode 100644 spec/rogue_one_spec.rb create mode 100644 spec/spec_helper.rb diff --git a/.gitignore b/.gitignore index 5e1422c..d5b7832 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,7 @@ /tmp/ # Used by dotenv library to load environment variables. -# .env +.env ## Specific to RubyMotion: .dat* @@ -42,9 +42,11 @@ build-iPhoneSimulator/ # for a library or gem, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: -# Gemfile.lock -# .ruby-version -# .ruby-gemset +Gemfile.lock +.ruby-version +.ruby-gemset # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: .rvmrc + +.rspec_status diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..34c5164 --- /dev/null +++ b/.rspec @@ -0,0 +1,3 @@ +--format documentation +--color +--require spec_helper diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..79d5c4c --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +--- +sudo: false +language: ruby +cache: bundler +rvm: + - 2.6 +before_install: gem install bundler -v 2.0.1 diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..7a666d7 --- /dev/null +++ b/Gemfile @@ -0,0 +1,4 @@ +source "https://rubygems.org" + +# Specify your gem's dependencies in rogue_one.gemspec +gemspec diff --git a/README.md b/README.md index 48a4852..a4e0f7b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,41 @@ -# rogue_one -Rogue One: a rogue DNS detector +# Rogue one: a rogue DNS detector + +[![Build Status](https://travis-ci.org/ninoseki/rogue_one.svg?branch=master)](https://travis-ci.org/ninoseki/rogue_one) +[![Coverage Status](https://coveralls.io/repos/github/ninoseki/rogue_one/badge.svg?branch=master)](https://coveralls.io/github/ninoseki/rogue_one?branch=master) + +## Installation + +```bash +gem install rogue_one +``` + +## Usage + +```bash +$ rogue_one +Commands: + rogue_one help [COMMAND] # Describe available commands or one specific command + rogue_one report [DNS_SERVER] # Show a report of a given DNS server + +$ rogue_one report 1.1.1.1 +{ + "verdict": "rogue one", + "landing_pages": [ + + ] +} + +$ rogue_one reprot 1.53.252.215 +{ + "verdict": "rogue one", + "landing_pages": [ + "1.171.170.228", + "1.171.168.19", + "61.230.102.66" + ] +} +``` + +## License + +The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..b7e9ed5 --- /dev/null +++ b/Rakefile @@ -0,0 +1,6 @@ +require "bundler/gem_tasks" +require "rspec/core/rake_task" + +RSpec::Core::RakeTask.new(:spec) + +task :default => :spec diff --git a/bin/console b/bin/console new file mode 100755 index 0000000..94e5dc7 --- /dev/null +++ b/bin/console @@ -0,0 +1,14 @@ +#!/usr/bin/env ruby + +require "bundler/setup" +require "rogue_one" + +# You can add fixtures and/or initialization code here to make experimenting +# with your gem easier. You can also use a different console, if you like. + +# (If you use this, don't forget to add pry to your Gemfile!) +# require "pry" +# Pry.start + +require "irb" +IRB.start(__FILE__) diff --git a/bin/setup b/bin/setup new file mode 100755 index 0000000..dce67d8 --- /dev/null +++ b/bin/setup @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' +set -vx + +bundle install + +# Do any other automated setup that you need to do here diff --git a/exe/rogue_one b/exe/rogue_one new file mode 100755 index 0000000..7126ece --- /dev/null +++ b/exe/rogue_one @@ -0,0 +1,8 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +$LOAD_PATH.unshift("#{__dir__}/../lib") + +require "rogue_one" + +RogueOne::CLI.start diff --git a/lib/rogue_one.rb b/lib/rogue_one.rb new file mode 100644 index 0000000..f33770d --- /dev/null +++ b/lib/rogue_one.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require "rogue_one/version" + +require "rogue_one/resolver" +require "rogue_one/detector" +require "rogue_one/cli" + +module RogueOne + class Error < StandardError; end +end diff --git a/lib/rogue_one/cli.rb b/lib/rogue_one/cli.rb new file mode 100644 index 0000000..b239266 --- /dev/null +++ b/lib/rogue_one/cli.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require "thor" +require "json" + +module RogueOne + class CLI < Thor + desc "report [DNS_SERVER]", "Show a report of a given DNS server" + def report(dns_server) + with_error_handling do + detector = Detector.new(target: dns_server) + puts JSON.pretty_generate(detector.report) + end + end + + no_commands do + def with_error_handling + yield + rescue StandardError => e + puts "Warning: #{e}" + end + end + end +end diff --git a/lib/rogue_one/data/top_100.yml b/lib/rogue_one/data/top_100.yml new file mode 100644 index 0000000..9ecd746 --- /dev/null +++ b/lib/rogue_one/data/top_100.yml @@ -0,0 +1,101 @@ +--- +- google.com +- facebook.com +- youtube.com +- yahoo.com +- baidu.com +- wikipedia.org +- qq.com +- taobao.com +- twitter.com +- amazon.com +- linkedin.com +- live.com +- google.co.in +- sina.com.cn +- hao123.com +- blogspot.com +- weibo.com +- tmall.com +- vk.com +- wordpress.com +- yahoo.co.jp +- sohu.com +- yandex.ru +- ebay.com +- google.de +- bing.com +- pinterest.com +- google.co.uk +- 163.com +- 360.cn +- google.fr +- ask.com +- instagram.com +- google.co.jp +- tumblr.com +- msn.com +- google.com.br +- mail.ru +- microsoft.com +- xvideos.com +- paypal.com +- google.ru +- soso.com +- adcash.com +- google.es +- google.it +- imdb.com +- apple.com +- imgur.com +- neobux.com +- craigslist.org +- amazon.co.jp +- t.co +- xhamster.com +- stackoverflow.com +- reddit.com +- google.com.mx +- google.com.hk +- cnn.com +- google.ca +- fc2.com +- go.com +- ifeng.com +- bbc.co.uk +- vube.com +- people.com.cn +- blogger.com +- aliexpress.com +- odnoklassniki.ru +- wordpress.org +- alibaba.com +- gmw.cn +- adobe.com +- huffingtonpost.com +- google.com.tr +- xinhuanet.com +- googleusercontent.com +- youku.com +- godaddy.com +- pornhub.com +- akamaihd.net +- thepiratebay.se +- kickass.to +- google.com.au +- amazon.de +- clkmon.com +- ebay.de +- alipay.com +- google.pl +- espn.go.com +- dailymotion.com +- about.com +- bp.blogspot.com +- blogspot.in +- netflix.com +- vimeo.com +- dailymail.co.uk +- redtube.com +- rakuten.co.jp +- conduit.com diff --git a/lib/rogue_one/detector.rb b/lib/rogue_one/detector.rb new file mode 100644 index 0000000..b303be1 --- /dev/null +++ b/lib/rogue_one/detector.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require "yaml" + +module RogueOne + class Detector + attr_reader :target + + GOOGLE_PUBLIC_DNS = "8.8.8.8" + + def initialize(target:) + @target = target + @memo = Hash.new(0) + @mismatched_domains = [] + end + + def report + inspect + + { + verdict: verdict, + landing_pages: landing_pages + } + end + + private + + def verdict + rogue_one? ? "rogue one" : "benign one" + end + + def rogue_one? + @mismatched_domains.length > 50 + end + + def landing_pages + return [] unless rogue_one? + + @memo.map do |ip, count| + count > 10 ? ip : nil + end.compact + end + + def inspect + top_100_domains.each do |domain| + normal_result = normal_resolver.dig(domain, "A") + target_result = target_resolver.dig(domain, "A") + + if normal_result != target_result + @mismatched_domains << domain + @memo[target_result] += 1 if target_result + end + end + end + + def top_100_domains + @top_100_domains ||= YAML.safe_load(File.read(File.expand_path("./data/top_100.yml", __dir__))) + end + + def normal_resolver + @normal_resolver ||= Resolver.new(nameserver: GOOGLE_PUBLIC_DNS) + end + + def target_resolver + @target_resolver ||= Resolver.new(nameserver: target) + end + end +end diff --git a/lib/rogue_one/resolver.rb b/lib/rogue_one/resolver.rb new file mode 100644 index 0000000..e202579 --- /dev/null +++ b/lib/rogue_one/resolver.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require "resolv" + +module RogueOne + class Resolver + attr_reader :nameserver + + def initialize(nameserver:) + @nameserver = nameserver + end + + def dig(domain, type) + _resolver.getresource(domain, resource_by_type(type)).address.to_s + rescue Resolv::ResolvError => e + nil + end + + private + + def _resolver + @_resolver ||= Resolv::DNS.new(nameserver: [nameserver]) + end + + def resource_by_type(type) + resources.dig(type.upcase.to_sym) + end + + def resources + { + ANY: Resolv::DNS::Resource::IN::ANY, + NS: Resolv::DNS::Resource::IN::NS, + CNAME: Resolv::DNS::Resource::IN::CNAME, + SOA: Resolv::DNS::Resource::IN::SOA, + HINFO: Resolv::DNS::Resource::IN::HINFO, + MINFO: Resolv::DNS::Resource::IN::MINFO, + MX: Resolv::DNS::Resource::IN::MX, + TXT: Resolv::DNS::Resource::IN::TXT, + A: Resolv::DNS::Resource::IN::A, + WKS: Resolv::DNS::Resource::IN::WKS, + PTR: Resolv::DNS::Resource::IN::PTR, + AAAA: Resolv::DNS::Resource::IN::AAAA, + SRV: Resolv::DNS::Resource::IN::SRV, + } + end + end +end diff --git a/lib/rogue_one/version.rb b/lib/rogue_one/version.rb new file mode 100644 index 0000000..35e5421 --- /dev/null +++ b/lib/rogue_one/version.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +module RogueOne + VERSION = "0.1.0" +end diff --git a/rogue_one.gemspec b/rogue_one.gemspec new file mode 100644 index 0000000..68180cf --- /dev/null +++ b/rogue_one.gemspec @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +lib = File.expand_path('lib', __dir__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require "rogue_one/version" + +Gem::Specification.new do |spec| + spec.name = "rogue_one" + spec.version = RogueOne::VERSION + spec.authors = ["Manabu Niseki"] + spec.email = ["manabu.niseki@gmail.com"] + + spec.summary = "Rogue one: a rogue DNS detector" + spec.description = 'Rogue one: a rogue DNS detector' + spec.homepage = "https://github.com/ninoseki/rogue_one" + spec.license = "MIT" + + # Specify which files should be added to the gem when it is released. + # The `git ls-files -z` loads the files in the RubyGem that have been added into git. + spec.files = Dir.chdir(File.expand_path(__dir__)) do + `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } + end + spec.bindir = "exe" + spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } + spec.require_paths = ["lib"] + + spec.add_development_dependency "bundler", "~> 2.0" + spec.add_development_dependency "coveralls", "~> 0.8" + spec.add_development_dependency "rake", "~> 12.3" + spec.add_development_dependency "rspec", "~> 3.8" + + spec.add_dependency "thor", "~> 0.19" +end diff --git a/spec/detector_spec.rb b/spec/detector_spec.rb new file mode 100644 index 0000000..a2ab0c3 --- /dev/null +++ b/spec/detector_spec.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +RSpec.describe RogueOne do + it "has a version number" do + expect(RogueOne::VERSION).not_to be nil + end +end diff --git a/spec/rogue_one_spec.rb b/spec/rogue_one_spec.rb new file mode 100644 index 0000000..e74f1f4 --- /dev/null +++ b/spec/rogue_one_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +RSpec.describe RogueOne::Detector do + subject { described_class.new(target: "1.1.1.1") } + + describe "#report" do + before do + allow(subject).to receive(:top_100_domains).and_return(%w(google.com)) + end + + let(:report) { subject.report } + + it do + expect(report.dig(:verdict)).to eq("benign one") + end + + it do + expect(report.dig(:landing_pages)).to eq([]) + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..c38d3fd --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require "bundler/setup" + +require "simplecov" +require "coveralls" +SimpleCov.formatter = Coveralls::SimpleCov::Formatter +SimpleCov.start do + add_filter "/spec" +end +Coveralls.wear! + +require "rogue_one" + +RSpec.configure do |config| + # Enable flags like --only-failures and --next-failure + config.example_status_persistence_file_path = ".rspec_status" + + # Disable RSpec exposing methods globally on `Module` and `main` + config.disable_monkey_patching! + + config.expect_with :rspec do |c| + c.syntax = :expect + end +end