ctf中web题目一般会有一个或多个包含密码学的题目,这块一直是弱项,这几天恶补了一波,总结一下,方便之后复习、寻找思路。
ECB 参考: 分组密码模式: ECB模式(电子密码本模式) 
可以看到明文分组和密文分组是一一对应的,且每一对分组都是单独加密的。那么攻击方式就很明显了,我们对某个密文分组进行修改,与之对应的明文在解密时就会被修改,由于每个分组是单独加密的,所以对其他的分组是没有影响的。
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 <?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: ecb.php" );              exit ();      }      if  (@!isset ($_COOKIE ['uid' ])||@!isset ($_COOKIE ['username' ])){              echo  '<form method="post" action="ecb.php?a=reg"> Username:<br>' ;          echo  '<input type="text"  name="username"> <br>' ;         echo  'Password:<br> <input type="text" name="password" > <br><br>' ;          echo  '<input type="submit" value="register">' ;          echo  '</form>' ;      } else  {              $uid  = DE__AES ($_COOKIE ['uid' ]);              if  ( $uid  != '4' ){                      echo  'uid:'  .$uid  .'<br/>' ;                      echo  ''  . DE__AES ($_COOKIE ['username' ]) .'<br/>' ;                      echo  'You are not administrotor!!' ;              } else  {                        echo  "Hi you are administrotor!!"  .'<br/>' ;                      echo  'Flag is 360 become better' ;              }      }  ?>  
简单分析一下,只要uid == 4,就可以得到flag。而在前面将uid设置为9并加密,而且将注册的username也进行了加密,两者都使用了ebc加密。username是可控的,可以通过注册一个特殊的用户名来获得uid的密文进行替换。
1 2 3 4 5 6 7 import  requestsurl = 'http://10.10.10.1/test/ecb.php?a=reg'  for  i in  range (1 , 31 ):    cookies = requests.post(url, data={'username' : 'a' *i, 'password' :'1' }, allow_redirects=False ).cookies     username = base64.b64decode(urllib.unquote(cookies['username' ]))     print  len ('a' *i), len (username) 
通过查看username即密文的长度即可判断分组的长度
1 2 3 4 5 6 7 ...... 13 16 14 16 15 16 16 16 17 32 ....... 
可以看到分组的长度为16,根据分析写出利用代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import  requestsimport  base64import  urlliburl = "http://10.10.10.1/test/ecb.php"  cookies = requests.post(url+'?a=reg' , data={'username' : 'aaaaaaaaaaaaaaaa4' , 'password' : '123456' }, allow_redirects=False ).cookies username = base64.b64decode(urllib.unquote(cookies['username' ])) uid = urllib.quote_plus(base64.b64encode(username[16 :]))   r = requests.get(url, cookies={'uid' : uid, 'username' : cookies['username' ]}) print  r.text
CBC 参考:分组密码模式: CBC模式(密码分组链接模式) 
cbc加密方式同样是先进行分组,区别在于将上一组的密文先与下一组的明文进行XOR,然后在进行加密,解密过程类似。http://www.tuicool.com/articles/vEVFZz )在ctf中非常常见,一般有两种攻击方式:
iv向量,影响第一个明文分组 
第n个密文分组,影响第n+1个明文分组 
 
其中第二个尤为常见,网上有很多题目,这里就不举例子了。放一个比较特殊的,上面两个攻击点都有利用,代码如下:
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 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" > <html> <head> <meta http-equiv="Content-Type"  content="text/html; charset=utf-8"  /> <title>Login Form</title> </head> <?php define ("SECRET_KEY" , 'kH3LH3sk45HLsd3n' );define ("METHOD" , "aes-128-cbc" );session_start ();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 );     $_SESSION ['username' ] = $info ['username' ];     setcookie ("iv" , base64_encode ($iv ));     setcookie ("cipher" , base64_encode ($cipher )); } function  check_login (    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 ("<p>base64_decode('" .base64_encode ($plain )."') can't unserialize</p>" );             $_SESSION ['username' ] = $info ['username' ];         }else {             die ("ERROR!" );         }     } } function  show_homepage (    if  ($_SESSION ["username" ]==='admin' ){         echo  '<p>Hello admin</p>' ;         echo  '<p>Flag is SKCTF{CBC_wEB_cryptography_6646dfgdg6}</p>' ;     }else {         echo  '<p>hello ' .$_SESSION ['username' ].'</p>' ;         echo  '<p>Only admin can see flag</p>' ;     }     echo  '<p><a href="loginout.php">Log out</a></p>' ; } if (isset ($_POST ['username' ]) && isset ($_POST ['password' ])){    $username  = (string )$_POST ['username' ];     $password  = (string )$_POST ['password' ];     if ($username  === 'admin' ){         exit ('<p>admin are not allowed to login</p>' );     }else {         $info  = array ('username' =>$username ,'password' =>$password );         login ($info );         show_homepage ();     } }else {     if (isset ($_SESSION ["username" ])){         check_login ();         show_homepage ();     }else {         echo  '<body class="login-body">                  <div id="wrapper">                     <div class="user-icon"></div>                     <div class="pass-icon"></div>                     <form name="login-form" class="login-form" action="" method="post">                         <div class="header">                         <h1>Login Form</h1>                         <span>Fill out the form below to login to my super awesome imaginary control panel.</span>                         </div>                         <div class="content">                         <input name="username" type="text" class="input username" value="Username" onfocus="this.value=\'\'" />                         <input name="password" type="password" class="input password" value="Password" onfocus="this.value=\'\'" />                         </div>                         <div class="footer">                         <input type="submit" name="submit" value="Login" class="button" />                         </div>                     </form>                 </div>             </body>' ;    } } ?> </html> 
大体流程就是注册一个用户,将数据序列化后加密,保存到cookie中。注册的用户不能是admin,但只有admin才能看到flag。iv和cipher都放在cookie中,就是可控的,我们可以注册一个1dmin用户,然后通过修改密文进行字节翻转将1翻转为a。但是这样做必然会将前一组数据破坏掉,导致解密出来的数据乱码。注意看check_login这个函数中的这行代码
1 $info  = unserialize ($plain ) or  die ("<p>base64_decode('" .base64_encode ($plain )."') can't unserialize</p>" );
如果数据被破坏,unserialize是无法执行成功的,我们就拿不到flag。还记得cookie里的iv吗?它并不是没有用的,我们需要再对iv进行翻转来保证第一个明文分组数据的正确性。不知道说的清不清楚,上个脚本吧。
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 49 50 51 52 import  requestsimport  base64import  urllibimport  reurl = 'http://10.10.10.1/test/cbc/index.php'  def  getCookies ():	global  url 	data = { 		'username' : '1dmin' , 		'password' : '12345' , 	} 	cookies = requests.post(url, data=data).cookies 	return  cookies def  exploit (cookies ):	global  url 	 	cipher = base64.b64decode(urllib.unquote(cookies['cipher' ])) 	pos = 9   	cipher = cipher[0 :pos] + chr (ord (cipher[pos]) ^ ord ('1' ) ^ ord ('a' )) + cipher[pos+1 :] 	cipher = urllib.quote_plus(base64.b64encode(cipher)) 	cookies = { 		'PHPSESSID' : cookies['PHPSESSID' ], 		'iv' 	   : cookies['iv' ], 		'cipher'    : cipher, 	} 	r = requests.get(url, cookies=cookies) 	 	plain = base64.b64decode(re.findall(r'base64.decode\((.*?)\)' , r.text)[0 ]) 	iv = base64.b64decode(urllib.unquote(cookies['iv' ])) 	old = plain[0 :16 ] 	new = 'a:2:{s:8:"userna'  	newiv = '' .join([chr (ord (iv[i]) ^ ord (old[i]) ^ ord (new[i])) for  i in  xrange(16 )])	 	newiv = urllib.quote_plus(base64.b64encode(newiv)) 	cookies = { 		'PHPSESSID' : cookies['PHPSESSID' ], 		'iv' 	   : newiv, 		'cipher'    : cipher, 	} 	r = requests.get(url, cookies=cookies) 	print  (r.text) if  __name__ == '__main__' :	cookies = getCookies() 	exploit(cookies) 
cbc就介绍这么多
padding oracle attack padding oracle attack其实也是对cbc模式加密的一种攻击,具体分析下面这篇文章介绍的很详细,不再赘述。我对Padding Oracle攻击的分析和思考(详细) 
攻击成立的两个重要条件
 
padding只有可能在0x01~0x08之间
 
