How we hacked more than 10,000 user accounts at the University of Amsterdam

Last year, Bram ter Borch (a fellow student of the Master System and Network Engineering at the University of Amsterdam) and I did a security review of the Blackboard implementation at the University of Amsterdam. In a couple of days time in total, we managed to find several vulnerabilities in the system, which eventually led us to take over more than 10,000 live user accounts. In this blogpost, I will try to shed a light on how we did this and what the impact was/is.

The official paper, as presented to the board of the UvA board of UvA’s ICT services department and as handed in for our study, can be found at here.

Before we begin, I want to make clear that ethics played a big role during the entire review, for which we had full cooperation and written consent of the people responsible at the university. This wasn’t about ‘just’ harvesting unnecessary information because we could. After, and even during our review, we immediately notified the stakeholders about our findings. The decision to publish the paper on was taken by the Ethics Committee of our study a year after we first presented to the university’s board about our findings, who left them around for a year. Then, after asking permission to do so, I wrote this blog. Feel free to contact me or them if you have any issues concerning this publication.

How it all started

At the end of April 2016, we decided to review the security of Blackboard at the University of Amsterdam (UvA) for a study course called Offensive Technologies. Blackboard, which ironically enough automatically redirects to from Dutch IPs (you will understand why that is ironic later), is used as an online learning environment for educational institutes. Courses can be created, students can be enrolled, homework can be distributed and graded, etc. In 2014, more than 75 percent of colleges and universities in the US used it, according to Wikipedia. In the Netherlands specifically, a quick Google search shows, the ten largest universities all have Blackboard running. The UvA, leader in this list with over 30.000 students, was no exception here. We had one month time, 2 days a week, to find security flaws. By the end of May, we owned more than 10,000 accounts.

Pwned in a minute

After literally a couple of seconds, we noticed that the login page was served over https, but that users were automatically redirected to plain http after a successful login. Cookies were set as HTTP-only, but nothing stopped us from doing a Man-in-the-Middle (MITM) attack to steal whatever session we wanted. It turned out there was virtually no way to do a safe login; even if a user decided to force the use of HTTPS we could easily strip it out. Then, the only type of authentication was basic authentication (base64(user:pass)) which was sent over plain HTTP. No security there!

At this point already, we could have written a script that automatically harvested valid sessions of students at public hotspots (Dutch trains have free wifi 🙂 ) or at the university network itself. However, we went on to look for more interesting things; a system that does this kind of stuff will probably have more flaws. We figured there would probably be a more “intelligent” way of breaching security.

One thing to mention here, however, is the password change feature in Blackboard. Whenever a user is logged in, he can change his password without having to provide the current password for that account. This means we could not only have taken over each valid session, but we could have hijacked all accounts on the same network. Assuming the administrator, which we had close contact with, also uses to log in on the same network, we could have become administrator on the system easily with a bit of social engineering or a WiFi Pineapple. According to Blackboard’s official documentation, the default settings allow an administrator to create additional administrator accounts, too (again, any student could easily do this on the local University network).


In the documentation linked to above, it turns out Blackboard’s password policy means a password has to “exist of at least one character, and cannot consist of spaces“. Nice to know; this is something that we used later on.

Testing, acceptance and production

Initially, we were provided with test accounts for the UvA’s testing environment. In short, the UvA only allowed us to do the security review if we used their testing environment instead of production. However, the first time we logged in we noticed the courses from the live system (where we as students both had an account already) were also listed in our testing accounts. As it turned out, the data in the testing environment was just an exact copy of the production environment (which could not have been more than a couple of months old). This meant that the data we were dealing with was just slightly (or not even) outdated real-life data.

Vulnerabilities in software

The version of Blackboard we tested originated from 2013. We found tens of CVEs for the server software it was running on. In this security review, we didn’t focus on those too much as they didn’t have an extremely high severity (i.e. we couldn’t just pop shells that way).

Getting ‘some’ accounts!

And now, the real fun starts.

In Blackboard, each user can be granted access to courses. Per course, privileges can be set for those users. A standard course would have one or more instructors, and of course some students. Additionally, other roles (such as guestcourse builder, etc.) can be assigned to users, too. As I was a teaching assistant once, I got assigned instructor’s privileges to a specific course (which they should never have done; in fact there is a specific user role called teaching assistant).

Normally, instructors add their students to the courses they prepared for them in Blackboard. As such, I was automatically granted access to those features as well (I was an “instructor” after all). Of course, we went to look for students here. The image below shows how users could be added to a course:

One option here is to add a user by just its username. But, of course, a teacher doesn’t know all usernames of the students. Therefore, Blackboard thought it would be nice to add a “Browse…” button. Let’s see what that looks like:

While we initially had the idea to scrape user accounts from this page, it turns out they actually implemented a search filter that says “Not blank“. What followed was more than 5,700 pages of user accounts, 25 rows each. Each account in the output consisted of the following information:

  • Status
  • First Name
  • Last Name
  • Username
  • Email

