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的密文进行替换。 首先我们通过fuzz来获取分组的长度,代码如下:
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,然后在进行加密,解密过程类似。 cbc字节翻转攻击(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攻击的分析和思考(详细) 这里截取一些重要的部分(划个重点~)
攻击成立的两个重要条件 1. 攻击者能够获得密文(Ciphertext),以及附带在密文前面的IV(初始化向量) 2. 攻击者能够触发密文的解密过程,且能够知道密文的解密结果
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及密文,满足攻击所需的两个条件。
接着我们将密文取出,并将iv置为0000000000000000
下面就是不断调整iv直到算出正确的padding
这里个人认为还是要把这个图解释一下,图中的Intermediary Value
是中间值,Initialization Vector
才是初始化的iv。 以测试代码为例,padding 0x01的结果为
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)/ 原题是用ruby写的,这里转化为php。
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长度扩展攻击研究
参考资料