Case When

DATE : 2023/12/17 ~ 19

[ Thinking About ] : Case When

이번 Post에서는 주어진 아래의 Query가 무엇을 의미하는 지에 대해 공부해볼 것이다.

sotingAd=,(case+when+ascii(substr((select+user+from+dual),1,1))=0+then+1+else+(1/0)+end)

무엇을 의도하고 위와 같은 Query를 작성한 것인지 알아보기 위해 우선,

case when ascii(substr((select user from dual),1,1))=0 then 1 else (1/0) end

case when에 작성된 내용을 살펴볼 필요가 있다.

case when은 if문과 같은 기능을 담당한다.

case when (condition) then (true) else (false) end

condition 위치에 조건을 작성해 넣으면, 해당 조건의 결과에 따라 실행되는 내용을 달리할 수 있다.

만약 조건의 결과가 참이라면, true 위치에 작성한 내용이,

조건의 결과가 거짓이라면, false 위치에 작성한 내용이 동작한다.

따라서 위에 작성된 case when은

ascii(substr((select user from dual),1,1))=0

(1) select user from dual : dual table에서 user 값을 가져와

(2) substr((select ~~),1,1) : 결과의 첫 번째 문자만 잘라냈을 때

(3) ascii(substr()) : 문자의 아스키 값이 0이냐

라는 조건을 작성한 것이고

case when (condition) then 1 else (1/0) end

그 결과가 참이라면 1을, 거짓이라면 (1/0)을 반환한다.

조건이 참인 경우에만 1이 반환 되어 에러가 발생하지 않기 때문에

True or False를 구분할 수 있게 짜여진 Query임을 알 수 있다.

이때 dual table은 Oracle DB에서 사용하는 조금은 특수한 테이블로

dual이 Query에 포함되어 있으니 이 Query는 oracle DB를 대상으로 함을 알 수 있다.

또한 조건에서 말한 아스키 값이 0이라는 건, NULL을 의미하고

false일 때 반환 되는 (1/0)은 에러를 일으키는 값으로

Condition의 결과가 참인 경우에만 에러가 발생하지 않음을 통해

Condition 내부에 작성한 select문의 결과를 구분할 수 있게 되는 것이다.

그렇다면 전체적인 Query는 이해를 했으니, 이 결과가 과연 어느 위치에 주입 되는 것인지 예측해보자!

sotingAd=,(case+when+ascii(substr((select+user+from+dual),1,1))=0+then+1+else+(1/0)+end)

Query의 결과를 sotingAd 라는 값에 할당하는 와중에

sotringAd=,1 (or (1/0))

대입 연산자 뒤에 콤마가 하나 찍혀있는 걸 볼 수 있다.

콤마와 숫자가 들어갈 만한 부분을 SQL에서 고려해본 결과,

SELECT ~~~ order by COLUMN(or index)

order by가 제일 먼저 떠올랐다. order by는 SELECT문 제일 뒤에 위치하며

SELECT문의 결과로 나온 record를 어떤 column 기준으로 정렬할 것인지 지정하는 역할을 한다.

이때 column name 대신 index를 사용할 수 있는데,

~~~ order by COLUMN_NAME(or index)

만약 서버에 이미 order by가 작성되어 있는 상태라고 해도 콤마를 붙이면

최대 column 인덱스를 넘어가지 않는 선에서 index를 추가적으로 전달할 수 있다.

이 말이 무엇을 의미하는 지 직접 SQL을 실행하면서 확인해보자.

[ Oracle DB ] : SQL 실행해보기

우선, SQL을 실행하기 위해 사용할 Table을 만들어준다.

처음으로 실행해볼 SQL은 name column 기준으로 Table을 정렬하라는 질의이다.

select * from member order by name;

이 SQL을 실행하게 되면 name이 알파벳 순서대로 오름차순 정렬된 걸 확인할 수 있다.

여기서 추가적으로 확인해보고 싶은 건!

만약 "콤마를 추가해 index를 여러 개 나열하더라도 order by가 정상적인 실행을 하느냐"이다.

이를 확인해보기 위해 1을 하나씩 붙여 보기로 했다.

select * from member order by name,1;

기본적으로 주어진 name column과 분리해 1을 하나 더 붙여도 앞선 SQL 결과와

동일한 결과가 나오는 걸 볼 수 있다.

여기에 1을 하나 더 붙이고

select * from member order by name, 1, 1;

실행해도 결과가 바뀌지 않는다!

혹시나 해서 1을 붙이고 붙이고 붙이고 붙여 봤더니

그래도 결과가 변화 없이 나오는 걸 확인할 수 있었다.

아직 위의 SQL이 왜 동작이 되는 지, 결과가 왜 동일한 지에 대해서는 알아내지 못했지만

여기서 중요한 건 콤마로 추가적인 index를 나열하더라도 실행이 정상적으로 된다는 사실이다.

다만, 위에서 말했듯이 column index의 최댓값을 넘어가게 되면 에러가 발생한다.

현재 member table에는 두 개의 column이 존재하기 때문에 이를 넘어간 3을 index로 넣게 되면

이와 같이 에러가 나게 된다.

이러한 사실을 바탕으로 우리에게 주어진 SQL의 결과를 확인해보도록 하자.


