CTF WEB题中的密码学

作为一只web,免不了遇到一些以web为载体的密码学,现在做个笔记,理一理

ECB

ECB模式的全称是Electronic CodeBook模式,将明文分组加密后直接成为密文分组,而密文则是由明文分组直接拼接而成

因为把明文分块,所以同样的明文块会被加密成相同的密文块。如果存在多组明文进行加密,那么我们只需要观察一组明文-密文对就能得到所有明文的现象。

这里就用一道ctf题来做例子

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
<?php
function AES($data){
$privateKey = "12345678123456781234567812345678";
$encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $privateKey, $data, MCRYPT_MODE_ECB);
$encryptedData = (base64_encode($encrypted));
return $encryptedData;
}
function DE__AES($data){
$privateKey = "12345678123456781234567812345678";
$encryptedData = base64_decode($data);
$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $privateKey, $encryptedData, MCRYPT_MODE_ECB);
$decrypted = rtrim($decrypted, "\0") ;
return $decrypted;
}
if (@$_GET['a']=='reg'){
setcookie('uid', AES('9'));
setcookie('username', AES($_POST['username']));
header("Location: http://127.0.0.1/ecb.php");
exit();
}
if (@!isset($_COOKIE['uid'])||@!isset($_COOKIE['username'])){
echo '<form method="post" action="ecb.php?a=reg"> Username:<br> <input type="text" name="username"> <br> Password:<br> <input type="text" name="password" > <br><br> <input type="submit" value="注册"> </form> ';
}
else{
$uid = DE__AES($_COOKIE['uid']);
if ( $uid != '4'){
echo 'uid:' .$uid .'<br/>';
echo 'Hi ' . DE__AES($_COOKIE['username']) .'<br/>';
echo 'You are not administrotor!!';
}
else {
echo "Hi you are administrotor!!" .'<br/>';
echo 'Flag is 360 become better';
}
}
?>

