Injektiot yleisesti
Injektio on haavoittuvuus, jossa yritetään parameterisoida jotain protokollaa, mutta parametriksi tarkoitettu data pääseekin murtautumaan ulos parametrin paikastaan ja muodostuu osaksi protokollan rakennetta.
URL-injektio
URL-injektio on tästä yksinkertainen esimerkki. Kun sovellus rakentaa URL-osoitteita, esimerkiksi tehdäkseen taustajärjestelmässä kutsun toiseen (sisäiseen) taustajärjestelmään, ja lisää rakennettuun URL-osoitteeseen jonkun polun tai parametrin esimerkiksi käyttäjän syötteestä, on riski URL-injektioon jos syötettä ei ole enkoodattu oikein.
Esimerkki
Seuraava kuva kuvastaa tilannetta, jossa URL-injektioita voi sijaita.
Yllä olevassa kuvassa verkkosivu suorittaa taustapalveluun HTTP-pyynnön käyttämällä käyttäjän antamaa tunnusta osana URL-osoitteen polkua.
url = "/varattu/" + kayttajan_antama_syote
Tämä vaikuttaa täysin turvalliselta sekä viattomalta, mutta mitä tapahtuu, jos käyttäjä antaakin tunnuksen sijaan arvon, joka uudelleenmäärittää verkkosivun tekemän kyselyn lopullisen sijainnin. Jos verkkosivu ei rakenna URL-osoitteita turvallisesti, niin tämä voi mahdollistaa esimerkiksi ../ merkkijonon käyttämistä syötteessä, jolla voidaan joskus matkustaa URL-polussa taaksepäin.
Yllä olevassa kyselyssä, käyttäjä antoi syötteenä ../kayttajat, joka muokkasi verkkosivun tekemän kyselyn lopullista sijaintia, jolloin sovellus päätyikin tekemään sisäisen HTTP-pyynnön eri polkuun kuin kehittäjä oli tarkoittanut.
Harjoitus
Käynnistä nyt alla oleva harjoitus ja lue eteenpäin.
URL-injektio harjoitus
Etsi ja tunnista URL-injektio haavoittuvuus ja hyväksikäytä tätä.
Tehtävässä käytetyn sovelluksen lähdekoodit:
import os
import flask
import requests
from urllib.parse import urlparse
from modules.database import Database
from flask import Flask, render_template, request, flash, abort, jsonify
database = Database()
app = Flask(__name__)
### RAJAPINNAT VAIN SISÄISILLE KYSELYILLE ###
# Rajapinta kehityksen auttamiseksi
@app.route("/api/users", methods=['GET'])
def users_api():
ip_address = flask.request.remote_addr
if ip_address not in ('127.0.0.1', '::1', 'localhost'):
abort(404)
users = database.get_users()
return jsonify({ 'Users': users })
# Tarkistetaan onko sposti jo kannassa
@app.route("/api/user/<user_mail>")
def user_api(user_mail):
ip_address = flask.request.remote_addr
if ip_address not in ('127.0.0.1', '::1', 'localhost'):
abort(404)
user_mail = request.view_args["user_mail"]
ret_user = database.get_user(user_mail)
return jsonify({ 'User': ret_user })
# ULKOISET RAJAPINNAT
@app.route("/varmista", methods=['POST'])
def varmista():
sposti = request.form.get("sposti")
responssi = requests.get("http://127.0.0.1:5000/api/user/"+sposti)
return jsonify(responssi.json())
@app.route("/", methods=["GET"])
def index():
return render_template("index.html")
if __name__ == '__main__':
app.run(debug=False, host='0.0.0.0')
Tehtävät
Flag
Löydä lippu (flag) labraympäristöstä ja syötä se alle.
Aloitamme suorittamalla sovelluksessa olevan rekisteröitymisen. Tarkistamme BurpSuite-ohjelmasta, tästä aiheutuneen HTTP-pyynnön.
Huomaamme, että rekisteröityminen aiheuttaa HTTP-pyynnön rajapintaan /varmista. Varmistetaan rajapinnan toiminta lähdekoodista.
@app.route("/varmista", methods=['POST'])
def varmista():
sposti = request.form.get("sposti")
responssi = requests.get("http://127.0.0.1:5000/api/user/"+sposti)
return jsonify(responssi.json())
Rajapinnan käyttämästä koodista näkee, että sovellus ottaa vastaan annetun sähköpostiosoitteen ja suorittaa uuden kyselyn rajapintaan /api/user/ + sposti. Lähdekoodista näemme myös, että sovellus ei suorita minkäänlaista formatointia annetulle sähköpostiosoitteelle, mikä tarkoittaa, että sovellus on haavoittuvainen URL-injektio hyökkäyksille.
Jos tutkimme kyseisen rajapinnan koodia, huomaamme, ettei rajapintaan voi suorittaa itse kyselyitä, sillä rajapinta tarkistaa, että kyselyt saapuvat osoitteesta 127.0.0.1.
@app.route("/api/user/<user_mail>")
def user_api(user_mail):
ip_address = flask.request.remote_addr
if ip_address not in ('127.0.0.1', '::1', 'localhost'):
...
Tutkimalla lähdekoodia lisää, voimme todeta, että sovelluksessa on myös toinen sisäinen rajapinta, jossa tehdään IP-osoitteen tarkistus.
@app.route("/api/users", methods=['GET'])
def users_api():
ip_address = flask.request.remote_addr
if ip_address not in ('127.0.0.1', '::1', 'localhost'):
abort(404)
users = database.get_users()
return jsonify({ 'Users': users })
Kyseinen rajapinta näyttää yksinkertaisesti palauttavan kaikki sovelluksen käyttäjät tietokannasta. Käytetään löydettyä URL-injektio haavoittuvuutta ja uudelleenohjataan /varmista rajapinnan tekemä kysely, yllä olevaan /api/users rajapintaan.
Voila! - Onnistuimme tekemään HTTP-pyynnön, jossa uudelleenohjasimme palvelimen suorittaman HTTP-pyynnön toiseen rajapintaan ja tämä palautti meille Flag:in.
Tärkeintä on muistaa, että emme halua käyttäjän kykenevän uudelleenmäärittämään sovelluksessa tehtävän HTTP-pyynnön rakennetta. Tähän tarkoitukseen voidaan käyttää URL-enkoodausta, johon löytyy erilaisia kirjastoja, riippuen hieman, mitä ratkaisuja käytät. Esimerkiksi, Node.js sovelluksissa voit käyttää encodeURIComponent funktiota, johon syötät käyttäjältä saadun tiedon, ennen kuin asetat tämän lopulliseen URL-osoitteeseen.
//Haavoittuv:
var url = "http://taustapalvelu.com/api/esim/" + tunnus;
//Ei haavoittuva
var url = "http://taustapalvelu.com/api/esim/" + encodeURIComponent(tunnus);
Tämä täytyy myös muistaa, kun käytät käyttäjältä saatua tietoa parametreissä, ettei hyökkääjä kykene manipuloimaan kyselyissä käytettyjä parametrejä.
//Haavoittuva
var url = "http://taustapalvelu.com/api/tunnus?nimi=" + nimi;
// nimi parametri sisältää "bob&poista=true"
//Ei haavoittuva
var url = "http://taustapalvelu.com/api/tunnus?nimi="+ encodeURIComponent(nimi);
Valmis ryhtymään eettiseksi hakkeriksi?
Aloita jo tänään.
Hakatemian jäsenenä saat rajoittamattoman pääsyn Hakatemian moduuleihin, harjoituksiin ja työkaluihin, sekä pääset discord-kanavalle jossa voit pyytää apua sekä ohjaajilta että muilta Hakatemian jäseniltä.