[ Step By Step ]

sotingAd=,(case+when+ascii(substr((select+user+from+dual),1,1))=0+then+1+else+(1/0)+end)

우리가 고민해보기로 한 SQL을 다시 떠올려보면 결과가

(1)1 또는 (1/0)로 나오게 된다는 점과 (2)콤마가 사용되었다는 점으로 미루어보아

order by에 삽입되는 SQL이라 예측했다.

그렇다면 1 또는 (1/0)이 각각 order by에 들어갔을 때,

어떤 결과를 가져오길래 위와 같은 SQL이 작성된 것인지 하나씩 실행해보도록 하자.

1] select user from dual

일단 조건문의 결과가 참인지 거짓인지 파악하기 위해 select문부터 파헤쳐보자!

select user from dual

이 Query를 실행하기 전에 dual table에 대해 짧게 설명하자면,

Table dual은 user sys 소유의 테이블이지만 다른 모든 사용자가 접근 가능한 테이블이다.

Table dual의 정보를 조회해보면

X만을 가지는 Dummy column으로 구성된 걸 확인할 수 있다.

그렇다면 dual에 user column 값은 없는 거겠네?? 라고 생각할 수 있지만

dual의 user column 값을 조회해보면 현재 DB에 접속한 Username인

"system"이라는 값이 나오는 걸 볼 수 있다.

(SYS로 접속한 상태로 실행하면 SYS라 나옴)

2] substr((select user from dual),1,1)

select문의 결과를 확인해보았으니, 그 다음 실행되는 substr()에 대해 살펴보자.

substr((select user from dual),1,1)

이 Query는 "select문의 결과를 첫 번째 문자부터 한 글자만 잘라와!"라는 의미이기 때문에

substr("SYSTEM",1,1) => "S"

알파벳 S가 결과로 나올 것을 쉽게 예측할 수 있다.

3] ascii(substr((select user from dual),1,1)) = 0

ascii()에 주어진 값이 알파벳 S임을 알았으니 이제 조건문의 결과를 알아낼 수 있다.

ascii(substr((select user from dual),1,1))
:> ascii(substr("SYSTEM",1,1))
:> ascii("S")

알파벳 S의 아스키 값을 확인해보면 83이 나온다.

그런데, 현재 조건문에서는 이 값이 0과 같다고 비교하기 때문에

주어진 SQL의 조건문 결과가 거짓임을 알 수 있다.

따라서 기본적으로 order by절이 작성되어있다는 가정 하에

거짓인 조건문의 결과로 (1/0)이 반환 되면, 이와 같이 에러가 발생하게 된다.

그럼 만약 SQL 결과가 참(1)인 경우에는 어떻게 될까?

(1/0)이 들어갔을 때와 달리 에러가 발생하지 않는 걸 볼 수 있다.

에러의 발생 유무가 다르기 때문에 이로써 Blind SQLi을 수행할 수 있다고 판단이 가능한 것이다.

위에서 기본적으로 order by가 작성되어 있다고 가정한 이유는 💡

주어진 SQL에서 콤마가 쓰이고 있는데, 콤마 앞에 아무런 값도 없다고 한다면

뒤에 어떤 값이 들어가든 에러가 나기 때문이다.

어떤 경우이든 에러가 난다는 건, 주입한 SQL의 결과가 참인지 거짓인지 구분할 수 없다는 의미이기에

Blind SQLi를 수행하기엔 적절하지 못한 상황이라 할 수 있다.

그럼 반대로!

만약 콤마를 사용하지 않는다면 1 또는 (1/0)이 가져다 주는 차이가 존재하는 것일까?? 궁금해졌다.

select name from member order by ___

우리에게 주어진 SQL의 결과가 바로 빈칸에 들어가는 구조라 가정해보면

조건문의 결과가 참일 때는 name column을 기준으로 오름차순 정렬된 결과가 나오고

조건문의 결과가 거짓일 때는 위와는 다른 순서로 결과가 정렬되는 걸 확인할 수 있다.

사실 이는 table에 들어가 있는 순서대로 결과가 나온 것이다.

즉, 콤마를 사용하지 않고 SQL의 결과가 바로 order by에 들어간다면

결과의 정렬 유무를 통해 우리는 조건문의 결과를 알아낼 수 있다.


자, 다시 돌아와서!

마지막으로 전체 SQL의 실행 결과를 확인해보도록 하자.

우리가 예측한 대로 SQL이 order by에 들어간다고 가정해보면

select * from member order by 1,(case when ascii(substr((select user from dual),1,1))=0 then 1 else (1/0) end);

조건문의 결과가 거짓이기 때문에 case when의 결과로 (1/0)이 나오는 걸 알 수 있고

결과적으로 에러가 발생한다는 걸 이제는 이해할 수 있을 것이다.

만약 반대로, 조건문의 결과가 참이라면 case when의 결과로 1이 나오기 때문에

에러 없이 결과가 나오는 걸 볼 수 있다.

이렇게 해서 case when의 결과에 따라 에러 발생의 유무를 통해 Blind SQL injection을

실행하기 위한 Query라고 추측을 해볼 수 있다!! 👏

Last updated