Martin Messer
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # MobSec report Lovoo dating app ## Description Lovoo is a popular dating app especially in Austria and Germany. It is geo location based and has the typical matching functions, people near you and also a streaming function. Source: https://about.lovoo.com/ ## Structure of report 1. Description 2. Registration process 3. Login process 6. Obtaining other users data 7. Direct profile access 8. Investigating premium functions 9. Liking Process 10. Tracking 11. Conclusion ## Registration process In the registration process of this app couldn't anything interesting be found. ## Login process The first thing the app does after starting up is to load the users data with a initial get request ``` GET /init HTTP/2 Host: api.lovoo.com Cache-Control: no-cache Accept: application/json Kissapi-App-Version: 12300 Kissapi-Version: 1.36.0 Kissapi-Device-Os: 28 Kissapi-Device-Model: AOSP on IA Emulator Tz: Europe/Vienna Kissapi-Device: android Kissapi-App-Package-Id: net.lovoo.android Kissapi-Android-Fingerprint: google/sdk_gphone_x86_arm/generic_x86_arm:9/PSR1.180720.117/5875966:user/release-keys Kissapi-App: lovoo Kissapi-Apptype: google User-Agent: LOVOO/12300 Dalvik/2.1.0 (Linux; U; Android 9; AOSP on IA Emulator Build/PSR1.180720.117) 4.9.1 Kissapi-Android-Id: 7eaa55c7260aeb8f Kissapi-App-Id: 682f302f-e535-4ca9-889c-d462ffe2b58b?identifier=lovoo.prod.firebase Kissapi-Gpsa-Id: 682f302f-e535-4ca9-889c-d462ffe2b58b?identifier=lovoo.prod.firebase Kissapi-Gpsa-On: true Accept-Language: en-US Wifi: 1 Kissapi-Mac: e3f5536a141811db40efd6400f1d0a4e Kissapi-Ad-Id: f8738acc8d7372f0dd5af125bfae4c9e Kissapi-Ad-Name: Organic Authorization: Bearer {Bearer Token} Accept-Encoding: gzip, deflate ``` The response has a lot of data, but the first part looks pretty interesting ```=json { "response":{ "user":{ "_type":"user", "id":"6266cd58b5e1341bb476f697", "name":"JeffBayzoss", "gender":1, "age":23, "lastOnlineTime":1654364115, "whazzup":"", "freetext":"I enjoy pina coladas, getting caught in the rain, and making love at midnight.", "subscriptions":[ ], "isVip":0, "flirtInterests":[ "frie", "chat" ], "options":{ "profileShareable":1 }, "compatibilityQuestionsAnswered":0, "counts":{ "v":45, "l":1, "pp":83, "p":1, "m":1, "ib":0 }, "credits":6, "locations":{ "home":{ "city":"Vienna", "country":"AT", "lat":48.18633, "lon":16.323064 }, "current":{ "city":"Graz", "country":"AT", "lat":47.0707133, "lon":15.4395033 }, "billing":{ "country":"US" } }, "isNew":0, "isOnline":1, "isMobile":1, "isHighlighted":0, "picture":"62767fb02a175046ef7c7350", "images":[ { "url":"https:\/\/img.lovoo.com\/users\/pictures\/62767fb02a175046ef7c7350\/image.jpg", "width":365, "height":481 }, { "url":"https:\/\/img.lovoo.com\/users\/pictures\/62767fb02a175046ef7c7350\/image_l.jpg", "width":365, "height":481 }, { "url":"https:\/\/img.lovoo.com\/users\/pictures\/62767fb02a175046ef7c7350\/image_m.jpg", "width":365, "height":481 }, { "url":"https:\/\/img.lovoo.com\/users\/pictures\/62767fb02a175046ef7c7350\/image_s.jpg", "width":243, "height":320 }, { "url":"https:\/\/img.lovoo.com\/users\/pictures\/62767fb02a175046ef7c7350\/thumb_xl.jpg", "width":320, "height":320 }, { "url":"https:\/\/img.lovoo.com\/users\/pictures\/62767fb02a175046ef7c7350\/thumb_l.jpg", "width":160, "height":160 }, { "url":"https:\/\/img.lovoo.com\/users\/pictures\/62767fb02a175046ef7c7350\/thumb_m.jpg", "width":120, "height":120 }, { "url":"https:\/\/img.lovoo.com\/users\/pictures\/62767fb02a175046ef7c7350\/thumb_s.jpg", "width":80, "height":80 } ], "email":"jeff.bayzoss@gmail.com", "birthday":"30.08.1998", "isConfirmed":0, "registerDate":1650904408, "apps":[ "lovoo" ], "interests":[ "frie", "chat" ], "genderLooking":2, "languages":[ "en", "de" ], "promoconsent":0, "settings":{ "screensaver":0, "share_profile_disabled":0, "radar_disabled":0, "match_disabled":0, "ads_gps_disabled":0, "ads_personalized_disabled":0, "push_intensity":"all", "gps":1, "sounds":1, "push":1, "push_msg":1, "push_visit":1, "push_match":1, "push_info":1, "push_comments":1, "push_likes":1, "push_follow":1, "push_match_vote":1, "push_reply":1, "message_preview":1, "push_video_nearby":1, "push_video_favorite":1, "push_video_favorite_blast":1, "push_best_picks":1 }, "searchSettings":{ "ageFrom":18, "ageTo":25, "radius":500, "matchRadius":0, "gender":2 }, "app":"12601", "verifications":{ "facebook":0, "verified":0, "confirmed":0 }, "verificationStatus":0, "resources":{ "icebreaker":{ "slotsLeft":3, "slotLimit":3, "refillInterval":86400, "refillTime":0, "slotsExtra":0, "slotsNotify":0 }, "boost":0, "boost_refill_time":0 } } } } ``` It looks like the app has saved the "home" location and tracks the current location. In the last part of the response is a tag "homeLocation":1. ## Obtaining other users data When going to the match section the app sends a request to load in user charts for voting. The request contains a filter like gender, age, but also the current location. ``` GET /matches?gender=2&ageFrom=18&ageTo=25&lat=47.0707133&long=15.4395033&limit=30 HTTP/2 Host: api.lovoo.com ``` The API then responds with users, who match the filter and sends their data. ```=json { "response":{ "result":[ { "_type":"user", "id":"514bf0cb150ba0e941000331", "name":"Victoria", "gender":2, "age":24, "lastOnlineTime":1654224749, "whazzup":"Bundesheer? Bitte, danke", "subscriptions":[ ], "isVip":0, "flirtInterests":[ "date", "frie" ], "options":{ "profileShareable":1 }, "counts":{ "p":15, "m":10 }, "possibleMatch":0, "locations":{ "home":{ "city":"Graz", "country":"AT", "distance":2.7 }, "current":{ "city":"Graz", "country":"AT", "distance":1.2 }, "billing":{ "country":"AT" } }, "isNew":0, "isOnline":0, "isMobile":1, "isHighlighted":0, "picture":"62250c4c515cd339521aa842", "images":[ { "url":"https:\/\/img.lovoo.com\/users\/pictures\/62250c4c515cd339521aa842\/image.jpg", "width":960, "height":1280 }, { "url":"https:\/\/img.lovoo.com\/users\/pictures\/62250c4c515cd339521aa842\/image_l.jpg", "width":720, "height":960 }, { "url":"https:\/\/img.lovoo.com\/users\/pictures\/62250c4c515cd339521aa842\/image_m.jpg", "width":480, "height":640 }, { "url":"https:\/\/img.lovoo.com\/users\/pictures\/62250c4c515cd339521aa842\/image_s.jpg", "width":240, "height":320 }, { "url":"https:\/\/img.lovoo.com\/users\/pictures\/62250c4c515cd339521aa842\/thumb_xl.jpg", "width":320, "height":320 }, { "url":"https:\/\/img.lovoo.com\/users\/pictures\/62250c4c515cd339521aa842\/thumb_l.jpg", "width":160, "height":160 }, { "url":"https:\/\/img.lovoo.com\/users\/pictures\/62250c4c515cd339521aa842\/thumb_m.jpg", "width":120, "height":120 }, { "url":"https:\/\/img.lovoo.com\/users\/pictures\/62250c4c515cd339521aa842\/thumb_s.jpg", "width":80, "height":80 } ], "verifications":{ "facebook":1, "verified":1, "confirmed":1 }, "verificationStatus":1 } ... ``` This response contains some interesting data that could be used malicously. Besides the userid and a possibleMatch prediction we get the last online time "1654224749". Converting that from UNIX Timestamp with units in seconds, we get "Fri 3 June 2022 02:52:29 UTC". The API sends the home and current location with the actual distance to the suggested user: ```=json "locations":{ "home":{ "city":"Graz", "country":"AT", "distance":2.7 }, "current":{ "city":"Graz", "country":"AT", "distance":1.2 }, "billing":{ "country":"AT" } } ``` Additionally the response contains tags like isNew, isOnline, isMobile, verification with facebook and the link to the picture. At the end of the response it shows us the remaining votes count ``` "allCount":30,"votesLeft":30,"timeToNewVotes":1 ``` Thereafter the app sends a POST request containing all the user ids it has loaded with the request before. ``` POST /users/6266cd58b5e1341bb476f697/connections_multi HTTP/2 Host: api.lovoo.com ``` The response shows the relationship to these users ```=json "5f1ba612d1e9903275732b32":{ "isMatch":0, "hasLiked":0, "icebreakerState":0, "hasContact":0 } ``` ## Direct profile access When clicking on a profile of a other user the app makes a few requests: It makes a POST request with the id it wants to load ``` POST /users/6266cd58b5e1341bb476f697/connections_multi HTTP/2 Host: api.lovoo.com ... ids=5ffdfaedd31a33034b422abd ``` Then loads some facts about the user where the app gets some data. ``` GET /users/5ffdfaedd31a33034b422abd/facts HTTP/2 Host: api.lovoo.com ``` In this data is the actual search setting age range of the user looking at, interests and data like height, home location, ... ```=json { "response":{ "gender_looking":"1", "searchsettings_age_from":19, "searchsettings_age_to":25, "locationhome_city":"Sankt P\u00f6lten", "locationhome_country":"AT", "locationhome_lat":0, "locationhome_long":0, "interests":[ "chat" ], "whazzup":"", "languages":[ "de", "en" ], "match_disabled":0, "interview_size":"s23", "interview_size_cm":162 }, ... } ... ``` It also loads in more data with the request ``` GET /users/5ffdfaedd31a33034b422abd/feed?resultLimit=40&sort=distance HTTP/2 Host: api.lovoo.com ... ``` But the data in the response looks similar to that returned in the match request. With this request the app gets again the pictures and other basic facts (somehow it's an array with 7 times the same struct in it). The app again loads data with the following two requests: ``` GET /users/5ffdfaedd31a33034b422abd/freetext HTTP/2 Host: api.lovoo.com ... ``` ``` GET /prompts/5ffdfaedd31a33034b422abd HTTP/2 Host: api.lovoo.com ... ``` These just load the texts and question - answer prompts of the user. Finally the app makes a POST request to the visit endpoint, to show the other user, that I clicked on the profile and where I did that. ``` POST /users/5ffdfaedd31a33034b422abd/visits HTTP/2 Host: api.lovoo.com ... ref=match ``` This app links photos of the user staticly, but has uses instead of the user id a unrelated picture id ```=json "id":"628e8f3ce42e4d62eb00ad6e" "url":"https:\/\/img.lovoo.com\/users\/pictures\/628e8f40a5457d5e181ff973\/image.jpg" ``` ## Investigating premium functions On the app this can be watched in the "News" section. The feature only shows the profiles unblurred with an upgraded account. Looking at the response of the load request in this section showed that without an upgraded account, the app gets encrypted user ids, blurred images but still the home and current location (without the exact distance). ```=json { ... "_type":"visit", "id":"CRYPKDc0t9vW+EPsWHs70PSaNhcLpSTPJbUhBeAFBIro4w=", "user":{ "_type":"user", "id":"CRYPKDc0t9vW+EPsWHs70PSaNhcLpSTPJbUhBeAFBIro4w=", "name":"Ceci", "gender":2, "age":20, "lastOnlineTime":1654511814, "whazzup":"", "freetext":"Ich mag Pizza, Sushi und Harry Potter. \n\nWenn du ein Hund oder eine Katze hast, oder T\u00e4towierer bist, hast du schon gewonnen!", "subscriptions":[ ], "isVip":0, "flirtInterests":[ "frie" ], "options":{ "profileShareable":1 }, "counts":{ "p":1, "m":4 }, "isLocked":1, "locations":{ "home":{ "city":"Kapfenberg", "country":"AT" }, "current":{ "city":"Deuchendorf", "country":"AT" }, "billing":{ "country":"AT" } }, "isNew":0, "isOnline":0, "isMobile":1, "isHighlighted":0, "picture":"28912190f25ee452c9baa29819d8be3d", "images":[ { "url":"https:\/\/img.lovoo.com\/users\/pictures\/28912190f25ee452c9baa29819d8be3d\/thumb_xl.jpg", "width":320, "height":320 }, { "url":"https:\/\/img.lovoo.com\/users\/pictures\/28912190f25ee452c9baa29819d8be3d\/thumb_l.jpg", "width":160, "height":160 }, { "url":"https:\/\/img.lovoo.com\/users\/pictures\/28912190f25ee452c9baa29819d8be3d\/thumb_m.jpg", "width":120, "height":120 }, { "url":"https:\/\/img.lovoo.com\/users\/pictures\/28912190f25ee452c9baa29819d8be3d\/thumb_s.jpg", "width":80, "height":80 } ], "verifications":{ "facebook":0, "verified":0, "confirmed":1 }, "verificationStatus":0 }, "unlocked":0, "time":1654501242 }, ...} ``` In this data are "isLocked" and "unlocked" tags. Intercepting the response and changing these lets skipping the upgrading and tries to load the profile. But the user ids are still encrypted and therefore the load requests fail with a unknown user error message. Investigating the "likes you" section reveals the same issue. ## Liking process The match request to vote contains the target user id. Trying to change the values to get some interesting results didn't work. ``` POST /matches/629cedf7e6166b0f82556935 HTTP/2 Host: api.lovoo.com ... vote=1&ref=direct%2Cmatch&dwell_time=1959 ``` So the liking process on this app seems to be straight forward. ## Tracking To track user activity for reasons like marketing, the app sends following requets with encrypted data to the so called "AppLovin" service. ``` POST /1.0/event/load?id=99ed804848a284ccc2c5ed1c1036cc7c5f90432c&load_time_ms=223&postback_ts=1654701969116 HTTP/2 Host: prod-ms.applovin.com ``` ``` POST /1.0/event/load?id=c0f8b73a85d500b3ee038b0e5884f24eb30d7d71&load_time_ms=60&postback_ts=1654701992235 HTTP/2 Host: stage-ms.applovin.com ``` ``` POST /1.0/mediate?p=1%3A96943fdea087a1d759e08734fce0068af23ef96e%3AWGUTRJff5vD55Paf-9cSLJ84TV6pnUas3JefM3thUQJ6QpVbKwF4Ly%3AMMPK3XoTuUjbcwDnQ9MFGeYLFPqCWyE7FrXUHjD-J4KOB8wezjR0WZIIrA-B6LRMSaskbRjPWiDXrtaGbCyT_g** HTTP/2 Host: ms4.applovin.com ``` AppLovin is a marketing and advertising service which helps to analyze user behaviour. Source: https://en.wikipedia.org/wiki/AppLovin ## Conclusion Lovoo gives away some information about their users. Looking at the data the app loads from a user, we know their location, if the user is online at the moment, the last time online, their search settings and so on. Around 6 years ago a user created a tool to calculate users location with requests to the API. This could easily be adapted and used for malicious reasons with state of the communication in this point of time. Source: https://github.com/posixpascal/lovoo-data-breach # MobSec report OKCupid dating app ## Description OK Cupid is also a very popular dating app. It has the typical matches, where you can like or dislike suggested users. But also tabs where the app suggests you users with the most "match percentages", recommendations, users who like you and "Cupid's picks". But like all dating apps, a lot of functions are behind a paywall: daily like cap, the users who like you are made unrecognizable by the app until you pay, popularity boost if premium user. Source: https://en.wikipedia.org/wiki/OkCupid **Device used:** Android emulator Pixel 4 Android 9 API level 28 ## Structure of report 1. Description 2. Registration process 3. Login process 6. Direct profile access and obtaining other users data 9. Liking Process 10. Tracking 11. Conclusion 12. Future work ## Registration process This app requires for a full registration a phone number. But we discovered that the app doesn't relly check if the number is valid. It tries to send a verification code to the entered number. After that the user needs to enter the code and if vaild, can log in. However, if we enter a invalid phone number, invalid code, intercept and modify the responses to look like a valid verfication, we can log in without any number. We can enter just any number: ``` POST /graphql/SendPhoneNumber HTTP/2 Host: okcupid.com Cookie: override_session=0; secure_login=1; secure_check=1; guest=7499627801677657669; session=7499627801677657669%3A16707096719338247505; authlink=158b116f; _fbp=fb.1.1652979626229.695918336; nano=k%3Diframe_prefix_lock_3%2Ce%3D1653930142760%2Cv%3D1%7Ck%3Diframe_prefix_lock_2%2Ce%3D1653650365181%2Cv%3D1%7Ck%3Diframe_prefix_lock_1%2Ce%3D1652979653689%2Cv%3D1%7Ck%3Diframe_prefix_lock_-1%2Ce%3D1653930218437%2Cv%3D1 Accept: application/json X-Apollo-Operation-Id: e8d883f7395f6212398be1a07497ce5f9d72bf05ffda5f90bf523214fb5e6065 X-Apollo-Operation-Name: SendPhoneNumber X-Okcupid-Device-Id: Android; AOSP on IA Emulator; 9; PSR1.180720.117; Unavailable; fwciLTsdTECsUopudsdVN0; X-Okcupid-App: OkCupid Android App 64.1.0 X-Okcupid-Platform: Android X-Okcupid-Version: 64.1.0 X-Okcupid-Locale: en X-Okcupid-Emulator: true User-Agent: Android 64.1.0 X-Emb-Path: /graphql/SendPhoneNumber Content-Type: application/json; charset=utf-8 Content-Length: 442 Accept-Encoding: gzip, deflate { "operationName":"SendPhoneNumber", "variables":{ "input":{ "tspAccessToken":"eyJhbGciOiJIUzI1NiJ9.eyJpZCI6Ijc2Njg0NTc3MyIsImV4cCI6MTY1NDMzNjEwMSwidW5pcXVlbmVzc19pZCI6IkdIR01qY1NRc05SeCJ9.qe-khFh1iLNxKyeaP_lumP0lER2ro8FEN6_Pn28-G1w", "phoneNumber":"+4386637206742", "platform":"android", "smsFlow":"PHONE_NUMBER_LINKING" } }, "query":"mutation SendPhoneNumber($input:AuthOTPSendInput!) { authOTPSend(input: $input) { __typename success statusCode } }" } ``` And get following response, where we need to set success to true and statuscode to 0. ``` HTTP/2 200 OK Date: Sat, 04 Jun 2022 09:24:32 GMT Content-Type: application/json; charset=utf-8 X-Powered-By: Express Vary: Origin, Accept-Encoding Vary: Accept-Encoding Access-Control-Allow-Credentials: true Etag: W/"6c-aYySrsNvT+jV1x6eiVIsKYgWWMU" Cf-Cache-Status: DYNAMIC Expect-Ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct" Strict-Transport-Security: max-age=15552000; includeSubDomains; preload X-Content-Type-Options: nosniff Server: cloudflare Cf-Ray: 715f9db0f88038c0-VIE { "data":{ "authOTPSend":{ "__typename":"AuthOTPSendPayload", "success":true, "statusCode":0 } }, "extensions":{ } } ``` Now we are able to enter the verification code, where the app sends the code via a request. ``` POST /graphql/SendVerificationCode HTTP/2 Host: okcupid.com Cookie: override_session=0; secure_login=1; secure_check=1; guest=7499627801677657669; session=7499627801677657669%3A16707096719338247505; authlink=158b116f; _fbp=fb.1.1652979626229.695918336; nano=k%3Diframe_prefix_lock_3%2Ce%3D1653930142760%2Cv%3D1%7Ck%3Diframe_prefix_lock_2%2Ce%3D1653650365181%2Cv%3D1%7Ck%3Diframe_prefix_lock_1%2Ce%3D1652979653689%2Cv%3D1%7Ck%3Diframe_prefix_lock_-1%2Ce%3D1653930218437%2Cv%3D1 Accept: application/json X-Apollo-Operation-Id: b83a704ae9c70899f8363708cff63a3fd2ee8fe6e1472ae1dadfaf822719914f X-Apollo-Operation-Name: SendVerificationCode X-Okcupid-Device-Id: Android; AOSP on IA Emulator; 9; PSR1.180720.117; Unavailable; fwciLTsdTECsUopudsdVN0; X-Okcupid-App: OkCupid Android App 64.1.0 X-Okcupid-Platform: Android X-Okcupid-Version: 64.1.0 X-Okcupid-Locale: en X-Okcupid-Emulator: true User-Agent: Android 64.1.0 X-Emb-Path: /graphql/SendVerificationCode Content-Type: application/json; charset=utf-8 Content-Length: 439 Accept-Encoding: gzip, deflate { "operationName":"SendVerificationCode", "variables":{ "input":{ "tspAccessToken":"eyJhbGciOiJIUzI1NiJ9.eyJpZCI6Ijc2Njg0NTc3MyIsImV4cCI6MTY1NDMzNjU5OSwidW5pcXVlbmVzc19pZCI6Ik9aTkVUR0VqQTR3biJ9.lBdwohptxBNDtiwMOEMITHIKZzx_p7knj3KeIAXi3Sk", "phoneNumber":"+4386637206742", "otp":"111111" } }, "query":"mutation SendVerificationCode($input:AuthTSPRefreshTokenCreateInput!) { authTSPRefreshTokenCreate(input: $input) { __typename tspRefreshToken } }" } ``` The response we get shows that the code entered was invalid. ``` HTTP/2 200 OK Date: Sat, 04 Jun 2022 09:32:25 GMT Content-Type: application/json; charset=utf-8 X-Powered-By: Express Vary: Origin, Accept-Encoding Vary: Accept-Encoding Access-Control-Allow-Credentials: true Etag: W/"20b-apZNTJoeVwK6QYZENYGytWrrtD8" Cf-Cache-Status: DYNAMIC Expect-Ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct" Strict-Transport-Security: max-age=15552000; includeSubDomains; preload X-Content-Type-Options: nosniff Server: cloudflare Cf-Ray: 715fa9411de6cbc8-VIE { "errors":[ { "message":"16 UNAUTHENTICATED: Tinder Status Code 992401 Invalid OTP attempt", "locations":[ { "line":1, "column":73 } ], "path":[ "authTSPRefreshTokenCreate" ], "extensions":{ "code":16, "exception":{ "stacktrace":[ "Error: 16 UNAUTHENTICATED: Tinder Status Code 992401 Invalid OTP attempt", " at /usr/src/app/dist/auth/dataSources/sms.js:105:13", " at runMicrotasks (<anonymous>)", " at processTicksAndRejections (internal/process/task_queues.js:95:5)" ] } } } ], "data":{ "authTSPRefreshTokenCreate":null }, "extensions":{ } } ``` So we need to change the response, that it looks like the verification was successful. Changing it to following response should do the job: ``` HTTP/2 200 OK Date: Sat, 04 Jun 2022 09:28:42 GMT Content-Type: application/json; charset=utf-8 X-Powered-By: Express Vary: Origin, Accept-Encoding Vary: Accept-Encoding Access-Control-Allow-Credentials: true Etag: W/"20b-apZNTJoeVwK6QYZENYGytWrrtD8" Cf-Cache-Status: DYNAMIC Expect-Ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct" Strict-Transport-Security: max-age=15552000; includeSubDomains; preload X-Content-Type-Options: nosniff Server: cloudflare Cf-Ray: 715fa3ce6f8f38b0-VIE { "errors":[ { "message":"0 AUTHENTICATED", "locations":[ { "line":1, "column":73 } ], "path":[ "authTSPRefreshTokenCreate" ], "extensions":{ } } ], "data":{ "authTSPRefreshTokenCreate":null }, "extensions":{ } } ``` Now the app wants to update the phone number and checks, if it was successful ``` POST /graphql/UpdatePhoneNumber HTTP/2 Host: okcupid.com Cookie: override_session=0; secure_login=1; secure_check=1; guest=7499627801677657669; session=7499627801677657669%3A16707096719338247505; authlink=158b116f; _fbp=fb.1.1652979626229.695918336; nano=k%3Diframe_prefix_lock_3%2Ce%3D1653930142760%2Cv%3D1%7Ck%3Diframe_prefix_lock_2%2Ce%3D1653650365181%2Cv%3D1%7Ck%3Diframe_prefix_lock_1%2Ce%3D1652979653689%2Cv%3D1%7Ck%3Diframe_prefix_lock_-1%2Ce%3D1653930218437%2Cv%3D1 Accept: application/json X-Apollo-Operation-Id: 706c24c6f60c764605db4e932c1ccdc80020cc928b1111bcc0a14531d265811e X-Apollo-Operation-Name: UpdatePhoneNumber X-Okcupid-Device-Id: Android; AOSP on IA Emulator; 9; PSR1.180720.117; Unavailable; fwciLTsdTECsUopudsdVN0; X-Okcupid-App: OkCupid Android App 64.1.0 X-Okcupid-Platform: Android X-Okcupid-Version: 64.1.0 X-Okcupid-Locale: en X-Okcupid-Emulator: true User-Agent: Android 64.1.0 X-Emb-Path: /graphql/UpdatePhoneNumber Content-Type: application/json; charset=utf-8 Content-Length: 230 Accept-Encoding: gzip, deflate { "operationName":"UpdatePhoneNumber", "variables":{ "input":{ "tspRefreshToken":"" } }, "query":"mutation UpdatePhoneNumber($input:UserUpdatePhoneNumberInput!) { userUpdatePhoneNumber(input: $input) { __typename success statusCode } }" } ``` We need to change the response to this request by setting "success" to true and the statuscode to 0. ``` HTTP/2 200 OK Date: Sat, 04 Jun 2022 09:43:55 GMT Content-Type: application/json; charset=utf-8 X-Powered-By: Express Vary: Origin, Accept-Encoding Vary: Accept-Encoding Access-Control-Allow-Credentials: true Etag: W/"81-hrlnHKfISioHEIcL0gOlcbam6Zg" Cf-Cache-Status: DYNAMIC Expect-Ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct" Strict-Transport-Security: max-age=15552000; includeSubDomains; preload X-Content-Type-Options: nosniff Server: cloudflare Cf-Ray: 715fba185fb577fb-VIE { "data":{ "userUpdatePhoneNumber":{ "__typename":"UserUpdatePhoneNumberResponse", "success":true, "statusCode":0 } }, "extensions":{ } } ``` Forwarding this modified response logs us in without a valid phone number. This whole process needs to be done really quick, because otherwise it shows a time-out error message. Besides that, this needs to be done everytime we restart the app, because it's only valid on the client side and no number is saved in the account. With automation of this process, a lot spam accounts could be made. Additionally the app also doesn't care about real email addresses. ## Direct profile access and obtaining other users data At first we wanted to investigate the data of users the app gives us. Luckily after first login, a staff sent us a "welcome" message. After clicking on the profile, the app sends a few request like: ``` GET /1/profile/4170169657126759250 HTTP/2 Host: api.okcupid.com Cookie: override_session=0; secure_login=1; secure_check=1; guest=7499627801677657669; session=7499627801677657669%3A16707096719338247505; authlink=158b116f; _fbp=fb.1.1652979626229.695918336; siftsession=710935292553977095; nano=k%3Diframe_prefix_lock_-1%2Ce%3D1653930218437%2Cv%3D1%7Ck%3Diframe_prefix_lock_3%2Ce%3D1653930142760%2Cv%3D1%7Ck%3Diframe_prefix_lock_2%2Ce%3D1653650365181%2Cv%3D1%7Ck%3Diframe_prefix_lock_1%2Ce%3D1652979653689%2Cv%3D1%7Ck%3Diframe_prefix_lock_4%2Ce%3D1654341185691%2Cv%3D1 Endpoint_version: 1 X-Okcupid-Device-Id: Android; AOSP on IA Emulator; 9; PSR1.180720.117; Unavailable; fwciLTsdTECsUopudsdVN0; X-Okcupid-App: OkCupid Android App 64.1.0 X-Okcupid-Platform: Android X-Okcupid-Version: 64.1.0 X-Okcupid-Locale: en X-Okcupid-Emulator: true User-Agent: Android 64.1.0 Accept-Encoding: gzip, deflate ``` Also to this endpoints: ``` GET /1/profile/4170169657126759250/fields?fields=userinfo%2Cphotos.limit%281%29%2Clikes%2Cpercentages ``` ``` GET /1/profile/4170169657126759250/answer_filters ``` ``` GET /1/profile/4170169657126759250/answers?filter=9&after=&limit=60&sort=0 HTTP/2 ``` These are to load the user data and present it on the user chart. But it also sends a request with my user id: ``` GET /1/profile/7499627801677657669/fields?fields=userinfo%2Cphotos.limit%281%29%2Clikes%2Cpercentages HTTP/2 ``` The response to this give a lot of my information: ```=json { "userinfo":{ "realname":"OhBoy", "gender_letter":"M", "gender":"Man", "age":22, "join_date":1652957903, "email_address":"oh.boy@gmail.com", "rel_status":"Single", "location":"Graz", "orientation":"Straight", "displayname":"OhBoy", "staff_badge":null }, "photos":[ { "full":"https://cdn.okccdn.com/php/load_okc_image.php/images/150x150/558x800/52x197/152x297/0/4918138661936607854.webp?v=3", "original":"https://cdn.okccdn.com/php/load_okc_image.php/images/52x197/152x297/0/4918138661936607854.webp?v=3", "info":{ "full":{ "thumbnail":{ "height":100, "y":197, "x":52, "width":100 }, "size":{ "height":481, "y":0, "x":0, "width":365 }, "center":{ "y":247, "x":102, "y_percent":51, "x_percent":27 } }, "caption":"", "id":"4918138661936607854", "original":{ "size":{ "width":365, "height":481 }, "thumbnail":{ "height":100, "y":197, "x":52, "width":100 } } }, "full_small":"https://cdn.okccdn.com/php/load_okc_image.php/images/150x150/279x400/52x197/152x297/0/4918138661936607854.webp?v=3" } ], "likes":{ "via_superboost":null, "mutual_like_vote":0, "recycled":0, "they_superlike":null, "passed_on":0, "they_like":null, "you_superlike":0, "you_like":null, "via_spotlight":null, "mutual_like":0, "vote":{ } }, "percentages":{ "match":96, "enemy":99 }, "inactive":false, "userid":"7499627801677657669", "staff":false, "username":"7499627801677657669", "isAdmin":false } ``` The staff and isAdmin tag was interesting on first sight. I tried to alter it in some other requests and response, but nothing interesting happend. However, investigating the reponse of the first request to load the staff profile showed some interesting data. It is a lot of JSON data with a lot of tags. The tag "staff" obviously is true, but also isAdmin is set to true on this profile. While reading through the data and comparing it with the profile chart on the app, we found a tag with data that was not visible on the app. Besides the location (New York), which is also visible, there are tags "neighbourhood" and "home_neighbourhood" which are set to "Chelsea" on this profile. The tag "distance" show the distance between both users. With more accounts and the actual distance of these that could be exploited to triangluate the target users exact location! There is also hidden information that could help scammers or people with bad intentions. Alice is her username for some reason. If you look at my JSON data, my username is my user id. ```=json "nudge":{ "text":"Alice is looking for people near Manhattan, NY", "type":"wiw", "id":"distance", "negative":true, "hidden":true } ``` The app uses staticly linked photos like ``` https://cdn.okccdn.com/php/load_okc_image.php/images/0x375/1125x1500/0/10209088093963045520.jpeg ``` where the user id is 10209088093963045520. The whole JSON data looks like this: ```=json { "user":{ "connection_compatibilities":[ ], "inactive":false, "likes":{ "via_superboost":null, "mutual_like_vote":0, "recycled":0, "they_superlike":null, "passed_on":0, "they_like":null, "you_superlike":0, "you_like":null, "via_spotlight":null, "mutual_like":0, "vote":{ } }, "details":[ { "text":{ "text":"Straight, Woman, Cis Woman, Single, Monogamous", "text_color":{ "blue":53, "green":47, "red":42, "alpha":1 } }, "info":{ "name":"basics" }, "icon":{ "icon_color":null, "url":"https://cdn.okccdn.com/media/img/icons/details_basics.png" } }, { "text":{ "text":"Uses she/her pronouns", "text_color":{ "blue":53, "green":47, "red":42, "alpha":1 } }, "info":{ "name":"pronouns" }, "icon":{ "icon_color":null, "url":"https://cdn.okccdn.com/media/img/icons/details_pronouns.png" } }, { "text":{ "text":"5\u2019 10\u201d, Average build", "text_color":{ "blue":53, "green":47, "red":42, "alpha":1 } }, "info":{ "name":"looks" }, "icon":{ "icon_color":null, "url":"https://cdn.okccdn.com/media/img/icons/details_looks.png" } }, { "text":{ "text":"White, Speaks English", "text_color":{ "blue":53, "green":47, "red":42, "alpha":1 } }, "info":{ "name":"background" }, "icon":{ "icon_color":null, "url":"https://cdn.okccdn.com/media/img/icons/details_background.png" } }, { "text":{ "text":"Doesn't smoke cigarettes, Drinks sometimes, Omnivore", "text_color":{ "blue":53, "green":47, "red":42, "alpha":1 } }, "info":{ "name":"lifestyle" }, "icon":{ "icon_color":null, "url":"https://cdn.okccdn.com/media/img/icons/details_lifestyle.png" } }, { "text":{ "text":"Has cat(s), Has dog(s)", "text_color":{ "blue":53, "green":47, "red":42, "alpha":1 } }, "info":{ "name":"family" }, "icon":{ "icon_color":null, "url":"https://cdn.okccdn.com/media/img/icons/details_family.png" } } ], "last_login":1653922874, "essays":[], "bookmarked":false, "location":{ "country_code":"US", "formatted":{ "distance_unit":"mi", "short":"Manhattan, US", "standard":"Manhattan, United States", "home_neighborhood":"Chelsea", "distance":4228.04917, "neighborhood":"Chelsea" }, "state_code":"NY", "locid":4335338 }, "userid":"4170169657126759250", "staff":true, "isAdmin":true, "photos":[], "match_genres":{ "sex":{ "insufficient":true, "you_answered":0, "they_answered":0, "match":50, "mutual_answered":0, "label":"Intimacy", "rel":"/profile/Alice/questions?Sex=1", "enemy":0 }, "religion":{ "insufficient":true, "you_answered":2, "they_answered":0, "match":50, "mutual_answered":0, "label":"Religion", "rel":"/profile/Alice/questions?Religion=1", "enemy":0 }, "ethics":{ "insufficient":true, "you_answered":0, "they_answered":1, "match":50, "mutual_answered":0, "label":"Ethics", "rel":"/profile/Alice/questions?Ethics=1", "enemy":0 }, "other":{ "insufficient":true, "you_answered":5, "they_answered":1, "match":50, "mutual_answered":0, "label":"Other", "rel":"/profile/Alice/questions?Other=1", "enemy":0 }, "lifestyle":{ "insufficient":true, "you_answered":5, "they_answered":0, "match":50, "mutual_answered":0, "label":"Lifestyle", "rel":"/profile/Alice/questions?Lifestyle=1", "enemy":0 }, "dating":{ "insufficient":true, "you_answered":3, "they_answered":0, "match":50, "mutual_answered":0, "label":"Dating", "rel":"/profile/Alice/questions?Dating=1", "enemy":0 } }, "last_contacts":{ "forward":0, "reverse":0 }, "online":false, "userinfo":{ "realname":"Sarah", "gender_letter":"F", "gender":"Woman", "age":38, "join_date":1296597315, "rel_status":"Single", "location":"Manhattan, United States", "orientation":"Straight", "staff_badge":"OkStaff", "displayname":"Sarah" }, "linked_account":null, "username":"Alice", "first_message":null, "hidden":false, "nudge":{ "text":"Alice is looking for people near Manhattan, NY", "type":"wiw", "id":"distance", "negative":true, "hidden":true }, "blocked":false, "percentages":{ "match":100, "enemy":0 } }, "extras":{ "shadowbox":{ "likesCap":{ "name":"likesCapAlist", "title":"You\u2019ve sent the max daily likes", "actions":[ { "target_url":"/upgrade?clear_history=1&cf=likescappaywall", "text":"GET A-LIST" }, { "cancel":1, "text":"Cancel", "action":"cancel" } ], "desc":"Send unlimited likes & find more matches with A-List", "icon":"likesCapError", "icon_src":"http://cdn.okccdn.com/media/img/mobile_app/illustrations/morelikes.png" } }, "hasHeader":true, "hasAds":true, "draft":null, "promo":{ "feature":"browse", "text":"<strong>Browse invisibly</strong>" }, "isVisibleThroughIncognito":false, "userInstructions":{}, "questionPlaceholder":{ "cta":"Answer", "text":"You haven\u2019t answered enough of the same questions.", "rel":"/profile/Alice/questions?unanswered=1", "strong":"Not enough data", "icon":"lock-closed" }, "selfView":false, "hasIncognito":false, "userGuides":{}, "isBoosting":false, "hasProfilePassing":true, "numQuestions":15, "isIncognito":false, "boostTokenCount":0, "canBribe":false, "reduxParams":{ "profile":{ "percentages":{ "match":100, "enemy":0 }, "details":[], "likes":{ "via_superboost":null, "mutual_like_vote":0, "recycled":0, "they_superlike":null, "passed_on":0, "they_like":null, "you_superlike":0, "you_like":null, "via_spotlight":null, "mutual_like":0, "vote":{ } }, "bookmarked":false, "isVisibleThroughIncognito":false, "userInstructions":{}, "photos":[], "viewerUnderstandsOcsLike":true, "online":false, "currentUserThumb":"https://cdn.okccdn.com/php/load_okc_image.php/images/160x160/160x160/52x197/152x297/2/4918138661936607854.webp?v=3", "likesCapRateCardViewCount":0, "isBoosting":false, "userinfo":{ "realname":"Sarah", "gender_letter":"F", "gender":"Woman", "age":38, "join_date":1296597315, "rel_status":"Single", "location":"Manhattan, United States", "orientation":"Straight", "staff_badge":"OkStaff", "displayname":"Sarah" }, "numQuestions":15, "location":{ "country_code":"US", "formatted":{ "distance_unit":"mi", "short":"Manhattan, US", "standard":"Manhattan, United States", "home_neighborhood":"Chelsea", "distance":4228.04917, "neighborhood":"Chelsea" }, "state_code":"NY", "locid":4335338 }, "isIncognito":false, "isLoggedIn":true, "boostTokenCount":0, "userid":"4170169657126759250", "displayname":"Sarah", "showNoPhotosBlock":false, "hasInstagram":false, "viewerUnderstandsOcsPass":true, "isSelfView":false, "linkedAccount":null, "likesCapResetTime":1654394400, "nudge":{ "text":"Alice is looking for people near Manhattan, NY", "type":"wiw", "id":"distance", "negative":true, "hidden":true }, "essays":[], "mobileMessageLink":"/messages?compose=1&userid=4170169657126759250", "staffBadge":"OkStaff", "blocked":false, "username":"Alice", "age":38, "firstMessage":null } }, "hasInstagram":false, "lastThreadId":null, "canVisit":false, "hasLikesRemaining":true, "current_user_has_seen_likes_cap":false, "notice":{ "display":{ }, "state":null }, "activeAlbumCount":1, "shareURL":"/1/apitun/profile/Alice/share", "icebreakers":{ "geo":{ "neighborhood":"Chelsea", "type":"geo" } }, "matchAnalysis":{ "match_pct":50, "text":"They need to answer more questions.", "id":"insufficient_them", "enemy_pct":0, "accurate":false, "title":"Not enough info" }, "morePhotos":[ ], "isBrowsingAnon":false, "hasAgeBlock":false, "staffBadge":"OkStaff", "blocked":false, "canBigMailbox":false, "morePhotosAlbumId":"14300816382384942176", "lastContact":0, "canMessage":false, "genreOrder":[ "sex", "religion", "ethics", "other", "lifestyle", "dating" ], "reportingIntro":{ "blocking":{ "userGuide":"", "shadowbox":{}, "understands":false } }, "profileCommenting":{ "userGuide":"profile_commenting_photo", "understands":true }, "lastOnlineString":"Last online Monday - 5:01pm", "showPhotoMessageBlock":false } } ``` More tags are "isOnline", "lastContact", "lastOnlineString" that shows last time online e.g. "Last online Wednesday - 12:11am", "isLoggedIn" and hasInstagram. There is also a section where you can see the "match_genres" what the user answered and what the match value is. Looking at the match analysis in this data we can see in advance, what the app "thinks" about the match without interacting with the user. ```=json "matchAnalysis":{ "disposition":"negative", "enemy_pct":60, "match_pct":78, "id":"high_enemy", "title":"Y\u2019all got issues", "accurate":true, "text":"Compatibility looks hazy." } ``` This information could be used to change the profile in a way, to match the target user and get a perfect match. Looking further in the location part shows that the neighbourhood tag is not automatically set. Maybe it's an option when living in a big city. But it shows still the distance and also the distance unit ```=json "location":{ "country_code":"AU", "formatted":{ "distance_unit":"mi", "short":"Lassnitzh\u00f6he", "standard":"Lassnitzh\u00f6he", "neighborhood":null, "distance":6.281863434 }, ``` ## Investigating premium functions The app offers a tab where the user can see who likes them. As usual, this is behind a pay wall. But here we can also modify the response, when data is loaded in with a request, to remove the locking function. First the app sends a request to ``` POST /graphql/SeeWhoLikesYouFeatureStatus HTTP/2 ``` Changing the values of "isActive" and "wasEverActive" the response to true, removes the scrolling lock. Sadly the profile are still defaced and still behind a paywall. ``` { "data":{ "me":{ "__typename":"User", "featureSubscription":{ "__typename":"FeatureSubscription", "id":"SEE_WHO_LIKES_YOU", "isActive":true, "wasEverActive":true, "timeOfMarkedLoss":null, "timeOfActualLoss":null } } }, "extensions":{ } } ``` ## Liking process Using the discover tab (where users are able to like/dislike suggested users) the app first loads in a few profiles with pictures. If the user likes or dislikes the presented other user, the app doesn't instantly send a POST request to the API. It seems that the app collects the votes and sends them after a few minutes. Looking at the POST request ``` POST /1/locals/vote HTTP/2 Host: api.okcupid.com Cookie: override_session=0; secure_login=1; secure_check=1; guest=7499627801677657669; session=7499627801677657669%3A16707096719338247505; authlink=158b116f; _fbp=fb.1.1652979626229.695918336; siftsession=710935292553977095; nano=k%3Diframe_prefix_lock_5%2Ce%3D1654344199164%2Cv%3D1%7Ck%3Diframe_prefix_lock_4%2Ce%3D1654341185691%2Cv%3D1%7Ck%3Diframe_prefix_lock_3%2Ce%3D1653930142760%2Cv%3D1%7Ck%3Diframe_prefix_lock_2%2Ce%3D1653650365181%2Cv%3D1%7Ck%3Diframe_prefix_lock_1%2Ce%3D1652979653689%2Cv%3D1%7Ck%3Diframe_prefix_lock_-1%2Ce%3D1654344250986%2Cv%3D1 Endpoint_version: 1 X-Okcupid-Device-Id: Android; AOSP on IA Emulator; 9; PSR1.180720.117; Unavailable; fwciLTsdTECsUopudsdVN0; X-Okcupid-App: OkCupid Android App 64.1.0 X-Okcupid-Platform: Android X-Okcupid-Version: 64.1.0 X-Okcupid-Locale: en X-Okcupid-Emulator: true User-Agent: Android 64.1.0 Content-Type: application/json; charset=UTF-8 Content-Length: 1742 Accept-Encoding: gzip, deflate { "votes":[ { "cardType":"user", "id":"10148139789107709024", "user_data":"CmaiYuuFatwY49Nv5Ci6chBljp77aOI3EtFZ28mKGhNNYe/m9oGA4anxsx2abwO9v+YT6yUY6ZVgtT2sGegwUrU7DvxuDf+vgudjxf11uiJnpK0pm3n4M8OVMXkdHVRJLhrQ424i1FcAv4n0+DM6h75HMCr/GpMusdJNNdtXV9QgBbZq2CZxfnBs3htDUDKA5H8icmnTHAX6tc2I3SKk5b2yflfgVPowZ7Qh+BSVCCSCu/ZZ5BlItm7+HM72zf3tEq3Z2BC/BZcJCR44UB+P6g==", "voteValue":"yes" }, { "cardType":"user", "id":"17006486363134290132", "user_data":"YOIEjbS5lKIFSXM48SLq7pa5a/Tj0TMtL/d/fHy390VPqbDxZ23OFbkwM/qMrARsODBCWbMxNKe58w7dE2rtWzQQ5jTSGxRrzvA66y6BYCZILGtzyfcIohYoSGdcldxtr9CCVluvDllIfrcl9pF4dIr3B1DTJkXz/ScjOfHdmmgnZGYqzQGsHsYIAdo20rbQr3xVUkqg+aYr5W80VRHZ18wzGOumIhDmewxXPb3w2S0Uk4JG+uua+FBxtj+UMt2K", "voteValue":"yes" }, {}, {}, {} ] } ``` we can see the collected votes. Besides the voteValue the app sends encrypted user_data. At the top, we can see the user_id of my account. In the session tag, we can see that the first part is also my user_id. Playing around and altering these values to like my account with their user_id or liking myself didn't do anything. Decrypting the user data also didn't show anything. Liking a lot of users, i finally hit the cap. The like remaining till the cap, are saved and updated on the API. ```=json { "likesRemaining":0, "likesCapBlankState":{ "id":"likesCapAList", "title":"You\u2019ve sent the max daily likes", "subtitle":"Send unlimited likes & find more matches with A-List", "buttons":[ { "intent":{ "url":"/upgrade?clear_history=1&cf=likescappaywall&source=doubletake" }, "text":"GET A-LIST" } ], "icon_src":"http://cdn.okccdn.com/media/img/mobile_app/illustrations/morelikes.png" }, "user":[ { "via_spotlight":false, "status":1, "mutual_like":false, "notify_good_vote":true }, { "via_spotlight":false, "status":1, "mutual_like":false, "notify_good_vote":true }, { "via_spotlight":false, "status":1, "mutual_like":false, "notify_good_vote":true }, { "via_spotlight":false, "status":1, "mutual_like":false, "notify_good_vote":true }, { "via_spotlight":false, "status":1, "mutual_like":false, "notify_good_vote":true } ], "likesCapResetTime":1654394400, "likesCapRateCardViewCount":0, "shouldTrackHitLikesCap":true } ``` That is the response from the API after liking the last user. From there liking a user doesn't do anything. The number of times to try to like is tracked and sent with a request: ``` POST /1/rate_card/view HTTP/2 Host: api.okcupid.com ... {"promo_id":"alist.likescappaywall"} ``` ## Tracking To log user events this app regularly sends this request with encrypted data to https://data.emb-api.com. ``` POST /v1/log/logging HTTP/1.1 ``` This can track events on the app and begins to track even in the register/login process. Thats how they can track user behaviour. ## Conclusion Looking at the data we got from other users, it can be said that this app leaks a lot of user data. Just by looking at a profile you get information about last login, join date, is boosting, is online and is login, has likes remaining, match analysis without any interaction, sometimes home neighbourhood and a pretty precise distance. Also tags about is staff or is admin are shown. So in theory staff and admins can be avoided. It's also interesting that the whole verification with a phone number can be skipped. So in theory if this can be automated, spam accounts could be made with ease, because they also don't care about the email entered. ## Future work The app could be modified in a way that the app can present all leaked data, when going to a user profile. And the number skip could possibly be automated. # MobSec report Plenty Of Fish dating app ## Description Plenty of Fish is a canadian dating app with the typical like/dislike charts, but also has a area, where user can watch a live stream of other users. It has a lot of recommentation of users in nearly every tab. Without upgrading (paying them some money) the user can like/dislike the charts, write a message to other users and watch a stream. Source: https://en.wikipedia.org/wiki/POF_(dating_website) ## Structure of report 1. Description 2. Registration process 3. Login process 6. Obtaining other users data 7. Direct profile access 9. Liking Process 10. Tracking 11. Conclusion ## Registration process Inspecting the registration process, showed nothing suspicious. A number is required to register. Skipping that didn't work. ## Login process Logged in we see that most of the communication of the app is encrypted. But some request and responses are still in clear text. At some early point in the research of the communication, burpsuit captured following request. The api seems to send keys and also an encryption key. Request: ``` POST /V2/Instance HTTP/1.1 Accept: text/json Content-Type: application/json; charset=utf-8 User-Agent: Dalvik/2.1.0 (Linux; U; Android 9; AOSP on IA Emulator Build/PSR1.180720.117) Host: pofapi.sinch.com Connection: close Accept-Encoding: gzip, deflate Content-Length: 325 { "ApplicationKey":"d61c01ea-111d-4805-b662-d47cc14320a1", "Capabilities":[ "ice-restart.1", "p2p", "push", "push.2", "push.3", "push.4", "srtp.1", "voip.1" ], "DeviceId":"0f10eeb6-01e6-40ad-8576-f3f391a704d2", "DeviceType":2, "PushData":"", "SdkVersion":"3.17.4", "Seed":"0", "Signature":"D5K4JWZbFNZxAKFZxmPPWsdcbcM=", "UserId":"316992311" } ``` The Response contains this data ```=json { "Data":{ "Instance":{ "UserId":"316992311", "ApplicationKey":"d61c01ea-111d-4805-b662-d47cc14320a1", "InstanceId":"d26ecbbd-f106-420b-9f4e-9cdeca242e84" }, "Config":{ "BroadcastChannel":"3c57058e-b8cd-45fb-85ec-3e8654e71f23B", "SignalChannel":"3c57058e-b8cd-45fb-85ec-3e8654e71f23S", "EncryptionKey":"jzIfVbDioSzztQpun9hqmQ==", "BroadcastSubscribeKey":"sub-c-c5e52f20-d446-11e3-b488-02ee2ddab7fe", "BroadcastPublishKey":"pub-c-491d2c47-7cf0-4cde-bc39-f282dcec788e", "SignalSubscribeKey":"sub-c-56224c36-d446-11e3-a531-02ee2ddab7fe", "SignalPublishKey":"pub-c-1ba05dee-92cb-49b5-83e2-96bec2103751", "PubSubUri":"rebtelsdk.pubnub.com", "Stun":"stun.l.google.com:19302", "Turn":"", "GcmSenderId":"1017500357356", "PubSubSsl":true } }, "Result":200, "Message":"InstanceCreated" } ``` Repeating the request gave the same result. ## Obtaining other users data When clicking on the live stream tab, the app requests the streaming users with: ``` POST /1/functions/sns-video:getBroadcastsByNearbyMarquee HTTP/2 Host: video-api.gateway.pof.com X-Parse-Session-Token: r:106d3f67bf0096b7b42aed87d7ef8774 X-Parse-Application-Id: sns-video X-Parse-App-Build-Version: 1501955 X-Parse-App-Display-Version: 4.66.0.1501955 X-Parse-Os-Version: 9 X-Parse-Installation-Id: cf64fa12-ddc9-45ca-89d2-132a1e2b6b21 X-Parse-Client-Key: com.pof.android User-Agent: Parse Android SDK 1.19.0 (com.pof.android/1501955) API Level 28 Content-Type: application/json Content-Length: 64 Accept-Encoding: gzip, deflate {"score":"0","nearMyAge":false,"gender":"female","pageSize":100} ``` The parameters here can be changed an effects the loaded in users. The score is the position in the loaded list and the pageSize is the number of users loaded in. So it does not really change the result. The response shows some interesting infos about the users loaded in: ```=json HTTP/2 200 OK Date: Wed, 01 Jun 2022 06:49:42 GMT Content-Type: application/json; charset=utf-8 Set-Cookie: AWSALB=I8EWtteIbhi22vDe81k4AZy6u3YzSGsHF35QcmuUfUFA2SH9x5OW6hut8z6CQZMd+Cka0Wh9VJPrmDVixdgKYslUsLjdTbPxN8o2EU/0cCCTdbKeuEToLoIwJPmL; Expires=Wed, 08 Jun 2022 06:49:42 GMT; Path=/ Set-Cookie: AWSALBCORS=I8EWtteIbhi22vDe81k4AZy6u3YzSGsHF35QcmuUfUFA2SH9x5OW6hut8z6CQZMd+Cka0Wh9VJPrmDVixdgKYslUsLjdTbPxN8o2EU/0cCCTdbKeuEToLoIwJPmL; Expires=Wed, 08 Jun 2022 06:49:42 GMT; Path=/; SameSite=None; Secure Server: nginx Vary: Accept-Encoding X-Powered-By: Express Access-Control-Allow-Origin: * Access-Control-Allow-Methods: GET,PUT,POST,DELETE,OPTIONS Access-Control-Allow-Headers: X-Parse-Master-Key, X-Parse-REST-API-Key, X-Parse-Javascript-Key, X-Parse-Application-Id, X-Parse-Client-Version, X-Parse-Session-Token, X-Requested-With, X-Parse-Revocable-Session, Content-Type Etag: W/"2de2-5VUYajM9MfBbl0gefcDp0VXrTKo" { "result":{ "more":false, "score":"6", "broadcasts":[ { "createdAt":"2022-06-01T04:48:12.484Z", "updatedAt":"2022-06-01T06:49:12.721Z", "activeUntil":{ "__type":"Date", "iso":"2022-06-01T06:50:12.716Z" }, "reports":[ { "userId":"wSDYGOqhEj", "reportedAt":1654063055515 } ], "socialNetwork":{ "__type":"Pointer", "className":"SNSSocialNetwork", "objectId":"gBkJOk6EBS" }, "language":"en", "broadcasterAge":32, "userDetails":{ "createdAt":"2001-01-01T00:00:00.0Z", "updatedAt":"2001-01-02T00:00:00.0Z", "profilePic":{ "large":"https://pics.pof.com/thumbnails/size480/1186/75/59/4633dd5ee-4bc9-4972-9ccd-ea5071f66b3b.jpg", "square":"https://pics.pof.com/thumbnails/size220/1186/75/59/4633dd5ee-4bc9-4972-9ccd-ea5071f66b3b.jpg" }, "birthDate": ... {"id":"fgisiSujdL", "distanceInKm":0, "isBattle":false, "isPoll":false, "isBlindModeActivated":false, "isNextGuest":false, "isNextDateGame":false, "isDateNightModeActivated":false }, {"id":"WBPFgGdBD2", "distanceInKm":0, "isBattle":false, "isPoll":false, "isBlindModeActivated":false, "isNextGuest":false, "isNextDateGame":false, "isDateNightModeActivated":false }, { "id":"FU92vqVftf", "distanceInKm":0, "isBattle":false, "isPoll":false, "isBlindModeActivated":false, "isNextGuest":false, "isNextDateGame":false, "isDateNightModeActivated":false }, { "id":"mLqTdSzYxL", "distanceInKm":0, "isBattle":false, "isPoll":false, "isBlindModeActivated":false, "isNextGuest":false, "isNextDateGame":false, "isDateNightModeActivated":false }, { "id":"0N6EmEjgCP", "distanceInKm":0, "isBattle":false, "isPoll":false, "isBlindModeActivated":false, "isNextGuest":false, "isNextDateGame":false, "isDateNightModeActivated":false }, { "id":"I0BAAnisdJ", "distanceInKm":0, "isBattle":false, "isPoll":false, "isBlindModeActivated":false, "isNextGuest":false, "isNextDateGame":false, "isDateNightModeActivated":false } ] }, "correlation":{ "id":"getNearByMarqueeVideos-pof:user:guest-1654066182368", "source":"search" } } ``` There are also static links to the profile pictures, but in this case it seems that the link is not directly related to the user id. In the livestream section several tabs. In one of them the app made a request and the reponse had lat. and longt. of my device in it: ```=json HTTP/2 200 OK Date: Wed, 01 Jun 2022 08:25:05 GMT Content-Type: application/json; charset=utf-8 Content-Length: 441 Set-Cookie: AWSALB=dYbhTsmIKnBG+GGjJjbBAQie4Q1q4Mon7qnBZTV9jvRzgs5QMY0p+uFNRh3gM/Di1pU7aHSoQ5aqlA3JisQEt5FYyifTrdSLbrn6EWO9t7gKB6EN6S4XRa0ZyD05; Expires=Wed, 08 Jun 2022 08:25:05 GMT; Path=/ Set-Cookie: AWSALBCORS=dYbhTsmIKnBG+GGjJjbBAQie4Q1q4Mon7qnBZTV9jvRzgs5QMY0p+uFNRh3gM/Di1pU7aHSoQ5aqlA3JisQEt5FYyifTrdSLbrn6EWO9t7gKB6EN6S4XRa0ZyD05; Expires=Wed, 08 Jun 2022 08:25:05 GMT; Path=/; SameSite=None; Secure Server: nginx X-Powered-By: Express Access-Control-Allow-Origin: * Access-Control-Allow-Methods: GET,PUT,POST,DELETE,OPTIONS Access-Control-Allow-Headers: X-Parse-Master-Key, X-Parse-REST-API-Key, X-Parse-Javascript-Key, X-Parse-Application-Id, X-Parse-Client-Version, X-Parse-Session-Token, X-Requested-With, X-Parse-Revocable-Session, Content-Type Etag: W/"1b9-vggWPuhsLr+dI/M0yBAA2g4i//s" { "objectId":"ZLYetyGZzl", "createdAt":"2022-06-01T08:25:00.241Z", "updatedAt":"2022-06-01T08:25:00.241Z", "username":"user_351791324@pof.net", "email":"user_351791324@pof.net", "birthDate":{ "__type":"Date", "iso":"2001-01-01T08:00:00.000Z" }, "locale":"en-US", "latitude":47.1, "longitude":15.4, "ACL":{ "*":{ "read":true }, "ZLYetyGZzl":{ "read":true, "write":true } }, "__type":"Object", "className":"_User", "sessionToken":"r:0d0afaccdf02a64ba34189703ec72ce1" } ``` But e.g. in the "nearby" tab the lat. and longt. were in the request. ## Direct profile access To get users data the app sends following request: ``` GET /profile/users/pof:user:351791324 HTTP/2 Host: api.gateway.pof.com Authorization: Bearer {Bearer Token} User-Agent: pof/1501955 (id=com.pof.android) android/28 (9) sns/4.24.7 (release) okhttp/??? Accept-Language: en-US Accept-Encoding: gzip, deflate ``` 351791324 is my userid in this case. The Response shows following: ```=json HTTP/2 200 OK Date: Wed, 01 Jun 2022 08:25:09 GMT Content-Type: application/json Content-Length: 1215 Set-Cookie: AWSALB=vpTA+JCypYbSVnJgVQqDtc+ks6yPbxuO5EOjL33ih2YRiaJiMfDPxEcUGrWwJ7+xPn3fLgA7w6go6Csidp09wqfvuM336aRwCjlqANzggahGVmXIHEST8f6Y3WJI; Expires=Wed, 08 Jun 2022 08:25:09 GMT; Path=/ Set-Cookie: AWSALBCORS=vpTA+JCypYbSVnJgVQqDtc+ks6yPbxuO5EOjL33ih2YRiaJiMfDPxEcUGrWwJ7+xPn3fLgA7w6go6Csidp09wqfvuM336aRwCjlqANzggahGVmXIHEST8f6Y3WJI; Expires=Wed, 08 Jun 2022 08:25:09 GMT; Path=/; SameSite=None; Secure Set-Cookie: AWSALB=Z58VQloOFLlbskqgwbpOALVl9lS3Ts2cLuPahp4x1TAtcn2ri47xcFn7B0eNKJZwSBmM3rz1UsvI3tEDke6lIW1H+wssFrJtTdbEAPxC2nKd0J8sUatv1m95sKm4; Expires=Wed, 08 Jun 2022 08:25:09 GMT; Path=/ Set-Cookie: AWSALBCORS=Z58VQloOFLlbskqgwbpOALVl9lS3Ts2cLuPahp4x1TAtcn2ri47xcFn7B0eNKJZwSBmM3rz1UsvI3tEDke6lIW1H+wssFrJtTdbEAPxC2nKd0J8sUatv1m95sKm4; Expires=Wed, 08 Jun 2022 08:25:09 GMT; Path=/; SameSite=None; Secure Vary: Accept-Encoding Server: TMG-Gateway/2.0.21 { "about":"What is love?\n\nSurfing, traveling, films, good food", "bodyType":[ "athletic" ], "covidVaccineStatus":null, "displayName":"JeffBayzoss1", "education":"some_college", "ethnicity":[ ], "firstName":"JeffBayzoss1", "gender":"male", "hasChildren":"not_specified", "height":1830, "id":"pof:user:351791324", "images":[ { "id":null, "square":"https://pics.pof.com/thumbnails/size220/1187/70/38/209b67a05-e67c-4e19-bf79-22bee43bb467.jpg", "large":"https://pics.pof.com/thumbnails/size480/1187/70/38/209b67a05-e67c-4e19-bf79-22bee43bb467.jpg" } ], "interestedIn":"women", "interests":[ ], "languages":null, "lastName":null, "lastSeen":1654071516963, "liveAbout":"What is love?\n\nSurfing, traveling, films, good food", "locale":"en-US", "location":{ "city":"Graz", "state":"Styria", "country":"AT" }, "lookingFor":[ "dating", "friendship" ], "network":"pof", "privacySettings":null, "registrationTs":1654065715680, "relationshipStatus":null, "religion":"not_specified", "searchGender":"male", "settings":null, "smoker":"not_specified", "verificationBadges":[ ], "age":21, "relations":{ "id":"pof:user:351791324", "following":false, "blocked":false, "$id":"/users/pof:user:351791324/relations", "$type":"resource" }, "$type":"resource", "$id":"/users/pof:user:351791324" } ``` It works also with other user ids, but the Bearer Token, which changes from time to time, needs to be valid. And it also shows nothing to revealing ... There is also a similar request to the video api: ``` GET /1/users/313614240 HTTP/2 Host: video-api.gateway.pof.com ``` It shows similar response but in this case, making a request to a other user gives an error: ``` HTTP/2 403 Forbidden ``` ## Liking process The liking process in this app is also encrypted. ## Tracking Inspecting the communication further showed, that the app logs what tab the user visits and what tab was previous visited: ``` POST /event-ingest/log HTTP/2 Host: api.gateway.pof.com Authorization: Bearer {Bearer Token} User-Agent: pof/1501955 (id=com.pof.android) android/28 (9) sns/4.24.7 (release) okhttp/??? Accept-Language: en-US Content-Type: application/json; charset=UTF-8 Content-Length: 196 Accept-Encoding: gzip, deflate { "events":[ { "body":{ "selected_tab":"new", "previous_tab":"for_you" }, "event":"s_mobile_tmglive_ui_tab_selected", "version":2, "timestamp":1654096835661, "uuid":"d53bf710-aaef-431e-aeed-4c95aa095ea6" } ] } ``` It also has some log requests to the API, but these are as well encrpyted. ## Conclusion For this app it can be said that it does a good job in preventing user data leaked. Besides encrypting a lot of the traffic sent and received, it makes e.g. exposure of the distance harmless, because it's rounded presented in km (which would be a big area to find a person). # MobSec report Tinder dating app ## Description Tinder is the most popular or better most common dating app nowadays. The name Tinder comes from an easily combustible material used to start a fire. It advertises with the simple method of swiping left or right on a users profile if they like them or dislike them. If both users like each other a so called match is made and they can start a conversation. It's very well made and also very simple in use. Because it has grown so big we didn't expect to find much on this app. But for completeness we have investigated this app as well. Source: https://tinder.com/about-tinder ## Structure of report 1. Description 2. Registration process 3. Login process 6. Obtaining other users data 7. Direct profile access 8. Investigating premium functions 9. Liking Process 10. Tracking 11. Conclusion 12. Future work ## Registration process In the registration process was nothing noticeable to be found besides that russian phone number don't work anymore. ## Login process Starting up the app makes some init requests with collecting some data about the device. One of them has a lot of interesting tags in it: ```=json POST /v1/open HTTP/2 Host: api2.branch.io ... { "device_fingerprint_id":"1062755633515839194", "identity_id":"1062755634115628367", "hardware_id":"bca0d57693dc1b8b", "is_hardware_id_real":true, "brand":"Google", "model":"Android SDK built for x86", "screen_dpi":440, "screen_height":1977, "screen_width":1080, "wifi":true, "ui_mode":"UI_MODE_TYPE_NORMAL", "os":"Android", "os_version":29, "country":"US", "language":"en", "local_ip":"10.0.2.15", "app_version":"13.6.1", "facebook_app_link_checked":false, "is_referrable":1, "debug":false, "update":1, "latest_install_time":1654617540261, "latest_update_time":1654617540261, "first_install_time":1654617540261, "previous_update_time":1654617540261, "environment":"FULL_APP", "cd":{ "mv":"-1", "pn":"com.tinder" }, "metadata":{ }, "google_advertising_id":"d104807b-7db9-4aa8-b9af-47d3efcde938", "lat_val":0, "instrumentation":{ "v1\/open-qwt":"0" }, "sdk":"android4.0.0", "branch_key":"key_live_mhkKfK7Br0UHcrNnmV6CVhbosDoGS8JH", "retryNumber":0 } ``` Besides the fingerprints of the device it tracks "is_hardware_id_real", if it's connected to the wifi, "local_ip", "latest_install_time", "first_install_time", "latest_update_time" and "previous_update_time". After that the app sends the current location to the API. ``` POST /v2/meta HTTP/2 Host: api.gotinder.com ... {"lat":47.0707133,"lon":15.4395033,"force_fetch_resources":true} ``` ## Obtaining other users data Clicking on the "main" tab, where the user can vote sends following request to load the user cards in. ``` GET /v2/recs/core?locale=en HTTP/2 Host: api.gotinder.com ``` The response to this gives us some data about the user: ```=json { "type":"user", "user":{ "_id":"62923183cc00110100309cee", "badges":[ ], "bio":"running & \uD83C\uDF55", "birth_date":"1994-06-10T16:21:50.584Z", "name":"Anita", "photos":[ ], "gender":1, "jobs":[ ], "schools":[ { "name":"Karl-Franzens-Universität Graz" } ], "city":{ "name":"Graz" }, "is_traveling":false, "show_gender_on_profile":true, "recently_active":true, "online_now":false, "selected_descriptors":[ ] }, "facebook":{ "common_connections":[ ], "connection_count":0, "common_interests":[ ] }, "spotify":{ }, "distance_mi":1, "content_hash":"6P8UoMck7HLF0pIa5f3LHbJI7bcl2CLMTaIPzcLhm9hp1", "s_number":1883739694839039, "teaser":{ "type":"school", "string":"Karl-Franzens-Universität Graz" }, "teasers":[ { "type":"school", "string":"Karl-Franzens-Universität Graz" } ], "experiment_info":{ }, "is_superlike_upsell":true, "tappy_content":[ ] } ``` In this data is the name, birthdate, school but also the distance in miles. More intersting are the tags "is_traveling", "recently_active" and "online_now". But besides that, it doesn't show much. ## Direct profile access Looking at the communication when clicking on a user shows a response with almost equal data, with not much revealing data of the user. ```=json "status":200, "results":{ "common_friends":[ ], "common_friend_count":0, "spotify_top_artists":[ ], "distance_mi":1, "connection_count":0, "common_connections":[ ], "bio":"\uD83D\uDC83\uD83C\uDFFC\uD83C\uDFC3\uD83C\uDFFC‍", "birth_date":"1994-06-10T16:53:44.966Z", "name":"Melanie", "jobs":[ ], "schools":[ { "name":"University of Graz", "displayed":false } ], "teasers":[ { "type":"sameSchool", "string":"University of Graz" } ], "gender":-1, "show_gender_on_profile":false, "birth_date_info":"fuzzy birth date active, not displaying real birth_date", "ping_time":"2014-12-09T00:00:00.000Z", "badges":[ ], "photos":[], "user_interests":{ "selected_interests":[ { "id":"it_4", "name":"Running" }, { "id":"it_28", "name":"Reading" }, { "id":"it_2016", "name":"Dancing" }, { "id":"it_10", "name":"Brunch" }, { "id":"it_7", "name":"Travel" } ] }, "common_likes":[ ], "common_like_count":0, "common_interests":[ ], "s_number":3805960890940599, "_id":"62817714cfd03201005c2335", "is_tinder_u":false } ``` ## Investigating premium functions The app regularly makes following request where it checks if the user is "boosting" (becomes more suggested to other users). ``` POST /updates?is_boosting=false&boost_cursor=0 HTTP/2 Host: api.gotinder.com ... {"last_activity_date":"2022-06-07T16:20:00.046Z"} ``` The response has some interesting fields in it with matches, harassing_messages, blocks, etc. ... ```=json { "matches":[ ], "blocks":[ ], "inbox":[ ], "liked_messages":[ ], "harassing_messages":[ ], "lists":[ ], "goingout":[ ], "deleted_lists":[ ], "matchmaker":[ ], "squads":[ ], "last_activity_date":"2022-06-07T16:20:00.046Z", "poll_interval":{ "standard":2000, "persistent":120000 } } ``` In combination with the request when clicking on the boost icon: ``` POST /boost HTTP/2 Host: api.gotinder.com ``` I thought that could be manipulated to get an effect. So I tried to change the request before to ``` POST /updates?is_boosting=true&boost_cursor=1 HTTP/2 ``` everytime the app would send it and intercepting and changing the response from the boost icon like this. ```=json { "duration":1800000, "allotment":1, "allotment_used":0, "allotment_remaining":1, "internal_remaining":1, "purchased_remaining":1, "remaining":1, "boost_refresh_amount":1, "boost_refresh_interval":1, "boost_refresh_interval_unit":"m", "out_of_boost":false } ``` Repeating this some time trying to trick the app into thinking we have purchased a boost, gave on time a successful popup! After that the app made some of these requests over some time: ``` GET /v2/profile?include=boost HTTP/2 Host: api.gotinder.com ``` But besides that we didn't have an indicator to see if it really worked. Some time later Tinder locked the account because of supicious activity until we verify our profile. ## Liking process The liking process on this app is really straight forward. With a request to this endpoint, the app sends how the user liked the other user. ```=json POST /like/6289d98ccfd03201005ee2e4 HTTP/2 Host: api.gotinder.com ... { "photoId":"30e94e1c-463d-4c80-90b9-389574ecf2a7", "content_hash":"RX8iwbIl2HZhMbhx2SZ5i5qHE3c93cDbheMUZQhXzi5rtmN", "super":0, "fast_match":0, "top_picks":0, "undo":0, "s_number":7248806679023904 } ``` The app gets a response with a status, a tag if a match happend and the remaining likes. ``` "status":200,"match":false,"likes_remaining":100,"X-Padding":... ``` The response on the other hand is to a other endpoint but with the info in the parameters. ``` GET /pass/5e2c8af6f057db0100f5f9b9?photoId=4a09f78d-6b36-4e1f-9621-8b0ef2890cb4&content_hash=RX3CGUrjIaEt2pcxOfZlHVFEwFGS8VTghdeCrXuRf55&s_number=5848852989094555 HTTP/2 Host: api.gotinder.com ``` Other then that here isn't really anything interesting to be found within the liking process. ## Tracking The app regularly checks if it's in background. ``` GET /v2/device-check/android?background=0 HTTP/2 Host: api.gotinder.com ``` It also makes POST requests with users coordinates to update them. ``` POST /v2/meta HTTP/2 Host: api.gotinder.com ... {"lat":47.0707133,"lon":15.4395033,"force_fetch_resources":true} ``` This request, described above, is really regularly made. Maybe related to the boosting function, which is time based (half an hour once activated). ``` POST /updates?is_boosting=false&boost_cursor=0 HTTP/2 ``` This request is made to a marketing service. I think for tracking purposes. ``` POST /api/v6.3/androidevent?app_id=com.tinder&buildnumber=6.3.1 HTTP/2 Host: inapps.appsflyer.com ``` ## Conclusion The app is really well made and we didn't find that much on this app. Not much of user data is leaked to other users especially in comparison to other dating apps. There is still some strange tracking with coordinates, "is_traveling" tag and the data pulled out of the device after login. ## Future work A deeper investigation of the boost activation with request could be interesting. A comparison how the app acts, when a boost in purchased and how it's visible for other user is necessary.

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully