Merge branch 'pr/LauchieHarvey/3031' into port-tests

This commit is contained in:
Stephen
2024-03-28 08:30:12 +10:00
9 changed files with 679 additions and 15 deletions

View File

@@ -105,6 +105,99 @@ module BeEF
{ 'success' => false, 'error' => e.message }
end
# Update an ARE rule set.
# @param [Hash] ARE rule ID.
# @param [Hash] ARE ruleset as JSON
# @return [Hash] {"success": Boolean, "rule_id": Integer, "error": String}
def update_rule_json(id, data)
# Quite similar in implementation to load_rule_json. Might benefit from a refactor.
name = data['name'] || ''
author = data['author'] || ''
browser = data['browser'] || 'ALL'
browser_version = data['browser_version'] || 'ALL'
os = data['os'] || 'ALL'
os_version = data['os_version'] || 'ALL'
modules = data['modules']
execution_order = data['execution_order']
execution_delay = data['execution_delay']
chain_mode = data['chain_mode'] || 'sequential'
begin
BeEF::Core::AutorunEngine::Parser.instance.parse(
name,
author,
browser,
browser_version,
os,
os_version,
modules,
execution_order,
execution_delay,
chain_mode
)
rescue => e
print_error("[ARE] Error updating ruleset (#{name}): #{e.message}")
return { 'success' => false, 'error' => e.message }
end
existing_rule = BeEF::Core::Models::Rule.where(
name: name,
author: author,
browser: browser,
browser_version: browser_version,
os: os,
os_version: os_version,
modules: modules.to_json,
execution_order: execution_order.to_s,
execution_delay: execution_delay.to_s,
chain_mode: chain_mode
).first
unless existing_rule.nil?
msg = "Duplicate rule already exists in the database (ID: #{existing_rule.id})"
print_info("[ARE] Skipping ruleset (#{name}): #{msg}")
return { 'success' => false, 'error' => msg }
end
old_are_rule = BeEF::Core::Models::Rule.find_by(id: id)
old_are_rule.update(
name: name,
author: author,
browser: browser,
browser_version: browser_version,
os: os,
os_version: os_version,
modules: modules.to_json,
execution_order: execution_order.to_s,
execution_delay: execution_delay.to_s,
chain_mode: chain_mode
)
print_info("[ARE] Ruleset (#{name}) updated successfully.")
if @debug_on
print_more "Target Browser: #{browser} (#{browser_version})"
print_more "Target OS: #{os} (#{os_version})"
print_more 'Modules to run:'
modules.each do |mod|
print_more "(*) Name: #{mod['name']}"
print_more "(*) Condition: #{mod['condition']}"
print_more "(*) Code: #{mod['code']}"
print_more '(*) Options:'
mod['options'].each do |key, value|
print_more "\t#{key}: (#{value})"
end
end
print_more "Exec order: #{execution_order}"
print_more "Exec delay: #{exec_delay}"
end
{ 'success' => true }
rescue TypeError, ArgumentError => e
print_error("[ARE] Failed to update ruleset (#{name}): #{e.message}")
{ 'success' => false, 'error' => e.message }
end
# Load an ARE ruleset from file
# @param [String] JSON ARE ruleset file path
def load_rule_file(json_rule_path)

View File

@@ -84,6 +84,26 @@ module BeEF
{ 'success' => false, 'error' => e.message }.to_json
end
#
# Update a ruleset
#
patch '/rule/:rule_id' do
rule_id = params[:rule_id]
rule = BeEF::Core::Models::Rule.find(rule_id)
raise InvalidParameterError, 'id' if rule.nil?
data = JSON.parse request.body.read
rloader = BeEF::Core::AutorunEngine::RuleLoader.instance
rloader.update_rule_json(rule_id, data)
{ 'success' => true }.to_json
rescue InvalidParameterError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while updating Autorun rule: #{e.message}"
{ 'success' => false, 'error' => e.message }.to_json
end
#
# Run a specified rule on all online hooked browsers (if the zombie matches the rule).
# Offline hooked browsers are ignored

View File

