HAKATEMIA
04BurpSuite lisäosien rakentaminen vanhalla tavalla (Extender API)

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

Keskitaso30MIN

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.

PY
1from burp import IBurpExtender
2from burp import IHttpListener
3
4class BurpExtender(IBurpExtender, IHttpListener):
5  def registerExtenderCallbacks(self, callbacks):
6    self.callbacks = callbacks
7    self.helpers = callbacks.getHelpers()
8    callbacks.setExtensionName("Handle Authentication Plugin")
9    callbacks.registerHttpListener(self)
10    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.

PY
1def makeAuth(self):
2    try:
3      headers = [
4        "POST /login HTTP/1.1",
5        "Host: 127.0.0.1:5000",
6        "Content-Length: 17",
7        "Content-Type: application/x-www-form-urlencoded"
8      ]
9      body = "username=username"
10
11      auth_message = self.helpers.buildHttpMessage(headers, body)
12      host = "127.0.0.1"
13      port = 5000
14      use_https = False
15
16      resp = self.callbacks.makeHttpRequest(host, port, use_https, auth_message)
17      resp_info = self.helpers.analyzeResponse(resp)
18      cookie = resp_info.getHeaders()[7]
19      self.session_token = cookie.split("session=")[-1].split(";")[0]
20    except Exception as e:
21      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.

PY
1  def updateCookies(self, headers):
2    new_headers = []
3    cookie_seen = False
4    for header in headers:
5      if "Cookie:" in header:
6        new_headers.append("Cookie: session={}".format(self.session_token))
7        cookie_seen = True
8      else:
9        new_headers.append(header)
10    if not cookie_seen:
11      new_headers.append("Cookie: session={}".format(self.session_token))
12    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:

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

että HTTP-vastauksille:

PY
1else:
2      response = message_info.getResponse()
3      response_info = self.helpers.analyzeResponse(response)
4      # haetaan status koodi ja jos se on 302, sekä 
5      # siirto on tapahtumassa polkuun /, tiedetää
6      # että sessio on vanhentunut, koska vastaus vie meidät 
7      # kirjautumissivulle
8      
9      if int(response_info.getStatusCode()) == 302 and "Location: /" in response_info.getHeaders():
10        # Päivitetään kirjautumiseväste eli suoritetaan 
11        # kirjautuminen
12        self.makeAuth()
13        
14        # Toistetaan äsken epäonnistunut HTTP-pyyntö, jossa
15        # palvelin pyysi uudelleen kirjautumista
16        req = message_info.getRequest()
17        req_i = self.helpers.analyzeRequest(req)
18        new_headers = self.updateCookies(req_i.getHeaders())
19        body = req[req_i.getBodyOffset():]
20        new_message = self.helpers.buildHttpMessage(new_headers, body)
21        host = "127.0.0.1"
22        port = 5000
23        use_https = False
24        success_resp = self.callbacks.makeHttpRequest(host, port, use_https, new_message)
25        # Asetetaan uudelleen suoritetun HTTP-pyynnön vastaus
26        # uudelleenkirjautumista vaativan vastauksen tilalle
27        message_info.setResponse(success_resp)
28    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ä:

PY
1from burp import IBurpExtender
2from burp import IHttpListener
3
4class BurpExtender(IBurpExtender, IHttpListener):
5  def registerExtenderCallbacks(self, callbacks):
6    self.callbacks = callbacks
7    self.helpers = callbacks.getHelpers()
8    callbacks.setExtensionName("Handle Authentication Plugin")
9    callbacks.registerHttpListener(self)
10    self.session_token = None
11
12  def makeAuth(self):
13    try:
14      headers = [
15        "POST /login HTTP/1.1",
16        "Host: 127.0.0.1:5000",
17        "Content-Length: 17",
18        "Content-Type: application/x-www-form-urlencoded"
19      ]
20      body = "username=username"
21
22      auth_message = self.helpers.buildHttpMessage(headers, body)
23      host = "127.0.0.1"
24      port = 5000
25      use_https = False
26
27      resp = self.callbacks.makeHttpRequest(host, port, use_https, auth_message)
28      resp_info = self.helpers.analyzeResponse(resp)
29      cookie = resp_info.getHeaders()[7]
30      self.session_token = cookie.split("session=")[-1].split(";")[0]
31    except Exception as e:
32      print(e)
33
34  def updateCookies(self, headers):
35    new_headers = []
36    cookie_seen = False
37    for header in headers:
38      if "Cookie:" in header:
39        new_headers.append("Cookie: session={}".format(self.session_token))
40        cookie_seen = True
41      else:
42        new_headers.append(header)
43    if not cookie_seen:
44      new_headers.append("Cookie: session={}".format(self.session_token))
45    return new_headers
46
47  def processHttpMessage(self, tool_flag, is_request, message_info):
48    if is_request:
49      request = message_info.getRequest()
50      request_info = self.helpers.analyzeRequest(request)
51
52      if "127.0.0.1:5000" in request_info.getHeaders()[1]:
53        if self.session_token:
54          new_headers = self.updateCookies(request_info.getHeaders())
55          body = request[request_info.getBodyOffset():]
56          new_message = self.helpers.buildHttpMessage(new_headers, body)
57          message_info.setRequest(new_message)
58
59    else:
60      response = message_info.getResponse()
61      response_info = self.helpers.analyzeResponse(response)
62      if int(response_info.getStatusCode()) == 302 and "Location: /" in response_info.getHeaders():
63        # Session on expiroitunut 
64        self.makeAuth() # Update session token
65        
66        # Replay the failed request with new session token
67        req = message_info.getRequest()
68        req_i = self.helpers.analyzeRequest(req)
69        new_headers = self.updateCookies(req_i.getHeaders())
70        body = req[req_i.getBodyOffset():]
71        new_message = self.helpers.buildHttpMessage(new_headers, body)
72        host = "127.0.0.1"
73        port = 5000
74        use_https = False
75        success_resp = self.callbacks.makeHttpRequest(host, port, use_https, new_message)
76        message_info.setResponse(success_resp)
77    return
1 / 2
Hakatemia Pro

Hakkeroinnin oppiminen alkaa tästä

Sadat interaktiiviset kurssit, virtuaalilabrat ja CTF-haasteet selaimessasi. Aloita ilmainen kokeilu ilman korttitietoja.