read more
On Aug 16, 2017, at 8:01 AM, Raphael Elspas  wrote:

Hello Joe and Tara,

I am a student at University of Maryland studying computer engineering and I really enjoyed using your site! The design is quite sleek and it's super easy to use.

I have discovered a persistent XSS vulnerability in your website that can expose user data and allow attackers to impersonate other accounts. This can cause a valid users page to be compromised.

Please let me know when we can discuss the vulnerability and negotiate a bounty.

Thank you,
Rafi Elspas

On Thu, Aug 17, 2017 at 3:19 PM, Joe Puccio  wrote:
Hi Rafi, 

Thanks for writing in. We’re glad to hear you like Coursicle! 

We appreciate you taking the time to pen test our site. Would you mind sharing more details about the XSS vulnerability? Does it require action by victims beyond clicking a carefully created link? We can setup a scenario where you demonstrate the XSS against a test account we have created, just send us instructions that we’ll carry out with our test account.

Going forward, we’d prefer if you refrain from pen testing Coursicle unless we invite you to do so (in the interest of protecting our users and our data). 


Joe Puccio
Co-founder, Coursicle

On Aug 18, 2017, at 9:19 AM, Raphael Elspas  wrote:

Hi Joe,

I have found three vulnerabilities. One is an authentication bypass error, another is a persistent self-XSS vulnerability, and last is a combination of the previous two to form a persistent XSS vulnerability that lacks the need for any Social Engineering. Ultimately, the last attack can be used to create a worm that can let an attacker compromise all user accounts that login with Facebook on your system.

1) Authentication bypass:
This attack will allow you to change another user's classes if you are friends with them on Facebook.
To replicate authentication bypass first login through facebook. Then view the POST response from retrieveFriendsClasses.php. This can be seen with the inspect element tool and then under the Network tab. It will return the unique user ID of a Facebook friend that has enrolled in classes through coursicle and several of the schedule names that that user has. You can then craft a POST request to storeUserClasses.php to bypass authentication and change another users classes using the following as the request body. The header does not need to be changed.

uniqueUserID=&schoolName=auburn¤tlySavedClasses=[{"class":"ACCT 2110-006","schedulesSavedTo":[],"semester":"fall2017"}]

where an example may be:
uniqueUserID=1814803848534230&schoolName=auburn¤tlySavedClasses=[{"class":"ACCT 2110-006","schedulesSavedTo":["Chalie's Schedule"],"semester":"fall2017"}]

This can be used to bypass authentication and overwrite any other user's classes. You can delete all of another user's classes with the request body:


2) Persistent self-XSS vulnerability:
This attack stores JS code on your servers and will only affect the user who inputs the code. Since it is persistent attack (where the JS is stored), JS will be legitimately coming from your site so it will bypass any browser XSS detectors. Also even if a page refreshes, the JS will persist.
First go to a university's page such as Then click the Schedule tab. Click the gear icon next to the semester name. Click rename. Change name of semester to . Refresh page, navigate to the schedule tab and a popup should appear. The popup should also appear if you are under the browse tab and you click on a star on a class tile. Modern browsers flag XSS, however, in this case, the browser will think the request looks legitimate since the JS is stored on your server and will not flag it.

This just shows that JS can be executed on the page. However, if you craft the JS carefully, you can:
a) Forward all of your cookies to another person, allowing an attacker to hijack your session.
b) You can disable the schedule tab page forever. If you inject  and refresh, the schedule tab page will appear blank. Since users cannot completely logout without deleting their cookies manually, or changing their session id, then an attacker can force their schedule page to break consistently.
c) Expose a valid user's friends on Facebook. If a victim is logged on with an FB account, an attacker can view the ID of the friends visible to the website through the FB API that is called through retrieveFriendsClasses.php.

3) Persistent non social engineered XSS vulnerability:
You can combine 1) and 2) to create an XSS attack that does not need a user to click on a link. You can create a POST request to storeUserClasses.php to inject JS into another persons account by using the following in the POST request body (no headers need to be changed). The script is injected in the same location as 1).

uniqueUserID=&schoolName=auburn¤tlySavedClasses=[{"class":"ACCT 2110-006","schedulesSavedTo":[],"semester":"fall2017"}]

An example of a POST request body that would forward a victim's cookies to an attacker the moment a victim clicked on the Schedule tab could be the following:

uniqueUserID=1814803848534230&schoolName=auburn¤tlySavedClasses=[{"class":"ACCT 2110-006","schedulesSavedTo":[


After a victim clicked on this link, an attacker would have his cookies, and therefore his session ID, and could hijack his session, logging in as the victim and have full access to his account. The victim and attacker need to be friends on Facebook.

4) Further

The JS can be crafted to always send the attacker a list of the victim's friend's IDs. Then, an attacker has access to his friend's friend's IDs. Then he can infect these people with 3) and get their friends. This will cause a never ending chain between all users on your site. An attacker can create a worm attack that compromises ALL user data on your site eventually. This is VERY dangerous.

I apologize for seeking these vulnerabilities without your permission. I'll stop until you give me permission. If you need any clarification on any of these please let me know.


Hi Rafi, 

First, I want to thank you very much for your time both investigating Coursicle’s security shortcomings as well as the detailed explanations you’ve provided. 

We were aware of most of those vulnerabilities (i.e. that we were not server-side authenticating storeUserClasses.php requests and that a student could XSS themselves and their Facebook friends using the schedule name), but we failed to realize how a student could craft a Samy-like worm that could infect the larger user base. 

I’ve attached discussion on each of the vulnerabilities below, and potential fixes or acceptance as won’t-fix. I would appreciate your comments/pushback. 

1) Authentication bypass
When integrating the Facebook login a couple years ago, I don’t remember them mentioning how we could verify server-side that a client request came from who it said it came from. I know Google’s login integration shows how to do this server-side check in their basic tutorial, though, so perhaps I missed it. Either way, we left it without server-side validation, knowing that a malicious user could potentially screw over all of their Facebook friends since they have easy access to those IDs. We also know that a particularly motivated user could try brute-forcing the user-ID space to screw over everyone. My inclination is to leave this unsecured for now until we get some downtime during which we can work on a fix. 

2) Persistent self-XSS vulnerability
Please see 4) for discussion. 

3) Persistent non social engineered XSS vulnerability
Correct me if I’m wrong, but since "document.cookie" is only going to be able to return the cookies in the domain, won’t it not be possible for an attacker to hijack a user’s session/login as them since they would need the cookies on the domain in order to do so? 

4) Further
This worm-potential is what we’re particularly concerned about, and would like to patch. Here’s our proposed fix, please let me know if you think it’d be sufficient: on the server side, we will run PHP’s strip_tags() on the input to storeUserClasses.php before storing the result in our database. This should make the self-XSS non-persistent and also would make it so an attacker who is carefully crafting storeUserClasses.php requests on his/her friends’ Facebook IDs will not achieve script execution on their computers, thereby limiting the attacker's potential damage to those they are directly connected to on Facebook. 

Regarding a bounty, which you mentioned in your original email: unfortunately as two-person startup we don’t really have the resources to pay out bounties, but we are immensely grateful for your help, so we do want to offer you an (admittedly small) bounty of $100 if you would like. 

Looking forward to your thoughts.


Joe Puccio
Co-founder, Coursicle

On Aug 21, 2017, at 6:14 PM, Raphael Elspas  wrote:

Hi Joe,

I'm glad to hear that you already had security in mind while building this site. Your company will surely grow more quickly the more your users know that they are being protected.

Hijacking a session:
Right now hijacking a session can be done with the following steps. First download a cookie editor extension for your browser like:
Open an incognito browser and navigate to Then login using FB login. Open developer console on the page (Ctrl+Shift+i and go to Colsole tab). Type document.cookie to get cookie. Copy the value of the variable PHPSESSID into your clipboard. Open a tab in the non incognito mode and navigate to Confirm that you are not logged into FB through the login. If you are, logout. Click on the cookie editor extension you just downloaded which should look like a cookie right next to your address bar. Click on PHPSESSID and change the value to clipboard stored PHPSESSID value from the incognito tab when you were logged in. If you have another cookie called fblo_ delete that cookie by clicking on it, and then clicking on the trashcan right next to the value field. Click the check at the bottom of the cookie editor window and refresh the page in your regular browser. Wait for all of the JS on the page to load and you should be logged in.

Preventing a session Hijack:
Not easily preventable if the attacker has the victim's session ID. Try to minimize an attackers access to it. FYI, in the way you have it set up now, a new session ID isn't given to the users browser each time they login or logout, it's generated the first time a user visits a site and never changes.

Here are the 2 fixes I recommend: fix self-XSS and verification of POST request in storeUserClasses.php

Fixing self-XSS:
Sanitizing the inputs can be done in several ways, and strip_tags() is good, but I would recommend htmlspecialchars(). Here's the reason:

