题目
题目原地址见:https://nazo.io/1007/aes.php
题目给了一个PHP文件,并使用highlight_file方法将其源码显示出来(知道你们不会去点链接,我直接贴出来算了。。。):
(注:我解的时候还没有Hint….)
<?php
// Super Secure Data Encryption Service!
// 2018-10-7 12:00 Hint: Try CBC Byte Flipping Attack
$data = $_GET['d'] ?? NULL;
define('KEY', md5((require 'key.php').$_SERVER['REMOTE_ADDR']));
define('METHOD', 'AES-256-CBC');
if ($data) {
if (!isset($_GET['dec'])) {
if (shouldShowFlag($data)) die('not supported.');
echo '<pre>'.bin2hex(encrypt($data)).'</pre>';
} else {
$text = decrypt(hex2bin($data));
echo '<pre>'.(htmlspecialchars($text) ?: bin2hex($text)).'</pre>';
if (shouldShowFlag($text)) echo require_once 'congratulations.php';
}
} else {
highlight_file(__FILE__);
}
function shouldShowFlag($data) {
return strpos($data, 'besb66.com') !== false;
}
function encrypt($text) {
$text = $text.str_repeat(chr(0), 16 - strlen($text) % 16);
$ciphertext = openssl_encrypt($text, METHOD, KEY, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv = openssl_random_pseudo_bytes(16));
return $iv.$ciphertext;
}
function decrypt($data) {
if (strlen($data) < 32) return 'error.';
$iv = substr($data, 0, 16);
$ciphertext = substr($data, 16);
$text = openssl_decrypt($ciphertext, METHOD, KEY, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);
return $text;
}
解谜思路
从源码得知,本题通过d与dec这两个GET参数来作为输入,并且引用了key.php、congratulations.php两个PHP文件。
先来做一些简单的输入,检查一下这个所谓的Super Secure Data Encryption到底是怎么样的,而从结果可见加密应该是按照标准来的,可以正常加解密,没什么坑。。。
然后来看两个引用的php文件,分别用浏览器去访问它们看看。。。然而什么都没有返回。。。(其实应该是PHP根据请求者返回不同的信息,对解题者返回空罢了)
于是只能静下心来看代码。。。
我们发现key.php返回的字符串后面是跟了一串$_SERVER[‘REMOTE_ADDR’]后再进行MD5 HASH的,这个参数的大致意义是返回你在服务器眼里的IP地址(一般情况下就是你的外网IP)。
于是可以判断这个key应当没有解的意义。(因为每个人IP都不同,尽管它对每个人来讲都是固定不变的)
那么我们来看这个congratulations.php,如何让这个网页返回它的内容?可以看出,这个加/解密算法进行解密处理后的值为besb66.com时会返回它。
但是我们很遗憾地发现,我们不能用刚刚测试时候那样的方法,直接去逛一圈就得到答案,因为加密时有一个”如果输入值恰为besb66.com时报错”的判断。
而因为加密的KEY不可知,故我们也不能把源码拉到本地环境,把那个判断删掉跑一遍加密,再去服务器上跑一遍解密拿答案。
于是去百度AES-256-CBC加密算法。。。我们知道,加密后值的前16位是IV,即程序中的$iv
。
关于IV的解释,我的理解是类似钥匙或者暗号之类的,这里有一个比较系统的解释:
IV:用于随机化加密的比特块,保证即使对相同明文多次加密,也可以得到不同的密文。
CBC密文中,整个结构是:
[IV][密文1][密文2]......
(其中每一段为16位)
然后这里有一张很关键的图(关于CBC解密过程的):
假设这个用Key对加密字符串str进行解密后的结果是f(str) (注意这个f(str)并不是密文,它只是一个过渡数据),那么我们可以得出:
f(str) xor IV = PlainText(即明文)
然后我们有一个定律,即:若 A xor B = C,那么 A xor C = B, C xor B = A
则有:f(str) = IV xor PlainText
好我们现在设一个可以被这个PHP文件成功加密的明文,如besb65.com,加密后得到(2位十六进制表示一个字符,所以说这个IV是16位没毛病):
又因为KEY固定,算法固定,故我们可以确认上述公式中的f(str)是一个固定值,那么:
f(str) = IV xor "besb65.com"
IV' = f(str) xor "besb66.com"
则有:IV' = IV xor "besb65.com" xor "besb66.com"
(这里有一个观点:f(str)这个过渡数据最终被解读成什么样,其实是由其本身与IV共同决定的,既然本身这个加密数据难改,那么改IV照样可以更改得到的明文内容)
那么,写一段程序来计算前面提到的公式。。。(注意:CBC在文本位数不够时会扩充0,扩充0的方法可参考题目)
(程序部分略…)
于是我们发现,得出的结果其实跟原加密字符串的头部十分相近(甚至只差一两个字符),于是将IV更改后再次提交给服务器进行解密,得出结果:
完毕~
题目进阶
题目地址:https://nazo.io/1007/aes2.php
原理不多赘述(上面说的够多了)
因为此题中的MD5刚好是16位,故其实密文被完整、平均、独立地分成了三段:
[IV][MD5][Cipher]
首先取出[MD5][Cipher]
利用aes.php进行形如前一题的操作(反正Key一样),获得新的[MD5*]
(密文)。
将[MD5]
部分替换成新的之后提交一次,获得此时的MD5值明文(wrong hash后面会跟此时的MD5解密结果)。
同样地,利用获取到的MD5明文,取出[IV][MD5*]
部分再进行形如前一题的操作(目标MD5值可以使用PHP现场算出来),获得[IV*]
。
将[IV*][MD5*][Cipher]
进行拼接后提交,结束。
相关代码:
<?php
$cipher = hex2bin("7b2f056b746233d4fa4fe5cf41f1ff6c095b35842abe71b74303dbeabe29a12c8458e50fec520eaecebcffd90770e988"); //besb65.com加密值
$iv = substr($cipher, 0, 16);
$md5_cipher = substr($cipher, 16, 16);
$text_cipher = bin2hex(substr($cipher, 32, 16));
$text = "besb66.com";
$textnow = "besb65.com";
$md5 = md5($text, true);
$md5now = hex2bin("11cf1434ed34929b12b2bb27057981ed"); //第一次提交后根据报错返回更改此值
$text = $text.str_repeat(chr(0), 16 - strlen($text) % 16);
$textnow = $textnow.str_repeat(chr(0), 16 - strlen($textnow) % 16);
$md5_cipher ^= $text ^ $textnow;
echo "First Output:\n";
output();
$iv ^= $md5 ^ $md5now;
echo "Second Output:\n";
output();
function output() {
global $iv, $md5_cipher, $text_cipher;
echo bin2hex($iv);
echo bin2hex($md5_cipher);
echo $text_cipher;
echo "\n";
}
结果:
未经允许不得转载:MikuAlpha's Blog » CBC字节翻转攻击