github twitter email
Bypassing Captchas on Telekom.de
May 20, 2019
5 minutes read

I was able to bypass two Captcha systems on Telekom.de and in this post I am going to explain how I managed to do so.

The First Bypass

Deutsche Telekom provides building owners the ability to check their current order status (https://www.telekom.de/hilfe/bauherren/auftragsstatus). This site contains an iFrame that loads https://bauherren.telekom-dienste.de/ where the actual form is located. For checking an order, you have to provide a six-digit order number and complete a Captcha to prove that you are not sending automated requests. This Captcha is composed of five icons, one of them does not belong to the set. A user has to pick that icon.

Bauherren Captcha This is the corresponding source code to the screenshot above.

<section id="captcha" data-captcha-name="tracker" data-captcha-key="ed65d6da90779277d3badbe55f0e2a385bd821fd7831883476c30d5771dceac5">
<h3 class="captcha-headline">Sicherheitsabfrage: Bitte wählen Sie das Symbol welches hier nicht passt</h3>
<div class="captcha-set">
    <a class="captcha-item" data-captcha-index="0" href="#">
        <img src="/system/captchas/83f26bdafff0be36267adaef84de652d6dbf711b.png?1508497417">
    </a>
    <a class="captcha-item" data-captcha-index="1" href="#">
        <img src="/system/captchas/8ed244bb0c9f2eb5f4ba471b9985df138a56ba11.png?1508497417">
    </a>
    <a class="captcha-item" data-captcha-index="2" href="#">
        <img src="/system/captchas/e9a20ea0bf6ff68f4c6a58e9e8f8b9656bf9cbf6.png?1508497416">
    </a>
    <a class="captcha-item" data-captcha-index="3" href="#">
        <img src="/system/captchas/e1055b6a3e124da6ab9e610dab2c022642c262dc.png?1508497417">
    </a>
    <a class="captcha-item" data-captcha-index="4" href="#">
        <img src="/system/captchas/62674cff374707e687eddbeb738aa01c6feeeaf5.png?1508497417">
    </a>
</div>
<a class="captcha-reload-button" href="#" title="Captcha neu laden">
    <img src="/assets/dtag_captcha/reload-icon-0497a5d120d8491c300d587de04c78a5b7b51508ca757b14e33f6c767c450417.png" alt="Reload icon">
</a>
</section>

This Captcha is loaded with the site itself or - after clicking the refresh button on the right - via an XHR. Therefore it’s trivial to request one with curl:

$ curl -X POST "https://bauherren.telekom-dienste.de/services/captcha/reload" --data "captcha[name]=fooBar"
{"captcha_key":"ed65d6da90779277d3badbe55f0e2a385bd821fd7831883476c30d5771dceac5","captcha":[
    "/system/captchas/83f26bdafff0be36267adaef84de652d6dbf711b.png?1508497417",
    "/system/captchas/8ed244bb0c9f2eb5f4ba471b9985df138a56ba11.png?1508497417",
    "/system/captchas/e9a20ea0bf6ff68f4c6a58e9e8f8b9656bf9cbf6.png?1508497416",
    "/system/captchas/e1055b6a3e124da6ab9e610dab2c022642c262dc.png?1508497417",
    "/system/captchas/62674cff374707e687eddbeb738aa01c6feeeaf5.png?1508497417"]}

The Vulnerability

Notice the weired numbers (Unix timestamps) at the end of every icons filepath and how only the third one differs from the others? Now look at the screenshot above. The third icon is also the solution to this Captcha! It seems like the timestamps are the time that the icons have been created. I have found out that there are four different timestamps:

  • 1508497416 (Football, Flags, Animals (Frog, Monkey, Cat, Bunny))
  • 1508497417 (Animals, Weather, T-Shirts, Tools/Household Items, Animals (Pig, Penguin))
  • 1508497418 (Food)
  • 1508497419 (Astrological Signs, Vehicels, Office Equipment)

If the Captcha consists of icons from different timestamps, you can detect the correct icon. Unfortunately this is not alway the case: In around 20% of all requests there is no unique timestamp: This happens when the Captcha is composed of icons from two categories with the same timestamp (e.g. football and flags) or when specific animal icons are used, as they may come from different timestamps (e.g. pig and frog). But: In all other 80% we now can programmatically check order numbers. Let’s check one with the correct icon index (captcha[position]=2) from the example above:

$ curl -X POST "https://bauherren.telekom-dienste.de/api/orders.json" --data "captcha[name]=fooBar&captcha[key]=ed65d6da90779277d3badbe55f0e2a385bd821fd7831883476c30d5771dceac5&captcha[position]=2&orders[contract_id]=123456"
{"error":"Mit ihren Angaben konnte kein Auftrag gefunden werden","type":"custom","key":"order_not_found"}

Hm, no order found with order number “123456”, but at least we solved the Captcha! I wrote a short Python script that iterates over all order numbers by requesting a new Captcha, solving it and checking the next order number. But how fast can we check all order numbers? The form input and FAQ below the form hint that the order number consists of exactly six numeric characters, so there are 10^6 possible permutations. I was able to check up to 5,5 order numbers per second with eight concurrent threads. Therefore we can iterate over all permutations in only 10^6/5,5/60/60/24 = 2,1 days. Of course it is not a good thing that this vulnerability exists, but it is definitely not critical: As the (hidden) status in the HTML hints and Telekom later confirmed to me, the only thing that could be exposed is the current status and timeline of the order, but no personal data like customer names, addresses, etc.

The Fix

None. Telekom explained that the Captcha will be fixed later as there is no personal data exposed.

The Second Bypass

The second Captcha I was able to bypass is used on “https://netzumschaltung.telekom-dienste.de/index.php", where customers can check when their internet connection is switched to an IP-based internet connection. Netzumschaltung Captcha This time the Captcha is not bound to a Captcha key as above, but to the PHPSESSID cookie, so either visit the site to retrieve a Captcha and PHPSESSID or retrieve it with curl:

$ curl -I "https://netzumschaltung.telekom-dienste.de/secimage/securimage_show.php"
HTTP/1.1 200 OK
[...]
Set-Cookie: PHPSESSID=qgbck71n77lh4qbmd380kbeur6; path=/; secure; HttpOnly
[...]
Content-Type: image/png

The Vulnerability

The problem is here that the Captchas audio playback is implemented improperly, so let’s request one with our PHPSESSID retrieved above:

$ curl "https://netzumschaltung.telekom-dienste.de/ajax.php?myELEMENT=migration_search&ajax=1&func_ajax=readcaptcha" -H "Cookie: PHPSESSID=qgbck71n77lh4qbmd380kbeur6"
{"word_arr":["secimage\/audio\/en\/3.mp3","secimage\/audio\/en\/B.mp3","secimage\/audio\/en\/4.mp3","secimage\/audio\/en\/4.mp3","secimage\/audio\/en\/U.mp3","secimage\/audio\/en\/P.mp3"]}

Well, as you can already see the audio files are named after the Captchas content, so only by looking at the filenames you can tell that the Captcha code is “3B44UP” (not case-sensitive).

The Fix

Around two weeks later I checked the Captcha again and noticed a change: The URL to retrieve the Captcha MP3s not longer returns the filenames, but this (shorted):

{"word_arr":["SUQzBAAAAAAAFlRFTkM...Q==","SUQzBAAAAAAAFlRFTkM...Q==","SUQzBAAAAAAAFlRFTkM...Q==","SUQzBAAAAAAAFlRFTkM...Q==","SUQzBAAAAAAAFlRFTkM...Q==","SUQzBAAAAAAAFlRFTkM...Q=="]}

Looks like some base64 encoded stuff! Let’s see what is encoded there by decoding the string with Python.

$ python -c 'import base64; print(base64.b64decode("SUQzBAAAAAAAFlRFTkM...Q==").encode("hex"))'
49 44 33 04 00 00 00 00 00 16  [...] 55 55 55 55 55 55 55 55 55 55

or in binary, by omitting the encode("hex") at the end:

ID3[...]TENC[...]Lavf51.12.2[...]LAME3.98 (beta)[...]

As you can see by the Magic Bytes (49 44 33) at the beginning, this is an MP3 file with an ID3v2 container and it seems that they used LAME 3.98 (beta) with Libavformat 51.12.2. We can confirm this observation by looking into the sites JavaScript: readCaptchaLetter(source) is called with the base64 encoded MP3 as ‘source’, the MP3 is attachted to the media player as a Data URL and played back.

/**
 * Hilfsfunktion liest einzelnen Buchstaben vor
 * @param source
 */
function readCaptchaLetter(source)
{
	var audioElement = document.getElementById("player_id");
	audioElement.setAttribute("src", 'data:audio/mp3;base64,' + source);
	audioElement.play();
}

Because we no longer receive the file path, but only the file contents, there is no chance for us to decode this Captcha. We could try analysing the audio file itself, but that would be way harder. Well done!

Timeline

  • 21.03.19 - first contact
  • 22.03.19 - report und PoC provided
  • 02.04.19 - second Captcha fixed
  • 29.04.19 - Telekom explained that the Captcha will be fixed later as there is no personal data exposed

Back to posts