Duolingo: A tale of two APIs

Jon Roethke
8 min readSep 7, 2019
Hi, I’m Duo the owl!

This is my first public disclosure of bugs I’ve discovered in the wild. I’ll just preface this entire article by stating that these are not security vulnerabilities, simply logic errors.

The main aim of this article is less on sharing how these logic errors can be exploited and more on emphasizing the curious discovery that: sometimes applications leverage two separate APIs, for whatever reason and, in doing so, this allows for the introduction of interesting bugs (i.e. this presents a larger attack surface due to the need for securing two independent services instead of just one).

This report is about Duolingo, one of my favorite applications for learning new languages.

The Researcher’s Dilemma

I’ve discovered a few bugs, alerted the team multiple times, and have received absolutely zero response.

At this point, my only options are: 1.) to publish an article on the findings for others to learn from or 2.) do nothing. Sharing information for the betterment of others is what is most important to me, and a large part of the information security community’s ethos, so I’ve elected for the former.

I’ve sent 3 emails to the Duolingo team over the course of the last 2 months and even published a finding through HackerOne’s disclosure assistance program, which determined these bugs not to be significant enough to take further action (i.e. reach out to the Duolingo team about it).

Response from HackerOne

That’s fine, and fortunately the bugs don’t impact other users, so from HackerOne’s perspective, I get it; it’s not worth the time, especially if the bugs don’t pose an imminent security threat.

From Duolingo’s perspective, however, I don’t get it, as these bugs allow users to circumvent paying for services within the app. If I were the business owner behind this I would certainly want to patch any holes where people were able to bypass paying for services through technical chicanery.

Even just a quick email saying “thank you” would have been better than nothing at all.

Nearly three months later, still just crickets..

Summary of findings (if you read no further)

When exploited, these bugs allow a user to benefit almost entirely as a Duolingo Plus member without paying the subscription fee.

The following bugs were found:

  1. Submission of free in-app store purchases (free perks)
  2. Duplicate submission of practice routines (daily/total XP gain)
  3. Duplicate submission of profile updates (arbitrary increase of total XP)

I say almost entirely because you aren’t able to save lessons for offline use, and worse over, you will still see ads…

please, no more ads..

Impact:

Duolingo has an estimated 300 million users worldwide. Let’s assume that just 1% of them are subscribers of Duolingo Plus, meaning they pay anywhere between $80-$120/year. If every Plus user were to drop their membership and exploit these bugs instead (which offer similar functionality to Plus members), Duolingo would be losing, at most, an estimated $360 million ($9.99/mo), and at minimum, $240 million ($80/year reduced rate) in annual revenue.

Some basic Owlgebra:

3,000,000 (estimated worldwide Plus users) * $120 (yearly subscription cost) = $360,000,000 in annual revenue lost.

This seems significant enough to warrant a response of some kind.

Path to discovery

Let’s begin with how I found these suckers!

Oddly enough, in finding these bugs, I wasn’t actually seeking them out.. I was simply using the application.

While progressing through a Duolingo course, say, Swedish, users accrue in-app tokens.

Saying in-app tokens is vague, but I say this because in the mobile app, we get Gems; in the web app, we get Lingots. Gems are slated to replace Lingots eventually but essentially both in-app tokens work similarly by allowing a user to purchase in-app items called Power-Ups.

Power-Ups include:
1. Double-or-Nothing wager

  • Double your 50 gem wager by maintaining a 7 day streak. Completing this allows you to generate even MORE gems/lingots

2. Streak Freeze

  • Streak Freeze allows your streak to remain in place for one full day of inactivity…in case you’re, say, in the wilderness hiking about without service

3. Heart Refills

  • Get full hearts so you can worry less about making mistakes in a lesson
  • Heart Refills are not needed if you are Duolingo Plus member because you have unlimited lives

4. Coach Outfits (dress up the little Duolingo Owl)

  • Dress up Duo, the learning coach owl

I occasionally switch between desktop and mobile Duolingo for convenience which led me to discover the difference between in-app rewards.

Upon discovery, I decided to fire up Burp to see if there was anything else interesting going on. I proxied my mobile traffic through Burp and this is when I discovered that Duolingo was leveraging two separate APIs; what appears to be one for the mobile app and one for the web app.

