PHP Magic Hash Vulnerability 취약점

HYEONG HWAN, MUN/ 5월 14, 2015/ 미분류/ 0 comments

https://blog.lael.be/post/1238

PHP 의 Magic Hash 취약점에 대해서 알아보았다.

 

 

실제로 Magic Hash 는 취약점이 아니라 특수동작입니다.

 

이론

우리가 다루는 대부분의 자료형은 “문자형(String)” 입니다.

일부 프로그래밍 언어(Some programming language)에서는 String 과 그 외 다른 자료형과 쉽게 쓸 수 있도록, Auto casting(=Type Juggling) 을 지원합니다.

 

krustjuggle

<이게 바로 Juggling 입니다>

 

아래는 실제로 동작하는 Type Juggling 의 예제입니다.

영어 단어 뜻, int = 숫자자료형, string = 문자자료형, concatenation = 결합 .

 


// PHP

$age = 10;

$msg = "I am " . $age . " years old";

echo $msg;

int 값 10string 과 concatenation 하기 위해서 string 으로 타입 변환됩니다.

 


// javascript

var age = 10;

var msg = "I am " + age + "years old";

alert(msg);

int 값 10string 과 concatenation 하기 위해서 string 으로 타입 변환됩니다.

 

-- mysql
-- `idx` field type is int,   but '28'  is string.
SELECT * FROM `member` WHERE `idx` = '28' ;

string 값 28int 필드타입의 쿼리를 날리기 위해서 int 로 타입 변환 됩니다.

 

이중에 몇가지 주의해서 사용해야 하는 것이 있는데, 바로 특수 숫자포맷 형태 입니다.

0xC  는 (hex)16진수 C 를 뜻합니다.   (10진수로는 12)

0o11 는 (octet)8진수 11 을 뜻합니다. (10진수로는 9)

7e3(exponential) 지수를 뜻합니다. (7 * 10^3 = 7000.)

 

이때 지수 표현식에 특수한 경우가 있습니다.

바로 앞 숫자를 0 으로 하는 것이죠.

 

0e7 = 0 * 10^ 7 입니다.  계산하면 0 이죠.

즉 0e12345 와 0e67890 은 동일한 값이 됩니다. (둘다 0)

0e12345 = 0 * 10^12345

0e67890 = 0 * 10^67890

 

동작 예제

프로그래밍 언어는 양쪽을 컴퓨터가 비교하기 편한 자료값으로 최대한 바꾸고 비교합니다.

- PHP 는 좌우가 같은 자료형이어도 최적의 자료형을 찾습니다.

- JAVASCRIPT 는 좌우가 같은 자료형이면 따로 자료형 변환을 하지 않습니다.

-  MYSQL 은 fieldtype 자료형으로 변환합니다. 변환이 실패하면 mysql_error 가 반환됩니다.

스크린샷 2015-05-15 오전 12.18.36

String -> float -> int  를 더 선호합니다.

‘123’ == 123   (양쪽 int 비교 가능, 좌변이 strong->int 로 형변환됩니다.)

‘1.5’ == 1 (양쪽 float 비교 가능, 좌변 string->float // 우변이 int->float 으로 형변환됩니다.)

‘0e1234’ == 0 (양쪽 float 비교 가능, 좌변 우변 모두 float 으로 형변환됩니다.)

‘0e1234’ == 0.0 (양쪽 float 비교 가능, 좌변 우변 모두 float 으로 형변환됩니다.)

‘0e1234’ == ‘0e5678’ (양쪽 float 비교 가능, 좌변 우변 모두 float 으로 형변환됩니다.)

‘0e1234abcd’ == ‘0e5678’ (양쪽 string 비교 가능, 형변환 되지 않고 비교합니다.)

0e1234 는 float 으로 변환이 가능하지만 0e1234abcd 는 분명한 string 입니다.

 

취약점 예제1

어떤 사용자가 아이디가 admin , 비밀번호가 0e1234 라고 가정합니다. (매우 매우 특이한 경우)

(Ex.  ID : admin , PW : 0e1234 )

MYSQL 의 경우 필드에 field type 를 설정하게 되어 있습니다.

 

해커가 admin, 0e5678 을 입력했다고 하죠.

SELECT * FROM `member` WHERE `id` = 'admin' AND `pw` = '0e5678'

This query is not vulnerable.

`id`, `pw` 필드타입을 보통 문자형(varchar,char,text)라고 할 경우 string 이 목적타입 이므로 안전하게 동작합니다.

쿼리를 실행할 때 형변환 하지 않습니다. 별다른 문제사항이 일어나지 않습니다.

 

!!하지만 만약에!!

코드가 다음과 같이 아이디를 먼저 불러오고, 그 후 비밀번호를 비교하도록 짜여있다면 로그인이 성공하게 됩니다.

$userinfo = mysql_query("SELECT * FROM `member` WHERE `id` = 'admin'");

//php try not string compare, but float compare.

다음의 코드가 Magic Hash Vulnerability 를 뜻합니다.

// $userinfo['pw'] is 0e1234.
// if $_POST['pw'] is 0e4152, PHP do float compare.
if ($userinfo['pw'] == $_POST['pw']) {
    echo 'success!';
}
else {
    echo 'failed';
}

 

취약점 예제2 (sql injection)

이 경우, magic hash 취약점 이외에 + sql injection 이 가능해야 합니다.

--
-- 테이블 구조 `users`
--

CREATE TABLE `users` (
  `id` varchar(50) NOT NULL,
  `hashed_pw` varchar(50) NOT NULL
) ;

--
-- 테이블의 덤프 데이터 `users`
--

INSERT INTO `users` (`id`, `hashed_pw`) VALUES
('admin', 'fc5e038d38a57032085441e7fe7010b0'),
('user1', 'a722c63db8ec8625af6cf71cb8c2d939'),
('user2', 'c1572d05424d0ecb2a65ec6a82aeacbf');

--
-- 테이블의 인덱스 `users`
--
ALTER TABLE `users`
  ADD PRIMARY KEY (`id`);

아래의 디비 테이블이 생긴다.
ID와 암호화된 PW 를 저장하고 있다.

입력한 pw 값을 md5 한 후 DB 의 값과 비교한다.

하지만 SQL Injection 을 위해서 id 필드에 다음의 값을 입력해보자.

' union SELECT 'admin', 0#

내부적으로 완성된 쿼리

SELECT `id`, `hashed_pw` FROM `users` WHERE `id` = '' union select 'admin',0#'

앞 select 조건에 해당하는 row 가 없으므로 union 뒤의 select 가 첫번째 row 가 된다.


이제 magic hash 를 위해서 pw 필드에 240610708 를 입력하자.  (md5 의 결과가 0e + number 로 구성되는 어느 문자도 가능하다. QNKCDZO 도 magic hash 문자이다.)

md5(240610708) = '0e462097431906509019562988736854'

 

그럼 sql injection 으로 원하는 값을 얻어왔고, magic hash 를 이용해 비교 연산도 넘어갔으니

You are admin

이 된다.

 

근데, 예제를 위해서 저렇게 한 것이고, md5 값과 password union 값만 일치시키면 굳이 magic hash 아니어도 무조건 인증을 넘어가게(pass) 된다.

 

 

대처방법

1) 비밀번호 암호화 함수를 사용합시다. md5() 든, password() 든, sha1() 든.