Fixing Authentication bypass:
This is just a server side authentication which should not be too difficult to fix. The vulnerability is that the post request to storeUserClasses.php doesn't validate who is sending the request. If you validate it by sending a key with the request (in a hidden field) that only that user would know, such as the session ID,  or another authentication key, then you could authenticate that POST request. For each user id, you could associate a list of all of the sessions they are logged onto. You can put this data in a Database table and update the sessions associated with each ID to verify that any specific UID is paired with the correct session ID every login and logout. You can create a new table or enter it in a table that already exists. For added security you may want to hash your UIDs/Session IDs in the lookup table for a similar reason why passwords are hashed in DBs--in case anyone hacks into your database, they won't be able to identify your users.

Brute Forcing FB Unique IDs/unique user IDs
Luckily, I wouldn't worry about brute forcing these. If an HTTP request takes 50ms and typical IDs look like this: 1814803848534230, then I wouldn't worry about brute forcing. Here's the math: 
If the previously mentioned user was the most recent ID, there could potentially be IDs with any value less than that. Therefore it would take 
1814803848534230*50ms = 7.2e16ms. Let's make that more understandable: 
1814803848534230*50ms/1000ms/60s/60m/24hr/365days = 2877352 years. This is unreasonable to brute force. You should be good.

Thank you for the bounty! I would gladly accept the $100. The best way for you to send me is through my PayPal Account at email
Please don't hesitate to reach out again for any questions.


Hi Rafi, 

Sorry about the late reply on this; we’ve been really busy with everyone starting school up again. 

Hijacking a session:
I followed your directions but wasn’t able to reproduce this. Were you testing this with one Facebook account in incognito and a different Facebook account in non-incognito? I ask because we do some funny stuff with caching information about the logged-in user in local storage (key: "loggedInUser") which may have been left over, causing it to appear as though the session was hijacked (we cache this data in local storage so that each time a user refreshes the page we can display their profile picture and name immediately rather than waiting for the network request to Facebook to come back each time). 

I do want to ensure that sessions aren’t hijackable, so please try this again when you get a chance to see if you can reproduce it (maybe try having the victim be in non-incognito instead of the reverse). Because we’re leaving all of the authentication to the Facebook SDK, I can't imagine any cookie on * would allow for hijacking a * session, but I could be wrong. 

Fixing self-XSS:
Oh man, I really thought this was going to be a simple solution. I ended up starting with strip_tags(), then switching to htmlspecialchars() and finally back to strip_tags(). Here’s what happened: at first, I tried going with strip_tags for simplicity because we store users' classes and saved schedules as JSON in our database, so running htmlspecialchars() in storeUserClasses.php before storing the data would have destroyed the JSON (namely, by escaping the double and single quotes). I tested running strip_tags on the thousands of saved schedules we have on our server to see what would change. There was only one non-malicious user whose schedule name would have been changed (they had a schedule which included a heart "<3", which strip_tags apparently thinks could be harmful). Interestingly, I still couldn’t go with strip_tags, but it didn’t have to do with destroying well-intentioned users' data, it was because strip_tags unintentionally gave more power to malicious users. When using strip_tags, a malicious user could create a schedule with certain opening HTML tags and no corresponding closing tag, and strip_tags would ignore everything after the opening tag, including the very much needed closing JSON quotes and brackets. Thus, if I went with strip_tags, a malicious user could create a schedule with just a specific opening tag, and immediately following this, the code that displays one’s Facebook friends on the course search would break for every single one of their Facebook friends due to the malformed JSON being fed to them from retrieveFriendsClasses.php. 

So, I decided to switch to htmlspecialchars() with the ENT_NOQUOTES flag to preserve quotes, but much to my chagrin, even when our front-end Javascript was fed "harmless" escaped Javascript, it still managed to execute the Javascript. You can test this by entering the following as a schedule name, toggle to a different schedule, and toggle back to the malformed schedule: Fall 2017 <script>alert("hey");</script>

Not sure why this happens (any ideas?), but rather than deal with fixing the front-end, I’ve decided to go with strip_tags() for now, knowing that there is a risk that a malicious user could break the social scheduling aspect of the site for their immediate friends, but sleeping easier knowing that they are no longer able to run arbitrary Javascript on (potentially and with patience) every user’s browser. 

Fixing Authentication bypass:
I’m going to leave this as a won’t-fix for now. Namely because the fix requires a decent bit of work and the potential downsides are low considering, now that the worm is patched, the only way an attacker could destroy a lot of people's classes is by brute forcing the user ID space which, as you have aptly pointed out, is computationally infeasible. 

Brute Forcing FB Unique IDs/unique user IDs
Ah, very good. I should have done the math! 

Could we Venmo you the bounty instead? We already have a company Venmo account setup. Just send me your Venmo username if that works for you.


Joe Puccio
Co-founder, Coursicle

