Skip to content

Add detect_media_devices browser module#3565

Merged
zinduolis merged 3 commits into
beefproject:masterfrom
sethumadh:feature/3542-detect-media-devices
May 27, 2026
Merged

Add detect_media_devices browser module#3565
zinduolis merged 3 commits into
beefproject:masterfrom
sethumadh:feature/3542-detect-media-devices

Conversation

@sethumadh
Copy link
Copy Markdown
Contributor

@sethumadh sethumadh commented May 14, 2026

Category

Module

Feature/Issue Description

Q: Please give a brief summary of your feature/fix
A: This PR intends to add a new browser-side recon module called Detect Media Devices wrapping navigator.mediaDevices.enumerateDevices() to enumerate the hooked browser's microphones, cameras, and speakers.
Pre Permission : Fully passive where no permission prompt is granted by user, no device is opened, no camera/mic indicator activates. The module returns counts for whichever kinds the browser is willing to enumerate pre-permission (please see browser divergence below), with empty-string labels for those kinds.Find the matrix below

Pre-permission behavior matrix

State before any getUserMedia consent has been granted to the origin. Assumes the machine has hardware for each kind (the common case).

Legend: ⬜ Empty-label placeholder (count capped at 1, "at least one exists")  ·  ❌ Kind absent from enumerated list

Browser audioinput (mic) videoinput (camera) audiooutput (speakers)
Chrome ⬜ Placeholder ⬜ Placeholder ⬜ Placeholder
Firefox 115+ ⬜ Placeholder ⬜ Placeholder ❌ Absent
Safari 14+ ⬜ Placeholder ⬜ Placeholder ❌ Absent

