# Web - The Cyber Jawara International 2024

## Example Box

| Category          | Points | Author     |
| ----------------- | ------ | ---------- |
| Web; **whitebox** | 495    | **farisv** |

### Code Analysis

Here is the main code that provided

```python
from flask import Flask, abort, render_template, request, Response
from re import sub
from unidecode import unidecode
from urllib3.util import parse_url
import requests

app = Flask(__name__)

allowed_hostname = ["example.com"]
allowed_path = ["", "/"]
fallback = "http://example.com/"
cache = {}

def normalize(token):
    if token == None:
        token = ""
    return sub(r'\s+', '', unidecode(str(token)))

def filter_url(url):
    parsed_url = parse_url(url)
    scheme = normalize(parsed_url.scheme) # http
    host = normalize(parsed_url.host)
    path = normalize(parsed_url.path)
    filtered_url = url
    if not scheme.startswith('http'):
        filtered_url = fallback
    if not host in allowed_hostname:
        filtered_url = fallback
    if not path in allowed_path:
        filtered_url = fallback
    return normalize(filtered_url)

@app.route('/', methods=['GET', 'POST'])
def index():
    url = request.form.get('url', '')
    return render_template('index.html', url=url)

@app.route('/fetch_url')
def fetch_url():
    url = request.args.get('url')
    filtered_url = filter_url(url)
    print("request from: ", request.remote_addr)
    # print("cache now: ")
    try:
        if filtered_url in cache:
            response = cache[filtered_url]
        else:
            response = requests.get(filtered_url)
            cache[filtered_url] = response
        return Response(response.content,
                        status=response.status_code,
                        content_type=response.headers.get('Content-Type'))
    except requests.exceptions.RequestException as e:
        return f"Error fetching the URL: {e}", 500

@app.route('/flag')
def flag():
    if request.remote_addr != '127.0.0.1':
        abort(403)
    with open('/flag.txt', 'r') as flag:
        return flag.read()

if __name__ == '__main__':
    app.run(debug=False, host='0.0.0.0', port=20002)
    
```

Reviewing the code we know that, this must be related to ssrf but there is some url parsing filter that we should bypass. Although the code looks very simple but i found that its really tricky to bypass.

Our objective is to access path flag with 127.0.0.1 remote addres `http://127.0.01/flag`

<pre><code><strong>flag():
</strong>    if request.remote_addr != '127.0.0.1':
        abort(403)
    with open('/flag.txt', 'r') as flag:
        return flag.read()
</code></pre>

Reviewing the code the `normalize` function is to check if there is whitespace or any unicode in the url.&#x20;

`filter_url` is check for the whitelist.&#x20;

```
def filter_url(url):
    parsed_url = parse_url(url)
    scheme = normalize(parsed_url.scheme) # http
    host = normalize(parsed_url.host)
    path = normalize(parsed_url.path)
    filtered_url = url
    if not scheme.startswith('http'):
        filtered_url = fallback
    if not host in allowed_hostname:
        filtered_url = fallback
    if not path in allowed_path:
        filtered_url = fallback
    return normalize(filtered_url)
```

the challenge is we need to bypass  parse\_url  and fallback overwrite, to perform this there is a good reads that that i found for bypassing the parse\_url() in python.&#x20;

<https://www.blackhat.com/docs/us-17/thursday/us-17-Tsai-A-New-Era-Of-SSRF-Exploiting-URL-Parser-In-Trending-Programming-Languages.pdf>

<figure><img src="/files/lJAHizdpEoZGHScHB1yu" alt=""><figcaption></figcaption></figure>

Yepp we can use `@` to bypass the parse\_url,&#x20;

### Exploitation

Because there are some whitelist so we need to use it to perform SSRF

```
allowed_hostname = ["example.com"]
allowed_path = ["", "/"]
fallback = "http://example.com/"
cache = {}
```

I perform some test code to debug the website and see how the parsed works

<figure><img src="/files/MgI8GO2zp0zS47NH32Yi" alt=""><figcaption></figcaption></figure>

We can see we have finally bypass the parse\_url and fallback. but the `request python` still accepting our request as `example.com`

