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);
}