| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
- Bandit
- Cross-Site Scripting
- TryHackMe
- Blue Team
- 해커
- linux
- http
- write-up
- 리눅스 기초
- 모의해킹
- SoC
- Web
- Cyber Security
- cert
- 정보보호
- OverTheWire
- 정보보안
- 해킹 스터디
- CTF
- 리눅스
- 해킹
- 워게임
- 보안 스터디
- 보안 관제
- IR
- XSS
- web hacking
- 사이버 보안
- THM
- 블루팀
- Today
- Total
AnbyMata의 해킹 노트
[THM] SQL Injection - EP.2 (Task 4~5) 본문
TryHackMe - "SQL Injection". Task 4~5. Write-up + Extra Study!

출처: https://tryhackme.com/room/sqlinjectionlm
SQL Injection
Learn how to detect and exploit SQL Injection vulnerabilities
tryhackme.com
[4] What is SQL Injection?
SQL Injection
- 사용자 입력값이 그대로 SQL query에 포함되면서 의도하지 않은 SQL 명령이 실행되는 취약점
- 입력값 검증 미흡으로 인하여 주로 발생 (Input Validation 부재)
- 인증 우회, 비공개 데이터 열람, DB 조작 등을 가능하게 함
SQL Injection 기본 동작
- 1. 사용자가 URL 파라미터(변수)로 값 전달
- 2. 웹 애플리케이션이 이 값을 그대로 SQL query에 삽입
- 3. 공격자가 SQL 문법을 섞어 query 구조를 변경
| [보충 설명] Query (쿼리)라는 단어가 계속 나오고 있습니다. Query는 데이터베이스 (DB)에 무엇을 해달라고 요청하는 명령문입니다. 즉, SQL 문장, SQL 명령어 정도로 알아두면 됩니다. |
이제 예시를 통해, 정상적인 SQL 쿼리와 SQL Injection이 개입한 변조된 SQL 쿼리를 살펴봅시다.
[4-1] 정상적인 SQL query
티스토리 같이 온라인 블로그 예시입니다.
각 블로그 글은 고유한 ID 번호를 가지고, 글은 public(공개) 또는 private(비공개) 상태를 가집니다.
| [보충 설명] 이를 DB 입장에서 살펴보면, 블로그 글 = DB 테이블의 Row(행) ID 번호 = 각 row를 식별하는 값 (= Private Key 역할) public/private 여부 = 별도의 private이라는 Column(열) 값으로 관리 (0 = public, 1 = private) |
1. URL

- URL에서 선택되는 블로그 글은 'id' parameter(파라미터)에서 결정됨
- 사용자가 'id=1' 이라는 값을 입력을 한 상태
- 즉, 서버에 id 값이 "1"인 블로그 글을 요청한 상태
- 웹 서버는 이 id 값을 받아 SQL query를 생성하여 DB 조회에 사용함
2. SQL query

- URL의 parameter 값을 참고해 서버에서 생성한 SQL query
- id가 "1"이고, private가 "0" 인 public(공개) 글을 하나만 조회
| [보충 설명] SQL query를 자세히 살펴보면, - SELECT * : 모든 Column 조회 - from blog : "blog"라는 Table에서 데이터를 가져옴 - where id=1 and private=0 : id 값이 "1"이고 private 값이 "0"인 Row(행)만 선택 - LIMIT 1 : 결과로 출력할 Row(행)을 1개로 제한 private 파라미터 값이 "0"이면 public(공개), "1"이면 private(비공개)를 뜻합니다. |
[4-2] SQL Injection으로 인해 변조된 SQL query
URL에 조작된 SQL 구문을 넣어 변조시켜 공격합니다.
SQL Injection 발생 이유
- SQL Injection은 사용자 입력값이 DB query에 직접 포함될 때 발생함
→ ID 값을 검증하지 않음
→ 입력한 id 파라미터가 그래도 SQL query에 사용됨
→ 공격자가 SQL 문법을 주입할 수 있음
1. URL

- 공격자가 조작한 URL
- 정상적인 URL 뒤에 ' -- '을 추가함
- ' ; ' 로 SQL 문이 종료되는데, 뒤에 ' -- ' 넣어 그 이후 부분들 주석 처리 해버림
- SQL 문법을 이용한 전형적인 공격 패턴
2. SQL query

