AnbyMata의 해킹 노트

[THM] SQL Injection - EP.2 (Task 4~5) 본문

TryHackMe/Web Hacking

[THM] SQL Injection - EP.2 (Task 4~5)

AnbyMata 2026. 2. 7. 22:00

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-BasedUnion-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"로 바꿔보겠습니다.

https://website.thm/article?id=1004

 = SQL Query가 내가 변경한 id 값인 "id = 1004"로 변경되었습니다.

 → 즉, id 파라미터가 취약 지점입니다.

 

 

 

2)  Error-Based SQLi 취약점 찾아보기

 - Error-based SQLi을 발견하는 핵심은 특정 문자 입력해 SQL query를 깨서 에러 메시지 발생시키는 것입니다.

 - 가장 흔히 사용하는 문자는 작은 따옴표( ' ) 또는 큰 따옴표( " ) 입니다.

 - 따옴표를 사용해 SQL 문법에 고의적으로 에러 유발시킵니다.

 - 에러가 화면에 나온다 → Error-Based SQLi 취약점 존재 가능성이 높습니다.

 

 - id 값에 ?id=100" 을 입력해보겠습니다.

https://website.thm/article?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 개수입니다.

https://website.thm/article?id=1 UNION SELECT 1

 = column의 개수가 다르다는 에러 메시지가 출력되었습니다.

 → `SELECT * from article where id = 1`의 column 개수는 1개가 아닙니다.

 

 

 

4)  `1 UNION SELECT 1,2` 입력 시도

 - column 개수가 2개인지 확인합니다.

https://website.thm/article?id=1 UNION SELECT 1,2

 = column의 개수가 다르다는 에러 메시지가 출력되었습니다.

 → `SELECT * from article where id = 1`의 column 개수는 2개가 아닙니다.

 

 

 

5)  `1 UNION SELECT 1,2,3` 입력 시도

 - column 개수가 3인지 확인합니다.

https://website.thm/article?id=1 UNION SELECT 1,2,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"으로 첫 번째 row1,2,3 이라는 더미 값을 추가했고, 결과적으로 1,2,3이 결과값으로 출력될 것입니다.

https://website.thm/article?id=0 UNION SELECT 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 위치에 삽입하여, 화면에 출력되는 위치에 데이터베이스 이름을 표시할 수 있다.

https://website.thm/article?id=0 UNION SELECT 1,2,database()

 = 현재 사용 중인 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 표준으로 정의된 메타데이터 스키마입니다.

https://website.thm/article?id=0 UNION SELECT 1,2,group_concat(table_name) FROM information_schema.tables WHERE table_schema='sqli_one'

 = 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 을 사용한 모습입니다.

https://website.thm/article?id=0 UNION SELECT 1,2,group_concat(column_name) FROM information_schema.columns WHERE table_name='staff_users'

 = staff_users 테이블은 id, password, username 이란 3가지 column으로 구성되어 있습니다.

 

 

 

9)  staff_users 테이블에서 계정 정보 추출

 - staff_users 테이블에는 id, password, username 3개의 column이 존재함을 확인했습니다.

 - usernamepassword 정보를 알아내면 사용자 정보를 가져올 수 있을 것입니다.

 - `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`를 살펴보면
- 중간에 ' : ' 를 추가해 usernamepassword구분되어 출력되게끔 한 모습입니다.
- 추가적으로 HTML의 <br> 태그를 사용해 각 결과가 줄바꿈되어 출력되게끔 한 모습입니다.
- 즉, 결과가 깔끔하게 보이게끔 조치를 취한 모습들입니다.

https://website.thm/article?id=0 UNION SELECT 1,2,group_concat(username,' : ', password SEPARATOR '<br>') FROM staff_users

= 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