Now that was easy. We chose to crank up the paging options to show as many accounts per page as possible, and scraped them all using a simple curl script. It took us 146 requests to retrieve the information of 143,147 user accounts. After parsing them, we analyzed them a bit and saw something remarkable: there were 574 users whose usernames equaled their first name and their last name. For example, such a user would have “john” as its username, “john” as its first name and “john” as its last name (this specific one is made up, of course). That made us think: what are the odds that their passwords would also be the same?

We decided to put it to the test. As we were allowed to do anything on the testing environment, we tried to log in using “username” == “password” (e.g. username “john“, password “john“) on the system for all 143,147 accounts (more on rate limiting later). What happened next blew our minds.

In fact, we initially thought it would be a bug in our code. We had the script output “–> hacked!” behind each compromised account as the list was looped through, but we saw that popping up for tens of users within not even a minute. Eventually, it turned out that it wasn’t a bug: we were indeed cracking those accounts successfully. By the time our script finished, we had cracked an astonishing 10,805 user accounts. Yes, you are reading this correctly: there were 10,805 user accounts that used their username as their password (>7.5 percent of all accounts).

Next, we thought it would be nice to have some insight in the user roles of these accounts (i.e. their privileges in the system). We used a simple curl loop that requested the homepage for each hacked user, providing their usernames and passwords we just retrieved as the request headers. Part of the response would be the list of courses the user was enrolled in (including old courses). On the top of the page, it would say “Courses where you are <user role>” (where <user role> was “instructor“, for example), or its Dutch equivalent if it was a Dutch account. By parsing the output, writing it to a file and sorting and counting them, we eventually got an overview of all user roles (see picture below).

The image above shows that 2,615 + 405 = 3,020 of the hacked accounts were student accounts. Instructor, which equals Cursusleider in Dutch, had 352 occurrences. This means we now had 352 accounts with full privileges over at least one course. Readers who notice the numbers do not add up to the total of 10,850 accounts are right. It turned out the rest of the accounts (which are not shown in this list) were not enrolled in any courses. Hence, the “Courses where you are” string could not be found in the response to our request for those users.

And then, just when we thought we had seen it all, we came across our little gem: the tester account. Yes, that’s right, there was an account with username “tester” and password “tester“, which showed up in our list of hacked user accounts. Not only was Tester a student in 80 courses, he also was course builder in 2 courses, reader in 2 courses and course leader in 3 coures. This showed us our attack did not even require initial access as a teacher. Just trying tester/tester as username/password when logging in was enough. Note that this was not only in the testing environment; it also worked in production (which we verified with permission of the UvA) as most of the data was the same in both environments anyway.

If you think the tester account was an exception: you are wrong. Just one of many examples is a (we assumed) shared account managing the information and courses of an entire faculty, which had access to 462 courses as either course leader or teaching assistant. The most insane “username==password”-account had access to 1,045 courses.

In total, it turned out we just granted ourselves access to 3,017 courses (see output below):

The top 10 accounts with access to most courses (usernames not included; the number is the number of courses the account had access to):

In the “Practical attack” section, we will show how we abused this information.

Stored XSS >> CSRF

Although we largely focused on hijacking user accounts, we also had a brief look into the contents of the courses. We quickly found a stored XSS vulnerability in the course overview page. This is the page students see when they click a course they are registered in. As an instructor (and possibly also as a less-privileged user; we did not verify this), one could inject arbitrary Javascript code in those pages, which a student visiting that course would automatically execute (see image below).

Just as an example, we injected a BeEF hook into the page which allowed us to trick imaginary users (in our test lab, of course) to download arbitrary browser plugins leading to remote code execution on student’s systems. While the exploit of this vulnerability is not UvA specific, it could give an instructor (or someone who hijacked an instructor account) the opportunity to trick students into allowing them access to their systems (see the BeEF Wiki for more information).

Initially, it seemed like the possibilities of exploiting stored XSS were limited as CSRF tokens were used for all POST requests. However, we managed to implement an XSS attack that lets an instructor fetch a valid CSRF token first in a XMLHttpRequest, which can then be used to do whatever action the attacker wants (as we provided a valid CSRF token). This included changing passwords, getting grades changed, adding, changing or removing enrollments; any request a target could do could now be done on behalf of that user (if he visits the injected page).

Example CSRF token:

Example CSRF attack to make an instructor add another account to a course as an instructor, too (exploited through stored XSS):


As we had granted ourselves access to so many courses, we realized we also had access to the files in those courses. In courses for which we had only student accounts, we of course could only see the files made visible to students. However, since we had hundreds of higher-privileged accounts, we could even see many files that were not supposed to be public (yet).