Path to exploitation

Once I discovered that two separate API endpoints were being utilized between the two applications to handle requests- and my mobile traffic was still being proxied- I began using the app as a normal user would: completing exercises, updating my profile, and purchasing Power Ups from the store with my hard earned Gems/lingots.

Below are three business logic bugs that were found, all seem to be examples of broken access control, allowing for privileges that only an admin should have:

  • 1. Submission of free in-app store purchases — POST requests (free perks)
  • 2. Duplicate submission of practice routines — PUT requests (daily/total XP gain)
  • 3. Duplicate submission of profile updates — PATCH requests (arbitrary increase of XP gain, mostly a cosmetic bug)

Vulnerable host: ios-api-2.duolingo.com

All of these are easily exploitable by connecting an iPhone to a web proxy in order to intercept and manipulate requests from the Duolingo iOS app and the backend Duolingo service.

Interestingly, I found these bugs did not exist within the web app API, only the mobile API.

1. Free Perks

All of the following allow me to receive perks without spending any gems/lingots through the online store.

The easiest way to exploit these bugs is to intercept a request to purchase a perk within the store. Upon interception of the request, replace the isFree field value of false with true within the request payload: {“isFree”: false} → {“isFree”: true}.

Certain perks are provided at cost for users who are on the Duolingo Plus plan, including unlimited hearts (no needing to purchase more hearts for incorrect submissions of answers) and streak repair. Allowing users to refill their hearts for free allows them to bypass some of incentives for users to subscribe to the Plus version.

Free Health Refill

Free Streak Freeze

Free Gem Wager

Free Coach Outfits

Enumeration through verbose error messages to find hidden information

2. Duplicate submission of practice routines

Note: much of the payload was cutoff due to screenshot size restrictions

This can be duplicated by running through a practice routine within the app and then intercepting the submission of the practice routine upon completion.

This allows a user to rapidly submit duplicate submissions and be rewarded accordingly with gems/lingots, some amount of XP, and more comically (if invoked enough times), by topping the ranks of the weekly top performing learners in the various language leagues by XP gain.

3. Duplicate submission of profile updates

This allows for arbitrary increases in XP gain

Note: the XP field in the payload with value set to 5000 as an example amount

Response:

UI tag increases before/after:

Before: 10,334
After: 15,334
After, after: 50,016,894

Additionally, as this application is written in Java, perhaps there is some potential for an integer overflow here? The fact that XP can be arbitrarily incremented does beg the question..

Impact

According to Expanded Ramblings and Crunchbase, the following are rough numbers from 2017:
Total Active Users: 300 Million - Duolingo Press
Monthly Active Users: 25 Million - Expanded Ramblings
Company Valuation: $700 Million - Duolingo Press
Monthly Downloads: 4.6 Million - Crunchbase

As stated previously, when exploited these bugs allow an attacker to benefit from Duolingo similar to a Plus user without needing to pay for the Plus subscription.

Though these findings are by no means critical security vulnerabilities, the impact of sending these crafted payloads directly to the Duolingo iOS API without proper access control checks in place could defraud Duolingo and result in lost revenue.

Food for thought for a company when determining how much money might be saved by choosing to run a vulnerability disclosure program versus operating without one.

Additional testing could have been performed and likely would have yielded additional (perhaps more significant) findings, but I was hesitant to perform anything additional due to the lack of a vulnerability disclosure program, and, more importantly, due to the fact that the company elected not to respond to my reports. This is not an application that I want to spend additional time on helping to secure. I wish this were different, but their lack of willingness to even respond to researchers is both astounding and repelling.

Update: I’ve discovered that these vulnerabilities were recently patched. Unfortunately, I have still received no comment from the team. I suppose this is the risk when disclosing bugs to companies without vulnerability disclosure programs; it’s a shame, but they can very easily just patch the bugs without rewarding or acknowledging researchers for their work.

In my mind, this is a very bad policy as it reduces overall willingness of researchers to help secure their systems going forward due to a lack of incentives and an absence of safe harbor agreements.

--

--