Lỗ hổng nghiêm trọng của TLS/SSL
Một phát hiện hết sức thú vị:
The SSL 3.0+ and TLS 1.0+ protocols are vulnerable to a set of related attacks which allow a man-in-the-middle (MITM) operating at or below the TCP layer to inject a chosen plaintext prefix into the encrypted data stream, often without detection by either end of the connection. This is possible because an “authentication gap” exists during the renegotiation process at which the MitM may splice together disparate TLS connections in a completely standards-compliant way. This represents a serious security defect for many or all protocols which run on top of TLS, including HTTPS.
Thú vị ở chỗ bao nhiêu người, bao nhiêu chuyên gia, bao nhiêu năm qua dòm vô TLS/SSL mà không thấy được lỗ hổng có vẻ như rất hiển nhiên mà các tác giả ở trên phát hiện.
Có lẽ nguyên nhân nhiều người dòm nhưng không thấy là vì họ chỉ dòm TLS/SSL khi nó đứng một mình, mà không nhìn vào bức tranh lớn OSI, trong đó TLS/SSL chỉ là một layer. Chuyện gì sẽ xảy ra nếu TLS/SSL không hiểu rõ cơ chế hoạt động của các protocol bên trên nó, như HTTP, SMTP hay POP3? Nói cách khác, chuyện gì sẽ xảy ra nếu các protocol ở mức Application không hiểu rõ cơ chế vận hành của TLS/SSL để sử dụng cho đúng cách? Đó là lúc lỗ hổng xuất hiện.
Tổng quan thì lỗ hổng này nằm ở sự thiếu “ăn rơ” giữa TLS/SSL và các protocol trên nó như HTTP hay SMTP. Khai thác lỗ hổng này thì kẻ tấn công có thể chèn thêm một đoạn plaintext bất kỳ vào TLS/SSL encrypted stream giữa client và server mà cả client và server đều không thể phát hiện được.
Đây là một lỗ hổng cực kỳ nghiêm trọng, bởi vì nó phá vỡ hoàn toàn cam kết an toàn của bộ giao thức TLS/SSL. Nói một cách *hoành tráng* thì về mặt lý thuyết, nền tảng của thương mại điện tử đang chao đảo. Tôi dùng chữ lý thuyết vì để cho hướng tấn công này nguy hiểm hơn trong thực tế, thì còn có nhiều trở ngại phải vượt qua (và sẽ bị vượt qua).
Để minh họa cho câu chuyện, và để dễ giải thích, tôi đặt ra một ví dụ như sau:
0. Giả định:
* Ngân hàng A có cung cấp dịch vụ Internet Banking ở địa chỉ https://www.ebank.com. Máy chủ của của họ chạy phần mềm có lỗ hổng mà chúng ta đang bàn ở đây. Chúng ta gọi máy chủ này là server.* Để tăng cường an ninh, ngân hàng A yêu cầu khi khách hàng (giờ cứ gọi là client) sử dụng các tính năng có liên quan đến giao dịch tài chính nằm trong khu vực https://www.ebank.com/account/, thì (browser của) họ phải có cài đặt client certificate cho ngân hàng A cung cấp. Lưu ý là nhiều ngân hàng ở VN thực hiện cái này lắm nha.
* Ngoài ra ngân hàng A còn hỗ trợ khách hàng truy cập bằng (Safari trên) iPhone, lúc đó khách hàng sẽ được chuyển đến https://www.ebank.com/iphone/. Do iPhone có processor yếu, nên ngân hàng A cấu hình máy chủ web của họ để sử dụng một bộ ciphersuite yếu hơn bộ ciphersuite mà họ sử dụng cho các khách hàng thông thường. Cái này trong thực tế cũng có nhiều công ty triển khai.
Rồi bây giờ tôi sẽ sử dụng cái kỹ thuật vừa mới phát hiện để tấn công các khách hàng của ngân hàng A theo 3 hướng tấn công mà các tác giả nêu ra. Àh lưu ý là đây là loại tấn công MITM, nghĩa là attacker phải có quyền theo dõi, điều chỉnh dữ liệu truyền qua lại giữa client và server nha. Attacker có thể làm việc này thông qua các tấn công vào các giao thức ARP hay DNS.
1. Hướng tấn công số 1
Đối với hướng tấn công số 1, tôi sẽ lợi dụng việc khi truy cập vào https://www.ebank.com/account/ thì server sẽ yêu cầu client phải trình certificate.
Sơ đồ bên dưới là tôi lấy từ paper của các tác giả phát hiện ra lỗ hổng này. Tôi thấy cái sơ đồ này giải thích rất rõ lỗ hổng này và cách thức tấn công theo hướng thứ 1. Thật ra thì hướng thứ 2 và hướng thứ 3 cũng khá giống hướng thứ 1, nên tôi nghĩ nắm rõ hướng thứ 1 thì sẽ thấy các hướng kia cũng đơn giản.
Có 4 bước khi triển khai tấn công này:
* Bước 1: client truy cập vào https://www.ebank.com. Lúc này client sẽ kết nối đến attacker, và gửi CLIENT_HELLO để bắt đầu giao thức TLS/SSL. Attacker sẽ tạm dừng cái kết nối này và lưu msg CLIENT_HELLO lại để dùng trong bước 3.
* Bước 2: attacker mở kết nối đến server thật. Hai bên sẽ bắt tay theo giao thức TLS/SSL để tạo thành một session. Sau khi hoàn tất bắt tay, attacker gửi một HTTP request, đại loại như:
POST /account/transfer?amount=1000&receiver=attacker HTTP/1.1\r\n
* Bước 3: server thấy có một request đến khu vực /account/ nên nó tạm thời dừng xử lý request này lại và như đã nói ở trên, nó yêu cầu attacker phải đưa client certificate cho nó xem. Cái hay ở đây, mặc dầu attacker không có (private key của) certificate của client, nhưng hắn vẫn có thể *proxy* cái certificate đó từ client lên server, mà không bị bên nào phát hiện cả.
Server bắt đầu quá trình xác thực bằng việc gửi một msg HELLO_REQUEST ngược lại cho attacker. Attacker nhận được msg này thì hắn gửi CLIENT_HELLO mà hắn đã lưu ở bước 1 ngược lại cho server. Rồi cứ thế, attacker đứng giữa, chuyển msg qua lại giữa client và server cho đến khi quá trình xác thực bằng client certificate kết thúc thành công.
Lưu ý là có 2 loại msg mà attacker sẽ gửi. Loại thứ nhất (trên sơ đồ là những msg kết thúc hoặc bắt đầu từ cột m) là những msg mà hắn phải giải mã/mã hóa trước khi gửi đi. Ví dụ như hắn nhận “Certificate” từ phía client thì hắn sẽ mã hóa cái msg này lại, rồi mới gửi cho server. Loại thứ hai (trên sơ đồ là những msg màu hồng và đỏ) là những msg mà hắn không đọc được (vì không có key), hắn chỉ làm mỗi việc là nhận từ client thì gửi qua server và ngược lại.
* Bước 4: quá trình xác thực client certificate đã kết thúc thành công, server tiếp tục xử lý cái request của attacker ở trên, và trả kết quả lại cho attacker (lưu ý là attacker sẽ không đọc được kết quả này).
Điểm yếu là ở đây. Như chúng ta thấy, khi attacker gửi request ở bước 3, lúc đó hắn chưa được xác thực. Nói cách khác, lúc này request của hắn là unauthenticated request. Việc xác thực diễn ra sau đó, và sau khi xác thực rồi thì server lại quay lại xử lý tiếp cái unauthenticated request của attacker.
Lưu ý, ở bước này, để tránh bị tình nghi, attacker có thể tiếp tục trả kết quả về cho client để đóng kết nối lại một cách êm đẹp.
2. Hướng tấn công số 2
Trước khi bắt đầu giải thích hướng số 2, tôi muốn nhấn mạnh ý này: tất cả 3 hướng tấn công này đều hướng đến chôm credential của client để gửi các authenticated request đến server. Credential ở đây có thể là certificate (như ở hướng số 1) hay cookie/session (như ở hướng số 2 và số 3). Nếu chỉ áp dụng cho HTTPS, nhìn ở một góc độ nào đó, các hướng tấn công này rất giống với tấn công CSRF. Nên nếu ứng dụng của bạn đã có các phương thức phòng chống CSRF rồi hay nếu ứng dụng của bạn không chấp nhận thay đổi state bằng GET, thì tạm thời cũng không phải có gì lo lắng.
Đối với hướng số 1, tôi lợi dụng client certificate để gửi một authenticated request. Ở trường hợp các server không xác thực bằng certificate, tôi sẽ sử dụng hướng tấn cống số 2.
Hướng tấn công này cũng có 4 bước:
* Bước số 1: tương tự như hướng tấn công số 1.
* Bước 2: attacker mở kết nối đến server thật. Hai bên sẽ bắt tay theo giao thức TLS/SSL để tạo thành một session.
Sau khi hoàn tất bắt tay, attacker gửi một HTTP request, đại loại như:
GET /iphone/login HTTP/1.1\r\n Host: ebank.com\r\n Connection: keep-alive\r\n \r\n GET /account/transfer?amount=1000&receiver=attacker HTTP/1.1\r\n Host: ebank.com\r\n Connection: close\r\n X-ignore-this:
* Bước số 3: server thấy có request đến /iphone/ nên nó tạm thời dừng xử lý request này lại và, như đã nói ở phần giả định, server sẽ bắt đầu quá trình renegotiate lại để chọn một bộ ciphersuite yếu hơn. Vấn đề ở đây là server sẽ buffer lại toàn bộ nhóm unauthenticated request này, khi mà renegotiate xong thì lại quay lại xử lý hết tất cả.
Trong quá trình renogotiation, vai trò của attacker cũng tương tự như ở bước số 3 của hướng tấn công số 1, nghĩa là hắn cũng chỉ *proxy* msg qua lại giữa client và server, cho đến khi quá trình renegotiate kết thúc thành công.
* Bước số 4: lúc này, client thấy đã handshake xong rồi, nên bản thân nó sẽ gửi tiếp cái HTTP request của nó ở dạng:
GET /index HTTP/1.1\r\n Cookie: AuthMe=Now\r\n \r\n
Chuyện bất ngờ diễn ra ở đây. Server nó sẽ gom nhóm unauthenticated request ở bước 2 (do attacker gửi) và cái authenticated request này (do client gửi) rồi xử lý chung một lần. Nguyên nhân server xử lý như thế là do cái cờ keep-alive ở request đầu tiên. Thành ra lúc này nhóm request trở thành như sau (màu cam là attacker gửi, màu xanh là client gửi):
GET /iphone/login HTTP/1.1\r\n Host: ebank.com\r\n Connection: keep-alive\r\n \r\n GET /account/transfer?amount=1000&receiver=attacker HTTP/1.1\r\n Host: ebank.com\r\n Connection: close\r\n X-ignore-this:GET /index HTTP/1.1\r\n Cookie: AuthMe=Now\r\n \r\n
Ở đây cái header X-ignore-this đã vô hiệu hóa cái request GET /index HTTP/1.1 của client, đồng thời chôm luôn cookie của client để gắn vào cái unauthenticated request GET /account/transfer?amount=1000&receiver=attacker. Rất hay!
3. Hướng tấn công số 3
Đây là hướng tấn công mạnh nhất, không cần server phải có cấu hình đặc biệt gì để thực hiện. Sự khác biệt cơ bản giữa tấn công này với hai hướng tấn công vừa rồi là trong trường hợp này, client bắt đầu quy trình renegotiation.
Ý tưởng thực hiện tấn công rất giống với hướng 2, chỉ khác nhau ở bước số 2, attacker sẽ không gửi GET /iphone/login nữa mà gửi trực tiếp luôn request của hắn, kèm theo một cái “X-ignore-this” header.
Ngay sau khi gửi cái request đó, attacker sẽ forward cái CLIENT_HELLO thu được ở bước 1 sang cho phía server để bắt đầu quy trình renegotiation. Khi đã renegotiate xong, client sẽ gửi request ban đầu của mình đến server, lúc này toàn bộ request sẽ trông như sau (phần màu cam của attacker gửi, phần màu xanh của client gửi):
GET /account/transfer?amount=1000&receiver=attacker HTTP/1.1\r\n Host: ebank.com\r\n Connection: close\r\n X-ignore-this: GET /index HTTP/1.1\r\n Cookie: AuthMe=Now\r\n \r\n
Tương tự ở trên, X-ignore-this đã vô hiệu hóa request của client và chôm cookie để biến request của attacker thành authenticated. Không cần keep-alive, không cần server phải có cấu hình đặc biệt gì cả!
How to recover RSA private key in a coredump of ssh-agent – Sapheads HackJam 2009 Challenge 6
October 1, 2009 by thaidn · Leave a Comment
Last week or so I joined CLGT to take part in HackJam 2009 by Sapheads. AFAIK this is the first CTF that Sapheads organizes, but they had done a very good job. To most people’s surprise, the contest attacted quite a lot of teams from around the world, and it had quickly become an international competition.
Did I tell you that we’re the winner? Ha ha ha this is our very first win since the name CLGT was born.
BTW, HackJam 2009 was a success because Sapheads had kept their promise which is to “provide challenges that greatly resemble real world scenarios and environments, at the same time, adding fun and educational ingredients to them”. We really had fun ^_^, not disturbing pains *_*, in solving the challenges. Thank you Sapheads! We’re looking forward to HackJam 2010.
I promised to some people in #sapheads that I would release some writeups about the challenges after the contest ended, and here they are. Sorry for the delay, I have been busy working with vendors on this which you may want to read too.
I’ll post writeups of challenge 4, 6, 7, 8, 9 on this blog and CLGT’s homepage in the next two weeks. These are the challenges that I solved or helped to solve. I leave out challenge 1 and challenge 2 because they are trivial. I was out or sleeping when the team solved challenge 3 and 5, so I guess I don’t write nothing about them either. You can download all the binaries in the contest here.
I hope you enjoy reading them as much as I enjoy writing them.
——-
As the title of this entry suggests, this is the writeup of challenge 6 which is IMHO the coolest CTF challenge ever :-D. I really like its concept and implementation. Thank you whats ^_^!
For those who didn’t take part in HackJam, these two men are the criminals who had stolen and leaked a new album of SNSD (you should check these girls out ^_^). All teams are in charged of tracking them down.
As you can see in the comic, the criminals got a coredump when they were trying to transfer a MP3 to his server at 67.202.60.164. And that coredump is the only file given in challenge 6.
So first thing first:
$ file core.6261 core.6261: ELF 32-bit LSB core file Intel 80386, version 1 (SYSV), SVR4-style, from 'ssh-agent'
As you can see, this is a coredump of ssh-agent. This is from the manpage of ssh-agent:
ssh-agent is a program to hold private keys used for public key
authentication (RSA, DSA). The idea is that ssh-agent is started in the
beginning of an X-session or a login session, and all other windows or
programs are started as clients to the ssh-agent program.
The manpage doesn’t tell where ssh-agent stores private keys, but one can guess that it would store them in its memory. When ssh-agent crashes, all of its memory content would be written to the coredump. So I guess the idea of challenge 6 is to recover the private key of the criminals stored in the coredump, and use that private key to SSH into 67.202.60.164 which may give me the key for challenge 7.
This is why Sapheads challenges are better than other CTFs. They don’t try to distract us by giving false trails. They give good trails which can be deduced using logical thinking. This challenge also greatly resembles real world scenario which in turn makes it much more interesting than other non-sense challenges somehow created only to show that their authors are smarter than others.
Okie enough bullshit, let’s get back to challenge 6. As whats from Sapheads suggests, I take a look at static variables of ssh-agent.c which are defined like below:
typedef struct identity {
TAILQ_ENTRY(identity) next;
Key *key;
char *comment;
u_int death;
u_int confirm;
} Identity;
typedef struct {
int nentries;
TAILQ_HEAD(idqueue, identity) idlist;
} Idtab;
/* private key table, one per protocol version */
Idtab idtable[3];
int max_fd = 0;
/* pid of shell == parent of agent */
pid_t parent_pid = -1;
u_int parent_alive_interval = 0;
/* pathname and directory for AUTH_SOCKET */
char socket_name[MAXPATHLEN];
char socket_dir[MAXPATHLEN];
So right above socket_name are 3 integers, and then comes idtable which contains pointers to Identity structures, which in turn contains pointers to Key structures. This is how a Key structure looks like (it’s in openssh/key.h):
struct Key {
int type;
int flags;
RSA *rsa;
DSA *dsa;
};
Ah RSA structure! This is how it looks like (it’s in openssl/rsa.h):
struct rsa_st
{
/* The first parameter is used to pickup errors where
* this is passed instead of aEVP_PKEY, it is set to 0 */
int pad;
long version;
const RSA_METHOD *meth;
/* functional reference if 'meth' is ENGINE-provided */
ENGINE *engine;
BIGNUM *n;
BIGNUM *e;
BIGNUM *d;
BIGNUM *p;
BIGNUM *q;
BIGNUM *dmp1;
BIGNUM *dmq1;
BIGNUM *iqmp;
/* be careful using this if the RSA structure is shared */
CRYPTO_EX_DATA ex_data;
int references;
int flags;
/* Used to cache montgomery values */
BN_MONT_CTX *_method_mod_n;
BN_MONT_CTX *_method_mod_p;
BN_MONT_CTX *_method_mod_q;
/* all BIGNUM values are actually in the following data,
* if it is not NULL */
char *bignum_data;
BN_BLINDING *blinding;
BN_BLINDING *mt_blinding;
};
typedef bignum_st BIGNUM;
struct bignum_st
{
BN_ULONG *d; /* Pointer to an array of 'BN_BITS2' bit chunks. */
int top; /* Index of last used d +1. */
/* The next are internal book keeping for bn_expand. */
int dmax; /* Size of the d array. */
int neg; /* one if the number is negative */
int flags;
};
As one can expect, a RSA structure contains n, e, d, p, q and some other data. We need to get these numbers, which are stored in BIGNUM structures, out of the coredump. So the next thing to do is know where idtable is in the coredump. I load the coredump into bless, my favourite hex editor (click on the image to zoom in):

