<simple_sqli>
https://dreamhack.io/wargame/challenges/24
simple_sqli
로그인 서비스입니다. SQL INJECTION 취약점을 통해 플래그를 획득하세요. 플래그는 flag.txt, FLAG 변수에 있습니다. Reference Server-side Basic
dreamhack.io
문제에서 다운받으라고 한 파일을 열어보면 이런 py 파일이 나온다.
#!/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)
/(index) /login 두 탭이 존재하는 걸 알 수 있고, guest 계정과 admin 계정 두가지가 생성되어 있음을 알 수 있다.
query_db를 보면 select * from users where userid="" and userpassword="" 모양이고 userid에 admin이 들어가면 된다. userid에 admin 값을 넣고 주석을 없애주면 된다.
#query 문을 통해 계정 두개가 생성되어있음 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")}");'
단순히 로그인만 하면 해결되는 문제로 보인다.
바로 플래그 값을 확인할 수 있다.
<Gremlin>
https://los.rubiya.kr/chall/gremlin_280c5552de8b681110e9287421b834fd.php
https://los.rubiya.kr/chall/gremlin_280c5552de8b681110e9287421b834fd.php
los.rubiya.kr
LoS는 따로 플래그를 입력하는 방식이 아sls, 코드 안의 solve() 함수가 동작하도록 하면 문제가 풀린다. solve() 함수를 동작시키기 위해선 if($result['id']) 라는 조건문을 통과해야 한다. id의 존재 여부에 따라 if 문의 조건을 통과할 수 있는지 없는지 결정된다. id가 없으면 0이 반환되기 때문에 아이디가 존재해야 한다.
select id from prob_gremlin where id='{$_GET[id]}' and pw='{$_GET[pw]}'
get 방식으로 아이디와 비밀번호를 입력받는 형식이다. 쿼리문을 손대면 되는데 검색해보니
- or 1=1: 이 부분은 SQL에서 OR 연산자를 사용하여 항상 참인 조건을 만듭니다. 1=1은 항상 참이므로 이 부분은 쿼리에 항상 참인 조건을 추가합니다.
- #: 이 부분은 주석을 나타냅니다. 주석은 SQL 쿼리의 나머지 부분을 무시하게 만듭니다.
1 or 1=1#
?id=admin' or '1==1' %23
이 내용을 php 뒤에 붙여주면 된다.
<cobolt>
https://los.rubiya.kr/chall/cobolt_b876ab5595253427d3bc34f1cd8f30db.php
https://los.rubiya.kr/chall/cobolt_b876ab5595253427d3bc34f1cd8f30db.php
los.rubiya.kr
쿼리문은 select id from prob_cobolt where id='{$_GET[id]}' and pw=md5('{$_GET[pw]}'
<br>You are not admin :(</h2>";
이 부분을 읽어보니 그냥 아이디가 admin이면 해결될 것 같다.
?id=admin&pw=123') or id=('admin 이 구문을 php 주소 뒤에 붙여주면
clear라고 뜬다.
<goblin>
https://los.rubiya.kr/chall/goblin_e5afb87a6716708e3af46a849517afdc.php
https://los.rubiya.kr/chall/goblin_e5afb87a6716708e3af46a849517afdc.php
los.rubiya.kr
쿼리문은 select id from prob_goblin where id='guest' and no={$_GET[no]}";
아이디의 값이 guest로 고정되어 있는 것을 알 수 있다. id가 admin이 되면 solve 함수가 풀리면서 clear가 된다. 그렇다면 no 부분에 1을 넣어보고
hello guest라는 문구가 뜬다. 도무지 생각해도 guest 자리에 admin을 어떻게 넣는지 모르겠어서 검색을 했더니
select id from prob_goblin where id='guest' and no=123 or id='admin'
값을 넣으면 해결이 될것같은데 싱글쿼터(') 가 필터링이 되어있어서 admin 값을 hax로 전달해서
싱글쿼터를 우회한다.
admin 을 hax로 바꿔주면 0x61646D696E 이 된다 이렇게 되면 싱글쿼터를 우회해서 문자를 넣어줄 수 있다.
이렇게 하면 싱글 쿼터를 우회하며 admin 을 넣어줄 수 있다.
?no=123 or id=0x61646D696E
<orc>
https://los.rubiya.kr/chall/orc_60e5b360f95c1f9688e4f3a86c5dd494.php
https://los.rubiya.kr/chall/orc_60e5b360f95c1f9688e4f3a86c5dd494.php
los.rubiya.kr
orc 문제는 이전 문제와는 달리 admin의 비밀번호를 알아내서 입력해야 한다.
쿼리문은 $query = "select pw from prob_orc where id='admin' and pw='{$_GET[pw]}'";
검색해보니 python requests 모듈을 사용해야 한다고 나온다. requests 모듈은 HTTP 요청을 보내고 받기 위해 사용되는 라이브러리로, 웹 서버와 상호작용하고 웹 리소스(웹 페이지, API 엔드포인트 등)를 가져오기 위한 간단하고 사용하기 쉬운 방법을 제공한다.
소스코드의 9줄을 보니, id를 맞추면 'Hello admin'이라는 문장을 출력해준다. 이를 이용해서, Blind SQL Injection으로 비밀번호를 유추하여 문제를 풀 수 있다. Blind SQL Injection은 쿼리 결과 에 따른 서버의 참과 거짓 반응을 통해 공격을 수행한다. 따라서, id가 admin인 참의 결과를 이용해서 비밀번호를 유추할 수 있을 것으로 예상된다.
query문에 id는 이미 작성되어 있는 것을 확인할 수 있다.
이를 참고하여 or연산으로 "Hello admin"을 출력할 수 있다.
? pw=' or 1=1%23 |
여기서 Blind injection은 웹 보안 공격 중 하나로, 주로 SQL Injection 공격과 관련이 있습니다. Blind injection은 공격자가 애플리케이션 또는 웹 사이트의 취약점을 통해 악의적인 SQL 쿼리를 실행하려고 시도하는 것을 의미합니다.
SQL Injection은 일반적으로 사용자 입력을 통해 데이터베이스 쿼리를 실행할 때 발생하며, 공격자는 입력 필드에 악의적인 SQL 코드를 주입하여 데이터베이스에 액세스하거나 데이터를 조작하려고 합니다. Blind injection은 시스템이 공격자에게 직접 결과를 표시하지 않는 경우에 발생합니다. 이 경우, 공격자는 결과를 확인하기 위해 다양한 기술을 사용합니다.
Blind injection 공격에는 두 가지 주요 형태가 있습니다:
1. Blind Boolean-Based Injection: 이 유형의 공격에서, 공격자는 데이터베이스에 대한 질의의 결과가 참 또는 거짓인지 확인하기 위해 논리 연산자(예: AND, OR)를 사용합니다. 공격자는 참과 거짓의 결과에 따라 논리적으로 프로세스를 수행할 수 있습니다.
2. Blind Time-Based Injection: 이 유형의 공격에서, 공격자는 시간 지연 함수(예: `SLEEP` 또는 `WAITFOR DELAY`)를 사용하여 데이터베이스가 응답을 지연시킬 것을 유도합니다. 그런 다음, 공격자는 이러한 지연을 통해 결과를 확인할 수 있습니다. 예를 들어, 데이터베이스가 지연되면 공격자는 주어진 조건이 참임을 확인할 수 있습니다.
Blind injection 공격을 방지하려면 입력 데이터를 신뢰성 있게 검증하고, SQL 쿼리를 실행하기 전에 적절한 방어적인 코딩 기법을 사용해야 합니다. 또한 웹 애플리케이션 방화벽과 보안 검사를 통해 공격을 탐지하고 방지할 수 있습니다.
비밀번호의 길이를 구하는 코드
비밀번호는 8자리인 것을 알 수 있다.
코드를 실행하면 pw=095a9852 임을 알 수 있다.