How a Blackstone real estate company exposed Social Security digits, dates of birth, addresses, and other applicant data through a GraphQL authorization flaw

Update: This post received some attention on Hacker News — see the discussion thread.

Beam Living sign-up and sign-in screen

Finding housing in NYC is hard. Everyone knows that. But what not everyone knows is that it is easier to find the last four digits of someone’s Social Security number than an apartment…

I was applying for a lease on Beam Living, a Blackstone portfolio company. I went through the normal flows, but (as a security-conscious individual) I always have my network tab open as I browse the web to make sure I am not putting my sensitive information into a website that a script kiddie (or GLM-5.2) could easily break into.

As I was submitting my Social Security number, I figured I should check out the GraphQL (rip, used to be the hot thing) queries that were processing it.

The GraphQL query

At first, I didn’t see anything of note. But when I went to my main profile, I saw a call to pd-dlcore.beamliving.com/graphql with the payload:

Redacted Beam Living GraphQL contact request shown in Chrome DevTools
{
  "operationName": "contact",
  "variables": {
    "contactId": "myemail@email.com"
  }
}

View the full GraphQL query

query contact($contactId: String!) {
  contact(contactId: $contactId) {
    ...contactInfo
    __typename
  }
}

fragment contactInfo on ContactDtoModel {
  incomeVerificationMethod
  incomeCheckStatus
  incomeCheckReferenceId
  creditScore
  address
  applicationStatus
  city
  companyOrSchool
  consentDate
  consentIp
  country
  dateOfBirth
  emailAddress
  emergencyContact {
    emergencyContactEmail
    emergencyContactName
    emergencyContactPhone
    emergencyContactRelationship
    __typename
  }
  firstName
  id
  identityVerificationSubmittedDate
  incomeCheckDate
  isCurrentLeasee
  jobTitle
  lastName
  noSsn
  occupation
  pets {
    birthDay
    breed
    id
    isServiceDog
    licenseNumber
    name
    rabiesExpirationDate
    weight
    __typename
  }
  postalCode
  preferredName
  screeningFeePaid
  ssnInfo
  state
  status {
    ...applicationStatus
    __typename
  }
  telephone
  __typename
}

fragment applicationStatus on StatusDtoModel {
  applicationProgress {
    basicInfo {
      actionRequired
      __typename
    }
    dogsInformation {
      actionRequired
      __typename
    }
    emergencyContact {
      actionRequired
      __typename
    }
    identityVerification {
      actionRequired
      __typename
    }
    incomeVerification {
      actionRequired
      __typename
    }
    payments {
      actionRequired
      __typename
    }
    review {
      actionRequired
      __typename
    }
    __typename
  }
  progressDetails {
    applicationForm {
      description
      status
      __typename
    }
    firstMonthRent {
      description
      status
      __typename
    }
    identityVerification {
      description
      identityVerificationCompletedDate
      identityVerificationStatus
      identityVerificationSubmittedDate
      status
      __typename
    }
    incomeVerification {
      description
      status
      __typename
    }
    leaseGuarantee {
      description
      status
      __typename
    }
    leaseSigning {
      description
      signatureId
      status
      __typename
    }
    managerReview {
      managerReview
      managerReviewDecisionCode
      status
      __typename
    }
    securityDeposit {
      description
      paymentLink
      securityReplacement
      __typename
    }
    backgroundScreeningCheck {
      status
      __typename
    }
    creditCheck {
      status
      __typename
    }
    __typename
  }
  guestCardCreated
  unitReservationFailed
  __typename
}

While not necessarily bad, supplying a user’s email into a GraphQL query like this, as opposed to just deriving it from the session cookie, is always a smell.

The obvious test

So I did the obvious thing – I took a friend’s email (I knew he had also used the service) and… boom. The last four digits of his Social Security number, date of birth, home address, IP address, phone number, etc., etc.

Redacted Beam Living GraphQL response showing exposed SSN and applicant fields

The impact was not limited to my application or my building. Beam Living used this leasing portal across its communities:

  • 8 Spruce
  • StuyTown
  • Peter Cooper Village
  • Kips Bay Court
  • Parker Towers

Anyone who had applied through that shared portal—and whose record remained in the system— had their Social Security number information, date of birth, home address, IP address, phone number, and other application data accessible to any one who knew their email address.

Disclosure and the silent patch

I immediately stopped testing and disclosed it to Beam Living. The disclosure process wasn’t ideal – I had to send many emails and eventually got on a phone call with someone from the Beam Living team. They said they had checked and there was no issue at all. I went back to try the exploit again and it had been silently patched. So I am glad that the issue is fixed, but this is not how companies (especially ones owned by giants like Blackstone) should handle disclosure…

The disclosure timeline went like this:

  • June 14: I emailed privacy@beamliving.com to report a serious vulnerability exposing applicants’ and guarantors’ PII—including the last four digits of SSNs, credit scores, dates of birth, addresses, and phone numbers. I asked how to disclose this vulnerability responsibly.
  • June 16: After receiving no reply, I followed up, emphasized the severity, and again asked Beam Living to confirm the correct disclosure channel or connect me with its security team. I received no response.
  • June 23: I told my Beam Living leasing agent that I had found a serious vulnerability exposing SSN digits, dates of birth, phone numbers, addresses, and other applicant data through an email address. I asked to be connected with the right team for responsible disclosure.
  • June 24: I followed up and warned that the vulnerability was still live. The leasing agent said my report had been forwarded to a team for investigation.
  • June 26–July 8: I continued trying to reach someone. On July 1 and again on July 8, I explicitly warned that my data—and other users’ data—was still exposed.
  • July 9: After a phone conversation, Beam Living’s Resident Experience team asked me to send details so they could pass them to the Technology team. I retested immediately afterward and found that the issue had finally been patched. I offered to coordinate disclosure and said I planned to publish after giving them time to respond.
  • After July 9: I connected with Beam Living’s head of Operations, who was very nice, and told her that I intended to disclose the vulnerability publicly.

Oh well! I alerted them that I was going to post this blog, and I hope I don’t get my lease canceled…

添加评论
点赞收藏
点踩分享查看原文
评论
?
参与讨论