diff --git a/.gitignore b/.gitignore index 2a468241..d00e0de2 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,10 @@ control/wordlists/* !control/wordlists/password.gz control/tmp/* !control/tmp/.blank +logs/*.log +!logs/.blank +logs/jobs/* +!logs/jobs/.blank *.pot *.restore *.idea diff --git a/CHANGELOG.md b/CHANGELOG.md index 577f692c..1187567b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,26 @@ Notable changes will be documented here ## Current Release +## [v0.7.2-beta] - 2017-10-19 +### Added + - Added Logging Facility, logs should now be under logs/*.log and logs/jobs/*.log (Logs will rotate daily. Logs greater than 30 days will be automatically deleted + - Added Collapsing window in analytics in Weak Account Password + - Added ability to download user accounts/passwords for accounts that are found to be weak in csv format + - Added ability to set OTP passwords for users using google authenticate (thanks: https://github.com/nicbrink) + +### Removed + - Wordlist Checksums is no longer a background task that fires every 5 seconds. Instead its queued up by wordlist importer. -## [v0.7.1-beta] - 2017-9-4 +### Fixed + - Fixed calculation bug where SmartWordlist was being refactored into new SmartWordlist. Now calculations are quicker + - Fixed (hopefully) bug where hashview prematurely 'completes' a job (and subsequently kills a running task). This only happens in rare cases where multiple agents are involved. + - Fixed (hopefully) issue where threads not exiting when they're told to. This resulted in issues related to: https://github.com/hashview/hashview/issues/264 + - Fixed issue where rules listed under task details was displaying rule.id, and not the rule.name: https://github.com/hashview/hashview/issues/342 + - Fixed SMTP sender error experienced when user sends test message + https://github.com/hashview/hashview/issues/341 + - Fixed issue where foreign DB's listed in config were not being connected too: https://github.com/hashview/hashview/issues/351 + +## [v0.7.1-beta] - 2017-09-04 ### Added - Rake task to reset db (thanks: nicbrink) - New hub route/tab if registered @@ -16,7 +34,7 @@ Notable changes will be documented here ### Fixed - Fixed issue where importing the same hash twice into the db where one had an incorrect hashtype resulted in a 500 error. Now the entry is updated with the new hashtype. - - Fixed timouts when searching large hash sets with Hashview Hub + - Fixed timeouts when searching large hash sets with Hashview Hub ## [v0.7.0-beta] - 2017-07-22 ### Added diff --git a/Gemfile b/Gemfile index 5f2b8ca9..d9f8c7b1 100644 --- a/Gemfile +++ b/Gemfile @@ -4,6 +4,7 @@ ruby '2.2.2' group :development do gem 'rubocop' gem 'factory_girl' + gem 'rotp' end group :test do @@ -28,5 +29,6 @@ gem 'mysql' gem 'foreman' gem 'rest-client' gem 'digest' +gem 'logger' diff --git a/Gemfile.lock b/Gemfile.lock index 6313a134..627f3551 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -111,6 +111,7 @@ GEM less (~> 2.6.0) sprockets (> 2, < 4) tilt + logger (1.2.8) loofah (2.0.3) nokogiri (>= 1.5.9) mail (2.6.4) @@ -177,6 +178,7 @@ GEM http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) + rotp (3.3.0) rubocop (0.42.0) parser (>= 2.3.1.1, < 3.0) powerpack (~> 0.1) @@ -238,6 +240,7 @@ DEPENDENCIES foreman haml json + logger mysql pony rake @@ -246,6 +249,7 @@ DEPENDENCIES resque-scheduler resque-web rest-client + rotp rubocop sinatra (~> 1.4.7) sinatra-flash @@ -254,4 +258,4 @@ RUBY VERSION ruby 2.2.2p95 BUNDLED WITH - 1.14.6 + 1.15.4 diff --git a/Rakefile b/Rakefile index 56dc631a..73438ae9 100644 --- a/Rakefile +++ b/Rakefile @@ -16,6 +16,31 @@ Rake::TestTask.new do |t| t.verbose end +# Catching Sigterm +def shut_down + puts 'Attempting to shutdown gracefully...' + # Technique based off of https://bugs.ruby-lang.org/issue/7917 + # and + # https://stackoverflow.com/questions/7416318/how-do-i-clear-stuck-stale-resque-workers + t = Thread.new do + Resque.workers.each {| w | w.unregister_worker} + end + t.join + sleep(5) +end + +# Trap ^C +Signal.trap('INT') { + shut_down + exit +} + +# Trap `kill ` +Signal.trap('TERM') { + shut_down + exit +} + # resque-scheduler needs to know basics from resque::setup desc 'Resque scheduler setup' namespace :resque do @@ -44,14 +69,6 @@ namespace :db do desc 'Drop from all tables except users and task' task :reset - # Are the below ever needed beyond our testing? - #desc 'create and setup schema' - #task :clean => [:create] # Should really be made to a series of DELETE FROM - #desc 'destroy db, create db, setup schema, load defaults' - #task :reset => [:destroy, :create, :provision_agent, :provision_defaults] - #desc 'destroy db, create db, setup schema' - #task :reset_clean => [:destroy, :create, :upgrade, :provision_agent] - task :create do if ENV['RACK_ENV'].nil? ENV['RACK_ENV'] = 'development' @@ -59,7 +76,7 @@ namespace :db do puts "setting up database for environment: #{ENV['RACK_ENV']}" config = YAML.load_file('config/database.yml') config = config[ENV['RACK_ENV']] - user, password, host = config['user'], config['password'], config['hostname'] + user, password, host = config['user'], config['password'], config['host'] database = config['database'] charset = config['charset'] || ENV['CHARSET'] || 'utf8' collation = config['collation'] || ENV['COLLATION'] || 'utf8_unicode_ci' @@ -117,7 +134,7 @@ namespace :db do puts "destroying database for environment: #{ENV['RACK_ENV']}" config = YAML.load_file('config/database.yml') config = config[ENV['RACK_ENV']] - user, password, host = config['user'], config['password'], config['hostname'] + user, password, host = config['user'], config['password'], config['host'] database = config['database'] # destroy database in mysql for datamapper @@ -138,7 +155,7 @@ namespace :db do puts "removing all data in the database for environment: #{ENV['RACK_ENV']}" config = YAML.load_file('config/database.yml') config = config[ENV['RACK_ENV']] - user, password, host = config['user'], config['password'], config['hostname'] + user, password, host = config['user'], config['password'], config['host'] database = config['database'] tables = [ 'customers','hashes','hashfilehashes','hashfiles','jobs','jobtasks','rules','sessions','taskqueues','wordlists' ] @@ -151,13 +168,13 @@ namespace :db do rescue raise 'Something went wrong. double check your config/database.yml file and manually test access to mysql.' end - end + end end task :provision_defaults do config = YAML.load_file('config/database.yml') config = config[ENV['RACK_ENV']] - user, password, host = config['user'], config['password'], config['hostname'] + user, password, host = config['user'], config['password'], config['host'] database = config['database'] puts '[*] Setting up default settings ...' @@ -242,7 +259,7 @@ namespace :db do rescue raise 'Error in creating default dictionary task + rule' end - + # Create Default SmartWordlist Dictionary puts '[*] Setting up default smart wordlist task' query = [ @@ -253,7 +270,7 @@ namespace :db do rescue raise 'Error in creating default SmartWordlist task' end - + # Create Default SmartWordlist Dictionary + Rule Task puts '[*] Setting up Smart Wordlist dictionary + rule task' query = [ @@ -264,8 +281,6 @@ namespace :db do rescue raise 'Error in creating Smart Wordlist dictionary task + rule' end - - # Create Default Mask task puts '[*] Setting up default mask task' @@ -288,7 +303,7 @@ namespace :db do rescue raise 'Error in creating default brute task' end - + # Create Default Hub Settings puts '[*] Setting up default hub settings' query = [ @@ -298,7 +313,7 @@ namespace :db do system(query.compact.join(' ')) rescue raise 'Error in creating default hub settings' - end + end end desc 'Setup local agent' @@ -310,7 +325,7 @@ namespace :db do puts "setting up local agent for environment: #{ENV['RACK_ENV']}" config = YAML.load_file('config/database.yml') config = config[ENV['RACK_ENV']] - user, password, host = config['user'], config['password'], config['hostname'] + user, password, host = config['user'], config['password'], config['host'] database = config['database'] agent_config = {} @@ -348,7 +363,7 @@ namespace :db do config = YAML.load_file('config/database.yml') config = config[ENV['RACK_ENV']] - user, password, host = config['user'], config['password'], config['hostname'] + user, password, host = config['user'], config['password'], config['host'] database = config['database'] puts '[*] Connecting to DB' @@ -365,9 +380,7 @@ namespace :db do end end - if has_version_column == false - db_version = Gem::Version.new('0.5.1') - end + db_version = Gem::Version.new('0.5.1') unless has_version_column # TODO turn into hash where version is key, and value is method/function name? if Gem::Version.new(db_version) < Gem::Version.new(application_version) @@ -388,6 +401,10 @@ namespace :db do if Gem::Version.new(db_version) < Gem::Version.new('0.7.1') upgrade_to_v071(user, password, host, database) end + # Upgrade to v0.7.2 + if Gem::Version.new(db_version) < Gem::Version.new('0.7.2') + upgrade_to_v072(user, password, host, database) + end else puts '[*] Your version is up to date!' end @@ -395,7 +412,7 @@ namespace :db do # Incase we missed anything DataMapper.repository.auto_upgrade! # DataMapper::Model.descendants.each {|m| m.auto_upgrade! if m.superclass == Object} - #puts 'db:auto:upgrade executed' + # puts 'db:auto:upgrade executed' end desc 'Migrate From old DB to new DB schema' @@ -406,7 +423,7 @@ namespace :db do config = YAML.load_file('config/database.yml') config = config[ENV['RACK_ENV']] - user, password, host = config['user'], config['password'], config['hostname'] + user, password, host = config['user'], config['password'], config['host'] database = config['database'] begin @@ -453,13 +470,11 @@ namespace :db do puts '[*] Inserting new data into table... standby..' hashes = conn.query("SELECT id,originalhash FROM hashes") - hashes.each_hash do | entry | + hashes.each_hash do |entry| olddata = conn.query("SELECT username,hashfile_id FROM targets WHERE originalhash='" + entry['originalhash'] + "'") hash_id = entry['id'] - olddata.each_hash do | row | - if row['username'].nil? - row['username'] = 'none' - end + olddata.each_hash do |row| + row['username'] = 'none' if row['username'].nil? row['username'] = row['username'].gsub("'", "\\\\'") conn.query("INSERT INTO hashfilehashes(hash_id,username,hashfile_id) VALUES ('#{hash_id}','#{row['username']}','#{row['hashfile_id']}')") end @@ -467,17 +482,15 @@ namespace :db do # Remove old tables puts '[*] Removing old tables' - conn.query("DROP TABLE targets") - + conn.query('DROP TABLE targets') rescue Mysql::Error => e puts e.errno puts e.error ensure - conn.close if conn + conn.close if conn end - end end @@ -594,12 +607,12 @@ def upgrade_to_v060(user, password, host, database) conn.query("UPDATE settings SET version = '0.6.0'") puts '[*] Upgrade to v0.6.0 complete.' - return '0.6.0' + '0.6.0' end def upgrade_to_v061(user, password, host, database) #DataMapper.repository.auto_upgrade! - DataMapper::Model.descendants.each {|m| m.auto_upgrade! if m.superclass == Object} + DataMapper::Model.descendants.each { |m| m.auto_upgrade! if m.superclass == Object } puts '[*] Upgrading from v0.6.0 to v0.6.1' conn = Mysql.new host, user, password, database @@ -608,12 +621,12 @@ def upgrade_to_v061(user, password, host, database) conn.query("UPDATE settings SET version = '0.6.1'") puts '[*] Upgrade to v0.6.1 complete.' - return '0.6.1' + '0.6.1' end def upgrade_to_v070(user, password, host, database) DataMapper.repository.auto_upgrade! - DataMapper::Model.descendants.each {|m| m.auto_upgrade! if m.superclass == Object} + DataMapper::Model.descendants.each { |m| m.auto_upgrade! if m.superclass == Object } puts '[*] Upgrading from v0.6.1 to v0.7.0' conn = Mysql.new host, user, password, database @@ -674,7 +687,7 @@ def upgrade_to_v070(user, password, host, database) # Adding to DB rule_file = Rules.new - rule_file.lastupdated = Time.now() + rule_file.lastupdated = Time.now rule_file.name = name rule_file.path = path_file rule_file.size = 0 @@ -727,7 +740,7 @@ def upgrade_to_v070(user, password, host, database) end def upgrade_to_v071(user, password, host, database) - DataMapper::Model.descendants.each {|m| m.auto_upgrade! if m.superclass == Object} + DataMapper::Model.descendants.each { |m| m.auto_upgrade! if m.superclass == Object } puts '[*] Upgrading from v0.7.0 to v0.7.1' conn = Mysql.new host, user, password, database @@ -737,3 +750,29 @@ def upgrade_to_v071(user, password, host, database) puts '[+] Upgrade to v0.7.1 complete.' end +def upgrade_to_v072(user, password, host, database) + DataMapper::Model.descendants.each { |m| m.auto_upgrade! if m.superclass == Object } + + puts '[*] Upgrading from v0.7.1 to v0.7.2' + conn = Mysql.new host, user, password, database + + # Remove unused columns + puts '[*] Removing unused database structures.' + conn.query('ALTER TABLE jobs DROP COLUMN policy_min_pass_length') + conn.query('ALTER TABLE jobs DROP COLUMN policy_complexity_default') + conn.query('ALTER TABLE jobs DROP COLUMN targettype') + conn.query('ALTER TABLE settings DROP COLUMN clientmode') + + # Updating database config file + puts '[*] Fixing db config entries.' + File.rename('config/database.yml', 'config/database.yml.old') + config_content = File.read('config/database.yml.old').gsub(/hostname:/, 'host:') + File.open('config/database.yml', 'w') do |out| + out << config_content + end + File.delete('config/database.yml.old') + + # FINALIZE UPGRADE + conn.query('UPDATE settings SET version = \'0.7.2\'') + puts '[+] Upgrade to v0.7.2 complete.' +end diff --git a/VERSION b/VERSION index 7deb86fe..d5cc44d1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.7.1 \ No newline at end of file +0.7.2 \ No newline at end of file diff --git a/config/database.travis.yml b/config/database.travis.yml index 229f3896..590015db 100644 --- a/config/database.travis.yml +++ b/config/database.travis.yml @@ -1,6 +1,6 @@ production: adapter: mysql - hostname: "localhost" + host: "localhost" port: 3306 user: "root" password: "" @@ -8,7 +8,7 @@ production: development: adapter: mysql - hostname: "localhost" + host: "localhost" port: 3306 user: "root" password: "" @@ -16,8 +16,8 @@ development: test: adapter: mysql - hostname: "localhost" + host: "localhost" port: 3306 user: "root" password: "" - database: "hashview_test" \ No newline at end of file + database: "hashview_test" diff --git a/config/database.yml.example b/config/database.yml.example index 36ac48b8..651c3a37 100644 --- a/config/database.yml.example +++ b/config/database.yml.example @@ -1,6 +1,6 @@ production: adapter: mysql - hostname: "localhost" + host: "localhost" port: 3306 user: "root" password: "CHANGEME" @@ -8,7 +8,7 @@ production: development: adapter: mysql - hostname: "localhost" + host: "localhost" port: 3306 user: "root" password: "CHANGEME" @@ -16,8 +16,8 @@ development: test: adapter: mysql - hostname: "localhost" + host: "localhost" port: 3306 user: "root" password: "CHANGEME" - database: "hashview_test" \ No newline at end of file + database: "hashview_test" diff --git a/config/resque_schedule.yml b/config/resque_schedule.yml index 04b5eb78..58e743da 100644 --- a/config/resque_schedule.yml +++ b/config/resque_schedule.yml @@ -14,23 +14,17 @@ # schedule for wordlist importer do_wordlist_importer: - every: 5s + every: 60s class: "WordlistImporter" queue: management description: "This job automatically imports wordlists if placed in controls/wordlists dir" do_rulelist_importer: - every: 5s + every: 10s class: "RuleImporter" queue: management description: "This job automatically imports rule files if placed in controls/rules dir" -do_wordlist_checksum: - every: 20s - class: "WordlistChecksum" - queue: management - description: "This job creates a sha256 checksum for all wordlists. This keeps wordlists in sync for multiple agents" - # schedule for cleanup task do_cleanUp: every: 1d diff --git a/hashview.rb b/hashview.rb index a43542f7..b15a5b09 100644 --- a/hashview.rb +++ b/hashview.rb @@ -4,6 +4,7 @@ require 'haml' require 'resque' require 'resque/server' +require 'logger' require_relative 'models/master' require_relative 'helpers/init' @@ -47,4 +48,4 @@ Rack::Utils.key_space_limit = 68719476736 # start our local agent -Resque.enqueue(LocalAgent) +Resque.enqueue(LocalAgent) \ No newline at end of file diff --git a/helpers/build_crack_cmd.rb b/helpers/build_crack_cmd.rb index 44807dfa..b0a25f4b 100644 --- a/helpers/build_crack_cmd.rb +++ b/helpers/build_crack_cmd.rb @@ -37,7 +37,7 @@ def buildCrackCmd(job_id, task_id) chunks[chunk_num] = [skip, limit] - chunk_num = chunk_num + 1 + chunk_num += 1 chunk_skip = limit end end @@ -55,57 +55,60 @@ def buildCrackCmd(job_id, task_id) # we assign and write output file before hashcat. # if hashcat creates its own output it does so with - # elvated permissions and we wont be able to read it + # elevated permissions and we wont be able to read it crack_file = 'control/outfiles/hc_cracked_' + @job.id.to_s + '_' + @task.id.to_s + '.txt' File.open(crack_file, 'w') - if attackmode == 'bruteforce' + case attackmode + when 'bruteforce' cmd = hc_binpath + ' -m ' + hashtype + ' --potfile-disable' + ' --status --status-timer=15' + ' --runtime=' + max_task_time + ' --outfile-format 5 ' + ' --outfile ' + crack_file + ' ' + ' -a 3 ' + target_file - elsif attackmode == 'maskmode' + when 'maskmode' cmd = hc_binpath + ' -m ' + hashtype + ' --potfile-disable' + ' --status --status-timer=15' + ' --outfile-format 5 ' + ' --outfile ' + crack_file + ' ' + ' -a 3 ' + target_file + ' ' + mask - elsif attackmode == 'dictionary' + when 'dictionary' if @task.hc_rule.nil? || @task.hc_rule == 'none' cmd = hc_binpath + ' -m ' + hashtype + ' --potfile-disable' + ' --status --status-timer=15' + ' --outfile-format 5 ' + ' --outfile ' + crack_file + ' ' + target_file + ' ' + wordlist.path else cmd = hc_binpath + ' -m ' + hashtype + ' --potfile-disable' + ' --status --status-timer=15' + ' --outfile-format 5 ' + ' --outfile ' + crack_file + ' ' + ' -r ' + rules_file.path + ' ' + target_file + ' ' + wordlist.path end - elsif attackmode == 'combinator' + when 'combinator' cmd = hc_binpath + ' -m ' + hashtype + ' --potfile-disable' + ' --status --status-timer=15' + ' --outfile-format 5 ' + ' --outfile ' + crack_file + ' ' + ' -a 1 ' + target_file + ' ' + wordlist_one.path + ' ' + ' ' + wordlist_two.path + ' ' + @task.hc_rule.to_s + else + puts 'INVALID ATTACK MODE: ' + attackmode.to_s end # Add global options # --opencl-device-types if hc_settings.opencl_device_types.to_s != '0' - cmd = cmd + ' --opencl-device-types ' + hc_settings.opencl_device_types.to_s + cmd += ' --opencl-device-types ' + hc_settings.opencl_device_types.to_s end # --workload-profile if hc_settings.workload_profile.to_s != '0' - cmd = cmd + ' --workload-profile ' + hc_settings.workload_profile.to_s + cmd += ' --workload-profile ' + hc_settings.workload_profile.to_s end # --gpu-temp-disable if hc_settings.gpu_temp_disable == true - cmd = cmd + ' --gpu-temp-disable' + cmd += ' --gpu-temp-disable' end # --gpu-temp-abort if hc_settings.gpu_temp_abort.to_s != '0' - cmd = cmd + ' --gpu-temp-abort=' + hc_settings.gpu_temp_abort.to_s + cmd += ' --gpu-temp-abort=' + hc_settings.gpu_temp_abort.to_s end # --gpu-temp-retain if hc_settings.gpu_temp_retain.to_s != '0' - cmd = cmd + ' --gpu-temp-retain=' + hc_settings.gpu_temp_retain.to_s + cmd += ' --gpu-temp-retain=' + hc_settings.gpu_temp_retain.to_s end # --force if hc_settings.hc_force == true - cmd = cmd + ' --force' + cmd += ' --force' end # add skip and limit if we are chunking this task - if chunking + if chunking == true chunks.each do |_unused, value| if attackmode == 'maskmode' || attackmode == 'dictionary' cmds << cmd + ' -s ' + value[0].to_s + ' -l ' + value[1].to_s @@ -116,6 +119,6 @@ def buildCrackCmd(job_id, task_id) cmds << cmd end - return cmds + cmds end end diff --git a/helpers/compute_task_keyspace.rb b/helpers/compute_task_keyspace.rb index 86e86d46..abb77322 100644 --- a/helpers/compute_task_keyspace.rb +++ b/helpers/compute_task_keyspace.rb @@ -1,6 +1,5 @@ # this helper generates the keyspace of a given task. Helpful when chunking the task for multiple agents. def getKeyspace(task) - p '=============== generateing keyspace for task =====================' # get hashcat binarypath from config hashcatbinpath = JSON.parse(File.read('config/agent_config.json'))['hc_binary_path'] @@ -39,7 +38,6 @@ def getKeyspace(task) cmd = hashcatbinpath + ' ' + wordlist1_path + ' --keyspace' keyspace2 = `#{cmd}` cmd = hashcatbinpath + ' ' + wordlist2_path + ' --keyspace' - end # run hashcat keyspace command diff --git a/helpers/cracked_importer.rb b/helpers/cracked_importer.rb index 27e6881b..eac3243b 100644 --- a/helpers/cracked_importer.rb +++ b/helpers/cracked_importer.rb @@ -12,13 +12,8 @@ def updateDbRunTime(job_id, hashfile_id, run_time) # imports the uploaded crackfile def importCracked(id, crack_file, run_time) # this assumes a job completed successfully. we need to add check for failures or killed processes - puts '==== Importing cracked hashes =====' - - # Disabling now that we are chunking. Not sure if this is a good idea yet - #updateJobTaskStatus(id, 'Importing') jobtasks = Jobtasks.first(id: id) - #crack_file = 'control/outfiles/hc_cracked_' + jobtasks.job_id.to_s + '_' + jobtasks.task_id.to_s + '.txt' job = Jobs.first(id: jobtasks.job_id) # determine hashfile path @@ -40,8 +35,7 @@ def importCracked(id, crack_file, run_time) hash_pass.pop # removes tail entry which should have been the plaintext (in hex) # Handle salted hashes # There's gotta be a better way to do this - if hashtype == '10' or hashtype == '20' or hashtype == '30' or hashtype == '40' or hashtype == '50' or hashtype == '60' or hashtype == '110' or hashtype == '120' or hashtype == '121' or hashtype == '130' or hashtype == '140' or hashtype == '150' or hashtype == '160' or hashtype == '1100' or hashtype == '1410' or hashtype == '1420' or hashtype == '1430' or hashtype == '1440' or hashtype == '1450' or hashtype == '1460' or hashtype == '2611' or hashtype == '2711' or hashtype == '3610' or hashtype == '3710' or hashtype == '3720' or hashtype == '3910' or hashtype == '4010' or hashtype == '4110' or hashtype == '2711' or hashtype == '11000' - #hash = hash_pass[0].to_s + ':' + hash_pass[1].to_s + if hashtype == '10' || hashtype == '20' || hashtype == '30' || hashtype == '40' || hashtype == '50' || hashtype == '60' ||hashtype == '110' || hashtype == '120' || hashtype == '121' || hashtype == '130' || hashtype == '140' || hashtype == '150' || hashtype == '160' || hashtype == '1100' || hashtype == '1410' || hashtype == '1420' || hashtype == '1430' || hashtype == '1440' || hashtype == '1450' || hashtype == '1460' || hashtype == '2611' || hashtype == '2711' || hashtype == '3610' || hashtype == '3710' || hashtype == '3720' || hashtype == '3910' || hashtype == '4010' || hashtype == '4110' || hashtype == '2711' || hashtype == '11000' hash = hash_pass.join(":") elsif hashtype == '5500' hash = hash_pass[3] + ':' + hash_pass[4] + ':' + hash_pass[5] @@ -49,23 +43,18 @@ def importCracked(id, crack_file, run_time) hash = hash_pass[0] + ':' + hash_pass[1] + ':' + hash_pass[2] + ':' + hash_pass[3] + ':' + hash_pass[4] + ':' + hash_pass[5] elsif hashtype == '7400' parts = hash_pass[0].split('$') - p 'PARTS: ' + parts.to_s - hash = '%' + parts[3].to_s + '$' + parts[4].to_s + # p 'PARTS: ' + parts.to_s + hash = '%' + parts[3].to_s + '$' + parts[4].to_s else hash = hash_pass[0] end - # p 'job.hashfile_id: ' + job.hashfile_id.to_s - # p 'hashfilehash.hash_id: ' + hashfilehash.to_s - # p 'hashtype: ' + hashtype.to_s - # p 'PLAINTEXT: ' + plaintext.to_s - # p 'Hash: ' + hash.to_s # This will pull all hashes from DB regardless of job id if hashtype == '7400' results = repository(:default).adapter.select('SELECT * FROM hashes WHERE (hashtype = 7400 AND originalhash like ?)', hash)[0] records = Hashes.all(fields: [:id, :cracked, :plaintext, :lastupdated], id: results.id) else - records = Hashes.all(fields: [:id, :cracked, :plaintext, :lastupdated], originalhash: hash, cracked: 0 ) + records = Hashes.all(fields: [:id, :cracked, :plaintext, :lastupdated], originalhash: hash, cracked: 0) end # Yes its slow... we know. records.each do |entry| @@ -87,10 +76,6 @@ def importCracked(id, crack_file, run_time) p 'ERROR: ' + $!.to_s end - puts '==== Crack File Deleted ====' - - # commenting this out now that we are chunking - #updateJobTaskStatus(id, 'Completed') # TODO this might be broken now that we are chunking updateDbRunTime(id, job.hashfile_id, run_time) end diff --git a/helpers/email.rb b/helpers/email.rb index faeb63bd..3df10b0b 100644 --- a/helpers/email.rb +++ b/helpers/email.rb @@ -10,7 +10,7 @@ def sendEmail(recipient, sub, msg) Pony.options = { :via => :smtp, :via_options => { - :from => smtp_sender.to_s, + :from => smtp_settings.smtp_sender.to_s, :address => smtp_server.to_s, :port => smtp_port.to_s, :enable_starttls_auto => use_tls.to_s, @@ -31,7 +31,7 @@ def sendEmail(recipient, sub, msg) } end - if smtp_settings.smtp_sender.nil? or smtp_settings.smtp_sender.empty? + if smtp_settings.smtp_sender.nil? || smtp_settings.smtp_sender.empty? sender_addr = 'no-reply@hashview' else sender_addr = smtp_settings.smtp_sender.to_s diff --git a/helpers/hashimporter.rb b/helpers/hashimporter.rb index a1bc11df..1e27857d 100644 --- a/helpers/hashimporter.rb +++ b/helpers/hashimporter.rb @@ -73,11 +73,7 @@ def importPwdump(hash, hashfile_id, type) end def machineAcct?(username) - if username =~ /\$/ - return true - else - return false - end + username =~ /\$/ ? true : false end def importShadow(hash, hashfile_id, type) @@ -398,12 +394,14 @@ def modeToFriendly(mode) return 'sha1($salt.unicode($pass))' if mode == '140' return 'HMAC-SHA1 (key = $pass)' if mode == '150' return 'HMAC-SHA1 (key = $salt)' if mode == '160' - # return 'sha1(LinkedIn)' if mode == '190' + # 'sha1(LinkedIn)' if mode == '190' return 'MySQL323' if mode == '200' return 'md5crypt' if mode == '500' return 'MD4' if mode == '900' return 'NTLM' if mode == '1000' + return 'SHA-256' if mode == '1400' return 'descrypt' if mode == '1500' + return 'SHA-512' if mode == '1700' return 'sha512crypt' if mode == '1800' return 'Double MD5' if mode == '2600' return 'LM' if mode == '3000' @@ -429,28 +427,44 @@ def modeToFriendly(mode) return 'Lotus Notes/Domino 5' if mode == '8600' return 'PrestaShop' if mode == '11000' return 'unknown' if mode == '99999' - return 'unknown' + 'unknown' end def friendlyToMode(friendly) return '0' if friendly == 'MD5' - return '1000' if friendly == 'NTLM' - return '3000' if friendly == 'LM' + return '10' if friendly == 'md5($pass.$salt)' + return '20' if friendly == 'md5($salt.$pass)' + return '30' if friendly == 'md5(unicode($pass).$salt)' + return '40' if friendly == 'md5($salt.unicode($pass))' return '100' if friendly == 'SHA-1' + return '200' if friendly == 'MySQL323' return '500' if friendly == 'md5crypt' + return '900' if friendly == 'MD4' + return '1000' if friendly == 'NTLM' + return '1400' if friendly == 'SHA-256' + return '1700' if friendly == 'SHA-512' + return '2600' if friendly == 'Double MD5' + return '3000' if friendly == 'LM' + return '3500' if friendly == 'md5(md5(md5($pass)))' + return '4400' if friendly == 'md5(sha1($pass))' + return '4500' if friendly == 'sha1(sha1($pass))' + return '4600' if friendly == 'sha1(sha1(sha1($pass)))' + return '4700' if friendly == 'sha1(md5($pass))' return '3200' if friendly == 'bcrypt' return '7400' if friendly == 'sha512crypt' return '1800' if friendly == 'sha256crypt' return '1500' if friendly == 'descrypt' return '5500' if friendly == 'NetNTLMv1' return '5600' if friendly == 'NetNTLMv2' + return '6000' if friendly == 'RipeMD160' + '99999' end def importHash(hash_array, hashfile_id, file_type, hashtype) hash_array.each do |entry| entry = entry.gsub(/\s+/, '') # remove all spaces - if file_type == 'pwdump' or file_type == 'smart hashdump' - importPwdump(entry.chomp, hashfile_id, hashtype) #because the format is the same aside from the trailing :: + if file_type == 'pwdump' || file_type == 'smart hashdump' + importPwdump(entry.chomp, hashfile_id, hashtype) # because the format is the same aside from the trailing :: elsif file_type == 'shadow' importShadow(entry.chomp, hashfile_id, hashtype) elsif file_type == 'hash_only' @@ -475,7 +489,7 @@ def detectHashType(hash_file, file_type) @hashtypes = [] File.readlines(hash_file).each do |entry| entry = entry.gsub(/\s+/, "") # remove all spaces - if file_type == 'pwdump' or file_type == 'smart_hashdump' + if file_type == 'pwdump' || file_type == 'smart_hashdump' elements = entry.split(':') @modes = getMode(elements[2]) @modes.each do |mode| diff --git a/helpers/hc_stdout_parser.rb b/helpers/hc_stdout_parser.rb index 274e2dd0..5b8c61ed 100644 --- a/helpers/hc_stdout_parser.rb +++ b/helpers/hc_stdout_parser.rb @@ -25,6 +25,6 @@ def hashcatParser(file) rescue SystemCallError => e puts e end - return status + status end end diff --git a/helpers/init.rb b/helpers/init.rb index bbb362ec..de2118f5 100644 --- a/helpers/init.rb +++ b/helpers/init.rb @@ -10,4 +10,4 @@ require_relative 'tasks' require_relative 'cracked_importer' require_relative 'compute_task_keyspace' -require_relative 'smartWordlist' +require_relative 'smartWordlist' \ No newline at end of file diff --git a/helpers/sanitization.rb b/helpers/sanitization.rb index a3276353..f8fb5545 100644 --- a/helpers/sanitization.rb +++ b/helpers/sanitization.rb @@ -2,12 +2,8 @@ # Take you to the var wash baby def varWash(params) params.keys.each do |key| - if params[key].is_a?(String) - params[key] = cleanString(params[key]) - end - if params[key].is_a?(Array) - params[key] = cleanArray(params[key]) - end + params[key] = cleanString(params[key]) if params[key].is_a?(String) + params[key] = cleanArray(params[key]) if params[key].is_a?(Array) end end @@ -20,7 +16,7 @@ def cleanArray(array) array.each do |entry| clean_array.push(cleanString(entry)) end - return clean_array + clean_array end end diff --git a/helpers/sessions.rb b/helpers/sessions.rb index 7d68c55a..8859abb0 100644 --- a/helpers/sessions.rb +++ b/helpers/sessions.rb @@ -12,10 +12,6 @@ def getUsername def agentAuthorized(uuid) auth = Agents.first(:uuid => uuid, :status.not => 'Pending') - if auth - return true - else - return false - end + auth ? true : false end end diff --git a/helpers/smartWordlist.rb b/helpers/smartWordlist.rb index aed5ba27..e2ecb315 100644 --- a/helpers/smartWordlist.rb +++ b/helpers/smartWordlist.rb @@ -8,7 +8,7 @@ def updateSmartWordlist end wordlist = Wordlists.first(name: 'Smart Wordlist') - # Create Smart word list if one doesnt exists + # Create Smart word list if one doesn't exists if wordlist.nil? wordlist = Wordlists.new wordlist.lastupdated = Time.now @@ -39,7 +39,7 @@ def updateSmartWordlist shell_cmd = 'sort --parallel ' + cpu_count.to_s + ' -u control/tmp/plaintext.txt ' @wordlists = Wordlists.all @wordlists.each do |entry| - shell_cmd = shell_cmd + entry.path.to_s + ' ' + shell_cmd = shell_cmd + entry.path.to_s + ' ' if entry.type == 'static' end # We move to temp to prevent wordlist importer from accidentally loading the smart wordlist too early shell_cmd += '-o control/tmp/SmartWordlist.txt' @@ -62,7 +62,7 @@ def updateSmartWordlist # Remove plaintext list File.delete('control/tmp/plaintext.txt') if File.exist?('control/tmp/plaintext.txt') - # Update keyspace per task ( really shouldbe done at runtime) + # Update keyspace per task ( really should be done at runtime) tasks = Tasks.all(wl_id: wordlist.id) tasks.each do |task| task.keyspace = getKeyspace(task) diff --git a/helpers/status.rb b/helpers/status.rb index 3f2cca6e..08f68819 100644 --- a/helpers/status.rb +++ b/helpers/status.rb @@ -1,6 +1,6 @@ def isBusy? @results = `ps awwux | grep -i Hashcat | egrep -v "(grep|sudo|resque|^$)"` - return true if @results.length > 1 + true if @results.length > 1 end def isDevelopment? @@ -19,9 +19,7 @@ def isOldVersion? has_version_column = false @tables = repository(:default).adapter.select('DESC settings') @tables.each do |row| - if row.field == 'version' - has_version_column = true - end + has_version_column = true if row.field == 'version' end if has_version_column @@ -35,9 +33,8 @@ def isOldVersion? end else puts 'No version column found. Assuming Version 0.5.1' - return true + true end - return false end def updateTaskqueueStatus(taskqueue_id, status, agent_id) @@ -49,8 +46,10 @@ def updateTaskqueueStatus(taskqueue_id, status, agent_id) # if we are setting a status to completed, check to see if this is the last task in queue. if so, set jobtask to completed if status == 'Completed' - remainingtasks = Taskqueues.all(jobtask_id: queue.jobtask_id, job_id: queue.job_id, status: 'Queued') - if remainingtasks.empty? + remaining_queued_tasks = Taskqueues.all(jobtask_id: queue.jobtask_id, job_id: queue.job_id, status: 'Queued') + remaining_running_tasks = Taskqueues.all(jobtask_id: queue.jobtask_id, job_id: queue.job_id, status: 'Running') + remaining_importing_tasks = Taskqueues.all(jobtask_id: queue.jobtask_id, job_id: queue.job_id, status: 'Importing') + if remaining_queued_tasks.empty? && remaining_running_tasks.empty? && remaining_importing_tasks.empty? updateJobTaskStatus(queue.jobtask_id, 'Completed') end end @@ -68,6 +67,7 @@ def updateJobTaskStatus(jobtask_id, status) job = Jobs.first(id: jobtask.job_id) if job.status == 'Queued' job.status = 'Running' + job.started_at = Time.now job.save end # find all tasks for current job: @@ -101,8 +101,9 @@ def updateJobTaskStatus(jobtask_id, status) end # toggle job status - if done == true + if done job.status = 'Completed' + job.ended_at = Time.now job.save # purge all queued tasks taskqueues = Taskqueues.all(job_id: job.id) diff --git a/helpers/test_helper.rb b/helpers/test_helper.rb index 00ed26f6..6fad7abf 100644 --- a/helpers/test_helper.rb +++ b/helpers/test_helper.rb @@ -2,8 +2,5 @@ require 'minitest/autorun' require 'rack/test' require 'factory_girl' -#require 'sinatra' -#require 'sinatra/flash' -#require 'haml' -require File.expand_path '../../hashview.rb', __FILE__ +require File.expand_path '../../hashview.rb', __FILE__ \ No newline at end of file diff --git a/jobs/background_worker.rb b/jobs/background_worker.rb index 3ef87e3c..5b905d88 100644 --- a/jobs/background_worker.rb +++ b/jobs/background_worker.rb @@ -9,21 +9,21 @@ class Api # obtain remote ip and port from local config begin options = JSON.parse(File.read('config/agent_config.json')) - @server = options['ip'] + ":" + options['port'] + @server = options['ip'] + ':' + options['port'] @uuid = options['uuid'] @hashcatbinpath = options['hc_binary_path'].to_s rescue - "Error reading config/agent_config.json. Did you run rake db:provision_agent ???" + 'Error reading config/agent_config.json. Did you run rake db:provision_agent ???' end ######### generic api handling of GET and POST request ########### def self.get(url) begin response = RestClient::Request.execute( - :method => :get, - :url => url, - :cookies => {:agent_uuid => @uuid}, - :verify_ssl => false + method: :get, + url: url, + cookies: {agent_uuid: @uuid}, + verify_ssl: false ) return response.body rescue RestClient::Exception => e @@ -34,12 +34,12 @@ def self.get(url) def self.post(url, payload) begin response = RestClient::Request.execute( - :method => :post, - :url => url, - :payload => payload.to_json, - :headers => {:accept => :json}, - :cookies => {:agent_uuid => @uuid}, - :verify_ssl => false + method: :post, + url: url, + payload: payload.to_json, + headers: {accept: :json}, + cookies: {agent_uuid: @uuid}, + verify_ssl: false ) return response.body rescue RestClient::Exception => e @@ -53,14 +53,14 @@ def self.post(url, payload) # get heartbeat when we are looking for work to do def self.heartbeat() url = "https://#{@server}/v1/agents/#{@uuid}/heartbeat" - puts "HEARTBEETING" + # puts "HEARTBEETING" return self.get(url) end # post hearbeat is used when agent is working def self.post_heartbeat(payload) url = "https://#{@server}/v1/agents/#{@uuid}/heartbeat" - puts "HEARTBEETING" + # puts "HEARTBEETING" return self.post(url, payload) end @@ -136,32 +136,21 @@ def self.wordlists() return self.get(url) end - # download a wordlist - #def self.wordlist() - # url = "https://#{@server}/v1/wordlist/:id" - # return self.get(url) - #end - - # save wordlist to disk - #def self.save_wordlist(localpath='control/wordlists/thisisjustatest.txt') - # File.write(localpath) - #end - # upload crack file def self.upload_crackfile(jobtask_id, crack_file, run_time) url = "https://#{@server}/v1/jobtask/#{jobtask_id}/crackfile/upload" - puts "attempting upload #{crack_file}" + # puts "attempting upload #{crack_file}" begin request = RestClient::Request.new( - :method => :post, - :url => url, - :payload => { - :multipart => true, - :file => File.new(crack_file, 'rb'), - :runtime => run_time + method: :post, + url: url, + payload: { + multipart: true, + file: File.new(crack_file, 'rb'), + runtime: run_time }, - :cookies => {:agent_uuid => @uuid}, - :verify_ssl => false + cookies: {agent_uuid: @uuid}, + verify_ssl: false ) response = request.execute rescue RestClient::Exception => e @@ -268,6 +257,16 @@ def self.perform() # this is our background worker for the task queue # other workers will be ran from a hashview agent + # Setup Logger + logger_background_worker = Logger.new('logs/jobs/background_worker.log', 'daily') + if ENV['RACK_ENV'] == 'development' + logger_background_worker.level = Logger::DEBUG + else + logger_background_worker.level = Logger::INFO + end + + logger_background_worker.debug('Background Worker Class() - has started') + hashcatbinpath = JSON.parse(File.read('config/agent_config.json'))['hc_binary_path'] # is hashcat working? if so, how fast are you? provide basic information to master server @@ -278,21 +277,22 @@ def self.perform() hc_perfstats = hashcatBenchmarkParser(hc_benchmark(hashcatbinpath)) Api.stats(hc_devices, hc_perfstats) - while(1) + while(true) sleep(4) # find pid pid = getHashcatPid # wait a bit to avoid race condition - if !pid.nil? and File.exist?('control/tmp/agent_current_task.txt') + if !pid.nil? && File.exist?('control/tmp/agent_current_task.txt') sleep(10) pid = getHashcatPid end # ok either do nothing or start working if pid.nil? - puts "AGENT IS WORKING RIGHT NOW" + # Do we need to even log this? + logger_background_worker.debug('Agent is working right now') else # if we have taskqueue tmp file locally, delete it @@ -303,11 +303,10 @@ def self.perform() payload['agent_status'] = 'Idle' payload['hc_benchmark'] = 'example data' heartbeat = Api.post_heartbeat(payload) - puts '======================================' heartbeat = JSON.parse(heartbeat) - puts heartbeat + logger_background_worker.info(heartbeat) - if heartbeat['type'] == 'message' and heartbeat['msg'] == 'START' + if heartbeat['type'] == 'message' && heartbeat['msg'] == 'START' jdata = Api.queue_by_id(heartbeat['task_id']) jdata = JSON.parse(jdata) @@ -359,7 +358,7 @@ def self.perform() # get our hashcat command and sub out the binary path cmd = jdata['command'] cmd = replaceHashcatBinPath(cmd) - puts cmd + logger_background_worker.debug(cmd) # this variable is used to determine if the job was canceled @canceled = false @@ -377,8 +376,8 @@ def self.perform() catch :mainloop do while thread1.status do sleep 4 - puts 'WORKING IN THREAD' - puts "WORKING ON ID: #{jdata['id']}" + #puts 'WORKING IN THREAD' + logger_background_worker.info("WORKING ON ID: #{jdata['id']}") payload = {} payload['agent_status'] = 'Working' payload['agent_task'] = jdata['id'] @@ -410,25 +409,19 @@ def self.perform() if File.exist?(crack_file) && ! File.zero?(crack_file) Api.upload_crackfile(jobtask['id'], crack_file, run_time) else - puts "No successful cracks for this task. Skipping upload." + # Does this need to be logged? + logger_background_worker.info('No successful cracks for this task. Skipping upload.') end # remove task data tmp file File.delete('control/tmp/agent_current_task.txt') if File.exist?('control/tmp/agent_current_task.txt') - # change status to completed for jobtask - # commenting out now that we are chunking - # if @canceled - # Api.post_jobtask_status(jdata['jobtask_id'], 'Canceled') - # else - # Api.post_jobtask_status(jdata['jobtask_id'], 'Completed') - # end - # set taskqueue item to complete and remove from queue Api.post_queue_status(jdata['id'], 'Completed') end end end end + logger_background_worker.debug('Background Worker Class() - has Completed') end -end +end \ No newline at end of file diff --git a/jobs/clean_up.rb b/jobs/clean_up.rb index a873e3a2..7f997c4c 100644 --- a/jobs/clean_up.rb +++ b/jobs/clean_up.rb @@ -3,12 +3,20 @@ require_relative '../helpers/status' def cleanDir(path) + # Setup Logger + # Not a fan of this, not sure if i can just pass the logger_cleanup object instead? + logger_cleanup = Logger.new('logs/jobs/cleanup.log', 'daily') + if ENV['RACK_ENV'] == 'development' + logger_cleanup.level = Logger::DEBUG + else + logger_cleanup.level = Logger::INFO + end + + @files = Dir.glob(File.join(path)) @files.each do |path_file| - if (Time.now - File.ctime(path_file))/(24*3600) > 30 # Need to change to a user defined setting - if ENV['RACK_ENV'] == 'development' - puts 'File: ' + path_file.to_s + ' is greater than 30 days old. Deleting' - end + if (Time.now - File.ctime(path_file)) / (24 * 3600) > 30 # TODO Need to change to a user defined setting + logger_cleanup.info('File: ' + path_file.to_s + ' is greater than 30 days old. Deleting') File.delete(path_file) end end @@ -17,10 +25,16 @@ def cleanDir(path) module CleanUp @queue = :management def self.perform() + # Setup Logger + logger_cleanup = Logger.new('logs/jobs/cleanup.log', 'daily') if ENV['RACK_ENV'] == 'development' - p 'CleanUp Class - start' + logger_cleanup.level = Logger::DEBUG + else + logger_cleanup.level = Logger::INFO end + logger_cleanup.debug('Cleanup Class() - has started') + # control/tmp/* cleanDir('control/tmp/*') @@ -31,7 +45,13 @@ def self.perform() cleanDir('control/outfiles/left_*') # control/hashes/hashfile_upload_* - cleanDir('hashes/hashfile_upload_*') + cleanDir('control/hashes/hashfile_upload_*') + + # control/logs/*.log + cleanDir('control/logs/*.log') + + # control/logs/jobs/*.log + cleanDir('control/logs/jobs/*.log') # TODO # Maybe do a better way of validating we're not going to delete an actively used file? @@ -46,8 +66,6 @@ def self.perform() cleanDir('control/hashes/hashfile_*.txt') end - if ENV['RACK_ENV'] == 'development' - p 'CleanUp Class - Done' - end + logger_cleanup.debug('Cleanup Class() - has completed') end end diff --git a/jobs/file_checksum.rb b/jobs/file_checksum.rb deleted file mode 100644 index 200dea40..00000000 --- a/jobs/file_checksum.rb +++ /dev/null @@ -1,19 +0,0 @@ -class FileChecksum - @queue = :management - def self.perform(type, id) - - if type == 'rules' - rules_file = Rules.first(id: id) - cmd = rules_file.path - checksum = `sha256sum "#{cmd}"` - rules_file.checksum = checksum - rules_file.save - elsif type == 'wordlist' - wordlists = Wordlists.first(id: id) - cmd = wordlists.path - checksum = `sha256sum "#{cmd}"` - wordlists.checksum = checksum - wordlists.save - end - end -end diff --git a/jobs/init.rb b/jobs/init.rb index d296f568..f7829f49 100644 --- a/jobs/init.rb +++ b/jobs/init.rb @@ -3,4 +3,4 @@ require_relative 'background_worker' require_relative 'wordlistChecksum' require_relative 'clean_up' -require_relative 'ruleImporter' +require_relative 'ruleImporter' \ No newline at end of file diff --git a/jobs/ruleImporter.rb b/jobs/ruleImporter.rb index 32156547..206572b1 100644 --- a/jobs/ruleImporter.rb +++ b/jobs/ruleImporter.rb @@ -1,13 +1,17 @@ -require_relative 'file_checksum' module RuleImporter @queue = :management def self.perform() sleep(rand(10)) + logger_ruleimporter = Logger.new('logs/jobs/ruleImporter.log', 'daily') if ENV['RACK_ENV'] == 'development' - puts 'Rule fil Importer Class' + logger_ruleimporter.level = Logger::DEBUG + else + logger_ruleimporter.level = Logger::INFO end + logger_ruleimporter.debug('Rule Importer Class() - has started') + # Identify all rules in directory @files = Dir.glob(File.join('control/rules/', '*.rule')) @files.each do |path_file| @@ -15,12 +19,11 @@ def self.perform() unless rule_entry # Get Name name = path_file.split('/')[-1] - - puts 'Importing new Rule file "' + name + '" into HashView.' + logger_ruleimporter.info('Importing new Rule ""' + name + '"" into HashView.') # Adding to DB rule_file = Rules.new - rule_file.lastupdated = Time.now() + rule_file.lastupdated = Time.now rule_file.name = name rule_file.path = path_file rule_file.size = 0 @@ -33,16 +36,15 @@ def self.perform() @files = Dir.glob(File.join('control/rules/', '*.rule')) @files.each do |path_file| rule_file = Rules.first(path: path_file) - id = rule_file.id if rule_file.size == '0' - size = File.foreach(path_file).inject(0) { |c| c + 1} + size = File.foreach(path_file).inject(0) do |c| + c + 1 + end rule_file.size = size rule_file.save end end - if ENV['RACK_ENV'] == 'development' - puts 'Rule file Importer Class() - done' - end + logger_ruleimporter.debug('Rule Importer Class() - has completed') end end diff --git a/jobs/wordlistChecksum.rb b/jobs/wordlistChecksum.rb index 18ea2d60..966e4825 100644 --- a/jobs/wordlistChecksum.rb +++ b/jobs/wordlistChecksum.rb @@ -3,12 +3,21 @@ module WordlistChecksum @queue = :management def self.perform() - puts '============== generating wordlist checksum ========================' + # Setup Logger + logger_wordlistchecksum = Logger.new('logs/jobs/wordlistchecksum.log', 'daily') + if ENV['RACK_ENV'] == 'development' + logger_wordlistchecksum.level = Logger::DEBUG + else + logger_wordlistchecksum.level = Logger::INFO + end + + logger_wordlistchecksum.debug('Wordlist Checksum Class() - has started') + # Identify all wordlists without checksums @wordlist = Wordlists.all(checksum: nil) @wordlist.each do |wl| # generate checksum - puts 'generating checksum for: ' + wl.path.to_s + logger_wordlistchecksum.info('generating checksum for: ' + wl.path.to_s) checksum = Digest::SHA2.hexdigest(File.read(wl.path)) # save checksum to database @@ -16,5 +25,6 @@ def self.perform() wl.save end + logger_wordlistchecksum.debug('Wordlist Checksum Class() - has completed') end end diff --git a/jobs/wordlistImporter.rb b/jobs/wordlistImporter.rb index a350d614..8dc0be55 100644 --- a/jobs/wordlistImporter.rb +++ b/jobs/wordlistImporter.rb @@ -3,12 +3,18 @@ module WordlistImporter def self.perform() sleep(rand(10)) + # Setup Logger + logger_wordlistimporter = Logger.new('logs/jobs/wordlistImporter.log', 'daily') if ENV['RACK_ENV'] == 'development' - puts 'Wordlist Importer Class' + logger_wordlistimporter.level = Logger::DEBUG + else + logger_wordlistimporter.level = Logger::INFO end + logger_wordlistimporter.debug('Wordlist Importer Class() - has started') + # Identify all wordlists in directory - @files = Dir.glob(File.join('control/wordlists/', "*")) + @files = Dir.glob(File.join('control/wordlists/', '*')) @files.each do |path_file| wordlist_entry = Wordlists.first(path: path_file) unless wordlist_entry @@ -17,11 +23,11 @@ def self.perform() # Make sure we're not dealing with a tar, gz, tgz, etc. Not 100% accurate! unless name.match(/\.tar|\.7z|\.gz|\.tgz|\.checksum/) - puts 'Importing new wordslist "' + name + '" into HashView.' + logger_wordlistimporter.info('Importing new wordslist "' + name + '" into HashView.') # Adding to DB wordlist = Wordlists.new - wordlist.lastupdated = Time.now() + wordlist.lastupdated = Time.now wordlist.type = 'static' wordlist.name = name wordlist.path = path_file @@ -32,15 +38,16 @@ def self.perform() end end - - @files = Dir.glob(File.join('control/wordlists/', "*")) + @files = Dir.glob(File.join('control/wordlists/', '*')) @files.each do |path_file| # Get Name name = path_file.split('/')[-1] unless name.match(/\.tar|\.7z|\.gz|\.tgz|\.checksum/) wordlist = Wordlists.first(path: path_file) if wordlist.size == '0' - size = File.foreach(path_file).inject(0) { |c| c + 1 } + size = File.foreach(path_file).inject(0) do |c| + c + 1 + end wordlist.size = size wordlist.save end @@ -51,8 +58,6 @@ def self.perform() # this checksum is used to compare differences with agents Resque.enqueue(WordlistChecksum) - if ENV['RACK_ENV'] == 'development' - puts 'Wordlist Importer Class() - done' - end + logger_wordlistimporter.debug('Wordlist Importer Class() - has completed') end end diff --git a/logs/.blank b/logs/.blank new file mode 100644 index 00000000..e69de29b diff --git a/logs/jobs/.blank b/logs/jobs/.blank new file mode 100644 index 00000000..e69de29b diff --git a/models/master.rb b/models/master.rb index 7fbb2718..07e3c40b 100644 --- a/models/master.rb +++ b/models/master.rb @@ -1,21 +1,22 @@ require 'rubygems' require 'data_mapper' require 'bcrypt' +require 'rotp' # read config options = YAML.load_file('config/database.yml') # there has to be a better way to handle this shit if ENV['RACK_ENV'] == 'test' - DataMapper::Logger.new($stdout, :debug) + # DataMapper::Logger.new($stdout, :debug) DataMapper.setup(:default, options['test']) elsif ENV['RACK_ENV'] == 'development' - DataMapper::Logger.new($stdout, :debug) + # DataMapper::Logger.new($stdout, :debug) DataMapper.setup(:default, options['development']) elsif ENV['RACK_ENV'] == ('production' || 'default') DataMapper.setup(:default, options['production']) else - puts 'ERROR: You must define an evironment. ex: RACK_ENV=production' + puts 'ERROR: You must define an environment. ex: RACK_ENV=production' exit end @@ -30,6 +31,8 @@ class User property :created_at, DateTime, default: DateTime.now property :phone, String, required: false property :email, String, required: false + property :mfa, Boolean + property :auth_secret, String attr_accessor :password validates_presence_of :username @@ -45,7 +48,9 @@ def self.encrypt(pass) def self.authenticate(username, pass) user = User.first(username: username) - if user + if user.mfa + return user.username if pass == ROTP::TOTP.new(user.auth_secret).now.to_s + elsif user return user.username if BCrypt::Password.new(user.hashed_password) == pass end end @@ -69,7 +74,7 @@ def self.delete_test_user(id) user.destroy end - def self.delete_all_users() + def self.delete_all_users @users = User.all @users.destroy end @@ -117,6 +122,7 @@ class Customers property :description, String, length: 500 end +# Agents table use to record status class Agents include DataMapper::Resource property :id, Serial @@ -144,10 +150,9 @@ class Jobs # status options should be "Running", "Paused", "Completed", "Queued", "Canceled", "Ready" property :status, String, length: 100 property :queued_at, DateTime - property :targettype, String, length: 2000 + property :started_at, DateTime + property :ended_at, DateTime property :hashfile_id, Integer - property :policy_min_pass_length, Integer - property :policy_complexity_default, Boolean property :customer_id, Integer property :notify_completed, Boolean end @@ -215,7 +220,6 @@ class Settings property :smtp_pass, String property :smtp_use_tls, Boolean property :smtp_auth_type, String # Options are plain, login, cram_md5, none - property :clientmode, Boolean property :ui_themes, String, default: 'Light', required: true property :version, String, length: 5 property :chunk_size, Integer, max: 9999999999999999999, default: 500000 @@ -249,7 +253,6 @@ class HubSettings property :balance, Integer, default: 0 end - # Wordlist Class class Wordlists include DataMapper::Resource @@ -261,7 +264,6 @@ class Wordlists property :path, String, length: 2000 property :size, String, length: 100 property :checksum, String, length: 64 - end # Rules Class @@ -274,7 +276,6 @@ class Rules property :path, String, length: 2000 property :size, String, length: 100 property :checksum, String, length: 64 - end # Hashfile Class @@ -299,7 +300,7 @@ class Taskqueues # status options should be "Running", "Completed", "Queued", "Canceled", "Paused" property :queued_at, DateTime property :status, String, length: 100 - property :agent_id, String, length: 2000 + property :agent_id, Integer property :command, String, length: 4000 end diff --git a/routes/accounts.rb b/routes/accounts.rb index 9e371ecf..97af4731 100644 --- a/routes/accounts.rb +++ b/routes/accounts.rb @@ -16,12 +16,12 @@ redirect to('/accounts/create') end - if params[:password].nil? || params[:password].empty? + if (params[:password].nil? || params[:password].empty?) && !params[:mfa] flash[:error] = 'You must have a password.' redirect to('/accounts/create') end - if params[:confirm].nil? || params[:confirm].empty? + if params[:confirm].nil? || params[:confirm].empty? && !params[:mfa] flash[:error] = 'You must have a password.' redirect to('/accounts/create') end @@ -37,6 +37,13 @@ new_user.username = params[:username] new_user.password = params[:password] new_user.email = params[:email] unless params[:email].nil? || params[:email].empty? + if params[:mfa] + new_user.mfa = 't' + new_user.auth_secret = ROTP::Base32.random_base32 + else + new_user.mfa = 'f' + new_user.auth_secret = '' + end new_user.admin = 't' new_user.save end @@ -51,7 +58,8 @@ varWash(params) @user = User.first(id: params[:account_id]) - + data = Rack::Utils.escape(ROTP::TOTP.new(@user.auth_secret).provisioning_uri(@user.username)) + @otp = "https://chart.googleapis.​com/chart?chs=200x200&chld=M|0&cht=qr&chl=#{data}" haml :account_edit end @@ -77,9 +85,18 @@ user.username = params[:username] user.password = params[:password] unless params[:password].nil? || params[:password].empty? user.email = params[:email] unless params[:email].nil? || params[:email].empty? + if params[:mfa] && user.auth_secret == '' + user.mfa = 't' + user.auth_secret = ROTP::Base32.random_base32 + elsif params[:mfa] + user.mfa='t' + else + user.mfa = 'f' + user.auth_secret = '' + end user.save - - flash[:success] = 'Account successfuly updated.' + + flash[:success] = 'Account successfully updated.' redirect to('/accounts/list') end diff --git a/routes/agents.rb b/routes/agents.rb index c26c8614..d3024778 100644 --- a/routes/agents.rb +++ b/routes/agents.rb @@ -9,36 +9,36 @@ end get '/agents/:id/edit' do - @agent = Agents.first(:id => params[:id]) + @agent = Agents.first(id: params[:id]) haml :agent_edit end post '/agents/:id/edit' do - agent = Agents.first(:id => params[:id]) - agent.name = params["name"] + agent = Agents.first(id: params[:id]) + agent.name = params['name'] agent.save redirect to('/agents/list') end get '/agents/:id/delete' do - agent = Agents.first(:id => params[:id]) + agent = Agents.first(id: params[:id]) agent.destroy redirect to('/agents/list') end get '/agents/:id/authorize' do - agent = Agents.first(:id => params[:id]) - agent.status = "Authorized" + agent = Agents.first(id: params[:id]) + agent.status = 'Authorized' agent.save redirect to('/agents/list') end get '/agents/:id/deauthorize' do - agent = Agents.first(:id => params[:id]) - if agent.status == "Working" + agent = Agents.first(id: params[:id]) + if agent.status == 'Working' flash[:warning] = 'Agent was working. The active task was not stopped and you will not receive the results.' end - agent.status = "Pending" + agent.status = 'Pending' agent.save redirect to('/agents/list') end \ No newline at end of file diff --git a/routes/analytics.rb b/routes/analytics.rb index a842b441..b59da812 100644 --- a/routes/analytics.rb +++ b/routes/analytics.rb @@ -229,7 +229,7 @@ # Used for Total Unique Users and originalhashes Tables: Total @total_unique_users_count = repository(:default).adapter.select('SELECT COUNT(DISTINCT(username)) FROM hashfilehashes')[0].to_s @total_unique_originalhash_count = repository(:default).adapter.select('SELECT COUNT(DISTINCT(originalhash)) FROM hashes')[0].to_s - + # Used for Total Run Time: @total_run_time = Hashfiles.sum(:total_run_time) @@ -289,11 +289,7 @@ unless crack.nil? unless crack.length == 0 len = crack.length - if @passwords[len].nil? - @passwords[len] = 1 - else - @passwords[len] = @passwords[len].to_i + 1 - end + @passwords[len].nil? ? @passwords[len] = 1 : @passwords[len] = @passwords[len].to_i + 1 end end end @@ -308,13 +304,13 @@ return @counts.to_json end - + # callback for d3 graph displaying top 10 passwords get '/analytics/graph2' do varWash(params) - + # This could probably be replaced with: SELECT COUNT(a.hash_id) AS frq, h.plaintext FROM hashfilehashes a LEFT JOIN hashes h ON h.id = a.hash_id WHERE h.cracked = '1' GROUP BY a.hash_id ORDER BY frq DESC LIMIT 10; - + plaintext = [] if params[:customer_id] && !params[:customer_id].empty? if params[:hashfile_id] && !params[:hashfile_id].empty? @@ -325,13 +321,13 @@ else @cracked_results = repository(:default).adapter.select('SELECT h.plaintext FROM hashes h LEFT JOIN hashfilehashes a ON h.id = a.hash_id WHERE h.cracked = 1') end - + @cracked_results.each do |crack| unless crack.nil? plaintext << crack unless crack.empty? end end - + @toppasswords = [] @top10passwords = {} # get top 10 passwords @@ -342,7 +338,7 @@ @top10passwords[pass] += 1 end end - + # sort and convert to array of json objects for d3 @top10passwords = @top10passwords.sort_by { |_key, value| value }.reverse.to_h # we only need top 10 @@ -351,10 +347,10 @@ @top10passwords.each do |key, value| @toppasswords << { password: key, count: value } end - + return @toppasswords.to_json end - + # callback for d3 graph displaying top 10 base words get '/analytics/graph3' do varWash(params) @@ -374,13 +370,13 @@ plaintext << crack unless crack.empty? end end - + @topbasewords = [] @top10basewords = {} # get top 10 basewords plaintext.each do |pass| word_just_alpha = pass.gsub(/^[^a-z]*/i, '').gsub(/[^a-z]*$/i, '') - unless word_just_alpha.nil? or word_just_alpha.empty? + unless word_just_alpha.nil? || word_just_alpha.empty? if @top10basewords[word_just_alpha].nil? @top10basewords[word_just_alpha] = 1 else @@ -388,7 +384,7 @@ end end end - + # sort and convert to array of json objects for d3 @top10basewords = @top10basewords.sort_by { |_key, value| value }.reverse.to_h # we only need top 10 @@ -397,6 +393,6 @@ @top10basewords.each do |key, value| @topbasewords << { password: key, count: value } end - + return @topbasewords.to_json end diff --git a/routes/api.rb b/routes/api.rb index 8b8dd917..f05d0184 100644 --- a/routes/api.rb +++ b/routes/api.rb @@ -2,9 +2,9 @@ get '/v1/notauthorized' do { - status: 200, - type: 'Error', - msg: 'Your agent is not authorized to work with this cluster.' + status: 200, + type: 'Error', + msg: 'Your agent is not authorized to work with this cluster.' }.to_json end @@ -20,9 +20,9 @@ else status 200 { - status: 200, - type: 'Error', - msg: 'There are no items on the queue to process' + status: 200, + type: 'Error', + msg: 'There are no items on the queue to process' }.to_json end end @@ -46,17 +46,14 @@ else status 200 { - status: 200, - type: 'Error', - msg: 'Missing UUID' + status: 200, + type: 'Error', + msg: 'Missing UUID' }.to_json end # check to see if this agent is suppose to be working on something - if @assigned_task - puts @assigned_task.to_json - return @assigned_task.to_json - end + return @assigned_task.to_json if @assigned_task end # remove item from queue @@ -76,7 +73,7 @@ jdata = JSON.parse(request.body.read) agent = Agents.first(uuid: jdata['agent_uuid']) - puts "[+] updating taskqueue id: #{params[:taskqueue_id]} to status: #{jdata['status']}" + updateTaskqueueStatus(params[:taskqueue_id], jdata['status'], agent.id) end @@ -86,9 +83,7 @@ redirect to('/v1/notauthorized') unless agentAuthorized(request.cookies['agent_uuid']) jdata = JSON.parse(request.body.read) - puts jdata - puts "=======================================" - puts "[+] updating jobtask id: #{params['jobtask_id']} to status: #{jdata['status']}" + updateJobTaskStatus(jdata['jobtask_id'], jdata['status']) end @@ -142,8 +137,6 @@ # Execute our compression `#{cmd}` # Serve File - #send_file wordlist.path, :type => 'application/octet-stream', :filename => "#{wordlist.path.split('/')[-1]}.gz" - #send_file wordlist.path, :type => 'application/octet-stream', :filename => "control/tmp/#{wordlist_orig}.gz" send_file "control/tmp/#{wordlist_orig}.gz", :type => 'application/octet-stream', :filename => "#{wordlist_orig}.gz" end @@ -169,9 +162,9 @@ redirect to('/v1/notauthorized') unless agentAuthorized(request.cookies['agent_uuid']) updateSmartWordlist data = { - status: 200, - type: 'message', - msg: 'OK' + status: 200, + type: 'message', + msg: 'OK' } return data.to_json end @@ -202,7 +195,6 @@ # is agent authorized redirect to('/v1/notauthorized') unless agentAuthorized(request.cookies['agent_uuid']) - puts '===== creating hash_file =======' jobtask_id = params[:jobtask_id] hashfile_id = params[:hashfile_id] @@ -233,8 +225,6 @@ f.close end - puts '===== Hash_File Created ======' - send_file hash_file end @@ -245,7 +235,7 @@ redirect to('/v1/notauthorized') unless agentAuthorized(request.cookies['agent_uuid']) tmpfile = "control/tmp/#{rand.to_s[2..2048]}.txt" - puts "[+] Agent uploaded crack file. Saving to: #{tmpfile}" + # puts "[+] Agent uploaded crack file. Saving to: #{tmpfile}" File.open(tmpfile, 'wb') do |f| f.write(params[:file][:tempfile].read) end @@ -259,7 +249,6 @@ # is agent authorized redirect to('/v1/notauthorized') unless agentAuthorized(request.cookies['agent_uuid']) - puts 'parsing uploaded hcoutput hash' return request.body.read end @@ -279,7 +268,7 @@ payload = JSON.parse(request.body.read) # get agent data from db if available - @agent = Agents.first(:uuid => params[:uuid]) + @agent = Agents.first(uuid: params[:uuid]) if !@agent.nil? if @agent.status == 'Authorized' # if agent is set to authorized, continue to authorization process @@ -314,9 +303,14 @@ # update db with the agents hashcat status if payload['hc_status'] - puts payload['hc_status'] + #puts payload['hc_status'] @agent.status = payload['agent_status'] @agent.hc_status = payload['hc_status'].to_json + payload['hc_status'].each do |item| + if item.to_s =~ /Speed Dev #/ + @agent.benchmark = item[1].split(' ')[0].to_s + ' ' + item[1].split(' ')[1].to_s + end + end @agent.save end @@ -331,31 +325,12 @@ @agent.status = payload['agent_status'] @agent.save { - status: 200, - type: 'message', - msg: 'OK' + status: 200, + type: 'message', + msg: 'OK' }.to_json end - # # are agent and server in sync - # if agenttask.to_i == taskqueue.id - # # update heartbeat and save hc_output for ui - # @agent.heartbeat = Time.now - # @agent.save - # { - # status: 200, - # type: 'message', - # msg: 'OK' - # }.to_json - # else - # # server and agent are out of sync, tell agent to stop working - # { - # status: 200, - # type: 'message', - # msg: 'Canceled' - # }.to_json - # end - elsif payload['agent_status'] == 'Idle' # assign work to agent @@ -380,7 +355,6 @@ }.to_json else # update agent heartbeat but do nothing for now - p '########### I have nothing for you to do now ###########' @agent.heartbeat = Time.now @agent.status = payload['agent_status'] @agent.hc_status = '' @@ -413,13 +387,13 @@ if params[:uuid].nil? status 200 { - status: 200, - type: 'Error', - msg: 'Missing UUID' + status: 200, + type: 'Error', + msg: 'Missing UUID' }.to_json else #TODO SECURITY - make sure this param is a formated as a valid uuid - agent = Agents.first(:uuid => params[:uuid]) + agent = Agents.first(uuid: params[:uuid]) end if !agent.nil? @@ -457,9 +431,9 @@ redirect to('/v1/notauthorized') unless agentAuthorized(request.cookies['agent_uuid']) payload = JSON.parse(request.body.read) - puts payload + #puts payload - agent = Agents.first(:uuid => params[:uuid]) + agent = Agents.first(uuid: params[:uuid]) agent.cpu_count = payload['cpu_count'].to_i agent.gpu_count = payload['gpu_count'].to_i agent.benchmark = payload['benchmark'].to_s diff --git a/routes/customers.rb b/routes/customers.rb index 0e621d63..fb6dbbf7 100644 --- a/routes/customers.rb +++ b/routes/customers.rb @@ -249,5 +249,4 @@ url += "?job_id=#{params[:job_id]}" url += '&edit=1' if params[:edit] redirect to(url) - end \ No newline at end of file diff --git a/routes/downloads.rb b/routes/downloads.rb index 844a5b07..6fa995e7 100644 --- a/routes/downloads.rb +++ b/routes/downloads.rb @@ -1,105 +1,111 @@ # encoding: utf-8 get '/download' do varWash(params) - - if params[:customer_id] && !params[:customer_id].empty? - if params[:hashfile_id] && !params[:hashfile_id].nil? - # Until we can figure out JOIN statments, we're going to have to hack it + if params[:graph] && !params[:graph].empty? + # What kind of graph data are we dealing with here + if params[:graph] == '1' # Total Hashes Cracked + # Do Something + elsif params[:graph] == '2' # Composition Breakdown + # Do Something + elsif params[:graph] == '3' # Analysis Detail @filecontents = Set.new - Hashfilehashes.all(fields: [:id], hashfile_id: params[:hashfile_id]).each do |entry| - if params[:type] == 'cracked' and Hashes.first(fields: [:cracked], id: entry.hash_id).cracked - if entry.username.nil? # no username - line = '' + file_name = 'error.txt' + if params[:customer_id] && !params[:customer_id].empty? + if params[:hashfile_id] && !params[:hashfile_id].nil? + # Customer and Hashfile + if params[:type] == 'cracked' + @results = repository(:default).adapter.select('SELECT a.username, h.originalhash, h.plaintext FROM hashes h LEFT JOIN hashfilehashes a ON h.id = a.hash_id LEFT JOIN hashfiles f on a.hashfile_id = f.id WHERE (f.customer_id = ? AND a.hashfile_id = ? and h.cracked = 1)', params[:customer_id],params[:hashfile_id]) + file_name = "found_#{params[:customer_id]}_#{params[:hashfile_id]}.txt" + elsif params[:type] == 'uncracked' + @results = repository(:default).adapter.select('SELECT a.username, h.originalhash FROM hashes h LEFT JOIN hashfilehashes a ON h.id = a.hash_id LEFT JOIN hashfiles f on a.hashfile_id = f.id WHERE (f.customer_id = ? AND a.hashfile_id = ? and h.cracked = 0)', params[:customer_id],params[:hashfile_id]) + file_name = "left_#{params[:customer_id]}_#{params[:hashfile_id]}.txt" else - line = entry.username + ':' + # Do Something + file_name = 'error.txt' end - line = line + Hashes.first(fields: [:originalhash], id: entry.hash_id).originalhash - line = line + ':' + Hashes.first(fields: [:plaintext], id: entry.hash_id, cracked: 1).plaintext - @filecontents.add(line) - elsif params[:type] == 'uncracked' and not Hashes.first(fields: [:cracked], id: entry.hash_id).cracked - if entry.username.nil? # no username - line = '' + else + # Just Customer + if params[:type] == 'cracked' + @results = repository(:default).adapter.select('SELECT a.username, h.originalhash, h.plaintext FROM hashes h LEFT JOIN hashfilehashes a ON h.id = a.hash_id LEFT JOIN hashfiles f on a.hashfile_id = f.id WHERE (f.customer_id = ? and h.cracked = 1)', params[:customer_id]) + file_name = "found_#{params[:customer_id]}.txt" + elsif params[:type] == 'uncracked' + @results = repository(:default).adapter.select('SELECT a.username, h.originalhash FROM hashes h LEFT JOIN hashfilehashes a ON h.id = a.hash_id LEFT JOIN hashfiles f on a.hashfile_id = f.id WHERE (f.customer_id = ? and h.cracked = 0)', params[:customer_id]) + file_name = "left_#{params[:customer_id]}.txt" else - line = entry.username + ':' + # Do Something + file_name = 'error.txt' end - line = line + Hashes.first(fields: [:originalhash], id: entry.hash_id).originalhash - @filecontents.add(line) + end + else + # All + if params[:type] == 'cracked' + @results = repository(:default).adapter.select('SELECT a.username, h.originalhash, h.plaintext FROM hashes h LEFT JOIN hashfilehashes a ON h.id = a.hash_id LEFT JOIN hashfiles f on a.hashfile_id = f.id WHERE (h.cracked = 1)') + file_name = 'found_all.txt' + elsif params[:type] == 'uncracked' + @results = repository(:default).adapter.select('SELECT a.username, h.originalhash FROM hashes h LEFT JOIN hashfilehashes a ON h.id = a.hash_id LEFT JOIN hashfiles f on a.hashfile_id = f.id WHERE (h.cracked = 0)') + file_name = 'left_all.txt' + else + # Do Something + file_name = 'error.txt' end end - else - @filecontents = Set.new - @hashfiles_ids = Hashfiles.all(fields: [:id], customer_id: params[:customer_id]).each do |hashfile| - Hashfilehashes.all(fields: [:id], hashfile_id: hashfile.id).each do |entry| - if params[:type] == 'cracked' and Hashes.first(fields: [:cracked], id: entry.hash_id).cracked - if entry.username.nil? # no username - line = '' - else - line = entry.username + ':' - end - line = line + Hashes.first(fields: [:originalhash], id: entry.hash_id).originalhash - line = line + ':' + Hashes.first(fields: [:plaintext], id: entry.hash_id, cracked: 1).plaintext - @filecontents.add(line) - elsif params[:type] == 'uncracked' and not Hashes.first(fields: [:cracked], id: entry.hash_id).cracked - if entry.username.nil? # no username - line = '' - else - line = entry.username + ':' - end - line = line + Hashes.first(fields: [:originalhash], id: entry.hash_id).originalhash - @filecontents.add(line) - end - end + + @results.each do |entry| + entry.username.nil? ? line = '' : line = entry.username.to_s + ':' + line += entry.originalhash.to_s + line += ':' + entry.plaintext.to_s if params[:type] == 'cracked' + @filecontents.add(line) end - end - else - @filecontents = Set.new - @hashfiles_ids = Hashfiles.all(fields: [:id]).each do |hashfile| - Hashfilehashes.all(fields: [:id], hashfile_id: hashfile.id).each do |entry| - if params[:type] == 'cracked' and Hashes.first(fields: [:cracked], id: entry.hash_id).cracked - if entry.username.nil? # no username - line = '' - else - line = entry.username + ':' - end - line = line + Hashes.first(fields: [:originalhash], id: entry.hash_id).originalhash - line = line + ':' + Hashes.first(fields: [:plaintext], id: entry.hash_id, cracked: 1).plaintext - @filecontents.add(line) - elsif params[:type] == 'uncracked' and not Hashes.first(fields: [:cracked], id: entry.hash_id).cracked - if entry.username.nil? # no username - line = '' - else - line = entry.username + ':' + + file_name = 'control/tmp/' + file_name + + File.open(file_name, 'w') do |f| + @filecontents.each do |entry| + f.puts entry + end + end + + send_file file_name, filename: file_name, type: 'Application/octet-stream' + elsif params[:graph] == '4' # Password Count by Length + # Do Something + elsif params[:graph] == '5' # Top 10 Passwords + # Do Something + elsif params[:graph] == '6' # Accounts With Weak Passwords + file_name = 'error.txt' + if params[:customer_id] && !params[:customer_id].empty? + if params[:hashfile_id] && !params[:hashfile_id].nil? + @complexity_hashes = repository(:default).adapter.select('SELECT a.username, h.plaintext FROM hashes h LEFT JOIN hashfilehashes a ON h.id = a.hash_id WHERE (a.hashfile_id = ? AND h.cracked = 1)', params[:hashfile_id]) + file_name = "Weak_Accounts_#{params[:customer_id]}_#{params[:hashfile_id]}.csv" + else + @complexity_hashes = repository(:default).adapter.select('SELECT a.username, h.plaintext FROM hashes h LEFT JOIN hashfilehashes a on h.id = a.hash_id LEFT JOIN hashfiles f on a.hashfile_id = f.id WHERE (f.customer_id = ? AND h.cracked = 1)', params[:customer_id]) + file_name = "Weak_Accounts_#{params[:customer_id]}.csv" + end + else + @complexity_hashes = repository(:default).adapter.select('SELECT a.username, h.plaintext FROM hashes h LEFT JOIN hashfilehashes a on h.id = a.hash_id LEFT JOIN hashfiles f on a.hashfile_id = f.id WHERE (h.cracked = 1)') + file_name = "Weak_Accounts_all.csv" + end + + file_name = 'control/tmp/' + file_name + + File.open(file_name, 'w') do |f| + line = 'username,password' + f.puts line + @complexity_hashes.each do |entry| + unless entry.plaintext.to_s =~ /^(?:(?=.*[a-z])(?:(?=.*[A-Z])(?=.*[\d\W])|(?=.*\W)(?=.*\d))|(?=.*\W)(?=.*[A-Z])(?=.*\d)).{8,}$/ + line = entry.username.to_s + ',' + entry.plaintext.to_s + f.puts line end - line = line + Hashes.first(fields: [:originalhash], id: entry.hash_id).originalhash - @filecontents.add(line) end end - end - end - # Write temp output file - if params[:customer_id] && !params[:customer_id].empty? - if params[:hashfile_id] && !params[:hashfile_id].nil? - file_name = "found_#{params[:customer_id]}_#{params[:hashfile_id]}.txt" if params[:type] == 'cracked' - file_name = "left_#{params[:customer_id]}_#{params[:hashfile_id]}.txt" if params[:type] == 'uncracked' - else - file_name = "found_#{params[:customer_id]}.txt" if params[:type] == 'cracked' - file_name = "left_#{params[:customer_id]}.txt" if params[:type] == 'uncracked' - end - else - file_name = 'found_all.txt' if params[:type] == 'cracked' - file_name = 'left_all.txt' if params[:type] == 'uncracked' - end - - file_name = 'control/outfiles/' + file_name + send_file file_name, filename: file_name, type: 'Application/octet-stream' - File.open(file_name, 'w') do |f| - @filecontents.each do |entry| - f.puts entry + elsif params[:graph] == '7' # Top 20 Password/Hashes Shared by Users + # Do something + else + # DO Something end end - - send_file file_name, filename: file_name, type: 'Application/octet-stream' end diff --git a/routes/hashfiles.rb b/routes/hashfiles.rb index 93233348..95993bf7 100644 --- a/routes/hashfiles.rb +++ b/routes/hashfiles.rb @@ -6,8 +6,6 @@ @cracked_status = {} @local_cracked_cnt = {} @local_uncracked_cnt = {} -# @hub_download_cnt = {} -# @hub_upload_cnt = {} @hashfiles.each do |hashfile| hashfile_cracked_count = repository(:default).adapter.select('SELECT COUNT(h.originalhash) FROM hashes h LEFT JOIN hashfilehashes a ON h.id = a.hash_id WHERE (a.hashfile_id = ? AND h.cracked = 1)', hashfile.id)[0].to_s @@ -15,73 +13,6 @@ @local_cracked_cnt[hashfile.id] = hashfile_cracked_count.to_s @local_uncracked_cnt[hashfile.id] = hashfile_total_count.to_i - hashfile_cracked_count.to_i @cracked_status[hashfile.id] = hashfile_cracked_count.to_s + '/' + hashfile_total_count.to_s - # hub upload cnt -# if @hub_settings.status == 'registered' -# @hashfile_hashes = Hashfilehashes.all(hashfile_id: hashfile.id) -# upload_cnt = 0 -# download_cnt = 0 -# @hash_array = [] -# @hashfile_hashes.each do |entry| -# # p 'HASH ID: ' + entry.hash_id.to_s -# # Build list of locally cracked hashes per hashfile -# local_cracked = Hashes.all(id: entry.hash_id, cracked: '1') -# unless local_cracked.nil? -# local_cracked.each do |hash| -# element = {} -# element['ciphertext'] = hash.originalhash -# element['hashtype'] = hash.hashtype.to_s -# # p 'ELEMENT: ' + element.to_s -# @hash_array.push(element) -# end -# end -# end -# # p 'HASH_ARRAY: ' + @hash_array.to_s -# # Submit query and record how many the hub doesnt have -# hub_response = Hub.hashSearch(@hash_array) -# hub_response = JSON.parse(hub_response) -# if hub_response['status'] == '200' -# @hub_hash_results = hub_response['hashes'] -# @hub_hash_results.each do |entry| -# if entry['cracked'] == '0' -# upload_cnt += 1 -# end -# end -# end - -# @hash_array = [] -# @hashfile_hashes.each do |entry| -# # Build list of locally uncracked per hashfile -# local_uncracked = Hashes.all(id: entry.hash_id, cracked: '0') -# unless local_uncracked.nil? || local_uncracked.empty? -# local_uncracked.each do |hash| -# element = {} -# element['ciphertext'] = hash.originalhash -# element['hashtype'] = hash.hashtype.to_s -# @hash_array.push(element) -# end -# end -# end - -# # Submit query and record how many the hub doesn't have -# hub_response = Hub.hashSearch(@hash_array) -# hub_response = JSON.parse(hub_response) -# if hub_response['status'] == '200' -# @hub_hash_results = hub_response['hashes'] -# @hub_hash_results.each do |entry| -# if entry['cracked'] == '1' -# # p 'ENTRY' + entry.to_s -# download_cnt += 1 -# end -# end -# end -# else -# upload_cnt = 0 -# download_cnt = 0 -# end - - # hub download cnt -# @hub_download_cnt[hashfile.id] = download_cnt -# @hub_upload_cnt[hashfile.id] = upload_cnt end haml :hashfile_list @@ -96,8 +27,7 @@ @hashfile = Hashfiles.first(id: params[:hashfile_id]) @hashfile.destroy unless @hashfile.nil? - flash[:success] = 'Successfuly removed hashfile.' + flash[:success] = 'Successfully removed hashfile.' redirect to('/hashfiles/list') -end - +end \ No newline at end of file diff --git a/routes/hub.rb b/routes/hub.rb index e03796f0..4eadf993 100644 --- a/routes/hub.rb +++ b/routes/hub.rb @@ -10,8 +10,8 @@ class Hub # Provision new config if none exists. unless File.exist?('config/hub_config.json') hub_config = { - :host => 'hub.hashview.io', - :port => '443', + host: 'hub.hashview.io', + port: '443' } File.open('config/hub_config.json', 'w') do |f| f.write(JSON.pretty_generate(hub_config)) @@ -45,13 +45,12 @@ def self.get(url) hub_settings = HubSettings.first response = RestClient::Request.execute( - :method => :get, - :url => url, - :cookies => {:uuid => hub_settings.uuid, :auth_key => hub_settings.auth_key}, - :timeout => nil, - :open_timeout => nil, - #:verify_ssl => false - :verify_ssl => true + method: :get, + url: url, + cookies: {uuid: hub_settings.uuid, auth_key: hub_settings.auth_key}, + timeout: nil, + open_timeout: nil, + verify_ssl: true ) p 'response: ' + response.body.to_s return response.body @@ -68,15 +67,14 @@ def self.post(url, payload) hub_settings = HubSettings.first p 'cookie: ' + hub_settings.uuid.to_s + ' ' + hub_settings.auth_key.to_s response = RestClient::Request.execute( - :method => :post, - :url => url, - :payload => payload.to_json, - :headers => {:accept => :json}, - :cookies => {:uuid => hub_settings.uuid, :auth_key => hub_settings.auth_key}, - :timeout => nil, - :open_timeout => nil, - #:verify_ssl => false #TODO VALIDATE - :verify_ssl => true + method: :post, + url: url, + payload: payload.to_json, + headers: {accept: :json}, + cookies: {uuid: hub_settings.uuid, auth_key: hub_settings.auth_key}, + timeout: nil, + open_timeout: nil, + verify_ssl: true ) p 'response: ' + response.body.to_s return response.body @@ -258,7 +256,7 @@ def self.statusAuth() entry.plaintext = element['plaintext'] entry.cracked = '1' entry.save - hub_count = hub_count + 1 + hub_count += 1 end end @@ -337,7 +335,7 @@ def self.statusAuth() if hub_response['status'] == '200' @hashes = hub_response['hashes'] @hashes.each do |element| - cnt = cnt + 1 + cnt += 1 # Add to local db entry = Hashes.first(hashtype: element['hashtype'], originalhash: element['ciphertext']) entry.lastupdated = Time.now diff --git a/routes/jobs.rb b/routes/jobs.rb index 2fea06bc..224beadd 100644 --- a/routes/jobs.rb +++ b/routes/jobs.rb @@ -100,7 +100,7 @@ flash[:error] = 'Customer ' + params[:name] + ' already exists.' redirect to('/jobs/create') end - + customer = Customers.new customer.name = params[:cust_name] customer.description = params[:cust_desc] @@ -136,7 +136,7 @@ @hashfiles = Hashfiles.all(customer_id: params[:customer_id]) @customer = Customers.first(id: params[:customer_id]) - @cracked_status = Hash.new + @cracked_status = {} @hashfiles.each do |hashfile| hashfile_cracked_count = repository(:default).adapter.select('SELECT COUNT(h.originalhash) FROM hashes h LEFT JOIN hashfilehashes a ON h.id = a.hash_id WHERE (a.hashfile_id = ? AND h.cracked = 1)', hashfile.id)[0].to_s hashfile_total_count = repository(:default).adapter.select('SELECT COUNT(h.originalhash) FROM hashes h LEFT JOIN hashfilehashes a ON h.id = a.hash_id WHERE a.hashfile_id = ?', hashfile.id)[0].to_s @@ -165,7 +165,7 @@ end url = "/jobs/assign_tasks?job_id=#{params[:job_id]}&customer_id=#{params[:customer_id]}&hashid=#{params[:hash_file]}" - url = url + '&edit=1' if params[:edit] + url += '&edit=1' if params[:edit] redirect to(url) end @@ -228,9 +228,9 @@ @temp_jobtasks.each_with_index do |task_id, index| if @temp_jobtasks[index] == params[:task_id].to_i - @new_jobtasks << @temp_jobtasks[index+1] + @new_jobtasks << @temp_jobtasks[index + 1] @new_jobtasks << params[:task_id].to_i - @temp_jobtasks.delete_at(index+1) + @temp_jobtasks.delete_at(index + 1) else @new_jobtasks << @temp_jobtasks[index].to_i end @@ -289,17 +289,12 @@ job.status = 'Ready' job.save - if params[:edit].to_s == '1' - flash[:success] = 'Job updated.' - else - flash[:success] = 'Job created.' - end + params[:edit].to_s == '1' ? flash[:success] = 'Job updated.' : flash[:success] = 'Job created.' redirect to('/jobs/list') end post '/jobs/complete' do - if !params[:tasks] || params[:tasks].nil? if !params[:edit] || params[:edit].nil? flash[:error] = 'You must assign at least one task' @@ -317,8 +312,7 @@ @tasks = Tasks.all # prevent adding duplicate tasks to a job - #count = Hash.new 0 - #params[:tasks] = params[:task].uniq + puts params if params[:tasks] # make sure the task that the user is adding is not already assigned to the job @@ -425,6 +419,7 @@ @jobtasks = Jobtasks.all(job_id: params[:id]) @job.status = 'Canceled' + @job.ended_at = Time.now @job.save @jobtasks.each do |task| @@ -558,7 +553,6 @@ results_entry['id'] = hash.id # TODO # Adding usernames to this result would be great - #results_entry['username'] = entry.username results_entry['ciphertext'] = element['ciphertext'] results_entry['hub_hash_id'] = element['hash_id'] results_entry['hashtype'] = element['hashtype'] diff --git a/routes/login.rb b/routes/login.rb index 1d69d3c2..8c38294b 100644 --- a/routes/login.rb +++ b/routes/login.rb @@ -8,7 +8,7 @@ haml :login end end - + get '/logout' do varWash(params) if session[:session_id] @@ -17,24 +17,24 @@ end redirect to('/') end - + post '/login' do varWash(params) if !params[:username] || params[:username].nil? flash[:error] = 'You must supply a username.' redirect to('/login') end - + if !params[:password] || params[:password].nil? flash[:error] = 'You must supply a password.' redirect to('/login') end - + @user = User.first(username: params[:username]) if @user usern = User.authenticate(params['username'], params['password']) - + # if usern and session[:session_id] unless usern.nil? # only delete session if one exists @@ -57,11 +57,11 @@ redirect to('/login') end end - + get '/protected' do return 'This is a protected page, you must be logged in.' end - + get '/not_authorized' do return 'You are not authorized.' end diff --git a/routes/main.rb b/routes/main.rb index 83af0eea..ebefa629 100644 --- a/routes/main.rb +++ b/routes/main.rb @@ -13,51 +13,107 @@ get '/home' do - @results = `ps awwux | grep -i Hashcat | egrep -v "(grep|screen|SCREEN|resque|^$)"` - @jobs = Jobs.all(:order => [:queued_at.asc]) + + @jobs = Jobs.all(order: [:queued_at.asc]) @jobtasks = Jobtasks.all @tasks = Tasks.all @taskqueues = Taskqueues.all @agents = Agents.all - - # not used anymore - # @recentlycracked = repository(:default).adapter.select('SELECT CONCAT(timestampdiff(minute, h.lastupdated, NOW()) ) AS time_period, h.plaintext, a.username FROM hashes h LEFT JOIN hashfilehashes a ON h.id = a.hash_id WHERE (h.cracked = 1) ORDER BY h.lastupdated DESC LIMIT 10') + @time_now = Time.now @customers = Customers.all @active_jobs = Jobs.all(fields: [:id, :status], status: 'Running') - # nvidia works without sudo: - @gpustatus = `nvidia-settings -q \"GPUCoreTemp\" | grep Attribute | grep -v gpu | awk '{print $3,$4}'` - if @gpustatus.empty? - @gpustatus = `lspci | grep "VGA compatible controller" | cut -d: -f3 | sed 's/\(rev a1\)//g'` - end - @gpustatus = @gpustatus.split("\n") - @gpustat = [] - @gpustatus.each do |line| - unless line.chomp.empty? - line = line.delete('.') - @gpustat << line - end - end + # JumboTron Display + # Building out an array of hashes for the jumbotron display + @jumbotron = [] + @jobs.each do |job| + element = {} + if job.status == 'Running' || job.status == 'importing' + #@Customers = Customers.first(id: job.customer_id).each do |customer| + @customers.each do |customer| + if customer.id == job.customer_id.to_i + element['customer_name'] = customer.name + end + end - @jobs.each do |j| - if j.status == 'Running' - # gather info for statistics + element['job_name'] = job.name @hash_ids = Array.new - Hashfilehashes.all(fields: [:hash_id], hashfile_id: j.hashfile_id).each do |entry| + Hashfilehashes.all(fields: [:hash_id], hashfile_id: job.hashfile_id).each do |entry| @hash_ids.push(entry.hash_id) end - @alltargets = Hashes.count(id: @hash_ids) - @crackedtargets = Hashes.count(id: @hash_ids, cracked: 1) + hashfile_total = Hashes.count(id: @hash_ids) + hashfile_cracked = Hashes.count(id: @hash_ids, cracked: 1) + + element['hashfile_cracked'] = hashfile_cracked + element['hashfile_total'] = hashfile_total + element['hashfile_progress'] = (hashfile_cracked.to_f / hashfile_total.to_f) * 100 + element['job_starttime'] = job.started_at + + time_now = Time.now + if time_now.to_time - job.started_at.to_time > 86400 + element['job_runtime'] = ((time_now.to_time - job.started_at.to_time).to_f / 86400).round(2).to_s + ' Days' + elsif time_now.to_time - job.started_at.to_time > 3600 + element['job_runtime'] = ((time_now.to_time - job.started_at.to_time).to_f / 3600).round(2).to_s + ' Hours' + elsif time_now.to_time - job.started_at.to_time > 60 + element['job_runtime'] = ((time_now.to_time - job.started_at.to_time).to_f / 60).round(2).to_s + ' Minutes' + elsif time_now.to_time - job.started_at.to_time >= 0 + element['job_runtime'] = (time_now.to_time - job.started_at.to_time).to_f.round(2).to_s + ' Seconds' + else + element['job_runtime'] = 'Im ready coach, just send me in.' + end + + total_speed = 0 + Taskqueues.all(job_id: job.id, status: 'Running').each do |queued_task| + agent = Agents.first(id: queued_task.agent_id) + + if agent.benchmark + # Normalizing Benchmark Speeds + if agent.benchmark.include? ' H/s' + speed = agent.benchmark.split[0].to_f + total_speed += speed + elsif agent.benchmark.include? 'kH/s' + speed = agent.benchmark.split[0].to_f + speed *= 1000 + total_speed += speed + elsif agent.benchmark.include? 'MH/s' + speed = agent.benchmark.split[0].to_f + speed *= 1000000 + total_speed += speed + elsif agent.benchmark.include? 'GH/s' + speed = agent.benchmark.split[0].to_f + speed *= 1000000000 + total_speed += speed + elsif agent.benchmark.include? 'TH/s' + speed = agent.benchmark.split[0].to_f + speed *= 1000000000000 + total_speed += speed + else + total_speed += 0 + end + end + end + + # Convert to Human Readable Format + if total_speed > 1000000000000 + element['job_crackrate'] = (total_speed.to_f / 1000000000000).round(2).to_s + ' TH/s' + elsif total_speed > 1000000000 + element['job_crackrate'] = (total_speed.to_f / 1000000000).round(2).to_s + ' GH/s' + elsif total_speed > 1000000 + element['job_crackrate'] = (total_speed.to_f / 1000000).round(2).to_s + ' MH/s' + elsif total_speed > 1000 + element['job_crackrate'] = (total_speed.to_f / 1000).round(2).to_s + ' kH/s' + elsif total_speed >= 0 + element['job_crackrate'] = total_speed.to_f.round(2).to_s + ' H/s' + else + element['job_crackrate'] = '0 H/s' + end - @progress = (@crackedtargets.to_f / @alltargets.to_f) * 100 - # parse a hashcat status file - @hashcat_status = hashcatParser('control/outfiles/hcoutput_' + j.id.to_s + '.txt') + @jumbotron.push(element) end end haml :home -end - +end \ No newline at end of file diff --git a/routes/rules.rb b/routes/rules.rb index 256fc01c..3e8fd117 100644 --- a/routes/rules.rb +++ b/routes/rules.rb @@ -46,10 +46,10 @@ redirect to('/rules/list') end - # Create DB entry first so that our background job doesnt accidentally pull it in. + # Create DB entry first so that our background job doesn't accidentally pull it in. rules_file = Rules.new rules_file.name = name - rules_file.lastupdated = Time.now() + rules_file.lastupdated = Time.now # temporarily save file for testing rules_file_path_name = "control/rules/#{name}.rule" @@ -59,7 +59,9 @@ # Parse uploaded file into an array rules_array = params[:new_rules].to_s.gsub(/\x0d\x0a/, "\x0a") # in theory we shouldnt run into any false positives? - File.open(rules_file_path_name, 'w') { |f| f.puts(rules_array) } + File.open(rules_file_path_name, 'w') do |f| + f.puts(rules_array) + end results = Rules.first(name: name) Resque.enqueue(FileChecksum('rules', results.id)) @@ -121,7 +123,9 @@ File.open(rules_file.path, 'w') { |f| f.puts(@rules) } # update file size - size = File.foreach(rules_file.path).inject(0) { |c| c + 1} + size = File.foreach(rules_file.path).inject(0) do |c| + c + 1 + end rules_file.size = size rules_file.checksum = Digest::SHA2.hexdigest(File.read(rules_file.path)) rules_file.save diff --git a/routes/settings.rb b/routes/settings.rb index 54a099f8..2e1005eb 100644 --- a/routes/settings.rb +++ b/routes/settings.rb @@ -50,10 +50,10 @@ # get hcbinpath (stored in config file vs db) @hc_binpath = JSON.parse(File.read('config/agent_config.json'))['hc_binary_path'] - + haml :global_settings end - + post '/settings' do if params[:form_id] == '1' # Hashcat Settings @@ -200,7 +200,7 @@ get '/test/email' do account = User.first(username: getUsername) - if account.email.nil? or account.email.empty? + if account.email.nil? || account.email.empty? flash[:error] = 'Current logged on user has no email address associated.' redirect to('/settings') end @@ -210,9 +210,5 @@ end flash[:success] = 'Email sent.' - redirect to('/settings') -end - - - +end \ No newline at end of file diff --git a/routes/tasks.rb b/routes/tasks.rb index d738e01d..01b24dd4 100644 --- a/routes/tasks.rb +++ b/routes/tasks.rb @@ -2,10 +2,11 @@ get '/tasks/list' do @tasks = Tasks.all @wordlists = Wordlists.all + @rules = Rules.all haml :task_list end - + get '/tasks/delete/:id' do varWash(params) @@ -20,7 +21,7 @@ redirect to('/tasks/list') end - + get '/tasks/edit/:id' do varWash(params) @task = Tasks.first(id: params[:id]) @@ -38,18 +39,12 @@ @combinator_right_rule = $1 end end - + @rules = Rules.all - #@rules = [] - # list wordlists that can be used - #Dir.foreach('control/rules/') do |item| - # next if item == '.' || item == '..' - # @rules << item - #end haml :task_edit end - + post '/tasks/edit/:id' do varWash(params) if !params[:name] || params[:name].nil? @@ -71,18 +66,18 @@ if wordlist_list == '' wordlist_list = wordlist_check.id.to_s + ',' else - wordlist_list = wordlist_list + wordlist_check.id.to_s + wordlist_list += wordlist_check.id.to_s end - wordlist_count = wordlist_count + 1 + wordlist_count += 1 end end end - + if wordlist_count != 2 flash[:error] = 'You must specify at exactly 2 wordlists.' redirect to("/tasks/edit/#{params[:id]}") end - + if params[:combinator_left_rule] && !params[:combinator_left_rule].empty? && params[:combinator_right_rule] && !params[:combinator_right_rule].empty? rule_list = '--rule-left=' + params[:combinator_left_rule] + ' --rule-right=' + params[:combinator_right_rule] elsif params[:combinator_left_rule] && !params[:combinator_left_rule].empty? @@ -93,12 +88,12 @@ rule_list = '' end end - + task = Tasks.first(id: params[:id]) task.name = params[:name] - + task.hc_attackmode = params[:attackmode] - + if params[:attackmode] == 'dictionary' task.wl_id = wordlist.id task.hc_rule = params[:rule].to_i @@ -108,27 +103,20 @@ task.hc_rule = 'NULL' task.hc_mask = params[:mask] elsif params[:attackmode] == 'combinator' - task.wl_id = wordlist_list + task.wl_id = wordlist_list task.hc_rule = rule_list task.hc_mask = 'NULL' end task.save - + redirect to('/tasks/list') end - + get '/tasks/create' do varWash(params) @hc_settings = HashcatSettings.first @rules = Rules.all - #@rules = [] - # list wordlists that can be used - #Dir.foreach('control/rules/') do |item| - # next if item == '.' || item == '..' - # @rules << item - #end - @wordlists = Wordlists.all haml :task_edit @@ -174,9 +162,9 @@ if wordlist_list == '' wordlist_list = wordlist_check.id.to_s + ',' else - wordlist_list = wordlist_list + wordlist_check.id.to_s + wordlist_list += wordlist_check.id.to_s end - wordlist_count = wordlist_count + 1 + wordlist_count += 1 end end end @@ -214,10 +202,8 @@ # generate keyspace of new task and save to db task.keyspace = getKeyspace(task) - task.save flash[:success] = "Task #{task.name} successfully created." - redirect to('/tasks/list') end diff --git a/routes/wordlists.rb b/routes/wordlists.rb index ea1c256c..ce2f2aec 100644 --- a/routes/wordlists.rb +++ b/routes/wordlists.rb @@ -1,5 +1,5 @@ # encoding: utf-8 -# require_relative '../jobs/init' # this shouldn't be needed? + get '/wordlists/list' do @wordlists = Wordlists.all @@ -20,7 +20,7 @@ else # check if wordlist is in use @task_list = Tasks.all(wl_id: @wordlist.id) - if !@task_list.empty? + unless @task_list.empty? flash[:error] = 'This word list is associated with a task, it cannot be deleted.' redirect to('/wordlists/list') end @@ -31,8 +31,6 @@ # delete from db @wordlist.destroy - # Update our magic wordlist - # Resque.enqueue(MagicWordlist) end redirect to('/wordlists/list') end @@ -68,8 +66,8 @@ wordlist.save File.open(file_name, 'wb') { |f| f.write(params[:file][:tempfile].read) } + Resque.enqueue(WordlistImporter) Resque.enqueue(WordlistChecksum) - # Update our magic wordlist - # Resque.enqueue(MagicWordlist) + redirect to('/wordlists/list') end diff --git a/views/account_edit.haml b/views/account_edit.haml index a3276019..20c3ca96 100644 --- a/views/account_edit.haml +++ b/views/account_edit.haml @@ -1,6 +1,5 @@ !!! %html - /https://jsfiddle.net/vigneshmoha/bbxMe/2/ %body .span15 %br @@ -13,45 +12,60 @@ .row .col-md-10 - if @user - %form{:class => "form-horizontal", :action => "/accounts/save", :method => "post"} + %form{class: 'form-horizontal', action: '/accounts/save', method: 'post'} .form-group - %label.control-label.col-xs-2{:for => ""} Username + %label.control-label.col-xs-2{for: ''} Username .col-md-6 - %input{:type => "textbox", :class => "form-control", :name => "username", :id => "username", :value => @user.username} + %input{type: 'textbox', class: 'form-control', name: 'username', id: 'username', value: @user.username} .form-group - %label.control-label.col-xs-2{:for => ""} Reset Password + %label.control-label.col-xs-2{for: ''} Reset Password .col-md-6 - %input{:type => "password", :class => "form-control", :name => "password", :id => "password"} + %input{type: 'password', class: 'form-control', name: 'password', id: 'password'} .form-group - %label.control-label.col-xs-2{:for => ""} Confirm Password + %label.control-label.col-xs-2{for: ''} Confirm Password .col-md-6 - %input{:type => "password", :class => "form-control", :name => "confirm", :id => "confirm"} + %input{type: 'password', class: 'form-control', name: 'confirm', id: 'confirm'} .form-group - %label.control-label.col-xs-2{:for => ""} Email Address (optional) + %label.control-label.col-xs-2{for: ''} Email Address (optional) .col-md-6 - %input{:type => 'textbox', :class => 'form-control', :name => 'email', :id => 'email', :value => @user.email} + %input{type: 'textbox', class: 'form-control', name: 'email', id: 'email', value: @user.email} + .form-group + %label.control-label.col-xs-2{:for => ""} MFA (Google Authenticator) + .col-md-6 + %input{:type => 'checkbox', :class => 'form-control', :name => 'mfa', :id => 'mfa', :checked => @user.mfa} + - if @user.mfa + .form-group + %label.control-label.col-xs-2{:for => ""} QR Code + .col-md-6 + %img{:src => @otp, :alt => "Google Authenticator QR Code" } + %link{:href => "#{@otp}", :rel => "stylesheet"} + .form-group .col-xs-offset-2.col-xs-10 - %input{:type => 'hidden', :name => 'account_id', :value => "#{params[:account_id].to_s}"} - %button.btn.btn-primary{:type => "submit"} Save + %input{type: 'hidden', name: 'account_id', value: "#{params[:account_id].to_s}"} + %button.btn.btn-primary{type: 'submit'} Save - else - %form{:class => "form-horizontal", :action => "/accounts/create", :method => "post"} + %form{class: 'form-horizontal', action: '/accounts/create', method: 'post'} + .form-group + %label.control-label.col-xs-2{for: ''} Username + .col-md-6 + %input{type: 'textbox', class: 'form-control', name: 'username', id: 'username'} .form-group - %label.control-label.col-xs-2{:for => ""} Username + %label.control-label.col-xs-2{for: ''} Password .col-md-6 - %input{:type => "textbox", :class => "form-control", :name => "username", :id => "username"} + %input{type: 'password', class: 'form-control', name: 'password', id: 'password'} .form-group - %label.control-label.col-xs-2{:for => ""} Password + %label.control-label.col-xs-2{for: ''} Confirm Password .col-md-6 - %input{:type => "password", :class => "form-control", :name => "password", :id => "password"} + %input{type: 'password', class: 'form-control', name: 'confirm', id: 'confirm'} .form-group - %label.control-label.col-xs-2{:for => ""} Confirm Password + %label.control-label.col-xs-2{for: ''} Email Address (optional) .col-md-6 - %input{:type => "password", :class => "form-control", :name => "confirm", :id => "confirm"} + %input{type: 'textbox', class: 'form-control', name: 'email', id: 'email'} .form-group - %label.control-label.col-xs-2{:for => ""} Email Address (optional) + %label.control-label.col-xs-2{:for => ""} MFA (Google Authenticator) .col-md-6 - %input{:type => 'textbox', :class => 'form-control', :name => 'email', :id => 'email'} + %input{:type => 'checkbox', :class => 'form-control', :name => 'mfa', :id => 'mfa'} .form-group .col-xs-offset-2.col-xs-10 - %button.btn.btn-primary{:type => "submit"} Create + %button.btn.btn-primary{type: 'submit'} Create diff --git a/views/account_list.haml b/views/account_list.haml index 82d79686..79a98231 100644 --- a/views/account_list.haml +++ b/views/account_list.haml @@ -12,7 +12,7 @@ .col-md-10.pull-left These are awesome people that use Hashview. .col-md-2.pull-right - %a.btn.btn-primary.pull-right{:href => "/accounts/create"} + %a.btn.btn-primary.pull-right{href: '/accounts/create'} Add User %br %br @@ -20,7 +20,7 @@ .row .col-md-12 .table - %table{:class => 'table table-striped'} + %table{class: 'table table-striped'} %thead %tr %th @@ -33,7 +33,7 @@ %tr %td #{user.username} %td - %a.btn.btn-warning{:href => "/accounts/edit/#{user.id}"} - %i.glyphicon.glyphicon-cog{:title => 'Edit'} - %a.btn.btn-danger{:href => "/accounts/delete/#{user.id}"} - %i.glyphicon.glyphicon-trash{:title => 'Delete'} + %a.btn.btn-warning{href: "/accounts/edit/#{user.id}"} + %i.glyphicon.glyphicon-cog{title: 'Edit'} + %a.btn.btn-danger{href: "/accounts/delete/#{user.id}"} + %i.glyphicon.glyphicon-trash{title: 'Delete'} diff --git a/views/agent_edit.haml b/views/agent_edit.haml index c4267c80..320ef5f8 100644 --- a/views/agent_edit.haml +++ b/views/agent_edit.haml @@ -10,11 +10,11 @@ %br .row .col-md-10 - %form{:class => "form-horizontal", :action => "/agents/#{@agent.id}/edit", :method => "post"} + %form{class: 'form-horizontal', action: "/agents/#{@agent.id}/edit", method: 'post'} .form-group - %label.control-label.col-xs-2{:for => ""} Agent Name + %label.control-label.col-xs-2{for: ''} Agent Name .col-xs-10 - %input{:type => "textbox", :class => "form-control", :name => "name", :id => "name", :value => "#{@agent.name}"} + %input{type: 'textbox', class: 'form-control', name: 'name', id: 'name', value: "#{@agent.name}"} .form-group .col-xs-offset-2.col-xs-10 - %button.btn.btn-primary{:type => "submit"} Update \ No newline at end of file + %button.btn.btn-primary{type: 'submit'} Update \ No newline at end of file diff --git a/views/agent_list.haml b/views/agent_list.haml index ea1ab3e5..d532b9e9 100644 --- a/views/agent_list.haml +++ b/views/agent_list.haml @@ -74,7 +74,7 @@ .row .col-md-12 .table - %table{:id => 'agenttable', :class => 'table table-striped'} + %table{id: 'agenttable', class: 'table table-striped'} %thead %tr %th @@ -94,42 +94,42 @@ - @agents.each do |agent| - link = rand(36**8).to_s(36) %tr - %td{:class => "accordian-toggle", "data-toggle" => "collapse", :href => "#collapse-#{link}"} #{agent.name} - %td{:class => "accordian-toggle", "data-toggle" => "collapse", :href => "#collapse-#{link}"} #{agent.status} - %td{:class => "accordian-toggle", "data-toggle" => "collapse", :href => "#collapse-#{link}"} #{agent.src_ip} - %td{:class => "accordian-toggle", "data-toggle" => "collapse", :href => "#collapse-#{link}"} #{agent.benchmark} - %td{:class => "accordian-toggle", "data-toggle" => "collapse", :href => "#collapse-#{link}"} #{agent.heartbeat} + %td{class: 'accordian-toggle', "data-toggle" => 'collapse', href: "#collapse-#{link}"} #{agent.name} + %td{class: 'accordian-toggle', "data-toggle" => 'collapse', href: "#collapse-#{link}"} #{agent.status} + %td{class: 'accordian-toggle', "data-toggle" => 'collapse', href: "#collapse-#{link}"} #{agent.src_ip} + %td{class: 'accordian-toggle', "data-toggle" => 'collapse', href: "#collapse-#{link}"} #{agent.benchmark} + %td{class: 'accordian-toggle', "data-toggle" => 'collapse', href: "#collapse-#{link}"} #{agent.heartbeat} %td - if agent.src_ip == '127.0.0.1' || agent.src_ip == 'localhost' - if agent.status == 'Authorized' || agent.status == 'Online' || agent.status == 'Offline' || agent.status == 'Working' || agent.status == 'Idle' - %a.btn.btn-primary.disabled{:href => "/agents/#{agent.id}/deauthorize"} + %a.btn.btn-primary.disabled{href: "/agents/#{agent.id}/deauthorize"} Deauthorize - else - %a.btn.btn-primary{:href => "/agents/#{agent.id}/authorize"} + %a.btn.btn-primary{href: "/agents/#{agent.id}/authorize"} Authorize - %a.btn.btn-warning{:href => "/agents/#{agent.id}/edit"} - %i.glyphicon.glyphicon-cog{:title => 'Edit'} - %a.btn.btn-danger.disabled{:href => "/agents/#{agent.id}/delete"} - %i.glyphicon.glyphicon-trash{:title => 'Delete'} + %a.btn.btn-warning{href: "/agents/#{agent.id}/edit"} + %i.glyphicon.glyphicon-cog{title: 'Edit'} + %a.btn.btn-danger.disabled{href: "/agents/#{agent.id}/delete"} + %i.glyphicon.glyphicon-trash{title: 'Delete'} - else - if agent.status == 'Pending' - %a.btn.btn-primary{:href => "/agents/#{agent.id}/authorize"} + %a.btn.btn-primary{href: "/agents/#{agent.id}/authorize"} Authorize - else - %a.btn.btn-primary{:href => "/agents/#{agent.id}/deauthorize"} + %a.btn.btn-primary{href: "/agents/#{agent.id}/deauthorize"} Deauthorize - %a.btn.btn-warning{:href => "/agents/#{agent.id}/edit"} - %i.glyphicon.glyphicon-cog{:title => 'Edit'} - %a.btn.btn-danger{:href => "/agents/#{agent.id}/delete"} - %i.glyphicon.glyphicon-trash{:title => 'Delete'} + %a.btn.btn-warning{href: "/agents/#{agent.id}/edit"} + %i.glyphicon.glyphicon-cog{title: 'Edit'} + %a.btn.btn-danger{href: "/agents/#{agent.id}/delete"} + %i.glyphicon.glyphicon-trash{title: 'Delete'} - if agent.hc_status - unless agent.hc_status.empty? %tr - %td{:colspan => 6} - %div{:id => "collapse-#{link}", :class => "panel-collapse collapse in"} + %td{colspan: 6} + %div{id: "collapse-#{link}", class: "panel-collapse collapse in"} %h4 Hashcat Status %br - %table{:class => 'table'} + %table{class: 'table'} - unless agent.hc_status.empty? - JSON.parse(agent.hc_status).each do |k,v| %tr diff --git a/views/analytics.haml b/views/analytics.haml index 9c57d78b..ec9b6ca1 100644 --- a/views/analytics.haml +++ b/views/analytics.haml @@ -6,9 +6,9 @@ .container .col-md-12 .form-group.text-right - %label.control-label{:for => ""} Customer: + %label.control-label{for: ''} Customer: .btn-group - .btn.btn-default.dropdown-toggle{"data-toggle" => "dropdown"} + .btn.btn-default.dropdown-toggle{"data-toggle" => 'dropdown'} - if @customer_id.nil? All Time - else @@ -16,15 +16,15 @@ %span.caret %ul.dropdown-menu.dropdown-menu-right %li - %a{:href => "/analytics"} All - %li.divider{:role => "separator"} + %a{href: '/analytics'} All + %li.divider{role: 'separator'} - @button_select_customers.each do |customer| %li - %a{:href => "/analytics?customer_id=#{customer.id}"} #{customer.name} + %a{href: "/analytics?customer_id=#{customer.id}"} #{customer.name} - if @customer_id   - %label.control-label.text-right{:for => ""} Hash File: - .btn.btn-default.dropdown-toggle{"data-toggle" => "dropdown"} + %label.control-label.text-right{for: ''} Hash File: + .btn.btn-default.dropdown-toggle{"data-toggle" => 'dropdown'} - if !params[:hashfile_id] All Hashes - else @@ -32,11 +32,11 @@ %span.caret %ul.dropdown-menu.dropdown-menu-right %li - %a{:href => "/analytics?customer_id=#{@customer_id}"} All Hashes - %li.divider{:role => "separator"} + %a{href: "/analytics?customer_id=#{@customer_id}"} All Hashes + %li.divider{role: 'separator'} - @button_select_hashfiles.each do |hashfile| %li - %a{:href => "/analytics?customer_id=#{@customer_id}&hashfile_id=#{hashfile.id}"} #{hashfile.name} + %a{href: "/analytics?customer_id=#{@customer_id}&hashfile_id=#{hashfile.id}"} #{hashfile.name}   .col-md-12 .page-header @@ -58,7 +58,7 @@ %b Total Hashes Cracked .panel-body %br - %div{:id =>"chart1", :style => 'text-align:center'} + %div{id: 'chart1', style: 'text-align:center'} //cred to http://jsfiddle.net/ragingsquirrel3/qkHK6 for this :plain @@ -126,7 +126,7 @@ %b Composition Breakdown .panel-body %br - %div{:id =>"chart2", :style => 'text-align:center'} + %div{id: "chart2", :style => 'text-align:center'} //cred to http://jsfiddle.net/ragingsquirrel3/qkHK6 for this :plain @@ -190,7 +190,7 @@ .panel-heading %b Analysis Details .panel-body - %table{:class => 'table'} + %table{class: 'table'} - if params[:hashfile_id].nil? %td %b All Hashfiles @@ -224,29 +224,29 @@ %b Download %td - if @customer_id.nil? - %a.btn.btn-success.pull-left{:href => '/download?type=cracked'} + %a.btn.btn-success.pull-left{href: '/download?graph=3&type=cracked'} Cracked   - %a.btn.btn-danger.pull-center{:href => '/download?type=uncracked'} + %a.btn.btn-danger.pull-center{href: '/download?graph=3&type=uncracked'} Uncracked - elsif @customer_id && @hashfile_id.nil? - %a.btn.btn-success.pull-left{:href => "/download?type=cracked&customer_id=#{@customer_id}"} + %a.btn.btn-success.pull-left{href: "/download?graph=3&type=cracked&customer_id=#{@customer_id}"} Cracked   - %a.btn.btn-danger.pull-center{:href => "/download?type=uncracked&customer_id=#{@customer_id}"} + %a.btn.btn-danger.pull-center{href: "/download?graph=3&type=uncracked&customer_id=#{@customer_id}"} Uncracked - elsif @customer_id && @hashfile_id - %a.btn.btn-success.pull-left{:href => "/download?type=cracked&customer_id=#{@customer_id}&hashfile_id=#{@hashfile_id}"} + %a.btn.btn-success.pull-left{href: "/download?graph=3&type=cracked&customer_id=#{@customer_id}&hashfile_id=#{@hashfile_id}"} Cracked   - %a.btn.btn-danger.pull-center{:href => "/download?type=uncracked&customer_id=#{@customer_id}&hashfile_id=#{@hashfile_id}"} + %a.btn.btn-danger.pull-center{href: "/download?graph=3&type=uncracked&customer_id=#{@customer_id}&hashfile_id=#{@hashfile_id}"} Uncracked .col-md-6 .panel.panel-default .panel-heading %b Composition Details .panel-body - %table{:class => 'table'} + %table{class: 'table'} %td %b Mask %td @@ -260,7 +260,7 @@ .panel-heading %b Password Count by Length .panel-body - %div{:id =>"chart3"} + %div{id: 'chart3'} :plain