Compare commits
87 Commits
red/test_a
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d07ebeb424 | ||
|
|
997a2e0a9e | ||
|
|
8a400906ae | ||
|
|
b36c502ec5 | ||
|
|
b98eff13e2 | ||
|
|
56b32f5da6 | ||
|
|
e5b227c049 | ||
|
|
7158f0fa44 | ||
|
|
70ec0de175 | ||
|
|
e3a668e258 | ||
|
|
39dd7e1d2d | ||
|
|
ca5e66e9bd | ||
|
|
9a648629d2 | ||
|
|
1fa17e8069 | ||
|
|
ae2b95a286 | ||
|
|
19d2780814 | ||
|
|
1d719b30ee | ||
|
|
eae19d30cd | ||
|
|
f9757c9e8f | ||
|
|
7e20b2883e | ||
|
|
b47c85cbaa | ||
|
|
4069c70a8f | ||
|
|
0316a9f224 | ||
|
|
fa8780706b | ||
|
|
646908176d | ||
|
|
d92d74501f | ||
|
|
ccf5d47bdb | ||
|
|
5f1ac0bd20 | ||
|
|
712dd3604a | ||
|
|
f238e20a51 | ||
|
|
68d19a3221 | ||
|
|
cbb95576b9 | ||
|
|
db8b2eca9c | ||
|
|
57777b301a | ||
|
|
399ab90207 | ||
|
|
391dea3fca | ||
|
|
b5c750d6d8 | ||
|
|
60b30fe849 | ||
|
|
086b0b1dc2 | ||
|
|
0f640ebc0e | ||
|
|
2eab4e51bd | ||
|
|
8078751c0c | ||
|
|
95052f2066 | ||
|
|
35c3912f65 | ||
|
|
647500cf78 | ||
|
|
7178460256 | ||
|
|
8b54f67566 | ||
|
|
d54ec6761d | ||
|
|
6ca679dc9e | ||
|
|
ec4d73915e | ||
|
|
d3aef8aec6 | ||
|
|
58696560dd | ||
|
|
8423b3701e | ||
|
|
20c69be87b | ||
|
|
f847cd0e37 | ||
|
|
6377b02c4f | ||
|
|
32af09b248 | ||
|
|
f923285da2 | ||
|
|
5872df9d64 | ||
|
|
45a046ed2d | ||
|
|
e92da3bdde | ||
|
|
ac3f14b045 | ||
|
|
d678c48608 | ||
|
|
96a08913d9 | ||
|
|
0970bdcd87 | ||
|
|
b2d832073f | ||
|
|
9b22d92265 | ||
|
|
ddc27c8880 | ||
|
|
ff281344d8 | ||
|
|
6409891724 | ||
|
|
2080cf5b0d | ||
|
|
5cec161a7b | ||
|
|
75f169e318 | ||
|
|
dadae2a79c | ||
|
|
ccda2a49f3 | ||
|
|
503e584d97 | ||
|
|
15095d2037 | ||
|
|
7efeef2fb7 | ||
|
|
113b154043 | ||
|
|
fe897906a3 | ||
|
|
2056e83050 | ||
|
|
065c7adf03 | ||
|
|
53c97721e1 | ||
|
|
721f1e790d | ||
|
|
334b0c7e06 | ||
|
|
9eca144092 | ||
|
|
15d2acf52a |
27
.github/workflows/github_actions.yml
vendored
27
.github/workflows/github_actions.yml
vendored
@@ -3,17 +3,28 @@ name: 'BrowserStack Test'
|
||||
on:
|
||||
pull_request_target:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
types: [ labeled ]
|
||||
|
||||
jobs:
|
||||
ubuntu-job:
|
||||
name: 'BrowserStack Test on Ubuntu'
|
||||
runs-on: ubuntu-latest # Can be self-hosted runner also
|
||||
environment:
|
||||
name: Integrate Pull Request
|
||||
env:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.label.name == 'safe_to_test'
|
||||
env:
|
||||
GITACTIONS: true
|
||||
steps:
|
||||
|
||||
- name: 'Remove safe_to_test label'
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.payload.pull_request.number,
|
||||
name: 'safe_to_test'
|
||||
});
|
||||
|
||||
- name: 'BrowserStack Env Setup' # Invokes the setup-env action
|
||||
uses: browserstack/github-actions/setup-env@master
|
||||
with:
|
||||
@@ -27,7 +38,7 @@ jobs:
|
||||
local-identifier: random
|
||||
|
||||
- name: 'Checkout the repository'
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
fetch-depth: 2
|
||||
@@ -40,11 +51,13 @@ jobs:
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install libcurl4 libcurl4-openssl-dev
|
||||
|
||||
- name: 'Configure Bundle testing and install gems'
|
||||
run: |
|
||||
bundle config unset --local without
|
||||
bundle config set --local with 'test' 'development'
|
||||
bundle install
|
||||
|
||||
- name: 'Run BrowserStack simple verification'
|
||||
run: |
|
||||
bundle exec rake browserstack --trace
|
||||
|
||||
12
.github/workflows/stale.yml
vendored
12
.github/workflows/stale.yml
vendored
@@ -18,18 +18,18 @@ jobs:
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/stale@v5
|
||||
- uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v10.0.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-stale: 14
|
||||
days-before-stale: 120
|
||||
days-before-pr-stale: 29
|
||||
days-before-close: 11
|
||||
days-before-close: 7
|
||||
days-before-pr-close: 31
|
||||
stale-issue-message: 'This issue as been marked as stale due to inactivity and will be closed in 7 days'
|
||||
stale-issue-message: 'This issue has been marked as stale due to inactivity and will be closed in 7 days'
|
||||
stale-pr-message: 'Stale pull request message'
|
||||
stale-issue-label: 'Stale'
|
||||
stale-pr-label: 'no-pr-activity'
|
||||
exempt-issue-labels: 'Critical, High, Low, Medium, Review, Backlog'
|
||||
exempt-milestones: true
|
||||
exempt-all-milestones: true
|
||||
exempt-draft-pr: true
|
||||
start-date: '2022-06-15'
|
||||
start-date: '2022-06-15T00:00:00Z'
|
||||
|
||||
11
Dockerfile
11
Dockerfile
@@ -78,9 +78,20 @@ RUN adduser --home /beef --gecos beef --disabled-password beef \
|
||||
zlib1g \
|
||||
bison \
|
||||
nodejs \
|
||||
firefox-esr \
|
||||
&& apt-get -y clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install geckodriver for Selenium tests
|
||||
# Pin version and verify checksum to mitigate supply chain attacks
|
||||
ENV GECKODRIVER_VERSION=v0.36.0
|
||||
ENV GECKODRIVER_SHA256=0bde38707eb0a686a20c6bd50f4adcc7d60d4f73c60eb83ee9e0db8f65823e04
|
||||
RUN wget -q "https://github.com/mozilla/geckodriver/releases/download/${GECKODRIVER_VERSION}/geckodriver-${GECKODRIVER_VERSION}-linux64.tar.gz" \
|
||||
&& echo "${GECKODRIVER_SHA256} geckodriver-${GECKODRIVER_VERSION}-linux64.tar.gz" | sha256sum -c - \
|
||||
&& tar -xzf "geckodriver-${GECKODRIVER_VERSION}-linux64.tar.gz" -C /usr/local/bin \
|
||||
&& chmod +x /usr/local/bin/geckodriver \
|
||||
&& rm "geckodriver-${GECKODRIVER_VERSION}-linux64.tar.gz"
|
||||
|
||||
# Use gemset created by the builder above
|
||||
COPY --chown=beef:beef . /beef
|
||||
COPY --from=builder /usr/local/bundle /usr/local/bundle
|
||||
|
||||
11
Gemfile
11
Gemfile
@@ -24,7 +24,7 @@ gem 'rake', '~> 13.3'
|
||||
gem 'activerecord', '~> 8.1'
|
||||
gem 'otr-activerecord', '~> 2.6.0'
|
||||
gem 'sqlite3', '~> 2.9'
|
||||
gem 'rubocop', '~> 1.82.1', require: false
|
||||
gem 'rubocop', '~> 1.85.0', require: false
|
||||
|
||||
# Geolocation support
|
||||
group :geoip do
|
||||
@@ -61,13 +61,14 @@ end
|
||||
|
||||
# For running unit tests
|
||||
group :test do
|
||||
gem 'simplecov', '~> 0.22'
|
||||
gem 'test-unit-full', '~> 0.0.5'
|
||||
gem 'rspec', '~> 3.13'
|
||||
gem 'rdoc', '~> 7.0'
|
||||
gem 'rdoc', '~> 7.2'
|
||||
gem 'browserstack-local', '~> 1.4'
|
||||
|
||||
gem 'irb', '~> 1.16'
|
||||
gem 'pry-byebug', '~> 3.11'
|
||||
gem 'irb', '~> 1.17'
|
||||
gem 'pry-byebug', '~> 3.12'
|
||||
|
||||
gem 'rest-client', '~> 2.1.0'
|
||||
gem 'websocket-client-simple', '~> 0.6.1'
|
||||
@@ -79,7 +80,7 @@ group :test do
|
||||
# Note: selenium-webdriver 3.x is incompatible with Firefox version 48 and prior
|
||||
# gem 'selenium' # Requires old version of selenium which is no longer available
|
||||
gem 'geckodriver-helper', '~> 0.24.0'
|
||||
gem 'selenium-webdriver', '~> 4.39'
|
||||
gem 'selenium-webdriver', '~> 4.41'
|
||||
|
||||
# Note: nokogiri is needed by capybara which may require one of the below commands
|
||||
# sudo apt-get install libxslt-dev libxml2-dev
|
||||
|
||||
100
Gemfile.lock
100
Gemfile.lock
@@ -1,13 +1,13 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
activemodel (8.1.1)
|
||||
activesupport (= 8.1.1)
|
||||
activerecord (8.1.1)
|
||||
activemodel (= 8.1.1)
|
||||
activesupport (= 8.1.1)
|
||||
activemodel (8.1.2)
|
||||
activesupport (= 8.1.2)
|
||||
activerecord (8.1.2)
|
||||
activemodel (= 8.1.2)
|
||||
activesupport (= 8.1.2)
|
||||
timeout (>= 0.4.0)
|
||||
activesupport (8.1.1)
|
||||
activesupport (8.1.2)
|
||||
base64
|
||||
bigdecimal
|
||||
concurrent-ruby (~> 1.0, >= 1.3.1)
|
||||
@@ -34,9 +34,10 @@ GEM
|
||||
async
|
||||
io-endpoint
|
||||
base64 (0.3.0)
|
||||
bigdecimal (3.3.1)
|
||||
bigdecimal (4.0.1)
|
||||
browserstack-local (1.4.3)
|
||||
byebug (12.0.0)
|
||||
byebug (13.0.0)
|
||||
reline (>= 0.6.0)
|
||||
capybara (3.40.0)
|
||||
addressable
|
||||
matrix
|
||||
@@ -47,8 +48,8 @@ GEM
|
||||
regexp_parser (>= 1.5, < 3.0)
|
||||
xpath (~> 3.2)
|
||||
coderay (1.1.3)
|
||||
concurrent-ruby (1.3.5)
|
||||
connection_pool (2.5.4)
|
||||
concurrent-ruby (1.3.6)
|
||||
connection_pool (3.0.2)
|
||||
console (1.34.0)
|
||||
fiber-annotation
|
||||
fiber-local (~> 1.1)
|
||||
@@ -57,6 +58,7 @@ GEM
|
||||
daemons (1.4.1)
|
||||
date (3.5.1)
|
||||
diff-lcs (1.6.2)
|
||||
docile (1.4.1)
|
||||
domain_name (0.6.20240107)
|
||||
drb (2.2.3)
|
||||
em-websocket (0.5.3)
|
||||
@@ -78,28 +80,35 @@ GEM
|
||||
http-cookie (1.0.8)
|
||||
domain_name (~> 0.5)
|
||||
http_parser.rb (0.8.0)
|
||||
i18n (1.14.7)
|
||||
i18n (1.14.8)
|
||||
concurrent-ruby (~> 1.0)
|
||||
io-console (0.8.2)
|
||||
io-endpoint (0.15.2)
|
||||
io-like (0.4.0)
|
||||
irb (1.16.0)
|
||||
irb (1.17.0)
|
||||
pp (>= 0.6.0)
|
||||
prism (>= 1.3.0)
|
||||
rdoc (>= 4.0.0)
|
||||
reline (>= 0.4.2)
|
||||
json (2.18.0)
|
||||
json (2.19.0)
|
||||
json-schema (6.1.0)
|
||||
addressable (~> 2.8)
|
||||
bigdecimal (>= 3.1, < 5)
|
||||
language_server-protocol (3.17.0.5)
|
||||
lint_roller (1.1.0)
|
||||
logger (1.7.0)
|
||||
matrix (0.4.3)
|
||||
maxmind-db (1.4.0)
|
||||
mcp (0.7.1)
|
||||
json-schema (>= 4.1)
|
||||
method_source (1.1.0)
|
||||
mime-types (3.7.0)
|
||||
logger
|
||||
mime-types-data (~> 3.2025, >= 3.2025.0507)
|
||||
mime-types-data (3.2025.0902)
|
||||
mini_mime (1.1.5)
|
||||
minitest (5.26.1)
|
||||
minitest (6.0.1)
|
||||
prism (~> 1.5)
|
||||
mojo_magick (0.6.8)
|
||||
msfrpc-client (1.1.2)
|
||||
msgpack (~> 1)
|
||||
@@ -112,40 +121,41 @@ GEM
|
||||
net-protocol
|
||||
netrc (0.11.0)
|
||||
nio4r (2.7.4)
|
||||
nokogiri (1.18.9-aarch64-linux-gnu)
|
||||
nokogiri (1.19.1-aarch64-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.9-aarch64-linux-musl)
|
||||
nokogiri (1.19.1-aarch64-linux-musl)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.9-arm-linux-gnu)
|
||||
nokogiri (1.19.1-arm-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.9-arm-linux-musl)
|
||||
nokogiri (1.19.1-arm-linux-musl)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.9-arm64-darwin)
|
||||
nokogiri (1.19.1-arm64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.9-x86_64-darwin)
|
||||
nokogiri (1.19.1-x86_64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.9-x86_64-linux-gnu)
|
||||
nokogiri (1.19.1-x86_64-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.9-x86_64-linux-musl)
|
||||
nokogiri (1.19.1-x86_64-linux-musl)
|
||||
racc (~> 1.4)
|
||||
otr-activerecord (2.6.0)
|
||||
activerecord (>= 6.0, < 9.0)
|
||||
parallel (1.27.0)
|
||||
parseconfig (1.1.2)
|
||||
parser (3.3.10.0)
|
||||
parser (3.3.10.2)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
power_assert (2.0.5)
|
||||
pp (0.6.3)
|
||||
prettyprint
|
||||
prettyprint (0.2.0)
|
||||
prism (1.7.0)
|
||||
pry (0.15.2)
|
||||
prism (1.9.0)
|
||||
pry (0.16.0)
|
||||
coderay (~> 1.1)
|
||||
method_source (~> 1.0)
|
||||
pry-byebug (3.11.0)
|
||||
byebug (~> 12.0)
|
||||
pry (>= 0.13, < 0.16)
|
||||
reline (>= 0.6.0)
|
||||
pry-byebug (3.12.0)
|
||||
byebug (~> 13.0)
|
||||
pry (>= 0.13, < 0.17)
|
||||
psych (5.3.1)
|
||||
date
|
||||
stringio
|
||||
@@ -154,7 +164,7 @@ GEM
|
||||
mojo_magick (~> 0.6.5)
|
||||
rqrcode_core (~> 1.0)
|
||||
racc (1.8.1)
|
||||
rack (3.2.4)
|
||||
rack (3.2.5)
|
||||
rack-protection (4.2.1)
|
||||
base64 (>= 0.1.0)
|
||||
logger (>= 1.6.0)
|
||||
@@ -166,7 +176,7 @@ GEM
|
||||
rack (>= 1.3)
|
||||
rainbow (3.1.1)
|
||||
rake (13.3.1)
|
||||
rdoc (7.0.3)
|
||||
rdoc (7.2.0)
|
||||
erb
|
||||
psych (>= 4.0.0)
|
||||
tsort
|
||||
@@ -194,20 +204,21 @@ GEM
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-support (3.13.6)
|
||||
rubocop (1.82.1)
|
||||
rubocop (1.85.0)
|
||||
json (~> 2.3)
|
||||
language_server-protocol (~> 3.17.0.2)
|
||||
lint_roller (~> 1.1.0)
|
||||
mcp (~> 0.6)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 3.3.0.2)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 2.9.3, < 3.0)
|
||||
rubocop-ast (>= 1.48.0, < 2.0)
|
||||
rubocop-ast (>= 1.49.0, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 2.4.0, < 4.0)
|
||||
rubocop-ast (1.48.0)
|
||||
rubocop-ast (1.49.0)
|
||||
parser (>= 3.3.7.2)
|
||||
prism (~> 1.4)
|
||||
prism (~> 1.7)
|
||||
ruby-progressbar (1.13.0)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (3.2.2)
|
||||
@@ -215,12 +226,18 @@ GEM
|
||||
json
|
||||
rest-client
|
||||
securerandom (0.4.1)
|
||||
selenium-webdriver (4.39.0)
|
||||
selenium-webdriver (4.41.0)
|
||||
base64 (~> 0.2)
|
||||
logger (~> 1.4)
|
||||
rexml (~> 3.2, >= 3.2.5)
|
||||
rubyzip (>= 1.2.2, < 4.0)
|
||||
websocket (~> 1.0)
|
||||
simplecov (0.22.0)
|
||||
docile (~> 1.1)
|
||||
simplecov-html (~> 0.11)
|
||||
simplecov_json_formatter (~> 0.1)
|
||||
simplecov-html (0.13.2)
|
||||
simplecov_json_formatter (0.1.4)
|
||||
sinatra (4.2.1)
|
||||
logger (>= 1.6.0)
|
||||
mustermann (~> 3.0)
|
||||
@@ -264,7 +281,7 @@ GEM
|
||||
logger
|
||||
rack (>= 1, < 4)
|
||||
tilt (2.6.1)
|
||||
timeout (0.4.4)
|
||||
timeout (0.6.0)
|
||||
timers (4.4.0)
|
||||
tins (1.43.0)
|
||||
bigdecimal
|
||||
@@ -312,7 +329,7 @@ DEPENDENCIES
|
||||
eventmachine (~> 1.2, >= 1.2.7)
|
||||
execjs (~> 2.10)
|
||||
geckodriver-helper (~> 0.24.0)
|
||||
irb (~> 1.16)
|
||||
irb (~> 1.17)
|
||||
json
|
||||
maxmind-db (~> 1.4)
|
||||
mime-types (~> 3.7)
|
||||
@@ -320,18 +337,19 @@ DEPENDENCIES
|
||||
net-smtp
|
||||
otr-activerecord (~> 2.6.0)
|
||||
parseconfig (~> 1.1, >= 1.1.2)
|
||||
pry-byebug (~> 3.11)
|
||||
pry-byebug (~> 3.12)
|
||||
qr4r (~> 0.6.1)
|
||||
rack (~> 3.2)
|
||||
rack-protection (~> 4.2.1)
|
||||
rake (~> 13.3)
|
||||
rdoc (~> 7.0)
|
||||
rdoc (~> 7.2)
|
||||
rest-client (~> 2.1.0)
|
||||
rspec (~> 3.13)
|
||||
rubocop (~> 1.82.1)
|
||||
rubocop (~> 1.85.0)
|
||||
rubyzip (~> 3.2)
|
||||
rushover (~> 0.3.0)
|
||||
selenium-webdriver (~> 4.39)
|
||||
selenium-webdriver (~> 4.41)
|
||||
simplecov (~> 0.22)
|
||||
sinatra (~> 4.1)
|
||||
slack-notifier (~> 2.4)
|
||||
sqlite3 (~> 2.9)
|
||||
|
||||
@@ -187,7 +187,7 @@ module BeEF
|
||||
def self.has_valid_browser_details_chars?(str)
|
||||
return false unless is_non_empty_string?(str)
|
||||
|
||||
!(str =~ %r{[^\w\d\s()-.,;:_/!\302\256]}).nil?
|
||||
(str =~ %r{[^\w\d\s()-.,;:_/!\302\256]}).nil?
|
||||
end
|
||||
|
||||
# Check for valid base details characters
|
||||
|
||||
@@ -8,10 +8,10 @@ module BeEF
|
||||
# Check the browser type value - for example, 'FF'
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid browser name characters
|
||||
def self.is_valid_browsername?(str)
|
||||
def self.is_valid_browsername?(str) # rubocop:disable Naming/PredicatePrefix
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if str.length > 2
|
||||
return false if has_non_printable_char?(str)
|
||||
return false unless has_valid_browser_details_chars?(str)
|
||||
|
||||
true
|
||||
end
|
||||
@@ -19,9 +19,9 @@ module BeEF
|
||||
# Check the Operating System name value - for example, 'Windows XP'
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid Operating System name characters
|
||||
def self.is_valid_osname?(str)
|
||||
def self.is_valid_osname?(str) # rubocop:disable Naming/PredicatePrefix
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if has_non_printable_char?(str)
|
||||
return false unless has_valid_browser_details_chars?(str)
|
||||
return false if str.length < 2
|
||||
|
||||
true
|
||||
@@ -30,9 +30,9 @@ module BeEF
|
||||
# Check the Hardware name value - for example, 'iPhone'
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid Hardware name characters
|
||||
def self.is_valid_hwname?(str)
|
||||
def self.is_valid_hwname?(str) # rubocop:disable Naming/PredicatePrefix
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if has_non_printable_char?(str)
|
||||
return false unless has_valid_browser_details_chars?(str)
|
||||
return false if str.length < 2
|
||||
|
||||
true
|
||||
@@ -41,12 +41,12 @@ module BeEF
|
||||
# Verify the browser version string is valid
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid browser version characters
|
||||
def self.is_valid_browserversion?(str)
|
||||
def self.is_valid_browserversion?(str) # rubocop:disable Naming/PredicatePrefix
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if has_non_printable_char?(str)
|
||||
return true if str.eql? 'UNKNOWN'
|
||||
return true if str.eql? 'ALL'
|
||||
return false if !nums_only?(str) and !str.match(/\A(0|[1-9][0-9]{0,3})(\.(0|[1-9][0-9]{0,3})){0,3}\z/)
|
||||
return false if !nums_only?(str) && !str.match(/\A(0|[1-9][0-9]{0,3})(\.(0|[1-9][0-9]{0,3})){0,3}\z/)
|
||||
return false if str.length > 20
|
||||
|
||||
true
|
||||
@@ -55,7 +55,7 @@ module BeEF
|
||||
# Verify the os version string is valid
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid os version characters
|
||||
def self.is_valid_osversion?(str)
|
||||
def self.is_valid_osversion?(str) # rubocop:disable Naming/PredicatePrefix
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if has_non_printable_char?(str)
|
||||
return true if str.eql? 'UNKNOWN'
|
||||
@@ -69,9 +69,9 @@ module BeEF
|
||||
# Verify the browser/UA string is valid
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid browser / ua string characters
|
||||
def self.is_valid_browserstring?(str)
|
||||
def self.is_valid_browserstring?(str) # rubocop:disable Naming/PredicatePrefix
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if has_non_printable_char?(str)
|
||||
return false unless has_valid_browser_details_chars?(str)
|
||||
return false if str.length > 300
|
||||
|
||||
true
|
||||
@@ -80,7 +80,7 @@ module BeEF
|
||||
# Verify the cookies are valid
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid cookie characters
|
||||
def self.is_valid_cookies?(str)
|
||||
def self.is_valid_cookies?(str) # rubocop:disable Naming/PredicatePrefix
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if has_non_printable_char?(str)
|
||||
return false if str.length > 2000
|
||||
@@ -91,9 +91,9 @@ module BeEF
|
||||
# Verify the system platform is valid
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid system platform characters
|
||||
def self.is_valid_system_platform?(str)
|
||||
def self.is_valid_system_platform?(str) # rubocop:disable Naming/PredicatePrefix
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if has_non_printable_char?(str)
|
||||
return false unless has_valid_browser_details_chars?(str)
|
||||
return false if str.length > 200
|
||||
|
||||
true
|
||||
@@ -102,7 +102,7 @@ module BeEF
|
||||
# Verify the date stamp is valid
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid date stamp characters
|
||||
def self.is_valid_date_stamp?(str)
|
||||
def self.is_valid_date_stamp?(str) # rubocop:disable Naming/PredicatePrefix
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if has_non_printable_char?(str)
|
||||
return false if str.length > 200
|
||||
@@ -113,7 +113,7 @@ module BeEF
|
||||
# Verify the CPU type string is valid
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid CPU type characters
|
||||
def self.is_valid_cpu?(str)
|
||||
def self.is_valid_cpu?(str) # rubocop:disable Naming/PredicatePrefix
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if has_non_printable_char?(str)
|
||||
return false if str.length > 200
|
||||
@@ -124,7 +124,7 @@ module BeEF
|
||||
# Verify the memory string is valid
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid memory type characters
|
||||
def self.is_valid_memory?(str)
|
||||
def self.is_valid_memory?(str) # rubocop:disable Naming/PredicatePrefix
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if has_non_printable_char?(str)
|
||||
return false if str.length > 200
|
||||
@@ -135,7 +135,7 @@ module BeEF
|
||||
# Verify the GPU type string is valid
|
||||
# @param [String] str String for testing
|
||||
# @return [Boolean] If the string has valid GPU type characters
|
||||
def self.is_valid_gpu?(str)
|
||||
def self.is_valid_gpu?(str) # rubocop:disable Naming/PredicatePrefix
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if has_non_printable_char?(str)
|
||||
return false if str.length > 200
|
||||
@@ -148,11 +148,11 @@ module BeEF
|
||||
# @return [Boolean] If the string has valid browser plugin characters
|
||||
# @note This string can be empty if there are no browser plugins
|
||||
# @todo Verify if the ruby version statement is still necessary
|
||||
def self.is_valid_browser_plugins?(str)
|
||||
def self.is_valid_browser_plugins?(str) # rubocop:disable Naming/PredicatePrefix
|
||||
return false unless is_non_empty_string?(str)
|
||||
return false if str.length > 1000
|
||||
|
||||
if str.encoding === Encoding.find('UTF-8')
|
||||
if str.encoding == Encoding.find('UTF-8') # Style/CaseEquality: Avoid the use of the case equality operator `===`.
|
||||
(str =~ /[^\w\d\s()-.,';_!\302\256]/u).nil?
|
||||
else
|
||||
(str =~ /[^\w\d\s()-.,';_!\302\256]/n).nil?
|
||||
|
||||
@@ -44,7 +44,7 @@ module BeEF
|
||||
|
||||
# hooked window host name
|
||||
log_zombie_port = 0
|
||||
if !@data['results']['browser.window.hostname'].nil?
|
||||
if !@data['results']['browser.window.hostname'].nil? && BeEF::Filters.is_valid_hostname?(@data['results']['browser.window.hostname'])
|
||||
log_zombie_domain = @data['results']['browser.window.hostname']
|
||||
elsif !@data['request'].referer.nil? and !@data['request'].referer.empty?
|
||||
referer = @data['request'].referer
|
||||
@@ -59,7 +59,7 @@ module BeEF
|
||||
end
|
||||
|
||||
# hooked window host port
|
||||
if @data['results']['browser.window.hostport'].nil?
|
||||
if @data['results']['browser.window.hostport'].nil? || !BeEF::Filters.is_valid_port?(@data['results']['browser.window.hostport'].to_s)
|
||||
log_zombie_domain_parts = log_zombie_domain.split(':')
|
||||
log_zombie_port = log_zombie_domain_parts[1].to_i if log_zombie_domain_parts.length > 1
|
||||
else
|
||||
@@ -92,6 +92,7 @@ module BeEF
|
||||
BD.set(session_id, 'browser.name.friendly', browser_friendly_name)
|
||||
else
|
||||
err_msg "Invalid browser name returned from the hook browser's initial connection."
|
||||
browser_name = 'Unknown'
|
||||
end
|
||||
|
||||
if BeEF::Filters.is_valid_ip?(zombie.ip)
|
||||
@@ -242,11 +243,17 @@ module BeEF
|
||||
X_FORWARDED
|
||||
X_FORWARDED_FOR
|
||||
].each do |header|
|
||||
proxy_clients << (JSON.parse(zombie.httpheaders)[header]).to_s unless JSON.parse(zombie.httpheaders)[header].nil?
|
||||
val = JSON.parse(zombie.httpheaders)[header]
|
||||
unless val.nil?
|
||||
val.to_s.split(',').each do |ip|
|
||||
proxy_clients << ip.strip if BeEF::Filters.is_valid_ip?(ip.strip)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# retrieve proxy server
|
||||
proxy_server = JSON.parse(zombie.httpheaders)['VIA'] unless JSON.parse(zombie.httpheaders)['VIA'].nil?
|
||||
proxy_server = nil unless proxy_server.nil? || BeEF::Filters.has_valid_browser_details_chars?(proxy_server)
|
||||
|
||||
# store and log proxy details
|
||||
if using_proxy == true
|
||||
@@ -273,6 +280,7 @@ module BeEF
|
||||
BD.set(session_id, 'browser.version', browser_version)
|
||||
else
|
||||
err_msg "Invalid browser version returned from the hook browser's initial connection."
|
||||
browser_version = 'Unknown'
|
||||
end
|
||||
|
||||
# get and store browser string
|
||||
@@ -293,7 +301,11 @@ module BeEF
|
||||
|
||||
# get and store browser language
|
||||
browser_lang = get_param(@data['results'], 'browser.language')
|
||||
BD.set(session_id, 'browser.language', browser_lang)
|
||||
if BeEF::Filters.has_valid_browser_details_chars?(browser_lang)
|
||||
BD.set(session_id, 'browser.language', browser_lang)
|
||||
else
|
||||
err_msg "Invalid browser language returned from the hook browser's initial connection."
|
||||
end
|
||||
|
||||
# get and store the cookies
|
||||
cookies = get_param(@data['results'], 'browser.window.cookies')
|
||||
@@ -309,6 +321,7 @@ module BeEF
|
||||
BD.set(session_id, 'host.os.name', os_name)
|
||||
else
|
||||
err_msg "Invalid operating system name returned from the hook browser's initial connection."
|
||||
os_name = 'Unknown'
|
||||
end
|
||||
|
||||
# get and store the OS family
|
||||
@@ -322,15 +335,28 @@ module BeEF
|
||||
# get and store the OS version
|
||||
# - without checks as it can be very different, for instance on linux/bsd)
|
||||
os_version = get_param(@data['results'], 'host.os.version')
|
||||
BD.set(session_id, 'host.os.version', os_version)
|
||||
if BeEF::Filters.has_valid_browser_details_chars?(os_version)
|
||||
BD.set(session_id, 'host.os.version', os_version)
|
||||
else
|
||||
err_msg "Invalid operating system version returned from the hook browser's initial connection."
|
||||
os_version = 'Unknown'
|
||||
end
|
||||
|
||||
# get and store the OS arch - without checks
|
||||
# get and store the OS arch
|
||||
os_arch = get_param(@data['results'], 'host.os.arch')
|
||||
BD.set(session_id, 'host.os.arch', os_arch)
|
||||
if BeEF::Filters.has_valid_browser_details_chars?(os_arch)
|
||||
BD.set(session_id, 'host.os.arch', os_arch)
|
||||
else
|
||||
err_msg "Invalid operating system architecture returned from the hook browser's initial connection."
|
||||
end
|
||||
|
||||
# get and store default browser
|
||||
default_browser = get_param(@data['results'], 'host.software.defaultbrowser')
|
||||
BD.set(session_id, 'host.software.defaultbrowser', default_browser)
|
||||
if BeEF::Filters.has_valid_browser_details_chars?(default_browser)
|
||||
BD.set(session_id, 'host.software.defaultbrowser', default_browser)
|
||||
else
|
||||
err_msg "Invalid default browser returned from the hook browser's initial connection."
|
||||
end
|
||||
|
||||
# get and store the hardware type
|
||||
hw_type = get_param(@data['results'], 'hardware.type')
|
||||
|
||||
@@ -467,24 +467,26 @@ try{
|
||||
}
|
||||
|
||||
// set zombie hover balloon text for tree node
|
||||
// Use Ext.util.Format.htmlEncode() to prevent XSS via malicious browser properties
|
||||
var encode = Ext.util.Format.htmlEncode;
|
||||
var balloon_text = "";
|
||||
balloon_text += hooked_browser.ip;
|
||||
balloon_text += encode(hooked_browser.ip);
|
||||
balloon_text += "<hr/>"
|
||||
balloon_text += "<img width='13px' height='13px' class='zombie-tree-icon' src='<%= @base_path %>/media/images/favicon.png' /> ";
|
||||
balloon_text += "Origin: " + hooked_browser.domain + ":" + hooked_browser.port;
|
||||
balloon_text += "Origin: " + encode(hooked_browser.domain) + ":" + encode(hooked_browser.port);
|
||||
balloon_text += "<br/>";
|
||||
balloon_text += "<img width='13px' height='13px' class='zombie-tree-icon' src='<%= @base_path %>/media/images/icons/" + escape(browser_icon) + "' /> ";
|
||||
balloon_text += "Browser: " + hooked_browser.browser_name + " " + hooked_browser.browser_version;
|
||||
balloon_text += "Browser: " + encode(hooked_browser.browser_name) + " " + encode(hooked_browser.browser_version);
|
||||
balloon_text += "<br/>";
|
||||
balloon_text += " <img width='13px' height='13px' class='zombie-tree-icon' src='<%= @base_path %>/media/images/icons/" + escape(os_icon) + "' /> ";
|
||||
if (hooked_browser.os_version == 'Unknown') {
|
||||
balloon_text += "OS: " + hooked_browser.os_name;
|
||||
balloon_text += "OS: " + encode(hooked_browser.os_name);
|
||||
} else {
|
||||
balloon_text += "OS: " + hooked_browser.os_name + ' ' + hooked_browser.os_version;
|
||||
balloon_text += "OS: " + encode(hooked_browser.os_name) + ' ' + encode(hooked_browser.os_version);
|
||||
}
|
||||
balloon_text += "<br/>";
|
||||
balloon_text += " <img width='13px' height='13px' class='zombie-tree-icon' src='<%= @base_path %>/media/images/icons/" + escape(hw_icon) + "' /> ";
|
||||
balloon_text += "Hardware: " + hooked_browser.hw_name;
|
||||
balloon_text += "Hardware: " + encode(hooked_browser.hw_name);
|
||||
balloon_text += "<br/>";
|
||||
|
||||
if ( !hooked_browser.country || !hooked_browser.country_code || hooked_browser.country == 'Unknown' ) {
|
||||
@@ -492,11 +494,11 @@ try{
|
||||
balloon_text += "Location: Unknown";
|
||||
} else {
|
||||
balloon_text += " <img width='13px' height='13px' class='zombie-tree-icon' src='<%= @base_path %>/media/images/icons/country-squared/" + escape(hooked_browser.country_code.toLowerCase()) + ".svg' /> ";
|
||||
balloon_text += "Location: " + hooked_browser.city + ", " + hooked_browser.country;
|
||||
balloon_text += "Location: " + encode(hooked_browser.city) + ", " + encode(hooked_browser.country);
|
||||
}
|
||||
|
||||
balloon_text += "<hr/>";
|
||||
balloon_text += "Local Date: " + hooked_browser.date;
|
||||
balloon_text += "Local Date: " + encode(hooked_browser.date);
|
||||
hooked_browser.qtip = balloon_text;
|
||||
|
||||
// set zombie text label for tree node
|
||||
@@ -511,7 +513,7 @@ try{
|
||||
text += "<img width='13px' height='13px' class='zombie-tree-icon' src='<%= @base_path %>/media/images/icons/country-squared/" + escape(hooked_browser.country_code.toLowerCase()) + ".svg' /> ";
|
||||
}
|
||||
|
||||
text += hooked_browser.ip;
|
||||
text += encode(hooked_browser.ip);
|
||||
hooked_browser.text = text;
|
||||
|
||||
//save a new online HB
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -4,17 +4,23 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
class Text_to_voice < BeEF::Core::Command
|
||||
require 'espeak'
|
||||
include ESpeak
|
||||
|
||||
def pre_send
|
||||
# Ensure lame and espeak are installed
|
||||
if IO.popen(%w[which lame], 'r').read.to_s.eql?('')
|
||||
print_error('[Text to Voice] Lame is not in $PATH (apt-get install lame)')
|
||||
# Check for required binaries
|
||||
if IO.popen(%w[which espeak], 'r').read.to_s.eql?('')
|
||||
print_error('[Text to Voice] eSpeak is not in $PATH (brew install espeak on macOS, apt-get install espeak on Linux)')
|
||||
return
|
||||
end
|
||||
if IO.popen(%w[which espeak], 'r').read.to_s.eql?('')
|
||||
print_error('[Text to Voice] eSpeak is not in $PATH (apt-get install espeak)')
|
||||
if IO.popen(%w[which lame], 'r').read.to_s.eql?('')
|
||||
print_error('[Text to Voice] Lame is not in $PATH (brew install lame on macOS, apt-get install lame on Linux)')
|
||||
return
|
||||
end
|
||||
|
||||
# Load espeak gem (only if binaries are available)
|
||||
begin
|
||||
require 'espeak'
|
||||
include ESpeak
|
||||
rescue LoadError, StandardError => e
|
||||
print_error("[Text to Voice] Failed to load espeak gem: #{e.message}")
|
||||
return
|
||||
end
|
||||
|
||||
@@ -25,9 +31,16 @@ class Text_to_voice < BeEF::Core::Command
|
||||
message = input['value'] if input['name'] == 'message'
|
||||
language = input['value'] if input['name'] == 'language'
|
||||
end
|
||||
unless Voice.all.map(&:language).include?(language)
|
||||
print_error("[Text to Voice] Language '#{language}' is not supported")
|
||||
print_more("Supported languages: #{Voice.all.map(&:language).join(',')}")
|
||||
|
||||
# Validate language
|
||||
begin
|
||||
unless Voice.all.map(&:language).include?(language)
|
||||
print_error("[Text to Voice] Language '#{language}' is not supported")
|
||||
print_more("Supported languages: #{Voice.all.map(&:language).join(',')}")
|
||||
return
|
||||
end
|
||||
rescue StandardError => e
|
||||
print_error("[Text to Voice] Could not validate language: #{e.message}")
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
9
package-lock.json
generated
9
package-lock.json
generated
@@ -666,10 +666,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true
|
||||
"version": "4.17.23",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
|
||||
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.camelcase": {
|
||||
"version": "4.3.0",
|
||||
|
||||
100
spec/beef/core/extension_spec.rb
Normal file
100
spec/beef/core/extension_spec.rb
Normal file
@@ -0,0 +1,100 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
|
||||
RSpec.describe BeEF::Extension do
|
||||
let(:config) { BeEF::Core::Configuration.instance }
|
||||
|
||||
describe '.is_present' do
|
||||
it 'returns true when extension exists in configuration' do
|
||||
allow(config).to receive(:get).with('beef.extension').and_return({ 'test_ext' => {} })
|
||||
expect(described_class.is_present('test_ext')).to be true
|
||||
end
|
||||
|
||||
it 'returns false when extension does not exist' do
|
||||
allow(config).to receive(:get).with('beef.extension').and_return({})
|
||||
expect(described_class.is_present('nonexistent')).to be false
|
||||
end
|
||||
|
||||
it 'converts extension key to string' do
|
||||
allow(config).to receive(:get).with('beef.extension').and_return({ 'test_ext' => {} })
|
||||
expect(described_class.is_present(:test_ext)).to be true
|
||||
end
|
||||
end
|
||||
|
||||
describe '.is_enabled' do
|
||||
it 'returns true when extension is present and enabled' do
|
||||
allow(config).to receive(:get).with('beef.extension').and_return({ 'test_ext' => {} })
|
||||
allow(config).to receive(:get).with('beef.extension.test_ext.enable').and_return(true)
|
||||
expect(described_class.is_enabled('test_ext')).to be true
|
||||
end
|
||||
|
||||
it 'returns false when extension is not present' do
|
||||
allow(config).to receive(:get).with('beef.extension').and_return({})
|
||||
expect(described_class.is_enabled('nonexistent')).to be false
|
||||
end
|
||||
|
||||
it 'returns false when extension is disabled' do
|
||||
allow(config).to receive(:get).with('beef.extension').and_return({ 'test_ext' => {} })
|
||||
allow(config).to receive(:get).with('beef.extension.test_ext.enable').and_return(false)
|
||||
expect(described_class.is_enabled('test_ext')).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe '.is_loaded' do
|
||||
it 'returns true when extension is enabled and loaded' do
|
||||
allow(config).to receive(:get).with('beef.extension').and_return({ 'test_ext' => {} })
|
||||
allow(config).to receive(:get).with('beef.extension.test_ext.enable').and_return(true)
|
||||
allow(config).to receive(:get).with('beef.extension.test_ext.loaded').and_return(true)
|
||||
expect(described_class.is_loaded('test_ext')).to be true
|
||||
end
|
||||
|
||||
it 'returns false when extension is not enabled' do
|
||||
allow(config).to receive(:get).with('beef.extension').and_return({ 'test_ext' => {} })
|
||||
allow(config).to receive(:get).with('beef.extension.test_ext.enable').and_return(false)
|
||||
expect(described_class.is_loaded('test_ext')).to be false
|
||||
end
|
||||
|
||||
it 'returns false when extension is not loaded' do
|
||||
allow(config).to receive(:get).with('beef.extension').and_return({ 'test_ext' => {} })
|
||||
allow(config).to receive(:get).with('beef.extension.test_ext.enable').and_return(true)
|
||||
allow(config).to receive(:get).with('beef.extension.test_ext.loaded').and_return(false)
|
||||
expect(described_class.is_loaded('test_ext')).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe '.load' do
|
||||
it 'returns true when extension file exists' do
|
||||
ext_path = "#{$root_dir}/extensions/test_ext/extension.rb"
|
||||
allow(File).to receive(:exist?).with(ext_path).and_return(true)
|
||||
allow(config).to receive(:set).with('beef.extension.test_ext.loaded', true).and_return(true)
|
||||
# Stub require on the module itself since it's called directly
|
||||
allow(described_class).to receive(:require).with(ext_path)
|
||||
expect(described_class.load('test_ext')).to be true
|
||||
end
|
||||
|
||||
it 'returns false when extension file does not exist' do
|
||||
ext_path = "#{$root_dir}/extensions/test_ext/extension.rb"
|
||||
allow(File).to receive(:exist?).with(ext_path).and_return(false)
|
||||
expect(described_class.load('test_ext')).to be false
|
||||
end
|
||||
|
||||
it 'sets loaded flag to true when successfully loaded' do
|
||||
ext_path = "#{$root_dir}/extensions/test_ext/extension.rb"
|
||||
allow(File).to receive(:exist?).with(ext_path).and_return(true)
|
||||
allow(described_class).to receive(:require).with(ext_path)
|
||||
expect(config).to receive(:set).with('beef.extension.test_ext.loaded', true).and_return(true)
|
||||
described_class.load('test_ext')
|
||||
end
|
||||
|
||||
it 'handles errors during loading gracefully' do
|
||||
ext_path = "#{$root_dir}/extensions/test_ext/extension.rb"
|
||||
allow(File).to receive(:exist?).with(ext_path).and_return(true)
|
||||
allow(described_class).to receive(:require).with(ext_path).and_raise(StandardError.new('Load error'))
|
||||
# The rescue block calls print_more which may return a value, so just verify it doesn't raise
|
||||
expect { described_class.load('test_ext') }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,3 +1,9 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
|
||||
RSpec.describe 'BeEF Extensions' do
|
||||
|
||||
it 'loaded successfully' do
|
||||
|
||||
310
spec/beef/core/filter/base_spec.rb
Normal file
310
spec/beef/core/filter/base_spec.rb
Normal file
@@ -0,0 +1,310 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
|
||||
RSpec.describe BeEF::Filters do
|
||||
describe '.is_non_empty_string?' do
|
||||
it 'nil' do
|
||||
expect(BeEF::Filters.is_non_empty_string?(nil)).to be(false)
|
||||
end
|
||||
|
||||
it 'Integer' do
|
||||
expect(BeEF::Filters.is_non_empty_string?(1)).to be(false)
|
||||
end
|
||||
|
||||
it 'Empty String' do
|
||||
expect(BeEF::Filters.is_non_empty_string?('')).to be(false)
|
||||
end
|
||||
|
||||
it 'null' do
|
||||
expect(BeEF::Filters.is_non_empty_string?("\x00")).to be(true)
|
||||
end
|
||||
|
||||
it 'First char is num' do
|
||||
expect(BeEF::Filters.is_non_empty_string?('0')).to be(true)
|
||||
end
|
||||
|
||||
it 'First char is alpha' do
|
||||
expect(BeEF::Filters.is_non_empty_string?('A')).to be(true)
|
||||
end
|
||||
|
||||
it 'Four num chars' do
|
||||
expect(BeEF::Filters.is_non_empty_string?('3333')).to be(true)
|
||||
end
|
||||
|
||||
it 'Four num chars begining with alpha' do
|
||||
expect(BeEF::Filters.is_non_empty_string?('A3333')).to be(true)
|
||||
end
|
||||
|
||||
it 'Four num chars begining with null' do
|
||||
expect(BeEF::Filters.is_non_empty_string?("\x003333")).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.only?' do
|
||||
it 'success' do
|
||||
expect(BeEF::Filters.only?('A', 'A')).to be(true)
|
||||
end
|
||||
|
||||
it 'fail' do
|
||||
expect(BeEF::Filters.only?('A', 'B')).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.exists?' do
|
||||
it 'success' do
|
||||
expect(BeEF::Filters.exists?('A', 'A')).to be(true)
|
||||
end
|
||||
|
||||
it 'fail' do
|
||||
expect(BeEF::Filters.exists?('A', 'B')).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.has_null?' do
|
||||
context 'false with' do
|
||||
it 'general' do
|
||||
chars = [nil, '', "\x01", "\xFF", 'A', 'A3333', '0', '}', '.', '+', '-', '-1', '0.A', '3333', '33 33', ' AAAAA', 'AAAAAA ']
|
||||
chars.each do |c|
|
||||
expect(BeEF::Filters.has_null?(c)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
it 'alphabet' do
|
||||
(1..255).each do |c|
|
||||
str = ''
|
||||
str.concat(c)
|
||||
expect(BeEF::Filters.has_null?(str)).to be(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'true with' do
|
||||
it 'general' do
|
||||
chars = ["\x00", "A\x00", "AAAAAA\x00", "\x00A", "\x00AAAAAAAA", "A\x00A", "AAAAA\x00AAAA", "A\n\r\x00", "\x00\n\rA", "A\n\r\x00\n\rA", "\tA\x00A"]
|
||||
chars.each do |c|
|
||||
expect(BeEF::Filters.has_null?(c)).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
it 'alphabet null after' do
|
||||
(1..255).each do |c|
|
||||
str = ''
|
||||
str.concat(c)
|
||||
str += "\x00"
|
||||
expect(BeEF::Filters.has_null?(str)).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
it 'alphabet null before' do
|
||||
(1..255).each do |c|
|
||||
str = "\x00"
|
||||
str.concat(c)
|
||||
expect(BeEF::Filters.has_null?(str)).to be(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.has_non_printable_char?' do
|
||||
context 'false with' do
|
||||
it 'general' do
|
||||
chars = [nil, '', 'A', 'A3333', '0', '}', '.', '+', '-', '-1', '0.A', '3333', ' 0AAAAA', ' 0AAA ']
|
||||
chars.each do |c|
|
||||
expect(BeEF::Filters.has_non_printable_char?(c)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
it 'lowercase' do
|
||||
('a'..'z').each do |c|
|
||||
expect(BeEF::Filters.has_non_printable_char?(c)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
it 'uppercase' do
|
||||
('A'..'Z').each do |c|
|
||||
expect(BeEF::Filters.has_non_printable_char?(c)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
it 'numbers' do
|
||||
('0'..'9').each do |c|
|
||||
expect(BeEF::Filters.has_non_printable_char?(c)).to be(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'true with' do
|
||||
it 'general' do
|
||||
chars = ["\x00", "\x01", "\x02", "A\x03", "\x04A", "\x0033333", "\x00AAAAAA", " AAAAA\x00", "\t\x00AAAAA", "\n\x00AAAAA", "\n\r\x00AAAAAAAAA", "AAAAAAA\x00AAAAAAA",
|
||||
"\n\x00"]
|
||||
chars.each do |c|
|
||||
expect(BeEF::Filters.has_non_printable_char?(c)).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
it 'alphabet null before' do
|
||||
(1..255).each do |c|
|
||||
str = ''
|
||||
str.concat(c)
|
||||
str += "\x00"
|
||||
expect(BeEF::Filters.has_non_printable_char?(str)).to be(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.nums_only?' do
|
||||
it 'false with general' do
|
||||
chars = [nil, 1, '', 'A', 'A3333', "\x003333", '}', '.', '+', '-', '-1']
|
||||
chars.each do |c|
|
||||
expect(BeEF::Filters.nums_only?(c)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
it 'true with general' do
|
||||
chars = %w[0 333]
|
||||
chars.each do |c|
|
||||
expect(BeEF::Filters.nums_only?(c)).to be(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.is_valid_float?' do
|
||||
it 'false with general' do
|
||||
chars = [nil, 1, '', 'A', 'A3333', "\x003333", '}', '.', '+', '-', '-1', '0', '333', '0.A']
|
||||
chars.each do |c|
|
||||
expect(BeEF::Filters.is_valid_float?(c)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
it 'true with general' do
|
||||
chars = ['33.33', '0.0', '1.0', '0.1']
|
||||
chars.each do |c|
|
||||
expect(BeEF::Filters.is_valid_float?(c)).to be(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.hexs_only?' do
|
||||
it 'false with general' do
|
||||
chars = [nil, 1, '', "\x003333", '}', '.', '+', '-', '-1', '0.A', '33.33', '0.0', '1.0', '0.1']
|
||||
chars.each do |c|
|
||||
expect(BeEF::Filters.hexs_only?(c)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
it 'true with general' do
|
||||
chars = %w[0123456789ABCDEFabcdef 0 333 A33333 A]
|
||||
chars.each do |c|
|
||||
expect(BeEF::Filters.hexs_only?(c)).to be(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.first_char_is_num?' do
|
||||
it 'false with general' do
|
||||
chars = ['', 'A', 'A33333', "\x0033333"]
|
||||
chars.each do |c|
|
||||
expect(BeEF::Filters.first_char_is_num?(c)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
it 'true with general' do
|
||||
chars = %w[333 0AAAAAA 0]
|
||||
chars.each do |c|
|
||||
expect(BeEF::Filters.first_char_is_num?(c)).to be(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.has_whitespace_char?' do
|
||||
it 'false with general' do
|
||||
chars = ['', 'A', 'A33333', "\x0033333", '0', '}', '.', '+', '-', '-1', '0.A']
|
||||
chars.each do |c|
|
||||
expect(BeEF::Filters.has_whitespace_char?(c)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
it 'true with general' do
|
||||
chars = ['33 33', ' ', ' ', ' 0AAAAAAA', ' 0AAAAAAA ', "\t0AAAAAAA", "\n0AAAAAAAA"]
|
||||
chars.each do |c|
|
||||
expect(BeEF::Filters.has_whitespace_char?(c)).to be(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.alphanums_only?' do
|
||||
context 'false with' do
|
||||
it 'general' do
|
||||
chars = [nil, '', "\n", "\r", "\x01", '}', '.', '+', '-', '-1', 'ee-!@$%^&*}=0.A', '33 33', ' AAAA', 'AAA ']
|
||||
chars.each do |c|
|
||||
expect(BeEF::Filters.alphanums_only?(c)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
it 'additional nulls' do
|
||||
chars = ["\x00", "A\x00", "AAAAAAAAA\x00", "\x00A", "\x00AAAAAAAAA", "A\x00A", "AAAAAAAA\x00AAAAAAAA", "A\n\r\x00", "\x00\n\rA", "A\n\r\x00\n\rA", "\tA\x00A"]
|
||||
chars.each do |c|
|
||||
expect(BeEF::Filters.alphanums_only?(c)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
it 'alphabet null after' do
|
||||
(1..255).each do |c|
|
||||
str = ''
|
||||
str.concat(c)
|
||||
str += "\x00"
|
||||
expect(BeEF::Filters.alphanums_only?(str)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
it 'alphabet null before' do
|
||||
(1..255).each do |c|
|
||||
str = "\x00"
|
||||
str.concat(c)
|
||||
expect(BeEF::Filters.alphanums_only?(str)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
it 'alphabet around null' do
|
||||
(1..255).each do |c|
|
||||
str = ''
|
||||
str.concat(c)
|
||||
str += "\x00"
|
||||
str.concat(c)
|
||||
expect(BeEF::Filters.alphanums_only?(str)).to be(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'true with' do
|
||||
it 'general' do
|
||||
chars = %w[A A3333 0 3333]
|
||||
chars.each do |c|
|
||||
expect(BeEF::Filters.alphanums_only?(c)).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
it 'uppercase' do
|
||||
('A'..'Z').each do |c|
|
||||
expect(BeEF::Filters.alphanums_only?(c)).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
it 'lowercase' do
|
||||
('a'..'z').each do |c|
|
||||
expect(BeEF::Filters.alphanums_only?(c)).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
it 'numbers' do
|
||||
('0'..'9').each do |c|
|
||||
expect(BeEF::Filters.alphanums_only?(c)).to be(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
76
spec/beef/core/filter/browser_spec.rb
Normal file
76
spec/beef/core/filter/browser_spec.rb
Normal file
@@ -0,0 +1,76 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
|
||||
RSpec.describe BeEF::Filters do
|
||||
describe '.is_valid_browsername?' do
|
||||
it 'validates browser names' do
|
||||
expect(BeEF::Filters.is_valid_browsername?('FF')).to be(true)
|
||||
expect(BeEF::Filters.is_valid_browsername?('IE')).to be(true)
|
||||
expect(BeEF::Filters.is_valid_browsername?('CH')).to be(true)
|
||||
expect(BeEF::Filters.is_valid_browsername?('TOOLONG')).to be(false)
|
||||
expect(BeEF::Filters.is_valid_browsername?('')).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.is_valid_osname?' do
|
||||
it 'validates OS names' do
|
||||
expect(BeEF::Filters.is_valid_osname?('Windows XP')).to be(true)
|
||||
expect(BeEF::Filters.is_valid_osname?('A')).to be(false) # too short
|
||||
expect(BeEF::Filters.is_valid_osname?('')).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.is_valid_hwname?' do
|
||||
it 'validates hardware names' do
|
||||
expect(BeEF::Filters.is_valid_hwname?('iPhone')).to be(true)
|
||||
expect(BeEF::Filters.is_valid_hwname?('A')).to be(false) # too short
|
||||
expect(BeEF::Filters.is_valid_hwname?('')).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.is_valid_browserversion?' do
|
||||
it 'validates browser versions' do
|
||||
expect(BeEF::Filters.is_valid_browserversion?('1.0')).to be(true)
|
||||
expect(BeEF::Filters.is_valid_browserversion?('1.2.3.4')).to be(true)
|
||||
expect(BeEF::Filters.is_valid_browserversion?('UNKNOWN')).to be(true)
|
||||
expect(BeEF::Filters.is_valid_browserversion?('ALL')).to be(true)
|
||||
expect(BeEF::Filters.is_valid_browserversion?('invalid')).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.is_valid_osversion?' do
|
||||
it 'validates OS versions' do
|
||||
expect(BeEF::Filters.is_valid_osversion?('10.0')).to be(true)
|
||||
expect(BeEF::Filters.is_valid_osversion?('UNKNOWN')).to be(true)
|
||||
expect(BeEF::Filters.is_valid_osversion?('ALL')).to be(true)
|
||||
expect(BeEF::Filters.is_valid_osversion?('invalid!')).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.is_valid_browserstring?' do
|
||||
it 'validates browser/UA strings' do
|
||||
expect(BeEF::Filters.is_valid_browserstring?('Mozilla/5.0')).to be(true)
|
||||
expect(BeEF::Filters.is_valid_browserstring?('A' * 300)).to be(true)
|
||||
expect(BeEF::Filters.is_valid_browserstring?('A' * 301)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.is_valid_cookies?' do
|
||||
it 'validates cookie strings' do
|
||||
expect(BeEF::Filters.is_valid_cookies?('session=abc123')).to be(true)
|
||||
expect(BeEF::Filters.is_valid_cookies?('A' * 2000)).to be(true)
|
||||
expect(BeEF::Filters.is_valid_cookies?('A' * 2001)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.is_valid_browser_plugins?' do
|
||||
it 'validates browser plugin strings' do
|
||||
expect(BeEF::Filters.is_valid_browser_plugins?('Flash, Java')).to be(true)
|
||||
expect(BeEF::Filters.is_valid_browser_plugins?('A' * 1000)).to be(true)
|
||||
expect(BeEF::Filters.is_valid_browser_plugins?('A' * 1001)).to be(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
57
spec/beef/core/filter/command_spec.rb
Normal file
57
spec/beef/core/filter/command_spec.rb
Normal file
@@ -0,0 +1,57 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
|
||||
RSpec.describe BeEF::Filters do
|
||||
describe '.is_valid_path_info?' do
|
||||
it 'validates path info' do
|
||||
expect(BeEF::Filters.is_valid_path_info?('/path/to/resource')).to be(true)
|
||||
expect(BeEF::Filters.is_valid_path_info?("\x00")).to be(false)
|
||||
expect(BeEF::Filters.is_valid_path_info?(nil)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.is_valid_hook_session_id?' do
|
||||
it 'validates hook session IDs' do
|
||||
expect(BeEF::Filters.is_valid_hook_session_id?('abc123')).to be(true)
|
||||
expect(BeEF::Filters.is_valid_hook_session_id?('')).to be(false)
|
||||
expect(BeEF::Filters.is_valid_hook_session_id?(nil)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.is_valid_command_module_datastore_key?' do
|
||||
it 'validates datastore keys' do
|
||||
expect(BeEF::Filters.is_valid_command_module_datastore_key?('test_key')).to be(true)
|
||||
expect(BeEF::Filters.is_valid_command_module_datastore_key?('')).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.is_valid_command_module_datastore_param?' do
|
||||
it 'validates datastore params' do
|
||||
expect(BeEF::Filters.is_valid_command_module_datastore_param?('test_value')).to be(true)
|
||||
expect(BeEF::Filters.is_valid_command_module_datastore_param?("\x00")).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.has_valid_key_chars?' do
|
||||
it 'validates key characters' do
|
||||
expect(BeEF::Filters.has_valid_key_chars?('test_key')).to be(true)
|
||||
expect(BeEF::Filters.has_valid_key_chars?('')).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.has_valid_param_chars?' do
|
||||
it 'false' do
|
||||
chars = [nil, '', '+']
|
||||
chars.each do |c|
|
||||
expect(BeEF::Filters.has_valid_param_chars?(c)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
it 'true' do
|
||||
expect(BeEF::Filters.has_valid_param_chars?('A')).to be(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,354 +0,0 @@
|
||||
RSpec.describe 'BeEF Filters' do
|
||||
|
||||
context 'is_non_empty_string?' do
|
||||
|
||||
it 'nil' do
|
||||
expect(BeEF::Filters::is_non_empty_string?(nil)).to be(false)
|
||||
end
|
||||
|
||||
it 'Integer' do
|
||||
expect(BeEF::Filters::is_non_empty_string?(1)).to be(false)
|
||||
end
|
||||
|
||||
it 'Empty String' do
|
||||
expect(BeEF::Filters::is_non_empty_string?("")).to be(false)
|
||||
end
|
||||
|
||||
it 'null' do
|
||||
expect(BeEF::Filters::is_non_empty_string?("\x00")).to be(true)
|
||||
end
|
||||
|
||||
it 'First char is num' do
|
||||
expect(BeEF::Filters::is_non_empty_string?("0")).to be(true)
|
||||
end
|
||||
|
||||
it 'First char is alpha' do
|
||||
expect(BeEF::Filters::is_non_empty_string?("A")).to be(true)
|
||||
end
|
||||
|
||||
it 'Four num chars' do
|
||||
expect(BeEF::Filters::is_non_empty_string?("3333")).to be(true)
|
||||
end
|
||||
|
||||
it 'Four num chars begining with alpha' do
|
||||
expect(BeEF::Filters::is_non_empty_string?("A3333")).to be(true)
|
||||
end
|
||||
|
||||
it 'Four num chars begining with null' do
|
||||
expect(BeEF::Filters::is_non_empty_string?("\x003333")).to be(true)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'only?' do
|
||||
|
||||
it 'success' do
|
||||
expect(BeEF::Filters::only?('A', 'A')).to be(true)
|
||||
end
|
||||
|
||||
it 'fail' do
|
||||
expect(BeEF::Filters::only?('A', 'B')).to be(false)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'exists?' do
|
||||
|
||||
it 'success' do
|
||||
expect(BeEF::Filters::exists?('A', 'A')).to be(true)
|
||||
end
|
||||
|
||||
it 'fail' do
|
||||
expect(BeEF::Filters::exists?('A', 'B')).to be(false)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'has_null?' do
|
||||
|
||||
context 'false with' do
|
||||
|
||||
it 'general' do
|
||||
chars = [nil, "", "\x01", "\xFF", "A", "A3333", "0", "}", ".", "+", "-", "-1", "0.A", "3333", "33 33", " AAAAA", "AAAAAA "]
|
||||
chars.each do |c|
|
||||
expect(BeEF::Filters::has_null?(c)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
it 'alphabet' do
|
||||
(1..255).each do |c|
|
||||
str = ''
|
||||
str.concat(c)
|
||||
expect(BeEF::Filters::has_null?(str)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'true with' do
|
||||
|
||||
it 'general' do
|
||||
chars = ["\x00", "A\x00", "AAAAAA\x00", "\x00A", "\x00AAAAAAAA", "A\x00A", "AAAAA\x00AAAA", "A\n\r\x00", "\x00\n\rA", "A\n\r\x00\n\rA", "\tA\x00A"]
|
||||
chars.each do |c|
|
||||
expect(BeEF::Filters::has_null?(c)).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
it 'alphabet null after' do
|
||||
(1..255).each do |c|
|
||||
str = ''
|
||||
str.concat(c)
|
||||
str += "\x00"
|
||||
expect(BeEF::Filters::has_null?(str)).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
it 'alphabet null before' do
|
||||
(1..255).each do |c|
|
||||
str = "\x00"
|
||||
str.concat(c)
|
||||
expect(BeEF::Filters::has_null?(str)).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'has_non_printable_char?' do
|
||||
|
||||
context 'false with' do
|
||||
|
||||
it 'general' do
|
||||
chars = [nil, "", "A", "A3333", "0", "}", ".", "+", "-", "-1", "0.A", "3333", " 0AAAAA", " 0AAA "]
|
||||
chars.each do |c|
|
||||
expect(BeEF::Filters::has_non_printable_char?(c)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
it 'lowercase' do
|
||||
('a'..'z').each do |c|
|
||||
expect(BeEF::Filters::has_non_printable_char?(c)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
it 'uppercase' do
|
||||
('A'..'Z').each do |c|
|
||||
expect(BeEF::Filters::has_non_printable_char?(c)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
it 'numbers' do
|
||||
('0'..'9').each do |c|
|
||||
expect(BeEF::Filters::has_non_printable_char?(c)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'true with' do
|
||||
|
||||
it 'general' do
|
||||
chars = ["\x00", "\x01", "\x02", "A\x03", "\x04A", "\x0033333", "\x00AAAAAA", " AAAAA\x00", "\t\x00AAAAA", "\n\x00AAAAA", "\n\r\x00AAAAAAAAA", "AAAAAAA\x00AAAAAAA", "\n\x00"]
|
||||
chars.each do |c|
|
||||
expect(BeEF::Filters::has_non_printable_char?(c)).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
it 'alphabet null before' do
|
||||
(1..255).each do |c|
|
||||
str = ''
|
||||
str.concat(c)
|
||||
str += "\x00"
|
||||
expect(BeEF::Filters::has_non_printable_char?(str)).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'nums_only?' do
|
||||
|
||||
it 'false with general' do
|
||||
chars = [nil, 1, "", "A", "A3333", "\x003333", "}", ".", "+", "-", "-1"]
|
||||
chars.each do |c|
|
||||
expect(BeEF::Filters::nums_only?(c)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
it 'true with general' do
|
||||
chars = ["0", "333"]
|
||||
chars.each do |c|
|
||||
expect(BeEF::Filters::nums_only?(c)).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'is_valid_float?' do
|
||||
|
||||
it 'false with general' do
|
||||
chars = [nil, 1, "", "A", "A3333", "\x003333", "}", ".", "+", "-", "-1", "0", "333", "0.A"]
|
||||
chars.each do |c|
|
||||
expect(BeEF::Filters::is_valid_float?(c)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
it 'true with general' do
|
||||
chars = ["33.33", "0.0", "1.0", "0.1"]
|
||||
chars.each do |c|
|
||||
expect(BeEF::Filters::is_valid_float?(c)).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'hexs_only?' do
|
||||
|
||||
it 'false with general' do
|
||||
chars = [nil, 1, "", "\x003333", "}", ".", "+", "-", "-1", "0.A", "33.33", "0.0", "1.0", "0.1"]
|
||||
chars.each do |c|
|
||||
expect(BeEF::Filters::hexs_only?(c)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
it 'true with general' do
|
||||
chars = ["0123456789ABCDEFabcdef", "0", "333", "A33333", "A"]
|
||||
chars.each do |c|
|
||||
expect(BeEF::Filters::hexs_only?(c)).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'first_char_is_num?' do
|
||||
|
||||
it 'false with general' do
|
||||
chars = ["", "A", "A33333", "\x0033333"]
|
||||
chars.each do |c|
|
||||
expect(BeEF::Filters::first_char_is_num?(c)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
it 'true with general' do
|
||||
chars = ["333", "0AAAAAA", "0"]
|
||||
chars.each do |c|
|
||||
expect(BeEF::Filters::first_char_is_num?(c)).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'has_whitespace_char?' do
|
||||
|
||||
it 'false with general' do
|
||||
chars = ["", "A", "A33333", "\x0033333", "0", "}", ".", "+", "-", "-1", "0.A"]
|
||||
chars.each do |c|
|
||||
expect(BeEF::Filters::has_whitespace_char?(c)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
it 'true with general' do
|
||||
chars = ["33 33", " ", " ", " 0AAAAAAA", " 0AAAAAAA ", "\t0AAAAAAA", "\n0AAAAAAAA"]
|
||||
chars.each do |c|
|
||||
expect(BeEF::Filters::has_whitespace_char?(c)).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'alphanums_only?' do
|
||||
|
||||
context 'false with' do
|
||||
|
||||
it 'general' do
|
||||
chars = [nil, "", "\n", "\r", "\x01", "}", ".", "+", "-", "-1", "ee-!@$%^&*}=0.A", "33 33", " AAAA", "AAA "]
|
||||
chars.each do |c|
|
||||
expect(BeEF::Filters::alphanums_only?(c)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
it 'additional nulls' do
|
||||
chars = ["\x00", "A\x00", "AAAAAAAAA\x00", "\x00A", "\x00AAAAAAAAA", "A\x00A", "AAAAAAAA\x00AAAAAAAA", "A\n\r\x00", "\x00\n\rA", "A\n\r\x00\n\rA", "\tA\x00A"]
|
||||
chars.each do |c|
|
||||
expect(BeEF::Filters::alphanums_only?(c)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
it 'alphabet null after' do
|
||||
(1..255).each do |c|
|
||||
str = ''
|
||||
str.concat(c)
|
||||
str += "\x00"
|
||||
expect(BeEF::Filters::alphanums_only?(str)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
it 'alphabet null before' do
|
||||
(1..255).each do |c|
|
||||
str = "\x00"
|
||||
str.concat(c)
|
||||
expect(BeEF::Filters::alphanums_only?(str)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
it 'alphabet around null' do
|
||||
(1..255).each do |c|
|
||||
str = ''
|
||||
str.concat(c)
|
||||
str += "\x00"
|
||||
str.concat(c)
|
||||
expect(BeEF::Filters::alphanums_only?(str)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'true with' do
|
||||
|
||||
it 'general' do
|
||||
chars = ["A", "A3333", "0", "3333"]
|
||||
chars.each do |c|
|
||||
expect(BeEF::Filters::alphanums_only?(c)).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
it 'uppercase' do
|
||||
('A'..'Z').each do |c|
|
||||
expect(BeEF::Filters::alphanums_only?(c)).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
it 'lowercase' do
|
||||
('a'..'z').each do |c|
|
||||
expect(BeEF::Filters::alphanums_only?(c)).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
it 'numbers' do
|
||||
('0'..'9').each do |c|
|
||||
expect(BeEF::Filters::alphanums_only?(c)).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'has_valid_param_chars?' do
|
||||
|
||||
it 'false' do
|
||||
chars = [nil, "", "+"]
|
||||
chars.each do |c|
|
||||
expect(BeEF::Filters::has_valid_param_chars?(c)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
it 'true' do
|
||||
expect(BeEF::Filters::has_valid_param_chars?("A")).to be(true)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
50
spec/beef/core/filter/http_spec.rb
Normal file
50
spec/beef/core/filter/http_spec.rb
Normal file
@@ -0,0 +1,50 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
|
||||
RSpec.describe BeEF::Filters do
|
||||
describe '.is_valid_hostname?' do
|
||||
it 'validates hostnames correctly' do
|
||||
expect(BeEF::Filters.is_valid_hostname?('example.com')).to be(true)
|
||||
expect(BeEF::Filters.is_valid_hostname?('sub.example.com')).to be(true)
|
||||
expect(BeEF::Filters.is_valid_hostname?('a' * 256)).to be(false) # too long
|
||||
expect(BeEF::Filters.is_valid_hostname?('')).to be(false)
|
||||
expect(BeEF::Filters.is_valid_hostname?(nil)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.is_valid_verb?' do
|
||||
it 'validates HTTP verbs' do
|
||||
%w[HEAD GET POST OPTIONS PUT DELETE].each do |verb|
|
||||
expect(BeEF::Filters.is_valid_verb?(verb)).to be(true)
|
||||
end
|
||||
expect(BeEF::Filters.is_valid_verb?('INVALID')).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.is_valid_url?' do
|
||||
it 'validates URLs' do
|
||||
expect(BeEF::Filters.is_valid_url?(nil)).to be(false)
|
||||
expect(BeEF::Filters.is_valid_url?('http://example.com')).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.is_valid_http_version?' do
|
||||
it 'validates HTTP versions' do
|
||||
expect(BeEF::Filters.is_valid_http_version?('HTTP/1.0')).to be(true)
|
||||
expect(BeEF::Filters.is_valid_http_version?('HTTP/1.1')).to be(true)
|
||||
expect(BeEF::Filters.is_valid_http_version?('HTTP/2.0')).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.is_valid_host_str?' do
|
||||
it 'validates host header strings' do
|
||||
expect(BeEF::Filters.is_valid_host_str?('Host:')).to be(true)
|
||||
host_str = "Host:\r".dup
|
||||
expect(BeEF::Filters.is_valid_host_str?(host_str)).to be(true)
|
||||
expect(BeEF::Filters.is_valid_host_str?('Invalid')).to be(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
24
spec/beef/core/filter/page_spec.rb
Normal file
24
spec/beef/core/filter/page_spec.rb
Normal file
@@ -0,0 +1,24 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
|
||||
RSpec.describe BeEF::Filters do
|
||||
describe '.is_valid_pagetitle?' do
|
||||
it 'validates page titles' do
|
||||
expect(BeEF::Filters.is_valid_pagetitle?('Test Page')).to be(true)
|
||||
expect(BeEF::Filters.is_valid_pagetitle?('A' * 500)).to be(true)
|
||||
expect(BeEF::Filters.is_valid_pagetitle?('A' * 501)).to be(false)
|
||||
expect(BeEF::Filters.is_valid_pagetitle?("\x00")).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.is_valid_pagereferrer?' do
|
||||
it 'validates page referrers' do
|
||||
expect(BeEF::Filters.is_valid_pagereferrer?('http://example.com')).to be(true)
|
||||
expect(BeEF::Filters.is_valid_pagereferrer?('A' * 350)).to be(true)
|
||||
expect(BeEF::Filters.is_valid_pagereferrer?('A' * 351)).to be(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
117
spec/beef/core/logger_spec.rb
Normal file
117
spec/beef/core/logger_spec.rb
Normal file
@@ -0,0 +1,117 @@
|
||||
#
|
||||
# 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'
|
||||
require 'fileutils'
|
||||
|
||||
RSpec.describe 'BeEF Logger' do
|
||||
let(:home_dir) { $home_dir } # rubocop:disable Style/GlobalVars
|
||||
let(:expected_log_path) { File.join(home_dir, 'beef.log') }
|
||||
|
||||
before(:each) do
|
||||
# Reset the logger to ensure clean state
|
||||
BeEF.logger = nil
|
||||
end
|
||||
|
||||
after(:each) do
|
||||
# Clean up any log files created during tests
|
||||
FileUtils.rm_f(expected_log_path)
|
||||
BeEF.logger = nil
|
||||
end
|
||||
|
||||
describe '.logger' do
|
||||
it 'returns a Logger instance' do
|
||||
expect(BeEF.logger).to be_a(Logger)
|
||||
end
|
||||
|
||||
it 'creates logger with correct file path' do
|
||||
logger = BeEF.logger
|
||||
expect(logger.instance_variable_get(:@logdev).dev.path).to eq(expected_log_path)
|
||||
end
|
||||
|
||||
it 'sets the progname to BeEF' do
|
||||
logger = BeEF.logger
|
||||
expect(logger.progname).to eq('BeEF')
|
||||
end
|
||||
|
||||
it 'sets the log level to WARN' do
|
||||
logger = BeEF.logger
|
||||
expect(logger.level).to eq(Logger::WARN)
|
||||
end
|
||||
|
||||
it 'returns the same logger instance on subsequent calls' do
|
||||
logger1 = BeEF.logger
|
||||
logger2 = BeEF.logger
|
||||
expect(logger1).to be(logger2)
|
||||
end
|
||||
|
||||
it 'creates the log file when logger is accessed' do
|
||||
# Ensure file doesn't exist initially
|
||||
FileUtils.rm_f(expected_log_path)
|
||||
|
||||
BeEF.logger
|
||||
|
||||
expect(File.exist?(expected_log_path)).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.logger=' do
|
||||
it 'allows setting a custom logger' do
|
||||
custom_logger = Logger.new($stdout)
|
||||
BeEF.logger = custom_logger
|
||||
|
||||
expect(BeEF.logger).to be(custom_logger)
|
||||
end
|
||||
|
||||
it 'uses the custom logger instead of creating a new one' do
|
||||
custom_logger = Logger.new($stdout)
|
||||
custom_logger.level = Logger::DEBUG
|
||||
BeEF.logger = custom_logger
|
||||
|
||||
expect(BeEF.logger.level).to eq(Logger::DEBUG)
|
||||
expect(BeEF.logger).to be(custom_logger)
|
||||
end
|
||||
|
||||
it 'allows resetting logger to nil' do
|
||||
BeEF.logger = nil
|
||||
expect(BeEF.instance_variable_get(:@logger)).to be_nil
|
||||
end
|
||||
|
||||
it 'creates a new logger after being set to nil' do
|
||||
original_logger = BeEF.logger
|
||||
BeEF.logger = nil
|
||||
new_logger = BeEF.logger
|
||||
|
||||
expect(new_logger).to be_a(Logger)
|
||||
expect(new_logger).not_to be(original_logger)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'logger functionality' do
|
||||
it 'can log messages at WARN level' do
|
||||
logger = BeEF.logger
|
||||
expect { logger.warn('Test warning message') }.not_to raise_error
|
||||
end
|
||||
|
||||
it 'can log messages at ERROR level' do
|
||||
logger = BeEF.logger
|
||||
expect { logger.error('Test error message') }.not_to raise_error
|
||||
end
|
||||
|
||||
it 'does not log messages below WARN level by default' do
|
||||
logger = BeEF.logger
|
||||
# INFO and DEBUG messages should not be logged at WARN level
|
||||
expect(logger.info?).to be(false)
|
||||
expect(logger.debug?).to be(false)
|
||||
end
|
||||
|
||||
it 'logs messages at WARN level and above' do
|
||||
logger = BeEF.logger
|
||||
expect(logger.warn?).to be(true)
|
||||
expect(logger.error?).to be(true)
|
||||
expect(logger.fatal?).to be(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,13 +1,234 @@
|
||||
RSpec.describe 'BeEF Command class testing' do
|
||||
it 'should return a beef configuration variable' do
|
||||
expect {
|
||||
BeEF::Modules.load if BeEF::Core::Configuration.instance.get('beef.module').nil?
|
||||
}.to_not raise_error
|
||||
command_mock = BeEF::Core::Command.new('test_get_variable')
|
||||
expect(command_mock.config.beef_host).to eq('0.0.0.0')
|
||||
#
|
||||
# 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 'modules/browser/hooked_origin/get_page_links/module'
|
||||
gpl = Get_page_links.new('test_get_variable')
|
||||
expect(gpl.config.beef_host).to eq('0.0.0.0')
|
||||
RSpec.describe BeEF::Core::Command do
|
||||
let(:config) { BeEF::Core::Configuration.instance }
|
||||
|
||||
before do
|
||||
# Ensure modules are loaded
|
||||
BeEF::Modules.load if config.get('beef.module').nil?
|
||||
|
||||
# Set up a test module configuration if it doesn't exist
|
||||
unless config.get('beef.module.test_get_variable')
|
||||
config.set('beef.module.test_get_variable.name', 'Test Get Variable')
|
||||
config.set('beef.module.test_get_variable.path', 'modules/test/')
|
||||
config.set('beef.module.test_get_variable.mount', '/command/test_get_variable.js')
|
||||
config.set('beef.module.test_get_variable.db.id', 1)
|
||||
end
|
||||
end
|
||||
|
||||
describe BeEF::Core::CommandUtils do
|
||||
describe '#format_multiline' do
|
||||
it 'converts newlines to escaped newlines' do
|
||||
result = BeEF::Core::CommandUtils.instance_method(:format_multiline).bind(Object.new).call("line1\nline2")
|
||||
expect(result).to eq("line1\\nline2")
|
||||
end
|
||||
|
||||
it 'handles strings without newlines' do
|
||||
result = BeEF::Core::CommandUtils.instance_method(:format_multiline).bind(Object.new).call("single line")
|
||||
expect(result).to eq("single line")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe BeEF::Core::CommandContext do
|
||||
it 'initializes with hash' do
|
||||
context = described_class.new({ 'key' => 'value' })
|
||||
expect(context['key']).to eq('value')
|
||||
end
|
||||
|
||||
it 'initializes without hash' do
|
||||
context = described_class.new
|
||||
expect(context).to be_a(Erubis::Context)
|
||||
end
|
||||
|
||||
it 'includes CommandUtils' do
|
||||
context = described_class.new
|
||||
expect(context).to respond_to(:format_multiline)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#initialize' do
|
||||
it 'initializes with module key' do
|
||||
command = described_class.new('test_get_variable')
|
||||
expect(command.config).to eq(config)
|
||||
expect(command.datastore).to eq({})
|
||||
expect(command.beefjs_components).to eq({})
|
||||
end
|
||||
|
||||
it 'sets friendlyname from configuration' do
|
||||
# Mock all config calls for initialization
|
||||
allow(config).to receive(:get).and_call_original
|
||||
allow(config).to receive(:get).with('beef.module.test_get_variable.name').and_return('Test Get Variable')
|
||||
allow(config).to receive(:get).with('beef.module.test_get_variable.path').and_return('modules/test/')
|
||||
allow(config).to receive(:get).with('beef.module.test_get_variable.mount').and_return('/command/test.js')
|
||||
allow(config).to receive(:get).with('beef.module.test_get_variable.db.id').and_return(1)
|
||||
command = described_class.new('test_get_variable')
|
||||
expect(command.friendlyname).to eq('Test Get Variable')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#needs_configuration?' do
|
||||
it 'returns true when datastore is not nil' do
|
||||
command = described_class.new('test_get_variable')
|
||||
command.instance_variable_set(:@datastore, {})
|
||||
expect(command.needs_configuration?).to be true
|
||||
end
|
||||
|
||||
it 'returns false when datastore is nil' do
|
||||
command = described_class.new('test_get_variable')
|
||||
command.instance_variable_set(:@datastore, nil)
|
||||
expect(command.needs_configuration?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe '#to_json' do
|
||||
it 'returns JSON with command information' do
|
||||
# Mock all config calls for this test
|
||||
allow(config).to receive(:get).and_call_original
|
||||
allow(config).to receive(:get).with('beef.module.test_get_variable.name').and_return('Test Get Variable')
|
||||
allow(config).to receive(:get).with('beef.module.test_get_variable.description').and_return('Test Description')
|
||||
allow(config).to receive(:get).with('beef.module.test_get_variable.category').and_return('Test Category')
|
||||
allow(config).to receive(:get).with('beef.module.test_get_variable.path').and_return('modules/test/')
|
||||
allow(config).to receive(:get).with('beef.module.test_get_variable.mount').and_return('/command/test.js')
|
||||
allow(config).to receive(:get).with('beef.module.test_get_variable.db.id').and_return(1)
|
||||
allow(BeEF::Module).to receive(:get_options).with('test_get_variable').and_return([])
|
||||
command = described_class.new('test_get_variable')
|
||||
json = command.to_json
|
||||
parsed = JSON.parse(json)
|
||||
expect(parsed['Name']).to eq('Test Get Variable')
|
||||
expect(parsed['Description']).to eq('Test Description')
|
||||
expect(parsed['Category']).to eq('Test Category')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#build_datastore' do
|
||||
it 'parses JSON data into datastore' do
|
||||
command = described_class.new('test_get_variable')
|
||||
data = '{"key": "value"}'
|
||||
command.build_datastore(data)
|
||||
expect(command.datastore).to eq({ 'key' => 'value' })
|
||||
end
|
||||
|
||||
it 'handles invalid JSON gracefully' do
|
||||
command = described_class.new('test_get_variable')
|
||||
command.build_datastore('invalid json')
|
||||
expect(command.datastore).to eq({})
|
||||
end
|
||||
end
|
||||
|
||||
describe '#build_callback_datastore' do
|
||||
it 'initializes datastore with http_headers' do
|
||||
command = described_class.new('test_get_variable')
|
||||
command.build_callback_datastore('result', 1, 'hook', nil, nil)
|
||||
expect(command.datastore).to have_key('http_headers')
|
||||
expect(command.datastore['http_headers']).to eq({})
|
||||
end
|
||||
|
||||
it 'adds results, command_id, and beefhook' do
|
||||
command = described_class.new('test_get_variable')
|
||||
command.build_callback_datastore('result', 1, 'hook', nil, nil)
|
||||
expect(command.datastore['results']).to eq('result')
|
||||
expect(command.datastore['cid']).to eq(1)
|
||||
expect(command.datastore['beefhook']).to eq('hook')
|
||||
end
|
||||
|
||||
it 'adds valid http_params to datastore' do
|
||||
allow(BeEF::Filters).to receive(:is_valid_command_module_datastore_key?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_command_module_datastore_param?).and_return(true)
|
||||
allow(Erubis::XmlHelper).to receive(:escape_xml) { |arg| arg }
|
||||
command = described_class.new('test_get_variable')
|
||||
command.build_callback_datastore('result', 1, 'hook', { 'param1' => 'value1' }, {})
|
||||
expect(command.datastore['param1']).to eq('value1')
|
||||
end
|
||||
|
||||
it 'skips invalid http_params' do
|
||||
allow(BeEF::Filters).to receive(:is_valid_command_module_datastore_key?).and_return(false)
|
||||
command = described_class.new('test_get_variable')
|
||||
command.build_callback_datastore('result', 1, 'hook', { 'invalid' => 'value' }, {})
|
||||
expect(command.datastore).not_to have_key('invalid')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#save' do
|
||||
it 'saves results' do
|
||||
command = described_class.new('test_get_variable')
|
||||
results = { 'data' => 'test' }
|
||||
command.save(results)
|
||||
expect(command.instance_variable_get(:@results)).to eq(results)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#map_file_to_url' do
|
||||
it 'calls AssetHandler bind' do
|
||||
mock_handler = double('AssetHandler')
|
||||
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(mock_handler)
|
||||
expect(mock_handler).to receive(:bind).with('file.txt', nil, nil, 1)
|
||||
command = described_class.new('test_get_variable')
|
||||
command.map_file_to_url('file.txt')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#use' do
|
||||
it 'adds component to beefjs_components when file exists' do
|
||||
# The path construction adds an extra /, so account for that
|
||||
component_path = "#{$root_dir}/core/main/client//net/local.js"
|
||||
allow(File).to receive(:exist?).and_call_original
|
||||
allow(File).to receive(:exist?).with(component_path).and_return(true)
|
||||
command = described_class.new('test_get_variable')
|
||||
command.use('beef.net.local')
|
||||
expect(command.beefjs_components).to have_key('beef.net.local')
|
||||
end
|
||||
|
||||
it 'raises error when component file does not exist' do
|
||||
component_path = "#{$root_dir}/core/main/client//net/nonexistent.js"
|
||||
allow(File).to receive(:exist?).and_call_original
|
||||
allow(File).to receive(:exist?).with(component_path).and_return(false)
|
||||
command = described_class.new('test_get_variable')
|
||||
expect { command.use('beef.net.nonexistent') }.to raise_error(/Invalid beefjs component/)
|
||||
end
|
||||
|
||||
it 'does not add component twice' do
|
||||
component_path = "#{$root_dir}/core/main/client//net/local.js"
|
||||
allow(File).to receive(:exist?).and_call_original
|
||||
allow(File).to receive(:exist?).with(component_path).and_return(true)
|
||||
command = described_class.new('test_get_variable')
|
||||
command.use('beef.net.local')
|
||||
command.use('beef.net.local')
|
||||
expect(command.beefjs_components.keys.count).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#oc_value' do
|
||||
it 'returns option value when option exists' do
|
||||
BeEF::Core::Models::OptionCache.create!(name: 'test_option', value: 'test_value')
|
||||
command = described_class.new('test_get_variable')
|
||||
expect(command.oc_value('test_option')).to eq('test_value')
|
||||
end
|
||||
|
||||
it 'returns nil when option does not exist' do
|
||||
command = described_class.new('test_get_variable')
|
||||
expect(command.oc_value('nonexistent')).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '#apply_defaults' do
|
||||
it 'applies option cache values to datastore' do
|
||||
BeEF::Core::Models::OptionCache.create!(name: 'option1', value: 'cached_value')
|
||||
command = described_class.new('test_get_variable')
|
||||
command.instance_variable_set(:@datastore, [{ 'name' => 'option1', 'value' => 'default_value' }])
|
||||
command.apply_defaults
|
||||
expect(command.datastore[0]['value']).to eq('cached_value')
|
||||
end
|
||||
|
||||
it 'keeps default value when option cache does not exist' do
|
||||
command = described_class.new('test_get_variable')
|
||||
command.instance_variable_set(:@datastore, [{ 'name' => 'option1', 'value' => 'default_value' }])
|
||||
command.apply_defaults
|
||||
expect(command.datastore[0]['value']).to eq('default_value')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
|
||||
RSpec.configure do |config|
|
||||
end
|
||||
|
||||
|
||||
67
spec/beef/core/main/crypto_spec.rb
Normal file
67
spec/beef/core/main/crypto_spec.rb
Normal file
@@ -0,0 +1,67 @@
|
||||
#
|
||||
# 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::Crypto' do
|
||||
let(:config) { BeEF::Core::Configuration.instance }
|
||||
|
||||
describe '.secure_token' do
|
||||
it 'generates a hex string of the specified length' do
|
||||
token = BeEF::Core::Crypto.secure_token(20)
|
||||
expect(token).to be_a(String)
|
||||
expect(token.length).to eq(40) # 20 bytes = 40 hex chars
|
||||
expect(token).to match(/\A[0-9a-f]+\z/)
|
||||
end
|
||||
|
||||
it 'uses config default length when no length provided' do
|
||||
default_length = config.get('beef.crypto_default_value_length').to_i
|
||||
token = BeEF::Core::Crypto.secure_token
|
||||
expect(token.length).to eq(default_length * 2) # bytes to hex conversion
|
||||
end
|
||||
|
||||
it 'raises TypeError for length below minimum' do
|
||||
expect { BeEF::Core::Crypto.secure_token(10) }.to raise_error(TypeError, /minimum length/)
|
||||
end
|
||||
|
||||
it 'generates different tokens on each call' do
|
||||
token1 = BeEF::Core::Crypto.secure_token(20)
|
||||
token2 = BeEF::Core::Crypto.secure_token(20)
|
||||
expect(token1).not_to eq(token2)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.random_alphanum_string' do
|
||||
it 'generates a string of the specified length' do
|
||||
result = BeEF::Core::Crypto.random_alphanum_string(15)
|
||||
expect(result).to be_a(String)
|
||||
expect(result.length).to eq(15)
|
||||
expect(result).to match(/\A[a-zA-Z0-9]+\z/)
|
||||
end
|
||||
|
||||
it 'raises TypeError for invalid inputs' do
|
||||
expect { BeEF::Core::Crypto.random_alphanum_string('invalid') }.to raise_error(TypeError)
|
||||
expect { BeEF::Core::Crypto.random_alphanum_string(0) }.to raise_error(TypeError, /Invalid length/)
|
||||
expect { BeEF::Core::Crypto.random_alphanum_string(-1) }.to raise_error(TypeError, /Invalid length/)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.random_hex_string' do
|
||||
it 'generates a hex string of the specified length' do
|
||||
result = BeEF::Core::Crypto.random_hex_string(10)
|
||||
expect(result).to be_a(String)
|
||||
expect(result.length).to eq(10)
|
||||
expect(result).to match(/\A[0-9a-f]+\z/)
|
||||
end
|
||||
|
||||
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/)
|
||||
end
|
||||
end
|
||||
|
||||
# NOTE: .dns_rule_id is not tested here as it requires database queries
|
||||
# and is better suited for integration tests
|
||||
end
|
||||
110
spec/beef/core/main/geoip_spec.rb
Normal file
110
spec/beef/core/main/geoip_spec.rb
Normal file
@@ -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
|
||||
#
|
||||
|
||||
RSpec.describe BeEF::Core::GeoIp do
|
||||
let(:config) { BeEF::Core::Configuration.instance }
|
||||
let(:geoip) { described_class.instance }
|
||||
|
||||
# Mock MaxMind module if not available
|
||||
before do
|
||||
unless defined?(MaxMind)
|
||||
stub_const('MaxMind', Module.new)
|
||||
stub_const('MaxMind::DB', Class.new)
|
||||
end
|
||||
# MODE_MEMORY is actually :MODE_MEMORY (not :memory) - use actual value if available
|
||||
mode_memory = defined?(MaxMind::DB::MODE_MEMORY) ? MaxMind::DB::MODE_MEMORY : :MODE_MEMORY
|
||||
stub_const('MaxMind::DB::MODE_MEMORY', mode_memory) unless defined?(MaxMind::DB::MODE_MEMORY)
|
||||
end
|
||||
|
||||
before do
|
||||
# Reset singleton instance for each test
|
||||
described_class.instance_variable_set(:@singleton__instance__, nil)
|
||||
# Allow config to receive other calls
|
||||
allow(config).to receive(:get).and_call_original
|
||||
end
|
||||
|
||||
describe '#initialize' do
|
||||
it 'disables GeoIP when configuration is false' do
|
||||
allow(config).to receive(:get).with('beef.geoip.enable').and_return(false)
|
||||
expect(geoip.enabled?).to be false
|
||||
end
|
||||
|
||||
it 'disables GeoIP when database file does not exist' do
|
||||
allow(config).to receive(:get).with('beef.geoip.enable').and_return(true)
|
||||
allow(config).to receive(:get).with('beef.geoip.database').and_return('/nonexistent/db.mmdb')
|
||||
allow(File).to receive(:exist?).with('/nonexistent/db.mmdb').and_return(false)
|
||||
expect(geoip.enabled?).to be false
|
||||
end
|
||||
|
||||
it 'enables GeoIP when database file exists' do
|
||||
# Set up stub BEFORE singleton is created
|
||||
allow(config).to receive(:get).with('beef.geoip.enable').and_return(true)
|
||||
allow(config).to receive(:get).with('beef.geoip.database').and_return('/path/to/db.mmdb')
|
||||
allow(File).to receive(:exist?).and_call_original
|
||||
allow(File).to receive(:exist?).with('/path/to/db.mmdb').and_return(true)
|
||||
mock_reader = double('MaxMind::DB')
|
||||
allow(mock_reader).to receive(:freeze)
|
||||
allow(MaxMind::DB).to receive(:new).with('/path/to/db.mmdb', { mode: MaxMind::DB::MODE_MEMORY }).and_return(mock_reader)
|
||||
# Reset singleton so it reinitializes with our stubs
|
||||
described_class.instance_variable_set(:@singleton__instance__, nil)
|
||||
expect(geoip.enabled?).to be true
|
||||
end
|
||||
|
||||
it 'disables GeoIP on initialization error' do
|
||||
allow(config).to receive(:get).with('beef.geoip.enable').and_return(true)
|
||||
allow(config).to receive(:get).with('beef.geoip.database').and_return('/path/to/db.mmdb')
|
||||
allow(File).to receive(:exist?).with('/path/to/db.mmdb').and_return(true)
|
||||
allow(MaxMind::DB).to receive(:new).and_raise(StandardError.new('Database error'))
|
||||
expect(geoip.enabled?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe '#enabled?' do
|
||||
it 'returns false when GeoIP is disabled' do
|
||||
allow(config).to receive(:get).with('beef.geoip.enable').and_return(false)
|
||||
expect(geoip.enabled?).to be false
|
||||
end
|
||||
|
||||
it 'returns true when GeoIP is enabled' do
|
||||
# Set up stub BEFORE singleton is created - singleton initializes when let(:geoip) is called
|
||||
allow(config).to receive(:get).with('beef.geoip.enable').and_return(true)
|
||||
allow(config).to receive(:get).with('beef.geoip.database').and_return('/path/to/db.mmdb')
|
||||
allow(File).to receive(:exist?).and_call_original
|
||||
allow(File).to receive(:exist?).with('/path/to/db.mmdb').and_return(true)
|
||||
mock_reader = double('MaxMind::DB')
|
||||
allow(mock_reader).to receive(:freeze)
|
||||
# The actual call is: MaxMind::DB.new('/path/to/db.mmdb', mode: :MODE_MEMORY)
|
||||
allow(MaxMind::DB).to receive(:new).with('/path/to/db.mmdb', { mode: :MODE_MEMORY }).and_return(mock_reader)
|
||||
# Reset singleton so it reinitializes with our stubs
|
||||
described_class.instance_variable_set(:@singleton__instance__, nil)
|
||||
expect(geoip.enabled?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
describe '#lookup' do
|
||||
it 'raises TypeError for non-string IP' do
|
||||
allow(config).to receive(:get).with('beef.geoip.enable').and_return(false)
|
||||
expect { geoip.lookup(123) }.to raise_error(TypeError, /"ip" needs to be a string/)
|
||||
end
|
||||
|
||||
it 'returns nil when GeoIP is disabled' do
|
||||
allow(config).to receive(:get).with('beef.geoip.enable').and_return(false)
|
||||
expect(geoip.lookup('192.168.1.1')).to be_nil
|
||||
end
|
||||
|
||||
it 'returns lookup result when GeoIP is enabled' do
|
||||
allow(config).to receive(:get).with('beef.geoip.enable').and_return(true)
|
||||
allow(config).to receive(:get).with('beef.geoip.database').and_return('/path/to/db.mmdb')
|
||||
allow(File).to receive(:exist?).with('/path/to/db.mmdb').and_return(true)
|
||||
mock_reader = double('MaxMind::DB')
|
||||
allow(mock_reader).to receive(:freeze)
|
||||
allow(mock_reader).to receive(:get).with('192.168.1.1').and_return({ 'city' => 'Test City' })
|
||||
allow(MaxMind::DB).to receive(:new).with('/path/to/db.mmdb', anything).and_return(mock_reader)
|
||||
result = geoip.lookup('192.168.1.1')
|
||||
expect(result).to eq({ 'city' => 'Test City' })
|
||||
end
|
||||
end
|
||||
end
|
||||
416
spec/beef/core/main/handlers/browserdetails_spec.rb
Normal file
416
spec/beef/core/main/handlers/browserdetails_spec.rb
Normal file
@@ -0,0 +1,416 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
|
||||
RSpec.describe BeEF::Core::Handlers::BrowserDetails do
|
||||
let(:config) { BeEF::Core::Configuration.instance }
|
||||
let(:session_id) { 'test_session_123' }
|
||||
let(:mock_request) do
|
||||
double('Request',
|
||||
ip: '127.0.0.1',
|
||||
referer: 'http://example.com',
|
||||
env: { 'HTTP_USER_AGENT' => 'Mozilla/5.0' })
|
||||
end
|
||||
let(:data) do
|
||||
{
|
||||
'beefhook' => session_id,
|
||||
'request' => mock_request,
|
||||
'results' => {
|
||||
'browser.name' => 'FF',
|
||||
'browser.version' => '91.0',
|
||||
'browser.window.hostname' => 'example.com',
|
||||
'browser.window.hostport' => '80'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
allow(config).to receive(:get).and_call_original
|
||||
allow(config).to receive(:get).with('beef.dns_hostname_lookup').and_return(false)
|
||||
allow(config).to receive(:get).with('beef.extension.network.enable').and_return(false)
|
||||
allow(config).to receive(:get).with('beef.http.websocket.enable').and_return(false)
|
||||
allow(BeEF::Filters).to receive(:is_valid_hook_session_id?).and_return(true)
|
||||
allow(BeEF::Core::Models::HookedBrowser).to receive(:where).and_return([])
|
||||
allow(BeEF::Core::Logger.instance).to receive(:register)
|
||||
allow(BeEF::Core::GeoIp.instance).to receive(:enabled?).and_return(false)
|
||||
# Stub NetworkHost if it exists, otherwise stub the constant
|
||||
if defined?(BeEF::Core::Models::NetworkHost)
|
||||
allow(BeEF::Core::Models::NetworkHost).to receive(:create)
|
||||
else
|
||||
stub_const('BeEF::Core::Models::NetworkHost', double('NetworkHost', create: nil))
|
||||
end
|
||||
end
|
||||
|
||||
describe '#initialize' do
|
||||
it 'initializes with data and calls setup' do
|
||||
expect_any_instance_of(described_class).to receive(:setup)
|
||||
described_class.new(data)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#err_msg' do
|
||||
let(:handler) do
|
||||
instance = described_class.allocate
|
||||
instance.instance_variable_set(:@data, data)
|
||||
instance
|
||||
end
|
||||
|
||||
it 'calls print_error with prefixed message' do
|
||||
expect(handler).to receive(:print_error).with('[Browser Details] test error')
|
||||
handler.err_msg('test error')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#get_param' do
|
||||
let(:handler) do
|
||||
# Create handler but prevent full setup execution
|
||||
instance = described_class.allocate
|
||||
instance.instance_variable_set(:@data, data)
|
||||
instance
|
||||
end
|
||||
|
||||
it 'returns value when key exists in hash' do
|
||||
result = handler.get_param(data['results'], 'browser.name')
|
||||
expect(result).to eq('FF')
|
||||
end
|
||||
|
||||
it 'returns nil when key does not exist' do
|
||||
result = handler.get_param(data['results'], 'nonexistent')
|
||||
expect(result).to be_nil
|
||||
end
|
||||
|
||||
it 'returns nil when query is not a hash' do
|
||||
result = handler.get_param('not a hash', 'key')
|
||||
expect(result).to be_nil
|
||||
end
|
||||
|
||||
it 'converts value to string' do
|
||||
result = handler.get_param({ 'key' => 123 }, 'key')
|
||||
expect(result).to eq('123')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#setup' do
|
||||
it 'validates session id' do
|
||||
invalid_data = data.dup
|
||||
invalid_data['beefhook'] = 'invalid'
|
||||
allow(BeEF::Filters).to receive(:is_valid_hook_session_id?).with('invalid').and_return(false)
|
||||
expect { described_class.new(invalid_data) }.not_to raise_error
|
||||
end
|
||||
|
||||
it 'skips setup if browser already registered' do
|
||||
existing_browser = double('HookedBrowser', session: session_id)
|
||||
allow(BeEF::Core::Models::HookedBrowser).to receive(:where).and_return([existing_browser])
|
||||
expect(BeEF::Core::Models::HookedBrowser).not_to receive(:new)
|
||||
described_class.new(data)
|
||||
end
|
||||
|
||||
it 'creates new hooked browser when not registered' 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(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_cookies?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_osname?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_hwname?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_date_stamp?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_pagetitle?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_url?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_pagereferrer?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_hostname?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_port?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_browser_plugins?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_system_platform?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:nums_only?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_yes_no?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_memory?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_gpu?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_cpu?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:alphanums_only?).and_return(true)
|
||||
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!)
|
||||
# Mock JSON.parse for proxy detection
|
||||
allow(JSON).to receive(:parse).with('{}').and_return({})
|
||||
allow(BeEF::Core::Models::HookedBrowser).to receive(:new).and_return(zombie)
|
||||
described_class.new(data)
|
||||
expect(BeEF::Core::Models::HookedBrowser).to have_received(:new).with(ip: '127.0.0.1', session: session_id)
|
||||
end
|
||||
|
||||
it 'extracts domain from referer when hostname is missing' do
|
||||
referer_data = data.dup
|
||||
referer_data['results'].delete('browser.window.hostname')
|
||||
referer_data['results'].delete('browser.window.hostport')
|
||||
referer_data['request'] = double('Request', ip: '127.0.0.1', referer: 'https://example.com/page', env: {})
|
||||
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(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_cookies?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_osname?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_hwname?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_date_stamp?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_pagetitle?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_url?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_pagereferrer?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_hostname?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_port?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_browser_plugins?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_system_platform?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:nums_only?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_yes_no?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_memory?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_gpu?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_cpu?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:alphanums_only?).and_return(true)
|
||||
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=).with('example.com')
|
||||
allow(zombie).to receive(:port=).with(443)
|
||||
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)
|
||||
described_class.new(referer_data)
|
||||
expect(zombie).to have_received(:domain=).with('example.com')
|
||||
expect(zombie).to have_received(:port=).with(443)
|
||||
end
|
||||
|
||||
it 'falls back to unknown domain when hostname and referer are missing' do
|
||||
unknown_data = data.dup
|
||||
unknown_data['results'].delete('browser.window.hostname')
|
||||
unknown_data['request'] = double('Request', ip: '127.0.0.1', referer: nil, env: {})
|
||||
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(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_cookies?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_osname?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_hwname?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_date_stamp?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_pagetitle?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_url?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_pagereferrer?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_hostname?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_port?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_browser_plugins?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_system_platform?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:nums_only?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_yes_no?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_memory?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_gpu?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_cpu?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:alphanums_only?).and_return(true)
|
||||
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=).with('unknown')
|
||||
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)
|
||||
described_class.new(unknown_data)
|
||||
expect(zombie).to have_received(:domain=).with('unknown')
|
||||
end
|
||||
|
||||
it 'parses HTTP headers from request env' do
|
||||
env_data = data.dup
|
||||
env_data['request'] = double('Request',
|
||||
ip: '127.0.0.1',
|
||||
referer: 'http://example.com',
|
||||
env: { 'HTTP_USER_AGENT' => 'Mozilla/5.0', 'HTTP_ACCEPT' => 'text/html' })
|
||||
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(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_cookies?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_osname?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_hwname?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_date_stamp?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_pagetitle?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_url?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_pagereferrer?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_hostname?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_port?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_browser_plugins?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_system_platform?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:nums_only?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_yes_no?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_memory?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_gpu?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_cpu?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:alphanums_only?).and_return(true)
|
||||
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=) do |headers|
|
||||
parsed = JSON.parse(headers)
|
||||
expect(parsed).to have_key('USER_AGENT')
|
||||
expect(parsed).to have_key('ACCEPT')
|
||||
expect(parsed['USER_AGENT']).to eq('Mozilla/5.0')
|
||||
end
|
||||
allow(zombie).to receive(:httpheaders).and_return('{}')
|
||||
allow(zombie).to receive(:save!)
|
||||
allow(JSON).to receive(:parse).and_call_original
|
||||
allow(JSON).to receive(:parse).with('{}').and_return({})
|
||||
allow(BeEF::Core::Models::HookedBrowser).to receive(:new).and_return(zombie)
|
||||
described_class.new(env_data)
|
||||
end
|
||||
|
||||
it 'performs DNS hostname lookup when enabled' do
|
||||
allow(config).to receive(:get).with('beef.dns_hostname_lookup').and_return(true)
|
||||
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_hostname?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_browserstring?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_cookies?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_osname?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_hwname?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_date_stamp?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_pagetitle?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_url?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_pagereferrer?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_port?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_browser_plugins?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_system_platform?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:nums_only?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_yes_no?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_memory?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_gpu?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_cpu?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:alphanums_only?).and_return(true)
|
||||
allow(BeEF::Core::Models::BrowserDetails).to receive(:set)
|
||||
allow(BeEF::Core::Constants::Browsers).to receive(:friendly_name).and_return('Firefox')
|
||||
allow(Resolv).to receive(:getname).with('127.0.0.1').and_return('localhost')
|
||||
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)
|
||||
expect(Resolv).to receive(:getname).with('127.0.0.1')
|
||||
expect(BeEF::Core::Models::BrowserDetails).to receive(:set).with(session_id, 'host.name', 'localhost')
|
||||
described_class.new(data)
|
||||
end
|
||||
|
||||
it 'handles GeoIP lookup when enabled' do
|
||||
allow(BeEF::Core::GeoIp.instance).to receive(:enabled?).and_return(true)
|
||||
geoip_data = {
|
||||
'city' => { 'names' => { 'en' => 'San Francisco' } },
|
||||
'country' => { 'names' => { 'en' => 'United States' }, 'iso_code' => 'US' },
|
||||
'registered_country' => { 'names' => { 'en' => 'United States' }, 'iso_code' => 'US' },
|
||||
'continent' => { 'names' => { 'en' => 'North America' }, 'code' => 'NA' },
|
||||
'location' => { 'latitude' => 37.7749, 'longitude' => -122.4194, 'time_zone' => 'America/Los_Angeles' }
|
||||
}
|
||||
allow(BeEF::Core::GeoIp.instance).to receive(:lookup).with('127.0.0.1').and_return(geoip_data)
|
||||
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(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_cookies?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_osname?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_hwname?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_date_stamp?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_pagetitle?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_url?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_pagereferrer?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_hostname?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_port?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_browser_plugins?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_system_platform?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:nums_only?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_yes_no?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_memory?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_gpu?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_cpu?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:alphanums_only?).and_return(true)
|
||||
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)
|
||||
expect(BeEF::Core::Models::BrowserDetails).to receive(:set).with(session_id, 'location.city', 'San Francisco')
|
||||
expect(BeEF::Core::Models::BrowserDetails).to receive(:set).with(session_id, 'location.country', 'United States')
|
||||
described_class.new(data)
|
||||
end
|
||||
|
||||
it 'detects and stores proxy information' do
|
||||
proxy_data = data.dup
|
||||
proxy_data['request'] = double('Request',
|
||||
ip: '127.0.0.1',
|
||||
referer: 'http://example.com',
|
||||
env: { 'HTTP_X_FORWARDED_FOR' => '192.168.1.1', 'HTTP_VIA' => 'proxy.example.com' })
|
||||
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(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_cookies?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_osname?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_hwname?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_date_stamp?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_pagetitle?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_url?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_pagereferrer?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_hostname?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_port?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_browser_plugins?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_system_platform?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:nums_only?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_yes_no?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_memory?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_gpu?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:is_valid_cpu?).and_return(true)
|
||||
allow(BeEF::Filters).to receive(:alphanums_only?).and_return(true)
|
||||
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=)
|
||||
headers_json = '{"X_FORWARDED_FOR":"192.168.1.1","VIA":"proxy.example.com"}'
|
||||
allow(zombie).to receive(:httpheaders=)
|
||||
allow(zombie).to receive(:httpheaders).and_return(headers_json)
|
||||
allow(zombie).to receive(:save!)
|
||||
allow(JSON).to receive(:parse).with(headers_json).and_return({ 'X_FORWARDED_FOR' => '192.168.1.1', 'VIA' => 'proxy.example.com' })
|
||||
allow(BeEF::Core::Models::HookedBrowser).to receive(:new).and_return(zombie)
|
||||
expect(BeEF::Core::Models::BrowserDetails).to receive(:set).with(session_id, 'network.proxy', 'Yes')
|
||||
expect(BeEF::Core::Models::BrowserDetails).to receive(:set).with(session_id, 'network.proxy.client', '192.168.1.1')
|
||||
expect(BeEF::Core::Models::BrowserDetails).to receive(:set).with(session_id, 'network.proxy.server', 'proxy.example.com')
|
||||
described_class.new(proxy_data)
|
||||
end
|
||||
end
|
||||
end
|
||||
180
spec/beef/core/main/handlers/commands_spec.rb
Normal file
180
spec/beef/core/main/handlers/commands_spec.rb
Normal file
@@ -0,0 +1,180 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
|
||||
RSpec.describe BeEF::Core::Handlers::Commands do
|
||||
let(:mock_request) do
|
||||
double('request',
|
||||
params: { 'cid' => 123, 'beefhook' => 'test_session_id', 'results' => { 'data' => 'test' } },
|
||||
env: { 'HTTP_USER_AGENT' => 'Mozilla/5.0' })
|
||||
end
|
||||
|
||||
let(:data) do
|
||||
{
|
||||
'request' => mock_request,
|
||||
'status' => 1,
|
||||
'results' => { 'data' => 'test' },
|
||||
'cid' => 123,
|
||||
'beefhook' => 'test_session_id'
|
||||
}
|
||||
end
|
||||
|
||||
let(:mock_command_class) do
|
||||
Class.new do
|
||||
def initialize(_key)
|
||||
@friendlyname = 'Test Command'
|
||||
end
|
||||
|
||||
attr_accessor :session_id
|
||||
|
||||
def friendlyname
|
||||
@friendlyname
|
||||
end
|
||||
|
||||
def build_callback_datastore(_result, _command_id, _beefhook, _http_params, _http_header); end
|
||||
|
||||
def post_execute; end
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
allow(BeEF::Core::Command).to receive(:const_get).and_return(mock_command_class)
|
||||
allow(BeEF::Module).to receive(:get_key_by_class).and_return('test_module')
|
||||
allow(BeEF::Core::Models::Command).to receive(:save_result).and_return(true)
|
||||
end
|
||||
|
||||
describe '#initialize' do
|
||||
it 'initializes with data and class name' do
|
||||
handler = described_class.new(data, 'test')
|
||||
expect(handler.instance_variable_get(:@data)).to eq(data)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#get_param' do
|
||||
let(:handler) { described_class.new(data, 'test') }
|
||||
|
||||
it 'returns value when key exists' do
|
||||
expect(handler.get_param(data, 'status')).to eq(1)
|
||||
end
|
||||
|
||||
it 'returns nil when key does not exist' do
|
||||
expect(handler.get_param(data, 'nonexistent')).to be_nil
|
||||
end
|
||||
|
||||
it 'returns nil when query is not a hash' do
|
||||
expect(handler.get_param('not a hash', 'key')).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '#setup' do
|
||||
context 'with valid parameters' do
|
||||
it 'processes command successfully' do
|
||||
allow(BeEF::Filters).to receive(:is_valid_hook_session_id?).and_return(true)
|
||||
handler = described_class.new(data, 'test')
|
||||
expect(BeEF::Core::Models::Command).to receive(:save_result).with(
|
||||
'test_session_id',
|
||||
123,
|
||||
'Test Command',
|
||||
{ 'data' => { 'data' => 'test' } },
|
||||
1
|
||||
)
|
||||
handler.setup
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid command id' do
|
||||
let(:invalid_data) do
|
||||
{
|
||||
'request' => double('request', params: { 'cid' => 'not_an_integer' }, env: {}),
|
||||
'status' => 1,
|
||||
'results' => {}
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns early without saving' do
|
||||
handler = described_class.new(invalid_data, 'test')
|
||||
expect(BeEF::Core::Models::Command).not_to receive(:save_result)
|
||||
handler.setup
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid session id' do
|
||||
let(:invalid_data) do
|
||||
{
|
||||
'request' => double('request', params: { 'cid' => 123, 'beefhook' => 'invalid' }, env: {}),
|
||||
'status' => 1,
|
||||
'results' => {}
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns early without saving' do
|
||||
allow(BeEF::Filters).to receive(:is_valid_hook_session_id?).and_return(false)
|
||||
handler = described_class.new(invalid_data, 'test')
|
||||
expect(BeEF::Core::Models::Command).not_to receive(:save_result)
|
||||
handler.setup
|
||||
end
|
||||
end
|
||||
|
||||
context 'with empty friendly name' do
|
||||
let(:empty_friendlyname_command) do
|
||||
Class.new do
|
||||
def initialize(_key)
|
||||
@friendlyname = ''
|
||||
end
|
||||
|
||||
attr_accessor :session_id
|
||||
|
||||
def friendlyname
|
||||
@friendlyname
|
||||
end
|
||||
|
||||
def build_callback_datastore(_result, _command_id, _beefhook, _http_params, _http_header); end
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns early without saving' do
|
||||
allow(BeEF::Core::Command).to receive(:const_get).and_return(empty_friendlyname_command)
|
||||
allow(BeEF::Filters).to receive(:is_valid_hook_session_id?).and_return(true)
|
||||
handler = described_class.new(data, 'test')
|
||||
expect(BeEF::Core::Models::Command).not_to receive(:save_result)
|
||||
handler.setup
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid status' do
|
||||
let(:invalid_status_data) do
|
||||
{
|
||||
'request' => mock_request,
|
||||
'status' => 'not_an_integer',
|
||||
'results' => { 'data' => 'test' }
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns early without saving' do
|
||||
allow(BeEF::Filters).to receive(:is_valid_hook_session_id?).and_return(true)
|
||||
handler = described_class.new(invalid_status_data, 'test')
|
||||
expect(BeEF::Core::Models::Command).not_to receive(:save_result)
|
||||
handler.setup
|
||||
end
|
||||
end
|
||||
|
||||
context 'with empty results' do
|
||||
let(:empty_results_data) do
|
||||
{
|
||||
'request' => mock_request,
|
||||
'status' => 1,
|
||||
'results' => {}
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns early without saving' do
|
||||
allow(BeEF::Filters).to receive(:is_valid_hook_session_id?).and_return(true)
|
||||
handler = described_class.new(empty_results_data, 'test')
|
||||
expect(BeEF::Core::Models::Command).not_to receive(:save_result)
|
||||
handler.setup
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
40
spec/beef/core/main/handlers/hookedbrowsers_spec.rb
Normal file
40
spec/beef/core/main/handlers/hookedbrowsers_spec.rb
Normal file
@@ -0,0 +1,40 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
|
||||
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
|
||||
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)
|
||||
end
|
||||
|
||||
expect(matched).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
102
spec/beef/core/main/migration_spec.rb
Normal file
102
spec/beef/core/main/migration_spec.rb
Normal file
@@ -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
|
||||
#
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'BeEF::Core::Migration' do
|
||||
let(:migration) { BeEF::Core::Migration.instance }
|
||||
let(:config) { BeEF::Core::Configuration.instance }
|
||||
let(:api_registrar) { BeEF::API::Registrar.instance }
|
||||
|
||||
describe '.instance' do
|
||||
it 'returns a singleton instance' do
|
||||
instance1 = BeEF::Core::Migration.instance
|
||||
instance2 = BeEF::Core::Migration.instance
|
||||
expect(instance1).to be(instance2)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#update_db!' do
|
||||
it 'calls update_commands!' do
|
||||
expect(migration).to receive(:update_commands!)
|
||||
expect(migration.update_db!).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '#update_commands!' do
|
||||
before do
|
||||
# Clear existing modules from database
|
||||
BeEF::Core::Models::CommandModule.destroy_all
|
||||
|
||||
# Mock API registrar to verify it's called
|
||||
allow(api_registrar).to receive(:fire)
|
||||
end
|
||||
|
||||
it 'creates new modules from config that are not in database' do
|
||||
# Setup config with a new module
|
||||
module_config = {
|
||||
'test_module' => { 'path' => 'modules/test/' }
|
||||
}
|
||||
allow(config).to receive(:get).with('beef.module').and_return(module_config)
|
||||
allow(config).to receive(:get).with('beef.module.test_module').and_return({ 'path' => 'modules/test/' })
|
||||
|
||||
initial_count = BeEF::Core::Models::CommandModule.count
|
||||
migration.update_commands!
|
||||
|
||||
expect(BeEF::Core::Models::CommandModule.count).to eq(initial_count + 1)
|
||||
created_module = BeEF::Core::Models::CommandModule.find_by(name: 'test_module')
|
||||
expect(created_module).not_to be_nil
|
||||
expect(created_module.path).to eq('modules/test/module.rb')
|
||||
end
|
||||
|
||||
it 'updates config with database IDs and paths for existing modules' do
|
||||
# Create a module in the database first
|
||||
existing_module = BeEF::Core::Models::CommandModule.create!(
|
||||
name: 'existing_module',
|
||||
path: 'modules/existing/module.rb'
|
||||
)
|
||||
|
||||
# Setup config to include this existing module
|
||||
module_config = {
|
||||
'existing_module' => { 'path' => 'modules/existing/' }
|
||||
}
|
||||
allow(config).to receive(:get).with('beef.module').and_return(module_config)
|
||||
allow(config).to receive(:get).with('beef.module.existing_module').and_return({ 'path' => 'modules/existing/' })
|
||||
allow(config).to receive(:set)
|
||||
|
||||
migration.update_commands!
|
||||
|
||||
expect(config).to have_received(:set).with('beef.module.existing_module.db.id', existing_module.id)
|
||||
expect(config).to have_received(:set).with('beef.module.existing_module.db.path', 'modules/existing/module.rb')
|
||||
end
|
||||
|
||||
it 'fires the migrate_commands API event' do
|
||||
allow(config).to receive(:get).with('beef.module').and_return({})
|
||||
migration.update_commands!
|
||||
expect(api_registrar).to have_received(:fire).with(BeEF::API::Migration, 'migrate_commands')
|
||||
end
|
||||
|
||||
it 'does not create modules that already exist in database' do
|
||||
# Create a module in the database
|
||||
BeEF::Core::Models::CommandModule.create!(
|
||||
name: 'existing_module',
|
||||
path: 'modules/existing/module.rb'
|
||||
)
|
||||
|
||||
# Setup config with the same module
|
||||
module_config = {
|
||||
'existing_module' => { 'path' => 'modules/existing/' }
|
||||
}
|
||||
allow(config).to receive(:get).with('beef.module').and_return(module_config)
|
||||
allow(config).to receive(:get).with('beef.module.existing_module').and_return({ 'path' => 'modules/existing/' })
|
||||
|
||||
initial_count = BeEF::Core::Models::CommandModule.count
|
||||
migration.update_commands!
|
||||
|
||||
# Should not create a duplicate
|
||||
expect(BeEF::Core::Models::CommandModule.count).to eq(initial_count)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,3 +1,9 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
|
||||
RSpec.describe 'BeEF BrowserDetails' do
|
||||
|
||||
before(:all) do
|
||||
|
||||
19
spec/beef/core/main/models/legacybrowseruseragents_spec.rb
Normal file
19
spec/beef/core/main/models/legacybrowseruseragents_spec.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
|
||||
RSpec.describe BeEF::Core::Models::LegacyBrowserUserAgents do
|
||||
describe '.user_agents' do
|
||||
it 'returns an array' do
|
||||
expect(described_class.user_agents).to be_a(Array)
|
||||
end
|
||||
|
||||
it 'returns an array that can be iterated' do
|
||||
result = described_class.user_agents.map { |ua| ua }
|
||||
|
||||
expect(result).to be_a(Array)
|
||||
end
|
||||
end
|
||||
end
|
||||
60
spec/beef/core/main/models/optioncache_spec.rb
Normal file
60
spec/beef/core/main/models/optioncache_spec.rb
Normal file
@@ -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
|
||||
#
|
||||
|
||||
RSpec.describe BeEF::Core::Models::OptionCache do
|
||||
describe '.first_or_create' do
|
||||
it 'creates a new option cache with a name' do
|
||||
name = 'test_option'
|
||||
option = described_class.first_or_create(name: name)
|
||||
|
||||
expect(option).to be_persisted
|
||||
expect(option.name).to eq(name)
|
||||
expect(option.value).to be_nil
|
||||
end
|
||||
|
||||
it 'returns existing option cache if it already exists' do
|
||||
name = 'existing_option'
|
||||
existing = described_class.create!(name: name, value: 'existing_value')
|
||||
|
||||
option = described_class.first_or_create(name: name)
|
||||
|
||||
expect(option.id).to eq(existing.id)
|
||||
expect(option.name).to eq(name)
|
||||
expect(option.value).to eq('existing_value')
|
||||
end
|
||||
end
|
||||
|
||||
describe '.where' do
|
||||
it 'finds option cache by name' do
|
||||
name = 'findable_option'
|
||||
described_class.create!(name: name, value: 'test_value')
|
||||
|
||||
option = described_class.where(name: name).first
|
||||
|
||||
expect(option).not_to be_nil
|
||||
expect(option.name).to eq(name)
|
||||
expect(option.value).to eq('test_value')
|
||||
end
|
||||
|
||||
it 'returns nil when option cache does not exist' do
|
||||
option = described_class.where(name: 'non_existent').first
|
||||
|
||||
expect(option).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe 'attributes' do
|
||||
it 'can set and retrieve name' do
|
||||
option = described_class.new(name: 'test_name')
|
||||
expect(option.name).to eq('test_name')
|
||||
end
|
||||
|
||||
it 'can set and retrieve value' do
|
||||
option = described_class.new(value: 'test_value')
|
||||
expect(option.value).to eq('test_value')
|
||||
end
|
||||
end
|
||||
end
|
||||
52
spec/beef/core/main/models/result_spec.rb
Normal file
52
spec/beef/core/main/models/result_spec.rb
Normal file
@@ -0,0 +1,52 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
|
||||
RSpec.describe BeEF::Core::Models::Result do
|
||||
describe 'associations' do
|
||||
it 'has_one command' do
|
||||
expect(described_class.reflect_on_association(:command)).not_to be_nil
|
||||
expect(described_class.reflect_on_association(:command).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 '.create' do
|
||||
let(:hooked_browser) { BeEF::Core::Models::HookedBrowser.create!(session: 'test_session', ip: '127.0.0.1') }
|
||||
let(:command_module) { BeEF::Core::Models::CommandModule.create!(name: 'test_module', path: 'modules/test/') }
|
||||
let(:command) { BeEF::Core::Models::Command.create!(hooked_browser_id: hooked_browser.id, command_module_id: command_module.id) }
|
||||
|
||||
it 'creates a result with required attributes' do
|
||||
result = described_class.create!(
|
||||
hooked_browser_id: hooked_browser.id,
|
||||
command_id: command.id,
|
||||
data: { 'test' => 'data' }.to_json,
|
||||
status: 0,
|
||||
date: Time.now.to_i
|
||||
)
|
||||
|
||||
expect(result).to be_persisted
|
||||
expect(result.hooked_browser_id).to eq(hooked_browser.id)
|
||||
expect(result.command_id).to eq(command.id)
|
||||
expect(result.status).to eq(0)
|
||||
end
|
||||
|
||||
it 'can access command_id' do
|
||||
result = described_class.create!(
|
||||
hooked_browser_id: hooked_browser.id,
|
||||
command_id: command.id,
|
||||
data: {}.to_json,
|
||||
status: 0,
|
||||
date: Time.now.to_i
|
||||
)
|
||||
|
||||
expect(result.command_id).to eq(command.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
149
spec/beef/core/main/network_stack/assethandler_spec.rb
Normal file
149
spec/beef/core/main/network_stack/assethandler_spec.rb
Normal file
@@ -0,0 +1,149 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
|
||||
RSpec.describe BeEF::Core::NetworkStack::Handlers::AssetHandler do
|
||||
let(:handler) { described_class.instance }
|
||||
|
||||
before do
|
||||
@mock_server = double('server', mount: true, unmount: true, remap: true)
|
||||
allow(BeEF::Core::Server).to receive(:instance).and_return(@mock_server)
|
||||
# Reset singleton state
|
||||
handler.instance_variable_set(:@allocations, {})
|
||||
handler.instance_variable_set(:@sockets, {})
|
||||
handler.instance_variable_set(:@http_server, @mock_server)
|
||||
end
|
||||
|
||||
describe '#initialize' do
|
||||
it 'initializes with empty allocations and sockets' do
|
||||
expect(handler.allocations).to eq({})
|
||||
expect(handler.root_dir).to be_a(String)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#build_url' do
|
||||
it 'returns path when path is provided' do
|
||||
expect(handler.build_url('/test', nil)).to eq('/test')
|
||||
end
|
||||
|
||||
it 'appends extension when provided' do
|
||||
expect(handler.build_url('/test', 'js')).to eq('/test.js')
|
||||
end
|
||||
|
||||
it 'generates random URL when path is nil' do
|
||||
url = handler.build_url(nil, nil)
|
||||
expect(url).to start_with('/')
|
||||
expect(url.length).to be > 1
|
||||
end
|
||||
|
||||
it 'generates random URL with extension when path is nil' do
|
||||
url = handler.build_url(nil, 'js')
|
||||
expect(url).to end_with('.js')
|
||||
expect(url).to start_with('/')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#check' do
|
||||
it 'returns false when URL is not allocated' do
|
||||
expect(handler.check('/nonexistent')).to be false
|
||||
end
|
||||
|
||||
it 'returns true when count is -1 (unlimited)' do
|
||||
handler.instance_variable_set(:@allocations, { '/test' => { 'count' => -1 } })
|
||||
expect(handler.check('/test')).to be true
|
||||
end
|
||||
|
||||
it 'decrements count and returns true when count > 0' do
|
||||
handler.instance_variable_set(:@allocations, { '/test' => { 'count' => 2 } })
|
||||
expect(handler.check('/test')).to be true
|
||||
expect(handler.allocations['/test']['count']).to eq(1)
|
||||
end
|
||||
|
||||
it 'unbinds when count reaches 0' do
|
||||
handler.instance_variable_set(:@allocations, { '/test' => { 'count' => 1 } })
|
||||
expect(handler).to receive(:unbind).with('/test')
|
||||
handler.check('/test')
|
||||
end
|
||||
|
||||
it 'returns false when count is 0' do
|
||||
handler.instance_variable_set(:@allocations, { '/test' => { 'count' => 0 } })
|
||||
expect(handler.check('/test')).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe '#bind_redirect' do
|
||||
it 'binds redirector to URL' do
|
||||
expect(@mock_server).to receive(:mount)
|
||||
expect(@mock_server).to receive(:remap)
|
||||
url = handler.bind_redirect('http://example.com', '/redirect')
|
||||
expect(url).to eq('/redirect')
|
||||
expect(handler.allocations['/redirect']).to eq({ 'target' => 'http://example.com' })
|
||||
end
|
||||
|
||||
it 'generates random URL when path is nil' do
|
||||
expect(@mock_server).to receive(:mount)
|
||||
expect(@mock_server).to receive(:remap)
|
||||
url = handler.bind_redirect('http://example.com')
|
||||
expect(url).to start_with('/')
|
||||
expect(handler.allocations[url]).not_to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '#bind_raw' do
|
||||
it 'binds raw HTTP response to URL' do
|
||||
expect(@mock_server).to receive(:mount)
|
||||
expect(@mock_server).to receive(:remap)
|
||||
url = handler.bind_raw('200', { 'Content-Type' => 'text/html' }, '<html></html>', '/raw')
|
||||
expect(url).to eq('/raw')
|
||||
expect(handler.allocations['/raw']).to eq({})
|
||||
end
|
||||
end
|
||||
|
||||
describe '#bind' do
|
||||
let(:test_file) { '/spec/support/assets/test.txt' }
|
||||
let(:test_file_path) { File.join(handler.root_dir, test_file) }
|
||||
|
||||
before do
|
||||
FileUtils.mkdir_p(File.dirname(test_file_path))
|
||||
File.write(test_file_path, 'test content')
|
||||
end
|
||||
|
||||
after do
|
||||
FileUtils.rm_f(test_file_path)
|
||||
end
|
||||
|
||||
it 'binds file to URL when file exists' do
|
||||
expect(@mock_server).to receive(:mount)
|
||||
expect(@mock_server).to receive(:remap)
|
||||
url = handler.bind(test_file, '/test')
|
||||
expect(url).to eq('/test')
|
||||
expect(handler.allocations['/test']['file']).to include(test_file)
|
||||
end
|
||||
|
||||
it 'returns nil when file does not exist' do
|
||||
expect(@mock_server).not_to receive(:mount)
|
||||
result = handler.bind('/nonexistent/file.txt', '/test')
|
||||
expect(result).to be_nil
|
||||
end
|
||||
|
||||
it 'uses text/plain content type when extension is nil' do
|
||||
expect(@mock_server).to receive(:mount) do |_url, handler_obj|
|
||||
expect(handler_obj.instance_variable_get(:@header)['Content-Type']).to eq('text/plain')
|
||||
end
|
||||
expect(@mock_server).to receive(:remap)
|
||||
handler.bind(test_file, '/test', nil)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#unbind' do
|
||||
it 'removes allocation and unmounts URL' do
|
||||
handler.instance_variable_set(:@allocations, { '/test' => {} })
|
||||
expect(@mock_server).to receive(:unmount).with('/test')
|
||||
expect(@mock_server).to receive(:remap)
|
||||
handler.unbind('/test')
|
||||
expect(handler.allocations).not_to have_key('/test')
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,3 +1,9 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
|
||||
RSpec.describe 'BeEF Dynamic Reconsturction' do
|
||||
|
||||
before(:all) do
|
||||
|
||||
51
spec/beef/core/main/network_stack/handlers/raw_spec.rb
Normal file
51
spec/beef/core/main/network_stack/handlers/raw_spec.rb
Normal file
@@ -0,0 +1,51 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
|
||||
RSpec.describe BeEF::Core::NetworkStack::Handlers::Raw do
|
||||
describe '#initialize' do
|
||||
it 'initializes with status, header, and body' do
|
||||
handler = described_class.new('200', { 'Content-Type' => 'text/html' }, '<html></html>')
|
||||
expect(handler.instance_variable_get(:@status)).to eq('200')
|
||||
expect(handler.instance_variable_get(:@header)).to eq({ 'Content-Type' => 'text/html' })
|
||||
expect(handler.instance_variable_get(:@body)).to eq('<html></html>')
|
||||
end
|
||||
|
||||
it 'initializes with default empty header and nil body' do
|
||||
handler = described_class.new('404')
|
||||
expect(handler.instance_variable_get(:@status)).to eq('404')
|
||||
expect(handler.instance_variable_get(:@header)).to eq({})
|
||||
expect(handler.instance_variable_get(:@body)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '#call' do
|
||||
it 'returns Rack::Response with correct status, header, and body' do
|
||||
handler = described_class.new('200', { 'Content-Type' => 'text/html' }, '<html></html>')
|
||||
response = handler.call({})
|
||||
|
||||
expect(response).to be_a(Rack::Response)
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.headers['Content-Type']).to eq('text/html')
|
||||
expect(response.body).to eq(['<html></html>'])
|
||||
end
|
||||
|
||||
it 'handles different status codes' do
|
||||
handler = described_class.new('404', {}, 'Not Found')
|
||||
response = handler.call({})
|
||||
|
||||
expect(response.status).to eq(404)
|
||||
expect(response.body).to eq(['Not Found'])
|
||||
end
|
||||
|
||||
it 'handles nil body' do
|
||||
handler = described_class.new('204', {})
|
||||
response = handler.call({})
|
||||
|
||||
expect(response.status).to eq(204)
|
||||
expect(response.body).to eq([])
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,3 +1,9 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
|
||||
RSpec.describe 'BeEF Redirector' do
|
||||
|
||||
before(:all) do
|
||||
|
||||
149
spec/beef/core/main/router/router_spec.rb
Normal file
149
spec/beef/core/main/router/router_spec.rb
Normal file
@@ -0,0 +1,149 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
|
||||
RSpec.describe BeEF::Core::Router::Router do
|
||||
let(:config) { BeEF::Core::Configuration.instance }
|
||||
|
||||
# Create a test instance that we can call private methods on
|
||||
let(:router_instance) do
|
||||
instance = described_class.allocate
|
||||
instance.instance_variable_set(:@config, config)
|
||||
instance
|
||||
end
|
||||
|
||||
describe '#response_headers' do
|
||||
it 'returns default headers when web server imitation is disabled' do
|
||||
allow(config).to receive(:get).with('beef.http.web_server_imitation.enable').and_return(false)
|
||||
headers = router_instance.send(:response_headers)
|
||||
expect(headers['Server']).to eq('')
|
||||
expect(headers['Content-Type']).to eq('text/html')
|
||||
end
|
||||
|
||||
it 'returns Apache headers when type is apache' do
|
||||
allow(config).to receive(:get).with('beef.http.web_server_imitation.enable').and_return(true)
|
||||
allow(config).to receive(:get).with('beef.http.web_server_imitation.type').and_return('apache')
|
||||
headers = router_instance.send(:response_headers)
|
||||
expect(headers['Server']).to eq('Apache/2.2.3 (CentOS)')
|
||||
expect(headers['Content-Type']).to eq('text/html; charset=UTF-8')
|
||||
end
|
||||
|
||||
it 'returns IIS headers when type is iis' do
|
||||
allow(config).to receive(:get).with('beef.http.web_server_imitation.enable').and_return(true)
|
||||
allow(config).to receive(:get).with('beef.http.web_server_imitation.type').and_return('iis')
|
||||
headers = router_instance.send(:response_headers)
|
||||
expect(headers['Server']).to eq('Microsoft-IIS/6.0')
|
||||
expect(headers['X-Powered-By']).to eq('ASP.NET')
|
||||
expect(headers['Content-Type']).to eq('text/html; charset=UTF-8')
|
||||
end
|
||||
|
||||
it 'returns nginx headers when type is nginx' do
|
||||
allow(config).to receive(:get).with('beef.http.web_server_imitation.enable').and_return(true)
|
||||
allow(config).to receive(:get).with('beef.http.web_server_imitation.type').and_return('nginx')
|
||||
headers = router_instance.send(:response_headers)
|
||||
expect(headers['Server']).to eq('nginx')
|
||||
expect(headers['Content-Type']).to eq('text/html')
|
||||
end
|
||||
|
||||
it 'returns default headers for invalid type' do
|
||||
allow(config).to receive(:get).with('beef.http.web_server_imitation.enable').and_return(true)
|
||||
allow(config).to receive(:get).with('beef.http.web_server_imitation.type').and_return('invalid')
|
||||
headers = router_instance.send(:response_headers)
|
||||
expect(headers['Server']).to eq('')
|
||||
expect(headers['Content-Type']).to eq('text/html')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#index_page' do
|
||||
it 'returns empty string when web server imitation is disabled' do
|
||||
allow(config).to receive(:get).with('beef.http.web_server_imitation.enable').and_return(false)
|
||||
result = router_instance.send(:index_page)
|
||||
expect(result).to eq('')
|
||||
end
|
||||
|
||||
it 'returns Apache index page when enabled and type is apache' do
|
||||
allow(config).to receive(:get).with('beef.http.web_server_imitation.enable').and_return(true)
|
||||
allow(config).to receive(:get).with('beef.http.web_server_imitation.type').and_return('apache')
|
||||
allow(config).to receive(:get).with('beef.extension.admin_ui.base_path').and_return('/ui')
|
||||
allow(config).to receive(:get).with('beef.http.web_server_imitation.hook_root').and_return(false)
|
||||
result = router_instance.send(:index_page)
|
||||
expect(result).to include('Apache HTTP Server Test Page')
|
||||
expect(result).to include('powered by CentOS')
|
||||
end
|
||||
|
||||
it 'returns IIS index page when enabled and type is iis' do
|
||||
allow(config).to receive(:get).with('beef.http.web_server_imitation.enable').and_return(true)
|
||||
allow(config).to receive(:get).with('beef.http.web_server_imitation.type').and_return('iis')
|
||||
allow(config).to receive(:get).with('beef.extension.admin_ui.base_path').and_return('/ui')
|
||||
allow(config).to receive(:get).with('beef.http.web_server_imitation.hook_root').and_return(false)
|
||||
result = router_instance.send(:index_page)
|
||||
expect(result).to include('Under Construction')
|
||||
end
|
||||
|
||||
it 'returns nginx index page when enabled and type is nginx' do
|
||||
allow(config).to receive(:get).with('beef.http.web_server_imitation.enable').and_return(true)
|
||||
allow(config).to receive(:get).with('beef.http.web_server_imitation.type').and_return('nginx')
|
||||
allow(config).to receive(:get).with('beef.http.web_server_imitation.hook_root').and_return(false)
|
||||
# nginx doesn't use base_path, but the method might check it
|
||||
allow(config).to receive(:get).with('beef.extension.admin_ui.base_path').and_return('/ui')
|
||||
result = router_instance.send(:index_page)
|
||||
expect(result).to include('Welcome to nginx!')
|
||||
end
|
||||
|
||||
it 'includes hook script when hook_root is enabled' do
|
||||
allow(config).to receive(:get).with('beef.http.web_server_imitation.enable').and_return(true)
|
||||
allow(config).to receive(:get).with('beef.http.web_server_imitation.type').and_return('apache')
|
||||
allow(config).to receive(:get).with('beef.extension.admin_ui.base_path').and_return('/ui')
|
||||
allow(config).to receive(:get).with('beef.http.web_server_imitation.hook_root').and_return(true)
|
||||
allow(config).to receive(:get).with('beef.http.hook_file').and_return('/hook.js')
|
||||
result = router_instance.send(:index_page)
|
||||
expect(result).to include("<script src='/hook.js'></script>")
|
||||
end
|
||||
end
|
||||
|
||||
describe '#error_page_404' do
|
||||
it 'returns simple message when web server imitation is disabled' do
|
||||
allow(config).to receive(:get).with('beef.http.web_server_imitation.enable').and_return(false)
|
||||
result = router_instance.send(:error_page_404)
|
||||
expect(result).to eq('Not Found.')
|
||||
end
|
||||
|
||||
it 'returns Apache 404 page when enabled and type is apache' do
|
||||
allow(config).to receive(:get).with('beef.http.web_server_imitation.enable').and_return(true)
|
||||
allow(config).to receive(:get).with('beef.http.web_server_imitation.type').and_return('apache')
|
||||
allow(config).to receive(:get).with('beef.http.web_server_imitation.hook_404').and_return(false)
|
||||
result = router_instance.send(:error_page_404)
|
||||
expect(result).to include('404 Not Found')
|
||||
expect(result).to include('Apache/2.2.3 (CentOS)')
|
||||
end
|
||||
|
||||
it 'returns IIS 404 page when enabled and type is iis' do
|
||||
allow(config).to receive(:get).with('beef.http.web_server_imitation.enable').and_return(true)
|
||||
allow(config).to receive(:get).with('beef.http.web_server_imitation.type').and_return('iis')
|
||||
allow(config).to receive(:get).with('beef.http.web_server_imitation.hook_404').and_return(false)
|
||||
result = router_instance.send(:error_page_404)
|
||||
expect(result).to include('The page cannot be found')
|
||||
end
|
||||
|
||||
it 'returns nginx 404 page when enabled and type is nginx' do
|
||||
allow(config).to receive(:get).with('beef.http.web_server_imitation.enable').and_return(true)
|
||||
allow(config).to receive(:get).with('beef.http.web_server_imitation.type').and_return('nginx')
|
||||
allow(config).to receive(:get).with('beef.http.web_server_imitation.hook_404').and_return(false)
|
||||
result = router_instance.send(:error_page_404)
|
||||
expect(result).to include('404 Not Found')
|
||||
expect(result).to include('nginx')
|
||||
end
|
||||
|
||||
it 'includes hook script when hook_404 is enabled' do
|
||||
allow(config).to receive(:get).with('beef.http.web_server_imitation.enable').and_return(true)
|
||||
allow(config).to receive(:get).with('beef.http.web_server_imitation.type').and_return('apache')
|
||||
allow(config).to receive(:get).with('beef.http.web_server_imitation.hook_404').and_return(true)
|
||||
allow(config).to receive(:get).with('beef.http.hook_file').and_return('/hook.js')
|
||||
result = router_instance.send(:error_page_404)
|
||||
expect(result).to include("<script src='/hook.js'></script>")
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
113
spec/beef/core/main/server_spec.rb
Normal file
113
spec/beef/core/main/server_spec.rb
Normal file
@@ -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
|
||||
#
|
||||
|
||||
RSpec.describe BeEF::Core::Server do
|
||||
let(:config) { BeEF::Core::Configuration.instance }
|
||||
let(:server) { described_class.instance }
|
||||
|
||||
before do
|
||||
# Reset singleton instance for each test
|
||||
described_class.instance_variable_set(:@singleton__instance__, nil)
|
||||
end
|
||||
|
||||
describe '#initialize' do
|
||||
it 'initializes with configuration' do
|
||||
expect(server.configuration).to eq(config)
|
||||
end
|
||||
|
||||
it 'sets root_dir' do
|
||||
expect(server.root_dir).to be_a(String)
|
||||
expect(server.root_dir).to be_a(Pathname).or(be_a(String))
|
||||
end
|
||||
|
||||
it 'initializes empty mounts hash' do
|
||||
expect(server.mounts).to eq({})
|
||||
end
|
||||
|
||||
it 'initializes empty command_urls hash' do
|
||||
expect(server.command_urls).to eq({})
|
||||
end
|
||||
|
||||
it 'creates a semaphore' do
|
||||
expect(server.semaphore).to be_a(Mutex)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#to_h' do
|
||||
it 'returns a hash with server information' do
|
||||
result = server.to_h
|
||||
expect(result).to be_a(Hash)
|
||||
expect(result).to have_key('beef_url')
|
||||
expect(result).to have_key('beef_root_dir')
|
||||
expect(result).to have_key('beef_host')
|
||||
expect(result).to have_key('beef_port')
|
||||
end
|
||||
|
||||
it 'includes hook file path' do
|
||||
# The to_h method calls config.get, so we need to allow it
|
||||
allow(config).to receive(:get).and_call_original
|
||||
allow(config).to receive(:get).with('beef.http.hook_file').and_return('/hook.js')
|
||||
result = server.to_h
|
||||
expect(result['beef_hook']).to eq('/hook.js')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#mount' do
|
||||
it 'mounts a handler without arguments' do
|
||||
handler_class = Class.new
|
||||
server.mount('/test', handler_class)
|
||||
expect(server.mounts['/test']).to eq(handler_class)
|
||||
end
|
||||
|
||||
it 'mounts a handler with arguments' do
|
||||
handler_class = Class.new
|
||||
server.mount('/test', handler_class, 'arg1')
|
||||
expect(server.mounts['/test']).to eq([handler_class, 'arg1'])
|
||||
end
|
||||
|
||||
it 'raises TypeError for non-string URL' do
|
||||
handler_class = Class.new
|
||||
expect { server.mount(123, handler_class) }.to raise_error(TypeError, /"url" needs to be a string/)
|
||||
end
|
||||
|
||||
it 'overwrites existing mount' do
|
||||
handler1 = Class.new
|
||||
handler2 = Class.new
|
||||
server.mount('/test', handler1)
|
||||
server.mount('/test', handler2)
|
||||
expect(server.mounts['/test']).to eq(handler2)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#unmount' do
|
||||
it 'removes a mounted handler' do
|
||||
handler_class = Class.new
|
||||
server.mount('/test', handler_class)
|
||||
server.unmount('/test')
|
||||
expect(server.mounts).not_to have_key('/test')
|
||||
end
|
||||
|
||||
it 'raises TypeError for non-string URL' do
|
||||
expect { server.unmount(123) }.to raise_error(TypeError, /"url" needs to be a string/)
|
||||
end
|
||||
|
||||
it 'does nothing if URL is not mounted' do
|
||||
expect { server.unmount('/nonexistent') }.not_to raise_error
|
||||
expect(server.mounts).not_to have_key('/nonexistent')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#remap' do
|
||||
it 'calls remap on rack_app with mounts' do
|
||||
handler_class = Class.new
|
||||
server.mount('/test', handler_class)
|
||||
mock_rack_app = double('Rack::URLMap')
|
||||
server.instance_variable_set(:@rack_app, mock_rack_app)
|
||||
expect(mock_rack_app).to receive(:remap).with(server.mounts)
|
||||
server.remap
|
||||
end
|
||||
end
|
||||
end
|
||||
189
spec/beef/core/module_spec.rb
Normal file
189
spec/beef/core/module_spec.rb
Normal file
@@ -0,0 +1,189 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
|
||||
RSpec.describe BeEF::Module do
|
||||
let(:config) { BeEF::Core::Configuration.instance }
|
||||
|
||||
describe '.is_present' do
|
||||
it 'returns true when module exists in configuration' do
|
||||
allow(config).to receive(:get).with('beef.module').and_return({ 'test_module' => {} })
|
||||
expect(described_class.is_present('test_module')).to be true
|
||||
end
|
||||
|
||||
it 'returns false when module does not exist' do
|
||||
allow(config).to receive(:get).with('beef.module').and_return({})
|
||||
expect(described_class.is_present('nonexistent')).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe '.is_enabled' do
|
||||
it 'returns true when module is present and enabled' do
|
||||
allow(config).to receive(:get).with('beef.module').and_return({ 'test_module' => {} })
|
||||
allow(config).to receive(:get).with('beef.module.test_module.enable').and_return(true)
|
||||
expect(described_class.is_enabled('test_module')).to be true
|
||||
end
|
||||
|
||||
it 'returns false when module is not present' do
|
||||
allow(config).to receive(:get).with('beef.module').and_return({})
|
||||
expect(described_class.is_enabled('nonexistent')).to be false
|
||||
end
|
||||
|
||||
it 'returns false when module is disabled' do
|
||||
allow(config).to receive(:get).with('beef.module').and_return({ 'test_module' => {} })
|
||||
allow(config).to receive(:get).with('beef.module.test_module.enable').and_return(false)
|
||||
expect(described_class.is_enabled('test_module')).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe '.is_loaded' do
|
||||
it 'returns true when module is enabled and loaded' do
|
||||
allow(config).to receive(:get).with('beef.module').and_return({ 'test_module' => {} })
|
||||
allow(config).to receive(:get).with('beef.module.test_module.enable').and_return(true)
|
||||
allow(config).to receive(:get).with('beef.module.test_module.loaded').and_return(true)
|
||||
expect(described_class.is_loaded('test_module')).to be true
|
||||
end
|
||||
|
||||
it 'returns false when module is not loaded' do
|
||||
allow(config).to receive(:get).with('beef.module').and_return({ 'test_module' => {} })
|
||||
allow(config).to receive(:get).with('beef.module.test_module.enable').and_return(true)
|
||||
allow(config).to receive(:get).with('beef.module.test_module.loaded').and_return(false)
|
||||
expect(described_class.is_loaded('test_module')).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe '.get_key_by_database_id' do
|
||||
it 'returns module key for matching database id' do
|
||||
modules = {
|
||||
'module1' => { 'db' => { 'id' => 1 } },
|
||||
'module2' => { 'db' => { 'id' => 2 } }
|
||||
}
|
||||
allow(config).to receive(:get).with('beef.module').and_return(modules)
|
||||
expect(described_class.get_key_by_database_id(2)).to eq('module2')
|
||||
end
|
||||
|
||||
it 'returns nil when no module matches' do
|
||||
allow(config).to receive(:get).with('beef.module').and_return({})
|
||||
expect(described_class.get_key_by_database_id(999)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '.get_key_by_class' do
|
||||
it 'returns module key for matching class' do
|
||||
modules = {
|
||||
'module1' => { 'class' => 'TestClass1' },
|
||||
'module2' => { 'class' => 'TestClass2' }
|
||||
}
|
||||
allow(config).to receive(:get).with('beef.module').and_return(modules)
|
||||
expect(described_class.get_key_by_class('TestClass2')).to eq('module2')
|
||||
end
|
||||
end
|
||||
|
||||
describe '.exists?' do
|
||||
it 'returns true when class exists' do
|
||||
test_class = Class.new
|
||||
BeEF::Core::Command.const_set(:Testmodule, test_class)
|
||||
expect(described_class.exists?('testmodule')).to be true
|
||||
BeEF::Core::Command.send(:remove_const, :Testmodule)
|
||||
end
|
||||
|
||||
it 'returns false when class does not exist' do
|
||||
expect(described_class.exists?('NonexistentClass')).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe '.match_target_browser' do
|
||||
it 'returns browser constant for valid browser string' do
|
||||
result = described_class.match_target_browser('FF')
|
||||
expect(result).to eq(BeEF::Core::Constants::Browsers::FF)
|
||||
end
|
||||
|
||||
it 'returns false for invalid browser string' do
|
||||
expect(described_class.match_target_browser('InvalidBrowser')).to be false
|
||||
end
|
||||
|
||||
it 'returns false for non-string input' do
|
||||
expect(described_class.match_target_browser(123)).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe '.match_target_os' do
|
||||
it 'returns OS constant for valid OS string' do
|
||||
result = described_class.match_target_os('Linux')
|
||||
expect(result).to eq(BeEF::Core::Constants::Os::OS_LINUX_UA_STR)
|
||||
end
|
||||
|
||||
it 'returns false for invalid OS string' do
|
||||
expect(described_class.match_target_os('InvalidOS')).to be false
|
||||
end
|
||||
|
||||
it 'returns false for non-string input' do
|
||||
expect(described_class.match_target_os(123)).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe '.match_target_browser_spec' do
|
||||
it 'returns hash with max_ver and min_ver' do
|
||||
spec = { 'max_ver' => 10, 'min_ver' => 5 }
|
||||
result = described_class.match_target_browser_spec(spec)
|
||||
expect(result['max_ver']).to eq(10)
|
||||
expect(result['min_ver']).to eq(5)
|
||||
end
|
||||
|
||||
it 'handles latest as max_ver' do
|
||||
spec = { 'max_ver' => 'latest' }
|
||||
result = described_class.match_target_browser_spec(spec)
|
||||
expect(result['max_ver']).to eq('latest')
|
||||
end
|
||||
|
||||
it 'returns empty hash for non-hash input' do
|
||||
expect(described_class.match_target_browser_spec('invalid')).to eq({})
|
||||
end
|
||||
|
||||
it 'includes OS when specified' do
|
||||
spec = { 'max_ver' => 10, 'os' => 'Linux' }
|
||||
result = described_class.match_target_browser_spec(spec)
|
||||
expect(result['os']).to eq(BeEF::Core::Constants::Os::OS_LINUX_UA_STR)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.merge_options' do
|
||||
it 'returns nil when module is not present' do
|
||||
allow(config).to receive(:get).with('beef.module').and_return({})
|
||||
expect(described_class.merge_options('nonexistent', [])).to be_nil
|
||||
end
|
||||
|
||||
it 'merges default options with custom options' do
|
||||
allow(config).to receive(:get).with('beef.module').and_return({ 'test_module' => {} })
|
||||
allow(described_class).to receive(:is_present).and_return(true)
|
||||
allow(described_class).to receive(:check_hard_load).and_return(true)
|
||||
allow(described_class).to receive(:get_options).and_return(
|
||||
[
|
||||
{ 'name' => 'option1', 'value' => 'default1' },
|
||||
{ 'name' => 'option2', 'value' => 'default2' }
|
||||
]
|
||||
)
|
||||
custom_opts = [{ 'name' => 'option1', 'value' => 'custom1' }]
|
||||
result = described_class.merge_options('test_module', custom_opts)
|
||||
|
||||
expect(result.length).to eq(2)
|
||||
expect(result.find { |o| o['name'] == 'option1' }['value']).to eq('custom1')
|
||||
expect(result.find { |o| o['name'] == 'option2' }['value']).to eq('default2')
|
||||
end
|
||||
end
|
||||
|
||||
describe '.check_hard_load' do
|
||||
it 'returns true when module is already loaded' do
|
||||
allow(described_class).to receive(:is_loaded).and_return(true)
|
||||
expect(described_class.check_hard_load('test_module')).to be true
|
||||
end
|
||||
|
||||
it 'calls hard_load when module is not loaded' do
|
||||
allow(described_class).to receive(:is_loaded).and_return(false)
|
||||
expect(described_class).to receive(:hard_load).with('test_module')
|
||||
described_class.check_hard_load('test_module')
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,24 +1,57 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
|
||||
RSpec.describe 'BeEF Modules' do
|
||||
|
||||
it 'loaded successfully' do
|
||||
expect {
|
||||
BeEF::Modules.load if BeEF::Core::Configuration.instance.get('beef.module').nil?
|
||||
}.to_not raise_error
|
||||
config = BeEF::Core::Configuration.instance
|
||||
|
||||
modules = BeEF::Core::Configuration.instance.get('beef.module').select do |k,v|
|
||||
v['enable'] == true and v['category'] != nil
|
||||
# Force reload modules to ensure fresh state
|
||||
BeEF::Modules.load
|
||||
|
||||
# Verify modules were loaded
|
||||
all_modules = config.get('beef.module')
|
||||
expect(all_modules).not_to be_nil, 'Modules should be loaded'
|
||||
expect(all_modules).to be_a(Hash), 'Modules should be a hash'
|
||||
expect(all_modules.length).to be > 0, 'At least one module should be loaded'
|
||||
|
||||
# Find enabled modules with categories
|
||||
modules = all_modules.select do |_k, v|
|
||||
v['enable'] == true && !v['category'].nil?
|
||||
end
|
||||
expect(modules.length).to be > 0
|
||||
|
||||
modules.each do |k,v|
|
||||
# Provide helpful error message if no enabled modules found
|
||||
if modules.empty?
|
||||
enabled_count = all_modules.count { |_k, v| v['enable'] == true }
|
||||
with_category = all_modules.count { |_k, v| !v['category'].nil? }
|
||||
raise "No enabled modules with categories found. Total modules: #{all_modules.length}, " \
|
||||
"Enabled: #{enabled_count}, With category: #{with_category}"
|
||||
end
|
||||
|
||||
expect(modules.length).to be > 0, 'At least one enabled module with category should exist'
|
||||
|
||||
modules.each_key do |k|
|
||||
expect(BeEF::Module.is_present(k)).to be(true)
|
||||
expect(BeEF::Module.is_enabled(k)).to be(true)
|
||||
expect {
|
||||
BeEF::Module.hard_load(k)
|
||||
}.to_not raise_error
|
||||
expect(BeEF::Module.is_loaded(k)).to be(true)
|
||||
BeEF::Core::Configuration.instance.get("beef.module.#{k}.target").each do |k,v|
|
||||
expect(v).to_not be_empty
|
||||
|
||||
# Skip hard_load if module file doesn't exist (e.g., test modules)
|
||||
mod_path = config.get("beef.module.#{k}.path")
|
||||
mod_file = "#{$root_dir}/#{mod_path}/module.rb" # rubocop:disable Style/GlobalVars
|
||||
if File.exist?(mod_file)
|
||||
expect do
|
||||
BeEF::Module.hard_load(k)
|
||||
end.to_not raise_error
|
||||
expect(BeEF::Module.is_loaded(k)).to be(true)
|
||||
end
|
||||
|
||||
# Only check target if it exists
|
||||
target = config.get("beef.module.#{k}.target")
|
||||
next unless target.is_a?(Hash)
|
||||
|
||||
target.each_value do |target_value|
|
||||
expect(target_value).to_not be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -26,9 +59,10 @@ RSpec.describe 'BeEF Modules' do
|
||||
it 'safe client debug log' do
|
||||
Dir['../../modules/**/*.js'].each do |path|
|
||||
next unless File.file?(path)
|
||||
|
||||
File.open(path) do |f|
|
||||
f.grep(/\bconsole\.log\W*\(/m) do |line|
|
||||
fail "Function 'console.log' instead of 'beef.debug' inside\n Path: #{path}\nLine: #{line}"
|
||||
f.grep(/\bconsole\.log\W*\(/m) do |line| # rubocop:disable Lint/UnreachableLoop -- false positive
|
||||
raise "Function 'console.log' instead of 'beef.debug' inside\n Path: #{path}\nLine: #{line}"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -37,12 +71,12 @@ RSpec.describe 'BeEF Modules' do
|
||||
it 'safe variable decleration' do
|
||||
Dir['../../modules/**/*.js'].each do |path|
|
||||
next unless File.file?(path)
|
||||
|
||||
File.open(path) do |f|
|
||||
f.grep(/\blet\W+[a-zA-Z0-9_\.]+\W*=/) do |line|
|
||||
fail "Variable declared with 'let' instead of 'var' inside\n Path: #{path}\nLine: #{line}"
|
||||
f.grep(/\blet\W+[a-zA-Z0-9_.]+\W*=/) do |line| # rubocop:disable Lint/UnreachableLoop -- false positive
|
||||
raise "Variable declared with 'let' instead of 'var' inside\n Path: #{path}\nLine: #{line}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
99
spec/beef/core/ruby/hash_spec.rb
Normal file
99
spec/beef/core/ruby/hash_spec.rb
Normal file
@@ -0,0 +1,99 @@
|
||||
#
|
||||
# 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 'Hash#deep_merge' do
|
||||
it 'merges two simple hashes' do
|
||||
hash1 = { a: 1, b: 2 }
|
||||
hash2 = { c: 3, d: 4 }
|
||||
result = hash1.deep_merge(hash2)
|
||||
|
||||
expect(result).to eq({ a: 1, b: 2, c: 3, d: 4 })
|
||||
end
|
||||
|
||||
it 'overwrites duplicate keys with values from calling hash' do
|
||||
hash1 = { a: 1, b: 2 }
|
||||
hash2 = { b: 3, c: 4 }
|
||||
result = hash1.deep_merge(hash2)
|
||||
|
||||
expect(result[:a]).to eq(1)
|
||||
expect(result[:b]).to eq(3) # hash2 value overwrites
|
||||
expect(result[:c]).to eq(4)
|
||||
end
|
||||
|
||||
it 'recursively merges nested hashes' do
|
||||
hash1 = { a: { b: 1, c: 2 }, d: 3 }
|
||||
hash2 = { a: { c: 4, e: 5 }, f: 6 }
|
||||
result = hash1.deep_merge(hash2)
|
||||
|
||||
expect(result[:a][:b]).to eq(1)
|
||||
expect(result[:a][:c]).to eq(4) # hash2 value overwrites
|
||||
expect(result[:a][:e]).to eq(5)
|
||||
expect(result[:d]).to eq(3)
|
||||
expect(result[:f]).to eq(6)
|
||||
end
|
||||
|
||||
it 'handles deeply nested hashes' do
|
||||
hash1 = { a: { b: { c: 1 } } }
|
||||
hash2 = { a: { b: { d: 2 } } }
|
||||
result = hash1.deep_merge(hash2)
|
||||
|
||||
expect(result[:a][:b][:c]).to eq(1)
|
||||
expect(result[:a][:b][:d]).to eq(2)
|
||||
end
|
||||
|
||||
it 'does not modify the original hash' do
|
||||
hash1 = { a: 1 }
|
||||
hash2 = { b: 2 }
|
||||
original_hash1 = hash1.dup
|
||||
|
||||
hash1.deep_merge(hash2)
|
||||
|
||||
expect(hash1).to eq(original_hash1)
|
||||
end
|
||||
|
||||
it 'handles empty hashes' do
|
||||
hash1 = {}
|
||||
hash2 = { a: 1 }
|
||||
result = hash1.deep_merge(hash2)
|
||||
|
||||
expect(result).to eq({ a: 1 })
|
||||
end
|
||||
|
||||
it 'handles merging with empty hash' do
|
||||
hash1 = { a: 1 }
|
||||
hash2 = {}
|
||||
result = hash1.deep_merge(hash2)
|
||||
|
||||
expect(result).to eq({ a: 1 })
|
||||
end
|
||||
|
||||
it 'handles non-hash values in nested structure' do
|
||||
hash1 = { a: { b: 1 } }
|
||||
hash2 = { a: 2 } # a is not a hash in hash2
|
||||
result = hash1.deep_merge(hash2)
|
||||
|
||||
expect(result[:a]).to eq(2) # Should overwrite with non-hash value
|
||||
end
|
||||
|
||||
it 'handles nil values in source hash' do
|
||||
hash1 = { a: nil, b: 1 }
|
||||
hash2 = { a: 2, c: 3 }
|
||||
result = hash1.deep_merge(hash2)
|
||||
|
||||
expect(result[:a]).to eq(2) # Should overwrite nil
|
||||
expect(result[:b]).to eq(1)
|
||||
expect(result[:c]).to eq(3)
|
||||
end
|
||||
|
||||
it 'handles nil values when merging nested hashes' do
|
||||
hash1 = { a: nil }
|
||||
hash2 = { a: { b: 1 } }
|
||||
result = hash1.deep_merge(hash2)
|
||||
|
||||
expect(result[:a]).to eq({ b: 1 }) # Should overwrite nil with hash
|
||||
end
|
||||
end
|
||||
94
spec/beef/core/ruby/module_spec.rb
Normal file
94
spec/beef/core/ruby/module_spec.rb
Normal file
@@ -0,0 +1,94 @@
|
||||
#
|
||||
# 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 'Module extensions' do
|
||||
# Create a test module to use in tests
|
||||
let(:test_module) do
|
||||
Module.new do
|
||||
def test_method
|
||||
'test'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#included_in_classes' do
|
||||
it 'returns an array' do
|
||||
result = test_module.included_in_classes
|
||||
expect(result).to be_an(Array)
|
||||
end
|
||||
|
||||
it 'finds classes that include the module' do
|
||||
mod = test_module
|
||||
test_class = Class.new do
|
||||
include mod
|
||||
end
|
||||
|
||||
# Force class to be created
|
||||
test_class.new
|
||||
|
||||
included_classes = mod.included_in_classes
|
||||
expect(included_classes.map(&:to_s)).to include(test_class.to_s)
|
||||
end
|
||||
|
||||
it 'returns unique classes only' do
|
||||
mod = test_module
|
||||
test_class = Class.new do
|
||||
include mod
|
||||
end
|
||||
|
||||
# Force class to be created multiple times
|
||||
test_class.new
|
||||
test_class.new
|
||||
|
||||
included_classes = mod.included_in_classes
|
||||
unique_class_names = included_classes.map(&:to_s)
|
||||
expect(unique_class_names.count(test_class.to_s)).to eq(1)
|
||||
end
|
||||
|
||||
it 'returns empty array when module is not included anywhere' do
|
||||
isolated_module = Module.new
|
||||
result = isolated_module.included_in_classes
|
||||
expect(result).to be_an(Array)
|
||||
# May or may not be empty depending on what's loaded, but should be an array
|
||||
end
|
||||
end
|
||||
|
||||
describe '#included_in_modules' do
|
||||
it 'returns an array' do
|
||||
result = test_module.included_in_modules
|
||||
expect(result).to be_an(Array)
|
||||
end
|
||||
|
||||
it 'finds modules that include the module' do
|
||||
mod = test_module
|
||||
including_module = Module.new do
|
||||
include mod
|
||||
end
|
||||
|
||||
# Force module to be created
|
||||
Class.new { include including_module }
|
||||
|
||||
included_modules = mod.included_in_modules
|
||||
expect(included_modules.map(&:to_s)).to include(including_module.to_s)
|
||||
end
|
||||
|
||||
it 'returns unique modules only' do
|
||||
mod = test_module
|
||||
including_module = Module.new do
|
||||
include mod
|
||||
end
|
||||
|
||||
# Force module to be created multiple times
|
||||
Class.new { include including_module }
|
||||
Class.new { include including_module }
|
||||
|
||||
included_modules = mod.included_in_modules
|
||||
unique_module_names = included_modules.map(&:to_s)
|
||||
expect(unique_module_names.count(including_module.to_s)).to eq(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
191
spec/beef/core/ruby/print_spec.rb
Normal file
191
spec/beef/core/ruby/print_spec.rb
Normal file
@@ -0,0 +1,191 @@
|
||||
#
|
||||
# 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 'Print functions' do
|
||||
let(:logger) { BeEF.logger }
|
||||
let(:test_message) { 'test message' }
|
||||
|
||||
before(:each) do
|
||||
# Mock stdout to avoid cluttering test output
|
||||
allow($stdout).to receive(:puts)
|
||||
allow($stdout).to receive(:print)
|
||||
|
||||
# Mock logger methods
|
||||
allow(logger).to receive(:error)
|
||||
allow(logger).to receive(:info)
|
||||
allow(logger).to receive(:warn)
|
||||
allow(logger).to receive(:debug)
|
||||
end
|
||||
|
||||
describe '#print_error' do
|
||||
it 'calls logger.error with the message' do
|
||||
expect(logger).to receive(:error).with(test_message)
|
||||
print_error(test_message)
|
||||
end
|
||||
|
||||
it 'outputs to stdout with timestamp and error prefix' do
|
||||
expect($stdout).to receive(:puts).with(match(/\[!\] #{test_message}/))
|
||||
print_error(test_message)
|
||||
end
|
||||
|
||||
it 'converts non-string arguments to string' do
|
||||
expect(logger).to receive(:error).with('123')
|
||||
print_error(123)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#print_info' do
|
||||
it 'calls logger.info with the message' do
|
||||
expect(logger).to receive(:info).with(test_message)
|
||||
print_info(test_message)
|
||||
end
|
||||
|
||||
it 'outputs to stdout with timestamp and info prefix' do
|
||||
expect($stdout).to receive(:puts).with(match(/\[\*\] #{test_message}/))
|
||||
print_info(test_message)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#print_status' do
|
||||
it 'calls print_info' do
|
||||
expect(logger).to receive(:info).with(test_message)
|
||||
print_status(test_message)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#print_warning' do
|
||||
it 'calls logger.warn with the message' do
|
||||
expect(logger).to receive(:warn).with(test_message)
|
||||
print_warning(test_message)
|
||||
end
|
||||
|
||||
it 'outputs to stdout with timestamp and warning prefix' do
|
||||
expect($stdout).to receive(:puts).with(match(/\[!\] #{test_message}/))
|
||||
print_warning(test_message)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#print_debug' do
|
||||
let(:config) { BeEF::Core::Configuration.instance }
|
||||
|
||||
context 'when debug is enabled' do
|
||||
before do
|
||||
allow(config).to receive(:get).with('beef.debug').and_return(true)
|
||||
allow(BeEF::Core::Console::CommandLine).to receive(:parse).and_return({})
|
||||
end
|
||||
|
||||
it 'calls logger.debug with the message' do
|
||||
expect(logger).to receive(:debug).with(test_message)
|
||||
print_debug(test_message)
|
||||
end
|
||||
|
||||
it 'outputs to stdout with timestamp and debug prefix' do
|
||||
expect($stdout).to receive(:puts).with(match(/\[>\] #{test_message}/))
|
||||
print_debug(test_message)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when verbose flag is set' do
|
||||
before do
|
||||
allow(config).to receive(:get).with('beef.debug').and_return(false)
|
||||
allow(BeEF::Core::Console::CommandLine).to receive(:parse).and_return({ verbose: true })
|
||||
end
|
||||
|
||||
it 'calls logger.debug with the message' do
|
||||
expect(logger).to receive(:debug).with(test_message)
|
||||
print_debug(test_message)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when debug is disabled and verbose is not set' do
|
||||
before do
|
||||
allow(config).to receive(:get).with('beef.debug').and_return(false)
|
||||
allow(BeEF::Core::Console::CommandLine).to receive(:parse).and_return({})
|
||||
end
|
||||
|
||||
it 'does not call logger.debug' do
|
||||
expect(logger).not_to receive(:debug)
|
||||
print_debug(test_message)
|
||||
end
|
||||
|
||||
it 'does not output to stdout' do
|
||||
expect($stdout).not_to receive(:puts)
|
||||
print_debug(test_message)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#print_success' do
|
||||
it 'calls logger.info with the message' do
|
||||
expect(logger).to receive(:info).with(test_message)
|
||||
print_success(test_message)
|
||||
end
|
||||
|
||||
it 'outputs to stdout with timestamp and success prefix' do
|
||||
expect($stdout).to receive(:puts).with(match(/\[\+\] #{test_message}/))
|
||||
print_success(test_message)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#print_good' do
|
||||
it 'calls print_success' do
|
||||
expect(logger).to receive(:info).with(test_message)
|
||||
print_good(test_message)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#print_more' do
|
||||
context 'with string input' do
|
||||
it 'splits string by newlines and formats each line' do
|
||||
multi_line = "line1\nline2\nline3"
|
||||
expect($stdout).to receive(:puts).with(match(/line1/))
|
||||
expect($stdout).to receive(:puts).with(match(/line2/))
|
||||
expect($stdout).to receive(:puts).with(match(/\|_ line3/)) # Last line has "|_"
|
||||
expect(logger).to receive(:info).exactly(3).times
|
||||
print_more(multi_line)
|
||||
end
|
||||
|
||||
it 'formats last line with |_ prefix' do
|
||||
single_line = 'single line'
|
||||
expect($stdout).to receive(:puts).with(match(/\|_ single line/))
|
||||
expect(logger).to receive(:info).with(match(/\|_ single line/))
|
||||
print_more(single_line)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with array input' do
|
||||
it 'formats each array element as a line' do
|
||||
lines_array = %w[line1 line2 line3]
|
||||
expect($stdout).to receive(:puts).exactly(3).times
|
||||
expect(logger).to receive(:info).exactly(3).times
|
||||
print_more(lines_array)
|
||||
end
|
||||
|
||||
it 'formats last array element with |_ prefix' do
|
||||
lines_array = %w[line1 line2]
|
||||
expect($stdout).to receive(:puts).with(match(/\| line1/))
|
||||
expect($stdout).to receive(:puts).with(match(/\|_ line2/))
|
||||
print_more(lines_array)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#print_over' do
|
||||
it 'calls logger.info with the message' do
|
||||
expect(logger).to receive(:info).with(test_message)
|
||||
print_over(test_message)
|
||||
end
|
||||
|
||||
it 'outputs formatted message to stdout' do
|
||||
# print is a private Kernel method, hard to stub directly
|
||||
# We verify the function executes and calls logger
|
||||
# The actual output includes ANSI color codes and carriage return
|
||||
expect { print_over(test_message) }.not_to raise_error
|
||||
expect(logger).to have_received(:info).with(test_message)
|
||||
end
|
||||
end
|
||||
end
|
||||
28
spec/beef/core/ruby/security_spec.rb
Normal file
28
spec/beef/core/ruby/security_spec.rb
Normal file
@@ -0,0 +1,28 @@
|
||||
#
|
||||
# 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 'Security method overrides' do
|
||||
it 'overrides exec method' do
|
||||
# The exec method should be overridden to prevent usage
|
||||
# We can't easily test the exit behavior without forking
|
||||
# so we just check that the method is overridden
|
||||
expect(method(:exec).source_location).not_to be_nil
|
||||
expect(method(:exec).source_location[0]).to include('core/ruby/security.rb')
|
||||
end
|
||||
|
||||
it 'overrides system method' do
|
||||
# The system method should be overridden
|
||||
expect(method(:system).source_location).not_to be_nil
|
||||
expect(method(:system).source_location[0]).to include('core/ruby/security.rb')
|
||||
end
|
||||
|
||||
it 'overrides Kernel.system method' do
|
||||
# Kernel.system should be overridden
|
||||
expect(Kernel.method(:system).source_location).not_to be_nil
|
||||
expect(Kernel.method(:system).source_location[0]).to include('core/ruby/security.rb')
|
||||
end
|
||||
end
|
||||
27
spec/beef/core/ruby/string_spec.rb
Normal file
27
spec/beef/core/ruby/string_spec.rb
Normal file
@@ -0,0 +1,27 @@
|
||||
#
|
||||
# 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 'String colorization' do
|
||||
it 'includes Term::ANSIColor module' do
|
||||
expect(String.included_modules).to include(Term::ANSIColor)
|
||||
end
|
||||
|
||||
it 'can use color methods on strings' do
|
||||
string = 'test'
|
||||
expect(string.respond_to?(:red)).to be(true)
|
||||
expect(string.respond_to?(:green)).to be(true)
|
||||
expect(string.respond_to?(:blue)).to be(true)
|
||||
end
|
||||
|
||||
it 'applies color methods correctly' do
|
||||
string = 'hello'
|
||||
colored = string.red
|
||||
|
||||
expect(colored).to be_a(String)
|
||||
expect(colored).not_to eq(string) # should now be: "\e[31mhello\e[0m" (red colored hello)
|
||||
end
|
||||
end
|
||||
32
spec/beef/core/settings_spec.rb
Normal file
32
spec/beef/core/settings_spec.rb
Normal file
@@ -0,0 +1,32 @@
|
||||
#
|
||||
# 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::Settings' do
|
||||
describe '.extension_exists?' do
|
||||
it 'returns true for existing extensions and false for non-existing ones' do
|
||||
# Test with a known extension if available
|
||||
expect(BeEF::Settings.extension_exists?('AdminUI')).to be(true) if BeEF::Extension.const_defined?('AdminUI')
|
||||
|
||||
expect(BeEF::Settings.extension_exists?('NonExistentExtension')).to be(false)
|
||||
end
|
||||
|
||||
it 'raises errors for invalid inputs' do
|
||||
expect { BeEF::Settings.extension_exists?(nil) }.to raise_error(TypeError)
|
||||
expect { BeEF::Settings.extension_exists?('') }.to raise_error(NameError)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.console?' do
|
||||
it 'delegates to extension_exists? with Console' do
|
||||
allow(BeEF::Settings).to receive(:extension_exists?).with('Console').and_return(true)
|
||||
expect(BeEF::Settings.console?).to be(true)
|
||||
|
||||
allow(BeEF::Settings).to receive(:extension_exists?).with('Console').and_return(false)
|
||||
expect(BeEF::Settings.console?).to be(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,3 +1,9 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
|
||||
RSpec.describe 'BeEF Filesystem' do
|
||||
def file_test(file)
|
||||
expect(File.file?(file)).to be(true)
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
#
|
||||
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
|
||||
# Browser Exploitation Framework (BeEF) - https://beefproject.com
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
|
||||
RSpec.describe 'BeEF Security Checks' do
|
||||
it 'dangerous eval usage' do
|
||||
Dir['**/*.rb'].each do |path|
|
||||
|
||||
@@ -3,6 +3,16 @@
|
||||
# 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'
|
||||
end
|
||||
|
||||
# Set external and internal character encodings to UTF-8
|
||||
Encoding.default_external = Encoding::UTF_8
|
||||
Encoding.default_internal = Encoding::UTF_8
|
||||
@@ -229,8 +239,7 @@ require 'socket'
|
||||
@host = '127.0.0.1'
|
||||
|
||||
unless port_available?
|
||||
print_error "Port #{@port} is already in use. Exiting."
|
||||
exit
|
||||
raise "Port #{@port} is already in use. Cannot start BeEF server."
|
||||
end
|
||||
load_beef_extensions_and_modules
|
||||
|
||||
@@ -317,11 +326,9 @@ require 'socket'
|
||||
end
|
||||
|
||||
def stop_beef_server(pid)
|
||||
exit if pid.nil?
|
||||
# Shutting down server
|
||||
return if pid.nil?
|
||||
Process.kill("KILL", pid) unless pid.nil?
|
||||
Process.wait(pid) unless pid.nil? # Ensure the process has exited and the port is released
|
||||
pid = nil
|
||||
end
|
||||
|
||||
end
|
||||
@@ -380,4 +387,4 @@ module SpecActiveRecordConnection
|
||||
ActiveRecord::Migrator.new(:up, context.migrations, context.schema_migration, context.internal_metadata).migrate
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,29 +10,36 @@ require 'spec/support/constants.rb'
|
||||
def start_beef_and_hook_browser()
|
||||
reset_beef_db
|
||||
pid = start_beef_server_and_wait
|
||||
beef_session = BeefTest.login
|
||||
hooked_browser = BeefTest.new_victim
|
||||
|
||||
expect(hooked_browser).not_to be_nil
|
||||
expect(hooked_browser).to be_a(Capybara::Session)
|
||||
expect(hooked_browser).to have_content('BeEF', wait: PAGE_LOAD_TIMEOUT)
|
||||
begin
|
||||
beef_session = BeefTest.login
|
||||
hooked_browser = BeefTest.new_victim
|
||||
|
||||
expect(beef_session).not_to be_nil
|
||||
expect(beef_session).to be_a(Capybara::Session)
|
||||
expect(beef_session).to have_content('Hooked Browsers', wait: PAGE_LOAD_TIMEOUT)
|
||||
expect(hooked_browser).not_to be_nil
|
||||
expect(hooked_browser).to be_a(Capybara::Session)
|
||||
expect(hooked_browser).to have_content('BeEF', wait: PAGE_LOAD_TIMEOUT)
|
||||
|
||||
navigate_to_hooked_browser(beef_session)
|
||||
expect(beef_session).not_to be_nil
|
||||
expect(beef_session).to be_a(Capybara::Session)
|
||||
expect(beef_session).to have_content('Hooked Browsers', wait: PAGE_LOAD_TIMEOUT)
|
||||
|
||||
expect(beef_session).to have_content('Commands', wait: PAGE_LOAD_TIMEOUT)
|
||||
beef_session.click_on('Commands')
|
||||
navigate_to_hooked_browser(beef_session)
|
||||
|
||||
return pid, beef_session, hooked_browser
|
||||
expect(beef_session).to have_content('Commands', wait: PAGE_LOAD_TIMEOUT)
|
||||
beef_session.click_on('Commands')
|
||||
|
||||
return pid, beef_session, hooked_browser
|
||||
rescue => e
|
||||
# If setup fails, cleanup the server before re-raising
|
||||
stop_beef_server(pid)
|
||||
raise e
|
||||
end
|
||||
end
|
||||
|
||||
def stop_beef_and_unhook_browser(pid, beef_session, hooked_browser)
|
||||
stop_beef_server(pid)
|
||||
beef_session.driver.browser.close
|
||||
hooked_browser.driver.browser.close
|
||||
beef_session.driver.browser.close if beef_session
|
||||
hooked_browser.driver.browser.close if hooked_browser
|
||||
end
|
||||
|
||||
def navigate_to_hooked_browser(session, hooked_browser_text = nil)
|
||||
|
||||
@@ -1,290 +0,0 @@
|
||||
# BeEF Manual Testing Plan (Local VM Edition)
|
||||
|
||||
This document provides a simplified approach for manually testing BeEF modules entirely within the same Linux Ubuntu VM where BeEF is running.
|
||||
|
||||
## 1. Environment Setup (Local VM)
|
||||
|
||||
### 1.1 BeEF Server
|
||||
1. **Dependencies**: Already installed via `./install`.
|
||||
2. **Configuration**: Credentials have been updated in `config.yaml`.
|
||||
3. **Launch**: Run `./beef` from the repository root.
|
||||
4. **Access**: Open the local browser (e.g., Firefox) and navigate to the BeEF UI: `http://127.0.0.1:3000/ui/panel`.
|
||||
|
||||
### 1.2 Hooked Browsers (Local)
|
||||
For local testing on the same machine:
|
||||
1. Open a new tab or window in your browser (Firefox, Chromium, etc.).
|
||||
2. Navigate to the hook demo page: `http://127.0.0.1:3000/demos/butcher/index.html`.
|
||||
3. The browser will appear in the BeEF "Online Browsers" list as `127.0.0.1`.
|
||||
|
||||
## 2. Testing Strategy: Grouped Execution
|
||||
|
||||
1. **Phase 1: Common Infrastructure (Firefox)**: Start here. These modules work on the standard Linux/Firefox setup provided by the VM and don't require external devices or specific insecure software.
|
||||
2. **Phase 2: Specific Requirements (Firefox)**: Test these if you have the specific requirements (e.g., Android device, Flash plugin, specific vulnerable server running).
|
||||
3. **Phase 3: Other Browsers**: Use Chrome/Edge/Safari for modules that explicitly don't work in Firefox.
|
||||
|
||||
## 3. Module Inventory and Instructions
|
||||
|
||||
### 3.1 Phase 1: Common Infrastructure (Standard Firefox)
|
||||
|
||||
Test these modules using **Firefox** on your local Linux VM. They leverage standard browser features or the BeEF infrastructure itself.
|
||||
|
||||
| Status | Module Name | Instructions / Description | Cleanup Needed | Comments |
|
||||
| :---: | :--- | :--- | :--- | :--- |
|
||||
| [x] | **Alert Dialog** | 1. Set `Title`, `Message`, and `Button name`.<br>2. Execute module.<br>3. Verify alert dialog appears on hooked page with configured text. | None. | |
|
||||
| [x] | **BlockUI Modal Dialog** | 1. Set `Message` and `Timeout (s)`.<br>2. Execute module.<br>3. Verify blocking overlay appears with message.<br>4. Wait for timeout or use UnBlockUI to remove. | None. | |
|
||||
| [x] | **Clickjacking** | 1. Set `iFrame Src` to target page for clickjacking.<br>2. Execute module.<br>3. Verify page shows overlay for click-based attack.<br>4. Click as user would to test interaction capture. | None. | |
|
||||
| [x] | **Confirm Close Tab** | 1. Configure: `Confirm text`, `Create a pop-under window on user\`<br>2. Click Execute.<br><br>_Shows a confirm dialog to the user when they try to close a tab._ | Close tab/window. Check for residual pop-unders. | a window pops up, but the text not as per command |
|
||||
| [x] | **Create Foreground iFrame** | 1. Execute module.<br>2. Click "Our Meaty Friends" button to reveal links.<br>3. Click any link (e.g., "The Browser Exploitation Framework Project homepage").<br>4. Verify page loads in fullscreen iFrame overlay (check DevTools for `<iframe>` with `z-index:1` and 100% width/height).<br>5. Confirm hook remains active in BeEF UI. | Close tab/window. | |
|
||||
| [x] | **Create Invisible Iframe** | 1. Set `URL` to any valid URL (e.g., `http://example.com`).<br>2. Execute module.<br>3. Open DevTools → Elements → search for `<iframe>` with `visibility:hidden` or `display:none`.<br>4. Verify iframe exists with correct src. | None. | |
|
||||
| [x] | **Create Pop Under** | 1. Set `Clickjack` to `on` (waits for click) or `off` (immediate).<br>2. Execute module.<br>3. If Clickjack=on, click anywhere on page.<br>4. Check for small hidden window in taskbar (or DevTools: new window to `/demos/plain.html`).<br>5. Verify BeEF shows 2nd hooked browser. | Close pop-under window. | |
|
||||
| [-] | **Cross-Origin Scanner (CORS)** | 1. Set `Scan IP range` (e.g., `127.0.0.1-127.0.0.1`) and `Ports` (e.g., `80,443,8080`).<br>2. Execute module.<br>3. Check command results for list of discovered web servers allowing CORS. | None. | See [CORS-001](testing_errors.md#cors-001-cross-origin-scanner-cors-module-error) |
|
||||
| [x] | **DNS Enumeration** | 1. Configure: `DNS (comma separated)`, `Timeout (ms)`<br>2. Click Execute.<br><br>_Discover DNS hostnames within the victim's network using dictionary and timing attacks._ | None. | |
|
||||
| [x] | **DOSer** | 1. Set `URL` to `http://127.0.0.1:3000/demos/plain.html`.<br>2. Set `Delay between requests (ms)` to `100`.<br>3. Set `HTTP Method` to `GET`.<br>4. Execute module.<br>5. Wait for status report in results (appears every 10s: "Requests sent: X").<br>6. Verify ongoing requests in browser DevTools → Network tab. | Refresh hooked page to stop worker. | |
|
||||
| [-] | **Detect Extensions** | 1. Execute module.<br>2. Check command results for list of detected Chrome/Firefox extensions. | None. | See [EXT-001](testing_errors.md#ext-001-detect-extensions-module-failure) |
|
||||
| [x] | **Detect MIME Types** | 1. Click Execute.<br><br>_This module retrieves the browser's supported MIME types._ | None. | |
|
||||
| [x] | **Detect Popup Blocker** | 1. Execute module.<br>2. Check command result: "Popup blocker enabled" or "Popup blocker not detected". | None. | |
|
||||
| [x] | **Fetch Port Scanner** | 1. Set `Scan IP or Hostname` (e.g., `127.0.0.1`) and `Specific port(s)` (e.g., `22,80,443,3000`).<br>2. Execute module.<br>3. Check command results for open/closed port status. | None. | |
|
||||
| [-] | **Fingerprint Browser (PoC)** | 1. Execute module.<br>2. Check command results for browser name, version, and platform. | None. | See [FP-001](testing_errors.md#fp-001-fingerprint-browser-poc-module-failure) |
|
||||
| [x] | **Fingerprint Browser** | 1. Execute module.<br>2. Check command results for detailed fingerprint (canvas, WebGL, fonts, plugins, etc.). | None. | |
|
||||
| [-] | **Fingerprint Local Network** | 1. Run `hostname -I` to find your IP (e.g., `192.168.1.5`).<br>2. Set `Scan IP range` to `common` (or specific IP).<br>3. Open Browser DevTools -> Network tab.<br>4. Execute module.<br>5. **Verify**: You will see many requests in DevTools (red/failed is normal).<br>6. **Duration**: `common` scan takes ~10-30s. Full /24 scan takes minutes.<br>7. Check BeEF results for any detected devices. | Refresh page to stop early. | See [NET-001](testing_errors.md#net-001-fingerprint-local-network-no-feedback) |
|
||||
| [-] | **Fingerprint Routers** | 1. Click Execute.<br><br>_This module attempts to discover network routers on the local network._ | None. | See [NET-002](testing_errors.md#net-002-fingerprint-routers-module-error) |
|
||||
| [x] | **Get Geolocation (API)** | 1. Execute module.<br>2. Allow/deny location permission in browser popup.<br>3. If allowed, check results for latitude/longitude coordinates. | None. | |
|
||||
| [ ] | **Get HTTP Servers (Favicon)** | 1. Configure: `Remote IP(s)`, `Ports`, `Workers`...<br>2. Click Execute.<br><br>_Attempts to discover HTTP servers on the specified IP range by checking for a favicon._ | None. | |
|
||||
| [ ] | **Get Internal IP WebRTC** | 1. Execute module.<br>2. Check command results for local/private IP address (e.g., `192.168.x.x`). | None. | |
|
||||
| [ ] | **Get Protocol Handlers** | 1. Configure: `Link Protocol(s)`, `Link Address`<br>2. Click Execute.<br><br>_This module attempts to identify protocol handlers present on the hooked browser._ | None. | |
|
||||
| [ ] | **Get Visited Domains** | 1. Configure: `Specify custom page to check`<br>2. Click Execute.<br><br>_This module will retrieve rapid history extraction through non-destructive cache timing._ | None. | |
|
||||
| [ ] | **Hijack Opener Window** | 1. First open demo page via a link from another page (so `window.opener` exists).<br>2. Execute module on the opened tab.<br>3. Check if opener window's location changed to BeEF `/iframe#` URL.<br>4. Verify result in command output. | Close affected windows. | |
|
||||
| [ ] | **Identify LAN Subnets** | 1. Configure: `Timeout for each request (ms)`<br>2. Click Execute.<br><br>_Discover active hosts in the internal network(s) of the hooked browser._ | None. | |
|
||||
| [ ] | **Lcamtuf Download** | 1. Configure: `Real File Path`, `Malicious File Path`, `Run Once`<br>2. Click Execute.<br><br>_This module will attempt to execute a lcamtuf download._ | Delete downloaded files. | |
|
||||
| [ ] | **Link Rewrite** | 1. Execute module.<br>2. Click "Our Meaty Friends" button to reveal links.<br>3. Hover over any link and check DevTools or status bar.<br>4. Verify all `href` attributes have been modified. | Refresh page to restore links. | |
|
||||
| [ ] | **Man-In-The-Browser** | 1. Execute module.<br>2. Click any link on page to navigate.<br>3. Verify page loads via AJAX (URL bar may not change, or content loads dynamically).<br>4. Confirm BeEF hook remains active.<br>5. Check command result shows "Browser hooked". | Close tab. | |
|
||||
| [ ] | **No Sleep** | 1. Click Execute.<br><br>_This module uses NoSleep.js to prevent display sleep and enable wake lock in any Android or iOS web browser._ | None. | |
|
||||
| [ ] | **Ping Sweep (FF)** | 1. Configure: `Scan IP range (C class or IP)`, `Timeout (ms)`, `Delay between requests (ms)`<br>2. Click Execute.<br><br>_Discover active hosts in the internal network of the hooked browser._ | None. | |
|
||||
| [ ] | **Ping Sweep (JS XHR)** | 1. Configure: `Scan IP range (C class)`, `Workers`<br>2. Click Execute.<br><br>_Discover active hosts in the internal network of the hooked browser using JavaScript XHR._ | None. | |
|
||||
| [ ] | **Play Sound** | 1. Set `Sound File Path` to a valid audio URL (e.g., `/demos/alert.mp3` or external URL).<br>2. Execute module.<br>3. Listen for audio playback on hooked browser. | None. | |
|
||||
| [ ] | **Port Scanner (Multiple Methods)** | 1. Set `Scan IP or Hostname` (e.g., `127.0.0.1`) and `Specific port(s)` (e.g., `22,80,443,3000`).<br>2. Execute module.<br>3. Check results for open ports (tries WebSockets, CORS, img tags). | None. | |
|
||||
| [ ] | **Pretty Theft** | 1. Set `Dialog Type` (e.g., `Facebook`, `LinkedIn`, `Windows`, `Generic`).<br>2. Set `Backing` (e.g., `Grey`, `Clear`).<br>3. Execute module.<br>4. Verify fake login dialog appears on hooked page.<br>5. Enter test credentials and submit.<br>6. Check BeEF command results for captured credentials. | None. | |
|
||||
| [ ] | **Raw JavaScript** | 1. Set `Javascript Code` (e.g., `alert('test')` or `console.log(document.cookie)`).<br>2. Execute module.<br>3. Verify JS executed (alert shown, or check DevTools console). | None. | |
|
||||
| [ ] | **Redirect Browser (Rickroll)** | 1. Execute module.<br>2. Verify page is replaced with fullscreen Rickroll video.<br>3. Confirm hook remains active in BeEF UI. | Refresh page to restore. | |
|
||||
| [ ] | **Redirect Browser (Standard)** | 1. Set `Redirect URL` (e.g., `https://example.com`).<br>2. Execute module.<br>3. Verify browser navigates to specified URL (hook will be lost). | Re-hook if needed. | |
|
||||
| [ ] | **Redirect Browser (iFrame)** | 1. Set `Redirect URL`, optional `Title` and `Favicon`.<br>2. Execute module.<br>3. Verify page shows iFrame overlay with target URL.<br>4. Confirm hook remains active. | Close tab. | |
|
||||
| [ ] | **Replace Videos (Fake Plugin)** | 1. Configure: `Payload URL`, `jQuery Selector`<br>2. Click Execute.<br><br>_Replaces an object selected with jQuery with an image advising the user to install a missing plugin._ | None. | |
|
||||
| [ ] | **Resource Exhaustion DoS** | 1. Execute module.<br>2. Observe browser becoming slow/unresponsive.<br>3. May need to force-close browser tab/window. | Force-close tab if needed. | |
|
||||
| [ ] | **Return Ascii Chars** | 1. Execute module.<br>2. Check command results for ASCII character set. | None. | |
|
||||
| [ ] | **Return Image** | 1. Execute module.<br>2. Check command results for base64-encoded PNG image data. | None. | |
|
||||
| [ ] | **Simple Hijacker** | 1. Configure: `Targetted domains`, `Template to use`<br>2. Click Execute.<br><br>_Hijack clicks on links to display what you want._ | None. | |
|
||||
| [ ] | **Spoof Address Bar (data URL)** | 1. Configure: `Spoofed URL`, `Real URL`<br>2. Click Execute.<br><br>_This module redirects the browser to a legitimate looking URL with a data scheme._ | None. | |
|
||||
| [ ] | **Spyder Eye** | 1. Set `Repeat` (number of screenshots) and `Delay` (ms between shots).<br>2. Execute module.<br>3. Check command results for base64-encoded screenshot(s) of the victim's viewport. | None. | |
|
||||
| [ ] | **TabNabbing** | 1. Set `URL` (e.g. fake login page) and `Wait` time (e.g., 1 minute).<br>2. Execute module.<br>3. Switch to a different tab and wait the configured time.<br>4. Switch back and verify the hooked tab has navigated to specified URL. | Close tab. | |
|
||||
| [ ] | **Test CORS Request** | 1. Configure: `Method`, `URL`, `Data`<br>2. Click Execute.<br><br>_Test the beef.net.cors.request function._ | None. | |
|
||||
| [ ] | **Test HTTP Redirect** | 1. Click Execute.<br><br>_Test the HTTP 'redirect' handler._ | None. | |
|
||||
| [ ] | **Test JS variable passing** | 1. Configure: `Payload Name`<br>2. Click Execute.<br><br>_Test for JS variable passing._ | None. | |
|
||||
| [ ] | **Test Network Request** | 1. Configure: `Scheme`, `Method`, `Domain`...<br>2. Click Execute.<br><br>_Test the beef.net.request function by retrieving a URL._ | None. | |
|
||||
| [ ] | **Test Returning Results** | 1. Configure: `Times to repeat`, `String to repeat`<br>2. Click Execute.<br><br>_This module will return a string of the specified length._ | None. | |
|
||||
| [ ] | **Test beef.debug()** | 1. Configure: `Debug Message`<br>2. Click Execute.<br><br>_Test the 'beef.debug()' function._ | None. | |
|
||||
| [ ] | **Text to Voice** | 1. Set `Text` (e.g., "Hello world") and `Language` (e.g., `en`).<br>2. Execute module.<br>3. Listen for audio playback of the text. | None. | |
|
||||
| [ ] | **UnBlockUI** | 1. First execute "BlockUI Modal Dialog" module to create a blocking overlay.<br>2. Then execute this "UnBlockUI" module.<br>3. Verify the BlockUI overlay is removed. | None. | |
|
||||
| [ ] | **Unhook** | 1. Execute module.<br>2. Verify hook JavaScript is removed from page (check DevTools console).<br>3. Confirm browser goes "Offline" in BeEF UI.<br>4. Confirm no further commands can be executed. | Re-hook page if needed. | |
|
||||
| [ ] | **iFrame Event Key Logger** | 1. Set `iFrame Src` (target URL to load in overlay).<br>2. Set `Send Back Interval` (e.g., 5000ms).<br>3. Execute module.<br>4. Type in the iFrame overlay.<br>5. Check BeEF command results for captured keystrokes. | Close tab. | |
|
||||
|
||||
|
||||
### 3.2 Phase 2: Specific Requirements (Firefox)
|
||||
|
||||
These modules require specific devices, plugins, vulnerable software, or valid credentials to work.
|
||||
|
||||
#### 3.2.1 Mobile & PhoneGap
|
||||
Requires an Android/iOS device or PhoneGap environment.
|
||||
|
||||
| Status | Module Name | Instructions / Description | Cleanup Needed | Comments |
|
||||
| :---: | :--- | :--- | :--- | :--- |
|
||||
| [ ] | **Alert User** | 1. Click Execute.<br><br>_Show user an alert. This module requires the PhoneGap API._ | None. | |
|
||||
| [ ] | **Beep** | 1. Click Execute.<br><br>_Make the phone beep. This module requires the PhoneGap API._ | None. | |
|
||||
| [ ] | **Check Connection** | 1. Click Execute.<br><br>_Find out the network connection type e.g. Wifi, 3G. This module requires the PhoneGap API._ | None. | |
|
||||
| [ ] | **Detect PhoneGap** | 1. Click Execute.<br><br>_Detects if the PhoneGap API is present._ | None. | |
|
||||
| [ ] | **Geolocation** | 1. Click Execute.<br><br>_Geo locate your victim. This module requires the PhoneGap API._ | None. | |
|
||||
| [ ] | **Get Network Connection Type** | 1. Click Execute.<br><br>_Retrieve the network connection type (wifi, 3G, etc). Note: Android only._ | None. | |
|
||||
| [ ] | **Globalization Status** | 1. Click Execute.<br><br>_Examine device local settings. This module requires the PhoneGap API._ | None. | |
|
||||
| [ ] | **Keychain** | 1. Configure: `Service name`, `Key`, `Value`...<br>2. Click Execute.<br><br>_Read/CreateUpdate/Delete Keychain Elements. This module requires the PhoneGap API._ | None. | |
|
||||
| [ ] | **List Contacts** | 1. Click Execute.<br><br>_Examine device contacts. This module requires the PhoneGap API._ | None. | |
|
||||
| [ ] | **List Files** | 1. Configure: `Directory`<br>2. Click Execute.<br><br>_Examine device file system. This module requires the PhoneGap API._ | None. | |
|
||||
| [ ] | **List Plugins** | 1. Click Execute.<br><br>_Attempts to guess installed plugins. This module requires the PhoneGap API._ | None. | |
|
||||
| [ ] | **Persist resume** | 1. Click Execute.<br><br>_Persist over applications sleep/wake events. This module requires the PhoneGap API._ | None. | |
|
||||
| [ ] | **Persistence (PhoneGap)** | 1. Configure: `Hook URL`<br>2. Click Execute.<br><br>_Insert the BeEF hook into PhoneGap's index.html (iPhone only). This module requires the PhoneGap API._ | None. | |
|
||||
| [ ] | **Prompt User** | 1. Configure: `Title`, `Question`, `Yes`...<br>2. Click Execute.<br><br>_Ask device user a question. This module requires the PhoneGap API._ | None. | |
|
||||
| [ ] | **Start Recording Audio** | 1. Configure: `File Name`<br>2. Click Execute.<br><br>_Start recording audio. This module requires the PhoneGap API._ | None. | |
|
||||
| [ ] | **Stop Recording Audio** | 1. Click Execute.<br><br>_Stop recording audio. This module requires the PhoneGap API._ | None. | |
|
||||
| [ ] | **Track Physical Movement** | 1. Click Execute.<br><br>_This module will track the physical movement of the user's device._ | None. | |
|
||||
| [ ] | **Upload File** | 1. Configure: `Destination`, `File Path`<br>2. Click Execute.<br><br>_Upload files from device to a server of your choice. This module requires the PhoneGap API._ | None. | |
|
||||
|
||||
#### 3.2.2 Legacy Plugins (Flash, Java, Silverlight, etc.)
|
||||
Requires the specific plugin to be installed and enabled in the browser.
|
||||
|
||||
| Status | Module Name | Instructions / Description | Cleanup Needed | Comments |
|
||||
| :---: | :--- | :--- | :--- | :--- |
|
||||
| [ ] | **Cross-Origin Scanner (Flash)** | 1. Configure: `Scan IP range (C class)`, `Ports`, `Workers`...<br>2. Click Execute.<br><br>_Scans an IP range... This module uses ContentHijacking.swf._ | None. | |
|
||||
| [ ] | **Detect Foxit Reader** | 1. Click Execute.<br><br>_This module will check if the browser has Foxit Reader Plugin._ | None. | |
|
||||
| [ ] | **Detect QuickTime** | 1. Click Execute.<br><br>_This module will check if the browser has Quicktime support._ | None. | |
|
||||
| [ ] | **Detect RealPlayer** | 1. Click Execute.<br><br>_This module will check if the browser has RealPlayer support._ | None. | |
|
||||
| [ ] | **Detect Silverlight** | 1. Click Execute.<br><br>_This module will check if the browser has Silverlight support._ | None. | |
|
||||
| [ ] | **Detect Unity Web Player** | 1. Click Execute.<br><br>_Detects Unity Web Player._ | None. | |
|
||||
| [ ] | **Detect VLC** | 1. Click Execute.<br><br>_This module will check if the browser has VLC plugin._ | None. | |
|
||||
| [ ] | **Detect Windows Media Player** | 1. Click Execute.<br><br>_This module will check if the browser has the Windows Media Player plugin installed._ | None. | |
|
||||
| [ ] | **Get Internal IP (Java)** | 1. Configure: `Number`<br>2. Click Execute.<br><br>_Retrieve the local network interface IP address of the victim machine using an unsigned Java applet._ | None. | |
|
||||
| [ ] | **Get System Info (Java)** | 1. Click Execute.<br><br>_This module will retrieve basic information about the host system using an unsigned Java Applet._ | None. | |
|
||||
| [ ] | **Webcam (Flash)** | 1. Configure: `Social Engineering Title`...<br>2. Click Execute.<br><br>_Shows the Adobe Flash 'Allow Webcam' dialog._ | None. | |
|
||||
| [ ] | **Webcam Permission Check** | 1. Click Execute.<br><br>_Checks if user has allowed BeEF domain to access Camera/Mic with Flash._ | None. | |
|
||||
|
||||
#### 3.2.3 Specific Target Software / Services
|
||||
Requires a specific vulnerable software or service to be running and accessible (e.g., Apache, JBoss, Printers).
|
||||
|
||||
| Status | Module Name | Instructions / Description | Cleanup Needed | Comments |
|
||||
| :---: | :--- | :--- | :--- | :--- |
|
||||
| [ ] | **Apache Cookie Disclosure** | 1. Click Execute.<br><br>_Exploits CVE-2012-0053. Requires Apache HTTP Server 2.2.0 through 2.2.21._ | Clear browser cookies. | |
|
||||
| [ ] | **Apache Felix Remote Shell** | 1. Configure: `Target Host`, `Target Port`...<br>2. Click Execute.<br><br>_Attempts to get a reverse shell on an Apache Felix Remote Shell server._ | None. | |
|
||||
| [ ] | **Bindshell (POSIX)** | 1. Configure: `Target Address`, `Target Port`, `Timeout (s)`...<br>2. Click Execute.<br><br>_Sends commands to a listening POSIX shell._ | None. | |
|
||||
| [ ] | **Bindshell (Windows)** | 1. Configure: `Target Address`, `Target Port`, `Timeout (s)`...<br>2. Click Execute.<br><br>_Sends commands to a listening Windows shell._ | None. | |
|
||||
| [ ] | **ColdFusion Directory Traversal** | 1. Configure: `Retrieve file`, `CF server OS`...<br>2. Click Execute.<br><br>_Exploits directory traversal in ColdFusion 8/9._ | None. | |
|
||||
| [ ] | **Cross-Site Faxing (XSF)** | 1. Configure: `Target Address`, `Target Port`...<br>2. Click Execute.<br><br>_Sends commands to ActiveFax RAW server socket._ | None. | |
|
||||
| [ ] | **Cross-Site Printing (XSP)** | 1. Configure: `Target Address`, `Target Port`...<br>2. Click Execute.<br><br>_Sends a message to a listening print port (9100)._ | None. | |
|
||||
| [ ] | **Detect Airdroid** | 1. Configure: `IP or Hostname`, `Port`<br>2. Click Execute.<br><br>_Attempts to detect Airdroid application for Android running on localhost._ | None. | |
|
||||
| [ ] | **Detect Burp** | 1. Run Burp Suite with browser proxied through it.<br>2. Execute module.<br>3. Check if Burp is detected (result shows "Burp detected" or similar). | None. | |
|
||||
| [ ] | **Detect CUPS** | 1. Configure: `IP or Hostname`, `Port`<br>2. Click Execute.<br><br>_Attempts to detect Common UNIX Printing System (CUPS) on localhost._ | None. | |
|
||||
| [ ] | **Detect Coupon Printer** | 1. Click Execute.<br><br>_Attempts to detect Coupon Printer on localhost._ | None. | |
|
||||
| [ ] | **Detect Ethereum ENS** | 1. Configure: `Image resource`...<br>2. Click Execute.<br><br>_Detects if using Ethereum ENS resolvers._ | None. | |
|
||||
| [ ] | **Detect Google Desktop** | 1. Click Execute.<br><br>_Attempts to detect Google Desktop running on the default port 4664._ | None. | |
|
||||
| [ ] | **Detect OpenNIC DNS** | 1. Configure: `Image resource`...<br>2. Click Execute.<br><br>_Detects if using OpenNIC DNS resolvers._ | None. | |
|
||||
| [ ] | **EXTRAnet Collaboration Tool** | 1. Configure: `Remote Host`, `Remote Port`...<br>2. Click Execute.<br><br>_Exploits command execution in 'admserver' component._ | None. | |
|
||||
| [ ] | **Farsite X25 gateway** | 1. Configure: `HTTP(s)`, `Remote Host`...<br>2. Click Execute.<br><br>_Exploits CVE-2014-7175/7173 to execute code._ | None. | |
|
||||
| [ ] | **Firephp 0.7.1 RCE** | 1. Click Execute.<br><br>_Exploit FirePHP <= 0.7.1._ | None. | |
|
||||
| [ ] | **Get Wireless Keys** | 1. Click Execute.<br><br>_Retrieve wireless profiles (Windows Vista and Windows 7 only)._ | None. | |
|
||||
| [ ] | **Get ntop Network Hosts** | 1. Configure: `Remote Host`, `Remote Port`<br>2. Click Execute.<br><br>_Retrieves information from ntop (unauthenticated)._ | None. | |
|
||||
| [ ] | **GlassFish WAR Upload** | 1. Configure: `Host`, `Filename`...<br>2. Click Execute.<br><br>_Attempts to deploy a malicious war file on GlassFish Server 3.1.1._ | None. | |
|
||||
| [ ] | **GroovyShell Server** | 1. Configure: `Remote Host`, `Remote Port`...<br>2. Click Execute.<br><br>_Uses GroovyShell Server interface to execute commands._ | None. | |
|
||||
| [ ] | **Hook Default Browser** | 1. Configure: `URL`<br>2. Click Execute.<br><br>_This module will use a PDF to attempt to hook the default browser._ | None. | |
|
||||
| [ ] | **HP uCMDB 9.0x add user** | 1. Configure: `Protocol`, `Host`, `Port`...<br>2. Click Execute.<br><br>_Attempts to add users to HP uCMDB._ | None. | |
|
||||
| [ ] | **IBM iNotes (Extract List)** | 1. Click Execute.<br><br>_Extracts iNotes contact list._ | None. | |
|
||||
| [ ] | **IBM iNotes (Flooder)** | 1. Configure: `To`, `Subject`, `Body`, `Count`...<br>2. Click Execute.<br><br>_Floods an email address from the victim's account._ | None. | |
|
||||
| [ ] | **IBM iNotes (Read)** | 1. Click Execute.<br><br>_Read a note from the victim's IBM iNotes._ | None. | |
|
||||
| [ ] | **IBM iNotes (Send)** | 1. Configure: `To`, `Subject`, `Body`<br>2. Click Execute.<br><br>_Sends an email from the victim's account._ | None. | |
|
||||
| [ ] | **IBM iNotes (Send w/ Attachment)** | 1. Configure: `To`, `Subject`, `Body`, `File`...<br>2. Click Execute.<br><br>_Sends an email with attachment from the victim's account._ | None. | |
|
||||
| [ ] | **IMAP** | 1. Configure: `IMAP Server`, `Port`, `Commands`<br>2. Click Execute.<br><br>_Sends commands to an IMAP4 server._ | None. | |
|
||||
| [ ] | **IRC** | 1. Configure: `IRC Server`, `Port`, `Username`...<br>2. Click Execute.<br><br>_Connects to an IRC server and sends messages._ | None. | |
|
||||
| [ ] | **IRC NAT Pinning** | 1. Configure: `Connect to`, `Private IP`, `Private Port`<br>2. Click Execute.<br><br>_Attempts to open closed ports on statefull firewalls compatible with IRC tracking._ | None. | |
|
||||
| [ ] | **Jboss 6.0.0M1 JMX Deploy** | 1. Configure: `Remote Target Host`...<br>2. Click Execute.<br><br>_Deploy a JSP reverse or bind shell using JMX._ | None. | |
|
||||
| [ ] | **Jenkins Code Exec CSRF** | 1. Configure: `Remote Host`, `Target URI`...<br>2. Click Execute.<br><br>_Attempts to get a reverse shell from Jenkins Groovy Script console._ | None. | |
|
||||
| [ ] | **Kemp LoadBalancer RCE** | 1. Configure: `URL`, `Remote Port`...<br>2. Click Execute.<br><br>_Exploits RCE in Kemp LoadBalancer 7.1-16._ | None. | |
|
||||
| [ ] | **QEMU Monitor 'migrate'** | 1. Configure: `Remote Host`, `Remote Port`...<br>2. Click Execute.<br><br>_Attempts to get a reverse shell from QEMU monitor service._ | None. | |
|
||||
| [ ] | **QNX QCONN Command Exec** | 1. Configure: `Remote Host`, `Remote Port`...<br>2. Click Execute.<br><br>_Exploits vulnerability in qconn component of QNX Neutrino._ | None. | |
|
||||
| [ ] | **RFI Scanner** | 1. Configure: `Target Protocol`, `Target Host`...<br>2. Click Execute.<br><br>_Scans web server for RFI vulnerabilities._ | None. | |
|
||||
| [ ] | **Redis** | 1. Configure: `Target Address`, `Target Port`...<br>2. Click Execute.<br><br>_Sends commands to a listening Redis daemon._ | None. | |
|
||||
| [ ] | **Shell Shock (CVE-2014-6271)** | 1. Configure: `Target`, `HTTP Method`...<br>2. Click Execute.<br><br>_Attemp to use vulnerability CVE-2014-627 to execute arbitrary code._ | None. | |
|
||||
| [ ] | **Shell Shock Scanner** | 1. Configure: `HTTP Method`, `Target Protocol`...<br>2. Click Execute.<br><br>_Attempts to get a reverse shell by requesting ~400 potentially vulnerable CGI scripts._ | None. | |
|
||||
| [ ] | **VTiger CRM Upload Exploit** | 1. Configure: `Target Web Server`...<br>2. Click Execute.<br><br>_Uploads and executes a reverse shell on VTiger CRM 5.0.4._ | None. | |
|
||||
| [ ] | **WAN Emulator Command Exec** | 1. Configure: `Target Host`, `Target Port`...<br>2. Click Execute.<br><br>_Attempts to get a reverse root shell on a WAN Emulator server._ | None. | |
|
||||
| [ ] | **WordPress Add User** | 1. Configure: `Username`, `Pwd`, `Email`...<br>2. Click Execute.<br><br>_Adds a WordPress User._ | None. | |
|
||||
| [ ] | **WordPress Add Administrator** | 1. Configure: `Username:`, `Pwd:`...<br>2. Click Execute.<br><br>_Stealthily adds a Wordpress administrator account._ | Close tab/window. Check for residual pop-unders. | |
|
||||
| [ ] | **WordPress Current User** | 1. Click Execute.<br><br>_Get the current logged in user information._ | None. | |
|
||||
| [ ] | **WordPress Upload RCE (Plugin)** | 1. Configure: `Auth Key`<br>2. Click Execute.<br><br>_Attempts to upload and activate a malicious wordpress plugin._ | None. | |
|
||||
| [ ] | **Wordpress Post-Auth RCE** | 1. Configure: `Target Web Server`<br>2. Click Execute.<br><br>_Attempts to upload and activate a malicious wordpress plugin._ | None. | |
|
||||
| [ ] | **Zenoss 3.x Add User** | 1. Configure: `Zenoss web root`...<br>2. Click Execute.<br><br>_Attempts to add a user to a Zenoss Core 3.x server._ | None. | |
|
||||
| [ ] | **Zenoss 3.x Command Exec** | 1. Configure: `Target Host`, `Target Port`...<br>2. Click Execute.<br><br>_Attempts to get a reverse shell on a Zenoss 3.x server._ | None. | |
|
||||
| [ ] | **ruby-nntpd Command Exec** | 1. Configure: `Remote Host`, `Remote Port`...<br>2. Click Execute.<br><br>_Uses 'eval' verb in ruby-nntpd 0.01dev to execute commands._ | None. | |
|
||||
|
||||
#### 3.2.4 Social Engineering / Account Phishing
|
||||
Requires the user to be logged into valid accounts (Gmail, Facebook, etc.) or susceptible to specific social engineering tricks.
|
||||
|
||||
| Status | Module Name | Instructions / Description | Cleanup Needed | Comments |
|
||||
| :---: | :--- | :--- | :--- | :--- |
|
||||
| [ ] | **Clippy** | 1. Configure: `Clippy image directory`...<br>2. Click Execute.<br><br>_Brings up a clippy image and asks the user to do stuff._ | None. | |
|
||||
| [ ] | **Detect Social Networks** | 1. Configure: `Detection Timeout`<br>2. Click Execute.<br><br>_Detects if authenticated to GMail, Facebook and Twitter._ | None. | |
|
||||
| [ ] | **Fake Flash Update** | 1. Configure: `Image`, `Payload URI`<br>2. Click Execute.<br><br>_Prompts the user to install an update to Adobe Flash Player._ | None. | |
|
||||
| [ ] | **Fake Notification Bar** | 1. Configure: `Notification text`<br>2. Click Execute.<br><br>_Displays a fake notification bar._ | None. | |
|
||||
| [ ] | **Fake Notification Bar (Chrome)**| 1. Configure: `URL`, `Notification text`<br>2. Click Execute.<br><br>_Displays a fake Chrome notification bar._ | None. | |
|
||||
| [ ] | **Fake Notification Bar (Firefox)**| 1. Configure: `Plugin URL`, `Notification text`<br>2. Click Execute.<br><br>_Displays a fake Firefox notification bar._ | None. | |
|
||||
| [ ] | **Fake Notification Bar (IE)** | 1. Configure: `URL`, `Notification text`<br>2. Click Execute.<br><br>_Displays a fake IE notification bar._ | None. | |
|
||||
| [x] | **Google Phishing** | 1. Configure: `XSS hook URI`, `Gmail logout interval`...<br>2. Click Execute.<br><br>_XSRF logout of Gmail, show phishing page._ | None. | |
|
||||
| [ ] | **Read Gmail** | 1. Click Execute.<br><br>_Grabs unread message ids from gmail atom feed._ | None. | |
|
||||
| [ ] | **Send Gvoice SMS** | 1. Configure: `To`, `Message`<br>2. Click Execute.<br><br>_Send a text message (SMS) through Google Voice._ | None. | |
|
||||
| [ ] | **Skype iPhone XSS** | 1. Click Execute.<br><br>_Steals iPhone contacts using a Skype XSS vuln._ | None. | |
|
||||
|
||||
#### 3.2.5 Advanced Network & Infrastructure
|
||||
Requires specific network configurations (e.g., DNS, Tor, Proxy, WPAD).
|
||||
|
||||
| Status | Module Name | Instructions / Description | Cleanup Needed | Comments |
|
||||
| :---: | :--- | :--- | :--- | :--- |
|
||||
| [ ] | **DNS Tunnel** | 1. Configure: `Domain`, `Data to send`<br>2. Click Execute.<br><br>_This module sends data one way over DNS, client to server only._ | None. | |
|
||||
| [ ] | **DNS Tunnel** | 1. Configure: `Domain`, `Message`, `Wait between requests (ms)`<br>2. Click Execute.<br><br>_This module sends data one way over DNS. Message split into chunks._ | None. | |
|
||||
| [ ] | **DNS Tunnel: Server-to-Client** | 1. Configure: `Payload Name`, `Zone`, `Message`<br>2. Click Execute.<br><br>_This module retrieves data sent by the server over DNS covert channel._ | None. | |
|
||||
| [ ] | **Detect Tor** | 1. Configure: `What Tor resource to request`, `Detection timeout`<br>2. Click Execute.<br><br>_This module will detect if the zombie is currently using Tor._ | None. | |
|
||||
| [ ] | **Get Proxy Servers (WPAD)** | 1. Click Execute.<br><br>_This module retrieves proxy server addresses for the zombie browser's local network using WPAD._ | None. | |
|
||||
|
||||
#### 3.2.6 Antivirus (Requires Specific AV/Extension)
|
||||
The "Detect Antivirus" module looks for artifacts (injected scripts, user-agent changes, or specific DOM elements) created by commercial antivirus products or their browser extensions.
|
||||
|
||||
**Setup Steps (Local VM):**
|
||||
1. **Install Browser Extension**: BeEF detects specfic artifacts in the DOM or User-Agent string. A free option to test is the **Avast Online Security** extension.
|
||||
- Open Firefox in the VM.
|
||||
- Navigate to the [Avast Online Security & Privacy](https://addons.mozilla.org/en-US/firefox/addon/avast-online-security/) addon page.
|
||||
- Click **Add to Firefox**.
|
||||
2. **Execute**: Run the module.
|
||||
- *Note: valid detection depends on the extension injecting specific signatures (e.g. `ASW/` in User-Agent) which may vary by version.*
|
||||
|
||||
| Status | Module Name | Instructions / Description | Cleanup Needed | Comments |
|
||||
| :---: | :--- | :--- | :--- | :--- |
|
||||
| [ ] | **Detect Antivirus** | 1. Install Avast extension (see above).<br>2. Execute module.<br>3. Check results for "Avast" or other detected AV. | Uninstall extension. | |
|
||||
|
||||
#### 3.2.7 Browser Extensions (Requires Installation)
|
||||
These modules detect specific browser extensions which must be installed in the hooked browser to be detectable.
|
||||
|
||||
**Setup Steps:**
|
||||
1. **LastPass**: Install the [LastPass Password Manager](https://addons.mozilla.org/en-US/firefox/addon/lastpass-password-manager/) extension in Firefox.
|
||||
2. **FireBug**: Note that FireBug is legacy/obsolete. This module may only work on older browser versions or specific legacy environments.
|
||||
|
||||
| Status | Module Name | Instructions / Description | Cleanup Needed | Comments |
|
||||
| :---: | :--- | :--- | :--- | :--- |
|
||||
| [x] | **Detect FireBug** | 1. Execute module.<br>2. Verify detection if legacy FireBug is present. | None. | |
|
||||
| [ ] | **Detect LastPass** | 1. Install LastPass extension.<br>2. Execute module.<br>3. Verify results show "Detected LastPass...". | Uninstall extension. | |
|
||||
| [ ] | **Detect Toolbars** | 1. Install a supported toolbar (e.g. legacy Google Toolbar, Alexa Toolbar).<br>2. Execute module.<br>3. Verify results show the detected toolbar name. | Uninstall toolbar. | |
|
||||
|
||||
#### 3.2.8 BeEF Extensions (Requires Configuration)
|
||||
Some modules require specific BeEF extensions to be enabled in the server configuration.
|
||||
|
||||
**Setup Steps:**
|
||||
1. **Enable ETag Extension**:
|
||||
- Open `config.yaml` in the BeEF root directory.
|
||||
- Find the `extension: etag:` section.
|
||||
- Set `enable: true`.
|
||||
2. **Enable S2C DNS Tunnel Extension** (if testing DNS Tunnel S2C):
|
||||
- In `config.yaml`, find `extension: s2c_dns_tunnel:`.
|
||||
- Set `enable: true`.
|
||||
3. **Restart BeEF**: You must restart the BeEF server for these changes to take effect.
|
||||
|
||||
| Status | Module Name | Instructions / Description | Cleanup Needed | Comments |
|
||||
| :---: | :--- | :--- | :--- | :--- |
|
||||
| [ ] | **ETag Tunnel: Server-to-Client** | 1. Enable **ETag extension** in `config.yaml` and restart BeEF.<br>2. Set `Payload Name` and `Message`.<br>3. Execute module.<br>4. Verify message is delivered (check browser results/window property). | Disable extension in `config.yaml` (optional). | |
|
||||
|
||||
### 3.3 Phase 3: Other Browsers & Specialized Extensions
|
||||
|
||||
Test these modules **only if they cannot be tested in Firefox**. Use Chrome, Safari, or Edge.
|
||||
|
||||
| Status | Module Name | Instructions / Description | Cleanup Needed | Comments |
|
||||
| :---: | :--- | :--- | :--- | :--- |
|
||||
| [ ] | **DNS Rebinding** | 1. Click Execute.<br><br>_dnsrebind_ | None. | |
|
||||
| [ ] | **Detect Evernote Web Clipper** | 1. Click Execute.<br><br>_This module checks if the Evernote Web Clipper extension is installed and active._ | None. | |
|
||||
| [ ] | **Execute On Tab** | 1. Configure: `URL`, `Javascript`<br>2. Click Execute.<br><br>_Open a new tab and execute the Javascript code on it. Chrome Extension specific._ | None. | |
|
||||
| [ ] | **Fake Evernote Web Clipper Login** | 1. Click Execute.<br><br>_Displays a fake Evernote Web Clipper login dialog._ | None. | |
|
||||
| [ ] | **Fake LastPass** | 1. Click Execute.<br><br>_Displays a fake LastPass user dialog. (Often Chrome specific)_ | None. | |
|
||||
| [ ] | **Get All Cookies** | 1. Configure: `Domain (e.g. http://facebook.com)`<br>2. Click Execute.<br><br>_Steal cookies, even HttpOnly cookies, providing the hooked extension has cookies access._ | Clear browser cookies. | |
|
||||
| [ ] | **Get Visited URLs (Avant Browser)** | 1. Configure: `Command ID`<br>2. Click Execute.<br><br>_Attempts to retrieve history requiring 'AFRunCommand()'. Avant Browser only._ | None. | |
|
||||
| [ ] | **Get Visited URLs (Old Browsers)** | 1. Configure: `URL(s)`<br>2. Click Execute.<br><br>_Detects visited URLs in older browsers._ | None. | |
|
||||
| [ ] | **Grab Google Contacts** | 1. Click Execute.<br><br>_Attempt to grab the contacts... exploiting export to CSV._ | None. | |
|
||||
| [ ] | **Hook Microsoft Edge** | 1. Configure: `URL`<br>2. Click Execute.<br><br>_Uses 'microsoft-edge:' protocol handler to hook Edge._ | None. | |
|
||||
| [ ] | **Inject BeEF** | 1. Click Execute.<br><br>_Attempt to inject the BeEF hook on all the available tabs._ | None. | |
|
||||
| [ ] | **JSONP Service Worker** | 1. Configure: `Path of the current domain`...<br>2. Click Execute.<br><br>_Exploits unfiltered callback in JSONP endpoint._ | Close tab/window. Check for residual pop-unders. | |
|
||||
| [ ] | **Local File Theft** | 1. Configure: `Target file`<br>2. Click Execute.<br><br>_JavaScript may have filesystem access if using file:// scheme (Safari/Local)._ | None. | |
|
||||
| [ ] | **Make Skype Call** | 1. Configure: `Number`<br>2. Click Execute.<br><br>_Forces browser to Skype call. Protocol handler `skype:`._ | None. | |
|
||||
| [ ] | **Make Telephone Call** | 1. Configure: `Number`<br>2. Click Execute.<br><br>_Forces browser to telephone call (iOS). Protocol handler `tel:`._ | None. | |
|
||||
| [ ] | **Ping Sweep (Java)** | 1. Configure: `Scan IP range (C class or IP)`, `Timeout (ms)`<br>2. Click Execute.<br><br>_Discover active hosts... using unsigned Java applet. (Alt for FF)_ | None. | |
|
||||
| [ ] | **Screenshot** | 1. Click Execute.<br><br>_Screenshots current tab (Chrome/HTML5)._ | None. | |
|
||||
| [ ] | **Webcam HTML5** | 1. Configure: `Screenshot size`<br>2. Click Execute.<br><br>_Leverage HTML5 WebRTC to capture webcam images. Only tested in Chrome._ | None. | |
|
||||
| [ ] | **iFrame Sniffer** | 1. Configure: `input URL`, `anchors to check`<br>2. Click Execute.<br><br>_Attempts to do framesniffing (aka Leaky Frame)._ | None. | |
|
||||
@@ -1,233 +0,0 @@
|
||||
# BeEF Module Testing Errors
|
||||
|
||||
This document tracks errors and issues encountered during manual testing of BeEF modules.
|
||||
|
||||
---
|
||||
|
||||
## CORS-001: Cross-Origin Scanner (CORS) Module Error
|
||||
|
||||
**Module**: Cross-Origin Scanner (CORS)
|
||||
**Category**: Network
|
||||
**Date**: 2026-01-04
|
||||
**Browser**: Firefox (Linux)
|
||||
**Status**: ❌ Not Passed
|
||||
**Github Issue**: [#3493](https://github.com/beefproject/beef/issues/3493)
|
||||
|
||||
### Test Configuration
|
||||
- **Scan IP range**: `127.0.0.1-127.0.0.1`
|
||||
- **Ports**: `8080`
|
||||
- **Test server**: Python CORS-enabled HTTP server running on localhost:8080
|
||||
|
||||
### Error Description
|
||||
The module crashes the BeEF server thread with an `ActiveModel::UnknownAttributeError` when attempting to save scan results to the database.
|
||||
|
||||
**Root Cause**: The module's `post_execute` method in `module.rb:24` attempts to create a `NetworkService` record using an attribute called `type`, but the model only has an attribute called `ntype`.
|
||||
|
||||
### Console Error
|
||||
```
|
||||
ActiveModel::UnknownAttributeError: unknown attribute 'type' for BeEF::Core::Models::NetworkService.
|
||||
|
||||
NoMethodError: undefined method `type=' for #<BeEF::Core::Models::NetworkService id: nil, hooked_browser_id: 0, proto: "http", ip: "127.0.0.1", port: "8080", ntype: nil>
|
||||
Did you mean? ntype=
|
||||
```
|
||||
|
||||
### Stack Trace (Key Lines)
|
||||
```
|
||||
from /beef/modules/network/cross_origin_scanner_cors/module.rb:24:in `post_execute'
|
||||
from /beef/core/main/handlers/commands.rb:59:in `setup'
|
||||
```
|
||||
|
||||
### Steps to Reproduce
|
||||
1. Start BeEF server
|
||||
2. Hook a browser (Firefox)
|
||||
3. Start a CORS-enabled test server on port 8080:
|
||||
```bash
|
||||
python3 -c "
|
||||
from http.server import HTTPServer, SimpleHTTPRequestHandler
|
||||
class CORSHandler(SimpleHTTPRequestHandler):
|
||||
def end_headers(self):
|
||||
self.send_header('Access-Control-Allow-Origin', '*')
|
||||
super().end_headers()
|
||||
HTTPServer(('127.0.0.1', 8080), CORSHandler).serve_forever()
|
||||
"
|
||||
```
|
||||
4. Execute Cross-Origin Scanner (CORS) module with:
|
||||
- Scan IP range: `127.0.0.1-127.0.0.1`
|
||||
- Ports: `8080`
|
||||
5. Observe error in BeEF server console
|
||||
|
||||
### Expected Result
|
||||
Module should return discovered CORS-enabled server at 127.0.0.1:8080 and save to database
|
||||
|
||||
### Actual Result
|
||||
Thread terminated with exception, scan results not saved
|
||||
|
||||
### Suggested Fix
|
||||
In `modules/network/cross_origin_scanner_cors/module.rb`, change `type:` to `ntype:` in the `NetworkService.create` call (line 24).
|
||||
|
||||
### Related Files
|
||||
- Module source: `modules/network/cross_origin_scanner_cors/command.js`
|
||||
|
||||
## EXT-001: Detect Extensions Module Failure
|
||||
|
||||
**Module**: Detect Extensions
|
||||
**Category**: Browser
|
||||
**Date**: 2026-01-12
|
||||
**Browser**: Firefox / Chrome (Modern)
|
||||
**Status**: ❌ Not Passed
|
||||
**Github Issue**: [#3494](https://github.com/beefproject/beef/issues/3494)
|
||||
|
||||
### Test Configuration
|
||||
- **Browser**: Firefox/Chrome (Latest)
|
||||
- **Extensions Installed**: Standard set (e.g. uBlock Origin, "Avast Online Security" from previous test)
|
||||
|
||||
### Error Description
|
||||
The module executes but returns no results, even when known extensions from its list are installed.
|
||||
|
||||
**Root Cause**:
|
||||
1. **Outdated Extension IDs**: The module uses a hardcoded list of extension IDs (e.g., `blpcfgokakmgnkcojhhkbfbldkacnbeo` for YouTube) which may be obsolete.
|
||||
2. **Browser Security**: Modern browsers (Chrome, Firefox) block external access to extension resources (`chrome-extension://...`) unless they are explicitly listed in `web_accessible_resources` in the extension's manifest. This prevents simple enumeration by checking for the existence of files.
|
||||
|
||||
### Steps to Reproduce
|
||||
1. Install a known extension.
|
||||
2. Execute "Detect Extensions" module.
|
||||
3. Observe Command Results.
|
||||
|
||||
### Expected Result
|
||||
List of detected extensions.
|
||||
|
||||
### Actual Result
|
||||
No output / "No extensions detected".
|
||||
|
||||
### Suggested Fix
|
||||
- Update the list of Extension IDs.
|
||||
- Investigate modern side-channel attacks for extension detection.
|
||||
|
||||
## UI-001: Module Search Broad Matching
|
||||
|
||||
**Module**: BeEF UI (Module Tree Search)
|
||||
**Category**: User Interface
|
||||
**Date**: 2026-01-12
|
||||
**Status**: ⚠️ Usability Issue
|
||||
**Github Issue**: [#3495](https://github.com/beefproject/beef/issues/3495)
|
||||
|
||||
### Error Description
|
||||
The module search bar in the "Commands" tab does not perform exact phrase matching or prioritized relevance sorting. Searching for a multi-word module name (e.g., "Detect FireBug") returns all modules matching the first word (e.g., "Detect"), resulting in a cluttered list of irrelevant modules.
|
||||
|
||||
### Steps to Reproduce
|
||||
1. Open the BeEF UI (`/ui/panel`).
|
||||
2. Select a hooked browser and navigate to the **Commands** tab.
|
||||
3. In the "Search capability..." input, type `Detect FireBug`.
|
||||
|
||||
### Expected Result
|
||||
The module tree should filter to show only modules matching "Detect FireBug".
|
||||
|
||||
### Actual Result
|
||||
The tree shows all modules containing "Detect" (e.g., "Detect Antivirus", "Detect Tor", etc.), making it difficult to find the specific module aimed for.
|
||||
|
||||
### Suggested Fix
|
||||
- Update the javascript search filter logic to strictly match the full search string or support quoted exact searches.
|
||||
- Modify the search to `AND` search terms instead of `OR` or partial matching on the first token.
|
||||
|
||||
## FP-001: Fingerprint Browser (PoC) Module Failure
|
||||
|
||||
**Module**: Fingerprint Browser (PoC)
|
||||
**Category**: Browser
|
||||
**Date**: 2026-01-12
|
||||
**Browser**: Firefox / Chrome (Modern)
|
||||
**Status**: ❌ Not Passed
|
||||
**Github Issue**: [#3496](https://github.com/beefproject/beef/issues/3496)
|
||||
|
||||
### Test Configuration
|
||||
- **Browser**: Firefox/Chrome (Latest)
|
||||
- **Environment**: Local VM
|
||||
|
||||
### Error Description
|
||||
The module executes successfully but fails to properly identify the browser type and version, returning "unknown" for both fields.
|
||||
|
||||
### Steps to Reproduce
|
||||
1. Start BeEF.
|
||||
2. Hook a modern browser (e.g., Firefox).
|
||||
3. Execute "Fingerprint Browser (PoC)" module.
|
||||
4. Check command results.
|
||||
|
||||
### Expected Result
|
||||
Parsed browser name (e.g., Firefox) and version (e.g., 120.0).
|
||||
|
||||
### Actual Result
|
||||
`data: browser_type=unknown&browser_version=unknown`
|
||||
|
||||
### Suggested Fix
|
||||
Update the browser identification logic in `modules/browser/fingerprint_browser_poc/command.js` to support modern User-Agent strings or use a more robust detection library.
|
||||
|
||||
## NET-001: Fingerprint Local Network No Feedback
|
||||
|
||||
**Module**: Fingerprint Local Network
|
||||
**Category**: Network
|
||||
**Date**: 2026-01-12
|
||||
**Browser**: Firefox (Linux)
|
||||
**Status**: ❌ Not Passed / ⚠️ UX Issue
|
||||
**Github Issue**: [#3497](https://github.com/beefproject/beef/issues/3497)
|
||||
|
||||
### Test Configuration
|
||||
- **Scan IP range**: `common` or specific local IP (e.g., `192.168.x.x`)
|
||||
- **Environment**: Local VM
|
||||
|
||||
### Error Description
|
||||
The module executes (visible via browser DevTools generating network requests), but provides absolutely no feedback in the BeEF UI.
|
||||
1. **No Progress Indicator**: There is no indication that the scan is running, how far along it is, or if it has finished.
|
||||
2. **No Final Status**: Command results remain empty even after the scan (presumably) finishes.
|
||||
3. **No Interruption Feedback**: If the user refreshes the browser to stop the scan, the BeEF UI does not register this change or update the command status; it simply hangs or stays empty.
|
||||
|
||||
### Steps to Reproduce
|
||||
1. Open DevTools -> Network tab in the hooked browser.
|
||||
2. Execute "Fingerprint Local Network" (range: `common`).
|
||||
3. Observe network requests in DevTools (module is running).
|
||||
4. Observe BeEF Command module results (remains empty).
|
||||
5. Refresh hooked browser.
|
||||
6. Observe BeEF Command module results (remains empty/no status update).
|
||||
|
||||
### Expected Result
|
||||
- The module should provide real-time or periodic status updates (e.g., "Scanning 10/20 IPs...").
|
||||
- It should report "No devices found" if nothing is detected, rather than staying silent.
|
||||
- It should handle browser disconnections/refreshes gracefully.
|
||||
|
||||
### Actual Result
|
||||
BeEF UI shows command as executing (or just sent), but no data is returned to the results panel. DevTools confirms the activity, but the operator is left blind.
|
||||
|
||||
### Suggested Fix
|
||||
- Implement `beef.net.send` calls within the JavaScript worker queue to report progress % back to the controller.
|
||||
- Ensure a final summary report is sent even if 0 positive matches are found.
|
||||
|
||||
## NET-002: Fingerprint Routers Module Error
|
||||
|
||||
**Module**: Fingerprint Routers
|
||||
**Category**: Network
|
||||
**Date**: 2026-01-12
|
||||
**Browser**: Firefox (Linux)
|
||||
**Status**: ❌ Not Passed
|
||||
**Github Issue**: [#3498](https://github.com/beefproject/beef/issues/3498)
|
||||
|
||||
### Test Configuration
|
||||
- **Browser**: Firefox
|
||||
- **Execution**: Standard execute (click button)
|
||||
|
||||
### Error Description
|
||||
The module crashes the BeEF server thread with an `ActiveModel::UnknownAttributeError` when attempting to save results to the database.
|
||||
|
||||
**Root Cause**: The module's `post_execute` method in `modules/network/jslanscanner/module.rb:29` attempts to create a `NetworkService` record using attribute `type`, but the model expects `ntype`.
|
||||
|
||||
### Console Error
|
||||
```
|
||||
ActiveModel::UnknownAttributeError: unknown attribute 'type' for BeEF::Core::Models::NetworkService.
|
||||
...
|
||||
from /beef/modules/network/jslanscanner/module.rb:29:in `post_execute'
|
||||
```
|
||||
|
||||
### Suggested Fix
|
||||
In `modules/network/jslanscanner/module.rb`:
|
||||
- Change line 29: `type: service` -> `ntype: service`
|
||||
- Check line 37: `type: device` -> `ntype: device` (if NetworkHost model also uses ntype).
|
||||
|
||||
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
# Untestable / Legacy Modules Report
|
||||
|
||||
This document lists modules that are likely incompatible with modern browsers (Firefox, Chrome, Edge, Safari) in a standard Ubuntu environment.
|
||||
|
||||
**Total Untestable Modules:** 49
|
||||
|
||||
| Module Name | Likely Reason for Untestability |
|
||||
| :--- | :--- |
|
||||
| **ARG-W4 ADSL Router DNS Hijack CSRF** | No compatible browsers listed in config |
|
||||
| **ASUS DSL-X11 ADSL Router DNS Hijack** | No compatible browsers listed in config |
|
||||
| **ActiveX Command Execution** | Requires ActiveX (IE only) |
|
||||
| **Airlive Add User CSRF** | No compatible browsers listed in config |
|
||||
| **AlienVault OSSIM 3.1 XSS** | No compatible browsers listed in config |
|
||||
| **Beetel BCM96338 Router DNS Hijack** | No compatible browsers listed in config |
|
||||
| **Cisco Collaboration Server 5 XSS** | No compatible browsers listed in config |
|
||||
| **Comtrend CT Series Router DNS Hijack** | No compatible browsers listed in config |
|
||||
| **Create Pop Under (IE)** | Requires ActiveX (IE only) |
|
||||
| **D-Link DSL-2640B DNS Hijack** | No compatible browsers listed in config |
|
||||
| **D-Link DSL-2640U ADSL Router DNS Hijack** | No compatible browsers listed in config |
|
||||
| **D-Link DSL-2740R DNS Hijack** | No compatible browsers listed in config |
|
||||
| **D-Link DSL-2780B ADSL Router DNS Hijack** | No compatible browsers listed in config |
|
||||
| **D-Link DSL-526B ADSL Router DNS Hijack** | No compatible browsers listed in config |
|
||||
| **Detect ActiveX** | Requires ActiveX (IE only) |
|
||||
| **Detect Default Browser** | Requires Internet Explorer (IE) |
|
||||
| **Detect Hewlett-Packard** | Requires Internet Explorer (IE) |
|
||||
| **Detect Local Drives** | Requires Internet Explorer (IE) |
|
||||
| **Detect MS Office** | Requires Internet Explorer (IE) |
|
||||
| **Detect Simple Adblock** | Requires Internet Explorer (IE) |
|
||||
| **Detect Software** | Requires Internet Explorer (IE) |
|
||||
| **Detect Unsafe ActiveX** | Requires ActiveX (IE only) |
|
||||
| **Detect Users** | Requires Internet Explorer (IE) |
|
||||
| **Disable Developer Tools** | No compatible browsers listed in config |
|
||||
| **Edge WScript WSH Injection** | Requires VBScript/WScript (IE only) |
|
||||
| **Exper EWM-01 ADSL Router DNS Hijack** | No compatible browsers listed in config |
|
||||
| **Get Clipboard** | Requires Internet Explorer (IE) |
|
||||
| **Get Registry Keys** | Requires ActiveX (IE only) |
|
||||
| **HTA PowerShell** | No compatible browsers listed in config |
|
||||
| **Inteno EG101R1 VoIP Router DNS Hijack** | No compatible browsers listed in config |
|
||||
| **Invisible HTMLFile (ActiveX)** | Requires Internet Explorer (IE) |
|
||||
| **NtfsCommonCreate DoS** | Requires Internet Explorer (IE) |
|
||||
| **Opencart Reset Password CSRF** | No compatible browsers listed in config |
|
||||
| **PHP 5.3.9 DoS** | No compatible browsers listed in config |
|
||||
| **PIKATEL Router DNS Hijack** | No compatible browsers listed in config |
|
||||
| **Philips DNS Hijack** | No compatible browsers listed in config |
|
||||
| **Planet VDR-300NU ADSL Router DNS Hijack** | No compatible browsers listed in config |
|
||||
| **SQLiteManager XSS** | No compatible browsers listed in config |
|
||||
| **Serendipity <= 1.6 XSS** | No compatible browsers listed in config |
|
||||
| **Shuttle Tech 915 WM DNS Hijack** | No compatible browsers listed in config |
|
||||
| **SiteKiosk Breakout** | No compatible browsers listed in config |
|
||||
| **Spring Framework Malicious Jar Exploit** | No compatible browsers listed in config |
|
||||
| **TP-Link DNS Hijack CSRF** | No compatible browsers listed in config |
|
||||
| **Tenda ADSL Router DNS Hijack** | No compatible browsers listed in config |
|
||||
| **UTstarcom WA3002G4 DNS Hijack** | No compatible browsers listed in config |
|
||||
| **User Interface Abuse (IE 9/10)** | Requires Internet Explorer (IE) |
|
||||
| **WiFi Pineapple Root Password CSRF** | No compatible browsers listed in config |
|
||||
| **boastMachine <= 3.1 Add User CSRF** | No compatible browsers listed in config |
|
||||
| **iBall Baton iB-WRA150N DNS Hijack** | No compatible browsers listed in config |
|
||||
Reference in New Issue
Block a user