@@ -25,21 +25,20 @@ module BeEF
get '/' do
mods = BeEF::Core::Models::CommandModule.all
mods_hash = {}
i = 0
mods_array = []
mods.each do |mod|
modk = BeEF::Module.get_key_by_database_id(mod.id)
next unless BeEF::Module.is_enabled(modk)
mods_hash[i] = {
mods_array << {
'id' => mod.id,
'class' => config.get("beef.module.#{modk}.class"),
'name' => config.get("beef.module.#{modk}.name"),
'category' => config.get("beef.module.#{modk}.category")
}
i += 1
end
mods_hash.to_json
mods_array.to_json
end
get '/search/:mod_name' do

View File

@@ -90,6 +90,9 @@ module BeEF
ui/panel/tabs/ZombieTabRTC.js
ui/panel/Logout.js
ui/panel/WelcomeTab.js
ui/panel/AutoRunTab.js
ui/panel/AutoRunRuleForm.js
ui/panel/AutoRunModuleForm.js
ui/panel/ModuleSearching.js
]

View File

@@ -0,0 +1,172 @@
loadModuleInfo = async function(token, moduleId, moduleName) {
try {
// If all we have is the name then we need to get the ID first.
if (moduleId === undefined) {
const searchResponse = await fetch(`/api/modules/search/${moduleName}?token=${token}`);
if (!searchResponse.ok) {
throw new Error(`Getting auto run rules failed with status ${searchResponse.status}`);
}
const searchData = await searchResponse.json();
if (typeof searchData.id === 'number') {
moduleId = searchData.id;
} else {
throw new Error("Searching module name failed.");
}
}
const infoResponse = await fetch(`/api/modules/${moduleId}?token=${token}`);
const infoData = await infoResponse.json();
if (!infoData) {
throw new Error(`Module with name ${moduleName} and ID ${moduleId} couldn't be retrived.`);
}
// Set the module Id incase we need it later.
infoData.id = moduleId;
return infoData;
} catch(error) {
console.error(error);
console.error("Failed to get module information.");
return null;
}
}
/**
* Form that displays fields for a module.
*
* moduleData: The object definition of this moduleData from the Auto Run Engine.
* deleteFn: callback function to delete this moduleData.
* moveUp: moves the module up one spot in the Auto Run execution order.
* moveDown: moves the module down one spot in the Auto Run exection order.
*/
AutoRunModuleForm = function(moduleData, deleteFn, moveUp, moveDown, ruleId, index, moduleList) {
const moduleTextAreaId = `rule-${ruleId}-module-textarea-${index}`;
const chainModeComboId = `rule-${ruleId}-module-combo-${index}`;
const token = BeefWUI.get_rest_token();
const comboStore = new Ext.data.Store({
data: moduleList,
reader: new Ext.data.JsonReader({
fields: ['id', 'name'],
}),
proxy: new Ext.data.MemoryProxy(moduleList)
});
const moduleSelect = new Ext.form.ComboBox({
fieldLabel: 'Change Module',
store: comboStore,
queryMode: 'local',
displayField: 'name',
valueField: 'id',
editable: false, // Disable manual editing of the field
forceSelection: true, // Force selection from the list
triggerAction: 'all',
typeAhead: true,
listeners: {
select: async function(combo) {
const selectedModuleId = combo.getValue()
const moduleInfo = await loadModuleInfo(token, selectedModuleId, undefined);
if (!moduleInfo) {
console.error("Failed to load new module.");
return;
}
// Update the module data to reflect the new module.
moduleData.name = moduleInfo.name;
moduleData.condition = moduleInfo.condition ? moduleInfo.condition : null;
moduleData.options = {};
for (let i = 0; i < moduleInfo.options.length; i++) {
const newOption = moduleInfo.options[i];
moduleData.options[newOption.name] = newOption.value ? newOption.value : '';
}
loadModule(moduleInfo);
}
}
});
const moduleOptionsContainer = new Ext.Panel({
title: `Module ${index + 1}`,
tbar: [moduleSelect],
layout: 'form',
border: false,
listeners: {
afterrender: function() {loadModule(undefined)}
}
});
async function loadModule(moduleInfo) {
if (!moduleInfo)
moduleInfo = await loadModuleInfo(token, undefined, moduleData.name);
if (!moduleInfo) {
moduleOptionsContainer.update("<p>Failed to load module information.</p>");
return;
}
// Update the combobox default value to be this module.
// Can't use the moduleData name since it doesn't match the ID.
moduleSelect.setValue(moduleInfo.id);
// Setup module form elements. Remove all incase we're switching from a different module.
moduleOptionsContainer.removeAll();
moduleOptionsContainer.add(new Ext.form.DisplayField({
fieldLabel: 'Module Name',
value: moduleInfo.name
}))
moduleOptionsContainer.add(new Ext.form.DisplayField({
fieldLabel: 'Module Author',
value: moduleInfo.author ? moduleInfo.author : 'anonymous'
}))
for (let i = 0; i < moduleInfo.options.length; i++) {
const inputField = generate_form_input_field(
moduleOptionsContainer,
moduleInfo.options[i],
moduleData.options[moduleInfo.options[i].name],
false,
{session: `${moduleInfo.name}-module-${index}-field-${i}`}
);
// Ensure any changes to the element are reflected in the newRule object.
// When the user saves the rule the whole newRule object will be saved,
// including any changes made to these input fields.
inputField.on('change', function(_inputF, newValue, oldValue) {
moduleData.options[moduleInfo.options[i].name] = newValue;
});
};
moduleOptionsContainer.doLayout();
}
const buttonContainer = new Ext.Container({
layout: {
type: 'hbox',
pack: 'end',
},
items: [
{
xtype: 'button',
text: 'Delete',
handler: deleteFn,
},{
xtype: 'button',
text: 'Move Forward',
handler: moveUp,
disabled: moveUp == undefined,
},{
xtype: 'button',
text: 'Move Back',
handler: moveDown,
disabled: moveDown == undefined,
}
]
});
AutoRunModuleForm.superclass.constructor.call(this, {
items: [
moduleOptionsContainer,
buttonContainer
]
});
};
Ext.extend(AutoRunModuleForm, Ext.Container, {});