the problem is if we access the path flag manually like this `http://127.0.0.1/flag@example.com` the parse will  works again and our input will be only `example.com`.

So we need to use unicode because normalize function and we are using `?` for the reuqest to not accessing path @example.com.&#x20;

<figure><img src="/files/dI6ACVmj4tKizL5bS2n0" alt=""><figcaption></figcaption></figure>

So here is the full payload:

[`http://127.0.0.1:20002／ⓕⓛⓐⓖ？@example.com`](http://127.0.0.1:20002／ⓕⓛⓐⓖ？@example.com)

<figure><img src="/files/bO3sKNFHmMRvuDx7IqU4" alt=""><figcaption></figcaption></figure>

Flag: `CJ{enough_with_example.com_here_is_your_nice_Phl4g!}`

## Java Box

| Category               | Points | Author |
| ---------------------- | ------ | ------ |
| Java Box; **blackbox** | 499    | farisv |

In this challenge our team work together to solve the challenge. Thanks to @daffainfo who find the initial foothold of the challenge we can continue the challenge and manage to solve 1 hour before the CTF ends.

### Blackbox

We are given a service and there is no code provided so it should be blackbox challenge. There is only register and login to the dahsboard and nothing special with other feature of the service.

<figure><img src="/files/Kz8JchvxVIzPGrKdvWW7" alt=""><figcaption></figcaption></figure>

Our team found that we can see java stack trace in `assets` path. After that we started by doing some enumeration there.&#x20;

<figure><img src="/files/x3pWCsLxVHd0kk4ItqrZ" alt=""><figcaption></figcaption></figure>

If we're accessing assets the error given is `java.lang.StringIndexOutOfBoundsException` accessing  But if we are accessing something like this the error given is different, it look like failed to get some resouce in the server we consider that is trying to access file in the server.

<figure><img src="/files/VLjLnyoGke9KdbtfSMOV" alt=""><figcaption></figcaption></figure>

### Exploitation

After some time we find a good article expalaing about some new release CVEs&#x20;

<https://attackerkb.com/assessments/25397f72-670e-4ef4-a19b-2a3a55120d18?referrer=profile>

But even is simmiliar we didnt find the real objective and still struggling, then several time my team friend got something like this<br>

<figure><img src="/files/bjZfXhEIrq1yOieBEZHJ" alt=""><figcaption></figcaption></figure>

After that From here, i figue out why not trying to access the path traversal like in java folder as usual im asking chat gpt for that and yep we found 200 status. But we didnt manage to get Main Controller at first.

<figure><img src="/files/3G07Qgva1kHj12YqCjz1" alt=""><figcaption></figcaption></figure>

We forgot that is being compiled so its not .java but .class here is the code we find

`com.cyberjawara.chall.web.javabox.controller.MainController`

{% tabs %}
{% tab title="MainController.class" %}

```java
package com.cyberjawara.chall.web.javabox.controller;

import com.cyberjawara.chall.web.javabox.util.JwtUtil;
import io.jsonwebtoken.Claims;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class MainController {
   @GetMapping({"/"})
   public String index() {
      return "index";
   }

   @GetMapping({"/register"})
   public String registerPage() {
      return "register";
   }

   @PostMapping({"/register"})
   public String register(@RequestParam String username, @RequestParam String password, HttpServletResponse response) {
      if (username != null && password != null && username.length() > 3 && password.length() > 3) {
         String jwt = JwtUtil.generateToken(username, false);
         Cookie cookie = new Cookie("jwt", jwt);
         cookie.setHttpOnly(true);
         cookie.setPath("/");
         response.addCookie(cookie);
         return "redirect:/dashboard";
      } else {
         return "redirect:/register";
      }
   }

   @GetMapping({"/dashboard"})
   public String dashboard(@CookieValue(value = "jwt",defaultValue = "") String jwt, Model model) {
      try {
         Claims claims = JwtUtil.validateToken(jwt);
         String username = (String)claims.get("username", String.class);
         Boolean isAdmin = (Boolean)claims.get("isAdmin", Boolean.class);
         if (isAdmin) {
            String filePath = "/flag.txt";

            try {
               BufferedReader br = new BufferedReader(new FileReader(filePath));

               try {
                  String content = br.readLine();
                  model.addAttribute("flag", content);
               } catch (Throwable var11) {
                  try {
                     br.close();
                  } catch (Throwable var10) {
                     var11.addSuppressed(var10);
                  }

                  throw var11;
               }

               br.close();
            } catch (IOException var12) {
            }
         }

         model.addAttribute("username", username);
         model.addAttribute("isAdmin", isAdmin);
         return "dashboard";
      } catch (Exception var13) {
         return "redirect:/register";
      }
   }

   @GetMapping({"/logout"})
   public String logout(HttpServletResponse response) {
      Cookie jwtCookie = new Cookie("jwt", "");
      jwtCookie.setMaxAge(0);
      jwtCookie.setPath("/");
      jwtCookie.setHttpOnly(true);
      response.addCookie(jwtCookie);
      return "redirect:/";
   }

   @GetMapping({"/assets/**"})
   public ResponseEntity<byte[]> getAssetFile(HttpServletRequest request) throws Exception {
      String requestURI = request.getRequestURI();
      String resourcePath = "/assets/" + requestURI.substring("/assets/".length());

      try {
         InputStream inputStream = this.getClass().getResourceAsStream(resourcePath);

         ResponseEntity var8;
         try {
            if (inputStream == null || !hasExtension(resourcePath)) {
               throw new Exception("getResourceAsStream failed");
            }

            byte[] fileContent = inputStream.readAllBytes();
            String mimeType = Files.probeContentType(Path.of(resourcePath, new String[0]));
            if (mimeType == null) {
               if (resourcePath.endsWith(".css")) {
                  mimeType = "text/css";
               } else {
                  mimeType = "text/plain";
               }
            }

            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.parseMediaType(mimeType));
            var8 = new ResponseEntity(fileContent, headers, HttpStatus.OK);
         } catch (Throwable var10) {
            if (inputStream != null) {
               try {
                  inputStream.close();
               } catch (Throwable var9) {
                  var10.addSuppressed(var9);
               }
            }

            throw var10;
         }

         if (inputStream != null) {
            inputStream.close();
         }

         return var8;
      } catch (IOException var11) {
         return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body((Object)null);
      }
   }

   public static boolean hasExtension(String filename) {
      if (filename != null && !filename.isEmpty()) {
         if (filename.length() < 3) {
            return false;
         } else {
            int dotIndex = filename.lastIndexOf(46);
            return dotIndex > 0 && dotIndex < filename.length() - 2;
         }
      } else {
         return false;
      }
   }
}
```

{% endtab %}

{% tab title="dashboard.html" %}

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Dashboard</title>
  <link rel="stylesheet" href="/assets/style.css">
</head>
<body>
  <div class="container">
    <h2>Welcome, <span th:text="${username}"></span>!</h2>
    <p>You are logged in as: <span th:text="${isAdmin} ? 'Admin (' + ${flag} + ')' : 'User'"></span></p>
    <img src="/assets/box.jpg" alt="Welcome Image" class="welcome-img">
    <a href="/logout" class="button">Logout</a>
  </div>
</body>
</html>
```

{% endtab %}
{% endtabs %}

Analyzing the source code is obvious we need to access admin to get the flag so we need to construct our jwt but we need the jwt secret key. We found it in:

`com.cyberjawara.chall.web.javabox.util.JwtUtil.class`

<figure><img src="/files/vTDtCb5hHvjEoORR3WRf" alt=""><figcaption></figcaption></figure>

the key we found: `c31bcd4ffcff8e971a6ad6ddcbdc613a1246f4223c00fa37404b501ad749257c`

From here we struggle a little bit changing the jwt.io but our tem found that we need to use token.dev for the jwt thanks to @Lyyn

<figure><img src="/files/zRzv31EqXQZV3m5YD4sd" alt=""><figcaption></figcaption></figure>

Then we change the jwt for the win :)

<figure><img src="/files/baOoZ5maJ6GGmwfI7aij" alt=""><figcaption></figcaption></figure>

Flag: `CJ{black_box_web_testing_is_not_that_bad_and_too_guessy_right?}`


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://araisantai.gitbook.io/araisantai-archives/ctf-archive/web-the-cyber-jawara-international-2024.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
