BurpSuite - Lisäosien rakentaminen

Lisäosan kehittäminen Extender API:lla: automaattinen istunnonhallinta osa 1

Keskitaso
30 min

Lisäosan rekisteröinti

Tässä moduulissa ratkaistaan oikean maailman ongelma, eli käsitellään automaattisesti tilanteet, jossa testattava sovellus käyttää todella lyhyitä kirjautumisaikoja. Tyypillisesti tämä koituu ongelmaksi kun halutaan käyttää minkään näköistä automaatioita, suorittaa skannauksia tai käyttää työkaluja joiden suorittaminen kestää pidempään kuin mitä käyttäjä pysyy kirjautuneena.

from burp import IBurpExtender
from burp import IHttpListener

class BurpExtender(IBurpExtender, IHttpListener):
  def registerExtenderCallbacks(self, callbacks):
    self.callbacks = callbacks
    self.helpers = callbacks.getHelpers()
    callbacks.setExtensionName("Handle Authentication Plugin")
    callbacks.registerHttpListener(self)
    self.session_token = None

Aloitetaan jo tähän mennessä tutuksi tulleella lisäosan rekisteröinnillä. Lisäämme tähän vain uuden objektin nimeltä self.session_token. Tämä tulee käyttöön myöhemmin, mutta on käytännössä vain voimassa olevan sessioevästeen säilöntäpaikka.

Kirjautumisen ohjelmointi

Seuraavaksi tarvitsemme funktion, joka osaa suorittaa sovelluksessa olevan kirjautumisen ohjelmallisesti ja päivittää tästä saatavan, uuden sessioevästeen aiemmin määritettyyn session_token -variaabeliin.

def makeAuth(self):
    try:
      headers = [
        "POST /login HTTP/1.1",
        "Host: 127.0.0.1:5000",
        "Content-Length: 17",
        "Content-Type: application/x-www-form-urlencoded"
      ]
      body = "username=username"

      auth_message = self.helpers.buildHttpMessage(headers, body)
      host = "127.0.0.1"
      port = 5000
      use_https = False

      resp = self.callbacks.makeHttpRequest(host, port, use_https, auth_message)
      resp_info = self.helpers.analyzeResponse(resp)
      cookie = resp_info.getHeaders()[7]
      self.session_token = cookie.split("session=")[-1].split(";")[0]
    except Exception as e:
      print(e)

Yllä olevassa makeAuth -funktiossa rakennamme käytännössä HTTP-pyynnön, jolla voimme kirjautua palveluun, sitten lähetämme tämän HTTP-pyynnön makeHttpRequest -funktiolla. Luemme tästä saapuvan vastauksen ja kaivamme siitä uuden kirjautumisevästeen, jonka palvelu loi meille kirjautumisen johdosta.

makeHttpRequest -funktio ottaa vastaan seuraavat parametrit:

  • host - eli domain-nimi, johon pyyntö lähetetään
  • port - eli portti, johon pyyntö lähetetään
  • use_https - käytetään HTTPS yhteyttä
  • auth_message - eli äsken rakennettu HTTP-pyyntö

Keksien päivittäminen otsakkeisiin

Seuraavaksi luomme toisen funktion, joka päivittää voimassa olevan kirjautumisevästeen sille annettuun otsakelistaan.

  def updateCookies(self, headers):
    new_headers = []
    cookie_seen = False
    for header in headers:
      if "Cookie:" in header:
        new_headers.append("Cookie: session={}".format(self.session_token))
        cookie_seen = True
      else:
        new_headers.append(header)
    if not cookie_seen:
      new_headers.append("Cookie: session={}".format(self.session_token))
    return new_headers

Tämän voi toki tehdä myös processHttpMessage -funktion sisällä, mutta tässä esimerkissä olemme erotelleet tämän omaksi funktioksi. Funktio palauttaa päivitetyn listan otsakkeita, jossa on voimassa oleva eväste.

HTTP-pyynnön ja -vastauksen käsittelyn rakentaminen

Lopuksi kirjoitamme vielä processHttpMessage -funktioon tarvittavan toimintalogiikan sekä HTTP-pyynnöille:

  def processHttpMessage(self, tool_flag, is_request, message_info):
    if is_request:
      request = message_info.getRequest()
      request_info = self.helpers.analyzeRequest(request)
      # Jos kohde on oikea
      if "127.0.0.1:5000" in request_info.getHeaders()[1]:
        # jos sessio eväste on tallennettu
        if self.session_token:
          # päivitetään voimassa oleva sessio eväste pyyntöön
          new_headers = self.updateCookies(request_info.getHeaders())
          body = request[request_info.getBodyOffset():]
          new_message = self.helpers.buildHttpMessage(new_headers, body)
          message_info.setRequest(new_message)

että HTTP-vastauksille:

