Web - The Cyber Jawara International 2024
This last october weekend i participated in cyber jawara international with team "swusjask fans club" we manage to secure 2nd position in the event.I personally contributed in solving 2 challs of web.
Example Box
Web; whitebox
495
farisv
Code Analysis
Here is the main code that provided
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
flag():
if request.remote_addr != '127.0.0.1':
abort(403)
with open('/flag.txt', 'r') as flag:
return flag.read()
Reviewing the code the normalize
function is to check if there is whitespace or any unicode in the url.
filter_url
is check for the whitelist.
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.

Yepp we can use @
to bypass the parse_url,
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

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.

So here is the full payload:
http://127.0.0.1:20002/ⓕⓛⓐⓖ?@example.com

Flag: CJ{enough_with_example.com_here_is_your_nice_Phl4g!}
Java Box
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.

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

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.

Exploitation
After some time we find a good article expalaing about some new release CVEs
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

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.

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
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;
}
}
}
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

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

Then we change the jwt for the win :)

Flag: CJ{black_box_web_testing_is_not_that_bad_and_too_guessy_right?}
Last updated