PenPal is an automation and reporting all-in-one tool that is meant to enable Cybersecurity Engineers to perform a better, more thorough job and produce better quality reports by automating many of the most tedious tasks in penetration testing and/or red teaming. It is built on a pluggable architecture that can allow for many tools to be integrated seamlessly into the structured, opinionated database scheme. This allows for a consistent approach to targeting that can enable trigger-based automations to perform actions when a condition occurs or on-demand.
- Core API for data standardization (Plugin)
- Customers (can have many projects)
- Projects
- Hosts
- Networks (have many hosts)
- Services (ports, etc)
- Vulnerabilities
- Credentials
- Files
- Notes
- Audit trails
- User Interface
- Pluggable Dashboard
- Projects Summary Page
- Project Details Page
- Notetaking
- .... other things and stuff
- DataStore abstraction layer
- DataStore Adapters
- Mongo Adapter
- Postgres Adapter (Plugin)
- Grepable Filesystem Adapter (Plugin)
- S3 Adapter
- MinIO (Plugin)
- Amazon S3 (Plugin)
- Docker support for plugins
- Report generation
- Ghostwriter (Plugin)
- Plugin agents system for distributing the various plugins for internal/external combo scans
- Tunneling
- Cross platform agent
- Data flow
- Agent selection based on nearby networks (for automations)
- Really anything from the core
- Ping sweep for IP range (host discovery -> add hosts via API)
- Nmap for service discovery for hosts or networks (host/service discovery -> add hosts/services via API)
- Rustscan for service discovery for hosts or networks (host/service discovery -> add hosts/services via API)
- httpx for scanning ports to see if they are a web service
- Burpsuite for vulnerability scanning
- Dirb/dirbuster/insert URL discovery here
- Gowitness for screenshots of websites
- Eyeballer for searching screenshots for interesting things
- Changeme for default password checking
PenPal is purely dependent on docker
and docker-compose
. It will definitely work on MacOS and maybe on Linux (does not currently support Windows)
Currently there are a number of services and endpoints that are interesting/useful. The current way to run it is by executing dev.sh
-- if you add more plugins to the Plugins folder they will automatically mount with the docker-compose
scripts and mount into the container. Here's a list of interesting URLs:
- Web UI - http://localhost:3000
- GraphQL Studio - http://localhost:3001/graphql
Below is documentation describing how plugins should be structured and what is required. Plugins are loaded live by the Vite (client) and Node (server) dynamically, so simply placing the plugin in the plugins/
folder will let you get started. Use the penpal-plugin-develop.py
python script to get a Template with a name put into the right place.
python3 penpal-plugin-develop.py --new-plugin --name MySuperCoolAwesomePlugin
Each plugin is required to have three server files: index.js
, manifest.json
, and plugin.js
. In general, the index.js
will register the plugin, the manifest.json
describes the plugin, and the the plugin.js
implements the plugin. The simplest possible plugin is shown in the snippets below:
File Structure:
plugins/
|-> Base/
|-> CoreAPI/
|-> YourPlugin/
| |-> install-dependencies.sh (optional shell script that will be automatically called if you need things like npm packages)
| |-> server/
| | |-> index.js
| | |-> manifest.json
| | |-> plugin.js
index.js
:
// The code below is used to register a plugin (at runtime), which will then be loaded
// once the main server finishes starting up.
// Overall PenPal coordinating server code
import PenPal from "@penpal/core";
// Plugin-specific info
import Plugin from "./plugin.js";
import Manifest from "./manifest.json";
// Register the plugin
PenPal.registerPlugin(Manifest, Plugin);
manifest.json
:
{
"name": "MyCoolPlugin",
"version": "0.1.0",
"dependsOn": ["AnotherPlugin@0.1.0"]
}
plugin.js
:
// This defines the custom server-side code being run by the plugin. It has GraphQL schemas and resolvers
// in order to interact with the plugged application
import { types, resolvers, loaders } from "./graphql";
const settings = {};
const MyCoolPlugin = {
loadPlugin() {
// Required
return {
graphql: {
// Optional
types, // Optional
resolvers, // Optional
loaders, // Optional
},
settings, // Optional
hooks: {
// Optional
settings: {}, // Optional
postload: () => null, // Optional
startup: () => null, // Optional
},
};
},
};
export default MyCoolPlugin;
PenPal
registerPlugin(manifest, plugin)
- this function registers the plugin with PenPal for it to be loaded. It takes two arguments:manifest
(required) - an object containing decriptive fields about the plugin, defined in theManifest
section belowplugin
(required) - an object containing fields that associate with the code of the plugin, defined in thePlugin
section below
Manifest
name
(required) - aString
that is a unique name for the pluginversion
(required) - aString
in semantic versioning formload
(optional) - aBoolean
that can be set tofalse
to disable and not load a plugin. Defaults to truedependsOn
(required) - a[String]
where eachString
is of the formname@version
for plugins. Your plugin will not load if any of the dependencies are missingrequiresImplementation
(optional) - aBoolean
specifying whether another plugin must implement this one in order to load. This is currently used by theDataStore
plugin, which defines a general API for interacting with data store plugins but does not actually implement one.implements
(optional) - aString
of the formname@version
that specifies if the plugin implements another plugins specification. For example,DataStoreMongoAdapter
implements theDataStore
specification.
Plugin
loadPlugin()
- This function takes no arguments and returns one object withtypes
,resolvers
,loaders
, andsettings
fields to define the schema and resolvers that can be used to interact with the plugin. The settings object contains all of the specific info that defines how the plugin queries will interact with the user interface and other server-side APIs (more on this in theSettings
section).
The hooks property that is returned from the loadPlugin
function allows you to pass in functions that can be called to validate and/or execute code when other plugins are loaded. The three hooks available are described below.
startup
- This function takes no arguments but is guaranteed to execute after all other plugins have been loaded and after all core services are running (databases, the GraphQL server, etc).
hooks: {
startup: () => null;
}
settings
- This hook takes an object where each key describes a section of the settings
object (described later) and the value is a function that is used to validate the settings in question. For example, the Docker
plugin uses this hook in Plugins/Docker/server/plugin.js to check other plugins' usage of the docker
field of the settings object.
hooks: {
settings: {
my_cool_settings_field: check_my_cool_settings_field;
}
}
postload
- This hook will fire after a plugin loads with a single argument of the plugin_name
. This can be used to take settings information and do something with it. For example, the DataStore
plugin uses this hook in Plugins/DataStore/server/plugin.js to fire a function that creates datastores for each plugin immediately after they are loaded. We do this after the plugin is loaded because we know all of its dependencies exist and before the startup hook in order to make sure that everything is ready for those hooks to fire.
hooks: {
postload: (plugin_name) => null;
}
The sections below enumerate the different settings available and what they do. Much of this is subject to change, so take the documentation with a grain of salt and look at examples for current functionality.
To utilize the automatic configuration page generator, utilize the following field in the settings object, which will allow PenPal to introspect your schema and generate a configuration editor
{
"configuration": {
"schema_root": "MyCoolPluginConfiguration",
"getter": "getMyCoolPluginConfiguration",
"setter": "setMyCoolPluginConfiguration"
}
}
This section of the settings object is used to automatically generate data stores (using the DataStore API). It can be used for actual PenPal data or just configuration information for your plugin. The datastores
field of the settings
object is an [Object]
where each Object
has a name
field. The name
is automatically prepended with your plugin name, so it is automatically namespaced. There is planned functionality for things like unique data stores for data types (S3 stores for files
, relational DB for data, etc), but that is not yet implemented.
{
"datastores": [
{
"name": "YourCollectionName"
}
]
}
This section of the settings object is used to automatically pull docker images (not yet implemented) or build provided docker files (implemented) at runtime. This is an easy way to make sure that your particular plugin is cross platform and can be executed regardless of where PenPal is running. See the Rustscan Plugin for an example.
The graphql
field of the loadPlugin
return value can have any of three fields: types
, resolvers
, and loaders
. These are automatically merged into the overall GraphQL schema to add API endpoints that are accessible on the /graphql
endpoint.