- Plugin must be a
structimplementing eitherServicePluginInterfaceorWebPluginInterface- The
Init()method is optional if extendingServicePluginBase
- The
- Plugin should embed
ServicePluginBase- And use its network facility whenever possible
- Plugin must respect the context it's provided as much as made possible by the driver
- Plugin should update the
*L9Eventpointer if it finds more software information - Plugin should update the
*L9Event.leakstruct with details about the leak - Plugin must set
hasLeakto true when a leak is found - Plugin must assume
optionscan be uninitialized andnil
Plugins are embedded in l9explore. Once you created a repository with your plugin, you can update l9explore's plugin map file and build a new version containing your plugin.
The --debug flag can be used to confirm the plugins are loading properly.
It might convenient to copy your plugin in l9explore source directory and work from there during initial development and debugging
Feel free to submit new plugins by PR to the l9explor repo, just update plugin_map.go, go.mod/go.sum and we'll review the addition!
An external plugin example is the NucleiPlugin at https://github.com/gboddin/l9-nuclei-plugin .
The following is a redis example :
package tcp
import (
"context"
"github.com/LeakIX/l9format"
"github.com/go-redis/redis/v8"
"log"
"net"
"strings"
)
type RedisOpenPlugin struct {
l9format.ServicePluginBase
}
func (RedisOpenPlugin) GetVersion() (int, int, int) {
return 0, 0, 1
}
func (RedisOpenPlugin) GetProtocols() []string {
return []string{"redis"}
}
func (RedisOpenPlugin) GetName() string {
return "RedisOpenPlugin"
}
func (RedisOpenPlugin) GetStage() string {
return "open"
}
func (plugin RedisOpenPlugin) Init() error {
// Do things once when the plugin loads
return nil
}
func (plugin RedisOpenPlugin) Run(ctx context.Context, event *l9format.L9Event, options map[string]string) bool {
client := redis.NewClient(&redis.Options{
Addr: net.JoinHostPort(event.Ip, event.Port),
Password: "", // no password set
DB: 0, // use default DB
Dialer: plugin.DialContext,
})
defer client.Close()
_, err := client.Ping(ctx).Result()
if err != nil {
log.Println("Redis PING failed, leaving early : ", err)
return false
}
redisInfo, err := client.Info(ctx).Result()
if err != nil {
log.Println("Redis INFO failed, leaving early : ", err)
return false
}
redisInfoDict := make(map[string]string)
redisInfo = strings.Replace(redisInfo, "\r", "", -1)
for _, line := range strings.Split(redisInfo, "\n") {
keyValuePair := strings.Split(line, ":")
if len(keyValuePair) == 2 {
redisInfoDict[keyValuePair[0]] = keyValuePair[1]
}
}
if _, found := redisInfoDict["redis_version"]; found {
event.Service.Software.OperatingSystem, _ = redisInfoDict["os"]
event.Service.Software.Name = "Redis"
event.Service.Software.Version, _ = redisInfoDict["redis_version"]
event.Leak.Severity = l9format.SEVERITY_MEDIUM
event.Summary = "Redis is open\n"
event.Leak.Type = "open_database"
event.Leak.Dataset.Rows = 1
return true
}
return false
}See l9plugins for more examples.