- 공격자의 URL의 parameter 값을 참고해 서버에서 생성한 SQL query
- id의 값이 "2"가 아닌, "2;--"로 들어간 상태
- ' -- ' 뒷 부분이 전부 주석 처리되면서 "and private=0 LIMIT 1;" 부분을 무시하게됨
- private 파라미터의 조건을 무시하게 되면서 비공개 글도 조회 가능
3. 실제 실행되는 query

- "and private=0 LIMIT 1;" 부분이 주석이 되면서 실제로 실행되는 SQL query
- id가 "2"인 글을 공개/비공개 여부와 관계없이 무조건 조회하는 SQL 명령이 됨
- 결과적으로 id 값을 바꿔가며 비공개인 글들도 마음대로 조회할 수 있음
- 접근 제어가 우회된 케이스
| [보충 설명] ' ; '은 마침표 역할을 해서 SQL 문을 종료시키는 역할을 합니다. 하지만 URL에서는 ' ; '나 ' -- ' 둘다 그냥 문자인 상태입니다. 정상적인 SQL query에서는 "id=1;"이 그대로 서버에 전달된 것이고, 변조된 SQL query에서는 "id=2;--"이 그대로 서버에 전달된 것입니다. 즉, 두 경우 모두 ' ; '까지 포함된 입력 값 전체가 SQL query의 일부로 포함됩니다. 뒤에 자동적으로 붙는 "and private=0 LIMIT 1"은 웹 애플리케이션 로직에 포함된 SQL 쿼리의 일부입니다. 즉, 웹 애플리케이션은 내부적으로 "SELECT * from blog where [사용자가 입력한 부분] and private=0 LIMIT 1;" 형태의 query를 생성합니다. 사용자가 입력한 부분이 그대로 넘어가는 형태라 SQL Injection 공격이 성립하게 됩니다. |
[4-3] SQL Injection 종류
SQL Injection에는 3가지 유형이 존재합니다.
- In-Band = 결과가 같은 채널에 바로 보임
- Blind = 결과가 직접 보이지 않음
- Out-of-Band = DNS, HTTP 등의 다른 채널을 사용
위의 예시의 경우 In-Band SQL Injection의 예시에 해당합니다.
[5] In-Band SQLi
In-Band SQL Injection
- 공격 입력과 공격 결과 출력이 같은 채널에서 이뤄짐
- 탐지하고 공격하기 가장 쉬운 SQL Injection 유형
- 가장 직관적이고 가장 많이 다뤄지는 SQL Injection 유형
| [보충 설명] 공격 입력과 공격 결과 출력이 같은 채널에서 이뤄진다는 것은 웹사이트 페이지에서 SQLi 취약점을 발견해서 공격하면 같은 페이지에서 DB의 데이터를 그대로 추출할 수 있다는 뜻입니다. |
[5-1] In-Band SQLi의 대표적인 2가지 유형
In-Band SQLi에는 크게 Error-Based와 Union-Based 유형이 있습니다.
Error-Based SQL Injection
- 데이터베이스 구조에 대한 정보를 쉽게 획득할 수 있는 유형
- DB의 에러 메시지를 직접 출력시킴
- 에러 메시지를 통해 테이블명, Column명, DB 종류 등의 구조 정보가 노출됨
- 즉, DB 에러가 화면에 직접 표시되어야 사용 가능함
- DB 구조 수집에 주로 사용됨
Union-Based SQL Injection
- UNION 연산자를 SELECT 문과 함께 사용하는 유형
- 기존 query 결과와 공격자가 원하는 추가 데이터를 함께 출력시킴
- Column 개수 및 타입이 원본 query와 일치해야 유효함
- 대량의 데이터를 추출할 때 주로 사용됨
- 실전 및 CTF에서 많이 사용됨
[5-2] In-Band SQL Injection 실습
1) 취약 지점 찾아내기
- URL의 query string의 id 값을 "1004"로 바꿔보겠습니다.

