Hackvent 2019: Day 13
Challenge
HV19.13 TrieMe
Introduction
Switzerland's national security is at risk. As you try to infiltrate a secret spy facility to save the nation you stumble upon an interesting looking login portal.
Can you break it and retrieve the critical information?
Resources:
- Facility: HV19-Day13-SpyPortal.html (http://whale.hacking-lab.com:8888/trieme/)
- HV19-Day13-NotesBean.java.zip
Solution
We are given a webpage with a form and the java source to the bean that serves that page.
Java source:
package com.jwt.jsf.bean;
import org.apache.commons.collections4.trie.PatriciaTrie;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.io.StringWriter;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import static org.apache.commons.lang3.StringEscapeUtils.unescapeJava;
import org.apache.commons.io.IOUtils;
@ManagedBean(name="notesBean")
@SessionScoped
public class NotesBean implements Serializable {
/**
*
*/
private PatriciaTrie<Integer> trie = init();
private static final long serialVersionUID = 1L;
private static final String securitytoken = "auth_token_4835989";
public NotesBean() {
super();
init();
}
public String getTrie() throws IOException {
if(isAdmin(trie)) {
InputStream in=getStreamFromResourcesFolder("data/flag.txt");
StringWriter writer = new StringWriter();
IOUtils.copy(in, writer, "UTF-8");
String flag = writer.toString();
return flag;
}
return "INTRUSION WILL BE REPORTED!";
}
public void setTrie(String note) {
trie.put(unescapeJava(note), 0);
}
private static PatriciaTrie<Integer> init(){
PatriciaTrie<Integer> trie = new PatriciaTrie<Integer>();
trie.put(securitytoken,0);
return trie;
}
private static boolean isAdmin(PatriciaTrie<Integer> trie){
return !trie.containsKey(securitytoken);
}
private static InputStream getStreamFromResourcesFolder(String filePath) {
return Thread.currentThread().getContextClassLoader().getResourceAsStream(filePath);
}
}
Initially, we try a few different approached to get our flag. We try to exploit the JSF Viewstate assuming that the state is stored client side which it is not. This post is a good read on the topic.
We also try to navigate to our flag located in the file /data/flag.txt
but this also fails. However, we do manage to access the WEB-INF/web.xml
file here:
http://whale.hacking-lab.com:8888/trieme/javax.faces.resource.../WEB-INF/web.xml.jsf
Finally, we notice that the default value for the input text field in the form has the value of getTrie()
there which returns "INTRUSION WILL BE REPORTED!" for now. Thus, we correctly assume the value of this text is used as an argument to setTrie()
which is otherwise unused. Here we do some digging into the Apache commons library methods being used. In particular the PatriciaTrie data structure. We have a limited attack vector in the name POST parameter to the form. The only thing we can really do is add more values to the trie and it will always contain the initial value of auth_token_4835989
. However, how can we use a PatriciaTrie.put()
call to remove an entry?
After some local debugging and static analysis we discover that the PatriciaTrie.put()
method does not properly compare strings that end with a null terminator character or \x00 with their null free counterpart while the PatriciaTrie.containsKey()
method does correctly distinguish them. That means we can simply pass in auth_token_4835989\0
to our setTrie()
method and have it overwrite the initial value already in the trie while changing the outcome of the !trie.containsKey(securitytoken)
check and thus the isAdmin(trie)
check.
This piece of java code demonstrates the issue:
PatriciaTrie<Integer> trie = new PatriciaTrie<Integer>();
// Put can't distinguish between strings with null terminators at end
trie.put("A", 0);
trie.put("A\0", 0);
System.out.println(trie.size()); // Should be 2 but is only 1
// containsKey works as expected
System.out.println("trie.containsKey(\"A\") = " + trie.containsKey("A"));
System.out.println("trie.containsKey(\"A\0\") = " + trie.containsKey("A\0"));
We pass in the string auth_token_4835989\0
to our form which gives us a message with the daily flag!
STATUS: We will steal all the national chocolate supplies at christmas, 3pm: Here's the building codes: HV19{get_th3_chocolateZ} !
Flag:
HV19{get_th3_chocolateZ}