View File

@@ -0,0 +1,181 @@
const areNotificationUpdateTest = {
"name": "Display an alert-----",
"author": "mgeeky",
"modules": [
{
"name": "alert_dialog",
"condition": null,
"options": {
"text":"You've been BeEFed ;>"
}
}
],
"execution_order": [0],
"execution_delay": [0],
"chain_mode": "nested-forward"
};
/**
* Form for the user to read, update and delete a specific Auto Run rule.
*
* rule: The object definition of this rule from the Auto Run Engine.
* modules: The list of all commands/modules that the user can choose from.
* deleteFn: callback function to delete this rule.
* updateFn: callback function to update this rule.
*/
AutoRunRuleForm = function(rule, modules, deleteFn, updateFn) {
const self = this;
const ruleTextFieldId = `rule-name-${rule.id}`;
const chainModeComboId = `rule-chain-mode-${rule.id}`;
const newRule = JSON.parse(JSON.stringify(rule));
newRule.modules = JSON.parse(newRule['modules']);
newRule.execution_delay = JSON.parse(newRule['execution_delay']);
newRule.execution_order = JSON.parse(newRule['execution_order']);
const moduleContainer = new Ext.Container({
style: {
padding: '10 10 10 10',
},
listeners: {
afterrender: setupModuleForms
}
});
function reorderModule(index, direction) {
// Rearrange modules into new order.
const currentModule = newRule.modules[index];
const newIndex = direction === 'back' ? index + 1 : index - 1;
newRule.modules.splice(index, 1);
newRule.modules.splice(newIndex, 0, currentModule);
// Update DOM.
setupModuleForms();
moduleContainer.doLayout();
}
function removeModule(index) {
// Remove element from execution_order and execution_delay arrays.
newRule.modules.splice(index, 1);
newRule.execution_delay.splice(index, 1);
// Update DOM.
setupModuleForms();
moduleContainer.doLayout();
}
function addModule() {
// New module is a copy of the last module.
newRule.modules.push(newRule.modules[newRule.modules.length - 1]);
newRule.execution_delay.push(newRule.execution_delay[newRule.execution_delay.length - 1]);
// Update DOM.
setupModuleForms();
moduleContainer.doLayout();
}
function setupModuleForms() {
moduleContainer.removeAll(true);
// I think execution order should always be sequential.
// The actual order comes from the modules array.
for (let i = 0; i < newRule.modules.length; ++i) {
const isFirstModule = i === 0;
const isLastModule = i >= newRule.modules.length - 1;
moduleContainer.add(new AutoRunModuleForm(
newRule.modules[i],
function() {removeModule(i)},
isFirstModule ? undefined : function() {reorderModule(i, 'forward')},
isLastModule ? undefined : function() {reorderModule(i, 'back')},
rule.id,
i,
modules
));
}
}
function handleUpdateRule() {
// TODO: Need to overwrite module order.
const form = self.getForm();
const formValues = form.getValues();
const updatedRule = {
...newRule,
name: formValues[ruleTextFieldId],
chain_mode: formValues[chainModeComboId],
execution_order: [...Array(newRule.modules.length).keys()],
};
updateFn(updatedRule);
}
AutoRunRuleForm.superclass.constructor.call(this, {
padding:'10 10 10 10',
title: `Rule ${rule.id}`,
items: [{
xtype: 'textfield',
id: ruleTextFieldId,
value: rule.name ? rule.name : '',
fieldLabel: 'Name',
},
{
xtype: 'displayfield',
fieldLabel: 'Author',
value: rule.author ? rule.author : 'anonymous',
},{
xtype: 'displayfield',
fieldLabel: 'Browser(s)',
value: rule.browser ? rule.browser : 'All',
},{
xtype: 'displayfield',
fieldLabel: 'Browser version(s)',
value: rule.browser_version ? rule.browser_version : 'All',
},{
xtype: 'displayfield',
fieldLabel: 'OS(s)',
value: rule.os ? rule.os : 'All',
},{
xtype: 'displayfield',
fieldLabel: 'OS version(s)',
value: rule.os_version ? rule.os_version : 'All',
},
moduleContainer,
{
xtype: 'button',
text: 'Add Module',
handler: addModule
},
{
xtype: 'combo',
id: chainModeComboId,
fieldLabel: 'Chain Mode',
store: ['sequential', 'nested-forward'],
queryMode: 'local', // Use local data.
triggerAction: 'all', // Show both options instead of just the default.
editable: false, // Disable manual text input.
forceSelection: true,
value: rule.chain_mode ? rule.chain_mode : 'sequential'
},{
xtype: 'displayfield',
fieldLabel: 'Execution Order',
value: newRule.execution_order ?
JSON.stringify(newRule.execution_order)
: 'undefined',
},{
xtype: 'displayfield',
fieldLabel: 'Execution Delay',
value: newRule.execution_delay ?
JSON.stringify(newRule.execution_delay)
: 'undefined',
}
],
buttons: [{
text: 'Delete',
handler: deleteFn
}, {
text: 'Save',
handler: handleUpdateRule
}],
border: false,
closable: false
});
};
Ext.extend(AutoRunRuleForm, Ext.FormPanel, {});

