| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | |||||
| 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 10 | 11 | 12 | 13 | 14 | 15 | 16 |
| 17 | 18 | 19 | 20 | 21 | 22 | 23 |
| 24 | 25 | 26 | 27 | 28 | 29 | 30 |
| 31 |
- SoC
- cert
- 사이버 보안
- 해킹
- Bandit
- 정보보호
- 해킹 스터디
- 모의해킹
- Cross-Site Scripting
- 해커
- 정보보안
- write-up
- Blue Team
- 보안 스터디
- http
- 리눅스
- XSS
- CTF
- Cyber Security
- 워게임
- Web
- linux
- THM
- 리눅스 기초
- web hacking
- TryHackMe
- OverTheWire
- 블루팀
- IR
- 보안 관제
- Today
- Total
AnbyMata의 해킹 노트
[THM] SQL Injection - EP.3 (Task 6~10). 完 본문
TryHackMe - "SQL Injection". Task 6~10. Write-up + Extra Study!

출처: https://tryhackme.com/room/sqlinjectionlm
SQL Injection
Learn how to detect and exploit SQL Injection vulnerabilities
tryhackme.com
[6] Blind SQLi - Authentication Bypass
Blind SQL Injection
- In-Band SQLi와 달리, 공격 결과를 화면에서 직접 확인할 수 없음
- 에러 메시지가 비활성화되어 있어 공격의 성공 여부 및 피드백을 받을 수 없음
- 에러 메시지가 안 보일 뿐, SQL Injection 취약점은 여전히 존재함
- 에러 메시지가 아닌 다른 간접적 반응을 통해 DB 구조 및 데이터까지 추출 가능
Authentication Bypass (인증 우회)
- Blind SQLi에서 가장 단순하고 직관적인 방법
- 로그인 폼과 같은 인증 절차를 우회하는 것
- 로그인 폼은 아이디와 비밀번호의 실제 내용보다, 두 값이 DB의 테이블에서 서로 짝이 맞는지 여부만 판단함
→ 즉, SQL 조건식을 조작해 항상 true(참)로 만들면 로그인을 무시(우회)할 수 있음
→ 실제 존재하는 아이디/비밀번호 쌍을 찾을 필요 없이 DB가 true를 반환하도록 만들기만 하면 됩니다.
| [보충 설명] 로그인 검증은 입력한 아이디/비밀번호가 DB 값과 같으냐를 직접 비교하는 것이 아닙니다. 아이디를 "anbymata", 비밀번호를 "asdf123"으로 입력했을 때, 아이디가 "anbymata"이고 비밀번호가 "asdf123"인 사용자가 DB에 존재하냐로 판단합니다. 존재하면 true, 존재하지 않으면 false 라는 단순 참/거짓 결과만으로 로그인 성공 여부를 판단합니다. 즉, SQL 조건식을 조작해서 무엇을 넣든지 true로 만들어버리면 실제 계정 정보와 무관하게 로그인을 우회할 수 있습니다. |
[6-1] Blind SQLi - Authentication Bypass 실습
1) DB에 전달되는 SQL query 분석
- 실제 DB에 전달되는 SQL query는 다음과 같습니다.

- [username], [password] 부분에 로그인 폼에서 입력한 값들이 들어갑니다.
- DB 실행 결과로 1행 이상 존재 → true → 로그인 성공
- DB 실행 결과 없음 → false → 로그인 실패
→ 즉, 로그인 검증에서는 데이터 내용 자체는 중요하지 않습니다.
→ 결과가 있냐/없냐만이 중요합니다.
2) 로그인 폼에 임의의 값 입력해보기
- 로그인 폼에 Username은 "anbymata", Password는 "test123"으로 입력해보았습니다.

= SQL Query의 [username]과 [password] 부분에 사용자 입력값이 그대로 전달됨을 확인했습니다.
3) 임의의 값으로 로그인해보기

