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模式有两个攻击点:
- vi向量,影响第一个明文分组
- 第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的实现
先将字符转化为16进制
使其长度在对512取模后的值为448。也就是说,len(message)%512==448。当消息长度不满448bit时(注意是位,而不是字符串长度),消息长度达到448bit即可。当然,如果消息长度已经达到448bit,也要进行补位。补位是必须的。补位的方式的二进制表示是在消息的后面加上一个,后面跟有限个hex(00),直到len(message)%512==448。如下,将字符串补位到448bit,也就是56byte。
补位过后,就是补长度,第57个字节储存的是补位之前的消息长度。令其后跟着n个字节的0x00,把消息补满64字节。
计算消息摘要必须用补位已经补长度完成之后的消息来进行运算,拿出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