Skip to content
This repository has been archived by the owner on Mar 3, 2020. It is now read-only.

Commit

Permalink
Updated LiveSync Security (#494)
Browse files Browse the repository at this point in the history
* Updated LiveSync Security

* Live Sync API is now disabled by default.

* Admins can enable or disable the Live Sync API from the Administration Configuration page.

* Live Sync API now has an optional 'Auth Key.'  When the auth key is set, anyone attempting to pull from the API must supply the auth key value in their request.  Without the auth key, no data is provided by the Live Sync API endpoint.

* When using the Auth Key, it must be added as a parameter to the URL value in the `liveimport` script: ```?auth=XXXXX_```

  * Example (with an auth key of `1234567890`:

  * `hhvm -vRepo.Central.Path=/var/run/hhvm/.hhvm.hhbc_liveimport /var/www/fbctf/src/scripts/liveimport.php --url 'https://10.10.10.101/data/livesync.php?auth=1234567890'`

  * Note:  When using the Auth Key you should use a secure key.

* The `livesync` API endpoint will provide error messages if the API is disabled, the key is missing or invalid, or if any general error is encountered.

* The `liveimport` script will check for errors and display those in the output if any are encountered.

* Updated LiveSync Security

* Combined Awaitables throughout LiveSync endpoint.

* Used hash_equals() for API key verification, mitigating timing attacks on the key.# Please enter the commit message for your changes. Lines starting
  • Loading branch information
justinwray authored and gsingh93 committed May 8, 2017
1 parent d326564 commit e880251
Show file tree
Hide file tree
Showing 5 changed files with 220 additions and 64 deletions.
2 changes: 2 additions & 0 deletions database/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,8 @@ INSERT INTO `configuration` (field, value, description) VALUES("password_type",
INSERT INTO `configuration` (field, value, description) VALUES("default_bonus", "30", "(Integer) Default value for bonus in levels");
INSERT INTO `configuration` (field, value, description) VALUES("default_bonusdec", "10", "(Integer) Default bonus decrement in levels");
INSERT INTO `configuration` (field, value, description) VALUES("language", "en", "(String) Language of the system");
INSERT INTO `configuration` (field, value, description) VALUES("livesync", "0", "(Boolean) LiveSync functionality");
INSERT INTO `configuration` (field, value, description) VALUES("livesync_auth_key", "", "(String) Optional LiveSync Auth Key");
UNLOCK TABLES;

--
Expand Down
2 changes: 2 additions & 0 deletions database/test_schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,8 @@ INSERT INTO `configuration` (field, value, description) VALUES("password_type",
INSERT INTO `configuration` (field, value, description) VALUES("default_bonus", "30", "(Integer) Default value for bonus in levels");
INSERT INTO `configuration` (field, value, description) VALUES("default_bonusdec", "10", "(Integer) Default bonus decrement in levels");
INSERT INTO `configuration` (field, value, description) VALUES("language", "en", "(String) Language of the system");
INSERT INTO `configuration` (field, value, description) VALUES("livesync", "0", "(Boolean) LiveSync functionality");
INSERT INTO `configuration` (field, value, description) VALUES("livesync_auth_key", "", "(String) Optional LiveSync Auth Key");
UNLOCK TABLES;

--
Expand Down
43 changes: 43 additions & 0 deletions src/controllers/AdminController.php
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,8 @@ class="fb-cta cta--yellow"
'autorun_cycle' => Configuration::gen('autorun_cycle'),
'start_ts' => Configuration::gen('start_ts'),
'end_ts' => Configuration::gen('end_ts'),
'livesync' => Configuration::gen('livesync'),
'livesync_auth_key' => Configuration::gen('livesync_auth_key'),
};

$results = await \HH\Asio\m($awaitables);
Expand All @@ -318,6 +320,8 @@ class="fb-cta cta--yellow"
$autorun_cycle = $results['autorun_cycle'];
$start_ts = $results['start_ts'];
$end_ts = $results['end_ts'];
$livesync = $results['livesync'];
$livesync_auth_key = $results['livesync_auth_key'];

$registration_on = $registration->getValue() === '1';
$registration_off = $registration->getValue() === '0';
Expand All @@ -337,6 +341,8 @@ class="fb-cta cta--yellow"
$gameboard_off = $gameboard->getValue() === '0';
$timer_on = $timer->getValue() === '1';
$timer_off = $timer->getValue() === '0';
$livesync_on = $livesync->getValue() === '1';
$livesync_off = $livesync->getValue() === '0';

$game_start_array = array();
if ($start_ts->getValue() !== '0' && $start_ts->getValue() !== 'NaN') {
Expand Down Expand Up @@ -887,6 +893,43 @@ class="fb-cta cta--yellow"
</div>
</div>
</section>
<section class="admin-box">
<header class="admin-box-header">
<h3>{tr('LiveSync')}</h3>
<div class="admin-section-toggle radio-inline">
<input
type="radio"
name="fb--conf--livesync"
id="fb--conf--livesync--on"
checked={$livesync_on}
/>
<label for="fb--conf--livesync--on">
{tr('On')}
</label>
<input
type="radio"
name="fb--conf--livesync"
id="fb--conf--livesync--off"
checked={$livesync_off}
/>
<label for="fb--conf--livesync--off">
{tr('Off')}
</label>
</div>
</header>
<div class="fb-column-container">
<div class="col col-pad col-1-4">
<div class="form-el el--block-label el--full-text">
<label>{tr('Optional LiveSync Auth Key')}</label>
<input
type="text"
value={$livesync_auth_key->getValue()}
name="fb--conf--livesync_auth_key"
/>
</div>
</div>
</div>
</section>
<section class="admin-box">
<header class="admin-box-header">
<h3>{tr('Language')}</h3>
Expand Down
233 changes: 169 additions & 64 deletions src/data/livesync.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,83 +6,188 @@ class LiveSyncDataController extends DataController {

public async function genGenerateData(): Awaitable<void> {
$data = array();
await tr_start();
$input_auth_key = idx(Utils::getGET(), 'auth', '');
$livesync_awaits = Map {
'livesync_enabled' => Configuration::gen('livesync'),
'livesync_auth_key' => Configuration::gen('livesync_auth_key'),
};
$livesync_awaits_results = await \HH\Asio\m($livesync_awaits);
$livesync_enabled = $livesync_awaits_results['livesync_enabled'];
$livesync_auth_key = $livesync_awaits_results['livesync_auth_key'];

$teams_array = array();
$all_teams = await Team::genAllTeams();
foreach ($all_teams as $team) {
$team_livesync_exists =
await Team::genLiveSyncExists($team->getId(), "fbctf");
if ($team_livesync_exists === true) {
$team_livesync_key =
await Team::genGetLiveSyncKey($team->getId(), "fbctf");
$teams_array[$team->getId()] = strval($team_livesync_key);
}
}
if ($livesync_enabled->getValue() === '1' &&
hash_equals(
strval($livesync_auth_key->getValue()),
strval($input_auth_key),
)) {

$livesync_enabled_awaits = Map {
'all_teams' => Team::genAllTeams(),
'all_scores' => ScoreLog::genAllScores(),
'all_hints' => HintLog::genAllHints(),
'all_levels' => Level::genAllLevels(),
};
$livesync_enabled_awaits_results =
await \HH\Asio\m($livesync_enabled_awaits);
$all_teams = $livesync_enabled_awaits_results['all_teams'];
invariant(
is_array($all_teams),
'all_teams should be an array and not null',
);

$all_scores = $livesync_enabled_awaits_results['all_scores'];
invariant(
is_array($all_scores),
'all_scores should be an array and not null',
);

$all_hints = $livesync_enabled_awaits_results['all_hints'];
invariant(
is_array($all_hints),
'all_hints should be an array and not null',
);

$scores_array = array();
$scored_teams = array();
$all_scores = await ScoreLog::genAllScores();
foreach ($all_scores as $score) {
if (in_array($score->getTeamId(), array_keys($teams_array)) === false) {
continue;
$all_levels = $livesync_enabled_awaits_results['all_levels'];
invariant(
is_array($all_levels),
'all_levels should be an array and not null',
);

$data = array();
$teams_array = array();
$team_livesync_exists = Map {};
$team_livesync_key = Map {};
foreach ($all_teams as $team) {
$team_id = $team->getId();
$team_livesync_exists->add(
Pair {$team_id, Team::genLiveSyncExists($team_id, "fbctf")},
);
}
$scores_array[$score->getLevelId()][$teams_array[$score->getTeamId()]]['timestamp'] =
$score->getTs();
$scores_array[$score->getLevelId()][$teams_array[$score->getTeamId()]]['capture'] =
true;
$scores_array[$score->getLevelId()][$teams_array[$score->getTeamId()]]['hint'] =
false;
$scored_teams[$score->getLevelId()][] = $score->getTeamId();
}
$all_hints = await HintLog::genAllHints();
foreach ($all_hints as $hint) {
if ($hint->getPenalty()) {
if (in_array($hint->getTeamId(), array_keys($teams_array)) ===
$team_livesync_exists_results = await \HH\Asio\m($team_livesync_exists);
foreach ($team_livesync_exists_results as $team_id => $livesync_exists) {
if ($livesync_exists === true) {
$team_livesync_key->add(
Pair {$team_id, Team::genGetLiveSyncKey($team_id, "fbctf")},
);
}
}
$team_livesync_key_results = await \HH\Asio\m($team_livesync_key);
$teams_array = $team_livesync_key_results->toArray();

$scores_array = array();
$scored_teams = array();

foreach ($all_scores as $score) {
if (in_array($score->getTeamId(), array_keys($teams_array)) ===
false) {
continue;
}
$scores_array[$hint->getLevelId()][$teams_array[$hint->getTeamId()]]['hint'] =
$scores_array[$score->getLevelId()][$teams_array[$score->getTeamId()]]['timestamp'] =
$score->getTs();
$scores_array[$score->getLevelId()][$teams_array[$score->getTeamId()]]['capture'] =
true;
if (in_array(
$hint->getTeamId(),
$scored_teams[$hint->getLevelId()],
) ===
false) {
$scores_array[$hint->getLevelId()][$teams_array[$hint->getTeamId()]]['capture'] =
false;
$scores_array[$hint->getLevelId()][$teams_array[$hint->getTeamId()]]['timestamp'] =
$hint->getTs();
$scores_array[$score->getLevelId()][$teams_array[$score->getTeamId()]]['hint'] =
false;
$scored_teams[$score->getLevelId()][] = $score->getTeamId();
}
foreach ($all_hints as $hint) {
if ($hint->getPenalty()) {
if (in_array($hint->getTeamId(), array_keys($teams_array)) ===
false) {
continue;
}
$scores_array[$hint->getLevelId()][$teams_array[$hint->getTeamId()]]['hint'] =
true;
if (in_array(
$hint->getTeamId(),
$scored_teams[$hint->getLevelId()],
) ===
false) {
$scores_array[$hint->getLevelId()][$teams_array[$hint->getTeamId()]]['capture'] =
false;
$scores_array[$hint->getLevelId()][$teams_array[$hint->getTeamId()]]['timestamp'] =
$hint->getTs();
}
}
}
}

$levels_array = array();
$all_levels = await Level::genAllLevels();
foreach ($all_levels as $level) {
$entity = await Country::gen($level->getEntityId());
$category = await Category::genSingleCategory($level->getCategoryId());
if (array_key_exists($level->getId(), $scores_array)) {
$score_level_array = $scores_array[$level->getId()];
} else {
$score_level_array = array();
$levels_array = array();
$entities = Map {};
$categories = Map {};
foreach ($all_levels as $level) {
$level_id = $level->getId();
$entities->add(Pair {$level_id, Country::gen($level->getEntityId())});
$categories->add(
Pair {
$level_id,
Category::genSingleCategory($level->getCategoryId()),
},
);
}
$one_level = array(
'active' => $level->getActive(),
'type' => $level->getType(),
'title' => $level->getTitle(),
'description' => $level->getDescription(),
'entity_iso_code' => $entity->getIsoCode(),
'category' => $category->getCategory(),
'points' => $level->getPoints(),
'bonus' => $level->getBonusFix(),
'bonus_dec' => $level->getBonusDec(),
'penalty' => $level->getPenalty(),
'teams' => $score_level_array,
$entities_results = await \HH\Asio\m($entities);
invariant(
$entities_results instanceof Map,
'entities_results should of type Map and not null',
);
$levels_array[] = $one_level;
}

$data = $levels_array;
$categories_results = await \HH\Asio\m($categories);
invariant(
$categories_results instanceof Map,
'categories_results should of type Map and not null',
);

foreach ($all_levels as $level) {
$level_id = $level->getId();
$entity = $entities_results->get($level_id);
invariant(
$entity instanceof Country,
'entity should of type Country and not null',
);

$category = $categories_results->get($level_id);
invariant(
$category instanceof Category,
'category should of type Category and not null',
);

if (array_key_exists($level->getId(), $scores_array)) {
$score_level_array = $scores_array[$level_id];
} else {
$score_level_array = array();
}
$one_level = array(
'active' => $level->getActive(),
'type' => $level->getType(),
'title' => $level->getTitle(),
'description' => $level->getDescription(),
'entity_iso_code' => $entity->getIsoCode(),
'category' => $category->getCategory(),
'points' => $level->getPoints(),
'bonus' => $level->getBonusFix(),
'bonus_dec' => $level->getBonusDec(),
'penalty' => $level->getPenalty(),
'teams' => $score_level_array,
);
$levels_array[] = $one_level;
}

$data = $levels_array;
} else if ($livesync_enabled->getValue() === '0') {
$data['error'] = tr(
'LiveSync is disabled, please contact the administrator for access.',
);
} else if (strval($input_auth_key) !==
strval($livesync_auth_key->getValue())) {
$data['error'] =
tr(
'LiveSync auth key is invalid, please contact the administrator for access.',
);
} else {
$data['error'] = tr(
'LiveSync failed, please contact the administrator for assistance.',
);
}
$this->jsonSend($data);
}

Expand Down
4 changes: 4 additions & 0 deletions src/scripts/liveimport.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ class LiveSyncImport {
$json = await self::genDownloadData($url, $check_certificates);
$data = json_decode($json);
if (empty($data) === false) {
if ((!is_array($data)) && (property_exists($data, 'error'))) {
self::debug(true, $url, '!!!', strval($data->error));
continue;
}
foreach ($data as $level) {
$mandatories_set = await self::genMandatoriesSet($level);
if ($mandatories_set === false) {
Expand Down

0 comments on commit e880251

Please sign in to comment.