On Sep 12, 2017, at 1:44 PM, Raphael Elspas  wrote:

Hi Joe, 

I apologize for the delay, I've been getting into my school schedule.

I don't know if hijacking a Facebook session is possible, since I don't know if Facebook even uses session IDs, or they may have more than one. Facebook has to protect an enormous amount of data, so they take every measure against attacks like this and obfuscate the process. Hijacking a session on was possible though. and are completely separate sites and have completely separate session IDs. No where are we hijacking an FB session, only hijacking a Coursicle session, which is authorized to access all of the friends that person has on FB. We were using validly authorized request to FB to get friends' user IDs (by themselves have no use), and use them to access a user account on Coursicle (because of the authentication bypass), and change/delete user data since Coursicle uses the user ID from FB to identify accounts. Solving XSS holes are the best way to fix Session Hijacking attacks.

For the XSS, maybe you can use both strip_tags() and htmlspecialchars()? I don't know how your exact backend works so I can't stay for sure.

Although the authentication problem is the most time consuming to fix, it is the most important to fix. To clarify:
1) Fixing authentication bypass and allowing XSS -> only Self XSS is allowed. No damage can be done from one account to another.
2) Allowing authentication bypass and fixing XSS -> Persistent XSS will not be allowed, but because authentication bypass allows POST requests to change other users' data, you can still change/delete other friends data. The worm attack will still be stopped though.

You can Venmo me at @Raphael-Elspas


From: Joe Puccio 
Subject: Re: Persistent XSS Vulnerability
Date: September 12, 2017 at 9:12:13 PM EDT
To: Raphael Elspas 

Hi Rafi, 

No worries about the delay. 

Okay, regarding this session hijacking, I am a bit confused and really want to make sure I understand. 

Discussion on PHPSESSID
In an earlier email you said an attacker could take the PHPSESSID cookie of a logged in user (from *, paste it into the cookies of an incognito browser, refresh the page, and be logged in as that user. I don’t understand how this could be the case, because Coursicle relies entirely on the Facebook SDK for login (which runs exclusively client-side). That means the login code on Coursicle is only interacting with, so a PHPSESSID cookie on should do nothing to log a user into Coursicle. Could you explain exactly how the PHPSESSID session hijacking you did works or try reproducing it again to confirm it exists? 

Discussion on potential effects of session hijacking
You said that "[a Coursicle session] is authorized to access all of the friends that person has on FB". I don’t see how this is the case (unless our definitions are different, see next paragraph). The list of your friends’ user IDs is retrieved from (using the SDK) and is exclusively stored on Each time you login to Facebook on Coursicle, we make a Facebook graph call to /me/friends (see socialSchedulingAndLogin.js) which returns a list of the unique IDs of all your friends on Coursicle, and that list is stored in Javascript. Then a call is made to retrieveFriendsClasses.php which passes those IDs as arguments and retrieves the classes for each of those users. In the interest of privacy, we don’t store any connections between users on our server (i.e. who is friends with whom). Thus, even a successful hijack of a server session (via stealing their PHPSESSID) would not grant an attacker access to the victim’s friends’ user IDs. 

Alternate meaning of "a Coursicle session"
Perhaps when you say "a Coursicle session", you mean "a user who has logged in with Facebook on" and not what I assumed you meant which is "the PHP server session on". Is this the case? That could be at the root of this miscommunication. However, still, because we’re using the Facebook SDK for Facebook login on Coursicle, I’m confident that the only way an attacker could hijack "a Coursicle session" under this definition would be to hijack a user's session because is solely responsible for declaring a user "logged in" on Coursicle (and, to repeat, is also solely responsible for granting a browser access to a user's Facebook friends’ user IDs).

htmlspecialchars() and strip_tags()
That’s a good thought, but if I did htmlspecialchars() first then strip_tags() on the result, strip_tags() isn’t going to remove any tags (because it won’t recognize the escaped tags as something it needs to strip), thus this is only as good as running htmlspecialchars() alone. And if I did strip_tags() first and then htmlspecialchars() on the result, I achieve the same issue as just running strip_tags(). That is, the JSON will be destroyed when strip_tags() tries to handle certain maliciously formed schedule names that contain only opening tags without a closing tag, and of course running htmlspecialchars() will have no effect since the tags have been stripped and the JSON already destroyed. 

Authentication of POST requests
I agree it’s important to add authentication to those POST requests, it’s just lower on our priorities than the worm because the vulnerability only allows an attacker to (within computational reason) mess with their Facebook friends’ Coursicle data rather than the Coursicle user-base at large.

Great, we’ll Venmo you the bounty soon.


Joe Puccio
Co-founder, Coursicle