Bug Blog: “LastPosted” reveals non-public data about users, exploited to gain advantages

In Brief:

The “Last Posted” field on the user profile updates when a user sends a private message or posts in a private subforum. By comparing this field to the timestamp of the latest post in a user’s activity feed, the public gains access to private information which they should not have access to. We can make this comparison programmatically with API calls and automation to collect data about when and how often people are PMing. This is very bad.

Proposed Solution

LastPosted should not be updated when a user sends a private message.

The Exploit

  1. Open a user’s public profile. Make note of the field Last Posted.
  2. Open the same user’s activity feed. Note the timestamp of the latest post.
  3. If the two are different (LastPosted within a couple minutes, activity feed shows latest post hours ago), you have gained access to information (the existence of a private message) which you should not have access to.

Developing the Exploit: API Calls and Automation

On the profile, this information is rounded and it is sometimes hard to draw precise conclusions. However, by using the public Discourse API, we can grab the real timestamps and compare them for exact information about when and how often a user is PMing.

We will use two different API routes for this.

  1. At https://meta.discourse.org/u/Wintermute.json, find user.last_posted_at.
  2. At https://meta.discourse.org/user_actions.json?username=Wintermute, find user_actions.created_at of the most recent post (disclaimer: some additional work here to filter out likes, replies to the user etc. We will omit it for brevity).

Provided we have an API key, we can make these calls programmatically and automate them.

I have a working setup which runs a CloudWatch rule every 2 minutes to execute a Lambda function. It makes these calls and then writes to a DynamoDB table if it finds the timestamps are different. I can then scan the dynamo table to produce data on when and how often people are PMing.

I can share this code if there’s interest.

Why? What's the Impact?

I belong to a community that uses a Discourse website to play Werewolf, a common online forum and party game. If you’ve ever played Town Of Salem, Trouble in Terrorist Town, MafiaUniverse, EpicMafia, or played any of the various live-action spinoffs at a Summer Camp with your friends, the rules should be familiar.

  1. There’s a group of people who are “the Town.”
  2. A second group hidden within the town are “Wolves.”
  3. Game rotates through “Day” and “Night” phases.
  4. Town tries to find the wolves during the day and kick them out.
  5. Wolves eat one town per night until one group is eliminated.
  6. The wolves typically have a private chat to plan their moves and make decisions at night.
  7. There can optionally be a bunch of other characters and roles which may also periodically PM the host for special actions or information about the game.

Until recently we were using the Discourse private messaging feature to facilitate these discussions. Since the discovery of this bug we’ve moved those chats to off-site third-party chat clients, but we’d like to move them back for future games. We’re hoping you all would agree that this is not desired behavior for the “LastPosted” stat and that this bug might be a quick & easy fix :slight_smile: Let me know if you have any questions.

Thanks for reading and have a nice Thanksgiving.

2 Likes

That’s a cool use for Discourse that I’ve never considered!

A lot of communities use personal messages and private subforums a lot, and so updating last_posted_at would be expected behaviour, and wouldn’t really be considered an exploit.

In your very specific case, you could make use of the new “Hide my public profile and presence features” setting. If users enable that, then their “last_posted_at” date will be hidden from other users. You could disable the setting again as soon as the werewolves have been revealed :wolf:

You’ll find that setting under the “interface” section of your user preferences.

5 Likes

Good suggestion which we had actually considered; Unfortunately, doing so disables a couple other site features, including the ability to “ISO” a user (look at their posts in isolation, which discourse enables with the nifty little button pictured below).

This is an important tool in the investigative aspect of our game, so we cannot disable it for the time being.

Additionally, we like the profile features discourse provides and wouldn’t want our users to have to hide them :smiley:

Should I take your post to mean that you guys prefer to update last_posted_at after a private message? Is there perhaps a site change we could make on our end that would disable that field? I know we can write CSS that hides that item on the user’s profile but I’d imagine the API data would still be an issue.

Yes we prefer the default to be “last time you posted anything to anyone in the forum”, we don’t think it should imply “last time you posted something in a public category visible to all people on the forum”

I worry changing this will cause wide spread confusion, I do not really want to touch this in core. Private categories mean that going down this rats nest means we will need different visible last_posted_at per user, this is just way too complex for almost no gain.

To amend this you are going to need a plugin that strategically disables:

https://github.com/discourse/discourse/blob/c10bc4012a1205e82b13db5501df68dee76d1bcc/lib/post_creator.rb#L508-L508

I guess a plugin with this source will do the trick for you

require_dependency 'post_creator'
module MyHack
   def update_user_counts
      super
      @user.update_attributes(last_posted_at: Time.new('2000-01-01'))
   end
end

class PostCreator
   prepend MyHack
end
5 Likes