= 당연히 로그인에 실패한 모습입니다.
4) SQL Query가 항상 true를 반환하게 하기
- 실제 존재하는 username과 password가 아니더라도 SQL Query가 true를 반환하면 로그인을 통과할 수 있습니다.
- 비밀번호로 ` ' OR 1=1;-- `을 입력하면, SQL Query는 true를 반환하게 됩니다.
- ` ' OR 1=1;-- `을 뜯어보면,
+ ` ' ` = 기존 문자열을 강제로 종료
+ `OR 1=1 ` = "1=1"은 항상 참(true)라서 where 조건을 무조건 참으로 만들 가능성이 생김
+ ` -- ` = 뒷부분을 주석처리시켜 무시

= 로그인을 시도해보면 성공적으로 로그인을 우회했다는 메시지기 출력됩니다.
5) SQL Query가 true를 반환한 이유
- 비밀번호로 ` ' OR 1=1;-- `을 입력했을 때의 SQL Query는

- `--` 부분으로 인해 `LIMIT 1;` 부분이 주석처리되면서 실제로 서버에 전달되는 SQL Query는

→ OR 연산은 두 조건 중 하나라도 참이면 전체 조건이 참이 되어 true를 반환합니다.
- 근데 여기서 앞부분의 조건은 username이 "anbymata"이고, password는 " ", 즉 공백인 사용자를 찾는 것으로 일반적으로는 false일 것입니다.
- 하지만, 뒷부분의 조건인 "1=1"은 무조건 true이기 때문에 이 SQL Query는 무조건 true를 반환하게됩니다.
[7] Blind SQLi - Boolean Based
Boolean Based (이진값 기반)
- Boolean은 true/fase, yes/no, 1/0 가지 두 가지 결과만 존재하는 형태
- Boolean-based SQLi은 우리가 injection(주입)을 시도했을 때 받는 response(응답)이 참인지 거짓인지만 알려줌
- 이를 통해 SQL Injection payload(공격자의 실제 의도를 담은 코드)의 성공 여부 확인
- 결과가 두 가지밖에 없어 서버가 데이터 자체를 보여주지는 않음
- 하지만, 이 두 가지 응답만으로도 DB의 구조와 내용 전체를 열거하는 것이 가능함
[7-1] Blind SQLi - Boolean Based 실습
1) mock browser(모의 브라우저) URL 분석

= "checkuser"를 통해 서버가 DB에게 username이 "admin"인 사용자가 존재하는지 확인할 가능성이 높아보입니다.
- browser의 Response 메시지의 body부분에 `{"taken":true}`를 통해 "admin"이란 username을 가진 계정이 등록되어있음을 나타내는 것 같습니다.
→ 아마 등록되지 않은 username을 넣으면 `{"taken":false}`로 바뀔 것 같습니다.
2) SQL Query 분석
- SQL Query 부분을 살펴보면,

= 우리가 제어 가능한 유일한 입력값은 username 파라미터입니다.
→ username 값을 사용해 SQL Injection을 수행하여 DB가 true를 반환하도록 유도해야 합니다.
3) username에 임의의 값 넣어보기
- username 값으로 "anbymata"를 넣어보겠습니다.

= Response body가 `{"taken":false}`로 바뀐 모습입니다.
= 추가적으로 SQL Query에 제가 입력한 값이 그대로 반영된 모습입니다.
4) users 테이블의 column(열) 개수 파악하기
- 이전과 마찬가지로 UNION 구문을 활용해 column 개수를 파악해봅니다.
| [보충 설명] column(열) 개수는 항상 먼저 파악되어야 하는 요소입니다. 두 SELECT 문의 column 개수가 동일하고 각 column의 데이터 타입이 호환되어야 UNION이 정상적으로 작동합니다. 그리고 우리는 이 UNION을 활용하여 우리가 원하는 데이터를 추가 정보로써 획득하기 때문에 column 개수를 맞추는 것은 필수적입니다. |
- ` anbymata' UNION SELECT 1;-- `을 입력해보면,

= Response body가 `{"taken":false}`입니다.
= 입력한 column 개수가 올바르지 않은 값입니다.
→ user 테이블의 column 개수는 1개가 아닙니다.
5) `anbymata' UNION SELECT 1,2;--` 입력 시도
- user 테이블의 column 개수가 2개인지 확인합니다.

= Response body가 `{"taken":false}`입니다.
= user 테이블의 column 개수는 2개가 아닙니다.
6) `anbymata' UNION SELECT 1,2,3;--` 입력 시도
- user 테이블의 column 개수가 3개인지 확인합니다.