View File

@@ -0,0 +1,185 @@
const defaultRule = {
"name": "Display an alert",
"author": "mgeeky",
"modules": [
{
"name": "alert_dialog",
"condition": null,
"options": {
"text":"You've been BeEFed ;>"
}
}
],
"execution_order": [0],
"execution_delay": [0],
"chain_mode": "nested-forward"
};
/**
* Asynchronously returns the currently active rules in an array.
* Empty array means no rules are active.
* null if there was an error.
*/
getCurrentRules = async function(token) {
try {
var res = await fetch(`/api/autorun/rules?token=${token}`);
if (!res.ok) {
throw new Error(`Getting auto run rules failed with status ${res.status}`);
}
const data = await res.json();
const rules = JSON.parse(data.rules);
if (data.success === true && Array.isArray(rules)) {
return rules;
}
console.log("No active auto run rules.");
return [];
} catch(error) {
console.error(error);
console.error("Failed to get auto run rules.");
return null;
}
}
getModules = async function(token) {
try {
var res = await fetch(`/api/modules?token=${token}`);
if (!res.ok) {
throw new Error(`Getting auto run rules failed with status ${res.status}`);
}
const modules = await res.json();
return modules;
} catch(error) {
console.error(error);
console.error("Failed to get auto run rules.");
return null;
}
}
AutoRunTab = function() {
// RESTful API token.
var token = BeefWUI.get_rest_token();
// Heading container to describe general Auto Run state.
var ruleLoadingState = new Ext.Container({
html: "<p>Loading Auto Run rules...</p>",
})
var headingContainer = new Ext.Panel({
style: {
font: '11px tahoma,arial,helvetica,sans-serif'
},
padding:'10 10 10 10',
border: false,
items: [{
xtype: 'container',
html: '\
<div>\
<h4>Auto Run Rules</h4>\
<p>These determine what commands run automatically when a browser is hooked.</p>\
</div>'
},
ruleLoadingState,
{
xtype: 'button',
text: 'Add New Rule',
handler: addRule
}],
listeners: {
afterrender: loadRules
}
});
// Contains all of the rules and inputs to change each rule.
var ruleContainer = new Ext.Panel({
border: false,
listeners: {
afterrender: loadRules
}
});
async function deleteRule(id) {
const res = await fetch(`/api/autorun/rule/${id}?token=${token}`, {method: 'DELETE'});
if (!res.ok) {
console.error(`Failed when deleting rule with id ${id}. Failed with status ${res.status}.`);
return;
}
// Update the entire rules panel. Not very efficient.
loadRules();
}
async function addRule() {
const res = await fetch(`/api/autorun/rule/add?token=${token}`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(defaultRule)
});
if (!res.ok) {
console.error(`Failed when adding a new rule with status ${res.status}.`);
return;
}
// Update the entire rules panel. Not very efficient.
loadRules();
}
async function updateRule(id, newRuleData) {
const res = await fetch(`/api/autorun/rule/${id}?token=${token}`, {
method: 'PATCH',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(newRuleData)
});
if (!res.ok) {
console.error(`Failed when adding a new rule with status ${res.status}.`);
return;
}
// Update the entire rules panel. Not very efficient.
loadRules();
}
async function loadRules() {
let modules = [];
let rules = [];
try {
modules = await getModules(token);
rules = await getCurrentRules(token);
} catch (error) {
console.error(error);
console.error("Failed to load command modules and/or rules for Auto Run.");
ruleLoadingState.update("<p>Failed to load Auto Run rules.</p>");
return;
}
if (rules !== null) {
ruleLoadingState.update(`<p>Loaded ${rules.length} Auto Run rules.</p>`);
ruleContainer.removeAll();
for (let i = 0; i < rules.length; i++) {
ruleForm = new AutoRunRuleForm(
rules[i],
modules,
function() {deleteRule(rules[i].id)},
function(newRuleData) {updateRule(rules[i].id, newRuleData)}
);
ruleContainer.add(ruleForm);
}
ruleContainer.doLayout();
} else {
ruleLoadingState.update("<p>Failed to load Auto Run rules.</p>");
}
}
AutoRunTab.superclass.constructor.call(this, {
region: 'center',
items: [headingContainer, ruleContainer],
autoScroll: true,
border: false,
closable: false
});
};
Ext.extend(AutoRunTab, Ext.Panel, {});

