#1# Copyright (c) 2006-2025 Wade Alcorn - [email protected]2# Browser Exploitation Framework (BeEF) - https://beefproject.com3# See the file 'doc/COPYING' for copying permission4#56module BeEF7module Core8module Rest9class Modules < BeEF::Core::Router::Router10config = BeEF::Core::Configuration.instance1112before do13error 401 unless params[:token] == config.get('beef.api_token')14halt 401 unless BeEF::Core::Rest.permitted_source?(request.ip)15headers 'Content-Type' => 'application/json; charset=UTF-8',16'Pragma' => 'no-cache',17'Cache-Control' => 'no-cache',18'Expires' => '0'19end2021#22# @note Get all available and enabled modules (id, name, category)23#24get '/' do25mods = BeEF::Core::Models::CommandModule.all2627mods_array = []2829mods.each do |mod|30modk = BeEF::Module.get_key_by_database_id(mod.id)31next unless BeEF::Module.is_enabled(modk)3233mods_array << {34'id' => mod.id,35'class' => config.get("beef.module.#{modk}.class"),36'name' => config.get("beef.module.#{modk}.name"),37'category' => config.get("beef.module.#{modk}.category")38}39end40mods_array.to_json41end4243get '/search/:mod_name' do44mod = BeEF::Core::Models::CommandModule.where(name: params[:mod_name]).first45result = {}46result = { 'id' => mod.id } unless mod.nil?47result.to_json48end4950#51# @note Get the module definition (info, options)52#53get '/:mod_id' do54cmd = BeEF::Core::Models::CommandModule.find(params[:mod_id])55error 404 if cmd.nil?56modk = BeEF::Module.get_key_by_database_id(params[:mod_id])57error 404 if modk.nil?5859# TODO: check if it's possible to also retrieve the TARGETS supported60{61'name' => cmd.name,62'description' => config.get("beef.module.#{cmd.name}.description"),63'category' => config.get("beef.module.#{cmd.name}.category"),64'options' => BeEF::Module.get_options(modk) # TODO: => get also payload options..get_payload_options(modk,text)65}.to_json66end6768# @note Get the module result for the specific executed command69#70# Example with the Alert Dialog71# GET /api/modules/wiJCKAJybcB6aXZZOj31UmQKhbKXY63aNBeODl9kvkIuYLmYTooeGeRD7Xn39x8zOChcUReM3Bt7K0xj/86/1?token=0a931a461d08b86bfee40df987aad7e9cfdeb050 HTTP/1.172# Host: 127.0.0.1:300073#===response (snip)===74# HTTP/1.1 200 OK75# Content-Type: application/json; charset=UTF-876#77# {"date":"1331637093","data":"{\"data\":\"text=michele\"}"}78#79get '/:session/:mod_id/:cmd_id' do80hb = BeEF::Core::Models::HookedBrowser.where(session: params[:session]).first81error 401 if hb.nil?82cmd = BeEF::Core::Models::Command.where(hooked_browser_id: hb.id,83command_module_id: params[:mod_id], id: params[:cmd_id]).first84error 404 if cmd.nil?85results = BeEF::Core::Models::Result.where(hooked_browser_id: hb.id, command_id: cmd.id)86error 404 if results.nil?8788results_hash = {}89i = 090results.each do |result|91results_hash[i] = {92'date' => result.date,93'data' => result.data94}95i += 196end97results_hash.to_json98end99100#101# @note Fire a new command module to the specified hooked browser.102# Return the command_id of the executed module if it has been fired correctly.103# Input must be specified in JSON format104#105# +++ Example with the Alert Dialog: +++106# POST /api/modules/wiJCKAJybcB6aXZZOj31UmQKhbKXY63aNBeODl9kvkIuYLmYTooeGeRD7Xn39x8zOChcUReM3Bt7K0xj/86?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1107# Host: 127.0.0.1:3000108# Content-Type: application/json; charset=UTF-8109# Content-Length: 18110#111# {"text":"michele"}112#===response (snip)===113# HTTP/1.1 200 OK114# Content-Type: application/json; charset=UTF-8115# Content-Length: 35116#117# {"success":"true","command_id":"1"}118#119# +++ Example with a Metasploit module (Adobe FlateDecode Stream Predictor 02 Integer Overflow) +++120# +++ note that in this case we cannot query BeEF/Metasploit if module execution was successful or not.121# +++ this is why there is "command_id":"not_available" in the response122# POST /api/modules/wiJCKAJybcB6aXZZOj31UmQKhbKXY63aNBeODl9kvkIuYLmYTooeGeRD7Xn39x8zOChcUReM3Bt7K0xj/236?token=83f13036060fd7d92440432dd9a9b5e5648f8d75 HTTP/1.1123# Host: 127.0.0.1:3000124# Content-Type: application/json; charset=UTF-8125# Content-Length: 81126#127# {"SRVPORT":"3992", "URIPATH":"77345345345dg", "PAYLOAD":"generic/shell_bind_tcp"}128#===response (snip)===129# HTTP/1.1 200 OK130# Content-Type: application/json; charset=UTF-8131# Content-Length: 35132#133# {"success":"true","command_id":"not_available"}134#135post '/:session/:mod_id' do136hb = BeEF::Core::Models::HookedBrowser.where(session: params[:session]).first137error 401 if hb.nil?138modk = BeEF::Module.get_key_by_database_id(params[:mod_id])139error 404 if modk.nil?140141request.body.rewind142begin143data = JSON.parse request.body.read144options = []145data.each { |k, v| options.push({ 'name' => k, 'value' => v }) }146exec_results = BeEF::Module.execute(modk, params[:session], options)147exec_results.nil? ? '{"success":"false"}' : '{"success":"true","command_id":"' + exec_results.to_s + '"}'148rescue StandardError149print_error "Invalid JSON input for module '#{params[:mod_id]}'"150error 400 # Bad Request151end152end153154#155# @note Fire a new command module to multiple hooked browsers.156# Returns the command IDs of the launched module, or 0 if firing got issues.157# Use "hb_ids":["ALL"] to run on all hooked browsers158# Use "hb_ids":["ALL_ONLINE"] to run on all hooked browsers currently online159#160# POST request body example (for modules that don't need parameters, just remove "mod_params")161# {162# "mod_id":1,163# "mod_params":{164# "question":"are you hooked?"165# },166# "hb_ids":[1,2]167# }168#169# response example: {"1":16,"2":17}170#171# curl example (alert module with custom text, 2 hooked browsers)):172#173# curl -H "Content-Type: application/json; charset=UTF-8" -d '{"mod_id":110,"mod_params":{"text":"mucci?"},"hb_ids":[1,2]}'174#-X POST http://127.0.0.1:3000/api/modules/multi_browser?token=2316d82702b83a293e2d46a0886a003a6be0a633175#176post '/multi_browser' do177request.body.rewind178begin179body = JSON.parse request.body.read180181modk = BeEF::Module.get_key_by_database_id body['mod_id']182error 404 if modk.nil?183mod_params = []184185unless body['mod_params'].nil?186body['mod_params'].each do |k, v|187mod_params.push({ 'name' => k, 'value' => v })188end189end190191hb_ids = body['hb_ids']192results = {}193194# run on all hooked browsers currently online?195if hb_ids.first =~ /\Aall_online\z/i196hb_ids = []197BeEF::Core::Models::HookedBrowser.where(198:lastseen.gte => (Time.new.to_i - 15)199).each { |hb| hb_ids << hb.id }200# run on all hooked browsers?201elsif hb_ids.first =~ /\Aall\z/i202hb_ids = []203BeEF::Core::Models::HookedBrowser.all.each { |hb| hb_ids << hb.id }204end205206# run modules207hb_ids.each do |hb_id|208hb = BeEF::Core::Models::HookedBrowser.find(hb_id)209if hb.nil?210results[hb_id] = 0211next212else213cmd_id = BeEF::Module.execute(modk, hb.session, mod_params)214results[hb_id] = cmd_id215end216end217results.to_json218rescue StandardError219print_error 'Invalid JSON input passed to endpoint /api/modules/multi_browser'220error 400 # Bad Request221end222end223224# @note Fire multiple command modules to a single hooked browser.225# Returns the command IDs of the launched modules, or 0 if firing got issues.226#227# POST request body example (for modules that don't need parameters, just pass an empty JSON object like {} )228# { "hb":"vkIwVV3ok5i5vH2f8sxlkoaKqAGKCbZXdWqE9vkHNFBhI8aBBHvtZAGRO2XqFZXxThBlmKlRiVwPeAzj",229# "modules": [230# { # test_return_long_string module with custom input231# "mod_id":99,232# "mod_input":[{"repeat":"10"},{"repeat_string":"ABCDE"}]233# },234# { # prompt_dialog module with custom input235# "mod_id":116,236# "mod_input":[{"question":"hooked?"}]237# },238# { # alert_dialog module without input (using default input, if any)239# "mod_id":128,240# "mod_input":[]241# }242# ]243# }244# response example: {"99":7,"116":8,"128":0} # <- This means the alert_dialog had issues (see return value 0)245#246# curl example (test_return_long_string and prompt_dialog module with custom inputs)):247#248# curl -H "Content-Type: application/json; charset=UTF-8" -d '{"hb":"vkIwVV3ok5i5vH2f8sxlkoaKqAGKCbZXdWqE9vkHNFBhI8aBBHvtZAGRO2XqFZXxThBlmKlRiVwPeAzj",249# "modules":[{"mod_id":99,"mod_input":[{"repeat":"10"},{"repeat_string":"ABCDE"}]},{"mod_id":116,"mod_input":[{"question":"hooked?"}]},{"mod_id":128,"mod_input":[]}]}'250# -X POST http://127.0.0.1:3000/api/modules/multi_module?token=e640483ae9bca2eb904f003f27dd4bc83936eb92251#252post '/multi_module' do253request.body.rewind254begin255body = JSON.parse request.body.read256hb = BeEF::Core::Models::HookedBrowser.where(session: body['hb']).first257error 401 if hb.nil?258259results = {}260unless body['modules'].nil?261body['modules'].each do |mod|262mod_id = mod['mod_id']263mod_k = BeEF::Module.get_key_by_database_id mod['mod_id']264if mod_k.nil?265results[mod_id] = 0266next267else268mod_params = []269mod['mod_input'].each do |input|270input.each do |k, v|271mod_params.push({ 'name' => k, 'value' => v })272end273end274cmd_id = BeEF::Module.execute(mod_k, hb.session, mod_params)275results[mod_id] = cmd_id276end277end278end279results.to_json280rescue StandardError281print_error 'Invalid JSON input passed to endpoint /api/modules/multi'282error 400 # Bad Request283end284end285end286end287end288end289290291