alien
이번 문제는 사용자의 입력 값(pw)이 2개의 변수(query, query2)에 할당되어 사용되고 있다.
$query 값은 no 파라미터 값을 숫자 형식으로 사용해야 하며, $query2 는 no 파라미터를 문자 형식으로 사용해야 한다.

문제를 풀기 위해서는 아래 조건을 만족해야 하는데,
1번째 조건에서는 $r['id'] 값이 “admin” 이어야 하고,
2번째, 3번째 조건에서는 $r['id'] 값이 “admin” 이면 안 되고,
4번째 조건에서는 $r['id'] 값이 “admin” 이어야 한다.

일단 한 단계씩 조건문을 통과하기 위해서 테스트 쿼리를 작성해보았다.
$query 는 no 파라미터에 입력한 값이 숫자로 인식되고 $query2 는 no 파라미터에 입력한 값이 문자로 인식된다.
따라서, 아래와 같이 페이로드를 작성하여 각각 쿼리 삽입이 가능하도록 하였다.
( 브라우저 상에서는 보이지 않지만 query2 에도 쿼리를 삽입 가능하도록 하기 위해 중간에 개행(%0A) 문자도 삽입했다. )
# query → ' UNION SELECT CHAR(97,100,109,105,110)#' UNION SELECT CHAR(97,100,109,105,110)#
# query2 → ' UNION SELECT CHAR(97,100,109,105,110)#' UNION SELECT CHAR(97,100,109,105,110)#'
(공격 페이로드) ?no=%27%20UNION%20SELECT%20CHAR(97,100,109,105,110)%23%27%0A%20UNION%20SELECT%20CHAR(97,100,109,105,110)%23

위 방법을 사용하면 쿼리문을 삽입할 수는 있지만 $r['id'] 값으로 항상 “admin” 이라는 결과만 반환한다.
때문에, 2번째 조건인 if($r['id'] === "admin") 를 통과하지 못하고 sandbox2 응답이 브라우저에 출력된다.
이를 우회하기 위해서는 특정 조건에 따라서 admin 값이 조회되도록 설정을 해야한다.
언제는 admin 값이 출력되고 언제는 admin 값이 출력되면 안 되는 신기한 조건을 만족해야 한다.
이러한 경우에는 시간 관련 함수인 SLEEP() 혹은 NOW() 함수를 사용해 볼 수 있다.
현재 시간(NOW)에 모듈러 연산을 수행하여 홀수/짝수가 될 때 특정 값이 출력되도록 유도하는 방법이다.
- 아래와 같이 모듈러 연산을 수행하면 현재 시간에 따라서 행의 개수가 3개 혹은 2개가 된다.
- 그리고 조회된 행들 중 LIMIT N,M 을 이용해서 3번째 행(값이 “admin”)을 가져오도록 하면 된다.

다시 코드를 살펴보면 실행된 쿼리($query, $query2)는 각각 2번씩 총 4번 실행된다.
여기서 $r['id'] 값은 순차적으로 각각 아래의 값이 반환되어야 한다.
| 쿼리 | fetch 순서 | id 값 |
|---|---|---|
| query | 1번째 | admin ⭕ |
| query | 2번째 | admin ❌ |
| query2 | 1번째 | admin ❌ |
| query2 | 2번째 | admin ⭕ |
![]() |
SLEEP 함수를 사용하는 경우 mysqli_fetch_array 에 의해서 SLEEP 함수가 총 4번 실행될 것이다.
그 중 “admin” 은 2번 연속으로 출력되어야 하므로 이런 구조로 구성된다.
- ( … O O X X O O X X O O X X … )
만약 SLEEP 함수를 1초로 설정하게 되면 총 4초동안mysqli_fetch_array작업이 발생하므로
HH:MM:S(홀수) → HH:MM:S(짝수) → HH:MM:S(홀수) → HH:MM:S(짝수) 가 되고
”admin” 행을 가져오는 순서가 O X O X 가 된다. 때문에 SLEEP 초를 다시 설정해야 한다.
이번에는 SLEEP 을 0.5초로 설정해보자.
HH:MM:S(홀수) → HH:MM:S(홀수) → HH:MM:S(짝수) → HH:MM:S(짝수) 가 되기 때문에
”admin” 행을 가져오는 순서가 O O X X 가 된다. 앞서 확인한 조건( … X X O O X X O O … )을 만족한다.
진짜 그런지 테스트를 해보면, 아래 결과를 확인할 수 있다. 진짜 그렇다.
( 앞선 쿼리와 조금 달라졌는데 query2 절에서 UNION 절로 바로 admin 행을 가져와서 행 순서가 admin,0,1 이 돼버렸다. )
( 이렇게 되면 LIMIT 1,2 의 행 값이 admin 이 아니라 1 이 되어버리기 때문에 마지막 $r['id'] === 'admin' 조건을 통과하지 못했던 것)
?no=%27%23%27%0A%20UNION%20SELECT%20SLEEP(1/2)%20UNION%20SELECT%20NOW()%2%20UNION%20SELECT%20CHAR(97,100,109,105,110)%20LIMIT%202,1%23

🐛.. 🐛.. 🐛..
번외
@{변수명} := {할당 값} 을 이용하여 IFNULL, COALESCE 함수 대신 활용해볼 수 있다.


