Skip to content

Plugin development guide

nil0x42 edited this page Sep 16, 2020 · 7 revisions

๐Ÿ“– Coding style

If you plan to write a plugin for phpsploit, please observe the following guidelines and conventions.

๐Ÿ”ธ Respect the PEP8 convention while writing python

It eases code visibility for other users, and maintains coherence with the framework's core (which already observes this coding style).

๐Ÿ”ธ Prefer a small PHP payload, and a bigger python code

This way, the plugin maximises its chances to be correctly executed in a single request, even with drastical server limitations. Keep in mind that the python plugin code (plugin.py) is executed from attacker side, while the php payload (*.php) is dynamically loaded in HTTP requests, and executed on target server.

๐Ÿญ Plugin structure

๐Ÿ”ธ Plugin location

plugins are stored in ./plugins/<CATEGORY>/<NAME>/.

๐Ÿ”ธ plugin.py (Executed on attacker machine)

This file is mandatory. It has 2 purposes:

  1. send PHP payload to remote server
  2. process raw results & return them to user
  • ๐Ÿ“œ plugin help docstring:
    Phpsploit generates help <PLUGIN> output from the docstring present at the start of this file

๐Ÿ”ธ PHP payload (Executed on remote target)

It's commonly named payload.php, but a plugin can have multiple PHP payloads (like edit plugin), and it can also have no payload (like whoami plugin).

  • โ“ !import(<LIB>) syntax:
    non-standard notation, includes contents of ./src/api/php-functions/<LIB>.php at runtime

  • ๐Ÿšฉ be retrocompatible:
    phpsploit sould be able to work on old versions of PHP (at least 4.3.0), until latest version. Keep it in mind while using PHP standard functions

๐Ÿ”ง Plugin API

The API documentation is available directly from phpsploit, like a standard python library.

To get started, run a python console from inside phpsploit, and browse the api's python docstrings:

phpsploit > # run python console from phpsploit
phpsploit > corectl python-console 

Phpsploit corectl: python console interpreter

>>> # browse api docstrings & objects from python console
>>> import api
>>> print(api.plugin.name)
plugin_example
>>> help(api)
...

๐Ÿ’ก Tips & Tricks

๐Ÿ”ธ run a tiny, temporary webserver to test your plugin

phpsploit comes with a small script included. It starts a tiny pre-backdoored php server listening on localhost to test plugins:

$ ./utils/start_phpsploit_connected.sh
use the following command to connect phpsploit to server:
    ----------------------------------------
    ./dev/phpsploit/phpsploit -t 127.0.0.1:64956 -ie 'set BACKDOOR %%DEFAULT%%; set REQ_HEADER_PAYLOAD %%DEFAULT%%; exploit'
    ----------------------------------------
PHP 7.3.19-1~deb10u1 Development Server started at Tue Sep 15 22:24:30 2020
Listening on http://127.0.0.1:64956
Document root is /tmp/phpsploit-temp-server
Press Ctrl-C to quit.

After that, just run the command returned by the script in another terminal to have a phpsploit session in connected mode.

๐Ÿ”ธ make changes on plugin without restarting phpsploit

After making a change, you can run corectl reload-plugins to refresh plugins, so you don't need to restart phpsploit each time you make a change.

๐Ÿ”ธ make heavy use of corectl command

It contains multiple tools very useful to write a plug-in. I heavily recommend to run help corectl from phpsploit for further info.

๐Ÿ”ธ Increase verbosity

In phpsploit, verbose outputs (lines starting with [#]) are hidden by default. Therefore, they might be useful to get more informations for dev and debug.

Use set VERBOSITY True to enable it.

Example:

phpsploit > lrun pwd
/tmp
phpsploit > set VERBOSITY True
[#] CMD('set' 'VERBOSITY' 'True'): Returned 0
phpsploit > lrun pwd
[#] CMD('lrun' 'pwd'): Running...
/tmp
[#] CMD('lrun' 'pwd'): Returned 0
phpsploit >

Example (rmdir plugin):

๐Ÿ”ธ plugin.py

"""Remove empty directory
SYNOPSIS:
    rmdir <REMOTE-DIRECTORY>
DESCRIPTION:
    Remove REMOTE-DIRECTORY if it is empty.
AUTHOR:
    nil0x42 <http://goo.gl/kb2wf>
"""
import sys

from api import plugin
from api import server

if len(plugin.argv) != 2:
    sys.exit(plugin.help)

rel_path = plugin.argv[1]
abs_path = server.path.abspath(rel_path)

payload = server.payload.Payload("payload.php") # prepare a payload request
payload["DIR"] = abs_path # this var will be added to PHP's $PHPSPLOIT variable
payload.send() # send payload

๐Ÿ”ธ payload.php

!import(dirAccess) // include content of ./src/api/php-functions/dirAccess.php
$dir = $PHPSPLOIT["DIR"]; // get variable given by plugin.py

// return error() if failed. otherwise, return anything else
if (!@file_exists($dir))
    return error("failed to remove '%s': No such file or directory", $dir);
if ((@fileperms($dir) & 0x4000) != 0x4000)
    return error("failed to remove '%s': Not a directory", $dir);

if (@rmdir($dir) === FALSE) {
    if (dirAccess($dir, 'r'))
        return error("failed to remove '%s': Directory not empty", $dir);
    return error("failed to remove '%s': Permission denied", $dir);
}