from:https://www.cyberchallenge.com.au/CySCA2014_Crypto.pdf
Fortcerts開發了不少涉及加密算法的程序并已經自行測試了這些程序,現在他們想要了解其他人對于這些加密程序安全性的看法。
Question 請對Fortcerts自定義加密算法的Slightly Secure Shell程序進行白盒測試。找出漏洞并證明其可以利用來獲取機密信息。服務器運行在172.16.1.20:12433
Source
#!python
a580fd052a2f1ef9a0753ee36ad6bd51-crypt01.py
=== snip... ===
def execute_command(command,plain,coded):
print "Running command: bash %s" % command
proc = subprocess.Popen(("/bin/bash","-c",command),stdout=subprocess.PIPE,stderr=subprocess.PIPE)
proc.wait()
stdoutdata = proc.stdout.read()
stdoutdata += proc.stderr.read()
output = ""
for letter in stdoutdata:
if letter in plain:
output += coded[plain language=".find(letter)"][/plain]
else:
output += letter
return output
def handle_client(conn,addr):
plain = "`1234567890-=~!@#$%^&*()_+[]\{}|;':\",./<>?abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ "
conn.send("You have connected to the Slightly Secure Shell server of Fortress Certifications.\n")
coded = shuffle_plain(plain)
command = ""
conn.send("#>")
while 1:
data = conn.recv(1)
if not data: break
if data == "\x7f" or data == "\x08":
if len(command) > 0:
command = command[:-1]
continue
if data == "\n" or data =="\r":
if len(command) == 0: continue
conn.send("Running command: '%s'\n" % command)
cmd_stdout = execute_command(command,plain,coded)
conn.sendall(cmd_stdout+"\n")
command = ""
conn.sendall("Key reset\n")
coded = shuffle_plain(plain)
conn.sendall("#>")
else:
if data not in plain:
continue
command += data
conn.sendall(data)
conn.close()
=== snip... ===
Designed Solution 選手可以在自定義命令之前發送一個echo命令來獲得加密密鑰并對輸出值進行破譯,然后使用多個命令來查找包含key的文件,并查看其內容。 Write Up 首先閱讀系統提供的源碼,明確程序讀取用戶的輸入值,直到它獲到一個換行符或回車符,然后通過bash來執行命令,并使用單碼代換對命令的執行結果加密后將輸出返回給用戶。在此之后,密鑰被重置。 我們可以通過管道命令來在單個密鑰的使用過程中執行多個命令,基于此來恢復密鑰和對輸出進行解密。 下面連接到服務器來發送一些命令以驗證我們的假設:可以執行多個命令并且密鑰僅在命令都執行完后被重置。
#!bash
#>nc 172.16.1.20 12433
You have connected to the Slightly Secure Shell server of Fortress Certifications.
#>echo AAAA
echo AAAARunning command: 'echo AAAA'
6666
Key reset
#>echo AAAA;echo AAAA
echo AAAA;echo AAAARunning command: 'echo AAAA;echo AAAA'
zzzz
zzzz
Key reset
#>echo AAAA;echo BBBB;echo ABAB
echo AAAA;echo BBBB; echo ABABRunning command: 'echo AAAA;echo BBBB; echo ABAB'
8888
5555
8585
Key reset
可以看出A和B總是被相同字符替換,因此可以確定這是一個單碼代換加密,同時可以得知密鑰是在所有命令的輸出結果顯示之后再更新。 我們通過兩個bash命令來執行任意命令,第一個echo明文字符串,第二個執行自定義命令。這樣輸出結果的開始將是密文字符表,剩下的內容就是第二個命令的輸出。
#!bash
#>echo abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ; echo TestString
echo abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ; echo TestStringRunning command: 'echo abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ; echo estString'
_u;pSw"y|r^QFJ <Lk\+{KnEX$#%=UDb/6[HlBOaPRA54(-!'xq: <<< Ciphertext alphabet
(S\+4+k|J" <<< Test string
由此可以使用密文字符表來解密測試字符串。 明文字符表 abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
密文字符表: _u;pSw"y|r^QFJ <Lk\+{KnEX$#%=UDb/6[HlBOaPRA54(-!'xq:
密文測試字符串: (S\+4+k|J"
明文測試字符串: TestString
這種方法看來行之有效,下面使用ls命令來定位flag,并用cat命令來顯示它。
#!bash
#>echo abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890.;ls -1
echo abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890.;ls -1Running command: 'echo abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890.;ls -1'
qaF)-:"XzJ^#Ehp2H_*]iDMUP\@8`rNdIm6AW3&5e%0}[Rk/VYS>{tl<;KfG4n <<Key
qXE << bin
F)i << dev
)*a << etc
-^ :n*M* << flag.txt
^Xq << lib
Key reset
#>echo abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890.; cat flag.txt
echo abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890.; cat flag.txtRunning command: 'echo
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890.; cat flag.txt'
q^0sE`"{G3KSo& @):nUZ=;+[BM?Lx*]CwIdmOg#2,VX-!Plav'e\9N4/_F(Wf7
Lq=Gq:?qKEs-{qoEW__ << CaviarBakedShame966
執行這些命令并對響應解碼后得到flag:CaviarBakedShame966 ?
Question 請對Fortcerts的高安全性密鑰生成服務器進行白盒測試。找出能造成秘密數據泄漏的漏洞。服務器運行在172.16.1.20:9999 Source 5a92cb8141992b7b71497a3bc920c7a5-crypt02.py
#!python
=== snip... ===
def compress(d):
c = zlib.compressobj()
return(c.compress(d) + c.flush(zlib.Z_SYNC_FLUSH)).encode("hex")
def encrypt(aes,d):
return aes.encrypt(d).encode("hex")
def handle_client(conn, addr):
AESKey = os.urandom(32)
ctr = Counter.new(128)
aes = AES.new(AESKey, AES.MODE_CTR, counter=ctr)
BANNER = "\t\tWelcome to the Keygen server.\n\t\t"
BANNER += "="*30
BANNER += "\n[+]All access is monitored and any unauthorised access will be treated as CRIME\n"
conn.send(BANNER)
SECRET = 'Key:' + helpers.load_flag()
data = conn.recv(2048).strip()
while len(data):
data = compress(SECRET + data)
data = encrypt(aes, data)
conn.send(data)
data = conn.recv(2048).strip()
conn.close()
=== snip... ===
Designed Solution 在這里,選手使用被認為是犯罪的攻擊方式。由于在使用流密碼加密有關被泄漏的私鑰之前對數據進行了壓縮,選手可以自寫利用泄露信息的腳本 Write Up 首先分析系統提供的源碼,服務器把我們的輸入值,附加到密鑰后面并壓縮,然后加密,最后返回一個經過加密的16進制編碼的字符串。它使用了AES的CTR模式,說明這里的AES是流密碼方式加密而不是分組加密。 使用netcat連接到遠程服務。標識表明這是一個密鑰服務器,并警告說任何未經授權的訪問都將被視為是犯罪,這里的CRIME特意用了大寫。服務器既不提供任何提示來與用戶進行交互,也沒有提供help功能。
#!bash
#>nc 172.16.1.20 9999
Welcome to the Keygen server.
==============================
[+]All access is monitored and any unauthorised access will be treated as CRIME
為了驗證在查看源碼片段時所得出有關代碼功能的結論是否正確,下面發送一些輸入到服務器。從這里我們可以看出,無論CTR模式中使用的計數器是怎么增加的,每次提交之間的密鑰不會被重置,所以加密的數據不同。同時也能看出密文長度變化很小,這也表明使用的是流密碼而不是分組密碼。
AAAA
0a7a99948f447f19b22d7a9a74e25d591b8ff864fa0d3b80804f18b2798219cdb4429e39c8bd6594bc
50f640432fa7db9368db69ce769d2fe4ca30a1bc728c4d0ccdf701b8ae79000db82220
AAAA
ae387373a2131e25e26793aae4d3d1e61a74680c0c654dcef76ec0c27667bf861bd87d272f8d70e03f
b55a515be355f0030bae31beef06903ee6da654a96dbe41d134bea66d4993fbb414512
AAAA
7ff3513568e314e954bb872b604d3d9518fb64382fc6129d80465f27f99ab53a0300aea122627cc2fa
2d5d06600626079c20406f51fe9dcd9a680a9c9041c421308ddc322aac3b2f7dc9f253
AAAABBBB
b1217dacfc0e7927f3519baedb290a14b791b3b97c821158e84e956d071c887f97afd85213476712ff
da16fab035d54a72ee8f8608f94557e7e8866480aa5252eb1ed533c8d006c647824fc95123782b
AAAAAAAA
a3c7b8ae0f2628e126914284f62db7f60122daa23fc39cfc7f3cbd8999c2f979a3f8a214ebd225c48e
5ec5b303a672a45450ef13c00c110758e14d3f2981b95356c537f678357340be941877
注意到8個A加密的輸出長度和4個A一樣。與通常的不一樣的是,8個A一般會被壓縮成和4個A一樣的長度。被壓縮的數據通過流密碼加密,因此加密數據的長度等于被壓縮數據的長度。我們可以使用壓縮數據的長度來獲取數據被壓縮的有關信息,這是一個有關CRIME攻擊的類似準則。你現在明白CRIME為什么要大寫了嗎? 再次審查源碼,我們明確了數據壓縮的方式。假設flag是“TestFlag”,輸入ABCD到服務器,壓縮數據將是“Key:TestFlagABCD”。這個數據不會被壓縮,我們也得到了一個較長的響應字符串。但是,如果輸入“Key:”,將要壓縮的數據就是“Key:TestFlagKey:”。當算法壓縮數據時,它將得到兩個“Key:”,然后有效地壓縮并返回一個較短的響應。 我們發送“Key:”來驗證推斷。每次發送后都得到了不同的響應內容,但是響應的長度沒有變化。繼續發送“Key:A”,響應的長度增加了;發送“Key:B”和“Key:C”都得到了與“Key:A”相同的響應長度。當發送“Key:D”給服務器后,響應變短了,長度與發送“Key:”的響應相同。
Key:
8bec8291d51787058bfe80c1303c3024f60b84812b4ec6a0d2a818dc74fc340bc45d99f89e29fbd7ee
c47f9091ebe518bd85b6e3d5c583719ee438ae4d597772afc9506aec2214865d9750ca
Key:A
964c86d2d100faaee0a920dee7716a29493cd3e11e7a33e1b0cfbe866e01f53208cab473a3eebc8607
dbf2ab25d197b6f6684c70f9a51b15debc4f282edf048cfb57c47b94e7b147b0f08fa4f27c
Key:B
85deee163f4e0982f67688fb0cdb7c735ccbe8f276796aadfdb7585a0528e5af9e107f5901637fdbc2
9c4622d821a91f4a4da9302115f601aeb97a120ffb86350797ed1e801a7e8c9f00f97e28dd
Key:C
af147c047eb3d0e84472cc95670919617ae4d10623b990ced591f074bae9d85f58bce66491ccc2a9c5
6ff9e438e78ce02f9c4d61086a5f52f16806a2eddc6d7daca5498c15ac854018a53491ab8d
Key:D
dd008730d0524e3af6332e4c69e69d7428968b7b21d32ebef9e35d8b908026b69023b09e2a9f978721
017b254b0b1b08ec7cd460a681e404eedb027c88e843f288ba5a15d276208aa86792b0
從這里我們得知flag可能是以大寫的D開頭。知道了這個缺陷,就可以使用Python腳本來連接服務器,然后暴力破解flag的每一個字符。
#!python
import socket
import string
known_part_of_the_key = "Key:"
def Checker(s, bs):
global known_part_of_the_key
strings = string.letters + string.digits + "%/+=:"
while 1:
for ch in strings:
s.send(known_part_of_the_key + ch)
resp = s.recv(2048)
if len(resp) == bs:
print known_part_of_the_key
known_part_of_the_key = known_part_of_the_key + ch
host = "172.16.1.20"
port = 9999
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, port))
banner = sock.recv(1024) #Receive the banner
print banner
sock.send(known_part_of_the_key)
resp = sock.recv(2048)
base_size = len(resp)
Checker(sock, base_size)
運行腳本之后就得到flag:DrizzleVerandaFinger576
#!python
#>python soln.py
Welcome to the Keygen server.
==============================
[+]All access is monitored and any unauthorised access will be treated as CRIME
Key:
Key:D
Key:Dr
Key:Dri
Key:Driz
****SNIP****
Key:DrizzleVerandaFinger576
****SNIP****
?
Question
Fortcerts的高管均表示反對使用白盒測試的方法,他們說真正的攻擊者不可能獲得源碼的訪問權。請對Fortcerts的一個非常安全的加密服務進行黑盒測試。找出服務中能夠恢復加密數據的漏洞。這個非常安全的加密服務運行在172.16.1.20:1337
Designed Solution
選手需要分析服務器以確定它是否使用了基于流密碼的算法。一旦他們是這樣做的,需要確定是否使用了能產生重復密鑰流的IV。選手可以建立一個密鑰流的數據庫,然后通過重復的密鑰流來對flag進行解密。
Write Up
由于開始時沒有任何文件提供,我們直接連接到加密服務看是否能明確它的功能。首先是一句“Key Reset!”的消息,然后從標識處得知程序使用了非常安全的加密。另外還列出了兩個命令并告訴我們輸出的格式。
#!bash
#>nc 172.16.1.20 1337
Key Reset!
Welcome to the FortCerts Certified Data Encryption Service
This program uses very secure encryption
Commands:
E - Encrypt specified data
D - Dump service stored data
Output is in format <IV>:<Encrypted Data>
我們輸入這兩個命令來確定其功能。輸入E,得到一個Invalid use of encrypt,這說明該命令需要附帶參數。使用D命令時得到的字符串表明加密結果與D命令下面的簡介信息相符,最后得到一個“Key Reset!”的消息。
E
Invalid use of encrypt. Usage E,<valuetoencrypt>
D
226e:01ef3044bac49aed75821296a10c060f0d0225386936
Key Reset!
依次輸入a到z,A到Z看是否存在未公開的命令。但是,除了E和D之外都顯示無效的命令,然后連接斷開。 我們嘗試給D和E命令附加一些參數來查看其輸出是否有變化。 結果表明:無論給D命令什么參數,盡管字符串發生了變化,但是它的輸出格式不變。加密數據的長度以及IV的長度總是恒定的,同時最后總有一個“Key Reset!”消息。
D
035b:3e4e9892b1ab053426dfa7168a0cc9827acf33d3480b
Key Reset!
D,
171f:73674b54f57f52874aed3027ece58d0f97780c88223e
Key Reset!
D,,,,
8e01:ba6b8ce2fa0e4d9f6b1888889df0110f4015b10a60fe
Key Reset!
D,AAAAA
a6ec:8f040a25951bdb727a974b7087c9cf733e44b427e2dd
Key Reset!
D,Test
b20f:0c823c92d80102913af7353254717d60b68f5e3d2a0e
Key Reset!
D
d4c0:b99815314cf8d5bec3d92be1204e66a044fc7050c48a
Key Reset!
D,BBBBBB
1026:bd9c781a6cd7d766040c0d539a13ebb9a590b1cebac1
Key Reset!
對于E命令,服務要求使用的正確命令格式為E,
E.
Invalid use of encrypt. Usage E,<valuetoencrypt>
E,,,
Invalid use of encrypt. Usage E,<valuetoencrypt>
E,,,,,,
Invalid use of encrypt. Usage E,<valuetoencrypt>
E..
Invalid use of encrypt. Usage E,<valuetoencrypt>
E,
Invalid use of encrypt. Usage E,<valuetoencrypt>
E,A
fb51:9e
E,AAAA
78c9:f469f095
E,AAAA
9d16:001b2784
E,AAAAAAAA
dbfb:87a24590fd8ac117
E,TEST
1038:8283d469
E,TEST
7e3d:0c057546
E,1234567890
65e4:fdba65422ede91d82f10
當使用E命令時,輸出字符都是16進制范圍內的0-9和a-f,因此字符串很可能是16進制編碼的字符。另外沒有顯示“Key Reset”的消息,這也表明可能每次使用E功能時密鑰將不會重置。 注意到每個IV都是不同的而且加密后的數據長度總是兩倍于輸入的數據。由于加密數據的長度是隨著輸入字符長度的增加而增加,我們確信這是一個流密碼而不是分組密碼。 在流密碼和流密碼攻擊中,我們知道WEP不安全的根源在于其IV僅有24位長。但是這里的IV僅僅只有2個16進制編碼字符即16位長。因此有可能得到重復使用的密鑰和IV,這將導致密鑰流的重用,同時允許我們能夠解密D命令返回的數據。我們嘗試一下。 對5個A加密500次,然后對結果進行排序并計數,最終有3個IV的碰撞而且每個碰撞的響應數據是相同的。
#!bash
#>for i in {0..500}; do echo "E,AAAAA" >> test.txt; done; echo "Q" >> test.txt
#The Q is so we get disconnected
#>tail test.txt
E,AAAAA
E,AAAAA
E,AAAAA
E,AAAAA
E,AAAAA
Q
#>cat test.txt | nc 172.16.1.20 1337 | sort | uniq -c | grep " [2-9] "
2 63b2:67c7b06d66
2 a315:beece8713b
2 f7db:08db5207b7
現在我們確認該服務器有IV重用攻擊的漏洞,只需要收集一些結果以便能夠恢復密鑰流并且對服務中存儲的數據進行解密。 我們需要明確的是,目的不是恢復密鑰而是密鑰流,因此需要從D命令的輸出中得到盡可能多的響應字節。查看D命令的輸出值,我們知道至少需要22字節的密鑰流。
D
3868:3bff03fff1fe385eb6d68c9e84f6771564968d7b017
我們寫一個腳本來加密25個A,并將結果存下來。當擁有10000個不同的IV值時,我們將能轉儲flag并嘗試進行解密。
#!python
import socket
clisock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
clisock.connect(("172.16.1.20",1337))
respdict = dict()
#Recv Key Reset message and Banner
print clisock.recv(1024)
print clisock.recv(1024)
while len(respdict) < 10000:
#Send encrypt
clisock.send("E,"+"A"*25+"\n")
resp = clisock.recv(1024).strip()
parts = resp.split(":")
respdict[parts[0]] = parts[1]
#print parts
if len(respdict) % 200 == 0:
print str(len(respdict))+"/10000"
#Dump the service secret
clisock.send("D\n")
secretdata = clisock.recv(1024).strip()
parts = secretdata.split(":")
#Look for the IV in the dict
if parts[0] in respdict:
print "Found secret iv in dict. Dumping responses"
print " A's response:",respdict[parts[0]]
print "Secret response:",parts[1]
else:
print "Didn't find secret iv in dict. Try again"
對服務器運行腳本,等待其對IV值的收集。最后收到一條消息表示dump命令的輸出值中有一個IV與存放在字典中的某個IV一致。
#!python
#>python solv.py
Connecting
Key Reset!
Welcome to the FortCerts Certified Data Encryption Service
This program uses very secure encryption
Commands:
E Encrypt specified data
D Dump service stored data
Output is in format <IV>:<Encrypted Data>
200/10000
**** SNIP (about 7 minutes) ****
9600/10000
9800/10000
10000/10000
Found secret iv in dict. Dumping responses
A's response: 32fe79d33e7055190ec47c1778c1029946c9fc47398c613936
Secret response: 35cd51f711555a373bec5e337be52fbe66fbc9334af8
復現圖
現在已有帶重用密鑰流的響應,首先恢復密鑰流。流密碼生成密鑰流,然后逐字節與明文進行異或得到密文。已知明文A的響應,只需要將響應數據與明文進行異或就能得到實際的密鑰流。
A's response: 32fe79d33e7055190ec47c1778c1029946c9fc47398c613936
A's plaintest: 41414141414141414141414141414141414141414141414141
Shared keystream: 73bf38927f3114584f853d56398043d80788bd0678cd207877
由已恢復的密鑰流很容易就能解密dump命令的響應,并得到flag:FriendNoticeBelfast525
Dump response: 35cd51f711555a373bec5e337be52fbe66fbc9334af8
Shared keystream: 73bf38927f3114584f853d56398043d80788bd0678cd207877
Dump plaintext: 467269656e644e6f7469636542656c66617374353235
Plaintext(ASCII): FriendNoticeBelfast525