One example we gave when presenting these issues to the board of the UvA board of UvA’s ICT services department was an unpublished exam which we could see (more on this later). As we were so highly privileged we could also see (and change) people’s grades, manage and see all course information, enroll users into them and even vanish entire faculties using our most powerful accounts. One thing to note here is that the UvA officially does not use Blackboard to register grades, according to the people responsible. However, personal experience shows that many teachers will first record all grades in Blackboard, and (sometimes much) later register them in the UvA’s official grading system, sis. In fact, I even had to do that myself when I was a teaching assistant.

Again, we thought we had seen it all. And again, we turned out to be wrong. The UvA had implemented a filesystem that could be connected to using WebDAV. Using a WebDAV client, for example, a user could retrieve all files he is allowed to see. Seemed like a dead end to us at first – it appeared like user and file permissions were configured properly – until we saw how the file URLs were built up. At one location on Blackboard, we stumbled upon a link to a file that had a number in it. Further research showed they were using inodes to link to files, which they called a “xythos ID” (xid). By browsing to “<random ID>“, we could see a file, but only if we had access to it. So what could we do next?

If we provided a valid xythos-ID, we would see the file. If we did not have access, but if the file existed, we were presented a screen showing us the full path to the file and the user (or users) who did have access to it (along with other information). Now wasn’t that useful? We wrote a script that iterated through all numbers from 1 to 10,000,000 and saved the file path if a file existed for that value:

After trying the values between ten and ten million, we were able to harvest the filepaths of 656,585 files. What we could do with those will be clear in a bit.

Practical attack

I always love to combine attacks. Let’s make this a bit more practical, and assume the following attack scenario (which, by the way, is a real life attack we successfully demonstrated to the UvA’s board):

  1. Our attacker, Malice, harvests instructor accounts using the method described above (using tester/tester as the initial foothold)
  2. Malice then lists all courses she has instructor privileges for
  3. Using the file list generated by iterating over the xid values, she knows there exists a file called “Exam May 2017.pdf“. She doesn’t have access to it, but she knows who does have access to it.
  4. She looks up whether she has already compromised the account that has access to the exam (keep in mind that one account we compromised had access to 1045 courses). If not, she can do three things to get access to the file:
    1. Look whether the target instructor is in one of the same courses Malice is instructor for. If so, she can easily perform a CSRF attack from the course’s homepage. If the target instructor visits the page, Malice is automatically added to the target course (where the file located) as an instructor, too (see the image below, where we did exactly this). She now has access using her own account.
    2. Try to launch a targeted dictionary (or brute force) attack to crack the target instructor’s password. If she manages to do this, she has access to the account.
    3. As the instructor works at the UvA by definition, a MITM attack in one of the UvA buildings could be use to either hijack the instructor’s session and grab the file on-the-fly, or to even change the password and get access to the file that way.

By doing exactly the steps above (it turned out we already had access to our “target instructor” from the example), we were able to get access to non-public exams that were still to be taken by students. The number of attack scenarios here are countless; it does not take too much imagination to think of a scenario in which grades are changed before they are submitted to the official system, or much worse (e.g. DoS of any kind).


It gradually became clear to us that we were not only dealing with a bad password policy here, but that there was a deeper, underlying problem. Users were given privileges they were never supposed to get, which happened to me personally as well. Using those unnecessary privileges, users could get access to everything described here. This, combined with the use of shared accounts and automatically generated accounts (which we assumed caused the hacked accounts not registered to any courses) resulted in an absolute mess.

Automatically generated accounts turned out to be provided with terrible passwords. This should never be allowed. Enforcing password policies is trivial and should not even be optional.

Bonus: Cracking a VIP’s password

As we had some time left, we decided to check and see how good the monitoring department did their work. We took one specific VIP account and launched a dictionary attack (did I already mention there was no rate limiting, no account lockouts and 16 different servers for the testing environment alone?). Although rockyou.txt didn’t do the job, we did manage to try all 14,344,392 passwords for that account in a really short time from one IP address. Monitoring? Not a clue.

Full disclosure

We waited for a year to go public. These are not minor issues, and should have been fixed long ago already. Initially, UvA scaled the “incident” up to a “Code Red” internally. Eventually, this turned out to be a sham. Nobody did anything, nobody cares about the students’ privacy and we more and more got the strong suspicion they will just wait for the new platform, Canvas, to be implemented (scheduled for 2018). If that happens, our findings will have less or no value anymore. After consulting the Ethics Committee of our study, they decided to go full disclosure for us. This blog post is meant to accompany the report in a less formal way.


  • 25 April 2016: Security review started
  • 24 May 2016: Presentation + disclosure of findings for the board of the UvA board of UvA’s ICT services department
  • 26 September 2016: UvA claims to have fixed stuff, turns out (almost) everything is still possible
  • 17 January 2017: Request for update so that we could publicly disclose our findings
  • 31 January 2017: No response, reminder sent
  • 31 January 2017: Response that we would “maybe” get a reply within a week
  • 29 April 2017: Still no response
  • 25 May 2017: Full disclosure


Leave a Comment

Your email address will not be published.