还有一些我们看着测试代码来分析,代码取自下面两位大佬的blog。padding oracle攻击原理分析(附带rsa资料) 从DES加密到Padding Oracle Attack 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php     $string  = 'bertram' ;     $key  = '123456789' ;     $cipher  = MCRYPT_DES;     $modes  = MCRYPT_MODE_CBC;          if (isset ($_GET ['iv' ]) && isset ($_GET ['str' ])){         $iv  = hex2bin ($_GET ['iv' ]);                  $str  = hex2bin ($_GET ['str' ]);         $decode  = mcrypt_decrypt ($cipher , $key , $str , $modes , $iv );                           echo  bin2hex ($decode );         $temp  = bin2hex ($decode );     } else  {         $iv  = hex2bin ("08fe1649311fd298" );         $encode  = mcrypt_encrypt ($cipher , $key , $string , $modes , $iv );         echo  "the iv is <h2>" .bin2hex ($iv )."</h2><br>" ;         echo  "the cipher is <h2>" .bin2hex ($encode )."</h2><br>" ;     } ?> 
如果代码运行不了的话应该是没有安装或php没有开启Mcrypt扩展。
接着我们将密文取出,并将iv置为0000000000000000
下面就是不断调整iv直到算出正确的padding
这里个人认为还是要把这个图解释一下,图中的Intermediary Value是中间值,Initialization Vector才是初始化的iv。
1 2 3 中间值 ^ 0x99 = 0x01 中间值 = 0x99 ^ 0x01 = 0x98 明文 = 中间值 ^ 原始iv = 0x98 ^ 0x98 = 0x00  
同理padding 0x02,需要再次调整iv(0x98 ^ 0x02 = 0x9a),这地方可能有点绕,靠个人理解吧。。。
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 import  requestsiv     = '08fe1649311fd298'  cipher = '262b4647cc70d363'  url    = 'http://seaii-blog.com/test/padding.php'  tmp_iv = '0000000000000000'  tail = ''  result = ''  for  i in  xrange(1 , 9 ):      for  j in  xrange(256 ):                   test = ('0'  + str (hex (j)[2 :])) if  j < 16  else  str (hex (j)[2 :])                     test_iv = tmp_iv[i*2 :] + test + tail                  r = requests.get(url, params={'iv' : test_iv, 'str' : cipher})         tmp = r.text         if  tmp[(8 -i)*2 :(9 -i)*2 ] == '0' +str (i):                          mid_val = eval ('0x' +test) ^ eval ('0x' +str (i))                             result = chr (mid_val ^ eval ('0x' +iv[(8 -i)*2 :(9 -i)*2 ])) + result                          tail = hex (mid_val ^ eval ('0x' +str (i+1 )))[2 :] + tail             print  test_iv             break  print  result
只测试了第一轮,运行结果如下:
如果想测试下一分组,根据cbc加密的原理,只要把iv换为第一分组的密文即可。
利用工具:
实战: Padding Oracle Attack 笔记 
CFB 参考:分组密码模式: CFB模式(密文反馈模式) 
cfb的题目比较少,在这里找到了一个Hitcon 2015的题目https://nusgreyhats.org/write-ups/HITCONCTF-Quals-2015-Simple-(Crypto-100)/ 
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 49 50 51 52 53 54 55 56 57 58 <!DOCTYPE html> <html> <head>     <title></title> </head> <?php     //error_reporting(0);     define("SECRET_KEY", 'kH3LH3sk45HLsd3n');     define("METHOD", "aes-128-cfb");     function get_random_iv() {         $random_iv = '';         for($i = 0; $i < 16; $i++){             $random_iv .= chr(rand(1, 255));         }         return $random_iv;     }     $username = @$_POST['user'];     $password = @$_POST['pass'];     if(isset($username) && isset($password)) {             $iv = get_random_iv();                  $info = array(             'username' => $username,             'password' => $password,         );         $info = json_encode($info);         $cipher = openssl_encrypt($info, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv);                  setcookie('iv', $iv);         setcookie('auth', $cipher);         header('Location: cfb.php');     } else {         if(isset($_COOKIE['auth']) && isset($_COOKIE['iv'])){             $cipher = $_COOKIE['auth'];             $iv = $_COOKIE['iv'];             $info = openssl_decrypt($cipher, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv);             $info = (array)json_decode($info);             if($info['admin'] === true){                 exit('flag{123456}');             } else {                 echo 'Hello '.$info['username'];                 }         } else {             echo '<body>                   <form action="" method="post">                       <input type="text" name="user"><br>                       <input type="password" name="pass"><br>                       <input type="submit" value="register">                   </form>                   </body>';         }     }  ?>  </html> 
套路和上面的都差不多,注册一个用户,将usernaem和password json一下,然后加密存到cookie中,json_decode之后info['admin'] === true即可。但是正常情况下应该只有info[‘username’]和info[‘password’]。
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 import  requestsimport  base64import  urlliburl = 'http://10.10.10.1/test/cfb.php'  cookies = requests.post(url, data={'user' : '1' , 'pass' : '123456' }, allow_redirects=False ).cookies old_iv = urllib.unquote(cookies['iv' ]).encode('hex' ) cipher = urllib.unquote(cookies['auth' ]).encode('hex' ) first_block = cipher[0 :32 ]  plain = '{"username":"1",'   encrypted_iv = hex (int (plain.encode('hex' ), 16 ) ^ int (first_block, 16 ))[2 :-1 ] new_plain = '{"admin": true }'  new_cipher = hex (int (encrypted_iv, 16 ) ^ int (new_plain.encode('hex' ), 16 ))[2 :-1 ] new_cookies = {     'auth' : urllib.quote_plus(new_cipher.decode('hex' )),     'iv'   : cookies['iv' ], } r = requests.get(url, cookies=new_cookies) print  r.text
具体分析代码中写的很详细了,但是这个题目没有体现cfb最典型的攻击——重放攻击,关于这种攻击的一个题目见:cfb重放攻击 
hash长度扩展攻击 详细介绍见另一篇博文:hash长度扩展攻击研究 
参考资料