Tags: web 

Rating: 4.7

Download source code from site, then we can see this is a simple PHP framework with login and register action.

Below is the function used to cat flag.

$app->get('/flag', function () use ($app) {
    if (isset($_SESSION['is_logined']) === false || isset($_SESSION['is_guest']) === true) {
        $app->redirect('/#try+harder');
    }
    return $app->flag;
});

Seems we need to login as a rule apart from guest, so let's look at register function.

$app->post('/register', function () use ($app) {
    $id = (isset($_POST['id']) === true && $_POST['id'] !== '') ? (string)$_POST['id'] : die('Missing id');
    $pw = (isset($_POST['pw']) === true && $_POST['pw'] !== '') ? (string)$_POST['pw'] : die('Missing pw');
    $code = (isset($_POST['code']) === true) ? (string)$_POST['code'] : '';

    if (strlen($id) > 32 || strlen($pw) > 32) {
        die('Invalid input');
    }

    $sth = $app->pdo->prepare('SELECT id FROM users WHERE id = :id');
    $sth->execute([':id' => $id]);
    if ($sth->fetch() !== false) {
        $app->redirect('/#duplicate+id');
    }

    $sth = $app->pdo->prepare('INSERT INTO users (id, pw) VALUES (:id, :pw)');
    $sth->execute([':id' => $id, ':pw' => $pw]);

    preg_match('/\A(ADMIN|USER|GUEST)--((?:###|\w)+)\z/i', $code, $matches);
    if (count($matches) === 3 && $app->code[$matches[1]] === $matches[2]) {
        $sth = $app->pdo->prepare('INSERT INTO acl (id, authorize) VALUES (:id, :authorize)');
        $sth->execute([':id' => $id, ':authorize' => $matches[1]]);
    } else {
        $sth = $app->pdo->prepare('INSERT INTO acl (id, authorize) VALUES (:id, "GUEST")');
        $sth->execute([':id' => $id]);
    }

    $app->redirect('/#registered');
});

The key point is $app->code[$matches[1]] === $matches[2], seems when we know code, we can register as admin or user, but the code's value is null.

$app->code = [
    'ADMIN' => null, // TODO: Set code
    'USER' => null, // TODO: Set code
    'GUEST' => '###GUEST###'
];

Seems we will never got right code to register. And i notice another function:

$app->post('/login-2fa', function () use ($app) {
    if (isset($_SESSION['id']) === false) {
        $app->redirect('/#missing+login');
    }

    $code = (isset($_POST['code']) === true && $_POST['code'] !== '') ? (string)$_POST['code'] : die('Missing code');

    require_once('libs/PHPGangsta/GoogleAuthenticator.php');
    $ga = new PHPGangsta_GoogleAuthenticator();

    $sth = $app->pdo->prepare('SELECT secret FROM users WHERE id = :id');
    $sth->execute([':id' => $_SESSION['id']]);
    $secret = $sth->fetch()[0];
    if ($ga->verifyCode($secret, $code) === false) {
        $app->redirect('/login-2fa#invalid+auth');
    }

    $sth = $app->pdo->prepare('SELECT authorize FROM acl WHERE id = :id');
    $sth->execute([':id' => $_SESSION['id']]);
    if ($sth->fetch()[0] === 'GUEST') {
        $_SESSION['is_guest'] = true;
    }

    $_SESSION['is_logined'] = true;
    $app->redirect('/#logined');
});

The key point is $sth->fetch()[0] === 'GUEST', maybe we do not need to register as admin or user, we just need not have an acl record.

preg_match('/\A(ADMIN|USER|GUEST)--((?:###|\w)+)\z/i', $code, $matches); give me some idea. Regexp is resource consuming, so we can construct a very long code, and php will timeout and not run following code.

So register with a code like ADMIN--###A###A....(repeat many times), and login with /login-2fa will get flag.

Original writeup (https://github.com/LyleMi/CTF/blob/master/2017/codebule/guestroom/index.md).