如果想要获得flag,就得使自己的uid=4,但是注册的时候系统强制将uid设置为9。这里的uid经过了aes加密,并且是ecb模式,我们可控的是username,所以可以依据username的明文操纵生成我们想要的uid密文。这里AES采用了128位的加密,即16个字节。所以,我们可以注册17个字节,多出的那一个字节就可以是我们希望的UID的值,而此时我们查看username的密文增加部分就是UID的密文,即可伪造UID。(因为第十七个字节单独为一组,前面十六个字节为一组)(ecb是有填充的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import requests
import base64
import urllib
reg_url = "http://127.0.0.1/ecb.php?a=reg"
index_url = "http://127.0.0.1/ecb.php"

reg_data = {"username":"aaaaaaaaaaaaaaaa4", "password":"123"}
re = requests.post(url=reg_url, data=reg_data, allow_redirects=False)
name_cookie = re.headers['Set-Cookie'][49:]
name_hex = base64.b64decode(urllib.unquote(name_cookie)).encode('hex')
uid = urllib.quote(base64.b64encode(name_hex[len(name_hex)/2:].decode('hex')))
flag_cookie = {"uid":uid, "username": name_cookie}
re = requests.get(url=index_url, cookies=flag_cookie)
print re.text

CBC

encryption

在CBC模式中,每个明文块先与前一个密文块进行异或后,再进行加密。在这种方法中,每个密文块都依赖于它前面的所有明文块。同时,为了保证每条消息的唯一性,在第一个块中需要使用初始化向量。

decryption

$P_i = D_k(Ci)\oplus C{i-1}$

$C_0 = IV$

attack

CBC模式有两个攻击点:

  1. vi向量,影响第一个明文分组
  2. 第n-1个密文分组,影响第n个明文分组

令A为第N-1块的密文,B为第N块经过key解密后(不是明文),C为第N块明文

则有$C = A \oplus B \implies C \oplus A \oplus B = 0 \implies C \oplus A \oplus B \oplus X = X$

并且密文A是可控的,所以可以令A=$A \oplus C \oplus X$ ,这样就能令C=X

这就是字节翻转

依旧是一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- please login as uid=1!--> 
<?php
include("AES.php");
highlight_file('index.php');
$v = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890auid=9;123123123123";
$b = array();
$enc = @encrypt($v);
//S9PsFp43k9VgyrggRHLbISjUAjwzSSPPajrF9Dzz0o/ieSZbxwGjTJ5xhAZEi5tDBjvwsQtH0BynlLC0p0F0zOZMx25M6iekcLvX//MNKSA=
$b = isset($_COOKIE[user])?@decrypt(base64_decode($_COOKIE[user])):$enc;
$uid = substr($b,strpos($b,"uid")+4,1);
echo 'uid:'.$uid.'<br/>';
if ($uid == 1){
echo $flag;
}
else {
echo "Hello Client!";
}
setcookie("user",base64_encode($enc));
?>

在v中9是第63位,因为是16位一个块,所以要改变上一个块的值,就是63-16=47位的值

1
2
3
4
5
<?php
$enc=base64_decode("S9PsFp43k9VgyrggRHLbISjUAjwzSSPPajrF9Dzz0o/ieSZbxwGjTJ5xhAZEi5tDBjvwsQtH0BynlLC0p0F0zOZMx25M6iekcLvX//MNKSA=");
$enc[47] = chr(ord($enc[47]) ^ ord("9") ^ ord ("1"));
echo base64_encode($enc);
?>

padding oracle attack

Padding Oracle Attack最关键的就是填充。如果最后的Padding不正确(值和数量不一致),则解密程序往往会抛出异常(Padding Error)。而利用应用的错误回显,我们就可以判断出Paddig是否正确。在Padding Oracle Attack攻击中,攻击者输入的参数是IV+Cipher,我们要通过对IV的”穷举”来请求服务器端返回对我们指定的Cipher进行解密,并对返回的结果进行判断。当然攻击者能够获得密文(Ciphertext),以及附带在密文前面的IV(初始化向量),并能够触发密文的解密过程,且能够知道密文的解密结果。

通俗来讲,如果解密没问题,账号密码也对,则会返回HTTP 200;如果解密没问题,但账号密码不对,也会返回200,并提示账号密码错误;但是如果padding规则错了,则直接翻脸不认人,返回500

例子

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
<?php
error_reporting(0);

define("key", "dfhehrhrh3429hrtg934");
define("method", "aes-128-cbc");

function getIV()
{
$iv = '';
for ($i = 0; $i < 16; $i++)
{
$iv .= chr(rand(1, 255));
}
return $iv;
}

function enc($data)
{
$iv = getIV();
$c = openssl_encrypt((string)$data, method, key, OPENSSL_RAW_DATA, $iv);
return bin2hex($iv . $c);
}

function dec($data)
{
$data = hex2bin($data);
if ($iv = substr($data, 0, 16))
{
if ($c = substr($data, 16))
{
if ($m = openssl_decrypt((string)$c, method, key, OPENSSL_RAW_DATA, $iv))
{
return $m;
}
else
{
return "dec error";
}
}
}
}

echo enc('1') . "<br/>";
if ($_GET['c'])
{
echo dec($_GET['c']) . "<br/>";
}

爆破脚本

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
#coding:utf-8
import requests
import re

#指定位置加一
def add1(data, pos):
data = list(data)
data[pos] = chr(ord(data[pos]) + 1)
return ''.join(data)

#增加iv的值
def addiv(iv, l):
return add1(iv.decode('hex'), l).encode('hex')

#逐位增加mid
def addmid(iv, l, mid):
mid = mid.decode('hex')
iv = list(iv.decode('hex'))
mid = chr(ord(iv[l]) ^ (16 - l)) + mid
return mid.encode('hex')

#通过mid计算新的iv
def newiv(mid, l):
mid = list(mid.decode('hex'))
iv = '0' * l * 2
tmp = 17 - l
for i in mid:
iv = iv + chr(ord(i) ^ tmp).encode('hex')
return iv

#初始化 mid是中间值 c是部分的密文
iv = '0' * 32
mid = ''
l = 15
c = ""

url = "http://localhost:8080/poa.php?c="
while l > 0: #一共15位最开始一位要爆破
#print url + iv + c
res = requests.get(url + iv + c)
if "dec error" not in res.content:
#print url + iv + c
mid = addmid(iv, l, mid)
iv = newiv(mid, l)
l = l - 1
print mid
continue
iv = addiv(iv, l)

这样就算出了中间值,就可以拿来跟可控的iv来构造任意明文了

Hash-Length-Extension-Attack

很多哈希算法都存在Length Extension攻击,这是因为这些哈希算法都使用了Merkle–Damgård construction进行数据的压缩,流行算法比如MD5、SHA-1等都受影响。

把消息分为若干块,最后一块若不足长度则进行长度填充,然后第一个消息块都会和一个输入向量做一个运算,把这个计算结果当成下个消息块的输入向量,以此类推。

MD5

这里就以MD5为例,首先了解下MD5的实现

  1. 先将字符转化为16进制

  2. 使其长度在对512取模后的值为448。也就是说,len(message)%512==448。当消息长度不满448bit时(注意是位,而不是字符串长度),消息长度达到448bit即可。当然,如果消息长度已经达到448bit,也要进行补位。补位是必须的。补位的方式的二进制表示是在消息的后面加上一个,后面跟有限个hex(00),直到len(message)%512==448。如下,将字符串补位到448bit,也就是56byte。

  3. 补位过后,就是补长度,第57个字节储存的是补位之前的消息长度。令其后跟着n个字节的0x00,把消息补满64字节。

  4. 计算消息摘要必须用补位已经补长度完成之后的消息来进行运算,拿出512bit的消息(即64字节)。计算消息摘要的时候,有一个初始的链变量,用来参与第一轮的运算。MD5的初始链变量为:

    1
    2
    3
    4
    A=0x67452301
    B=0xefcdab89
    C=0x98badcfe
    D=0x10325476

我们不需要关系计算细节,我们只需要知道经过一次消息摘要后,上面的链变量将会被新的值覆盖,而最后一轮产生的链变量经过高低位互换(如:aabbccdd->ddccbbaa)后就是我们计算出来的md5值。

attack

当知道MD5(secret)时,在不知道secret的情况下,可以很轻易的推算出MD5(secret||padding||m')

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
$SECRET = "phpno1"
$hash = md5($SECRET .$auth);
if(isset($_COOKIE["auth"])){
$hash = md5($SECRET . $_COOKIE["auth"]);
echo 'auth:'.$_COOKIE["auth"].'<br>';
echo 'hash:'.$hash.'<br>';
if($hash !== $_COOKIE['hash']){
die("Be a good student !");
}else{
if($_COOKIE["auth"] !== "user"){
echo $_COOKIE["auth"].'<br>';
echo 'Congratulations! You pass it !';
}else{
echo "Work more harder!<br>";
}
}
}else{
setcookie("auth", $auth);
setcookie("hash",$hash);
echo "Init!<br>";
}

?>

拿到了初始的明文与hash值auth:guest, hash:2d1060124a9405191782f1330650cdf8

平时是拿不到$secret的长度,需要爆破,这里为了省事就当知道了

开始先进行填充,6个字节的secret + 5个字节的guest,len=88, 所以填充1 * \x80 + 44 * \x00

xxxxxxguest\x80\x00 * 44

然后开始长度填充

xxxxxxguest\x80\x00 * 44 + \x58\x00\x00\x00\x00\x00\x00\x00

然后就是填充构造的消息,并计算md5值(hashpump)

xxxxxxguest\x80\x00 * 44 + \x58\x00\x00\x00\x00\x00\x00\x00admin

ed8424f07e20284dde476819730b730c

跟页面上计算出来的一样

1
2
3
auth:guest�Xadmin
hash:ed8424f07e20284dde476819730b730c
Be a good student !

参考

http://c014.cn/2017/05/23/%E4%BB%8E%E9%9B%B6%E5%AD%A6%E4%B9%A0%E5%93%88%E5%B8%8C%E9%95%BF%E5%BA%A6%E6%89%A9%E5%B1%95%E6%94%BB%E5%87%BB/

http://momomoxiaoxi.com/2016/12/08/WebCrypt/

https://www.anquanke.com/post/id/84724

https://en.wikipedia.org/wiki/Merkle%E2%80%93Damg%C3%A5rd_construction

http://tan90.me/2017/12/06/%E5%AF%86%E7%A0%81%E5%88%86%E7%BB%84%E9%93%BE%E6%8E%A5%E6%A8%A1%E5%BC%8F/

https://zh.wikipedia.org/wiki/%E5%88%86%E7%BB%84%E5%AF%86%E7%A0%81%E5%B7%A5%E4%BD%9C%E6%A8%A1%E5%BC%8F