As you can see, at offset 0×13b0 is socket_name. Based on the analysis of the last paragraph, we can guess that max_fd, parent_pid, and parent_alive_interval are stored in 12 bytes between offset 0×13a4 and 0×13af. Then one can ask, what are those 16 null bytes from offset 0×1394 to 0×13a3? I don’t know. If you know, please drop me a line. But I know for sure, idtable is stored in 36 bytes from offset 0×1370 to 0×1393, just right before those weird 16 null bytes.
The idtable array has 3 entries, one for each SSH protocol version. Each entry is an structure whose length is 12 bytes (hence 36 bytes for the whole array). When ssh-agent starts, each Idtab is initiated like this:
TAILQ_INIT(&idtable.idlist); idtable.nentries = 0;
where TAILQ_INIT is a macro defined in sys/queue.h as following:
#define TAILQ_INIT(head) do {
(head)->tqh_first = NULL;
(head)->tqh_last = &(head)->tqh_first;
} while (/*CONSTCOND*/0)
So after initiation, an Idtab structure would contain 1 zero integer for nentries, 1 null pointer for idlist.tqh_first, and 1 pointer for idlist.tqh_last which points back to idlist.tqh_first. Looking at the coredump, one can see that the first two Idtab entries of idtable don’t contain any key information because their nentries is 0. This is as expected, since SSH protocol 1 and 1.1 are long deprecated. The last entry of idtable is the Idtab of SSH protocol version 2. As one can see, its nenetries is 1, and we can guess that the pointer 0×0806E3B8 is pointing to an identity structure which contains 4 pointers and 2 integers. Let’s see if this is the case:
$ gdb /usr/bin/ssh-agent core.6261 Core was generated by `/usr/bin/ssh-agent'. [New process 6261] #0 0xb7f55424 in __kernel_vsyscall () (gdb) x/6x 0x0806E3B8 0x806e3b8: 0x00000000 0x0806297c 0x0806e158 0x0806e3a8 0x806e3c8: 0x00000000 0x00000000 (gdb) x/s 0x0806e3a8 0x806e3a8: "id_rsa"
As you can see, the 4th pointer is comment. So the 3rd pointer should be key, i.e. it should point to a Key structure which contains 2 integers and 2 pointerss:
(gdb) x/4x 0x0806e158 0x806e158: 0x00000001 0x00000000 0x0806e170 0x00000000
If everything is correct, the pointer 0×0806e170 should point to a RSA structure:
(gdb) x/22x 0x0806e170 0x806e170: 0x00000000 0x00000000 0xb7ed5840 0x00000000 0x806e180: 0x0806e210 0x0806e228 0x0806e240 0x0806e288 0x806e190: 0x0806e270 0x0806e2b8 0x0806e2a0 0x0806e258 0x806e1a0: 0x00000000 0x00000000 0x00000001 0x0000000e 0x806e1b0: 0x00000000 0x00000000 0x00000000 0x00000000 0x806e1c0: 0x0806e950 0x00000000
How to be sure this is a RSA structure? Is there any known value to test? Fortunately, the answer is yes. If this is a RSA structure, the 6th pointer 0×0806e228 should point to a BIGNUM structure containing the value of the e parameter which should be 0×23, the default value that ssh-keygen uses for e. Let’s see:
(gdb) x/5x 0x0806e228 0x806e228: 0x0806e2e0 0x00000001 0x00000001 0x00000000 0x806e238: 0x00000001 (gdb) x/2x 0x0806e2e0 0x806e2e0: 0x00000023 0xb7d39150
Yay! We got it!
The next step is to extract all the parameters. It was not as easy as it sounds though. I spent quite a lot of time to read out the value of these parameters due to my ignorant of big-edian and little-edian storage. But I managed to get them out eventually. I generated the RSA private key from p and q, and used it to SSH into 67.202.60.164 which indeed gave me the key for challenge 7. I got my first breakthrough he he he.
That’s it. Thanks for reading.
Err…but how to generate RSA private key from n, d, e, p and q? I’m glad that you ask. Tools like openssl can not help in this case. You must write your own tool. I suggest you taking a look at ASN.1. There’s a very good tutorial here.
If you understand ASN.1, I’m pretty sure you’d know how to generate RSA private key from its parameters. You can use pyasn1 which is a very good ASN.1 library for Python. I can’t release my tool because it’s part of the upcoming CTF that I’m organizing. After that CTF, I’ll update this post with the tool.
WOWHacker CTF – Crypto Challenges
August 25, 2009 by thaidn · Leave a Comment
Challenge 1
Challenge 1 is…crazy hahaha. Only one or two teams could solve it until the author (hello hinehong :-D) gave out a list of 7 hints. I have designed some web-related crypto challenges (which you will see soon ^^) so I think the difficulty of challenge 1 relies on how fast people can guess the meaning of the cookie. It would be easier for the teams if the author sets the cookie as cookie = cipher + “|” + key. BTW, here’s my solution.
When you access the link above, you’ll see a bunch of javascripts. After decoding those javascripts (which I leave as exercise for readers), you’ll see a form whose target is http://221.143.48.96:8080/you_are_the_man_but_try_again.jsp. This form accepts a parameter named “hong” which is either true or false. If you set hong=true, the server sends back a cookie like below:
id=<code>pKCdQgyJb4dziUESVUv+5qBIoGwQgL2WB@ae506e</code>
This looks like a base64 encoded string, but it’s not. In fact you need to modify it a little bit before you can base64 decode it. This is, as I said previously, why this challenge is hard.
One trick I learn from this challenge is to guess the boundary between key and cipher text, one should try to truncate one character a time and base64 decode the string until he gets an output whose length in bytes is a multiple of 8 or 16, which are common block cipher’s block length.
The cookie can be either “cipher + key” or “key + cipher”, so one should try the above process in both cases. If you can’t find any such output, then you know your theory is wrong, i.e. the cookie is in some other form. Fortunately, my theory is right in this case.
It turns out that the first 32 bytes of the cookie is the cipher text, and the rest 8 bytes is the key. It’s a 8-bytes key, so this cookie should be encrypted by DES which is a popular 8-bytes key block cipher. I wrote a small python script to decrypt the it:
>>>from Crypto.Cipher import DES >>>cookie = 'pKCdQgyJb4dziUESVUv+5qBIoGwQgL2WB@ae506e' >>> key = cookie[32:] >>> data = base64.b64decode(cookie[0:32]) >>> des = DES.new(key, DES.MODE_CBC) >>> des.decrypt(data) 'wowhacke\xd6\xe0\xbc*e\xe7\n\xc7\x1a\xf92w6H\xfd\xe5'
Hmm. I remembered I tried to submit ‘wowhacke’ to the scoring server, but, of course, it’s not the correct answer. Then I wasted the next hour to test various stupid theories to understand what the last 16 bytes of the output are.
It was not until I nearly gave up on this challenge, I realized the obvious: this is mode ECB stupid!!! Why on earth I always thought it’s CBC? I changed the mode, and the result is:
>>> des = DES.new(key) >>> des.decrypt(data) 'wowhacker@!hine@ipsec\x03\x03\x03'
Remember those ‘\x03\x03\x03′. You’ll see them again in my crypto challenges ;-).
Challenge 10
Challenge 10 is cool. In summary, the author sets a RSA private key as a property of a Java object, then he gives out the serialization stream of that object, and asks teams to recover the private key to decrypt a ciphertext.
So the first thing we must do is to understand how Java does serialization. I was never a fan of Java, so this is something completely new to me. But that’s why I really enjoy playing capture the flag games. It forces me to learn new thing fast in a very short time.
I spent nearly 1 hour reading the spec, mostly on the object serialization stream protocol. Then I spent one and a half hour starring at my hex editor screen and acting as a binary parser which was, I don’t know why I feel that, really fun (later on Tora of SexyPwndas fame showed me a much less painful way to recover the object. Thanks Tora!)
I recovered the RSA private key eventually. How to use it to decrypt the ciphertext in key.txt? While de-serializing the object, you would see that there’s a field named tripleDesKey containing a 24-bit string which you can get by base64-decoding the last 32 bytes of the serialized object.
At first I thought I should use the RSA key to decrypt this 24-bit string to get the real tripleDesKey, and uses that key in turn to decrypt key.txt. This hybrid approach is the standard way to do encryption using public key cryptosystem. But you shouldn’t expect anything standard in CTF, rite?
It turns out all that tripleDes key and ciphertext are just there to distract me. I have to admit that I don’t like challenges giving false trails. You can either give good trails or no trail at all. Giving false trail is a sin :-P.
Anyway, if you look at key.txt, you’ll see that its content is a 128-bytes string. 128-bytes = 1024-bit = the size of the modulus in the recovered RSA private key. So this string should be the ciphertext encrypted directly using the RSA private key. Indeed it is!
>>> from M2Crypto.RSA import *
>>> rsa = load_key('my_rsa_key')
>>> data = open('key.bin').read()
>>> rsa.private_decrypt(data, 3)
'\x00\x02#padding#x00isec@#$wowhacker!!'
There’s a small minor issue that I intentionally left out. Can you find out what it is and resolve it yourself?
I hope you enjoy reading this. Happy hacking!
Vá lỗi cho gói openssl
May 14, 2008 by lukas · Leave a Comment
Các phiên bản có chứa lỗi:
* Ubuntu 7.04 (Feisty)
* Ubuntu 7.10 (Gutsy)
* Ubuntu 8.04 LTS (Hardy)
* Ubuntu “Intrepid Ibex” (development): libssl <= 0.9.8g-8
* Debian 4.0 (etch) (see corresponding Debian security advisory)
Debian có cung cấp một script kiểm tra key, nếu status trả về weak tức là hệ thống của bạn có lỗi. Bạn nên update hệ thống ngay lập tức.
http://security.debian.org/project/extra/dowkd/dowkd.pl.gz
http://security.debian.org/project/extra/dowkd/dowkd.pl.gz.asc
Việc kiểm tra này cần phải tính toán lại nên có thể khá lâu, bạn có thể bỏ qua bước này và chỉ cần apt-get update/upgrade mà thôi, hiện các repo đã có đầy đủ các gói cập nhật.
Có người đã gọi sự cố này là ngày “Thảm họa Debian” sau sự kiện năm 2003.
Links:
Linux security
Planet Debian
Debian openssl package fix predictable random number generator
May 13, 2008 by RD · Leave a Comment
Debian developer vào 05/2006 đã chỉnh sửa lại code của OpenSSL sau khi sửa lỗi “uninitialized variable” do valgrind cảnh báo. Tuy nhiên do không hiểu đây là sự cố tình sử dụng biến không được khởi tạo như một yếu tố “ngẫu nhiễn” của OpenSSL developer, Debian developer đã loại bỏ một số đoạn mã trong hàm PRNG khiến cho cho bộ sinh số ngẫu nhiên này chỉ được “seed” bởi process pid từ hệ thống. Điều này dẫn đến thư viện OpenSSL do Debian cung cấp này chỉ sinh ra 32,768 cặp khóa duy nhất từ PRNG, đồng nghĩa với việc độ an toàn của các khóa RSA, DSA, … chỉ còn là 15 bits.
Các mã khóa được sinh ra từ các gói có sử dụng thư viện OpenSSL bị lỗi này như SSH, OpenVPN, DNSSEC, X.509 certificates đều cần phải được sinh (generate) lại khóa từ đầu.
Đây là một ví dụ điển hình cho việc vì sao các nhà đóng gói phần mềm không nên tự ý chỉnh sửa code của các thư viện, phần mềm nếu không hiểu rõ về nó và nếu có chỉnh sửa thì nên gửi lại bản patch cho nhà phát triển của gói phần mềm đó để kiểm tra và cập nhật.
Links:


