37번째 문제 갑시다!
지피티야 sql injection이 모야
**SQL Injection(인젝션)**은 웹 애플리케이션의 보안 취약점 중 하나로, **사용자 입력을 제대로 검증하지 않고 SQL 쿼리문에 포함시킬 때 발생**합니다. 이를 통해 공격자는 **데이터베이스에 직접적으로 악의적인 SQL 문을 삽입**하여 데이터를 무단으로 조회하거나 조작하는 등의 공격을 할 수 있습니다.
이 취약점은 데이터베이스에 대한 직접적인 접근 권한 없이도 웹 애플리케이션의 취약한 입력 필드를 통해 다양한 공격을 실행할 수 있게 만듭니다. **로그인 우회**, **데이터 유출**, **데이터 수정 및 삭제**, **권한 상승** 등 다양한 형태로 피해를 입힐 수 있습니다.
### SQL Injection의 기본 원리
SQL 인젝션의 기본 원리는 사용자의 입력값을 신뢰하고 검증 없이 SQL 쿼리에 포함시키는 것입니다. 예를 들어, 사용자가 입력한 값을 포함하여 SQL 쿼리를 구성하는 아래의 PHP 코드를 보겠습니다:
```php
$query = "SELECT * FROM users WHERE username = '" . $_POST['username'] . "' AND password = '" . $_POST['password'] . "'";
```
이 쿼리는 정상적인 경우에 `username`과 `password` 값을 이용해 데이터베이스에서 해당 사용자를 찾습니다. 그러나, 공격자가 의도적으로 다음과 같은 값을 입력한다면:
- `username`: `' OR '1'='1`
- `password`: 아무 값
그러면 쿼리는 다음과 같은 형태로 변형됩니다:
```sql
SELECT * FROM users WHERE username = '' OR '1'='1' AND password = '';
```
이 쿼리는 `'1'='1'`이라는 조건이 항상 참이기 때문에, 공격자는 올바른 사용자 이름과 비밀번호를 입력하지 않고도 데이터베이스에 접근할 수 있습니다. 결과적으로, 인증이 우회되거나 데이터베이스의 모든 정보에 접근할 수 있는 상황이 발생할 수 있습니다.
### SQL Injection 공격의 종류
1. **에러 기반 SQL 인젝션 (Error-based SQL Injection)**
- 데이터베이스에서 반환되는 오류 메시지를 이용하여 공격자가 시스템 정보를 획득하는 방법입니다. 예를 들어, 쿼리 실행 중 발생하는 오류 메시지에 시스템의 내부 정보(테이블 이름, 칼럼 이름 등)가 포함될 수 있습니다.
2. **블라인드 SQL 인젝션 (Blind SQL Injection)**
- 에러 메시지를 반환하지 않더라도, 참 또는 거짓의 조건을 기반으로 데이터베이스에서 정보를 추출할 수 있는 방식입니다. 공격자는 참/거짓 질문을 던지고, 응답 시간을 통해 참인지 거짓인지 유추하여 정보를 빼낼 수 있습니다.
3. **유니온 기반 SQL 인젝션 (Union-based SQL Injection)**
- `UNION` SQL 명령어를 사용하여 여러 쿼리 결과를 결합함으로써 공격자가 원하는 데이터를 획득하는 방식입니다. 이를 통해 공격자는 여러 테이블에서 데이터를 수집할 수 있습니다.
4. **타임 기반 SQL 인젝션 (Time-based SQL Injection)**
- 데이터베이스가 응답하는 시간에 따라 참/거짓을 구분하는 방법입니다. 공격자는 조건이 참일 때 데이터베이스에서 일정 시간 동안 대기하게 만드는 `SLEEP()` 함수와 같은 쿼리를 실행하여 참/거짓 여부를 알아낼 수 있습니다.
### SQL Injection의 위험성
SQL 인젝션은 아래와 같은 심각한 피해를 초래할 수 있습니다:
- **인증 우회**: 로그인 시스템을 우회하여 인증되지 않은 사용자가 관리자 권한으로 접속할 수 있음.
- **데이터 유출**: 비밀번호, 개인 정보 등의 민감한 데이터를 탈취.
- **데이터 수정/삭제**: 공격자가 테이블을 삭제하거나 데이터를 조작할 수 있음.
- **원격 코드 실행**: 심각한 경우, 서버에서 명령어를 실행하는 등의 피해를 발생시킬 수 있음.
### SQL Injection 방어 방법
1. **준비된 쿼리 (Prepared Statements) 사용**:
- 사용자 입력값을 쿼리에 직접 포함하지 않고, 파라미터화된 쿼리를 사용하여 입력값을 별도로 처리하는 방식입니다. 이는 SQL 쿼리에 데이터가 안전하게 바인딩되어 실행되므로, SQL 인젝션을 방지할 수 있습니다.
예시 (PHP, PDO):
```php
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username AND password = :password");
$stmt->execute(['username' => $username, 'password' => $password]);
```
2. **입력값 검증 (Input Validation)**:
- 사용자가 입력한 데이터를 철저히 검증하여 악의적인 문자가 포함되지 않도록 해야 합니다. 예를 들어, 숫자형 데이터는 숫자인지 확인하고, 특수 문자는 적절히 필터링해야 합니다.
3. **최소 권한 원칙 적용**:
- 애플리케이션이 사용하는 데이터베이스 사용자에게 최소한의 권한만 부여하여, 공격자가 시스템을 공격하더라도 피해를 최소화합니다.
4. **ORM 사용**:
- SQL 쿼리를 직접 작성하지 않고, ORM(Object-Relational Mapping) 라이브러리를 사용하여 데이터베이스와 상호작용하는 것도 SQL 인젝션을 방지하는 방법입니다.
5. **에러 메시지 감추기**:
- 데이터베이스 에러 메시지를 사용자에게 그대로 노출하지 않도록 하여 내부 정보를 유출하지 않도록 해야 합니다.
### 결론
SQL 인젝션은 매우 심각한 보안 취약점이며, 이를 악용하면 데이터베이스에 직접 접근하여 민감한 정보를 탈취하거나 시스템을 무너뜨릴 수 있습니다. 그러나, 준비된 쿼리(Prepared Statements)와 같은 적절한 보안 대책을 적용하면 이러한 위험을 방지할 수 있습니다.
크흐 쿼리문이 참인 값을 찾아내는 직접적인 여러가지 공격 방법이 있는거군.
소스코드를 보자
#!/usr/bin/python3
from flask import Flask, request, render_template, g
import sqlite3
import os
import binascii
app = Flask(__name__)
app.secret_key = os.urandom(32)
try:
FLAG = open('./flag.txt', 'r').read()
except:
FLAG = '[**FLAG**]'
DATABASE = "database.db"
if os.path.exists(DATABASE) == False:
db = sqlite3.connect(DATABASE)
db.execute('create table users(userid char(100), userpassword char(100));')
db.execute(f'insert into users(userid, userpassword) values ("guest", "guest"), ("admin", "{binascii.hexlify(os.urandom(16)).decode("utf8")}");')
db.commit()
db.close()
def get_db():
db = getattr(g, '_database', None)
if db is None:
db = g._database = sqlite3.connect(DATABASE)
db.row_factory = sqlite3.Row
return db
def query_db(query, one=True):
cur = get_db().execute(query)
rv = cur.fetchall()
cur.close()
return (rv[0] if rv else None) if one else rv
@app.teardown_appcontext
def close_connection(exception):
db = getattr(g, '_database', None)
if db is not None:
db.close()
@app.route('/')
def index():
return render_template('index.html')
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
return render_template('login.html')
else:
userid = request.form.get('userid')
userpassword = request.form.get('userpassword')
res = query_db(f'select * from users where userid="{userid}" and userpassword="{userpassword}"')
if res:
userid = res[0]
if userid == 'admin':
return f'hello {userid} flag is {FLAG}'
return f'<script>alert("hello {userid}");history.go(-1);</script>'
return '<script>alert("wrong");history.go(-1);</script>'
app.run(host='0.0.0.0', port=8000)
DATABASE = "database.db"
if os.path.exists(DATABASE) == False:
db = sqlite3.connect(DATABASE)
db.execute('create table users(userid char(100), userpassword char(100));')
db.execute(f'insert into users(userid, userpassword) values ("guest", "guest"), ("admin", "{binascii.hexlify(os.urandom(16)).decode("utf8")}");')
db.commit()
db.close()
엌ㅋㅋ admin 비번 값이 아주 복잡하다 지피티야 설명해줘...
`"{binascii.hexlify(os.urandom(16)).decode("utf8")}"`는 **Python** 코드로, 난수(랜덤 값)를 생성하고, 그 값을 16진수 문자열로 변환한 후, UTF-8 문자열로 디코딩하는 과정입니다. 이를 단계별로 설명하면 다음과 같습니다.
### 1. `os.urandom(16)`
이 함수는 **운영 체제에서 제공하는 난수 생성기**를 사용하여 **16바이트의 랜덤 데이터를 생성**합니다. 이 난수는 암호화에서 사용할 수 있을 만큼 안전한 무작위 데이터를 생성하는 데 사용되며, 보통 보안 관련 작업에서 쓰입니다.
- **출력 예시**:
`b'\xd2\xf3\x9d\xa7\x1d\xae\xe2\x12\x93\xb6\xa9\xdc\x1b\xcd\x1b\x89'`
(이 값은 매번 실행할 때마다 달라집니다.)
### 2. `binascii.hexlify()`
이 함수는 **바이너리 데이터를 16진수로 인코딩**합니다. 위에서 생성한 `os.urandom(16)`의 바이트 데이터를 **16진수 문자열로 변환**합니다.
- **출력 예시**:
`b'd2f39da71daee21293b6a9dc1bcd1b89'`
(이 값도 매번 실행할 때마다 달라집니다.)
### 3. `.decode("utf8")`
`hexlify()` 함수는 **바이너리 문자열**(bytes)로 결과를 반환합니다. 이 결과를 UTF-8 문자열(사람이 읽을 수 있는 문자열)로 변환하기 위해 `.decode("utf8")`을 사용합니다.
- **출력 예시**:
`'d2f39da71daee21293b6a9dc1bcd1b89'`
(이 값은 bytes 데이터를 UTF-8로 변환한 문자열입니다.)
### 4. `"{}"` 포맷팅
`{}`는 Python의 **문자열 포맷팅 방식** 중 하나입니다. 이 구문은 `binascii.hexlify(os.urandom(16)).decode("utf8")`의 결과 값을 `{}`에 넣어 최종적으로 하나의 문자열로 출력하게 됩니다.
### 종합 설명
이 코드는 **16바이트 길이의 무작위 데이터를 생성한 후**, **이를 16진수로 변환한 다음**, **UTF-8로 디코딩한 문자열**을 생성하는 역할을 합니다. 최종 출력은 무작위한 16진수 문자열입니다. 이 과정은 주로 **토큰 생성**, **암호화 키 생성**, **고유 식별자 생성**과 같은 작업에 사용될 수 있습니다.
### 예시
다음은 코드 실행 결과의 예시입니다:
```python
import os
import binascii
hex_string = binascii.hexlify(os.urandom(16)).decode("utf8")
print(hex_string)
```
- **출력**: `'d2f39da71daee21293b6a9dc1bcd1b89'`
(실행할 때마다 다른 16진수 문자열이 생성됨)
이러한 16진수 문자열은 무작위성을 제공하며, 보안적으로도 안전하게 사용할 수 있습니다.
복안 짱짱하네...복호화할려면
이 코드는 16바이트 길이의 무작위 데이터를 생성한 후, 이를 16진수로 변환한 다음, UTF-8로 디코딩한 문자열
UTF-8로 디코딩->16진수 변환-> 16바이트 길이의 무작위 데이터 확인 가능하겠네.
근데 이렇게 하면 너무 복잡한데, 실행할때마다 다른 무작위 데이터를 복호화 하기보다
문제 제목처럼 SQL INJECTION 항상 참이되게 만드는게 빠를 것 같다.
그리고 그게 이 문제의 해결방법이라 생각된다.
맞지 지피티야?
이 코드는 **Flask** 웹 애플리케이션을 사용하여 간단한 로그인 시스템을 구현한 Python 프로그램입니다. 주요 기능은 다음과 같습니다:
### 주요 기능 설명
1. **Flask 앱 설정**
- `app = Flask(__name__)`: Flask 애플리케이션을 생성합니다.
- `app.secret_key = os.urandom(32)`: 앱의 세션 암호화를 위한 랜덤한 비밀 키를 설정합니다.
2. **데이터베이스 설정 및 초기화**
- `DATABASE = "database.db"`: SQLite 데이터베이스 파일을 설정합니다.
- 데이터베이스가 존재하지 않을 경우, `users` 테이블을 생성하고 두 개의 기본 사용자(`guest`와 `admin`)를 추가합니다. 이때, `admin`의 비밀번호는 16바이트의 랜덤한 값을 생성하여 저장합니다.
```python
db.execute(f'insert into users(userid, userpassword) values ("guest", "guest"), ("admin", "{binascii.hexlify(os.urandom(16)).decode("utf8")}");')
```
3. **데이터베이스 연결 및 쿼리**
- `get_db()`: 현재 Flask 애플리케이션의 컨텍스트에 데이터베이스 연결을 제공합니다.
- `query_db()`: SQL 쿼리를 실행하고 결과를 반환합니다. `one=True`이면 첫 번째 결과만 반환하고, 그렇지 않으면 전체 결과를 반환합니다.
4. **라우팅**
- `/`: 기본 페이지로, `index.html` 템플릿을 렌더링합니다.
- `/login`: 로그인 페이지로, GET 요청 시 로그인 폼을 보여주고, POST 요청 시 사용자 인증을 처리합니다.
로그인 로직에서는 사용자가 제출한 `userid`와 `userpassword`를 기반으로 `users` 테이블을 조회하여 인증을 진행합니다. 만약 `admin` 사용자로 로그인에 성공하면 플래그(`FLAG`)를 반환합니다.
```python
if res:
userid = res[0]
if userid == 'admin':
return f'hello {userid} flag is {FLAG}'
```
5. **데이터베이스 연결 종료**
- `@app.teardown_appcontext`: 앱이 종료될 때 데이터베이스 연결을 닫는 함수를 정의합니다.
### 보안 취약점
이 코드에는 몇 가지 **SQL 인젝션(SQL Injection)** 취약점이 존재합니다. 특히, 다음 부분에서 문제가 발생할 수 있습니다:
```python
res = query_db(f'select * from users where userid="{userid}" and userpassword="{userpassword}"')
```
이 쿼리는 사용자로부터 입력된 값을 그대로 SQL 쿼리에 포함시키고 있습니다. 만약 사용자가 악의적인 입력을 제공한다면, SQL 인젝션 공격을 통해 인증을 우회하거나 데이터베이스에 접근할 수 있습니다.
#### SQL 인젝션 공격 예시
공격자가 `userid` 필드에 `" OR "1"="1"`과 같은 값을 입력하면, 쿼리는 다음과 같이 변경됩니다:
```sql
SELECT * FROM users WHERE userid="" OR "1"="1" AND userpassword="";
```
이 쿼리는 항상 참이므로, 데이터베이스의 모든 사용자가 반환될 수 있습니다.
### 방어 방안
이러한 SQL 인젝션을 방지하려면 **Prepared Statement** 또는 **파라미터화된 쿼리**를 사용해야 합니다. 예를 들어, `sqlite3` 모듈에서 파라미터를 사용하는 방식으로 수정할 수 있습니다:
```python
def query_db(query, args=(), one=True):
cur = get_db().execute(query, args)
rv = cur.fetchall()
cur.close()
return (rv[0] if rv else None) if one else rv
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
return render_template('login.html')
else:
userid = request.form.get('userid')
userpassword = request.form.get('userpassword')
res = query_db('SELECT * FROM users WHERE userid=? AND userpassword=?', [userid, userpassword])
if res:
userid = res[0]
if userid == 'admin':
return f'hello {userid} flag is {FLAG}'
return f'<script>alert("hello {userid}");history.go(-1);</script>'
return '<script>alert("wrong");history.go(-1);</script>'
```
이와 같이 입력값을 직접 SQL 쿼리에 포함하지 않고, **변수를 바인딩**하는 방식으로 안전하게 처리할 수 있습니다.
### 결론
이 코드는 SQL 인젝션에 취약한 로그인 시스템을 구현하고 있으며, 이를 방어하기 위해서는 파라미터화된 쿼리를 사용하는 방식으로 수정해야 합니다. 또한, 입력값에 대한 검증을 강화하고, 적절한 보안 대책을 적용하여 시스템을 더욱 안전하게 만드는 것이 중요합니다.
낄낄.. 근데 이걸 어떻게..입력하는거죠..? 이 소스코드는 오라클이 아닌데...어어...
도움!
https://goldsony.tistory.com/264
키야 웹에서 주석 처리가 가능하네; 기능 미쳤다; sql lite 주석처리 방법은 "--"이다. #주석도 있다(이건 안된다)
이거 진짜 보안 조금이라도 허술하면 너무 쉽게 로그인이 가능하지않나 이 생각이 들었다.
해킹의 세계는 진짜 공부할수록 기초가 진짜 무서운 것 같다.
'Dreamhack > Dreamhack Wargame (Challenge)' 카테고리의 다른 글
[39] IT 비전공자 [dreamhack]csrf-1문제 풀기 (10) | 2024.10.18 |
---|---|
[38] IT 비전공자 [dreamhack]xss-2문제 풀기 (3) | 2024.10.17 |
[36] IT 비전공자 [dreamhack]xss-1문제 풀기 (2) | 2024.10.15 |
[35] IT 비전공자 [dreamhack]session-basic문제 풀기 (4) | 2024.10.14 |
[34] IT 비전공자 [dreamhack]Broken Password문제 풀기 (0) | 2024.10.13 |