mirror of
https://github.com/beefproject/beef
synced 2026-06-08 13:15:56 +00:00
Merge branch 'master' into feature/3542-detect-media-devices
This commit is contained in:
@@ -1,3 +1,2 @@
|
||||
---
|
||||
BUNDLE_WITHOUT: "development:test"
|
||||
BUNDLE_WITH: "geoip:ext_msf:ext_notifications:ext_dns:ext_qrcode"
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
name: JavaScript Linting
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
lint-changed-files:
|
||||
name: Lint Changed JavaScript Files (Blocking)
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: '24.x'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Get changed JavaScript files
|
||||
id: changed-js-files
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: '**/*.js'
|
||||
files_ignore: |
|
||||
node_modules/**
|
||||
core/main/client/lib/**
|
||||
**/*.min.js
|
||||
extensions/admin_ui/media/javascript-min/**
|
||||
separator: ' '
|
||||
|
||||
- name: Lint changed JavaScript files
|
||||
if: steps.changed-js-files.outputs.any_changed == 'true'
|
||||
run: |
|
||||
echo "Linting ${{ steps.changed-js-files.outputs.all_changed_files_count }} changed JavaScript file(s)"
|
||||
npx eslint ${{ steps.changed-js-files.outputs.all_changed_files }}
|
||||
|
||||
- name: No JavaScript files changed
|
||||
if: steps.changed-js-files.outputs.any_changed == 'false'
|
||||
run: |
|
||||
echo "No JavaScript files were changed in this PR. Skipping ESLint."
|
||||
|
||||
lint-all-files:
|
||||
name: Lint All JavaScript Files (Non-blocking Report)
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: '24.x'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Lint all JavaScript files (report-only)
|
||||
run: |
|
||||
echo "Running ESLint on entire codebase"
|
||||
echo "This is a report-only job and will not block the PR."
|
||||
npx eslint . || true
|
||||
|
||||
- name: Summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "Full codebase linting completed. Check the logs above for details."
|
||||
echo "This job does not affect PR merge status."
|
||||
@@ -6,6 +6,12 @@ beef.log
|
||||
test/msf-test
|
||||
extensions/admin_ui/media/javascript-min/
|
||||
custom-config.yaml
|
||||
|
||||
# Generated at runtime by Hook_default_browser#pre_send
|
||||
modules/host/hook_default_browser/bounce_to_ie_configured.pdf
|
||||
|
||||
# Generated at runtime by browser/spyder_eye#post_execute (and possibly other capture modules)
|
||||
screenshot_*.png
|
||||
.DS_Store
|
||||
.gitignore
|
||||
.rvmrc
|
||||
|
||||
@@ -2,3 +2,5 @@
|
||||
--color
|
||||
--require spec_helper
|
||||
-I .
|
||||
--tag ~run_on_browserstack
|
||||
--tag ~run_on_long_tests
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
# SimpleCov configuration file
|
||||
# This provides a cleaner alternative to configuring SimpleCov in spec_helper.rb
|
||||
|
||||
SimpleCov.configure do
|
||||
# Basic filters
|
||||
add_filter '/spec/'
|
||||
add_filter '/config/'
|
||||
add_filter '/test/'
|
||||
|
||||
# Group coverage by component
|
||||
add_group 'Core', 'core'
|
||||
add_group 'Extensions', 'extensions'
|
||||
add_group 'Modules', 'modules'
|
||||
|
||||
# Track files based on coverage focus
|
||||
if ENV['COVERAGE'] == 'core'
|
||||
track_files 'core/**/*.rb'
|
||||
elsif ENV['COVERAGE'] == 'extensions'
|
||||
track_files 'extensions/**/*.rb'
|
||||
elsif ENV['COVERAGE'] == 'modules'
|
||||
track_files 'modules/**/*.rb'
|
||||
else
|
||||
# Default: track everything
|
||||
track_files '{core,extensions,modules}/**/*.rb'
|
||||
end
|
||||
|
||||
# Coverage thresholds
|
||||
minimum_coverage 80 if ENV['CI']
|
||||
|
||||
# Formatters
|
||||
formatter SimpleCov::Formatter::HTMLFormatter
|
||||
end
|
||||
+2
-2
@@ -90,7 +90,7 @@ GEM
|
||||
prism (>= 1.3.0)
|
||||
rdoc (>= 4.0.0)
|
||||
reline (>= 0.4.2)
|
||||
json (2.19.4)
|
||||
json (2.19.5)
|
||||
language_server-protocol (3.17.0.5)
|
||||
lint_roller (1.1.0)
|
||||
logger (1.7.0)
|
||||
@@ -171,7 +171,7 @@ GEM
|
||||
rack-test (2.2.0)
|
||||
rack (>= 1.3)
|
||||
rainbow (3.1.1)
|
||||
rake (13.4.1)
|
||||
rake (13.4.2)
|
||||
rdoc (7.2.0)
|
||||
erb
|
||||
psych (>= 4.0.0)
|
||||
|
||||
@@ -5,12 +5,82 @@
|
||||
#
|
||||
require 'rspec/core/rake_task'
|
||||
|
||||
task :default => ["short"]
|
||||
task default: ['short']
|
||||
|
||||
RSpec::Core::RakeTask.new(:short) do |task|
|
||||
task.rspec_opts = ['--tag ~run_on_browserstack', '--tag ~run_on_long_tests']
|
||||
# Run rspec with an explicit file list (avoids envs that only run 557).
|
||||
# Note: when run with the full file list, module specs load after extensions; the Dns stub in
|
||||
# network_spec must not run before the real Dns extension (dns_spec) or you get "superclass mismatch".
|
||||
desc 'Run short spec suite (all specs except browserstack/long)'
|
||||
task :short do
|
||||
short_files = Dir[File.join(Dir.pwd, 'spec', '**', '*_spec.rb')].sort
|
||||
$stderr.puts "[rake short] spec files=#{short_files.size}"
|
||||
abort '[rake short] Expected 60+ spec files; check you are in project root.' if short_files.size < 60
|
||||
opts = [
|
||||
'--tag', '~run_on_browserstack',
|
||||
'--tag', '~run_on_long_tests'
|
||||
]
|
||||
ok = system('bundle', 'exec', 'rspec', *short_files, *opts)
|
||||
abort 'rspec failed' unless ok
|
||||
end
|
||||
|
||||
# Legacy namespace for backward compatibility
|
||||
namespace :coverage do
|
||||
task :modules => 'coverage_modules'
|
||||
task :core => 'coverage_core'
|
||||
task :extensions => 'coverage_extensions'
|
||||
task :all => 'coverage'
|
||||
end
|
||||
|
||||
# Base spec tasks
|
||||
RSpec::Core::RakeTask.new(:spec) do |t|
|
||||
t.pattern = 'spec/**/*_spec.rb'
|
||||
t.rspec_opts = ['--tag', '~run_on_browserstack', '--tag', '~run_on_long_tests']
|
||||
end
|
||||
|
||||
RSpec::Core::RakeTask.new(:spec_core) do |t|
|
||||
t.pattern = 'spec/beef/core/**/*_spec.rb'
|
||||
t.rspec_opts = ['--tag', '~run_on_browserstack', '--tag', '~run_on_long_tests']
|
||||
end
|
||||
|
||||
RSpec::Core::RakeTask.new(:spec_extensions) do |t|
|
||||
t.pattern = 'spec/beef/extensions/**/*_spec.rb'
|
||||
t.rspec_opts = ['--tag', '~run_on_browserstack', '--tag', '~run_on_long_tests']
|
||||
end
|
||||
|
||||
RSpec::Core::RakeTask.new(:spec_modules) do |t|
|
||||
t.pattern = 'spec/beef/modules/**/*_spec.rb'
|
||||
t.rspec_opts = ['--tag', '~run_on_browserstack', '--tag', '~run_on_long_tests']
|
||||
end
|
||||
|
||||
# Coverage tasks using environment variables for cleaner configuration
|
||||
desc 'Run all specs with complete coverage tracking'
|
||||
task :coverage do
|
||||
ENV['COVERAGE'] = 'all'
|
||||
Rake::Task['spec'].invoke
|
||||
end
|
||||
|
||||
desc 'Run core specs with coverage'
|
||||
task :coverage_core do
|
||||
ENV['COVERAGE'] = 'core'
|
||||
Rake::Task['spec_core'].invoke
|
||||
end
|
||||
|
||||
desc 'Run extensions specs with coverage'
|
||||
task :coverage_extensions do
|
||||
ENV['COVERAGE'] = 'extensions'
|
||||
Rake::Task['spec_extensions'].invoke
|
||||
end
|
||||
|
||||
desc 'Run modules specs with coverage'
|
||||
task :coverage_modules do
|
||||
ENV['COVERAGE'] = 'modules'
|
||||
Rake::Task['spec_modules'].invoke
|
||||
end
|
||||
|
||||
# Alias for backward compatibility
|
||||
task :coverage_complete => :coverage
|
||||
task :coverage_all => :coverage
|
||||
|
||||
RSpec::Core::RakeTask.new(:long) do |task|
|
||||
task.rspec_opts = ['--tag ~run_on_browserstack']
|
||||
end
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
"use strict";
|
||||
|
||||
const js = require("@eslint/js");
|
||||
const globals = require("globals");
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
ignores: [
|
||||
"core/main/client/lib/**",
|
||||
"modules/**",
|
||||
"node_modules/**",
|
||||
"extensions/admin_ui/media/javascript-min/**",
|
||||
"**/*.min.js",
|
||||
],
|
||||
},
|
||||
js.configs.recommended,
|
||||
{
|
||||
files: ["**/*.js"],
|
||||
ignores: ["eslint.config.js"],
|
||||
languageOptions: {
|
||||
ecmaVersion: 5,
|
||||
sourceType: "script",
|
||||
globals: {
|
||||
...globals.browser,
|
||||
|
||||
// BeEF
|
||||
beef: "readonly",
|
||||
beef_init: "readonly",
|
||||
|
||||
// jQuery
|
||||
$: "readonly",
|
||||
jQuery: "readonly",
|
||||
$j: "readonly",
|
||||
|
||||
// Libraries
|
||||
MobileEsp: "readonly",
|
||||
evercookie: "readonly",
|
||||
swfobject: "readonly",
|
||||
|
||||
// Browser-specific (old IE / old Firefox)
|
||||
XDomainRequest: "readonly",
|
||||
MozWebSocket: "readonly",
|
||||
clipboardData: "readonly",
|
||||
|
||||
// Debug
|
||||
isDebug: "readonly",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["eslint.config.js"],
|
||||
languageOptions: {
|
||||
ecmaVersion: "latest",
|
||||
sourceType: "commonjs",
|
||||
globals: {
|
||||
module: "writable",
|
||||
require: "readonly",
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
Generated
+883
-5
File diff suppressed because it is too large
Load Diff
+9
-4
@@ -3,13 +3,18 @@
|
||||
"version": "0.6.0.0",
|
||||
"description": "The Browser Exploitation Framework Project",
|
||||
"scripts": {
|
||||
"docs": "./node_modules/.bin/jsdoc -c conf.json"
|
||||
"docs": "./node_modules/.bin/jsdoc -c conf.json",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint --fix ."
|
||||
},
|
||||
"author": "Wade Alcorn",
|
||||
"license": "GNU General Public License v2.0",
|
||||
"devDependencies": {
|
||||
"jsdoc": "^4.0.5",
|
||||
"jsdoc-to-markdown": "^9.1.3"
|
||||
"@eslint/js": "10.0.1",
|
||||
"eslint": "10.4.0",
|
||||
"globals": "17.6.0",
|
||||
"jsdoc": "4.0.5",
|
||||
"jsdoc-to-markdown": "9.1.3"
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
}
|
||||
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
# BeEF Test Suite
|
||||
|
||||
This directory contains the BeEF test suite using RSpec and SimpleCov for comprehensive testing and coverage reporting.
|
||||
|
||||
## Setup
|
||||
|
||||
### Prerequisites
|
||||
- Ruby 3.0+
|
||||
- Bundler
|
||||
- All gems from `Gemfile`
|
||||
|
||||
### Configuration Files
|
||||
- `spec/spec_helper.rb` - Main test configuration
|
||||
- `.simplecov` - Coverage configuration
|
||||
- `spec/support/` - Test helpers and utilities
|
||||
|
||||
## Running Tests
|
||||
|
||||
### Basic Commands
|
||||
|
||||
```bash
|
||||
# Run all tests (fast, no coverage)
|
||||
bundle exec rake short
|
||||
# or
|
||||
bundle exec rspec spec/ --tag '~run_on_browserstack' --tag '~run_on_long_tests'
|
||||
|
||||
# Run specific component tests
|
||||
bundle exec rspec spec/beef/core/
|
||||
bundle exec rspec spec/beef/extensions/
|
||||
bundle exec rspec spec/beef/modules/
|
||||
```
|
||||
|
||||
### Coverage Commands
|
||||
|
||||
```bash
|
||||
# Complete coverage (recommended)
|
||||
bundle exec rake coverage
|
||||
# or
|
||||
COVERAGE=all bundle exec rspec spec/ --tag '~run_on_browserstack' --tag '~run_on_long_tests'
|
||||
|
||||
# Component-specific coverage
|
||||
bundle exec rake coverage_core # Core only
|
||||
bundle exec rake coverage_modules # Modules only
|
||||
bundle exec rake coverage_extensions # Extensions only
|
||||
```
|
||||
|
||||
### Legacy Commands (Still Supported)
|
||||
|
||||
```bash
|
||||
# Old coverage commands still work
|
||||
bundle exec rake coverage:modules
|
||||
bundle exec rake coverage:all
|
||||
bundle exec rake coverage_complete
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
### SimpleCov Configuration
|
||||
|
||||
- **Environment-based**: Uses `COVERAGE=core|modules|extensions|all` environment variable
|
||||
- **Grouped reporting**: Separate groups for Core, Extensions, and Modules
|
||||
- **Filtered tracking**: Only tracks relevant files based on focus area
|
||||
- **HTML reports**: Generated in `coverage/` directory
|
||||
|
||||
### Test Organization
|
||||
|
||||
- **Centralized config**: `BeefTestConfig` module provides test data instead of global constants
|
||||
- **Component isolation**: Each component (core/extensions/modules) has dedicated specs
|
||||
- **Branch coverage**: Realistic test data for conditional logic testing
|
||||
- **Mock management**: Proper mocking of external dependencies
|
||||
|
||||
### Rake Tasks
|
||||
|
||||
- **Clean separation**: Base `spec*` tasks vs coverage `coverage*` tasks
|
||||
- **Environment variables**: Coverage controlled via `COVERAGE` env var
|
||||
- **No sequential execution**: Single test runs with proper filtering
|
||||
- **Backward compatibility**: Old task names still work
|
||||
|
||||
## Key Improvements
|
||||
|
||||
### ✅ **Eliminated Global Constant Collisions**
|
||||
- Branch-coverage data for the dynamic module spec loaders lives in
|
||||
`BeefTestConfig.branch_coverage_for(:component)` (in `spec/spec_helper.rb`).
|
||||
- Each module spec file pulls its own component (`:browser`, `:exploits`, `:host`,
|
||||
`:misc`, `:network`, `:social_engineering`) into a uniquely-named local
|
||||
constant (e.g. `BRANCH_COVERAGE_HOST`), so loading the full suite no longer
|
||||
triggers "already initialized constant" warnings or silently overwrites data.
|
||||
|
||||
### ✅ **Simplified Coverage Logic**
|
||||
- Cleaner filtering using `track_files` instead of complex `add_filter` logic
|
||||
- Environment variable `COVERAGE` instead of `COVERAGE_FOCUS`
|
||||
|
||||
### ✅ **Better Test Organization**
|
||||
- Centralized test configuration in `BeefTestConfig`
|
||||
- Component-specific test data loading
|
||||
- Reduced code duplication
|
||||
|
||||
### ✅ **Cleaner Rake Tasks**
|
||||
- Single execution instead of sequential runs
|
||||
- Proper environment variable usage
|
||||
- Backward compatibility maintained
|
||||
|
||||
### ✅ **Standard Patterns**
|
||||
- Uses `.simplecov` config file (standard practice)
|
||||
- Follows RSpec best practices
|
||||
- Better separation of concerns
|
||||
|
||||
## Module Spec Pattern (dynamic loaders)
|
||||
|
||||
Specs under `spec/beef/modules/**/<category>_spec.rb` walk every
|
||||
`modules/<category>/**/module.rb` file and generate a `RSpec.describe` block per
|
||||
class. They cover:
|
||||
|
||||
- `.options` returns an `Array` (when defined)
|
||||
- `#pre_send` runs without raising (when defined)
|
||||
- `#post_execute` runs without raising (when defined)
|
||||
- An extra `#post_execute` example with a realistic datastore for any module
|
||||
listed in `BeefTestConfig.branch_coverage_for(:category)`
|
||||
|
||||
Trade-off: this is a coverage-driving baseline. The `expect { ... }.not_to
|
||||
raise_error` assertion catches load failures, missing constants, nil crashes,
|
||||
and undefined methods, but it does not assert behavioural correctness. Add
|
||||
targeted assertions for high-value modules (see the `Wordpress_add_user` and
|
||||
`Test_get_variable` examples in `spec/beef/modules/misc/misc_spec.rb`).
|
||||
|
||||
## Coverage Focus Areas
|
||||
|
||||
- **core**: Framework core functionality
|
||||
- **extensions**: Extension modules
|
||||
- **modules**: Command modules (main focus)
|
||||
- **all**: Complete coverage across all areas
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **"already initialized constant" warnings**
|
||||
- Fixed by using centralized config instead of global constants
|
||||
|
||||
2. **Low coverage percentages**
|
||||
- Use `COVERAGE=all` for complete coverage
|
||||
- Ensure realistic test data triggers conditional paths
|
||||
|
||||
3. **Test failures**
|
||||
- Check that mocks are properly configured
|
||||
- Verify test data matches module expectations
|
||||
|
||||
### Debug Commands
|
||||
|
||||
```bash
|
||||
# Run with debug output
|
||||
bundle exec rspec --format documentation
|
||||
|
||||
# Run single test file
|
||||
bundle exec rspec spec/beef/modules/browser/browser_spec.rb
|
||||
|
||||
# Check coverage report
|
||||
open coverage/index.html
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
When adding new tests:
|
||||
|
||||
1. Use `BeefTestConfig.branch_coverage_for(:component)` for branch test data
|
||||
2. Add realistic datastore values that trigger conditional logic
|
||||
3. Mock external dependencies (database, network, etc.)
|
||||
4. Follow existing patterns for consistency
|
||||
@@ -307,4 +307,142 @@ RSpec.describe BeEF::Filters do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.is_valid_ip?' do
|
||||
it 'returns false for nil, empty, or non-string' do
|
||||
expect(BeEF::Filters.is_valid_ip?(nil)).to be(false)
|
||||
expect(BeEF::Filters.is_valid_ip?('')).to be(false)
|
||||
end
|
||||
|
||||
it 'returns true for valid IPv4' do
|
||||
expect(BeEF::Filters.is_valid_ip?('127.0.0.1')).to be(true)
|
||||
expect(BeEF::Filters.is_valid_ip?('192.168.1.1')).to be(true)
|
||||
expect(BeEF::Filters.is_valid_ip?('10.0.0.1')).to be(true)
|
||||
expect(BeEF::Filters.is_valid_ip?('0.0.0.0')).to be(true)
|
||||
end
|
||||
|
||||
it 'returns false for invalid IPv4' do
|
||||
expect(BeEF::Filters.is_valid_ip?('256.1.1.1')).to be(false)
|
||||
expect(BeEF::Filters.is_valid_ip?('1.2.3')).to be(false)
|
||||
expect(BeEF::Filters.is_valid_ip?('not.an.ip')).to be(false)
|
||||
end
|
||||
|
||||
it 'accepts :ipv4 version' do
|
||||
expect(BeEF::Filters.is_valid_ip?('127.0.0.1', :ipv4)).to be(true)
|
||||
expect(BeEF::Filters.is_valid_ip?('256.1.1.1', :ipv4)).to be(false)
|
||||
end
|
||||
|
||||
it 'accepts :both version (default)' do
|
||||
expect(BeEF::Filters.is_valid_ip?('127.0.0.1')).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.is_valid_private_ip?' do
|
||||
it 'returns false when ip is not valid' do
|
||||
expect(BeEF::Filters.is_valid_private_ip?(nil)).to be(false)
|
||||
expect(BeEF::Filters.is_valid_private_ip?('8.8.8.8')).to be(false)
|
||||
end
|
||||
|
||||
it 'returns true for 127.x (localhost)' do
|
||||
expect(BeEF::Filters.is_valid_private_ip?('127.0.0.1')).to be(true)
|
||||
end
|
||||
|
||||
it 'returns true for 192.168.x' do
|
||||
expect(BeEF::Filters.is_valid_private_ip?('192.168.1.1')).to be(true)
|
||||
end
|
||||
|
||||
it 'returns true for 10.x' do
|
||||
expect(BeEF::Filters.is_valid_private_ip?('10.0.0.1')).to be(true)
|
||||
end
|
||||
|
||||
it 'returns false for public IPv4' do
|
||||
expect(BeEF::Filters.is_valid_private_ip?('8.8.8.8')).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.is_valid_port?' do
|
||||
it 'returns true for valid port range' do
|
||||
expect(BeEF::Filters.is_valid_port?(1)).to be(true)
|
||||
expect(BeEF::Filters.is_valid_port?('80')).to be(true)
|
||||
expect(BeEF::Filters.is_valid_port?(65535)).to be(true)
|
||||
end
|
||||
|
||||
it 'returns false for 0 or negative' do
|
||||
expect(BeEF::Filters.is_valid_port?(0)).to be(false)
|
||||
expect(BeEF::Filters.is_valid_port?('0')).to be(false)
|
||||
end
|
||||
|
||||
it 'returns false for port above 65535' do
|
||||
expect(BeEF::Filters.is_valid_port?(65536)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.is_valid_domain?' do
|
||||
it 'returns false for nil or empty' do
|
||||
expect(BeEF::Filters.is_valid_domain?(nil)).to be(false)
|
||||
expect(BeEF::Filters.is_valid_domain?('')).to be(false)
|
||||
end
|
||||
|
||||
it 'returns true for valid domain format' do
|
||||
expect(BeEF::Filters.is_valid_domain?('example.com')).to be(true)
|
||||
expect(BeEF::Filters.is_valid_domain?('sub.example.co.uk')).to be(true)
|
||||
end
|
||||
|
||||
it 'returns false for invalid domain format' do
|
||||
expect(BeEF::Filters.is_valid_domain?('no-tld')).to be(false)
|
||||
expect(BeEF::Filters.is_valid_domain?('.leading')).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.has_valid_browser_details_chars?' do
|
||||
it 'returns false for nil or empty' do
|
||||
expect(BeEF::Filters.has_valid_browser_details_chars?(nil)).to be(false)
|
||||
expect(BeEF::Filters.has_valid_browser_details_chars?('')).to be(false)
|
||||
end
|
||||
|
||||
it 'returns true when string only has allowed chars' do
|
||||
# Method returns nil-on-no-match: nil.nil? == true, so a fully-valid string returns true.
|
||||
expect(BeEF::Filters.has_valid_browser_details_chars?('abc')).to be(true)
|
||||
expect(BeEF::Filters.has_valid_browser_details_chars?('a-b (c)')).to be(true)
|
||||
end
|
||||
|
||||
it 'returns false when string contains disallowed character' do
|
||||
expect(BeEF::Filters.has_valid_browser_details_chars?('ab@c')).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.has_valid_base_chars?' do
|
||||
it 'returns false for nil or empty' do
|
||||
expect(BeEF::Filters.has_valid_base_chars?(nil)).to be(false)
|
||||
expect(BeEF::Filters.has_valid_base_chars?('')).to be(false)
|
||||
end
|
||||
|
||||
it 'returns true when string only has printable (and registered symbol)' do
|
||||
expect(BeEF::Filters.has_valid_base_chars?('abc')).to be(true)
|
||||
expect(BeEF::Filters.has_valid_base_chars?('Hello 123')).to be(true)
|
||||
end
|
||||
|
||||
it 'returns false when string has non-printable character' do
|
||||
expect(BeEF::Filters.has_valid_base_chars?("ab\x00c")).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.is_valid_yes_no?' do
|
||||
it 'returns true for Yes and No (case insensitive)' do
|
||||
expect(BeEF::Filters.is_valid_yes_no?('Yes')).to be(true)
|
||||
expect(BeEF::Filters.is_valid_yes_no?('No')).to be(true)
|
||||
expect(BeEF::Filters.is_valid_yes_no?('yes')).to be(true)
|
||||
expect(BeEF::Filters.is_valid_yes_no?('no')).to be(true)
|
||||
end
|
||||
|
||||
it 'returns false for other values' do
|
||||
expect(BeEF::Filters.is_valid_yes_no?('')).to be(false)
|
||||
expect(BeEF::Filters.is_valid_yes_no?('maybe')).to be(false)
|
||||
expect(BeEF::Filters.is_valid_yes_no?('1')).to be(false)
|
||||
end
|
||||
|
||||
it 'returns false when string has non-printable character' do
|
||||
expect(BeEF::Filters.is_valid_yes_no?("Yes\x00")).to be(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -11,6 +11,10 @@ RSpec.describe BeEF::Filters do
|
||||
expect(BeEF::Filters.is_valid_path_info?("\x00")).to be(false)
|
||||
expect(BeEF::Filters.is_valid_path_info?(nil)).to be(false)
|
||||
end
|
||||
|
||||
it 'returns false when argument is not a String' do
|
||||
expect(BeEF::Filters.is_valid_path_info?(123)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.is_valid_hook_session_id?' do
|
||||
@@ -43,15 +47,22 @@ RSpec.describe BeEF::Filters do
|
||||
end
|
||||
|
||||
describe '.has_valid_param_chars?' do
|
||||
it 'false' do
|
||||
it 'returns false for nil, empty, or invalid chars' do
|
||||
chars = [nil, '', '+']
|
||||
chars.each do |c|
|
||||
expect(BeEF::Filters.has_valid_param_chars?(c)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
it 'true' do
|
||||
it 'returns true for word, underscore, and colon' do
|
||||
expect(BeEF::Filters.has_valid_param_chars?('A')).to be(true)
|
||||
expect(BeEF::Filters.has_valid_param_chars?('key_name')).to be(true)
|
||||
expect(BeEF::Filters.has_valid_param_chars?('a:1')).to be(true)
|
||||
end
|
||||
|
||||
it 'returns false for string with spaces or special chars' do
|
||||
expect(BeEF::Filters.has_valid_param_chars?('a b')).to be(false)
|
||||
expect(BeEF::Filters.has_valid_param_chars?('a-b')).to be(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BeEF::HBManager do
|
||||
describe '.get_by_session' do
|
||||
it 'returns the hooked browser when session exists' do
|
||||
hb = BeEF::Core::Models::HookedBrowser.create!(session: 'hb_session_123', ip: '127.0.0.1')
|
||||
|
||||
result = described_class.get_by_session('hb_session_123')
|
||||
|
||||
expect(result).to eq(hb)
|
||||
expect(result.session).to eq('hb_session_123')
|
||||
end
|
||||
|
||||
it 'returns nil when no hooked browser has the session' do
|
||||
result = described_class.get_by_session('nonexistent_session')
|
||||
|
||||
expect(result).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '.get_by_id' do
|
||||
it 'returns the hooked browser when id exists' do
|
||||
hb = BeEF::Core::Models::HookedBrowser.create!(session: 'hb_by_id', ip: '127.0.0.1')
|
||||
|
||||
result = described_class.get_by_id(hb.id)
|
||||
|
||||
expect(result).to eq(hb)
|
||||
expect(result.id).to eq(hb.id)
|
||||
end
|
||||
|
||||
it 'raises when id does not exist' do
|
||||
expect { described_class.get_by_id(999_999) }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,196 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BeEF::Core::Console::Banners do
|
||||
let(:config) { BeEF::Core::Configuration.instance }
|
||||
|
||||
before do
|
||||
allow(described_class).to receive(:print_info)
|
||||
allow(described_class).to receive(:print_more)
|
||||
end
|
||||
|
||||
describe '.print_welcome_msg' do
|
||||
it 'calls print_info with version from config' do
|
||||
allow(config).to receive(:get).with('beef.version').and_return('1.0.0')
|
||||
described_class.print_welcome_msg
|
||||
expect(described_class).to have_received(:print_info).with('Browser Exploitation Framework (BeEF) 1.0.0')
|
||||
end
|
||||
|
||||
it 'calls print_more with project links' do
|
||||
allow(config).to receive(:get).with('beef.version').and_return('1.0.0')
|
||||
described_class.print_welcome_msg
|
||||
expect(described_class).to have_received(:print_more).with(a_string_including('@beefproject'))
|
||||
expect(described_class).to have_received(:print_more).with(a_string_including('beefproject.com'))
|
||||
expect(described_class).to have_received(:print_more).with(a_string_including('github.com/beefproject'))
|
||||
end
|
||||
|
||||
it 'calls print_info with project creator' do
|
||||
allow(config).to receive(:get).with('beef.version').and_return('1.0.0')
|
||||
described_class.print_welcome_msg
|
||||
expect(described_class).to have_received(:print_info).with(a_string_including('Wade Alcorn'))
|
||||
expect(described_class).to have_received(:print_info).with(a_string_including('@WadeAlcorn'))
|
||||
end
|
||||
end
|
||||
|
||||
describe '.print_network_interfaces_count' do
|
||||
it 'uses config local_host and sets interfaces when host is 0.0.0.0' do
|
||||
allow(config).to receive(:local_host).and_return('0.0.0.0')
|
||||
mock_addrs = [double('Addr', ip_address: '127.0.0.1', ipv4?: true), double('Addr', ip_address: '192.168.1.1', ipv4?: true)]
|
||||
allow(Socket).to receive(:ip_address_list).and_return(mock_addrs)
|
||||
described_class.print_network_interfaces_count
|
||||
expect(described_class.interfaces).to eq(['127.0.0.1', '192.168.1.1'])
|
||||
expect(described_class).to have_received(:print_info).with('2 network interfaces were detected.')
|
||||
end
|
||||
|
||||
it 'sets single interface when host is not 0.0.0.0' do
|
||||
allow(config).to receive(:local_host).and_return('192.168.1.1')
|
||||
described_class.print_network_interfaces_count
|
||||
expect(described_class.interfaces).to eq(['192.168.1.1'])
|
||||
expect(described_class).to have_received(:print_info).with('1 network interfaces were detected.')
|
||||
end
|
||||
end
|
||||
|
||||
describe '.print_loaded_extensions' do
|
||||
it 'calls print_info with count from Extensions.get_loaded' do
|
||||
allow(BeEF::Extensions).to receive(:get_loaded).and_return({ 'AdminUI' => { 'name' => 'Admin UI' }, 'DNS' => { 'name' => 'DNS' } })
|
||||
described_class.print_loaded_extensions
|
||||
expect(described_class).to have_received(:print_info).with('2 extensions enabled:')
|
||||
expect(described_class).to have_received(:print_more).with(a_string_including('Admin UI'))
|
||||
expect(described_class).to have_received(:print_more).with(a_string_including('DNS'))
|
||||
end
|
||||
|
||||
it 'handles empty extensions' do
|
||||
allow(BeEF::Extensions).to receive(:get_loaded).and_return({})
|
||||
described_class.print_loaded_extensions
|
||||
expect(described_class).to have_received(:print_info).with('0 extensions enabled:')
|
||||
end
|
||||
end
|
||||
|
||||
describe '.print_loaded_modules' do
|
||||
it 'calls print_info with count from Modules.get_enabled' do
|
||||
enabled = double('Relation', count: 42)
|
||||
allow(BeEF::Modules).to receive(:get_enabled).and_return(enabled)
|
||||
described_class.print_loaded_modules
|
||||
expect(described_class).to have_received(:print_info).with('42 modules enabled.')
|
||||
end
|
||||
end
|
||||
|
||||
describe '.print_ascii_art' do
|
||||
it 'reads and puts file content when beef.ascii exists' do
|
||||
allow(File).to receive(:exist?).with('core/main/console/beef.ascii').and_return(true)
|
||||
io = StringIO.new("BEEF\nASCII\n")
|
||||
allow(File).to receive(:open).with('core/main/console/beef.ascii', 'r').and_yield(io)
|
||||
allow(described_class).to receive(:puts)
|
||||
described_class.print_ascii_art
|
||||
expect(described_class).to have_received(:puts).with("BEEF\n")
|
||||
expect(described_class).to have_received(:puts).with("ASCII\n")
|
||||
end
|
||||
|
||||
it 'does nothing when beef.ascii does not exist' do
|
||||
allow(File).to receive(:exist?).with('core/main/console/beef.ascii').and_return(false)
|
||||
expect(File).not_to receive(:open)
|
||||
described_class.print_ascii_art
|
||||
end
|
||||
end
|
||||
|
||||
describe '.print_network_interfaces_routes' do
|
||||
before do
|
||||
described_class.interfaces = ['127.0.0.1', '192.168.1.1']
|
||||
end
|
||||
|
||||
it 'prints hook and UI URL for each interface when admin_ui enabled' do
|
||||
allow(config).to receive(:local_proto).and_return('http')
|
||||
allow(config).to receive(:hook_file_path).and_return('/hook.js')
|
||||
allow(config).to receive(:get).with('beef.extension.admin_ui.enable').and_return(true)
|
||||
allow(config).to receive(:get).with('beef.extension.admin_ui.base_path').and_return('/ui')
|
||||
allow(config).to receive(:local_port).and_return(3000)
|
||||
allow(config).to receive(:public_enabled?).and_return(false)
|
||||
|
||||
described_class.print_network_interfaces_routes
|
||||
|
||||
expect(described_class).to have_received(:print_info).with('running on network interface: 127.0.0.1')
|
||||
expect(described_class).to have_received(:print_info).with('running on network interface: 192.168.1.1')
|
||||
expect(described_class).to have_received(:print_more).with(a_string_matching(%r{Hook URL: http://127\.0\.0\.1:3000/hook\.js}))
|
||||
expect(described_class).to have_received(:print_more).at_least(:twice).with(a_string_matching(%r{UI URL:.*/ui/panel}))
|
||||
end
|
||||
|
||||
it 'omits UI URL when admin_ui disabled' do
|
||||
allow(config).to receive(:local_proto).and_return('http')
|
||||
allow(config).to receive(:hook_file_path).and_return('/hook.js')
|
||||
allow(config).to receive(:get).with('beef.extension.admin_ui.enable').and_return(false)
|
||||
allow(config).to receive(:get).with('beef.extension.admin_ui.base_path').and_return('/ui')
|
||||
allow(config).to receive(:local_port).and_return(3000)
|
||||
allow(config).to receive(:public_enabled?).and_return(false)
|
||||
|
||||
described_class.print_network_interfaces_routes
|
||||
|
||||
expect(described_class).to have_received(:print_more).with("Hook URL: http://127.0.0.1:3000/hook.js\n")
|
||||
expect(described_class).to have_received(:print_more).with("Hook URL: http://192.168.1.1:3000/hook.js\n")
|
||||
end
|
||||
|
||||
it 'prints public hook and UI when public_enabled?' do
|
||||
allow(config).to receive(:local_proto).and_return('http')
|
||||
allow(config).to receive(:hook_file_path).and_return('/hook.js')
|
||||
allow(config).to receive(:get).with('beef.extension.admin_ui.enable').and_return(true)
|
||||
allow(config).to receive(:get).with('beef.extension.admin_ui.base_path').and_return('/ui')
|
||||
allow(config).to receive(:local_port).and_return(3000)
|
||||
allow(config).to receive(:public_enabled?).and_return(true)
|
||||
allow(config).to receive(:hook_url).and_return('http://public.example.com/hook.js')
|
||||
allow(config).to receive(:beef_url_str).and_return('http://public.example.com')
|
||||
|
||||
described_class.print_network_interfaces_routes
|
||||
|
||||
expect(described_class).to have_received(:print_info).with('Public:')
|
||||
expect(described_class).to have_received(:print_more).with(a_string_including('http://public.example.com/hook.js'))
|
||||
expect(described_class).to have_received(:print_more).at_least(:once).with(a_string_including('/ui/panel'))
|
||||
end
|
||||
end
|
||||
|
||||
describe '.print_websocket_servers' do
|
||||
it 'prints WebSocket server line with host, port and timer' do
|
||||
allow(config).to receive(:beef_host).and_return('0.0.0.0')
|
||||
allow(config).to receive(:get).with('beef.http.websocket.ws_poll_timeout').and_return(5)
|
||||
allow(config).to receive(:get).with('beef.http.websocket.port').and_return(61_985)
|
||||
allow(config).to receive(:get).with('beef.http.websocket.secure').and_return(false)
|
||||
|
||||
described_class.print_websocket_servers
|
||||
|
||||
expect(described_class).to have_received(:print_info).with('Starting WebSocket server ws://0.0.0.0:61985 [timer: 5]')
|
||||
end
|
||||
|
||||
it 'prints WebSocketSecure server when secure enabled' do
|
||||
allow(config).to receive(:beef_host).and_return('0.0.0.0')
|
||||
allow(config).to receive(:get).with('beef.http.websocket.ws_poll_timeout').and_return(10)
|
||||
allow(config).to receive(:get).with('beef.http.websocket.port').and_return(61_985)
|
||||
allow(config).to receive(:get).with('beef.http.websocket.secure').and_return(true)
|
||||
allow(config).to receive(:get).with('beef.http.websocket.secure_port').and_return(61_986)
|
||||
|
||||
described_class.print_websocket_servers
|
||||
|
||||
expect(described_class).to have_received(:print_info).with('Starting WebSocket server ws://0.0.0.0:61985 [timer: 10]')
|
||||
expect(described_class).to have_received(:print_info).with(a_string_matching(/WebSocketSecure.*wss:.*61986.*timer: 10/))
|
||||
end
|
||||
end
|
||||
|
||||
describe '.print_http_proxy' do
|
||||
it 'prints proxy address and port from config' do
|
||||
allow(config).to receive(:get).with('beef.extension.proxy.address').and_return('127.0.0.1')
|
||||
allow(config).to receive(:get).with('beef.extension.proxy.port').and_return(8080)
|
||||
|
||||
described_class.print_http_proxy
|
||||
|
||||
expect(described_class).to have_received(:print_info).with('HTTP Proxy: http://127.0.0.1:8080')
|
||||
end
|
||||
end
|
||||
|
||||
describe '.print_dns' do
|
||||
it 'does not raise when DNS config is not set' do
|
||||
expect { described_class.print_dns }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,126 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BeEF::Core::Console::CommandLine do
|
||||
DEFAULT_OPTIONS = {
|
||||
verbose: false,
|
||||
resetdb: false,
|
||||
ascii_art: false,
|
||||
ext_config: '',
|
||||
port: '',
|
||||
ws_port: '',
|
||||
update_disabled: false,
|
||||
update_auto: false
|
||||
}.freeze
|
||||
|
||||
def reset_commandline_state
|
||||
described_class.instance_variable_set(:@already_parsed, false)
|
||||
described_class.instance_variable_set(:@options, DEFAULT_OPTIONS.dup)
|
||||
end
|
||||
|
||||
before do
|
||||
reset_commandline_state
|
||||
end
|
||||
|
||||
describe '.parse' do
|
||||
it 'returns default options when ARGV is empty' do
|
||||
original_argv = ARGV.dup
|
||||
ARGV.replace([])
|
||||
result = described_class.parse
|
||||
ARGV.replace(original_argv)
|
||||
expect(result[:verbose]).to be false
|
||||
expect(result[:resetdb]).to be false
|
||||
expect(result[:ext_config]).to eq('')
|
||||
expect(result[:port]).to eq('')
|
||||
end
|
||||
|
||||
it 'sets verbose when -v is given' do
|
||||
original_argv = ARGV.dup
|
||||
ARGV.replace(%w[-v])
|
||||
result = described_class.parse
|
||||
ARGV.replace(original_argv)
|
||||
expect(result[:verbose]).to be true
|
||||
end
|
||||
|
||||
it 'sets resetdb when -x is given' do
|
||||
original_argv = ARGV.dup
|
||||
ARGV.replace(%w[-x])
|
||||
result = described_class.parse
|
||||
ARGV.replace(original_argv)
|
||||
expect(result[:resetdb]).to be true
|
||||
end
|
||||
|
||||
it 'sets ascii_art when -a is given' do
|
||||
original_argv = ARGV.dup
|
||||
ARGV.replace(%w[-a])
|
||||
result = described_class.parse
|
||||
ARGV.replace(original_argv)
|
||||
expect(result[:ascii_art]).to be true
|
||||
end
|
||||
|
||||
it 'sets ext_config when -c FILE is given' do
|
||||
original_argv = ARGV.dup
|
||||
ARGV.replace(%w[-c custom.yaml])
|
||||
result = described_class.parse
|
||||
ARGV.replace(original_argv)
|
||||
expect(result[:ext_config]).to eq('custom.yaml')
|
||||
end
|
||||
|
||||
it 'sets port when -p PORT is given' do
|
||||
original_argv = ARGV.dup
|
||||
ARGV.replace(%w[-p 9090])
|
||||
result = described_class.parse
|
||||
ARGV.replace(original_argv)
|
||||
expect(result[:port]).to eq('9090')
|
||||
end
|
||||
|
||||
it 'sets ws_port when -w WS_PORT is given' do
|
||||
original_argv = ARGV.dup
|
||||
ARGV.replace(%w[-w 61985])
|
||||
result = described_class.parse
|
||||
ARGV.replace(original_argv)
|
||||
expect(result[:ws_port]).to eq('61985')
|
||||
end
|
||||
|
||||
it 'sets update_disabled when -d is given' do
|
||||
original_argv = ARGV.dup
|
||||
ARGV.replace(%w[-d])
|
||||
result = described_class.parse
|
||||
ARGV.replace(original_argv)
|
||||
expect(result[:update_disabled]).to be true
|
||||
end
|
||||
|
||||
it 'sets update_auto when -u is given' do
|
||||
original_argv = ARGV.dup
|
||||
ARGV.replace(%w[-u])
|
||||
result = described_class.parse
|
||||
ARGV.replace(original_argv)
|
||||
expect(result[:update_auto]).to be true
|
||||
end
|
||||
|
||||
it 'returns cached options on second parse' do
|
||||
original_argv = ARGV.dup
|
||||
ARGV.replace([])
|
||||
first = described_class.parse
|
||||
ARGV.replace(%w[-v -x])
|
||||
second = described_class.parse
|
||||
ARGV.replace(original_argv)
|
||||
expect(second).to eq(first)
|
||||
expect(second[:verbose]).to be false
|
||||
end
|
||||
|
||||
it 'prints and exits on invalid option' do
|
||||
original_argv = ARGV.dup
|
||||
ARGV.replace(%w[--invalid-option])
|
||||
allow(Kernel).to receive(:puts).with(/Invalid command line option/)
|
||||
allow(Kernel).to receive(:exit).with(1) { raise SystemExit.new(1) }
|
||||
expect { described_class.parse }.to raise_error(SystemExit)
|
||||
ARGV.replace(original_argv)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,60 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BeEF::Core::Constants::Browsers do
|
||||
describe 'constants' do
|
||||
it 'defines short browser codes' do
|
||||
expect(described_class::FF).to eq('FF')
|
||||
expect(described_class::C).to eq('C')
|
||||
expect(described_class::IE).to eq('IE')
|
||||
expect(described_class::S).to eq('S')
|
||||
expect(described_class::ALL).to eq('ALL')
|
||||
expect(described_class::UNKNOWN).to eq('UN')
|
||||
end
|
||||
|
||||
it 'defines friendly names' do
|
||||
expect(described_class::FRIENDLY_FF_NAME).to eq('Firefox')
|
||||
expect(described_class::FRIENDLY_C_NAME).to eq('Chrome')
|
||||
expect(described_class::FRIENDLY_UN_NAME).to eq('UNKNOWN')
|
||||
end
|
||||
end
|
||||
|
||||
describe '.friendly_name' do
|
||||
it 'returns Firefox for FF' do
|
||||
expect(described_class.friendly_name(described_class::FF)).to eq('Firefox')
|
||||
end
|
||||
|
||||
it 'returns Chrome for C' do
|
||||
expect(described_class.friendly_name(described_class::C)).to eq('Chrome')
|
||||
end
|
||||
|
||||
it 'returns Internet Explorer for IE' do
|
||||
expect(described_class.friendly_name(described_class::IE)).to eq('Internet Explorer')
|
||||
end
|
||||
|
||||
it 'returns Safari for S' do
|
||||
expect(described_class.friendly_name(described_class::S)).to eq('Safari')
|
||||
end
|
||||
|
||||
it 'returns MSEdge for E' do
|
||||
expect(described_class.friendly_name(described_class::E)).to eq('MSEdge')
|
||||
end
|
||||
|
||||
it 'returns UNKNOWN for UN' do
|
||||
expect(described_class.friendly_name(described_class::UNKNOWN)).to eq('UNKNOWN')
|
||||
end
|
||||
|
||||
it 'returns nil for unknown browser code' do
|
||||
expect(described_class.friendly_name('XX')).to be_nil
|
||||
end
|
||||
|
||||
it 'returns nil for nil' do
|
||||
expect(described_class.friendly_name(nil)).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,18 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BeEF::Core::Constants::CommandModule do
|
||||
describe 'constants' do
|
||||
it 'defines verified working status values' do
|
||||
expect(described_class::VERIFIED_WORKING).to eq(0)
|
||||
expect(described_class::VERIFIED_UNKNOWN).to eq(1)
|
||||
expect(described_class::VERIFIED_USER_NOTIFY).to eq(2)
|
||||
expect(described_class::VERIFIED_NOT_WORKING).to eq(3)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,69 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BeEF::Core::Constants::Hardware do
|
||||
describe 'constants' do
|
||||
it 'defines hardware UA strings and image paths' do
|
||||
expect(described_class::HW_IPHONE_UA_STR).to eq('iPhone')
|
||||
expect(described_class::HW_IPAD_UA_STR).to eq('iPad')
|
||||
expect(described_class::HW_BLACKBERRY_UA_STR).to eq('BlackBerry')
|
||||
expect(described_class::HW_ALL_UA_STR).to eq('All')
|
||||
expect(described_class::HW_UNKNOWN_IMG).to eq('pc.png')
|
||||
end
|
||||
end
|
||||
|
||||
describe '.match_hardware' do
|
||||
it 'returns iPhone for iphone-like strings' do
|
||||
expect(described_class.match_hardware('iPhone')).to eq('iPhone')
|
||||
expect(described_class.match_hardware('iPhone OS')).to eq('iPhone')
|
||||
end
|
||||
|
||||
it 'returns iPad for ipad-like strings' do
|
||||
expect(described_class.match_hardware('iPad')).to eq('iPad')
|
||||
end
|
||||
|
||||
it 'returns iPod for ipod-like strings' do
|
||||
expect(described_class.match_hardware('iPod')).to eq('iPod')
|
||||
end
|
||||
|
||||
it 'returns BlackBerry for blackberry-like strings' do
|
||||
expect(described_class.match_hardware('BlackBerry')).to eq('BlackBerry')
|
||||
end
|
||||
|
||||
it 'returns Windows Phone for windows phone-like strings' do
|
||||
expect(described_class.match_hardware('Windows Phone')).to eq('Windows Phone')
|
||||
end
|
||||
|
||||
it 'returns Kindle for kindle-like strings' do
|
||||
expect(described_class.match_hardware('Kindle')).to eq('Kindle')
|
||||
end
|
||||
|
||||
it 'returns Nokia for nokia-like strings' do
|
||||
expect(described_class.match_hardware('Nokia')).to eq('Nokia')
|
||||
end
|
||||
|
||||
it 'returns HTC for htc-like strings' do
|
||||
expect(described_class.match_hardware('HTC')).to eq('HTC')
|
||||
end
|
||||
|
||||
it 'returns Nexus for google-like strings' do
|
||||
expect(described_class.match_hardware('Google Nexus')).to eq('Nexus')
|
||||
end
|
||||
|
||||
it 'is case insensitive' do
|
||||
expect(described_class.match_hardware('IPHONE')).to eq('iPhone')
|
||||
expect(described_class.match_hardware('ipad')).to eq('iPad')
|
||||
expect(described_class.match_hardware('BLACKBERRY')).to eq('BlackBerry')
|
||||
end
|
||||
|
||||
it 'returns ALL for unknown hardware strings' do
|
||||
expect(described_class.match_hardware('UnknownDevice')).to eq('ALL')
|
||||
expect(described_class.match_hardware('')).to eq('ALL')
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,73 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BeEF::Core::Constants::Os do
|
||||
describe 'constants' do
|
||||
it 'defines OS UA strings and image paths' do
|
||||
expect(described_class::OS_WINDOWS_UA_STR).to eq('Windows')
|
||||
expect(described_class::OS_LINUX_UA_STR).to eq('Linux')
|
||||
expect(described_class::OS_MAC_UA_STR).to eq('Mac')
|
||||
expect(described_class::OS_ANDROID_UA_STR).to eq('Android')
|
||||
expect(described_class::OS_ALL_UA_STR).to eq('All')
|
||||
expect(described_class::OS_UNKNOWN_IMG).to eq('unknown.png')
|
||||
end
|
||||
end
|
||||
|
||||
describe '.match_os' do
|
||||
it 'returns Windows for win-like strings' do
|
||||
expect(described_class.match_os('Windows')).to eq('Windows')
|
||||
expect(described_class.match_os('Windows NT')).to eq('Windows')
|
||||
expect(described_class.match_os('Win32')).to eq('Windows')
|
||||
end
|
||||
|
||||
it 'returns Linux for lin-like strings' do
|
||||
expect(described_class.match_os('Linux')).to eq('Linux')
|
||||
expect(described_class.match_os('Lin')).to eq('Linux')
|
||||
end
|
||||
|
||||
it 'returns Mac for os x, osx, mac-like strings' do
|
||||
expect(described_class.match_os('Mac OS X')).to eq('Mac')
|
||||
expect(described_class.match_os('OSX')).to eq('Mac')
|
||||
expect(described_class.match_os('Macintosh')).to eq('Mac')
|
||||
end
|
||||
|
||||
it 'returns iOS for iphone, ipad, ipod' do
|
||||
expect(described_class.match_os('iPhone')).to eq('iOS')
|
||||
expect(described_class.match_os('iPad')).to eq('iOS')
|
||||
expect(described_class.match_os('iPod')).to eq('iOS')
|
||||
expect(described_class.match_os('iOS')).to eq('iOS')
|
||||
end
|
||||
|
||||
it 'returns Android for android-like strings' do
|
||||
expect(described_class.match_os('Android')).to eq('Android')
|
||||
end
|
||||
|
||||
it 'returns BlackBerry for blackberry-like strings' do
|
||||
expect(described_class.match_os('BlackBerry')).to eq('BlackBerry')
|
||||
end
|
||||
|
||||
it 'returns QNX for qnx-like strings' do
|
||||
expect(described_class.match_os('QNX')).to eq('QNX')
|
||||
end
|
||||
|
||||
it 'returns SunOS for sun-like strings' do
|
||||
expect(described_class.match_os('SunOS')).to eq('SunOS')
|
||||
end
|
||||
|
||||
it 'is case insensitive' do
|
||||
expect(described_class.match_os('WINDOWS')).to eq('Windows')
|
||||
expect(described_class.match_os('linux')).to eq('Linux')
|
||||
expect(described_class.match_os('ANDROID')).to eq('Android')
|
||||
end
|
||||
|
||||
it 'returns ALL for unknown OS strings' do
|
||||
expect(described_class.match_os('UnknownOS')).to eq('ALL')
|
||||
expect(described_class.match_os('')).to eq('ALL')
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -59,6 +59,7 @@ RSpec.describe 'BeEF::Core::Crypto' do
|
||||
it 'raises TypeError for invalid inputs' do
|
||||
expect { BeEF::Core::Crypto.random_hex_string('invalid') }.to raise_error(TypeError)
|
||||
expect { BeEF::Core::Crypto.random_hex_string(0) }.to raise_error(TypeError, /Invalid length/)
|
||||
expect { BeEF::Core::Crypto.random_hex_string(-1) }.to raise_error(TypeError, /Invalid length/)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -412,5 +412,158 @@ RSpec.describe BeEF::Core::Handlers::BrowserDetails do
|
||||
expect(BeEF::Core::Models::BrowserDetails).to receive(:set).with(session_id, 'network.proxy.server', 'proxy.example.com')
|
||||
described_class.new(proxy_data)
|
||||
end
|
||||
|
||||
context 'filter failures (err_msg branches)' do
|
||||
# Full data with optional keys so later filters (e.g. battery, capabilities) don't trigger err_msg
|
||||
let(:full_data) do
|
||||
data.merge('results' => data['results'].merge(
|
||||
'hardware.battery.level' => '50%',
|
||||
'browser.name.reported' => 'Mozilla/5.0',
|
||||
'browser.engine' => 'Gecko',
|
||||
'browser.window.cookies' => 'session=abc',
|
||||
'host.os.name' => 'Windows',
|
||||
'host.os.family' => 'Windows',
|
||||
'host.os.version' => '10',
|
||||
'browser.capabilities.vbscript' => 'yes'
|
||||
))
|
||||
end
|
||||
|
||||
def stub_all_filters_valid_except(except_key = nil)
|
||||
%i[
|
||||
is_valid_hook_session_id? is_valid_browsername? is_valid_browserversion? is_valid_ip?
|
||||
is_valid_browserstring? is_valid_cookies? is_valid_osname? is_valid_hwname?
|
||||
is_valid_date_stamp? is_valid_pagetitle? is_valid_url? is_valid_pagereferrer?
|
||||
is_valid_hostname? is_valid_port? is_valid_browser_plugins? is_valid_system_platform?
|
||||
nums_only? is_valid_yes_no? is_valid_memory? is_valid_gpu? is_valid_cpu? alphanums_only?
|
||||
].each do |m|
|
||||
allow(BeEF::Filters).to receive(m).and_return(except_key == m ? false : true)
|
||||
end
|
||||
end
|
||||
|
||||
it 'calls err_msg when browser name is invalid' do
|
||||
allow(BeEF::Core::Models::HookedBrowser).to receive(:where).and_return([])
|
||||
stub_all_filters_valid_except(:is_valid_browsername?)
|
||||
allow(BeEF::Core::Models::BrowserDetails).to receive(:set)
|
||||
allow(BeEF::Core::Constants::Browsers).to receive(:friendly_name)
|
||||
zombie = double('HookedBrowser', id: 1, ip: '127.0.0.1')
|
||||
allow(zombie).to receive(:firstseen=)
|
||||
allow(zombie).to receive(:domain=)
|
||||
allow(zombie).to receive(:port=)
|
||||
allow(zombie).to receive(:httpheaders=)
|
||||
allow(zombie).to receive(:httpheaders).and_return('{}')
|
||||
allow(zombie).to receive(:save!)
|
||||
allow(JSON).to receive(:parse).with('{}').and_return({})
|
||||
allow(BeEF::Core::Models::HookedBrowser).to receive(:new).and_return(zombie)
|
||||
err_msg_calls = []
|
||||
allow_any_instance_of(described_class).to receive(:err_msg) { |*args| err_msg_calls << args.last }
|
||||
described_class.new(full_data)
|
||||
expect(err_msg_calls).to include(a_string_matching(/Invalid browser name/))
|
||||
end
|
||||
|
||||
it 'calls err_msg when IP is invalid' do
|
||||
allow(BeEF::Core::Models::HookedBrowser).to receive(:where).and_return([])
|
||||
stub_all_filters_valid_except(:is_valid_ip?)
|
||||
allow(BeEF::Core::Models::BrowserDetails).to receive(:set)
|
||||
allow(BeEF::Core::Constants::Browsers).to receive(:friendly_name).and_return('Firefox')
|
||||
zombie = double('HookedBrowser', id: 1, ip: '127.0.0.1')
|
||||
allow(zombie).to receive(:firstseen=)
|
||||
allow(zombie).to receive(:domain=)
|
||||
allow(zombie).to receive(:port=)
|
||||
allow(zombie).to receive(:httpheaders=)
|
||||
allow(zombie).to receive(:httpheaders).and_return('{}')
|
||||
allow(zombie).to receive(:save!)
|
||||
allow(JSON).to receive(:parse).with('{}').and_return({})
|
||||
allow(BeEF::Core::Models::HookedBrowser).to receive(:new).and_return(zombie)
|
||||
err_msg_calls = []
|
||||
allow_any_instance_of(described_class).to receive(:err_msg) { |*args| err_msg_calls << args.last }
|
||||
described_class.new(full_data)
|
||||
expect(err_msg_calls).to include(a_string_matching(/Invalid IP address/))
|
||||
end
|
||||
|
||||
it 'calls err_msg when browser version is invalid' do
|
||||
allow(BeEF::Core::Models::HookedBrowser).to receive(:where).and_return([])
|
||||
stub_all_filters_valid_except(:is_valid_browserversion?)
|
||||
allow(BeEF::Core::Models::BrowserDetails).to receive(:set)
|
||||
allow(BeEF::Core::Constants::Browsers).to receive(:friendly_name).and_return('Firefox')
|
||||
zombie = double('HookedBrowser', id: 1, ip: '127.0.0.1')
|
||||
allow(zombie).to receive(:firstseen=)
|
||||
allow(zombie).to receive(:domain=)
|
||||
allow(zombie).to receive(:port=)
|
||||
allow(zombie).to receive(:httpheaders=)
|
||||
allow(zombie).to receive(:httpheaders).and_return('{}')
|
||||
allow(zombie).to receive(:save!)
|
||||
allow(JSON).to receive(:parse).with('{}').and_return({})
|
||||
allow(BeEF::Core::Models::HookedBrowser).to receive(:new).and_return(zombie)
|
||||
err_msg_calls = []
|
||||
allow_any_instance_of(described_class).to receive(:err_msg) { |*args| err_msg_calls << args.last }
|
||||
described_class.new(full_data)
|
||||
expect(err_msg_calls).to include(a_string_matching(/Invalid browser version/))
|
||||
end
|
||||
|
||||
it 'calls err_msg when browser.name.reported is invalid' do
|
||||
allow(BeEF::Core::Models::HookedBrowser).to receive(:where).and_return([])
|
||||
allow(BeEF::Filters).to receive(:is_valid_browsername?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_browserversion?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_ip?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_browserstring?).and_return(false)
|
||||
stub_all_filters_valid_except(nil)
|
||||
allow(BeEF::Filters).to receive(:is_valid_browserstring?).and_return(false)
|
||||
allow(BeEF::Core::Models::BrowserDetails).to receive(:set)
|
||||
allow(BeEF::Core::Constants::Browsers).to receive(:friendly_name).and_return('Firefox')
|
||||
zombie = double('HookedBrowser', id: 1, ip: '127.0.0.1')
|
||||
allow(zombie).to receive(:firstseen=)
|
||||
allow(zombie).to receive(:domain=)
|
||||
allow(zombie).to receive(:port=)
|
||||
allow(zombie).to receive(:httpheaders=)
|
||||
allow(zombie).to receive(:httpheaders).and_return('{}')
|
||||
allow(zombie).to receive(:save!)
|
||||
allow(JSON).to receive(:parse).with('{}').and_return({})
|
||||
allow(BeEF::Core::Models::HookedBrowser).to receive(:new).and_return(zombie)
|
||||
err_msg_calls = []
|
||||
allow_any_instance_of(described_class).to receive(:err_msg) { |*args| err_msg_calls << args.last }
|
||||
described_class.new(full_data)
|
||||
expect(err_msg_calls).to include(a_string_matching(/browser\.name\.reported/))
|
||||
end
|
||||
|
||||
it 'calls err_msg when cookies are invalid' do
|
||||
allow(BeEF::Core::Models::HookedBrowser).to receive(:where).and_return([])
|
||||
stub_all_filters_valid_except(:is_valid_cookies?)
|
||||
allow(BeEF::Core::Models::BrowserDetails).to receive(:set)
|
||||
allow(BeEF::Core::Constants::Browsers).to receive(:friendly_name).and_return('Firefox')
|
||||
zombie = double('HookedBrowser', id: 1, ip: '127.0.0.1')
|
||||
allow(zombie).to receive(:firstseen=)
|
||||
allow(zombie).to receive(:domain=)
|
||||
allow(zombie).to receive(:port=)
|
||||
allow(zombie).to receive(:httpheaders=)
|
||||
allow(zombie).to receive(:httpheaders).and_return('{}')
|
||||
allow(zombie).to receive(:save!)
|
||||
allow(JSON).to receive(:parse).with('{}').and_return({})
|
||||
allow(BeEF::Core::Models::HookedBrowser).to receive(:new).and_return(zombie)
|
||||
err_msg_calls = []
|
||||
allow_any_instance_of(described_class).to receive(:err_msg) { |*args| err_msg_calls << args.last }
|
||||
described_class.new(full_data)
|
||||
expect(err_msg_calls).to include(a_string_matching(/Invalid cookies/))
|
||||
end
|
||||
|
||||
it 'calls err_msg when host.os.name is invalid' do
|
||||
allow(BeEF::Core::Models::HookedBrowser).to receive(:where).and_return([])
|
||||
stub_all_filters_valid_except(:is_valid_osname?)
|
||||
allow(BeEF::Core::Models::BrowserDetails).to receive(:set)
|
||||
allow(BeEF::Core::Constants::Browsers).to receive(:friendly_name).and_return('Firefox')
|
||||
zombie = double('HookedBrowser', id: 1, ip: '127.0.0.1')
|
||||
allow(zombie).to receive(:firstseen=)
|
||||
allow(zombie).to receive(:domain=)
|
||||
allow(zombie).to receive(:port=)
|
||||
allow(zombie).to receive(:httpheaders=)
|
||||
allow(zombie).to receive(:httpheaders).and_return('{}')
|
||||
allow(zombie).to receive(:save!)
|
||||
allow(JSON).to receive(:parse).with('{}').and_return({})
|
||||
allow(BeEF::Core::Models::HookedBrowser).to receive(:new).and_return(zombie)
|
||||
err_msg_calls = []
|
||||
allow_any_instance_of(described_class).to receive(:err_msg) { |*args| err_msg_calls << args.last }
|
||||
described_class.new(full_data)
|
||||
expect(err_msg_calls).to include(a_string_matching(/operating system name/))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,37 +4,160 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BeEF::Core::Handlers::HookedBrowsers do
|
||||
# Test the confirm_browser_user_agent logic directly
|
||||
describe 'confirm_browser_user_agent logic' do
|
||||
it 'matches legacy browser user agents' do
|
||||
allow(BeEF::Core::Models::LegacyBrowserUserAgents).to receive(:user_agents).and_return(['IE 8.0'])
|
||||
|
||||
# Test the logic: browser_type = user_agent.split(' ').last
|
||||
user_agent = 'Mozilla/5.0 IE 8.0'
|
||||
browser_type = user_agent.split(' ').last
|
||||
|
||||
# Test the matching logic
|
||||
matched = false
|
||||
BeEF::Core::Models::LegacyBrowserUserAgents.user_agents.each do |ua_string|
|
||||
matched = true if ua_string.include?(browser_type)
|
||||
end
|
||||
|
||||
expect(matched).to be true
|
||||
# .new returns Sinatra::Wrapper; use allocate to get the real class instance for unit testing
|
||||
let(:handler) { described_class.allocate }
|
||||
|
||||
describe "GET '/'" do
|
||||
let(:config) { BeEF::Core::Configuration.instance }
|
||||
# Use a host permitted by Router's host_authorization (.localhost, .test, or config public host)
|
||||
let(:rack_env) { { 'REMOTE_ADDR' => '192.168.1.1', 'HTTP_HOST' => 'localhost' } }
|
||||
|
||||
def app
|
||||
described_class
|
||||
end
|
||||
|
||||
it 'does not match non-legacy browser user agents' do
|
||||
allow(BeEF::Core::Models::LegacyBrowserUserAgents).to receive(:user_agents).and_return([])
|
||||
|
||||
user_agent = 'Chrome/91.0'
|
||||
browser_type = user_agent.split(' ').last
|
||||
|
||||
matched = false
|
||||
BeEF::Core::Models::LegacyBrowserUserAgents.user_agents.each do |ua_string|
|
||||
matched = true if ua_string.include?(browser_type)
|
||||
before do
|
||||
allow(BeEF::Core::Logger.instance).to receive(:register)
|
||||
allow(config).to receive(:get).and_call_original
|
||||
allow(config).to receive(:get).with('beef.http.restful_api.allow_cors').and_return(false)
|
||||
end
|
||||
|
||||
it 'returns 404 when permitted_hooking_subnet is nil' do
|
||||
allow(config).to receive(:get).with('beef.restrictions.permitted_hooking_subnet').and_return(nil)
|
||||
get '/', {}, rack_env
|
||||
expect(last_response.status).to eq(404)
|
||||
end
|
||||
|
||||
it 'returns 404 when permitted_hooking_subnet is empty' do
|
||||
allow(config).to receive(:get).with('beef.restrictions.permitted_hooking_subnet').and_return([])
|
||||
get '/', {}, rack_env
|
||||
expect(last_response.status).to eq(404)
|
||||
end
|
||||
|
||||
it 'returns 404 when client IP is not in permitted subnet' do
|
||||
allow(config).to receive(:get).with('beef.restrictions.permitted_hooking_subnet').and_return(['10.0.0.0/8'])
|
||||
get '/', {}, rack_env
|
||||
expect(last_response.status).to eq(404)
|
||||
end
|
||||
|
||||
it 'returns 404 when client IP is in excluded_hooking_subnet' do
|
||||
allow(config).to receive(:get).with('beef.restrictions.permitted_hooking_subnet').and_return(['0.0.0.0/0'])
|
||||
allow(config).to receive(:get).with('beef.restrictions.excluded_hooking_subnet').and_return(['192.168.1.0/24'])
|
||||
get '/', {}, rack_env
|
||||
expect(last_response.status).to eq(404)
|
||||
end
|
||||
|
||||
it 'returns 200 and hook body when IP permitted, not excluded, no session (new browser)' do
|
||||
allow(config).to receive(:get).with('beef.restrictions.permitted_hooking_subnet').and_return(['192.168.0.0/16'])
|
||||
allow(config).to receive(:get).with('beef.restrictions.excluded_hooking_subnet').and_return([])
|
||||
allow(config).to receive(:get).with('beef.http.hook_session_name').and_return('beefhook')
|
||||
allow(BeEF::Core::Models::HookedBrowser).to receive(:where).and_return([])
|
||||
allow(BeEF::Filters).to receive(:is_valid_hostname?).with('localhost').and_return(true)
|
||||
allow(config).to receive(:get).with('beef.http.websocket.enable').and_return(false)
|
||||
allow_any_instance_of(described_class).to receive(:confirm_browser_user_agent).and_return(false)
|
||||
allow_any_instance_of(described_class).to receive(:legacy_build_beefjs!).with('localhost')
|
||||
get '/', {}, rack_env
|
||||
expect(last_response.status).to eq(200)
|
||||
expect(last_response.headers['Content-Type']).to include('javascript')
|
||||
end
|
||||
|
||||
it 'uses multi_stage_beefjs when websocket disabled and confirm_browser_user_agent true' do
|
||||
allow(config).to receive(:get).with('beef.restrictions.permitted_hooking_subnet').and_return(['0.0.0.0/0'])
|
||||
allow(config).to receive(:get).with('beef.restrictions.excluded_hooking_subnet').and_return([])
|
||||
allow(config).to receive(:get).with('beef.http.hook_session_name').and_return('beefhook')
|
||||
allow(BeEF::Core::Models::HookedBrowser).to receive(:where).and_return([])
|
||||
allow(BeEF::Filters).to receive(:is_valid_hostname?).with('localhost').and_return(true)
|
||||
allow(config).to receive(:get).with('beef.http.websocket.enable').and_return(false)
|
||||
allow_any_instance_of(described_class).to receive(:confirm_browser_user_agent).and_return(true)
|
||||
allow_any_instance_of(described_class).to receive(:multi_stage_beefjs!).with('localhost')
|
||||
get '/', {}, { 'REMOTE_ADDR' => '127.0.0.1', 'HTTP_HOST' => 'localhost' }
|
||||
expect(last_response.status).to eq(200)
|
||||
end
|
||||
|
||||
it 'returns early with empty body when hostname is invalid' do
|
||||
allow(config).to receive(:get).with('beef.restrictions.permitted_hooking_subnet').and_return(['0.0.0.0/0'])
|
||||
allow(config).to receive(:get).with('beef.restrictions.excluded_hooking_subnet').and_return([])
|
||||
allow(config).to receive(:get).with('beef.http.hook_session_name').and_return('beefhook')
|
||||
allow(BeEF::Core::Models::HookedBrowser).to receive(:where).and_return([])
|
||||
# Use permitted host so request reaches handler; stub hostname validation to fail
|
||||
allow(BeEF::Filters).to receive(:is_valid_hostname?).with('localhost').and_return(false)
|
||||
get '/', {}, { 'REMOTE_ADDR' => '127.0.0.1', 'HTTP_HOST' => 'localhost' }
|
||||
expect(last_response.status).to eq(200)
|
||||
expect(last_response.body).to eq('')
|
||||
end
|
||||
|
||||
context 'when session exists (existing browser path)' do
|
||||
let(:hooked_browser) do
|
||||
double('HookedBrowser',
|
||||
id: 1,
|
||||
ip: '192.168.1.1',
|
||||
lastseen: Time.new.to_i - 120,
|
||||
session: 'existing_session',
|
||||
count!: nil,
|
||||
save!: true).tap do |d|
|
||||
allow(d).to receive(:lastseen=)
|
||||
allow(d).to receive(:ip=)
|
||||
end
|
||||
end
|
||||
|
||||
expect(matched).to be false
|
||||
|
||||
before do
|
||||
allow(config).to receive(:get).with('beef.restrictions.permitted_hooking_subnet').and_return(['192.168.0.0/16'])
|
||||
allow(config).to receive(:get).with('beef.restrictions.excluded_hooking_subnet').and_return([])
|
||||
allow(config).to receive(:get).with('beef.http.hook_session_name').and_return('beefhook')
|
||||
allow(config).to receive(:get).with('beef.http.allow_reverse_proxy').and_return(false)
|
||||
relation = double('Relation', first: hooked_browser)
|
||||
allow(BeEF::Core::Models::HookedBrowser).to receive(:where).with(session: 'existing_session').and_return(relation)
|
||||
allow(BeEF::Core::Models::Command).to receive(:where).with(hooked_browser_id: 1, instructions_sent: false).and_return([])
|
||||
allow(BeEF::API::Registrar.instance).to receive(:fire)
|
||||
end
|
||||
|
||||
it 'returns 200 and updates lastseen' do
|
||||
get '/', { 'beefhook' => 'existing_session' }, rack_env
|
||||
expect(last_response.status).to eq(200)
|
||||
expect(hooked_browser).to have_received(:save!)
|
||||
end
|
||||
|
||||
it 'logs zombie comeback when lastseen was more than 60 seconds ago' do
|
||||
get '/', { 'beefhook' => 'existing_session' }, rack_env
|
||||
expect(BeEF::Core::Logger.instance).to have_received(:register).with('Zombie', /appears to have come back online/, '1')
|
||||
end
|
||||
|
||||
it 'calls add_command_instructions for each pending command' do
|
||||
command = double('Command', id: 1, command_module_id: 1)
|
||||
allow(BeEF::Core::Models::Command).to receive(:where).with(hooked_browser_id: 1, instructions_sent: false).and_return([command])
|
||||
expect_any_instance_of(described_class).to receive(:add_command_instructions).with(command, hooked_browser)
|
||||
get '/', { 'beefhook' => 'existing_session' }, rack_env
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#confirm_browser_user_agent' do
|
||||
it 'returns true when user_agent suffix matches a legacy UA string' do
|
||||
allow(BeEF::Core::Models::LegacyBrowserUserAgents).to receive(:user_agents).and_return(['IE 8.0'])
|
||||
|
||||
# browser_type = user_agent.split(' ').last => '8.0'; 'IE 8.0'.include?('8.0') => true
|
||||
expect(handler.confirm_browser_user_agent('Mozilla/5.0 IE 8.0')).to be true
|
||||
end
|
||||
|
||||
it 'returns true when first legacy UA matches' do
|
||||
allow(BeEF::Core::Models::LegacyBrowserUserAgents).to receive(:user_agents).and_return(['IE 8.0', 'Firefox/3.6'])
|
||||
|
||||
expect(handler.confirm_browser_user_agent('Mozilla/5.0 IE 8.0')).to be true
|
||||
end
|
||||
|
||||
it 'returns false when no legacy UA includes the browser type' do
|
||||
allow(BeEF::Core::Models::LegacyBrowserUserAgents).to receive(:user_agents).and_return([])
|
||||
|
||||
expect(handler.confirm_browser_user_agent('Mozilla/5.0 Chrome/91.0')).to be false
|
||||
end
|
||||
|
||||
it 'returns false when legacy list has entries but none match' do
|
||||
allow(BeEF::Core::Models::LegacyBrowserUserAgents).to receive(:user_agents).and_return(['IE 8.0'])
|
||||
|
||||
expect(handler.confirm_browser_user_agent('Chrome/91.0')).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BeEF::Core::Logger do
|
||||
let(:logger) { described_class.instance }
|
||||
let(:log_double) { instance_double(BeEF::Core::Models::Log, save!: true) }
|
||||
|
||||
before do
|
||||
allow(BeEF::Core::Models::Log).to receive(:create).and_return(log_double)
|
||||
allow(logger).to receive(:print_debug)
|
||||
logger.instance_variable_set(:@notifications, nil)
|
||||
end
|
||||
|
||||
describe '#register' do
|
||||
it 'creates a log entry with from, event, and hooked_browser_id' do
|
||||
result = logger.register('Authentication', 'User logged in', 0)
|
||||
|
||||
expect(result).to be true
|
||||
expect(BeEF::Core::Models::Log).to have_received(:create).with(
|
||||
hash_including(
|
||||
logtype: 'Authentication',
|
||||
event: 'User logged in',
|
||||
hooked_browser_id: 0
|
||||
)
|
||||
)
|
||||
expect(log_double).to have_received(:save!)
|
||||
end
|
||||
|
||||
it 'converts hb to integer' do
|
||||
logger.register('From', 'Event', '42')
|
||||
|
||||
expect(BeEF::Core::Models::Log).to have_received(:create).with(
|
||||
hash_including(hooked_browser_id: 42)
|
||||
)
|
||||
end
|
||||
|
||||
it 'defaults hb to 0 when not provided' do
|
||||
logger.register('From', 'Event')
|
||||
|
||||
expect(BeEF::Core::Models::Log).to have_received(:create).with(
|
||||
hash_including(hooked_browser_id: 0)
|
||||
)
|
||||
end
|
||||
|
||||
it 'raises TypeError when from is not a String' do
|
||||
expect { logger.register(123, 'Event', 0) }.to raise_error(
|
||||
TypeError, "'from' is Integer; expected String"
|
||||
)
|
||||
end
|
||||
|
||||
it 'raises TypeError when event is not a String' do
|
||||
expect { logger.register('From', nil, 0) }.to raise_error(
|
||||
TypeError, "'event' is NilClass; expected String"
|
||||
)
|
||||
end
|
||||
|
||||
it 'calls notifications when extension is enabled' do
|
||||
notifications_double = double('Notifications')
|
||||
logger.instance_variable_set(:@notifications, notifications_double)
|
||||
allow(notifications_double).to receive(:new)
|
||||
logger.register('Zombie', 'Browser hooked', 7)
|
||||
expect(notifications_double).to have_received(:new).with(
|
||||
'Zombie',
|
||||
'Browser hooked',
|
||||
kind_of(Time),
|
||||
7
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,123 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BeEF::Core::Models::Command do
|
||||
describe 'associations' do
|
||||
it 'has_many results' do
|
||||
expect(described_class.reflect_on_association(:results)).not_to be_nil
|
||||
expect(described_class.reflect_on_association(:results).macro).to eq(:has_many)
|
||||
end
|
||||
|
||||
it 'has_one command_module' do
|
||||
expect(described_class.reflect_on_association(:command_module)).not_to be_nil
|
||||
expect(described_class.reflect_on_association(:command_module).macro).to eq(:has_one)
|
||||
end
|
||||
|
||||
it 'has_one hooked_browser' do
|
||||
expect(described_class.reflect_on_association(:hooked_browser)).not_to be_nil
|
||||
expect(described_class.reflect_on_association(:hooked_browser).macro).to eq(:has_one)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.show_status' do
|
||||
it 'returns ERROR for status -1' do
|
||||
expect(described_class.show_status(-1)).to eq('ERROR')
|
||||
end
|
||||
|
||||
it 'returns SUCCESS for status 1' do
|
||||
expect(described_class.show_status(1)).to eq('SUCCESS')
|
||||
end
|
||||
|
||||
it 'returns UNKNOWN for status 0' do
|
||||
expect(described_class.show_status(0)).to eq('UNKNOWN')
|
||||
end
|
||||
|
||||
it 'returns UNKNOWN for any other status' do
|
||||
expect(described_class.show_status(2)).to eq('UNKNOWN')
|
||||
expect(described_class.show_status(99)).to eq('UNKNOWN')
|
||||
end
|
||||
end
|
||||
|
||||
describe '.save_result' do
|
||||
let(:hooked_browser) { BeEF::Core::Models::HookedBrowser.create!(session: 'cmd_save_session', ip: '127.0.0.1') }
|
||||
let(:command_module) { BeEF::Core::Models::CommandModule.create!(name: 'cmd_save_mod', path: 'modules/test/') }
|
||||
let(:command) do
|
||||
described_class.create!(
|
||||
hooked_browser_id: hooked_browser.id,
|
||||
command_module_id: command_module.id
|
||||
)
|
||||
end
|
||||
|
||||
before do
|
||||
allow(BeEF::Core::Logger.instance).to receive(:register)
|
||||
allow(described_class).to receive(:print_info)
|
||||
end
|
||||
|
||||
it 'creates a Result and returns true when all args are valid' do
|
||||
result = described_class.save_result(
|
||||
'cmd_save_session',
|
||||
command.id,
|
||||
'Friendly Name',
|
||||
{ 'output' => 'data' },
|
||||
1
|
||||
)
|
||||
|
||||
expect(result).to be true
|
||||
created = BeEF::Core::Models::Result.last
|
||||
expect(created).not_to be_nil
|
||||
expect(created.command_id).to eq(command.id)
|
||||
expect(created.hooked_browser_id).to eq(hooked_browser.id)
|
||||
expect(created.status).to eq(1)
|
||||
expect(JSON.parse(created.data)).to eq({ 'output' => 'data' })
|
||||
end
|
||||
|
||||
it 'raises TypeError when hook_session_id is not a String' do
|
||||
expect do
|
||||
described_class.save_result(123, command.id, 'Name', {}, 1)
|
||||
end.to raise_error(TypeError, '"hook_session_id" needs to be a string')
|
||||
end
|
||||
|
||||
it 'raises TypeError when command_id is not an Integer' do
|
||||
expect do
|
||||
described_class.save_result('cmd_save_session', '1', 'Name', {}, 1)
|
||||
end.to raise_error(TypeError, '"command_id" needs to be an integer')
|
||||
end
|
||||
|
||||
it 'raises TypeError when command_friendly_name is not a String' do
|
||||
expect do
|
||||
described_class.save_result('cmd_save_session', command.id, 123, {}, 1)
|
||||
end.to raise_error(TypeError, '"command_friendly_name" needs to be a string')
|
||||
end
|
||||
|
||||
it 'raises TypeError when result is not a Hash' do
|
||||
expect do
|
||||
described_class.save_result('cmd_save_session', command.id, 'Name', 'string', 1)
|
||||
end.to raise_error(TypeError, '"result" needs to be a hash')
|
||||
end
|
||||
|
||||
it 'raises TypeError when status is not an Integer' do
|
||||
expect do
|
||||
described_class.save_result('cmd_save_session', command.id, 'Name', {}, '1')
|
||||
end.to raise_error(TypeError, '"status" needs to be an integer')
|
||||
end
|
||||
|
||||
it 'raises TypeError when hooked_browser is not found for session' do
|
||||
expect do
|
||||
described_class.save_result('nonexistent_session', command.id, 'Name', {}, 1)
|
||||
end.to raise_error(TypeError, 'hooked_browser is nil')
|
||||
end
|
||||
|
||||
it 'raises TypeError when command is not found for id and hooked_browser' do
|
||||
BeEF::Core::Models::HookedBrowser.create!(session: 'other_session', ip: '127.0.0.1')
|
||||
|
||||
expect do
|
||||
described_class.save_result('other_session', command.id, 'Name', {}, 1)
|
||||
end.to raise_error(TypeError, 'command is nil')
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,26 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BeEF::Core::Models::CommandModule do
|
||||
describe 'associations' do
|
||||
it 'has_many commands' do
|
||||
expect(described_class.reflect_on_association(:commands)).not_to be_nil
|
||||
expect(described_class.reflect_on_association(:commands).macro).to eq(:has_many)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.create' do
|
||||
it 'creates a command module with name and path' do
|
||||
mod = described_class.create!(name: 'test_module', path: 'modules/test/')
|
||||
|
||||
expect(mod).to be_persisted
|
||||
expect(mod.name).to eq('test_module')
|
||||
expect(mod.path).to eq('modules/test/')
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,44 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BeEF::Core::Models::HookedBrowser do
|
||||
describe 'associations' do
|
||||
it 'has_many commands' do
|
||||
expect(described_class.reflect_on_association(:commands)).not_to be_nil
|
||||
expect(described_class.reflect_on_association(:commands).macro).to eq(:has_many)
|
||||
end
|
||||
|
||||
it 'has_many results' do
|
||||
expect(described_class.reflect_on_association(:results)).not_to be_nil
|
||||
expect(described_class.reflect_on_association(:results).macro).to eq(:has_many)
|
||||
end
|
||||
|
||||
it 'has_many logs' do
|
||||
expect(described_class.reflect_on_association(:logs)).not_to be_nil
|
||||
expect(described_class.reflect_on_association(:logs).macro).to eq(:has_many)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#count!' do
|
||||
it 'sets count to 1 when count is nil' do
|
||||
hb = described_class.create!(session: 'count_nil', ip: '127.0.0.1', count: nil)
|
||||
|
||||
hb.count!
|
||||
|
||||
expect(hb.count).to eq(1)
|
||||
end
|
||||
|
||||
it 'increments count when count is already set' do
|
||||
hb = described_class.create!(session: 'count_set', ip: '127.0.0.1', count: 3)
|
||||
|
||||
hb.count!
|
||||
|
||||
expect(hb.count).to eq(4)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,42 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BeEF::Core::Models::Log do
|
||||
describe 'associations' do
|
||||
it 'has_one hooked_browser' do
|
||||
expect(described_class.reflect_on_association(:hooked_browser)).not_to be_nil
|
||||
expect(described_class.reflect_on_association(:hooked_browser).macro).to eq(:has_one)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.create' do
|
||||
it 'creates a log with logtype, event, and date' do
|
||||
log = described_class.create!(
|
||||
logtype: 'TestSource',
|
||||
event: 'Test event message',
|
||||
date: Time.now
|
||||
)
|
||||
|
||||
expect(log).to be_persisted
|
||||
expect(log.logtype).to eq('TestSource')
|
||||
expect(log.event).to eq('Test event message')
|
||||
end
|
||||
|
||||
it 'can store hooked_browser_id' do
|
||||
hb = BeEF::Core::Models::HookedBrowser.create!(session: 'log_hb', ip: '127.0.0.1')
|
||||
log = described_class.create!(
|
||||
logtype: 'Hook',
|
||||
event: 'Browser hooked',
|
||||
date: Time.now,
|
||||
hooked_browser_id: hb.id
|
||||
)
|
||||
|
||||
expect(log.hooked_browser_id).to eq(hb.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -110,4 +110,68 @@ RSpec.describe BeEF::Core::Server do
|
||||
server.remap
|
||||
end
|
||||
end
|
||||
|
||||
describe '#prepare' do
|
||||
before do
|
||||
allow(BeEF::API::Registrar.instance).to receive(:fire).with(BeEF::API::Server, 'mount_handler', server)
|
||||
allow(config).to receive(:get).and_return(nil)
|
||||
allow(config).to receive(:get).with('beef.http.hook_file').and_return('/hook.js')
|
||||
allow(config).to receive(:get).with('beef.http.host').and_return('0.0.0.0')
|
||||
allow(config).to receive(:get).with('beef.http.port').and_return('3000')
|
||||
allow(config).to receive(:get).with('beef.http.debug').and_return(false)
|
||||
allow(config).to receive(:get).with('beef.http.https.enable').and_return(false)
|
||||
allow(config).to receive(:get).with('beef.debug').and_return(false)
|
||||
allow(Thin::Server).to receive(:new).and_return(double('Thin::Server'))
|
||||
end
|
||||
|
||||
it 'mounts hook file handler and init handler' do
|
||||
server.prepare
|
||||
expect(server.mounts).to have_key('/hook.js')
|
||||
expect(server.mounts).to have_key('/init')
|
||||
expect(server.mounts['/hook.js']).not_to be_nil
|
||||
expect(server.mounts['/init']).to eq(BeEF::Core::Handlers::BrowserDetails)
|
||||
end
|
||||
|
||||
it 'builds Rack URLMap from mounts' do
|
||||
server.prepare
|
||||
expect(server.instance_variable_get(:@rack_app)).to be_a(Rack::URLMap)
|
||||
end
|
||||
|
||||
it 'returns early when @http_server already set' do
|
||||
allow(Thin::Server).to receive(:new)
|
||||
existing = double('Thin::Server')
|
||||
server.instance_variable_set(:@http_server, existing)
|
||||
server.prepare
|
||||
expect(Thin::Server).not_to have_received(:new)
|
||||
end
|
||||
|
||||
it 'sets Thin::Logging when beef.http.debug is true' do
|
||||
allow(config).to receive(:get).with('beef.http.debug').and_return(true)
|
||||
allow(Thin::Logging).to receive(:silent=)
|
||||
allow(Thin::Logging).to receive(:debug=)
|
||||
server.prepare
|
||||
expect(Thin::Logging).to have_received(:silent=).with(false)
|
||||
expect(Thin::Logging).to have_received(:debug=).with(true)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#start' do
|
||||
it 'rescues port-in-use error and exits' do
|
||||
mock_thin = double('Thin::Server')
|
||||
allow(mock_thin).to receive(:start).and_raise(RuntimeError.new('no acceptor'))
|
||||
server.instance_variable_set(:@http_server, mock_thin)
|
||||
allow(server).to receive(:print_error)
|
||||
allow(server).to receive(:exit).with(127) { raise SystemExit.new(127) }
|
||||
expect { server.start }.to raise_error(SystemExit)
|
||||
expect(server).to have_received(:print_error).with(/port|invalid IP/i)
|
||||
expect(server).to have_received(:exit).with(127)
|
||||
end
|
||||
|
||||
it 're-raises RuntimeError when message does not include no acceptor' do
|
||||
mock_thin = double('Thin::Server')
|
||||
allow(mock_thin).to receive(:start).and_raise(RuntimeError.new('other error'))
|
||||
server.instance_variable_set(:@http_server, mock_thin)
|
||||
expect { server.start }.to raise_error(RuntimeError, 'other error')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -25,4 +25,30 @@ RSpec.describe 'Security method overrides' do
|
||||
expect(Kernel.method(:system).source_location).not_to be_nil
|
||||
expect(Kernel.method(:system).source_location[0]).to include('core/ruby/security.rb')
|
||||
end
|
||||
|
||||
describe 'override behavior' do
|
||||
it 'exec prints security message and exits' do
|
||||
allow(Kernel).to receive(:puts)
|
||||
allow(Kernel).to receive(:exit)
|
||||
exec('ls')
|
||||
expect(Kernel).to have_received(:puts).with(/security reasons.*exec/)
|
||||
expect(Kernel).to have_received(:exit)
|
||||
end
|
||||
|
||||
it 'system prints security message and exits' do
|
||||
allow(Kernel).to receive(:puts)
|
||||
allow(Kernel).to receive(:exit)
|
||||
system('ls')
|
||||
expect(Kernel).to have_received(:puts).with(/security reasons.*system/)
|
||||
expect(Kernel).to have_received(:exit)
|
||||
end
|
||||
|
||||
it 'Kernel.system prints security message and exits' do
|
||||
allow(Kernel).to receive(:puts)
|
||||
allow(Kernel).to receive(:exit)
|
||||
Kernel.system('ls')
|
||||
expect(Kernel).to have_received(:puts).with(/security reasons.*system/)
|
||||
expect(Kernel).to have_received(:exit)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
# Unit tests for every module under modules/browser/ (including hooked_origin).
|
||||
# Branch coverage: extra post_execute for modules that set BrowserDetails when results match.
|
||||
#
|
||||
|
||||
require_relative '../../../spec_helper'
|
||||
|
||||
project_root = File.expand_path('../../../../', __dir__)
|
||||
browser_module_paths = Dir[File.join(project_root, 'modules/browser/**/module.rb')].sort
|
||||
|
||||
# Load branch coverage data from centralised config
|
||||
BRANCH_COVERAGE_BROWSER = BeefTestConfig.branch_coverage_for(:browser)
|
||||
|
||||
browser_module_paths.each do |path|
|
||||
rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '')
|
||||
branch_key = File.dirname(path).sub("#{project_root}/", '')
|
||||
require_path = File.join('../../../../', rel)
|
||||
class_line = File.read(path).lines.find { |l| l =~ /\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/ }
|
||||
next unless class_line
|
||||
|
||||
klass_name = class_line.match(/\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/)[1]
|
||||
require_relative require_path
|
||||
mod = Object.const_get(klass_name)
|
||||
|
||||
RSpec.describe mod do
|
||||
if described_class.respond_to?(:options)
|
||||
describe '.options' do
|
||||
it 'returns an Array' do
|
||||
config = instance_double(BeEF::Core::Configuration)
|
||||
allow(config).to receive(:beef_host).and_return('127.0.0.1')
|
||||
allow(config).to receive(:beef_proto).and_return('http')
|
||||
allow(config).to receive(:beef_port).and_return('3000')
|
||||
allow(config).to receive(:beef_url_str).and_return('http://127.0.0.1:3000')
|
||||
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
|
||||
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
|
||||
|
||||
expect(described_class.options).to be_an(Array)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if described_class.method_defined?(:pre_send)
|
||||
describe '#pre_send' do
|
||||
it 'runs without error' do
|
||||
handler = instance_double('AssetHandler')
|
||||
allow(handler).to receive(:unbind).and_return(nil)
|
||||
allow(handler).to receive(:bind).and_return(nil)
|
||||
allow(handler).to receive(:bind_raw).and_return(nil)
|
||||
allow(handler).to receive(:remap).and_return(nil)
|
||||
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
|
||||
config = instance_double(BeEF::Core::Configuration)
|
||||
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
|
||||
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
|
||||
instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0')
|
||||
expect { run_pre_send(instance) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if described_class.method_defined?(:post_execute)
|
||||
describe '#post_execute' do
|
||||
it 'runs without error' do
|
||||
handler = instance_double('AssetHandler')
|
||||
allow(handler).to receive(:unbind)
|
||||
allow(handler).to receive(:bind)
|
||||
allow(handler).to receive(:remap)
|
||||
allow(handler).to receive(:bind_raw)
|
||||
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
|
||||
# Some modules (e.g. Spyder_eye) write screenshot files to $home_dir on the success path.
|
||||
# Stub File.open with a block-aware double so the test never touches the real filesystem.
|
||||
file_double = double('File').as_null_object
|
||||
allow(File).to receive(:open) do |*_args, &block|
|
||||
block ? block.call(file_double) : file_double
|
||||
end
|
||||
|
||||
instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0')
|
||||
expect { run_post_execute(instance) }.not_to raise_error
|
||||
end
|
||||
|
||||
if BRANCH_COVERAGE_BROWSER[branch_key]
|
||||
it 'runs branch path with realistic datastore' do
|
||||
branch = BRANCH_COVERAGE_BROWSER[branch_key]
|
||||
|
||||
handler = instance_double('AssetHandler')
|
||||
allow(handler).to receive(:unbind)
|
||||
allow(handler).to receive(:bind)
|
||||
allow(handler).to receive(:remap)
|
||||
allow(handler).to receive(:bind_raw)
|
||||
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
|
||||
allow(BeEF::Core::Models::BrowserDetails).to receive(:set)
|
||||
instance = build_command_instance(described_class, branch[:datastore])
|
||||
expect { run_post_execute(instance) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,71 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
# Unit tests for every module under modules/chrome_extensions/.
|
||||
# Each directory is tested for .options (when present) and #post_execute.
|
||||
#
|
||||
|
||||
require_relative '../../../spec_helper'
|
||||
|
||||
project_root = File.expand_path('../../../../', __dir__)
|
||||
chrome_module_paths = Dir[File.join(project_root, 'modules/chrome_extensions/**/module.rb')].sort
|
||||
|
||||
chrome_module_paths.each do |path|
|
||||
rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '')
|
||||
require_path = File.join('../../../../', rel)
|
||||
class_line = File.read(path).lines.find { |l| l =~ /\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/ }
|
||||
next unless class_line
|
||||
|
||||
klass_name = class_line.match(/\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/)[1]
|
||||
require_relative require_path
|
||||
mod = Object.const_get(klass_name)
|
||||
|
||||
RSpec.describe mod do
|
||||
if described_class.respond_to?(:options)
|
||||
describe '.options' do
|
||||
it 'returns an Array' do
|
||||
config = instance_double(BeEF::Core::Configuration)
|
||||
allow(config).to receive(:beef_host).and_return('127.0.0.1')
|
||||
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
|
||||
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
|
||||
|
||||
expect(described_class.options).to be_an(Array)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if described_class.method_defined?(:pre_send)
|
||||
describe '#pre_send' do
|
||||
it 'runs without error' do
|
||||
handler = instance_double('AssetHandler')
|
||||
allow(handler).to receive(:unbind).and_return(nil)
|
||||
allow(handler).to receive(:bind).and_return(nil)
|
||||
allow(handler).to receive(:bind_raw).and_return(nil)
|
||||
allow(handler).to receive(:remap).and_return(nil)
|
||||
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
|
||||
config = instance_double(BeEF::Core::Configuration)
|
||||
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
|
||||
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
|
||||
instance = build_command_instance(described_class, 'return' => '', 'result' => '', 'cid' => '0')
|
||||
expect { run_pre_send(instance) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if described_class.method_defined?(:post_execute)
|
||||
describe '#post_execute' do
|
||||
it 'runs without error' do
|
||||
handler = instance_double('AssetHandler')
|
||||
allow(handler).to receive(:unbind)
|
||||
allow(handler).to receive(:bind)
|
||||
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
|
||||
|
||||
instance = build_command_instance(described_class, 'return' => '', 'result' => '', 'cid' => '0')
|
||||
expect { run_post_execute(instance) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,75 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
# Unit tests for every module under modules/debug/.
|
||||
# Each directory is tested for .options (when present) and #post_execute.
|
||||
# See test_beef_debugs_spec.rb for integration/E2E tests (BrowserStack).
|
||||
#
|
||||
|
||||
require_relative '../../../spec_helper'
|
||||
|
||||
project_root = File.expand_path('../../../../', __dir__)
|
||||
debug_module_paths = Dir[File.join(project_root, 'modules/debug/**/module.rb')].sort
|
||||
|
||||
debug_module_paths.each do |path|
|
||||
rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '')
|
||||
require_path = File.join('../../../../', rel)
|
||||
class_line = File.read(path).lines.find { |l| l =~ /\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/ }
|
||||
next unless class_line
|
||||
|
||||
klass_name = class_line.match(/\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/)[1]
|
||||
require_relative require_path
|
||||
mod = Object.const_get(klass_name)
|
||||
|
||||
RSpec.describe mod do
|
||||
if described_class.respond_to?(:options)
|
||||
describe '.options' do
|
||||
it 'returns an Array' do
|
||||
config = instance_double(BeEF::Core::Configuration)
|
||||
allow(config).to receive(:beef_host).and_return('127.0.0.1')
|
||||
allow(config).to receive(:beef_port).and_return('3000')
|
||||
allow(config).to receive(:beef_proto).and_return('http')
|
||||
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
|
||||
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
|
||||
|
||||
expect(described_class.options).to be_an(Array)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if described_class.method_defined?(:pre_send)
|
||||
describe '#pre_send' do
|
||||
it 'runs without error' do
|
||||
handler = instance_double('AssetHandler')
|
||||
allow(handler).to receive(:unbind).and_return(nil)
|
||||
allow(handler).to receive(:bind).and_return(nil)
|
||||
allow(handler).to receive(:bind_raw).and_return(nil)
|
||||
allow(handler).to receive(:bind_redirect).with(anything, anything).and_return(nil)
|
||||
allow(handler).to receive(:remap).and_return(nil)
|
||||
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
|
||||
config = instance_double(BeEF::Core::Configuration)
|
||||
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
|
||||
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
|
||||
instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0')
|
||||
expect { run_pre_send(instance) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if described_class.method_defined?(:post_execute)
|
||||
describe '#post_execute' do
|
||||
it 'runs without error' do
|
||||
handler = instance_double('AssetHandler')
|
||||
allow(handler).to receive(:unbind)
|
||||
allow(handler).to receive(:bind)
|
||||
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
|
||||
|
||||
instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0')
|
||||
expect { run_post_execute(instance) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,110 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
# Unit tests for every module under modules/exploits/.
|
||||
# Branch coverage: extra post_execute for modules with conditional branches
|
||||
# (driven by BeefTestConfig.branch_coverage_for(:exploits) in spec_helper.rb).
|
||||
#
|
||||
|
||||
require_relative '../../../spec_helper'
|
||||
|
||||
project_root = File.expand_path('../../../../', __dir__)
|
||||
exploit_module_paths = Dir[File.join(project_root, 'modules/exploits/**/module.rb')].sort
|
||||
|
||||
BRANCH_COVERAGE_EXPLOITS = BeefTestConfig.branch_coverage_for(:exploits)
|
||||
|
||||
exploit_module_paths.each do |path|
|
||||
rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '')
|
||||
branch_key = File.dirname(path).sub("#{project_root}/", '')
|
||||
require_path = File.join('../../../../', rel)
|
||||
class_line = File.read(path).lines.find { |l| l =~ /\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/ }
|
||||
next unless class_line
|
||||
|
||||
klass_name = class_line.match(/\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/)[1]
|
||||
require_relative require_path
|
||||
mod = Object.const_get(klass_name)
|
||||
|
||||
RSpec.describe mod do
|
||||
if described_class.respond_to?(:options)
|
||||
describe '.options' do
|
||||
it 'returns an Array' do
|
||||
config = instance_double(BeEF::Core::Configuration)
|
||||
allow(config).to receive(:beef_host).and_return('127.0.0.1')
|
||||
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
|
||||
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
|
||||
|
||||
expect(described_class.options).to be_an(Array)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if described_class.method_defined?(:pre_send)
|
||||
describe '#pre_send' do
|
||||
it 'runs without error' do
|
||||
handler = instance_double('AssetHandler')
|
||||
allow(handler).to receive(:unbind).and_return(nil)
|
||||
allow(handler).to receive(:bind).and_return(nil)
|
||||
allow(handler).to receive(:bind_raw).and_return(nil)
|
||||
allow(handler).to receive(:remap).and_return(nil)
|
||||
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
|
||||
config = instance_double(BeEF::Core::Configuration)
|
||||
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
|
||||
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
|
||||
instance = build_command_instance(described_class, {})
|
||||
expect { run_pre_send(instance) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if described_class.method_defined?(:post_execute)
|
||||
describe '#post_execute' do
|
||||
it 'runs without error' do
|
||||
handler = instance_double('AssetHandler')
|
||||
allow(handler).to receive(:unbind)
|
||||
allow(handler).to receive(:bind)
|
||||
allow(handler).to receive(:remap)
|
||||
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
|
||||
|
||||
instance = build_command_instance(described_class, {})
|
||||
expect { run_post_execute(instance) }.not_to raise_error
|
||||
end
|
||||
|
||||
if BRANCH_COVERAGE_EXPLOITS[branch_key]
|
||||
it 'runs branch path with realistic datastore' do
|
||||
raw = BRANCH_COVERAGE_EXPLOITS[branch_key]
|
||||
branches = raw.is_a?(Array) ? raw : [raw]
|
||||
|
||||
# NetworkService / NetworkHost belong to the network extension and may not be
|
||||
# loaded during a modules-only spec run. Provide class doubles per-example
|
||||
# so `allow(...).to receive(:create)` resolves regardless.
|
||||
stub_const('BeEF::Core::Models::NetworkService', Class.new { def self.create(*); end }) unless defined?(BeEF::Core::Models::NetworkService)
|
||||
stub_const('BeEF::Core::Models::NetworkHost', Class.new { def self.create(*); end }) unless defined?(BeEF::Core::Models::NetworkHost)
|
||||
|
||||
branches.each do |branch|
|
||||
handler = instance_double('AssetHandler')
|
||||
allow(handler).to receive(:unbind)
|
||||
allow(handler).to receive(:bind)
|
||||
allow(handler).to receive(:remap)
|
||||
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
|
||||
allow(BeEF::Core::Models::NetworkService).to receive(:create)
|
||||
allow(BeEF::Core::Models::NetworkHost).to receive(:create)
|
||||
allow(BeEF::Filters).to receive(:is_valid_ip?).and_return(true)
|
||||
if branch[:config_get]
|
||||
config = instance_double(BeEF::Core::Configuration)
|
||||
allow(config).to receive(:beef_host).and_return('127.0.0.1')
|
||||
allow(config).to receive(:get).with(anything) do |key|
|
||||
branch[:config_get].key?(key) ? branch[:config_get][key] : '127.0.0.1'
|
||||
end
|
||||
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
|
||||
end
|
||||
instance = build_command_instance(described_class, branch[:datastore])
|
||||
expect { run_post_execute(instance) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,131 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
# Unit tests for every module under modules/host/.
|
||||
# Each directory is tested for .options (when present), #pre_send, and #post_execute.
|
||||
# Branch coverage: extra post_execute runs for modules listed in
|
||||
# BeefTestConfig.branch_coverage_for(:host) (defined in spec_helper.rb).
|
||||
#
|
||||
|
||||
require_relative '../../../spec_helper'
|
||||
|
||||
project_root = File.expand_path('../../../../', __dir__)
|
||||
host_module_paths = Dir[File.join(project_root, 'modules/host/**/module.rb')].sort
|
||||
|
||||
BRANCH_COVERAGE_HOST = BeefTestConfig.branch_coverage_for(:host)
|
||||
|
||||
host_module_paths.each do |path|
|
||||
rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '')
|
||||
branch_key = File.dirname(path).sub("#{project_root}/", '')
|
||||
require_path = File.join('../../../../', rel)
|
||||
class_line = File.read(path).lines.find { |l| l =~ /\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/ }
|
||||
next unless class_line
|
||||
|
||||
klass_name = class_line.match(/\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/)[1]
|
||||
require_relative require_path
|
||||
mod = Object.const_get(klass_name)
|
||||
|
||||
RSpec.describe mod do
|
||||
if described_class.respond_to?(:options)
|
||||
describe '.options' do
|
||||
it 'returns an Array' do
|
||||
config = instance_double(BeEF::Core::Configuration)
|
||||
allow(config).to receive(:beef_host).and_return('127.0.0.1')
|
||||
allow(config).to receive(:beef_url_str).and_return('http://127.0.0.1:3000')
|
||||
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
|
||||
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
|
||||
|
||||
expect(described_class.options).to be_an(Array)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if described_class.method_defined?(:pre_send)
|
||||
describe '#pre_send' do
|
||||
it 'runs without error' do
|
||||
handler = instance_double('AssetHandler')
|
||||
allow(handler).to receive(:unbind).and_return(nil)
|
||||
allow(handler).to receive(:bind).and_return(nil)
|
||||
allow(handler).to receive(:bind_raw).and_return(nil)
|
||||
allow(handler).to receive(:remap).and_return(nil)
|
||||
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
|
||||
config = instance_double(BeEF::Core::Configuration)
|
||||
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
|
||||
allow(config).to receive(:beef_host).and_return('127.0.0.1')
|
||||
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
|
||||
# Stub File.open so Hook_default_browser#pre_send doesn't read/write the real
|
||||
# bounce_to_ie_configured.pdf in modules/host/hook_default_browser/.
|
||||
# The source calls File.open(path, 'w') without a block and File.open(path, 'r') { |f| ... } with one.
|
||||
file_double = double('File').as_null_object
|
||||
allow(File).to receive(:open) do |*_args, &block|
|
||||
block ? block.call(file_double) : file_double
|
||||
end
|
||||
instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0')
|
||||
expect { run_pre_send(instance) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if described_class.method_defined?(:post_execute)
|
||||
describe '#post_execute' do
|
||||
it 'runs without error' do
|
||||
handler = instance_double('AssetHandler')
|
||||
allow(handler).to receive(:unbind)
|
||||
allow(handler).to receive(:bind)
|
||||
allow(handler).to receive(:remap)
|
||||
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
|
||||
|
||||
file_double = double('File').as_null_object
|
||||
allow(File).to receive(:open).and_return(file_double)
|
||||
allow(BeEF::Core::Models::Command).to receive(:save_result)
|
||||
allow_any_instance_of(described_class).to receive(:ip).and_return('0.0.0.0')
|
||||
allow_any_instance_of(described_class).to receive(:timestamp).and_return('0')
|
||||
|
||||
# Minimal datastore so modules that call .sub/.to_s on keys don't get nil
|
||||
instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0')
|
||||
expect { run_post_execute(instance) }.not_to raise_error
|
||||
end
|
||||
|
||||
if BRANCH_COVERAGE_HOST[branch_key]
|
||||
it 'runs branch path with realistic datastore' do
|
||||
branch = BRANCH_COVERAGE_HOST[branch_key]
|
||||
|
||||
# NetworkService / NetworkHost belong to the network extension and may not be
|
||||
# loaded during a modules-only spec run. Provide class doubles per-example
|
||||
# so `allow(...).to receive(:create)` resolves regardless.
|
||||
stub_const('BeEF::Core::Models::NetworkService', Class.new { def self.create(*); end }) unless defined?(BeEF::Core::Models::NetworkService)
|
||||
stub_const('BeEF::Core::Models::NetworkHost', Class.new { def self.create(*); end }) unless defined?(BeEF::Core::Models::NetworkHost)
|
||||
|
||||
handler = instance_double('AssetHandler')
|
||||
allow(handler).to receive(:unbind)
|
||||
allow(handler).to receive(:bind)
|
||||
allow(handler).to receive(:remap)
|
||||
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
|
||||
file_double = double('File').as_null_object
|
||||
allow(File).to receive(:open).and_return(file_double)
|
||||
allow(BeEF::Core::Models::Command).to receive(:save_result)
|
||||
allow_any_instance_of(described_class).to receive(:ip).and_return('0.0.0.0')
|
||||
allow_any_instance_of(described_class).to receive(:timestamp).and_return('0')
|
||||
allow(BeEF::Core::Models::NetworkService).to receive(:create)
|
||||
allow(BeEF::Core::Models::NetworkHost).to receive(:create)
|
||||
allow(BeEF::Core::Models::BrowserDetails).to receive(:get).and_return('Linux')
|
||||
allow(BeEF::Filters).to receive(:is_valid_ip?).and_return(true)
|
||||
|
||||
config = instance_double(BeEF::Core::Configuration)
|
||||
allow(config).to receive(:beef_host).and_return('127.0.0.1')
|
||||
allow(config).to receive(:beef_url_str).and_return('http://127.0.0.1:3000')
|
||||
allow(config).to receive(:get).with(anything) do |key|
|
||||
branch[:config_get].key?(key) ? branch[:config_get][key] : '127.0.0.1'
|
||||
end
|
||||
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
|
||||
|
||||
instance = build_command_instance(described_class, branch[:datastore])
|
||||
expect { run_post_execute(instance) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,113 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
# Unit tests for every module under modules/ipec/.
|
||||
#
|
||||
# Some ipec modules reference BeEF::Extension::ETag and
|
||||
# BeEF::Extension::ServerClientDnsTunnel which are not loaded in unit tests.
|
||||
# We stub these per-example with stub_const so the stubs don't leak across
|
||||
# the suite or shadow the real extensions when they happen to be loaded.
|
||||
#
|
||||
|
||||
require_relative '../../../spec_helper'
|
||||
|
||||
project_root = File.expand_path('../../../../', __dir__)
|
||||
paths = Dir[File.join(project_root, 'modules/ipec/**/module.rb')].sort
|
||||
|
||||
# Lightweight stand-ins for the extension singletons; no real behaviour required.
|
||||
def stub_etag_messages_class
|
||||
Class.new do
|
||||
def self.instance
|
||||
@instance ||= Struct.new(:messages).new({})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def stub_dns_tunnel_server_class
|
||||
Class.new do
|
||||
def self.instance
|
||||
@instance ||= Struct.new(:messages).new({})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
paths.each do |path|
|
||||
rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '')
|
||||
require_path = File.join('../../../../', rel)
|
||||
class_line = File.read(path).lines.find { |l| l =~ /\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/ }
|
||||
next unless class_line
|
||||
|
||||
klass_name = class_line.match(/\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/)[1]
|
||||
require_relative require_path
|
||||
mod = Object.const_get(klass_name)
|
||||
|
||||
RSpec.describe mod do
|
||||
before(:each) do
|
||||
# Per-example stubs (auto-removed after each example).
|
||||
BeEF::Extension.const_set(:ETag, Module.new) unless BeEF::Extension.const_defined?(:ETag)
|
||||
BeEF::Extension.const_set(:ServerClientDnsTunnel, Module.new) unless BeEF::Extension.const_defined?(:ServerClientDnsTunnel)
|
||||
stub_const('BeEF::Extension::ETag::ETagMessages', stub_etag_messages_class) unless BeEF::Extension::ETag.const_defined?(:ETagMessages)
|
||||
stub_const('BeEF::Extension::ServerClientDnsTunnel::Server', stub_dns_tunnel_server_class) unless BeEF::Extension::ServerClientDnsTunnel.const_defined?(:Server)
|
||||
end
|
||||
|
||||
if described_class.respond_to?(:options)
|
||||
describe '.options' do
|
||||
it 'returns an Array' do
|
||||
config = instance_double(BeEF::Core::Configuration)
|
||||
allow(config).to receive(:beef_host).and_return('127.0.0.1')
|
||||
allow(config).to receive(:beef_port).and_return('3000')
|
||||
allow(config).to receive(:beef_proto).and_return('http')
|
||||
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
|
||||
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
|
||||
expect(described_class.options).to be_an(Array)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if described_class.method_defined?(:pre_send)
|
||||
describe '#pre_send' do
|
||||
it 'runs without error' do
|
||||
handler = instance_double('AssetHandler')
|
||||
allow(handler).to receive(:unbind).and_return(nil)
|
||||
allow(handler).to receive(:bind).and_return(nil)
|
||||
allow(handler).to receive(:bind_raw).and_return(nil)
|
||||
allow(handler).to receive(:remap).and_return(nil)
|
||||
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
|
||||
config = instance_double(BeEF::Core::Configuration)
|
||||
allow(config).to receive(:get).with(anything) do |key|
|
||||
case key
|
||||
when 'beef.extension.etag.enable' then true
|
||||
when 'beef.extension.s2c_dns_tunnel.enable' then true
|
||||
when 'beef.extension.s2c_dns_tunnel.zone' then 'example.com'
|
||||
else '127.0.0.1'
|
||||
end
|
||||
end
|
||||
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
|
||||
instance = build_command_instance(described_class, [{ 'name' => 'data', 'value' => 'test' }])
|
||||
expect { run_pre_send(instance) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if described_class.method_defined?(:post_execute)
|
||||
describe '#post_execute' do
|
||||
it 'runs without error' do
|
||||
handler = instance_double('AssetHandler')
|
||||
allow(handler).to receive(:unbind)
|
||||
allow(handler).to receive(:bind)
|
||||
allow(handler).to receive(:remap)
|
||||
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
|
||||
file_double = double('File').as_null_object
|
||||
allow(File).to receive(:open).and_return(file_double)
|
||||
allow(BeEF::Core::Models::Command).to receive(:save_result)
|
||||
allow_any_instance_of(described_class).to receive(:ip).and_return('0.0.0.0')
|
||||
allow_any_instance_of(described_class).to receive(:timestamp).and_return('0')
|
||||
instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0')
|
||||
expect { run_post_execute(instance) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,163 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
# Unit tests for every module under modules/misc/ (including subdirs).
|
||||
# Branch coverage: extra post_execute runs for modules in
|
||||
# BeefTestConfig.branch_coverage_for(:misc) (defined in spec_helper.rb).
|
||||
#
|
||||
|
||||
require_relative '../../../spec_helper'
|
||||
|
||||
project_root = File.expand_path('../../../../', __dir__)
|
||||
paths = Dir[File.join(project_root, 'modules/misc/**/module.rb')].sort
|
||||
|
||||
BRANCH_COVERAGE_MISC = BeefTestConfig.branch_coverage_for(:misc)
|
||||
|
||||
paths.each do |path|
|
||||
rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '')
|
||||
branch_key = File.dirname(path).sub("#{project_root}/", '')
|
||||
require_path = File.join('../../../../', rel)
|
||||
class_line = File.read(path).lines.find { |l| l =~ /\bclass\s+(\w+)\s+<\s+(?:\w+|BeEF::\w+(?:::\w+)*)/ }
|
||||
next unless class_line
|
||||
|
||||
klass_name = class_line.match(/\bclass\s+(\w+)\s+<\s+(?:\w+|BeEF::\w+(?:::\w+)*)/)[1]
|
||||
require_relative require_path
|
||||
mod = Object.const_get(klass_name)
|
||||
|
||||
RSpec.describe mod do
|
||||
if described_class.respond_to?(:options)
|
||||
describe '.options' do
|
||||
it 'returns an Array' do
|
||||
config = instance_double(BeEF::Core::Configuration)
|
||||
allow(config).to receive(:beef_host).and_return('127.0.0.1')
|
||||
allow(config).to receive(:beef_port).and_return('3000')
|
||||
allow(config).to receive(:beef_proto).and_return('http')
|
||||
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
|
||||
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
|
||||
expect(described_class.options).to be_an(Array)
|
||||
end
|
||||
end
|
||||
|
||||
# Targeted structural assertions for selected misc modules. Higher value than
|
||||
# the generic smoke test: checks option count, names, types and shape.
|
||||
if klass_name == 'Wordpress_add_user'
|
||||
describe '.options (Wordpress_add_user specifics)' do
|
||||
it 'includes wordpress path and user options' do
|
||||
config = instance_double(BeEF::Core::Configuration)
|
||||
allow(config).to receive(:beef_host).and_return('127.0.0.1')
|
||||
allow(config).to receive(:beef_port).and_return('3000')
|
||||
allow(config).to receive(:beef_proto).and_return('http')
|
||||
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
|
||||
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
|
||||
|
||||
options = described_class.options
|
||||
expect(options).to be_an(Array)
|
||||
expect(options.length).to eq(5) # wp_path (parent) + username, password, email, role
|
||||
|
||||
wp_path_option = options.find { |opt| opt['name'] == 'wp_path' }
|
||||
expect(wp_path_option).not_to be_nil
|
||||
expect(wp_path_option['value']).to eq('/')
|
||||
|
||||
username_option = options.find { |opt| opt['name'] == 'username' }
|
||||
expect(username_option).not_to be_nil
|
||||
expect(username_option['value']).to eq('beef')
|
||||
|
||||
password_option = options.find { |opt| opt['name'] == 'password' }
|
||||
expect(password_option).not_to be_nil
|
||||
expect(password_option['value']).to be_a(String)
|
||||
expect(password_option['value'].length).to eq(10) # SecureRandom.hex(5) = 10 chars
|
||||
|
||||
role_option = options.find { |opt| opt['name'] == 'role' }
|
||||
expect(role_option).not_to be_nil
|
||||
expect(role_option['value']).to eq('administrator')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if klass_name == 'Test_get_variable'
|
||||
describe '.options (Test_get_variable specifics)' do
|
||||
it 'returns payload_name option with correct structure' do
|
||||
config = instance_double(BeEF::Core::Configuration)
|
||||
allow(config).to receive(:beef_host).and_return('127.0.0.1')
|
||||
allow(config).to receive(:beef_port).and_return('3000')
|
||||
allow(config).to receive(:beef_proto).and_return('http')
|
||||
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
|
||||
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
|
||||
|
||||
options = described_class.options
|
||||
expect(options).to be_an(Array)
|
||||
expect(options.length).to eq(1)
|
||||
|
||||
option = options.first
|
||||
expect(option['name']).to eq('payload_name')
|
||||
expect(option['ui_label']).to eq('Payload Name')
|
||||
expect(option['type']).to eq('text')
|
||||
expect(option['value']).to eq('message')
|
||||
expect(option['width']).to eq('400px')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if described_class.method_defined?(:pre_send)
|
||||
describe '#pre_send' do
|
||||
it 'runs without error' do
|
||||
handler = instance_double('AssetHandler')
|
||||
allow(handler).to receive(:unbind).and_return(nil)
|
||||
allow(handler).to receive(:bind).and_return(nil)
|
||||
allow(handler).to receive(:bind_raw).and_return(nil)
|
||||
allow(handler).to receive(:remap).and_return(nil)
|
||||
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
|
||||
config = instance_double(BeEF::Core::Configuration)
|
||||
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
|
||||
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
|
||||
allow(IO).to receive(:popen).and_return(StringIO.new(''))
|
||||
instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0')
|
||||
expect { run_pre_send(instance) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if described_class.method_defined?(:post_execute)
|
||||
describe '#post_execute' do
|
||||
it 'runs without error' do
|
||||
handler = instance_double('AssetHandler')
|
||||
allow(handler).to receive(:unbind)
|
||||
allow(handler).to receive(:bind)
|
||||
allow(handler).to receive(:remap)
|
||||
allow(handler).to receive(:bind_raw)
|
||||
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
|
||||
file_double = double('File').as_null_object
|
||||
allow(File).to receive(:open).and_return(file_double)
|
||||
allow(BeEF::Core::Models::Command).to receive(:save_result)
|
||||
allow_any_instance_of(described_class).to receive(:ip).and_return('0.0.0.0')
|
||||
allow_any_instance_of(described_class).to receive(:timestamp).and_return('0')
|
||||
instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0')
|
||||
expect { run_post_execute(instance) }.not_to raise_error
|
||||
end
|
||||
|
||||
if BRANCH_COVERAGE_MISC[branch_key]
|
||||
it 'runs branch path with realistic datastore' do
|
||||
branch = BRANCH_COVERAGE_MISC[branch_key]
|
||||
|
||||
handler = instance_double('AssetHandler')
|
||||
allow(handler).to receive(:unbind)
|
||||
allow(handler).to receive(:bind)
|
||||
allow(handler).to receive(:remap)
|
||||
allow(handler).to receive(:bind_raw)
|
||||
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
|
||||
file_double = double('File').as_null_object
|
||||
allow(File).to receive(:open).and_return(file_double)
|
||||
allow(BeEF::Core::Models::Command).to receive(:save_result)
|
||||
allow_any_instance_of(described_class).to receive(:ip).and_return('0.0.0.0')
|
||||
allow_any_instance_of(described_class).to receive(:timestamp).and_return('0')
|
||||
instance = build_command_instance(described_class, branch[:datastore])
|
||||
expect { run_post_execute(instance) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,172 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
# Unit tests for every module under modules/network/ (including ADC).
|
||||
#
|
||||
# Branch coverage data lives in BeefTestConfig.branch_coverage_for(:network)
|
||||
# (see spec_helper.rb). Per-example stub_const calls inject a Dns::Server
|
||||
# stand-in only when the real Dns extension is not loaded, avoiding the
|
||||
# "superclass mismatch for class Server" hazard that arises if the stub
|
||||
# leaks into the suite before the real extension's spec loads.
|
||||
#
|
||||
|
||||
require_relative '../../../spec_helper'
|
||||
|
||||
BRANCH_COVERAGE_NETWORK = BeefTestConfig.branch_coverage_for(:network)
|
||||
|
||||
# Lightweight Dns server stand-in used when the real BeEF::Extension::Dns is
|
||||
# not loaded (e.g. when running modules-only specs).
|
||||
DNS_SERVER_STUB = Class.new do
|
||||
def self.instance
|
||||
@instance ||= Object.new.tap do |o|
|
||||
def o.add_rule(*) 1 end
|
||||
def o.remove_rule!(*) nil end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
project_root = File.expand_path('../../../../', __dir__)
|
||||
paths = Dir[File.join(project_root, 'modules/network/**/module.rb')].sort
|
||||
|
||||
paths.each do |path|
|
||||
rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '')
|
||||
branch_key = File.dirname(path).sub("#{project_root}/", '')
|
||||
require_path = File.join('../../../../', rel)
|
||||
class_line = File.read(path).lines.find { |l| l =~ /\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/ }
|
||||
next unless class_line
|
||||
|
||||
klass_name = class_line.match(/\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/)[1]
|
||||
require_relative require_path
|
||||
mod = Object.const_get(klass_name)
|
||||
|
||||
RSpec.describe mod do
|
||||
before(:each) do
|
||||
# Irc_nat_pinning calls sleep 30 in post_execute; suppress so the suite doesn't block.
|
||||
allow(Kernel).to receive(:sleep)
|
||||
allow_any_instance_of(described_class).to receive(:sleep).and_return(nil)
|
||||
|
||||
# Provide a per-example Dns::Server stand-in only if the real extension hasn't been loaded.
|
||||
# Using stub_const keeps the stub scoped to this example only.
|
||||
unless defined?(BeEF::Extension::Dns) && BeEF::Extension::Dns.const_defined?(:Server)
|
||||
BeEF::Extension.const_set(:Dns, Module.new) unless BeEF::Extension.const_defined?(:Dns)
|
||||
stub_const('BeEF::Extension::Dns::Server', DNS_SERVER_STUB)
|
||||
end
|
||||
end
|
||||
|
||||
if described_class.respond_to?(:options)
|
||||
describe '.options' do
|
||||
it 'returns an Array' do
|
||||
config = instance_double(BeEF::Core::Configuration)
|
||||
allow(config).to receive(:beef_host).and_return('127.0.0.1')
|
||||
allow(config).to receive(:beef_port).and_return('3000')
|
||||
allow(config).to receive(:beef_proto).and_return('http')
|
||||
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
|
||||
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
|
||||
expect(described_class.options).to be_an(Array)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if described_class.method_defined?(:pre_send)
|
||||
describe '#pre_send' do
|
||||
it 'runs without error' do
|
||||
# If the real Dns extension is loaded (e.g. rake short), stub Server.instance so Dns_rebinding works.
|
||||
if defined?(BeEF::Extension::Dns) && BeEF::Extension::Dns.const_defined?(:Server)
|
||||
dns_instance = double('DnsServer', add_rule: 1, remove_rule!: nil)
|
||||
allow(BeEF::Extension::Dns::Server).to receive(:instance).and_return(dns_instance)
|
||||
end
|
||||
handler = instance_double('AssetHandler')
|
||||
allow(handler).to receive(:unbind).and_return(nil)
|
||||
allow(handler).to receive(:bind).and_return(nil)
|
||||
allow(handler).to receive(:bind_raw).and_return(nil)
|
||||
allow(handler).to receive(:bind_socket).and_return(nil)
|
||||
allow(handler).to receive(:unbind_socket).and_return(nil)
|
||||
allow(handler).to receive(:bind_cached).and_return(nil)
|
||||
allow(handler).to receive(:remap).and_return(nil)
|
||||
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
|
||||
config = instance_double(BeEF::Core::Configuration)
|
||||
allow(config).to receive(:get).with(anything) do |key|
|
||||
case key
|
||||
when 'beef.extension.dns_rebinding' then { 'address_http_external' => '127.0.0.1', 'port_proxy' => '1234' }
|
||||
when 'beef.module.dns_rebinding.domain' then 'example.com'
|
||||
else '127.0.0.1'
|
||||
end
|
||||
end
|
||||
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
|
||||
# Dns_rebinding expects @datastore as Array of option hashes (target, domain, url_callback).
|
||||
datastore = described_class.name.include?('Dns_rebinding') ? [{ 'value' => '192.168.0.1' }, { 'value' => 'example.com' }, {}] : { 'result' => '', 'results' => '', 'cid' => '0' }
|
||||
instance = build_command_instance(described_class, datastore)
|
||||
expect { run_pre_send(instance) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if described_class.method_defined?(:post_execute)
|
||||
describe '#post_execute' do
|
||||
it 'runs without error' do
|
||||
handler = instance_double('AssetHandler')
|
||||
allow(handler).to receive(:unbind)
|
||||
allow(handler).to receive(:bind)
|
||||
allow(handler).to receive(:bind_socket)
|
||||
allow(handler).to receive(:unbind_socket)
|
||||
allow(handler).to receive(:remap)
|
||||
allow(handler).to receive(:bind_cached)
|
||||
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
|
||||
file_double = double('File').as_null_object
|
||||
allow(File).to receive(:open).and_return(file_double)
|
||||
allow(BeEF::Core::Models::Command).to receive(:save_result)
|
||||
allow_any_instance_of(described_class).to receive(:ip).and_return('0.0.0.0')
|
||||
allow_any_instance_of(described_class).to receive(:timestamp).and_return('0')
|
||||
instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0')
|
||||
expect { run_post_execute(instance) }.not_to raise_error
|
||||
end
|
||||
|
||||
if BRANCH_COVERAGE_NETWORK[branch_key]
|
||||
it 'runs branch path with realistic datastore' do
|
||||
raw = BRANCH_COVERAGE_NETWORK[branch_key]
|
||||
branches = raw.is_a?(Array) ? raw : [raw]
|
||||
|
||||
# NetworkService / NetworkHost belong to the network extension and may not be
|
||||
# loaded during a modules-only spec run. Provide class doubles per-example
|
||||
# so `allow(...).to receive(:create)` resolves regardless.
|
||||
stub_const('BeEF::Core::Models::NetworkService', Class.new { def self.create(*); end }) unless defined?(BeEF::Core::Models::NetworkService)
|
||||
stub_const('BeEF::Core::Models::NetworkHost', Class.new { def self.create(*); end }) unless defined?(BeEF::Core::Models::NetworkHost)
|
||||
|
||||
branches.each do |branch|
|
||||
handler = instance_double('AssetHandler')
|
||||
allow(handler).to receive(:unbind)
|
||||
allow(handler).to receive(:bind)
|
||||
allow(handler).to receive(:bind_socket)
|
||||
allow(handler).to receive(:unbind_socket)
|
||||
allow(handler).to receive(:remap)
|
||||
allow(handler).to receive(:bind_cached)
|
||||
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
|
||||
file_double = double('File').as_null_object
|
||||
allow(File).to receive(:open).and_return(file_double)
|
||||
allow(BeEF::Core::Models::Command).to receive(:save_result)
|
||||
allow_any_instance_of(described_class).to receive(:ip).and_return('0.0.0.0')
|
||||
allow_any_instance_of(described_class).to receive(:timestamp).and_return('0')
|
||||
allow(BeEF::Core::Models::NetworkService).to receive(:create)
|
||||
allow(BeEF::Core::Models::NetworkHost).to receive(:create)
|
||||
allow(BeEF::Filters).to receive(:is_valid_ip?).and_return(true)
|
||||
|
||||
config = instance_double(BeEF::Core::Configuration)
|
||||
allow(config).to receive(:beef_host).and_return('127.0.0.1')
|
||||
allow(config).to receive(:beef_port).and_return('3000')
|
||||
allow(config).to receive(:beef_proto).and_return('http')
|
||||
allow(config).to receive(:get).with(anything) do |key|
|
||||
branch[:config_get] && branch[:config_get].key?(key) ? branch[:config_get][key] : '127.0.0.1'
|
||||
end
|
||||
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
|
||||
|
||||
instance = build_command_instance(described_class, branch[:datastore])
|
||||
expect { run_post_execute(instance) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,75 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
# Unit tests for every module under modules/persistence/.
|
||||
#
|
||||
|
||||
require_relative '../../../spec_helper'
|
||||
|
||||
project_root = File.expand_path('../../../../', __dir__)
|
||||
paths = Dir[File.join(project_root, 'modules/persistence/**/module.rb')].sort
|
||||
|
||||
paths.each do |path|
|
||||
rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '')
|
||||
require_path = File.join('../../../../', rel)
|
||||
class_line = File.read(path).lines.find { |l| l =~ /\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/ }
|
||||
next unless class_line
|
||||
|
||||
klass_name = class_line.match(/\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/)[1]
|
||||
require_relative require_path
|
||||
mod = Object.const_get(klass_name)
|
||||
|
||||
RSpec.describe mod do
|
||||
if described_class.respond_to?(:options)
|
||||
describe '.options' do
|
||||
it 'returns an Array' do
|
||||
config = instance_double(BeEF::Core::Configuration)
|
||||
allow(config).to receive(:beef_host).and_return('127.0.0.1')
|
||||
allow(config).to receive(:beef_port).and_return('3000')
|
||||
allow(config).to receive(:beef_proto).and_return('http')
|
||||
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
|
||||
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
|
||||
expect(described_class.options).to be_an(Array)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if described_class.method_defined?(:pre_send)
|
||||
describe '#pre_send' do
|
||||
it 'runs without error' do
|
||||
handler = instance_double('AssetHandler')
|
||||
allow(handler).to receive(:unbind).and_return(nil)
|
||||
allow(handler).to receive(:bind).and_return(nil)
|
||||
allow(handler).to receive(:bind_raw).and_return(nil)
|
||||
allow(handler).to receive(:remap).and_return(nil)
|
||||
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
|
||||
config = instance_double(BeEF::Core::Configuration)
|
||||
allow(config).to receive(:get).with(anything).and_return('/hook.js')
|
||||
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
|
||||
instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0')
|
||||
expect { run_pre_send(instance) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if described_class.method_defined?(:post_execute)
|
||||
describe '#post_execute' do
|
||||
it 'runs without error' do
|
||||
handler = instance_double('AssetHandler')
|
||||
allow(handler).to receive(:unbind)
|
||||
allow(handler).to receive(:bind)
|
||||
allow(handler).to receive(:remap)
|
||||
allow(handler).to receive(:bind_raw)
|
||||
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
|
||||
file_double = double('File').as_null_object
|
||||
allow(File).to receive(:open).and_return(file_double)
|
||||
allow(BeEF::Core::Models::Command).to receive(:save_result)
|
||||
instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0')
|
||||
expect { run_post_execute(instance) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,72 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
# Unit tests for every module under modules/phonegap/.
|
||||
# Some modules use #callback instead of #post_execute; only post_execute is tested here.
|
||||
#
|
||||
|
||||
require_relative '../../../spec_helper'
|
||||
|
||||
project_root = File.expand_path('../../../../', __dir__)
|
||||
paths = Dir[File.join(project_root, 'modules/phonegap/**/module.rb')].sort
|
||||
|
||||
paths.each do |path|
|
||||
rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '')
|
||||
require_path = File.join('../../../../', rel)
|
||||
class_line = File.read(path).lines.find { |l| l =~ /\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/ }
|
||||
next unless class_line
|
||||
|
||||
klass_name = class_line.match(/\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/)[1]
|
||||
require_relative require_path
|
||||
mod = Object.const_get(klass_name)
|
||||
|
||||
RSpec.describe mod do
|
||||
if described_class.respond_to?(:options)
|
||||
describe '.options' do
|
||||
it 'returns an Array' do
|
||||
config = instance_double(BeEF::Core::Configuration)
|
||||
allow(config).to receive(:beef_host).and_return('127.0.0.1')
|
||||
allow(config).to receive(:beef_proto).and_return('http')
|
||||
allow(config).to receive(:beef_port).and_return('3000')
|
||||
allow(config).to receive(:hook_file_path).and_return('/hook.js')
|
||||
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
|
||||
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
|
||||
expect(described_class.options).to be_an(Array)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if described_class.method_defined?(:pre_send)
|
||||
describe '#pre_send' do
|
||||
it 'runs without error' do
|
||||
handler = instance_double('AssetHandler')
|
||||
allow(handler).to receive(:unbind).and_return(nil)
|
||||
allow(handler).to receive(:bind).and_return(nil)
|
||||
allow(handler).to receive(:bind_raw).and_return(nil)
|
||||
allow(handler).to receive(:remap).and_return(nil)
|
||||
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
|
||||
config = instance_double(BeEF::Core::Configuration)
|
||||
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
|
||||
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
|
||||
instance = build_command_instance(described_class, 'result' => '', 'Result' => '', 'cid' => '0')
|
||||
expect { run_pre_send(instance) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if described_class.method_defined?(:post_execute)
|
||||
describe '#post_execute' do
|
||||
it 'runs without error' do
|
||||
handler = instance_double('AssetHandler')
|
||||
allow(handler).to receive(:unbind)
|
||||
allow(handler).to receive(:bind)
|
||||
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
|
||||
instance = build_command_instance(described_class, 'result' => '', 'Result' => '', 'cid' => '0')
|
||||
expect { run_post_execute(instance) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,103 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
# Unit tests for every module under modules/social_engineering/.
|
||||
# Branch coverage data and pre_send skip list live in BeefTestConfig
|
||||
# (see spec_helper.rb).
|
||||
#
|
||||
|
||||
require_relative '../../../spec_helper'
|
||||
|
||||
project_root = File.expand_path('../../../../', __dir__)
|
||||
paths = Dir[File.join(project_root, 'modules/social_engineering/**/module.rb')].sort
|
||||
|
||||
BRANCH_COVERAGE_SOCENG = BeefTestConfig.branch_coverage_for(:social_engineering)
|
||||
PRE_SEND_SKIP_SOCENG = BeefTestConfig.pre_send_skip_for(:social_engineering)
|
||||
|
||||
paths.each do |path|
|
||||
rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '')
|
||||
branch_key = File.dirname(path).sub("#{project_root}/", '')
|
||||
require_path = File.join('../../../../', rel)
|
||||
class_line = File.read(path).lines.find { |l| l =~ /\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/ }
|
||||
next unless class_line
|
||||
|
||||
klass_name = class_line.match(/\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/)[1]
|
||||
require_relative require_path
|
||||
mod = Object.const_get(klass_name)
|
||||
|
||||
RSpec.describe mod do
|
||||
if described_class.respond_to?(:options)
|
||||
describe '.options' do
|
||||
it 'returns an Array' do
|
||||
config = instance_double(BeEF::Core::Configuration)
|
||||
allow(config).to receive(:beef_host).and_return('127.0.0.1')
|
||||
allow(config).to receive(:beef_proto).and_return('http')
|
||||
allow(config).to receive(:beef_port).and_return('3000')
|
||||
allow(config).to receive(:beef_url_str).and_return('http://127.0.0.1:3000')
|
||||
allow(config).to receive(:get).with(anything) do |key|
|
||||
key == 'beef.module.simple_hijacker.templates' ? [] : '127.0.0.1'
|
||||
end
|
||||
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
|
||||
expect(described_class.options).to be_an(Array)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if described_class.method_defined?(:pre_send) && !PRE_SEND_SKIP_SOCENG.include?(klass_name)
|
||||
describe '#pre_send' do
|
||||
it 'runs without error' do
|
||||
handler = instance_double('AssetHandler')
|
||||
allow(handler).to receive(:unbind).and_return(nil)
|
||||
allow(handler).to receive(:bind).and_return(nil)
|
||||
allow(handler).to receive(:bind_raw).with(anything, anything, anything, anything, anything).and_return(nil)
|
||||
allow(handler).to receive(:remap).and_return(nil)
|
||||
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
|
||||
config = instance_double(BeEF::Core::Configuration)
|
||||
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
|
||||
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
|
||||
allow(IO).to receive(:popen).and_return(StringIO.new(''))
|
||||
# Many pre_send implementations expect @datastore to be an Array of option hashes (name/value)
|
||||
pre_send_datastore = [
|
||||
{ 'name' => 'payload', 'value' => 'cmd' },
|
||||
{ 'name' => 'payload_handler', 'value' => 'http://127.0.0.1:8080' },
|
||||
{ 'name' => 'extension_name', 'value' => 'TestExt' },
|
||||
{ 'name' => 'xpi_name', 'value' => 'test.xpi' },
|
||||
{ 'name' => 'lport', 'value' => '4444' }
|
||||
]
|
||||
instance = build_command_instance(described_class, pre_send_datastore)
|
||||
expect { run_pre_send(instance) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if described_class.method_defined?(:post_execute)
|
||||
describe '#post_execute' do
|
||||
it 'runs without error' do
|
||||
handler = instance_double('AssetHandler')
|
||||
allow(handler).to receive(:unbind)
|
||||
allow(handler).to receive(:bind)
|
||||
allow(handler).to receive(:remap)
|
||||
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
|
||||
instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'answer' => '', 'cid' => '0')
|
||||
expect { run_post_execute(instance) }.not_to raise_error
|
||||
end
|
||||
|
||||
if BRANCH_COVERAGE_SOCENG[branch_key]
|
||||
it 'runs branch path with realistic datastore' do
|
||||
branch = BRANCH_COVERAGE_SOCENG[branch_key]
|
||||
|
||||
handler = instance_double('AssetHandler')
|
||||
allow(handler).to receive(:unbind)
|
||||
allow(handler).to receive(:bind)
|
||||
allow(handler).to receive(:remap)
|
||||
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
|
||||
instance = build_command_instance(described_class, branch[:datastore])
|
||||
expect { run_post_execute(instance) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+207
-7
@@ -3,14 +3,214 @@
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
|
||||
# Coverage must start before loading application code.
|
||||
require 'simplecov'
|
||||
SimpleCov.start do
|
||||
add_filter '/spec/'
|
||||
add_group 'Core', 'core'
|
||||
add_group 'Extensions', 'extensions'
|
||||
add_group 'Modules', 'modules'
|
||||
track_files '{core,extensions,modules}/**/*.rb'
|
||||
require 'simplecov' and SimpleCov.start if ENV['COVERAGE']
|
||||
|
||||
# Branch-coverage datastore/config fixtures for the dynamic module spec loaders.
|
||||
# Each spec file under spec/beef/modules/* uses BeefTestConfig.branch_coverage_for(:component)
|
||||
# to drive an extra post_execute example with realistic data, hitting conditional branches
|
||||
# that the generic smoke test misses.
|
||||
#
|
||||
# Centralising these here:
|
||||
# - removes duplicate top-level BRANCH_COVERAGE constant definitions across spec files
|
||||
# (which previously triggered "already initialized constant" warnings and silent overrides),
|
||||
# - makes the test data discoverable from one place, and
|
||||
# - matches what spec/README.md claims.
|
||||
module BeefTestConfig
|
||||
PRE_SEND_SKIP = {
|
||||
# Modules whose pre_send needs Zip, real filesystem access, or speech tooling we
|
||||
# don't want to invoke in unit tests; the dynamic loader skips the generic pre_send
|
||||
# example for these described_class names.
|
||||
social_engineering: %w[
|
||||
Firefox_extension_bindshell Firefox_extension_dropper Firefox_extension_reverse_shell
|
||||
Text_to_voice Ui_abuse_ie
|
||||
].freeze
|
||||
}.freeze
|
||||
|
||||
def self.pre_send_skip_for(component)
|
||||
PRE_SEND_SKIP[component] || []
|
||||
end
|
||||
|
||||
def self.branch_coverage_for(component)
|
||||
case component
|
||||
when :browser
|
||||
{
|
||||
'modules/browser/detect_wmp' => { datastore: { 'results' => 'wmp=Yes', 'wmp' => '', 'result' => '', 'cid' => '0', 'beefhook' => '1' } },
|
||||
'modules/browser/detect_vlc' => { datastore: { 'results' => 'vlc=Yes', 'vlc' => '', 'result' => '', 'cid' => '0', 'beefhook' => '1' } },
|
||||
'modules/browser/detect_office' => { datastore: { 'results' => 'office=Office 2016', 'result' => '', 'cid' => '0', 'beefhook' => '1' } },
|
||||
'modules/browser/detect_foxit' => { datastore: { 'results' => 'foxit=Yes', 'result' => '', 'cid' => '0', 'beefhook' => '1' } },
|
||||
'modules/browser/detect_activex' => { datastore: { 'results' => 'activex=Yes', 'activex' => '', 'result' => '', 'cid' => '0', 'beefhook' => '1' } }
|
||||
}
|
||||
when :exploits
|
||||
{
|
||||
'modules/exploits/vtiger_crm_upload_exploit' => { datastore: { 'result' => 'ok', 'cid' => '0' } },
|
||||
'modules/exploits/router/asus_rt_n12e_get_info' => {
|
||||
datastore: {
|
||||
'result' => '', 'results' => 'ip=192.168.1.1&clients=192.168.1.2,00:11:22:33:44:55&wanip=1.2.3.4&netmask=255.255.255.0&gateway=192.168.1.1&dns=8.8.8.8 8.8.4.4',
|
||||
'beefhook' => '1', 'cid' => '0'
|
||||
},
|
||||
config_get: { 'beef.extension.network.enable' => true }
|
||||
}
|
||||
}
|
||||
when :host
|
||||
{
|
||||
'modules/host/detect_cups' => {
|
||||
datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=631&cups=Installed', 'beefhook' => '1', 'result' => '', 'cid' => '0' },
|
||||
config_get: { 'beef.extension.network.enable' => true }
|
||||
},
|
||||
'modules/host/detect_airdroid' => {
|
||||
datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=8888&airdroid=Installed', 'airdroid' => '', 'beefhook' => '1', 'result' => '', 'cid' => '0' },
|
||||
config_get: { 'beef.extension.network.enable' => true }
|
||||
},
|
||||
'modules/host/get_internal_ip_java' => {
|
||||
datastore: { 'results' => '192.168.1.1', 'Result' => '', 'result' => '', 'beefhook' => '1', 'cid' => '0' },
|
||||
config_get: { 'beef.extension.network.enable' => true }
|
||||
},
|
||||
'modules/host/get_internal_ip_webrtc' => {
|
||||
datastore: { 'results' => 'IP is 192.168.1.1', 'Result' => '', 'result' => '', 'beefhook' => '1', 'cid' => '0' },
|
||||
config_get: { 'beef.extension.network.enable' => true }
|
||||
}
|
||||
}
|
||||
when :misc
|
||||
{
|
||||
'modules/misc/wordpress_post_auth_rce' => { datastore: { 'result' => 'ok', 'cid' => '0' } }
|
||||
}
|
||||
when :network
|
||||
{
|
||||
'modules/network/port_scanner' => {
|
||||
datastore: { 'results' => 'ip=1.2.3.4&port=HTTP: Port 80 is OPEN Apache', 'beefhook' => '1', 'result' => '', 'port' => '', 'cid' => '0' },
|
||||
config_get: { 'beef.extension.network.enable' => true }
|
||||
},
|
||||
'modules/network/ping_sweep' => {
|
||||
datastore: { 'results' => 'ip=192.168.1.1&ping=10ms', 'beefhook' => '1', 'result' => '', 'cid' => '0' },
|
||||
config_get: { 'beef.extension.network.enable' => true }
|
||||
},
|
||||
'modules/network/ping_sweep_ff' => {
|
||||
datastore: { 'results' => 'host=192.168.1.1 is alive', 'beefhook' => '1', 'result' => '', 'host' => '', 'cid' => '0' },
|
||||
config_get: { 'beef.extension.network.enable' => true }
|
||||
},
|
||||
'modules/network/get_http_servers' => {
|
||||
datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=80&url=http://1.2.3.4/', 'beefhook' => '1', 'result' => '', 'url' => '', 'cid' => '0' },
|
||||
config_get: { 'beef.extension.network.enable' => true }
|
||||
},
|
||||
'modules/network/cross_origin_scanner_cors' => {
|
||||
datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=80&status=200', 'beefhook' => '1', 'result' => '', 'cid' => '0' },
|
||||
config_get: { 'beef.extension.network.enable' => true }
|
||||
},
|
||||
'modules/network/detect_burp' => {
|
||||
datastore: { 'results' => 'has_burp=true&response=PROXY 127.0.0.1:8080', 'beefhook' => '1', 'result' => '', 'cid' => '0' },
|
||||
config_get: { 'beef.extension.network.enable' => true }
|
||||
},
|
||||
'modules/network/get_ntop_network_hosts' => {
|
||||
datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=3000&data={"hostNumIpAddress":"192.168.1.1"}', 'beefhook' => '1', 'result' => '', 'cid' => '0' },
|
||||
config_get: { 'beef.extension.network.enable' => true }
|
||||
},
|
||||
'modules/network/internal_network_fingerprinting' => {
|
||||
datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=80&discovered=Apache&url=http://test/', 'beefhook' => '1', 'result' => '', 'discovered' => '', 'url' => '', 'cid' => '0' },
|
||||
config_get: { 'beef.extension.network.enable' => true }
|
||||
},
|
||||
'modules/network/get_proxy_servers_wpad' => [
|
||||
{ datastore: { 'results' => 'proxies=PROXY 192.168.1.1:3128', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, config_get: { 'beef.extension.network.enable' => true } },
|
||||
{ datastore: { 'results' => 'proxies=SOCKS 192.168.1.1:1080', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, config_get: { 'beef.extension.network.enable' => true } }
|
||||
],
|
||||
'modules/network/jslanscanner' => [
|
||||
{ datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=80&service=HTTP', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, config_get: { 'beef.extension.network.enable' => true } },
|
||||
{ datastore: { 'results' => 'ip=1.2.3.4&device=Router', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, config_get: { 'beef.extension.network.enable' => true } }
|
||||
],
|
||||
'modules/network/fetch_port_scanner' => {
|
||||
datastore: { 'result' => 'ok', 'beefhook' => '1', 'cid' => '0' },
|
||||
config_get: { 'beef.extension.network.enable' => true }
|
||||
},
|
||||
'modules/network/cross_origin_scanner_flash' => [
|
||||
{ datastore: { 'results' => 'ip=1.2.3.4&status=alive', 'result' => '', 'beefhook' => '1', 'cid' => '0' }, config_get: { 'beef.extension.network.enable' => true } },
|
||||
{ datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=80&title=Apache', 'result' => '', 'beefhook' => '1', 'cid' => '0' }, config_get: { 'beef.extension.network.enable' => true } }
|
||||
]
|
||||
}
|
||||
when :social_engineering
|
||||
{
|
||||
'modules/social_engineering/fake_lastpass' => { datastore: { 'meta' => 'KILLFRAME', 'result' => '', 'results' => '', 'answer' => '', 'cid' => '0' } },
|
||||
'modules/social_engineering/fake_evernote_clipper' => { datastore: { 'meta' => 'KILLFRAME', 'result' => '', 'results' => '', 'answer' => '', 'cid' => '0' } }
|
||||
}
|
||||
else
|
||||
{}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Returns [total_lines, covered_lines] for a group, or nil if no group.
|
||||
def group_coverage(r, group_name)
|
||||
group = r.groups[group_name]
|
||||
return nil unless group&.respond_to?(:each)
|
||||
|
||||
total_lines = 0
|
||||
covered_lines = 0
|
||||
group.each do |src_file|
|
||||
total_lines += src_file.lines_of_code
|
||||
covered_lines += src_file.covered_lines.compact.size
|
||||
end
|
||||
[total_lines, covered_lines]
|
||||
end
|
||||
|
||||
# Print one group's coverage (skip forked children: very low coverage or zero).
|
||||
def print_group_coverage(r, group_name, color: "\e[36m")
|
||||
total_lines, covered_lines = group_coverage(r, group_name)
|
||||
return unless total_lines && total_lines.positive?
|
||||
return if covered_lines.zero?
|
||||
|
||||
pct = covered_lines.to_f / total_lines * 100
|
||||
# Skip noise from forked children (e.g. 0.03% Core).
|
||||
return if pct < 1.0
|
||||
|
||||
puts "#{color}#{group_name} coverage: #{pct.round(2)}% (#{covered_lines} / #{total_lines} lines)\e[0m"
|
||||
end
|
||||
|
||||
# Only print from the main process (skip forked children with tiny coverage).
|
||||
MIN_COVERAGE_PCT = 5.0
|
||||
|
||||
def coverage_from_main_process?(r, focus)
|
||||
case focus
|
||||
when 'all'
|
||||
%w[Core Extensions Modules].any? do |name|
|
||||
total, covered = group_coverage(r, name)
|
||||
next false unless total && total.positive?
|
||||
(covered.to_f / total * 100) >= MIN_COVERAGE_PCT
|
||||
end
|
||||
when 'core'
|
||||
total, covered = group_coverage(r, 'Core')
|
||||
total && total.positive? && (covered.to_f / total * 100) >= MIN_COVERAGE_PCT
|
||||
when 'extensions'
|
||||
total, covered = group_coverage(r, 'Extensions')
|
||||
total && total.positive? && (covered.to_f / total * 100) >= MIN_COVERAGE_PCT
|
||||
when 'modules', nil
|
||||
total, covered = group_coverage(r, 'Modules')
|
||||
total && total.positive? && (covered.to_f / total * 100) >= MIN_COVERAGE_PCT
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
at_exit do
|
||||
if defined?(SimpleCov) && SimpleCov.respond_to?(:result) && (r = SimpleCov.result)
|
||||
focus = ENV['COVERAGE']
|
||||
next unless coverage_from_main_process?(r, focus)
|
||||
|
||||
case focus
|
||||
when 'core'
|
||||
print_group_coverage(r, 'Core')
|
||||
when 'extensions'
|
||||
print_group_coverage(r, 'Extensions')
|
||||
when 'modules'
|
||||
print_group_coverage(r, 'Modules')
|
||||
when 'all'
|
||||
puts
|
||||
print_group_coverage(r, 'Core')
|
||||
print_group_coverage(r, 'Extensions')
|
||||
print_group_coverage(r, 'Modules')
|
||||
else
|
||||
print_group_coverage(r, 'Modules')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Set external and internal character encodings to UTF-8
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
# Helpers for unit specs of command modules (modules/*/module.rb).
|
||||
# Use with stubbed config; no REST/API or real server.
|
||||
#
|
||||
|
||||
module ModuleSpecHelper
|
||||
#
|
||||
# Build a command instance for testing post_execute (or other instance methods).
|
||||
# Uses allocate so we avoid Command#initialize reading from config.
|
||||
# Set @datastore before calling post_execute; results are stored in @results via save().
|
||||
#
|
||||
# @param klass [Class] The command module class (e.g. Test_beef_debug)
|
||||
# @param datastore [Hash] Data to set on @datastore (simulates callback data)
|
||||
# @return [Object] Instance with @datastore set; call post_execute then read instance_variable_get(:@results)
|
||||
#
|
||||
def build_command_instance(klass, datastore = {})
|
||||
instance = klass.allocate
|
||||
instance.instance_variable_set(:@datastore, datastore)
|
||||
instance
|
||||
end
|
||||
|
||||
#
|
||||
# Call post_execute on a command instance and return the saved results.
|
||||
# Use after build_command_instance(klass, datastore).
|
||||
#
|
||||
# @param instance [Object] Command instance from build_command_instance
|
||||
# @return [Hash, nil] The value passed to save() (stored in @results)
|
||||
#
|
||||
def run_post_execute(instance)
|
||||
instance.post_execute
|
||||
instance.instance_variable_get(:@results)
|
||||
end
|
||||
|
||||
#
|
||||
# Call pre_send on a command instance (e.g. before sending the command to the hooked browser).
|
||||
# Use after build_command_instance(klass, datastore). Stub AssetHandler, Configuration, etc. before calling.
|
||||
#
|
||||
# @param instance [Object] Command instance from build_command_instance
|
||||
#
|
||||
def run_pre_send(instance)
|
||||
instance.pre_send
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.include ModuleSpecHelper
|
||||
|
||||
# Stub Kernel.sleep for all module specs so modules that call sleep
|
||||
# (e.g. Irc_nat_pinning's post_execute sleep 30) don't slow the suite.
|
||||
config.before(:each) do |example|
|
||||
if example.metadata[:file_path]&.include?('spec/beef/modules')
|
||||
allow(Kernel).to receive(:sleep)
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user