require 'support/shared_examples_for_issue_checks'
RSpec.describe Pluginscan::FileIssuesScanner do
##################
# MATCH EXAMPLES #
##################
# Most of the following examples come from real pluginscan test runs and
# should be considered regression specs: things which we shouldn't fail to match in future!
describe "SUPERGLOBAL EXAMPLES" do
it_behaves_like "matches a variable assigned to a superglobal", "$_GET"
it_behaves_like "matches a variable assigned to a superglobal", "$_POST"
it_behaves_like "matches a variable assigned to a superglobal", "$_SERVER"
it_behaves_like "matches a variable assigned to a superglobal", "$_REQUEST"
it_behaves_like "matches a variable assigned to a superglobal", "$_COOKIE"
it_behaves_like "matches a variable assigned to a superglobal", "$_ENV"
it_behaves_like "matches a variable assigned to a superglobal", "$_FILES"
# Should be looked at:
it_behaves_like "matches lines containing", "$_POST", %($contact_form->set_title( $_POST['wpcf7-title'] );)
it_behaves_like "matches lines containing", "$_POST", %($locale = trim( $_POST['wpcf7-locale'] );)
it_behaves_like "matches lines containing", "$_POST", %($properties['form'] = trim( $_POST['wpcf7-form'] );)
it_behaves_like "matches lines containing", "$_POST", %($id = $_POST['post_ID'];)
it_behaves_like "matches lines containing", "$_REQUEST", %(return $_REQUEST['action'];)
it_behaves_like "matches lines containing", "$_POST", %($submitted = isset( $_POST[$tagname] ) ? $_POST[$tagname] : '';)
it_behaves_like "matches lines containing", "$_POST", %($value = $_POST[$fe['name']];)
it_behaves_like "matches lines containing", "$_REQUEST", %($replaced_login = str_replace('%user_login%', $_REQUEST['user_login'], $email);)
it_behaves_like "matches lines containing", "$_REQUEST", %($replaced_all = str_replace('%pass1%', $_REQUEST['pass1'], $replaced_login);)
it_behaves_like "matches lines containing", "$_SERVER", %($redirect_to = isset( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : admin_url( 'edit-comments.php' );)
it_behaves_like "matches lines containing", "$_SERVER", %(return isset( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : null;)
it_behaves_like "matches lines containing", "$_REQUEST", %($this->items = MLAMime::mla_query_view_items( $_REQUEST, ( ( $current_page - 1 ) * $per_page ), $per_page );)
it_behaves_like "matches lines containing", "$_REQUEST", %q(echo ' - search results for "' . esc_html( stripslashes( trim( $_REQUEST['s'] ) ) ) . "\"\r\n";)
it_behaves_like "matches lines containing", "$_GET", %($_GET['order'] = $_REQUEST['order'];)
it_behaves_like "matches lines containing", "$_REQUEST", %($_GET['order'] = $_REQUEST['order'];), check_index: 0, match_index: 1
it_behaves_like "matches lines containing", "$_SERVER", %()
it_behaves_like "matches lines containing", "$_POST", %()
# Should be looked at - but look similar to safe infixes:
it_behaves_like "matches lines containing", "$_POST", %('name' => $_POST['EventBriteTicketName'],)
it_behaves_like "matches lines containing", "$_POST", %('cost' => $_POST['EventBriteEventCost'] * 100,)
# Double check the infixes: there's a risk of false negatives due to double-counting infixes (e.g. == and ===):
# NOT SEEN IN THE WILD:
it_behaves_like "matches lines containing", "$_GET", %($x = $_GET['message'] <= 1; $y = $_GET['message'] )
it_behaves_like "matches lines containing", "$_GET", %($x = $_GET['message'] < 1; $y = $_GET['message'] )
it_behaves_like "matches lines containing", "$_GET", %($x = $_GET['message'] > 1; $y = $_GET['message'] )
it_behaves_like "matches lines containing", "$_GET", %($x = $_GET['message'] >= 1; $y = $_GET['message'] )
it_behaves_like "matches lines containing", "$_GET", %($x = $_GET['message'] === 1; $y = $_GET['message'] )
it_behaves_like "matches lines containing", "$_GET", %($x = $_GET['message'] !== 1; $y = $_GET['message'] )
it_behaves_like "matches lines containing", "$_GET", %($x = $_GET['message'] == 1; $y = $_GET['message'] )
it_behaves_like "matches lines containing", "$_GET", %($x = $_GET['message'] != 1; $y = $_GET['message'] )
# Matches because the value ends up in $danger, even though it's used in a check
it_behaves_like "matches lines containing", "$_POST", %(if( ($danger = $_POST['post_type']) == 'page') \{)
# Requires looking at surrounding lines:
it_behaves_like "matches lines containing", "$_POST", %{$_POST['wpcf7-mail-2-additional-headers'] );}
# IGNORE:
#########
# Ignored because value is checked but not used:
it_behaves_like "ignores lines containing", "$_POST", %(if ( isset( $_POST['wpcf7-title'] ) ) \{)
it_behaves_like "ignores lines containing", "$_POST", %(if ( ! empty( $_POST['post_ID'] ) ))
it_behaves_like "ignores lines containing", "$_POST", %($id = empty( $_POST['post_ID'] ))
it_behaves_like "ignores lines containing", "$_POST", %($mail['use_html'] = ! empty( $_POST['wpcf7-mail-use-html'] );)
it_behaves_like "ignores lines containing", "$_REQUEST", %(if ( 'created' == $_REQUEST['message'] ))
it_behaves_like "ignores lines containing", "$_SERVER", %(if ( 'POST' == $_SERVER['REQUEST_METHOD'] ) \{)
it_behaves_like "ignores lines containing", "$_POST", %('message' => ( -1 == $_POST['post_ID'] ) ? 'created' : 'saved',)
it_behaves_like "ignores lines containing", "$_SERVER", %(return $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';)
it_behaves_like "ignores lines containing", "$_GET", %(if ( ! ( isset( $_GET['recheckqueue'] ) || ( isset( $_REQUEST['action'] ) && 'akismet_recheck_queue' == $_REQUEST['action'] ) ) ))
it_behaves_like "ignores lines containing", "$_REQUEST", %(if ( isset( $_REQUEST[ $value['value'] ] ) ))
it_behaves_like "ignores lines containing", "$_SERVER", %(&& strpos( $_SERVER['REQUEST_URI'], 'wp-admin/update.php' ) !== false)
it_behaves_like "ignores lines containing", "$_GET", %(if ($_GET['s'])\{)
it_behaves_like "ignores lines containing", "$_POST", %(if ( is_email( $_POST['id_emailaddress'] ) ) \{)
it_behaves_like "ignores lines containing", "$_GET", %(if ( intval($_GET['message']) == 1 )\{)
it_behaves_like "ignores lines containing", "$_POST", %(if ( !wp_verify_nonce( $_POST['_wpnonce'], self::NONCE ) ))
it_behaves_like "ignores lines containing", "$_POST", %($comment = get_comment( intval( $_POST['id'] ), ARRAY_A );)
it_behaves_like "ignores lines containing", "$_REQUEST", %(if (wp_verify_nonce($_REQUEST['nonce'], $this->hook . '_ajax-nonce')) \{)
it_behaves_like "ignores lines containing", "$_REQUEST", %(return wp_verify_nonce($_REQUEST['nonce'], $this->plugin_slug . '_foolic-ajax-nonce');)
it_behaves_like "ignores lines containing", "$_POST", %(if ( ! wp_verify_nonce( $_POST['wp_nonce'], 'BWPS_admin_save' ) || ( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE ) || ( $_POST['post_type'] == 'page' && ! current_user_can( 'edit_page', $id ) ) || ( $_POST['post_type'] == 'post' && ! current_user_can( 'edit_post', $id ) ) ) \{)
it_behaves_like "ignores lines containing", "$_POST", %(: absint( $_POST['post_ID'] );)
it_behaves_like "ignores lines containing", "$_REQUEST", %(? absint( $_REQUEST['post'] ))
it_behaves_like "ignores lines containing", "$_REQUEST", %(unset( $_REQUEST['heading_suffix'] );)
it_behaves_like "ignores lines containing", "$_GET", %(switch ( $_GET['page'] ) )
it_behaves_like "ignores lines containing", "$_GET", %{$x = $_GET[eval($danger)] == 1 }
# however:
it_behaves_like "matches lines containing", "eval", %($x = $_GET[eval($danger)] == 1 ), check_index: 1, match_index: 0
# Ignored because some infixes make superglobals safe:
it_behaves_like "ignores lines containing", "$_GET", %($x = $_GET['message'] <= 1 )
it_behaves_like "ignores lines containing", "$_GET", %($x = $_GET['message'] < 1 )
it_behaves_like "ignores lines containing", "$_GET", %($x = $_GET['message'] > 1 )
it_behaves_like "ignores lines containing", "$_GET", %($x = $_GET['message'] >= 1 )
it_behaves_like "ignores lines containing", "$_GET", %($x = $_GET['message'] === 1 )
it_behaves_like "ignores lines containing", "$_GET", %($x = $_GET['message'] !== 1 )
it_behaves_like "ignores lines containing", "$_GET", %($x = $_GET['message'] == 1 )
it_behaves_like "ignores lines containing", "$_GET", %($x = $_GET['message'] != 1 )
it_behaves_like "ignores lines containing", "$_GET", %($x = $_GET['message']<=1 )
it_behaves_like "ignores lines containing", "$_GET", %($x = $_GET['message']<1 )
it_behaves_like "ignores lines containing", "$_GET", %($x = $_GET['message']>1 )
it_behaves_like "ignores lines containing", "$_GET", %($x = $_GET['message']>=1 )
it_behaves_like "ignores lines containing", "$_GET", %($x = $_GET['message']===1 )
it_behaves_like "ignores lines containing", "$_GET", %($x = $_GET['message']!==1 )
it_behaves_like "ignores lines containing", "$_GET", %($x = $_GET['message']==1 )
it_behaves_like "ignores lines containing", "$_GET", %($x = $_GET['message']!=1 )
# Ignored because two values are checked but neither is used:
it_behaves_like "ignores lines containing", "$_POST", %(if ( !empty( $_POST['id'] ) && !empty( $_POST['url'] ) && check_admin_referer( 'comment_author_url_nonce' ) ) \{)
it_behaves_like "ignores lines containing", "$_REQUEST", %(if ( ! ( isset( $_GET['recheckqueue'] ) || ( isset( $_REQUEST['action'] ) && 'akismet_recheck_queue' == $_REQUEST['action'] ) ) )), check_index: 0, match_index: 1
it_behaves_like "ignores lines containing", "$_POST", %(if ( isset( $_POST['action'] ) && $_POST['action'] == 'enter-key' ) \{)
it_behaves_like "ignores lines containing", "$_GET", %(if ( isset( $_GET['page'] ) && 'akismet-stats-display' == $_GET['page'] ) \{)
it_behaves_like "ignores lines containing", "$_GET", %(elseif ( isset( $_GET['view'] ) && $_GET['view'] == 'stats' ))
it_behaves_like "ignores lines containing", "$_POST", %(( isset( $_POST['comment_status'] ) && in_array( $_POST['comment_status'], array( 'spam', 'unspam' ) ) ))
it_behaves_like "ignores lines containing", "$_REQUEST", %(if ( isset( $_REQUEST['action'] ) && -1 != $_REQUEST['action'] ))
it_behaves_like "ignores lines containing", "$_REQUEST", %(if ( isset( $_REQUEST['m'] ) && ( '0' != $_REQUEST['m'] ) ))
# Ignored because multiple values are checked but none are used:
it_behaves_like "ignores lines containing", "$_POST", %(if ( isset( $_POST['action'] ) && ( $_POST['action'] == 'query-attachments' ) && isset( $_POST['query']['s'] ) && is_array( $_POST['query']['s'] ) )\{)
# Ignored because in a comment:
it_behaves_like "ignores lines containing", "$_REQUEST", %(// ignore anything else in $_REQUEST)
it_behaves_like "ignores lines containing", "$_POST", %(// Check group $_POST data)
it_behaves_like "ignores lines containing", "$_REQUEST", %(* Initializes some properties from $_REQUEST variables, then)
it_behaves_like "ignores lines containing", "$_REQUEST", %(* @param array query parameters from web page, usually found in $_REQUEST)
# SHOULD BE ignored because value is checked but not used:
it_behaves_like "matches lines containing", "$_GET", %(if ( isset( $_GET['token'] ) && preg_match('/^(\d+)-[0-9a-f]{20}$/', $_GET['token'] ) ))
# SHOULD BE ignored because multiple values are checked but none are used
it_behaves_like "matches lines containing", "$_REQUEST", %(if ( ! ( isset( $_REQUEST['cmb_ajax_nonce'], $_REQUEST['oembed_url'] ) && wp_verify_nonce( $_REQUEST['cmb_ajax_nonce'], 'ajax_nonce' ) ) ) )
# (should be ignored because in a comment. Actually ignored because in an empty)
it_behaves_like "ignores lines containing", "$_REQUEST", %{\} // (!empty($_REQUEST['mla_admin_action'])}
# SHOULD BE ignored because value is escaped?:
it_behaves_like "matches lines containing", "$_REQUEST", %()
it_behaves_like "matches lines containing", "$_REQUEST", %{. '', esc_html( $_REQUEST['s'] ) );}
# SHOULD BE ignored because value is sanitized? (Probably not?):
it_behaves_like "matches lines containing", "$_REQUEST", %($oembed_string = sanitize_text_field( $_REQUEST['oembed_url'] );)
end
describe "DATABASE ACCESS EXAMPLES" do
it_behaves_like "ignores lines containing", "$wpdb", %(global $wpdb;)
it_behaves_like "ignores lines containing", "$wpdb", %($q = "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '_old_cf7_unit_id'")
it_behaves_like "matches lines containing", "$wpdb", %($db = new db_subscribe($wpdb);)
it_behaves_like "matches lines containing", "$wpdb", %(strangefunc($wpdb);)
it_behaves_like "matches lines containing", "$wpdb", %($foo = $wpdb;)
it_behaves_like "matches lines containing", "$wpdb", %(. $wpdb->prepare( " AND meta_value = %d", $old_id );)
it_behaves_like "matches lines containing", "$wpdb", %(if ( $new_id = $wpdb->get_var( $q ) ))
it_behaves_like "matches lines containing", "$wpdb", %($table_name = $wpdb->prefix . "contact_form_7";)
it_behaves_like "matches lines containing", "$wpdb", %($wpdb->query( "DROP TABLE IF EXISTS $table_name" );)
it_behaves_like "matches lines containing", "$wpdb", %($key = $wpdb->get_var($wpdb->prepare("SELECT activation_key FROM {$wpdb->signups} WHERE user_login = %s AND user_email = %s", $new_user_login, $_REQUEST['email']));), check_index: 1
end
describe "MYSQL EXAMPLES" do
# Let's find some real ones!!!
# Not seen in the wild:
it_behaves_like "matches lines containing", "mysql_foo", %(mysql_foo($evil);)
it_behaves_like "matches lines containing", "mysqli_foo", %(mysqli_foo($evil);)
end
describe "PHP CODE GENERATION FUNCTION EXAMPLES" do
it_behaves_like "matches lines containing", "create_function", %q{$pee = preg_replace_callback( '/<(script|style|textarea).*?<\/\\1>/s', create_function( '$matches', 'return str_replace("\n", "", $matches[0]);' ), $pee );}
it_behaves_like "matches lines containing", "create_function", %{$function = @create_function('', 'return ' . $value . ';' );}
end
describe "USER-CONTROLLABLE FUNCTION CALL EXAMPLES" do
it_behaves_like "matches lines containing", "call_user_func", %(return $m[1] . call_user_func( $func, $scanned_tag ) . $m[6];)
it_behaves_like "matches lines containing", "call_user_func", %(return is_array( $value ) ? array_map( 'sanitize_text_field', $value ) : call_user_func( 'sanitize_text_field', $value );)
end
describe "VARIABLE EVALUATION EXAMPLES" do
# Not seen in the wild:
it_behaves_like "matches lines containing", "parse_str", %(parse_str($_GET['meow']); eval($x);), check_index: 2
it_behaves_like "matches lines containing", "extract", %(extract($var_array, EXTR_PREFIX_SAME, "wddx");)
end
describe "SYSTEM CALLS:" do
# Not actually system commands (??) but potentially dodgy inclusions in SQL?
it_behaves_like "matches lines containing", "`post_content`", %(WHERE {$exclude_revisions}(CONVERT(`post_content` USING utf8 ) LIKE %s)", "%{$like}%")
it_behaves_like "matches lines containing", "`post_content`", %(CONVERT(`post_content` USING utf8 ))
# Ignored because in a comment
it_behaves_like "ignores lines containing", "`$saved`", %(// Add to `$saved` array)
it_behaves_like "ignores lines containing", "`if`", %(// TODO: should this `if` be in the constructor instead?)
end
describe "FILE OPERATIONS EXAMPLES" do
it_behaves_like "matches lines containing", "unlink", %(@unlink( $dir );)
it_behaves_like "matches lines containing", "unlink", %(@unlink( $dir . $file );)
it_behaves_like "matches lines containing", "chmod", %(@chmod( $new_file, 0400 );)
it_behaves_like "matches lines containing", "chmod", %(elseif ( ! is_writable( MLA_BACKUP_DIR ) && ! @chmod( MLA_BACKUP_DIR , '0777') ) \{)
it_behaves_like "matches lines containing", "move_uploaded_file", %(if ( false === @move_uploaded_file( $file['tmp_name'], $new_file ) ) \{)
it_behaves_like "matches lines containing", "file_put_contents", %(@file_put_contents( $htaccess_upload, $htaccess_content, LOCK_EX );)
it_behaves_like "matches lines containing", "file_put_contents", %(if ( @file_put_contents( $htaccess_upload, $file_rules ) === false )\{)
it_behaves_like "matches lines containing", "file_get_contents", %($settings = @file_get_contents( $filename, false );)
it_behaves_like "matches lines containing", "file_get_contents", %($object_content = file_get_contents( $file_name, true, NULL, $file_offset, $chunksize );)
it_behaves_like "matches lines containing", "rename", %(@rename( $current_datastore_path, $new_datastore_path );)
it_behaves_like "matches lines containing", "glob", %($files_found = glob( $directory_pattern );)
it_behaves_like "matches lines containing", "unlink", %($removed = @unlink( $filepath );)
it_behaves_like "matches lines containing", "file", %(return @file( $filepath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES );)
it_behaves_like "matches lines containing", "fread", %($chunk = fread( $file, $seek );)
it_behaves_like "matches lines containing", "fwrite", %(if(false === @fwrite($file_pointer, $settings)) \{)
# Note the use of %q here to get around the \n and \\ escaping:
it_behaves_like "matches lines containing", "fwrite", %q{fwrite( $handle, 'Order deny,allow' . "\n" );}
it_behaves_like "matches lines containing", "fwrite", %q{fwrite( $handle, '' . "\n" );}
it_behaves_like "matches lines containing", "fwrite", 'fwrite( $handle, "Deny from all\n" );'
it_behaves_like "matches lines containing", "fread", 'if ( fread( $file, 1 ) != "\n" ) { $lines -= 1; }'
# IGNORE:
#########
# ignored because not actually calling one of the dangerous functions
it_behaves_like "ignores lines containing", "copy", %(public function copy() \{)
# SHOULD BE ignored because not actually calling one of the dangerous functions
it_behaves_like "matches lines containing", "copy", %($new_contact_form = $contact_form->copy();)
it_behaves_like "matches lines containing", "file", %($page_content['message'] = "ERROR: reading the settings file ( {$filename} ){$php_errormsg}";)
end
describe "OBFUSCATION EXAMPLES" do
it_behaves_like "matches lines containing", "base64_decode", %($data = base64_decode( $HTTP_RAW_POST_DATA );)
end
describe "NETWORK FUNCTION EXAMPLES" do
it_behaves_like "matches lines containing", "wp_remote_get", %($response = wp_remote_get( $url, $req_args );)
it_behaves_like "matches lines containing", "wp_remote_post", %($response = wp_remote_post( $url, $req_args );)
it_behaves_like "matches lines containing", "wp_remote_head", %($resp = wp_remote_head( $possible_repository );)
end
describe "UNSAFE FUNCTION EXAMPLES" do
it_behaves_like "matches lines containing", "wp_unslash", %($new_value[] = $pipes->do_pipe( wp_unslash( $v ) );)
it_behaves_like "matches lines containing", "wp_unslash", %($value = $pipes->do_pipe( wp_unslash( $value ) );)
it_behaves_like "matches lines containing", "wp_unslash", %{$free_text_atts['value'] = wp_unslash(}
it_behaves_like "matches lines containing", "wp_unslash", %($answer = wp_unslash( $answer );)
it_behaves_like "matches lines containing", "wp_unslash", %($replaced = wp_unslash( trim( $replaced ) );)
it_behaves_like "matches lines containing", "htmlspecialchars", %($text = htmlspecialchars( $text );)
it_behaves_like "matches lines containing", "wp_unslash", %($new_user_login = apply_filters('pre_user_login', sanitize_user(wp_unslash($_REQUEST['user_login']), true));), check_index: 1
it_behaves_like "matches lines containing", "wp_unslash", %(return isset( $_POST[$name] ) ? wp_unslash( $_POST[$name] ) : $default;), check_index: 1
it_behaves_like "matches lines containing", "wp_unslash", %q{? trim( wp_unslash( strtr( (string) $_POST[$name], "\n", " " ) ) )}, check_index: 1
end
describe "PHP OBJECT INJECTION EXAMPLES" do
# Let's find some
end
describe "REDUNDANT FUNCTION EXAMPLES" do
# Should be looked at?
it_behaves_like "matches lines containing", "mail", %($mail = mail($_REQUEST['email'], 'Login details', $replaced_all, $headers);), check_index: 1
# Ignored because they're defining mail functions - not using a built-in function(?):
it_behaves_like "ignores lines containing", "mail", %(private function mail() \{)
it_behaves_like "ignores lines containing", "mail", %(public static function mail() \{)
# SHOULD BE ignored because they're not actually calling top-level mail functions (?):
it_behaves_like "matches lines containing", "mail", %{'use' => __( 'Use mail (2)', 'contact-form-7' ) ) );}
it_behaves_like "matches lines containing", "mail", %($template = self::mail();)
it_behaves_like "matches lines containing", "mail", %(\} elseif ( $this->mail() ))
end
describe "BATSHIT WIERD EXAMPLES" do
it_behaves_like "matches lines containing", "ini_set", %(ini_set( 'display_errors', 'on' );)
end
describe "ACCIDENTS EXAMPLES" do
it_behaves_like "matches lines containing", "security", %(// Add some security, no direct load !)
it_behaves_like "matches lines containing", "todo", %(// @todo more hardening?)
it_behaves_like "matches lines containing", "hardening", %(// @todo more hardening?), check_index: 0, match_index: 1
it_behaves_like "matches lines containing", "TODO", %(__( 'Start', JM_TC_TEXTDOMAIN ) => '8l4k3zrD4Z0', /*TODO : redo tutorial (shorter)*/)
# Don't actually represent anything bad, but probably no way we can automatically ignore?
it_behaves_like "matches lines containing", "broken", %(return esc_attr( substr( $the_excerpt, 0, 200 ) ); // to prevent meta from being broken by e.g "")
it_behaves_like "matches lines containing", "broken", %($weight = '' . __( 'Image is heavier than 1MB ! Card will be broken !', JM_TC_TEXTDOMAIN ) . '';)
end
describe "CONTENT SECURITY EXAMPLES", "Inline JavaScript" do
# Mostly seen in the wild (but not in WP plugin code):
it_behaves_like "matches lines containing", ")
it_behaves_like "ignores lines containing", ")
it_behaves_like "ignores lines containing", ")
# Pathalogical examples:
it_behaves_like "matches lines containing", ")
it_behaves_like "ignores lines containing", ")
it_behaves_like "ignores lines containing", ")
it_behaves_like "matches lines containing", "