else:
      response = message_info.getResponse()
      response_info = self.helpers.analyzeResponse(response)
      # haetaan status koodi ja jos se on 302, sekä 
      # siirto on tapahtumassa polkuun /, tiedetää
      # että sessio on vanhentunut, koska vastaus vie meidät 
      # kirjautumissivulle
      
      if int(response_info.getStatusCode()) == 302 and "Location: /" in response_info.getHeaders():
        # Päivitetään kirjautumiseväste eli suoritetaan 
        # kirjautuminen
        self.makeAuth()
        
        # Toistetaan äsken epäonnistunut HTTP-pyyntö, jossa
        # palvelin pyysi uudelleen kirjautumista
        req = message_info.getRequest()
        req_i = self.helpers.analyzeRequest(req)
        new_headers = self.updateCookies(req_i.getHeaders())
        body = req[req_i.getBodyOffset():]
        new_message = self.helpers.buildHttpMessage(new_headers, body)
        host = "127.0.0.1"
        port = 5000
        use_https = False
        success_resp = self.callbacks.makeHttpRequest(host, port, use_https, new_message)
        # Asetetaan uudelleen suoritetun HTTP-pyynnön vastaus
        # uudelleenkirjautumista vaativan vastauksen tilalle
        message_info.setResponse(success_resp)
    return

Eli käytännössä koodimme reagoi tilanteisiin, jossa palvelin vastaa tietyllä tavalla, joka viittaa siihen että kirjautuminen on vanhentunut. Sitten suoritamme itse kirjautumisen ohjelmallisesti, tallennamme tästä saadun, uuden evästeen ja käytämme tätä tulevissa kutsuissa. Ainoa monimutkaistava tekijä on, että kun huomaamme, että kirjautuminen on vanhentunut, niin joudumme toistamaan sen alkuperäisen HTTP-pyynnön johon se palvelin alunperin vastasi, että sessio ei ole enää voimassa. Kun olemme sen suorittaneet uudella evästeellä, asetamme tämän vastauksen sitten uutta kirjautumista vaativan vastauksen tilalle. Täten mikään työkalu ei ole tietoinen siitä, että kesken suorituksen tehtiin lennosta uudelleen kirjautuminen, eikä täten mikään mene rikki.

Lopullinen koodi

Lopullinen koodi tulee näyttämään jotakuinkin tältä:

from burp import IBurpExtender
from burp import IHttpListener

class BurpExtender(IBurpExtender, IHttpListener):
  def registerExtenderCallbacks(self, callbacks):
    self.callbacks = callbacks
    self.helpers = callbacks.getHelpers()
    callbacks.setExtensionName("Handle Authentication Plugin")
    callbacks.registerHttpListener(self)
    self.session_token = None

  def makeAuth(self):
    try:
      headers = [
        "POST /login HTTP/1.1",
        "Host: 127.0.0.1:5000",
        "Content-Length: 17",
        "Content-Type: application/x-www-form-urlencoded"
      ]
      body = "username=username"

      auth_message = self.helpers.buildHttpMessage(headers, body)
      host = "127.0.0.1"
      port = 5000
      use_https = False

      resp = self.callbacks.makeHttpRequest(host, port, use_https, auth_message)
      resp_info = self.helpers.analyzeResponse(resp)
      cookie = resp_info.getHeaders()[7]
      self.session_token = cookie.split("session=")[-1].split(";")[0]
    except Exception as e:
      print(e)

  def updateCookies(self, headers):
    new_headers = []
    cookie_seen = False
    for header in headers:
      if "Cookie:" in header:
        new_headers.append("Cookie: session={}".format(self.session_token))
        cookie_seen = True
      else:
        new_headers.append(header)
    if not cookie_seen:
      new_headers.append("Cookie: session={}".format(self.session_token))
    return new_headers

  def processHttpMessage(self, tool_flag, is_request, message_info):
    if is_request:
      request = message_info.getRequest()
      request_info = self.helpers.analyzeRequest(request)

      if "127.0.0.1:5000" in request_info.getHeaders()[1]:
        if self.session_token:
          new_headers = self.updateCookies(request_info.getHeaders())
          body = request[request_info.getBodyOffset():]
          new_message = self.helpers.buildHttpMessage(new_headers, body)
          message_info.setRequest(new_message)

    else:
      response = message_info.getResponse()
      response_info = self.helpers.analyzeResponse(response)
      if int(response_info.getStatusCode()) == 302 and "Location: /" in response_info.getHeaders():
        # Session on expiroitunut 
        self.makeAuth() # Update session token
        
        # Replay the failed request with new session token
        req = message_info.getRequest()
        req_i = self.helpers.analyzeRequest(req)
        new_headers = self.updateCookies(req_i.getHeaders())
        body = req[req_i.getBodyOffset():]
        new_message = self.helpers.buildHttpMessage(new_headers, body)
        host = "127.0.0.1"
        port = 5000
        use_https = False
        success_resp = self.callbacks.makeHttpRequest(host, port, use_https, new_message)
        message_info.setResponse(success_resp)
    return

Sekunnin kirjautumisaika

Tehtävässä käytetty sovellus sisältää yksinkertaisen kirjautumisen. Tämän kirjautumisen voimassaoloaika on kuitenkin vain sekunnin. Ratkaise tämä ongelma ja hae lippu kirjautumisen takaa.

Tehtävät

Flag

Löydä lippu (flag) labraympäristöstä ja syötä se alle.

hakatemia pro

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ä.