vcsmap 2.0.1

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ce25dd4820391402c9309ce065ee9d349e566985
4
+ data.tar.gz: 81fbabbbc3ade9495cb3aa3b1307ee70ba1efa8e
5
+ SHA512:
6
+ metadata.gz: 621f538ba408036582602c9a1e4958e7c2f6f1626cf7f74acaa01d1bd3d0acc147f932cf1a5630bddd5b5112bbf5897879965570b3b723886d26bcfb271ab618
7
+ data.tar.gz: 68638ec08e026e5584a0dedb0f958b590a420dd383a47e51a06117443789ea1213553a95bf5be497f26ae84e816815fc0bfb21c5f1b702d68e1b46e7498a5d38
data/bin/vcsmap ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift(File.dirname(File.realpath(__FILE__)) + '/../lib')
5
+
6
+ require 'vcsmap'
7
+
8
+ cli = Vcsmap::CLI.new(ARGV)
9
+ cli.run
data/lib/vcsmap/cli.rb ADDED
@@ -0,0 +1,86 @@
1
+ require 'tty'
2
+
3
+ module Vcsmap
4
+ class CLI
5
+ def initialize(arguments)
6
+ @command = arguments[0]
7
+ @plugin = arguments[1]
8
+ @pages = arguments[2]
9
+ @output = arguments[3]
10
+ end
11
+
12
+ def run
13
+ case @command
14
+ when 'list'
15
+ list_plugins
16
+ when 'run'
17
+ run_plugin(@output === '--no-ascii')
18
+ when nil
19
+ abort "vcsmap requires a command. #{usage}"
20
+ else
21
+ abort "Command not recognized. #{usage}"
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def usage
28
+ "See http://vcsmap.org or open #{Helpers.project_directory}/README.md for instructions."
29
+ end
30
+
31
+ def list_plugins
32
+ puts "Available plugins:\n\n"
33
+ PluginList.render_list
34
+ exit
35
+ end
36
+
37
+ def run_plugin(no_ascii)
38
+ begin
39
+ plugin = PluginList.get_object(@plugin)
40
+ rescue KeyError
41
+ abort "Cannot find plugin with name '#{@plugin}'."
42
+ rescue NameError
43
+ abort "The plugin '#{@plugin}' has not been implemented yet."
44
+ end
45
+
46
+ unless @pages && (1..100).cover?(@pages.to_i)
47
+ abort 'Specify a number of pages (1-100) to load after the plugin name (1 page = ~10 results).'
48
+ end
49
+
50
+ puts 'Searching for matching files ...'
51
+ provider = Vcsmap::Provider::GitHub.new
52
+ results = provider.search(plugin, @pages.to_i)
53
+
54
+ bar = Vcsmap::ProgressBar.new(results.count)
55
+
56
+ data = []
57
+
58
+ abort "No files were found matching the search string (#{plugin.search_string})." if results.empty?
59
+
60
+ results.each do |result|
61
+ bar.step
62
+ file = HTTP.follow(true).get(result).body.to_s
63
+ credentials = plugin.credentials(file)
64
+ credentials << result.split('/').slice(3, 2).join('/')
65
+ # TODO: make an object that holds credentials and has empty? and valid? methods.
66
+ # this object should also be able to filter 'false' creds like localhost and default ones.
67
+ data << credentials unless credentials[1].nil? || credentials[1].empty?
68
+ end
69
+
70
+ if data.empty?
71
+ abort "Some files were loaded (#{results.count}), but none of them contained matching credentials. " \
72
+ 'You could try a higher page number.'
73
+ end
74
+
75
+ bar.clear
76
+
77
+ csv_writer = Vcsmap::CsvWriter.new(@plugin, plugin.table_header, data)
78
+ csv_writer.write!
79
+
80
+ unless no_ascii
81
+ table = TTY::Table.new plugin.table_header << 'Repo', data
82
+ puts table.render(:ascii)
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,35 @@
1
+ require 'csv'
2
+
3
+ module Vcsmap
4
+ class CsvWriter
5
+ def initialize(plugin_name, plugin_header, data)
6
+ @file_path = file_path(plugin_name)
7
+ @header = header(plugin_header)
8
+ @data = data
9
+ end
10
+
11
+ def write!
12
+ puts "Writing CSV to #{Helpers.project_directory}/#{@file_path} ..."
13
+ CSV.open(@file_path, 'wb', force_quotes: true) do |csv|
14
+ csv << @header
15
+ @data.each do |line|
16
+ csv << line
17
+ end
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def folder
24
+ 'output/'
25
+ end
26
+
27
+ def file_path(plugin_name)
28
+ folder + "#{DateTime.now.strftime('%Y%m%dT%H%M')}-#{plugin_name}.csv"
29
+ end
30
+
31
+ def header(plugin_header)
32
+ plugin_header << 'Repo'
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,7 @@
1
+ module Vcsmap
2
+ module Helpers
3
+ def self.project_directory
4
+ File.dirname(File.dirname(__FILE__)).to_s
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,4 @@
1
+ module Vcsmap
2
+ module Plugin
3
+ end
4
+ end
@@ -0,0 +1,67 @@
1
+ module Vcsmap
2
+ class PluginList
3
+ PLUGINS = {
4
+ aws: {
5
+ title: 'AWS access key',
6
+ description: 'Extracts AWS credentials from config and credentials files.',
7
+ class_name: 'Vcsmap::Plugin::AwsAccessToken'
8
+ },
9
+ wordpress_config: {
10
+ title: 'Wordpress configuration files',
11
+ description: 'Extracts database credentials from wp-config.php.',
12
+ class_name: 'Vcsmap::Plugin::WordpressConfig'
13
+ },
14
+ google_oauth: {
15
+ title: 'Google oAuth tokens',
16
+ description: 'Extracts oAuth credentials from client_secrets.json.',
17
+ class_name: 'Vcsmap::Plugin::GoogleOauth'
18
+ },
19
+ filezilla_xml: {
20
+ title: 'Filezilla configuration XML',
21
+ description: 'Extracts FTP credentials from Filezilla configuration files.',
22
+ class_name: 'Vcsmap::Plugin::FilezillaXml'
23
+ },
24
+ solr_dataconfig: {
25
+ title: 'Solr dataConfig.xml credentials',
26
+ description: 'Extracts JdbcDataSource credentials from a Solor dataConfig.xml.',
27
+ class_name: 'Vcsmap::Plugin::SolrDataconfig'
28
+ },
29
+ sublime_github: {
30
+ title: 'Sublime Text GitHub tokens',
31
+ description: 'Extracts GitHub tokens from the Sublime Text settings file for GitHub.',
32
+ class_name: 'Vcsmap::Plugin::GithubSublimesettings'
33
+ },
34
+ facebook_secrets: {
35
+ title: 'Facebook app secret',
36
+ description: 'Extracts Facebook tokens from fb_client_secrets.json.',
37
+ class_name: 'Vcsmap::Plugin::FacebookClientSecrets'
38
+ },
39
+ instagram_tokens: {
40
+ title: 'Instagram access tokens',
41
+ description: 'Extracts Instagram access tokens.',
42
+ class_name: 'Vcsmap::Plugin::InstagramTokens'
43
+ }
44
+ }.freeze
45
+
46
+ def self.all
47
+ PLUGINS.sort
48
+ end
49
+
50
+ def self.find(name)
51
+ PLUGINS.fetch(name.to_sym)
52
+ end
53
+
54
+ def self.get_object(name)
55
+ plugin = find(name)
56
+ Object.const_get(plugin[:class_name]).new
57
+ end
58
+
59
+ def self.render_list
60
+ all.each do |plugin|
61
+ puts Pastel.new.green "[#{plugin[0]}] #{plugin[1][:title]}"
62
+ puts plugin[1][:description]
63
+ puts
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,25 @@
1
+ module Vcsmap
2
+ module Plugin
3
+ class AwsAccessToken < Vcsmap::Plugin::BasePlugin
4
+ def initialize
5
+ @search_string = ['aws_secret_access_key',
6
+ 'filename:config',
7
+ 'filename:credentials'].join('+')
8
+ @access_key_id_regex = /=\s+(AKIA[0-9A-Z]{16})/
9
+ @secret_access_key_regex = %r{=\s+([0-9a-zA-Z\/+]{40})}
10
+ end
11
+
12
+ def credentials(file)
13
+ @access_key_id = capture_match(@access_key_id_regex, file)
14
+ @secret_access_key = capture_match(@secret_access_key_regex, file)
15
+ [@access_key_id, @secret_access_key]
16
+ rescue NoMethodError
17
+ []
18
+ end
19
+
20
+ def table_header
21
+ ['Access Key ID', 'Secret Access Key']
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,19 @@
1
+ module Vcsmap
2
+ module Plugin
3
+ class BasePlugin
4
+ attr_reader :search_string
5
+
6
+ def capture_match(regex, file)
7
+ match = regex.match(clean_file(file))
8
+ return '' if match.nil?
9
+ match.captures.first
10
+ end
11
+
12
+ private
13
+
14
+ def clean_file(file)
15
+ file.scrub
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,23 @@
1
+ module Vcsmap
2
+ module Plugin
3
+ class FacebookClientSecrets < Vcsmap::Plugin::BasePlugin
4
+ def initialize
5
+ @search_string = 'filename:fb_client_secrets.json+app_secret'
6
+ @app_id_regex = /(?:\"|')app_id(?:\"|')\:(?:\ |)(?:\"|')(.*?)(?:\"|')/i
7
+ @app_secret_regex = /(?:\"|')app_secret(?:\"|')\:(?:\ |)(?:\"|')(.*?)(?:\"|')/i
8
+ end
9
+
10
+ def credentials(file)
11
+ @app_id = capture_match(@app_id_regex, file)
12
+ @app_secret = capture_match(@app_secret_regex, file)
13
+ ['oAuth', @app_id, @app_secret]
14
+ rescue NoMethodError
15
+ []
16
+ end
17
+
18
+ def table_header
19
+ %w(Protocol app_id app_secret)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,37 @@
1
+ module Vcsmap
2
+ module Plugin
3
+ class FilezillaXml < Vcsmap::Plugin::BasePlugin
4
+ def initialize
5
+ @search_string = 'filename:filezilla.xml+Pass'
6
+ @host_regex = /<Host>(.*)<\/Host>/
7
+ @username_regex = /<User>(.*)<\/User>/
8
+ @password_regex = /<Pass>(.*)<\/Pass>/
9
+ @encoded_password_regex = /<Pass encoding="base64">(.*)<\/Pass>/
10
+ @port_regex = /<Port>(.*)<\/Port>/
11
+ end
12
+
13
+ def credentials(file)
14
+ @host = capture_match(@host_regex, file)
15
+ @user = capture_match(@username_regex, file)
16
+ @pass = find_password(file)
17
+ @port = capture_match(@port_regex, file)
18
+ ['FTP', @host, @port, @user, @pass]
19
+ end
20
+
21
+ def table_header
22
+ %w(Protocol Host Port Username Password)
23
+ end
24
+
25
+ private
26
+
27
+ def find_password(file)
28
+ @pass = capture_match(@password_regex, file)
29
+ @base64_pass = capture_match(@encoded_password_regex, file)
30
+
31
+ return @pass unless @pass.empty?
32
+ return Base64.decode64(@base64_pass) unless @base64_pass.empty?
33
+ ''
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,21 @@
1
+ module Vcsmap
2
+ module Plugin
3
+ class GithubSublimesettings < Vcsmap::Plugin::BasePlugin
4
+ def initialize
5
+ @search_string = 'filename:GitHub.sublime-settings+github_token'
6
+ @token_regex = /(?:\"|')github_token(?:\"|')\:(?:\ |)(?:\"|')(.*?)(?:\"|')/i
7
+ end
8
+
9
+ def credentials(file)
10
+ @token = capture_match(@token_regex, file)
11
+ ['GitHub token', @token]
12
+ rescue NoMethodError
13
+ []
14
+ end
15
+
16
+ def table_header
17
+ %w(Protocol Token)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,23 @@
1
+ module Vcsmap
2
+ module Plugin
3
+ class GoogleOauth < Vcsmap::Plugin::BasePlugin
4
+ def initialize
5
+ @search_string = 'filename:client_secrets.json+.apps.googleusercontent.com'
6
+ @client_id_regex = /(?:\"|')client_id(?:\"|')\:(?:\ |)(?:\"|')(.*?)(?:\"|')/i
7
+ @client_secret_regex = /(?:\"|')client_secret(?:\"|')\:(?:\ |)(?:\"|')(.*?)(?:\"|')/i
8
+ end
9
+
10
+ def credentials(file)
11
+ @client_id = capture_match(@client_id_regex, file)
12
+ @client_secret = capture_match(@client_secret_regex, file)
13
+ ['oAuth', @client_id, @client_secret]
14
+ rescue NoMethodError
15
+ []
16
+ end
17
+
18
+ def table_header
19
+ %w(Protocol client_id client_secret)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,19 @@
1
+ module Vcsmap
2
+ module Plugin
3
+ class InstagramTokens < Vcsmap::Plugin::BasePlugin
4
+ def initialize
5
+ @search_string = 'instagram+access_token'
6
+ @access_token_regex = /([0-9]{9,10}\.[a-f0-9]{7}\.[a-f0-9]{31,32})/
7
+ end
8
+
9
+ def credentials(file)
10
+ @access_token = capture_match(@access_token_regex, file)
11
+ ['Instagram', @access_token]
12
+ end
13
+
14
+ def table_header
15
+ ['Protocol', 'Access Token']
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,25 @@
1
+ module Vcsmap
2
+ module Plugin
3
+ class SolrDataconfig < Vcsmap::Plugin::BasePlugin
4
+ def initialize
5
+ @search_string = 'filename:dataConfig.xml+dataSource+JdbcDataSource+password'
6
+ @url_regex = /url=\"(jdbc(.*?))\"/
7
+ @username_regex = /user=\"(.*?)\"/
8
+ @password_regex = /password=\"(.*?)\"/
9
+ end
10
+
11
+ def credentials(file)
12
+ @url = capture_match(@url_regex, file)
13
+ @user = capture_match(@username_regex, file)
14
+ @pass = capture_match(@password_regex, file)
15
+ ['JDBC', @url, @user, @pass]
16
+ rescue NoMethodError
17
+ []
18
+ end
19
+
20
+ def table_header
21
+ %w(Protocol URL Username Password)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,27 @@
1
+ module Vcsmap
2
+ module Plugin
3
+ class WordpressConfig < Vcsmap::Plugin::BasePlugin
4
+ def initialize
5
+ @search_string = 'filename:wp-config.php+DB_PASSWORD'
6
+ @host_regex = /(?:\'|\")DB_HOST(?:\'|\")\,(?:\ |)(?:\'|\")(.*?)(?:\'|\")/i
7
+ @username_regex = /(?:\'|\")DB_USER(?:\'|\")\,(?:\ |)(?:\'|\")(.*?)(?:\'|\")/i
8
+ @password_regex = /(?:\'|\")DB_PASSWORD(?:\'|\")\,(?:\ |)(?:\'|\")(.*?)(?:\'|\")/i
9
+ @database_regex = /(?:\'|\")DB_NAME(?:\'|\")\,(?:\ |)(?:\'|\")(.*?)(?:\'|\")/i
10
+ end
11
+
12
+ def credentials(file)
13
+ @host = capture_match(@host_regex, file)
14
+ @user = capture_match(@username_regex, file)
15
+ @pass = capture_match(@password_regex, file)
16
+ @database = capture_match(@database_regex, file)
17
+ ['MySQL', @host, @user, @pass, @database]
18
+ rescue NoMethodError
19
+ []
20
+ end
21
+
22
+ def table_header
23
+ %w(Protocol Host Username Password Database)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,18 @@
1
+ require 'tty'
2
+
3
+ module Vcsmap
4
+ class ProgressBar
5
+ def initialize(count)
6
+ @bar = TTY::ProgressBar.new("Loading #{count} possible results [:bar]", total: count)
7
+ end
8
+
9
+ def step
10
+ @bar.advance
11
+ end
12
+
13
+ def clear
14
+ print "\r"
15
+ $stdout.flush
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,4 @@
1
+ module Vcsmap
2
+ module Provider
3
+ end
4
+ end
@@ -0,0 +1,46 @@
1
+ module Vcsmap
2
+ module Provider
3
+ class GitHub
4
+ def initialize
5
+ @cookie = get_cookie
6
+ end
7
+
8
+ def search(plugin, total_pages)
9
+ result_urls = []
10
+ @query = plugin.search_string
11
+ (1..total_pages).each do |page|
12
+ result_urls += get_results_from_page(page)
13
+ end
14
+ result_urls
15
+ end
16
+
17
+ private
18
+
19
+ def get_cookie
20
+ cookie = ENV['GITHUB_COOKIE']
21
+ http = HTTP.cookies(user_session: cookie).get('https://github.com/login')
22
+ abort "[Error] No valid session cookie in ENV['GITHUB_COOKIE']." unless http.status == 302
23
+ cookie
24
+ end
25
+
26
+ def get_results_from_page(page)
27
+ url = "https://github.com/search?p=#{page}&o=desc&q=#{@query}&s=indexed&type=Code"
28
+ html = HTTP.cookies(user_session: @cookie).get(url)
29
+ parse_response(html.body.to_s)
30
+ end
31
+
32
+ def parse_response(body)
33
+ doc = Nokogiri::HTML(body)
34
+ hits = doc.css('p.title a:last')
35
+ # TODO: use this when there are no file boxes.
36
+ # hits = doc.css('.code-list-item .full-path a')
37
+ urls = hits.map { |hit| 'https://github.com' + hit.attr('href') }
38
+ urls.map { |url| get_raw(url) }
39
+ end
40
+
41
+ def get_raw(url)
42
+ url.gsub('blob', 'raw')
43
+ end
44
+ end
45
+ end
46
+ end
data/lib/vcsmap.rb ADDED
@@ -0,0 +1,30 @@
1
+ require 'http'
2
+ require 'yaml'
3
+ require 'nokogiri'
4
+
5
+ # TODO: include only if needed
6
+ require_relative 'vcsmap/cli'
7
+ require_relative 'vcsmap/csv_writer'
8
+ require_relative 'vcsmap/progress_bar'
9
+
10
+ require_relative 'vcsmap/helpers'
11
+ require_relative 'vcsmap/plugin'
12
+ require_relative 'vcsmap/plugin_list'
13
+ require_relative 'vcsmap/provider'
14
+
15
+ # TODO: work on require_all
16
+ require_relative 'vcsmap/plugins/base_plugin'
17
+ require_relative 'vcsmap/plugins/aws_access_token'
18
+ require_relative 'vcsmap/plugins/facebook_client_secrets'
19
+ require_relative 'vcsmap/plugins/filezilla_xml'
20
+ require_relative 'vcsmap/plugins/github_sublimesettings'
21
+ require_relative 'vcsmap/plugins/google_oauth'
22
+ require_relative 'vcsmap/plugins/instagram_tokens'
23
+ require_relative 'vcsmap/plugins/solr_dataconfig'
24
+ require_relative 'vcsmap/plugins/wordpress_config'
25
+
26
+ require_relative 'vcsmap/providers/github'
27
+
28
+ module Vcsmap
29
+ VERSION = '2.0.1'.freeze
30
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vcsmap
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Melvin Lammerts
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-11-24 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A plugin-based tool to scan public version control systems for sensitive
14
+ information.
15
+ email: hi@melvin.sh
16
+ executables:
17
+ - vcsmap
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - bin/vcsmap
22
+ - lib/vcsmap.rb
23
+ - lib/vcsmap/cli.rb
24
+ - lib/vcsmap/csv_writer.rb
25
+ - lib/vcsmap/helpers.rb
26
+ - lib/vcsmap/plugin.rb
27
+ - lib/vcsmap/plugin_list.rb
28
+ - lib/vcsmap/plugins/aws_access_token.rb
29
+ - lib/vcsmap/plugins/base_plugin.rb
30
+ - lib/vcsmap/plugins/facebook_client_secrets.rb
31
+ - lib/vcsmap/plugins/filezilla_xml.rb
32
+ - lib/vcsmap/plugins/github_sublimesettings.rb
33
+ - lib/vcsmap/plugins/google_oauth.rb
34
+ - lib/vcsmap/plugins/instagram_tokens.rb
35
+ - lib/vcsmap/plugins/solr_dataconfig.rb
36
+ - lib/vcsmap/plugins/wordpress_config.rb
37
+ - lib/vcsmap/progress_bar.rb
38
+ - lib/vcsmap/provider.rb
39
+ - lib/vcsmap/providers/github.rb
40
+ homepage: http://vcsmap.org
41
+ licenses:
42
+ - MIT
43
+ metadata: {}
44
+ post_install_message:
45
+ rdoc_options: []
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ requirements: []
59
+ rubyforge_project:
60
+ rubygems_version: 2.5.2
61
+ signing_key:
62
+ specification_version: 4
63
+ summary: Scans public repositories for sensitive information.
64
+ test_files: []