View File

@@ -38,6 +38,8 @@ MainPanel = function(){
this.welcome_tab = new WelcomeTab;
this.auto_run_tab = new AutoRunTab;
MainPanel.superclass.constructor.call(this, {
id:'main-tabs',
activeTab:0,
@@ -76,6 +78,15 @@ MainPanel = function(){
items:[
this.zombies_grid
]
},
{
id:'autorun-view',
title:'Auto Run',
layout:'border',
hideMode:'offsets',
items:[
this.auto_run_tab
]
}]
});

View File

@@ -102,9 +102,9 @@ function generate_form_input_field(form, input, value, disabled, zombie) {
if(value) input_field.setValue(value);
if(disabled) input_field.setDisabled(true);
form.add(input_field);
return input_field;
};
function get_dynamic_payload_details(payload, zombie) {
@@ -357,13 +357,13 @@ function genNewExploitPanel(panel, command_module_id, command_module_name, zombi
fieldLabel: 'Description',
fieldClass: 'command-module-panel-description',
value: module.Description
}),
new Ext.form.DisplayField({
name: 'command_module_id_visible',
fieldLabel: 'Id',
fieldClass: 'command-module-panel-description',
value: command_module_id
})
}),
new Ext.form.DisplayField({
name: 'command_module_id_visible',
fieldLabel: 'Id',
fieldClass: 'command-module-panel-description',
value: command_module_id
})
],
buttons:[{
@@ -424,4 +424,4 @@ function genNewExploitPanel(panel, command_module_id, command_module_name, zombi
}
});
}
};
};