= Response body가 `{"taken":true}`입니다.
= user 테이블의 column 개수는 3개 입니다.
7) 데이터베이스 이름 알아내기
- 현재 사용 중인 DB의 이름을 반환하는 database() 함수를 활용합니다.
- 조건으로 `database() LIKE '%'`을 사용하면, 현재 DB 이름에 문자열이 존재하는지 확인해줍니다.
- "%"는 와일드카드로 모든 문자열을 뜻합니다.
- 따라서, `database() LIKE '%'` 조건 자체는 항상 true입니다. (당연히 이름에 문자열이 존재하겠죠..)
- `anbymata' UNION SELECT 1,2,3 WHERE database() like '%';--`를 통해 UNION 구문이 정상작동하는지 확인합니다.

= Response body가 `{"taken":true}`입니다.
= UNION 구문이 정상적으로 작동합니다.
→ 이 구문을 활용해서 데이터베이스의 이름을 알아낼 것입니다.
8) DB 이름의 첫 글자 확인하기
- 조건으로 `database() LIKE 'a%'`를 사용하면, DB의 이름이 "a"로 시작하는지 확인해줍니다.

= DB의 이름은 "a"로 시작하지 않습니다.
- 이를 응용하여 'b%', 'c%' 등으로 한 글자씩 알파벳을 바꿔가면서 확인해볼 수 있습니다.
- true가 나오는 순간까지 반복하여 DB 이름이 무엇으로 시작하는지 찾아줍니다.
9) DB 이름을 한 글자씩 확인하기
- 동일한 방식으로 두 번째, 세 번째 글자가 무엇인지 찾아냅니다.
| [보충 설명] database() LIKE 'ab%' = DB의 이름이 "ab"로 시작하는가? database() LIKE 'abcd%' = DB의 이름이 "abcd"로 시작하는가? 이런식으로 한 글자씩 찾아나가면 됩니다. 실전에서는 수동으로 한 글자씩 찾는 것이 아닌 자동화 도구를 사용하여 찾아낼 것입니다. |

= 최종적으로 "sqli_three' 라는 DB 이름을 완성하게 됩니다.
10) sql_three DB 안의 Table 이름 확인하기
- DB 이름을 알아낸 방식과 동일하게 한 글자씩 확인해가며 table 이름을 알아냅니다.
- DBMS가 관리하는 메타데이터 테이블인 information_schema.tables를 활용합니다.
- information_schema.tables에는 어떤 DB에 어떤 table이 있는지에 대한 정보가 들어있습니다.
- 조건을 만들면, `UNION SELECT 1,2,3 FROM information_schema.tables WHERE table_schema='sqli_three' and table_name like '%';--` 입니다.
| [보충 설명] `UNION SELECT 1,2,3` = column 수를 맞추는 더미 값 `FROM information_schema.tables` = DBMS 내부 메타데이터 테이블에서 값을 가져옴 `WHERE table_schema='sqli_three' = 대상 DB를 sql_three로 제한하여 sql_three DB 안의 테이블만 확인 `and table_name LIKE '%'` = sql_three DB 안의 테이블이 하나라도 존재하는지 확인 `--` = 뒷 부분의 SQL 구문 무력화 |
→ DB 이름을 찾을 때처럼 '%' 부분에 'a%', 'b%', 'ab%' 등을 넣어가면서 테이블의 이름을 찾아내면 됩니다.

= 최종적으로 "users' 라는 Table 이름을 완성하게 됩니다.
11) DB 이름과 Table 이름 검증
- sqli_three DB 안에 users Table이 존재하는지 확인합니다.
- `anbymata' UNION SELECT 1,2,3 FROM information_schema.tables WHERE table_schema='sqli_three' and table_name='users';--`를 입력합니다.
| [보충 설명] `information_schema.tables` = 모든 DB의 테이블 정보가 저장된 곳 `table_schema='sqli_three` = 대상 DB를 sql_three로 제한하여 sql_three DB 안의 테이블만 확인 `table_name='users'` = 테이블 이름이 "users"인지 확인 |

= Response body가 `{"taken":true}`입니다.
→ sqli_three DB에 "users"라는 테이블이 존재합니다.
- false가 반환됐다면, "users"라는 테이블은 sqli_three DB에 없다는 뜻입니다.
12) users 테이블 안의 Column 이름 확인하기
- DB 이름과 Table 이름을 알아낸 방식과 동일하게 한 글자씩 확인해가며 Column 이름을 알아냅니다.
- information_schema.COLUMNS를 활용합니다.
- information_schema.columns에는 어떤 Table에 어떤 Column이 있는지에 대한 정보가 들어있습니다.
- 조건을 만들면, `UNION SELECT 1,2,3 FROM information_schema.columns WHERE table_schema='sqli_three' and table_name='users' and column_name like '%';--` 입니다.
| [보충 설명] `UNION SELECT 1,2,3` = column 수를 맞추는 더미 값 `FROM information_schema.tables` = DBMS 내부 메타데이터 테이블에서 값을 가져옴 `WHERE table_schema='sqli_three'`= 대상 DB를 sql_three로 제한하여 sql_three DB 안의 테이블만 확인 `table_name='users'` = 대상 Table을 users로 제한하여 users Table 안의 Column만 확인 `and table_name LIKE '%'` = sql_three DB 안의 테이블이 하나라도 존재하는지 확인 `--` = 뒷 부분의 SQL 구문 무력화 |
→ DB 이름과 Table 이름을 찾을 때처럼 '%' 부분에 'a%', 'b%', 'ab%' 등을 넣어가면서 컬럼의 이름을 찾아내면 됩니다.

= 최종적으로 "id' 라는 Column 이름을 완성하게 됩니다.
13) 다른 Column 이름 찾기
- 알다시피 users 테이블에는 3개의 Column이 존재합니다.
- `column_name != 'id';` 즉, column 이름이 "id"가 아니다라는 조건을 추가하여 Column 이름을 찾는 과정을 다시 해준다면, id가 아닌 다른 column 이름을 찾아낼 수 있습니다.
- 조건을 적어보면, `UNION SELECT 1,2,3 FROM information_schema.columns WHERE table_schema='sqli_three' and table_name='users' and column_name like '%' and column_name != 'id';--`입니다.
- 다른 column인 "username"도 찾아낸 이후, `column_name != 'username'`이란 조건을 추가하여 다시 Column 이름을 찾아보면 3번째 column도 찾아낼 수 있습니다.

= 3번째 column 이름인 "password"도 찾아냈습니다.
= Column 이름들은 "id", "username", "password" 입니다.
14) 등록된 유효한 username 찾기
- 이제 동일한 방법으로 DB에 등록된 username을 하나 찾아봅니다.
- 넣을 username 값을 적어보면, `anbymata' UNION SELECT 1,2,3 FROM users WHERE username like '%';--`입니다.
→ '%' 부분에 'a%', 'b%', 'ab%' 등을 넣어가면서 등록된 username을 하나 찾아내면 됩니다.

= 최종적으로 "admin" 이라는 등록된 username을 하나 찾았습니다.
- 사실 맨처음에 "admin"은 이미 {"taken":true} 로 등록된 username임을 알 수 있었습니다.
15) "admin"의 password 찾기
- 동일한 방법으로 "admin"의 password를 찾아봅니다.
- 넣을 username 값을 적어보면, `anbymata' UNION SELECT 1,2,3 FROM users WHERE username='admin' and password like '%';--`입니다.
→ '%' 부분에 'a%', 'b%', 'ab%' 등을 넣어가면서 "admin"의 password를 찾아내면 됩니다.