Key divergence: Chrome enumerates all three kinds pre-permission; Firefox and Safari omit audiooutput entirely. Firefox shipped this restriction in Firefox 115 (June 2023, Bug #1528042). Safari shipped equivalent restrictions in Safari 14 (per Mozilla's intent-to-ship thread). Chrome has not implemented these restrictions as of writing. All three behaviors are W3C-spec-compliant and the spec is permissive about pre-permission output.

When hardware is absent for a kind (e.g., headless Linux, audio-stripped corporate workstation, certain VMs):

  • All three browsers omit audioinput if no microphone hardware exists. So audioinput_count=0 is a uniform cross-browser signal meaning "no mic on this machine."
  • Same logic for videoinput: videoinput_count=0 means "no camera," uniform across browsers.
  • One thing to be aware of : audiooutput_count=0:** In Chrome it means "no speakers exist on the machine," on Firefox / Safari it means "speakers may or may not exist, can't always mean pre-permission."

Post-permission : Post-permission, the module shows real labels and accurate counts for kinds whose consent surface was granted by the user's getUserMedia. Kinds NOT granted permission stay in pre-permission placeholder state (count capped at 1, label empty).
Also for post permission each kind unlocks only when the user gives consent for that specific kind. But audio is an exception. if the permsission is granted for only one audio kind in hooked browser, the permission is bundled for both the audio kind and the enumerate detail will consist of both audio kind, label and count. This is verified across all three major browser engines on macOS. Find the matrix below

Post-permission behavior matrix

Legend: ✅ Real labels exposed, accurate count  ·  ⬜ Empty-label placeholder (count capped at 1)  ·  ❌ Kind absent from enumerated list

User grants getUserMedia(...) with audioinput (mic) videoinput (camera) audiooutput (speakers)
{ audio: true }  (mic only) ✅ Real ⬜ Placeholder ✅ Real (bundled with audioinput)
{ video: true }  on Chrome ⬜ Placeholder ✅ Real ⬜ Placeholder
{ video: true }  on Firefox / Safari ⬜ Placeholder ✅ Real ❌ Absent
{ audio: true, video: true } ✅ Real ✅ Real ✅ Real

Key pattern: The audiooutput column reveals real labels (✅) only when audio consent was granted and never from video-only consent. The audio surface (audioinput + audiooutput) bundles as one privacy target. The video surface (videoinput) is strictly per-kind. This asymmetry is consistent across all three engines.

Closes #3542 (raised by @bcoles).

Q: Give a technical rundown of what you have changed (if applicable)
A: Three new files in the standard 3-file module pattern (command.js / module.rb / config.yaml), borrowed template from modules/browser/detect_lastpass/. command.js calls enumerateDevices(), groups results by kind, and POSTs via beef.net.send. Errors from Missing-API and exception are reported using beef.status.error(). module.rb stores six form-body keys into @datastore and saves them. No new dependencies.

Two design notes:

  1. Multi-key form body, not a single JSON value. BeEF's display layer (core/main/handlers/commands.rb:83) wraps every result body under a 'data': key. A URL-encoded JSON blob renders as %7B%22audioinput%22... — unreadable. Using audioinput_count=...&audioinput_labels=... (one key per data point) which is an existing multi-key pattern in webcam_html5'.

  2. Permission-state insight in the count. Pre-permission, enumerateDevices() returns one placeholder per kind that exists. Post-permission will return the full inventory. So the count alone can be considered as a permission-state signal, independent of labels which are generic pre permission.
    Please note here re: browser divergence: There's also a vendor signal, meaning this will tell us the kind or type of browsers, in the pre-permission shape: Chrome lists all 3 kinds, while Firefox 115+ and Safari 14+ list only 2 (no audiooutput), per Mozilla Bug #1528042.

Test Cases

Q: Describe your test cases, what you have covered and if there are any use cases that still need addressing.
A: Tested manually on macOS against BeEF's /demos/basic.html hooked demo page. Screenshots below.

Chrome
chrome-post-permission
chrome-pre-permission

Firefox
firefox-post-permission
firefox-pre-permission

Safari
safari-post-permission
safari-pre-permission

Browser Pre-permission Post-permission
Chrome (latest) 3 entries: 1/1/1, empty labels 10 entries: (Built-in) / (Virtual) / (Aggregate) suffixes
Firefox 115+ 2 entries: 1/0/1 (no audiooutput) 9 entries: plain labels, Default audiooutput deduped
Safari 14+ 2 entries: 1/0/1 (matches Firefox) 10 entries: Default - <device> audiooutput prefix; surfaces Continuity Camera Desk View

Manual reproduction (5 steps):

  1. Load http://<beef>:3000/demos/basic.html to hook a browser.
  2. Admin panel - hooked browser - Commands - Browser - Detect Media Devices - Execute. Observe the pre-permission result in Module Results History.
  3. In a DevTools console on the hooked tab, run navigator.mediaDevices.getUserMedia({audio:true, video:true}).then(s => s.getTracks().forEach(t => t.stop())) and click Allow to allow permission to devices.
  4. Re-Execute the module and observe the post-permission result.

sethumadh and others added 2 commits May 14, 2026 15:28
Implements navigator.mediaDevices.enumerateDevices() detection.
Groups results by kind (audioinput, audiooutput, videoinput) and
returns counts unconditionally + labels when permissions allow.
Handles the no-API case explicitly via beef.status.error().

Closes beefproject#3542
@zinduolis zinduolis self-requested a review May 26, 2026 23:46
@zinduolis zinduolis added the safe_to_test Label to trigger tests on PR label May 27, 2026
@github-actions github-actions Bot removed the safe_to_test Label to trigger tests on PR label May 27, 2026
Copy link
Copy Markdown
Contributor

@zinduolis zinduolis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @sethumadh , thanks for your PR.

All applicable checks pass:

  • RuboCop: clean
  • YAML: valid
  • RSpec browser suite: 147/147 pass with new module included
  • ESLint: not applicable to ERB-templated command.js (project-wide convention)

The implementation is well-scoped, follows BeEF conventions, has thorough manual testing documented in the PR description, and I have verified it manually on:

  • Firefox 148.0.2 (Ubuntu) - pre-permission: audioinput_count=1&audioinput_labels=&audiooutput_count=0&audiooutput_labels=&videoinput_count=0&videoinput_labels= (confirms audiooutput absent pre-permission)
  • Firefox 148.0.2 (Ubuntu) - post-permission ({audio:true} granted): real labels for both audioinput (2 entries: device + PulseAudio monitor) and audiooutput (1 entry), confirming the PR's documented audio-surface bundling behavior
  • Chrome 148.0.7778.167 (Ubuntu) - pre-permission: audioinput_count=1&audioinput_labels=&audiooutput_count=1&audiooutput_labels=&videoinput_count=0&videoinput_labels= (confirms all three kinds enumerated)

The module is automatically covered by the existing dynamic test loader.

The PR is now approved and you can merge it. Let me know if you don't have permissions to merge it and I'll do it for you.

Thanks

@sethumadh
Copy link
Copy Markdown
Contributor Author

@zinduolis
Thank you for the approval. Looks like I do not have the permission to merge it. Would it be possible for you to merge it?

Thanks

@zinduolis zinduolis merged commit ef93322 into beefproject:master May 27, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Browser Details: Detect media devices

2 participants