그 암호화 함수 앞에 자신만의 문자를 붙이세요. “LAEL@” + md5($pw)

 

2) 더 나은 비밀번호 암호화 함수를 사용합시다.

요즘 암호화 함수는 암호화 결과물시작 부분이 거의 $ 아니면 * 입니다.

 

3) 형변환을 하지않는 비교(===)를 사용합시다.

적어도 비밀번호 비교에서는 === 를 무조건 사용합시다.

비교시에 형변환이 일어나지 않으므로 magic hash 취약점이 일어나지 않습니다.

 

라엘이의 결론 (My Conclusion)

취약점이라고 생각하지 않는다.

어떤 사람의 비밀번호 md5 결과물이 0e로 생성될 확률도 극히 낮으므로, 위험성은 very low 정도?

 

다만 ‘0e1234’ 가 평소에는 string 이었다가, 등가비교 (==) 를 할 때에는 float 이 되는 점은 숙지하고 있어야 겠다.

전세계에서 가장 많이 사용하는 데이터베이스인 MySQL과, 전세계에서 가장 많이 사용하는 언어 JAVASCRIPT 및 그 파생언어가 이러한 동작을 하므로 당연히 이 이론을 알고 있어야 할 것이다.

혹시나 나중에 코딩하다가 string 을 0 과 비교하는 구문을 짤 때 이 글이 생각나겠지.

 


 

다음의 문서를 참조하였습니다. (Reference)

https://blog.whitehatsec.com/magic-hashes/

http://php.net/manual/kr/language.types.type-juggling.php

http://php.net/manual/kr/language.types.string.php

 


 

- Magic Hash는 아니지만 Type Juggling 관련 예제를 만들어보았다. 대학교 전공생 중간고사에 나올법한 난이도.

 

스크린샷 2015-11-16 오전 11.00.16

 

스크린샷 2015-11-16 오전 11.02.58

 


<?php

$mem = array();

$mem['id'] = 0;
$mem['pw'] = '0';

$json_mem = json_encode($mem);

// json 은 타입 형태를 손실하지 않고 인코딩한다.
echo $json_mem;

echo "\n======\n";

// json 은 타입 형태를 손실하지 않고 디코딩한다.
$json_mem_dec = json_decode($json_mem, true); // decode as an array.

var_dump($json_mem_dec['id']);
var_dump($json_mem_dec['pw']);

echo "\n======\n";
// 등가비교 예제.

// int 형 비교를 한다. intval(null) 이 0 이기 때문에 true 가 나온다.
var_dump(null == 0);

// string 형 비교를 한다. strval(null) 이 "" 이기 때문에 false 가 나온다.
var_dump(null == '0');

// string 형 비교를 한다. strval(0) 이 "0" 이기 때문에 true 가 나온다.
var_dump($json_mem_dec['id'] == $json_mem_dec['pw']);
echo "\n======\n";

// int 형 비교를 한다. intval(null) 이 0 이기 때문에 true 가 나온다.
var_dump(null == $json_mem_dec['id']);

// string 형 비교를 한다. intval(null) 이 "" 이기 때문에 false 가 나온다.
var_dump(null == $json_mem_dec['pw']);

Leave a Comment

작성하신 댓글은 관리자의 수동 승인 후 게시됩니다.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>
*
*