= SQL Query가 내가 변경한 id 값인 "id = 1004"로 변경되었습니다.
→ 즉, id 파라미터가 취약 지점입니다.
2) Error-Based SQLi 취약점 찾아보기
- Error-based SQLi을 발견하는 핵심은 특정 문자를 입력해 SQL query를 깨서 에러 메시지를 발생시키는 것입니다.
- 가장 흔히 사용하는 문자는 작은 따옴표( ' ) 또는 큰 따옴표( " ) 입니다.
- 따옴표를 사용해 SQL 문법에 고의적으로 에러를 유발시킵니다.
- 에러가 화면에 나온다 → Error-Based SQLi 취약점 존재 가능성이 높습니다.
- id 값에 ?id=100" 을 입력해보겠습니다.

= Syntax error or access violation 에러 문구가 화면에 출력되었습니다.
→ 즉,SQL Injection 취약점이 존재할 가능성이 높습니다.
3) `1 UNION SELECT 1` 입력 시도
- 이제 에러 메시지를 출력하지 않으면서도 데이터를 받아야 합니다.
- Union-based SQLi 취약점을 사용하는 과정으로 우리가 원하는 정보를 추가 결과로서 출력시켜야 합니다.
- UNION은 기존 query 결과와 추가 결과를 함께 반환하게끔 해주기에 사용합니다.
- `1 UNION SELECT 1`는 컬럼 개수 불일치 에러를 유도해 구조를 파악하기 위한 것입니다.
| [보충 설명] `1 UNION SELECT 1` 은 의미 있는 데이터 추출이 목적이 아닙니다. Union-based SQLi 공격의 가능 여부와 Column 구조 파악이 목적입니다. `id=1 UNION SELECT 1` 입력값를 뜯어보면, "id=1" = 기존 query를 정상 실행시키기 위한 값 "UNION" = 2개의 SELECT 결과를 하나로 합쳐서 앞의 기존 query 값과 우리가 원하는 추가 결과를 같은 결과셋으로 출력 "SELECT 1" = 의미 없는 더미 값으로, Column이 1개짜리 SELECT이 가능한지 확인 즉, 이 명령어가 실패하면 Column 개수는 1개가 아니라는 뜻입니다. 여기서 말하는 column 개수는 `SELECT * from article where id = 1`이 반환하는 column 개수입니다. |

= column의 개수가 다르다는 에러 메시지가 출력되었습니다.
→ `SELECT * from article where id = 1`의 column 개수는 1개가 아닙니다.
4) `1 UNION SELECT 1,2` 입력 시도
- column 개수가 2개인지 확인합니다.

= column의 개수가 다르다는 에러 메시지가 출력되었습니다.
→ `SELECT * from article where id = 1`의 column 개수는 2개가 아닙니다.
5) `1 UNION SELECT 1,2,3` 입력 시도
- column 개수가 3개인지 확인합니다.

= id 값이 "1"인 글의 내용이 정상 출력되었습니다.
→ `SELECT * from article where id = 1`의 column 개수는 3개 입니다.
6) `0 UNION SELECT 1,2,3`으로 공격 결과 확인
- `id=1 UNION SELECT 1,2,3`의 경우, "id=1"에 해당되는 정상 글이 먼저 반환되어 공격 결과가 보이지 않습니다.
- 그래서 `id=0`으로 만들어 첫 번째 SELECT 결과를 없애서 "UNION SELECT 1,2,3" 부분의 결과를 화면에 출력시킵니다.
| [보충 설명] "id=0"인 글, 즉, id 값이 "0"인 글은 존재하지 않습니다. 그래서 `id=0 UNION SELECT 1,2,3`에서 "id=0"에 해당되는 SELECT 값은 비어있습니다. 첫 번째 SELECT 값이 비어있기에, 두 번째 SELECT인 "UNION SELECT 1,2,3" 부분의 값이 출력됩니다. "UNION SELECT 1,2,3"으로 첫 번째 row에 1,2,3 이라는 더미 값을 추가했고, 결과적으로 1,2,3이 결과값으로 출력될 것입니다. |

= 각 column이 화면의 어느 위치에 출력되는지 확인할 수 있습니다.
- "1"이라는 출력값이 안보임 → 1번 column은 화면에 보이지 않습니다
- "2", "3"이라는 출력값이 보임 → 각 숫자의 위치가 2번, 3번 column 결과의 출력 위치입니다.
- "Article ID: 1" → 웹 애플리케이션이 원래 출력하는 고정 텍스트로 무시해도 되는 UI 요소입니다.
7) `0 UNION SELECT 1,2,database()`로 현재 사용 중인 DB 이름 알아내기
- 3번째 column이 화면에 출력되는 위치를 알게 되었습니다.
- database() 는 현재 사용 중인 DB 이름을 반환해주는 함수입니다.
- 이 함수의 반환값을 3번 Column 위치에 삽입하여, 화면에 출력되는 위치에 데이터베이스 이름을 표시할 수 있다.

= 현재 사용 중인 DB의 이름은 "sqli_one"입니다.
8) 현재 DB에 존재하는 Table 목록 수집하기
- `0 UNION SELECT 1,2,group_concat(table_name) FROM information_schema.tables WHERE table_schema = 'sqli_one'` SQL query를 넣어 sqli_one 데이터베이스의 Table 목록을 3번 column 위치에 출력할 것입니다.
- group_concat() 은 여러 row의 값을 하나의 문자열로 결합하여 화면에 출력할 수 있게끔 해줍니다.
- information_schema 는 일반적으로 사용자가 접근할 수 있는 테이블 이름, 소속 DB 등의 metadata(메타데이터)를 담고 있는 데이터베이스입니다.
- 즉, sqli_one 데이터베이스에 포함된 모든 테이블을 나열하는 SQL query입니다.
| [보충 설명] 대부분의 관계형 DBMS에는 "information_schema"가 있습니다. 일반적으로 사용자에게도 읽기 권한이 제공됩니다. 테이블 이름, 소속 데이터베이스 등의 메타데이터를 저장하는 시스템 DB입니다. information_schema는 SQL 표준에 정의된 메타데이터 스키마이기 때문에 대부분 존재하는 것입니다. `information_schema.tables` 는 데이터베이스 구조에 대한 메타데이터를 담고 있는 시스템 테이블입니다. group_concat()에 사용된 "table_name"도 SQL 표준에서 정의된 메타데이터 스키마입니다. 그래서 information_schema 안의 tables에는 table_name column이 표준으로 정의되어 있습니다. table_schema = DB(스키마) 이름 / table_name = 테이블 이름 등이 SQL 표준으로 정의된 메타데이터 스키마입니다. |

= sqli_one 데이터베이스에는 article 테이블과 staff_users 테이블이 존재합니다.
8) staff_users 테이블 구조 확인
- 테이블 이름으로 유추해볼 때, staff_users 테이블에 직원 정보가 담겨있을 것 같습니다.
- `0 UNION SELECT 1,2,group_concat(column_name) FROM information_schema.columns WHERE table_name = 'staff_users'` SQL query를 넣어 staff_users 테이블의 구조, 즉, column 구성을 확인합니다.
| [보충 설명] 아까는 table 정보를 얻기 위해 table_name, information_schema.tables 을 사용했지만, 이번엔 column 정보를 얻기 위해 column_name, information_schema.columns 을 사용한 모습입니다. |

= staff_users 테이블은 id, password, username 이란 3가지 column으로 구성되어 있습니다.
9) staff_users 테이블에서 계정 정보 추출
- staff_users 테이블에는 id, password, username 3개의 column이 존재함을 확인했습니다.
- username과 password 정보를 알아내면 사용자 정보를 가져올 수 있을 것입니다.
- `0 UNION SELECT 1,2,group_concat(username,' : ', password SEPARATOR '<br>') FROM staff_users` SQL query를 넣어 사용자 정보를 추출합니다.
| [보충 설명] `0 UNION SELECT 1,2,group_concat(username,' : ', password SEPARATOR '<br>') FROM staff_users`를 살펴보면 - 중간에 ' : ' 를 추가해 username과 password가 구분되어 출력되게끔 한 모습입니다. - 추가적으로 HTML의 <br> 태그를 사용해 각 결과가 줄바꿈되어 출력되게끔 한 모습입니다. - 즉, 결과가 깔끔하게 보이게끔 조치를 취한 모습들입니다. |

= 3명의 username과 이에 대응되는 password를 알아냈습니다.
- 이로써 성공적으로 직원들의 계정 정보를 획득했습니다.
[TryHackMe] SQL Injection - EP.2 (Task 4~5). END.
[TryHackMe] SQL Injection - EP.3 (Task 6~10) 完. Continue...
https://anbymata.tistory.com/53
[THM] SQL Injection - EP.3 (Task 6~10). 完
TryHackMe - "SQL Injection". Task 6~10. Write-up + Extra Study!출처: https://tryhackme.com/room/sqlinjectionlm SQL InjectionLearn how to detect and exploit SQL Injection vulnerabilitiestryhackme.com [6] Blind SQLi - Authentication BypassBlind SQL Injecti
anbymata.tistory.com
'TryHackMe > Web Hacking' 카테고리의 다른 글
| [THM] XSS - EP.1 (Task 1~3) (1) | 2026.02.25 |
|---|---|
| [THM] SQL Injection - EP.3 (Task 6~10). 完 (0) | 2026.02.16 |
| [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 |
| [TryHackMe] OWASP Top 10: 2021 - EP.1 (Task 1~4) (0) | 2025.11.08 |