= 최종적으로 "3845" 라는 password를 찾았습니다.
→ username "admin"과 password "3845" 조합으로 로그인을 할 수 있습니다.
[8] Blind SQLi - Time Based
Time-Based
- Boolean-based 방식과 매우 유사함
- Boolean-based SQLi와 동일한 request(요청)을 보냄
- true/false 메시지가 없어서 성공 여부를 시각적으로 확인할 수 없음
- delay(응답 시간)으로 성공 여부를 판단함
- SLEEP() 함수와 UNION 구문을 함께 사용함
- SLEEP() 함수는 UNION SELECT가 성공적으로 실행되어야 실행됨
| [보충 설명] 조건이 true(참) → SLEEP() 실행 → 응답 지연 발생 조건이 false(거짓) → 즉시 응답 → SLEEP() 실행 안됨 SLEEP() 함수의 실행 여부로 true/false를 판단한다는 점만 다를 뿐, 전체적인 구조는 Boolean-based 방식과 유사합니다. |
[8-1] Blind SQLi - Time Based 실습
1) 테이블의 column(열) 개수 파악하기
- 역시나 먼저 column 개수를 확인해야 합니다.
- referrer 값으로 `anbymata' UNION SELECT SLEEP(5);--`을 입력합니다.
+ "anbymata"는 기존 문자열을 정상 종료시키기 위한 임의의 더미 값입니다.
+ UNION이 성공하면 `SLEEP(5)`가 실행되어 응답이 5초 지연됩니다.
+ UNION이 실패하면 지연없이 바로 응답합니다.

= 지연 시간 5초가 없습니다.
→ analytics_referrers 테이블의 column 개수는 1개가 아닙니다.
2) `anbymata' UNION SELECT SLEEP(5),2;--` 입력 시도
- user 테이블의 column 개수가 2개인지 확인합니다.

= 지연 시간 5초가 발생했습니다.
→ analytics_referrers 테이블의 column 개수는 2개 입니다.
3) Boolean-based SQLi에서의 과정 반복하여 DB 구조 파악하기
- 이제 [7-1]에서 했던 과정을 반복하여 DB 이름, Table 이름, Column 이름을 구해내면 됩니다.
- 다만, UNION 구문에 SLEEP() 함수를 추가하면 됩니다.
- 각 단계의 query를 적어보면,
+ DB 이름 구하기 = `anbymata' UNION SELECT SLEEP(5),2 WHERE database() like '%';--`
+ Table 이름 구하기 = `anbymata' UNION SELECT SLEEP(5),2 FROM information_schema.tables WHERE table_schema='[DB 이름]' and table_name like '%';--`
+ Column 이름 구하기 = `anbymata' UNION SELECT SLEEP(5),2 FROM information_schema.columns WHERE
table_schema='[DB 이름]' and table_name='[Table 이름]' and column_name like '%';--`
→ '%' 부분에 'a%', 'b%', 'ab%' 등을 넣어가면서 찾아내면 됩니다.
→ `like '%'`를 빼고 `=`를 입력하면 내가 구한 이름이 맞는 이름인지 확인할 수 있습니다.
| [보충 설명] `like '%ab'`를 넣어서 "ab"로 이름이 시작되는지 확인해왔습니다. `='ab'`로 넣어버리면, 이름이 "ab"인지 확인할 수 있습니다. 처음부터 일일이 넣는건 시간이 너무 오래 걸리니 힌트를 드리겠습니다. Boolean-based 실습은 Level Three 였고, DB 이름은 "sqli_three"였습니다. 그리고 Time-based 실습은 Level Four 입니다. DB 구조는 Boolean-based 실습과 유사합니다. 다시 말하자면, 실제로는 일일이 넣어보지 않고 자동화 도구를 활용합니다. |
- column을 2개 동시에 검토하는 것은 불가능합니다.
→ 그래서 column_name을 바꿔가며 2번 검토해야 합니다.

4) Boolean-based SQLi에서의 과정 반복하여 등록된 계정 정보 찾기
- 이제 [7-1]에서 했던 과정을 반복하여 로그인 가능한 계정 정보를 구해내면 됩니다.
- 각 단계의 query를 적어보면,
+ Username 찾기 = `anbymata' UNION SELECT SLEEP(5),2 FROM [DB 이름] WHERE username like '%';--`
+ Password 찾기 = `anbymata' UNION SELECT SLEEP(5),2 FROM [DB 이름] WHERE username='[Username]' and password like '%';--`
→ '%' 부분에 'a%', 'b%', 'ab%' 등을 넣어가면서 찾아내면 됩니다.
→ `like '%'`를 빼고 `=`를 입력하면 내가 구한 이름이 맞는 이름인지 확인할 수 있습니다.
| [보충 설명] 힌트를 드리겠습니다. DB에는 어지간하면 "admin"이라는 관리자 계정이 존재합니다. 그리고 [7-1]과 동일하게 4자리의 숫자 비밀번호를 가지고 있습니다. |
- 이제 구해낸 username과 password 조합으로 로그인을 하면 됩니다.
[9] Out-of-Band SQLi
Out-of-Band SQL Injection
- 비교적 흔하지 않은 SQLi
- 에러 메시지 없고, 화면 반응 없고, 시간 기반 차이도 없지만, DB가 외부 통신이 가능할 때 가능
- 특별한 조건이 필요한 SQLi (둘 중 하나를 만족해야 함)
+ 조건 1. DB 서버의 파일 접근 기능, DNS/HTTP 요청 기능이 활성화
+ 조건 2. Web App이 SQL 결과를 기반으로 외부 API 호출
- 2개의 서로 다른 통신 채널을 사용함
+ 1. Attack Channel: SQL Injection 전달
+ 2. Data Channel: 외부 HTTP/DNS 요청으로 데이터 수집
Out-of-Band SQLi 공격 흐름
- 1. 공격자가 SQLi 취약점이 있는 웹사이트에 payload(공격자의 실제 의도를 담은 코드)를 포함한 request(요청)을 보냄
- 2. 웹사이트가 DB에 공격자의 payload가 포함된 SQL query를 실행
- 3. payload는 DB가 공격자의 서버로 HTTP request(요청)을 보내도록 강제함
- 4. 공격자의 서버에 보내진 request(요청)에 DB 정보가 포함됨
[10] Remediation (대응 및 방어)
SQL Injection 취약점에 대한 방어 전략들을 알아봅시다.
1. Prepared Statement (with Parameterized Queries)
- 개발자가 먼저 SQL query 구조를 작성하고, 그 이후에 사용자 입력값을 parameter(파라미터)로 추가하는 방식
→ 즉, SQL 구문과 사용자의 입력을 분리
- SQL 코드의 구조가 변경되지 않음 → DB는 query와 data를 구분할 수 있음
- 결과적으로 사용자 입력은 데이터로만 처리. SQL 구문은 해석되지 않음
- 부가적으로 코드가 더 깔끔하며 가독성도 높아지고 성능 최적화 효과도 있음
2. Input Validation (입력값 검증)
- 사용자 입력값을 사전에 검증하여 허용된 값만 통과시키는 방식
- 입력값 검증은 SQL query에 포함되는 값을 보호하는 데 큰 도움이 됨
- Allow List(허용 목록)을 사용해 입력을 특정 문자열로만 제한 가능
- 프로그래밍 언어에서 string replacement(문자열 치환)을 사용해 위험한 문자나 패턴을 차단함
3. Escaping User Input
- ` ' `, ` " `, ` $ `, ` \ ` 와 같은 문자가 포함된 사용자 입력을 허용할 시 SQL query가 깨질 수 있고, Injection 공격에 노출됨
- Escaping = 특수문자 앞에 백슬래시 ( \ )를 붙여 SQL에서 문자 그대로 해석되게 만드는 기법
- 특수문자를 일반 문자열로 처리하게 만들지만, SQLi의 근본적인 해결책은 아님
| [보충 설명] Input Validation이나 Escaping User Input 방식의 경우 SQL Injection을 줄이는 수단들이지만, 둘다 파훼법이 존재합니다. 결국 근본적인 해결책은 Prepared Statements 입니다. |
[TryHackMe] SQL Injection - EP.3 (Task 6~10). END.
[TryHackMe] SQL Injection. Finish!
'TryHackMe > Web Hacking' 카테고리의 다른 글
| [THM] XSS - EP.2 (Task 4~5) (0) | 2026.03.03 |
|---|---|
| [THM] XSS - EP.1 (Task 1~3) (1) | 2026.02.25 |
| [THM] SQL Injection - EP.2 (Task 4~5) (0) | 2026.02.07 |
| [THM] SQL Injection - EP.1 (Task 1~3) (1) | 2026.02.02 |
| [TryHackMe] OWASP Top 10: 2021 - EP.2 (Task 5~8) (0) | 2025.11.10 |