Compare commits

...

7 commits

Author SHA1 Message Date
Dane Everitt
4028588621
Cache bust server JS 2020-10-03 10:06:20 -07:00
Stepan Fedotov
1cd08e2f8d
Fix XSS in server owner selection (#2441)
Co-authored-by: Stepan Fedotov <stepan@crident.com>
Co-authored-by: Sergej <me@sergiz.com>
2020-10-03 09:55:35 -07:00
Dane Everitt
dcf5cb3cd3
Update changelog 2020-07-26 11:58:27 -07:00
Dane Everitt
6d69f6ef47
Address security vulnerability when listing servers as a client 2020-07-26 11:40:48 -07:00
Dane Everitt
78514f9eb4
Disallow creating more than 5 account API keys; closes #2123
Additional fixes for https://github.com/pterodactyl/panel/security/advisories/GHSA-pjmh-7xfm-r4x9
2020-07-26 11:26:20 -07:00
Dane Everitt
7deed07cd1
Merge pull request #1907 from Spirit55555/patch-1
Fix server-description colspan
2020-04-12 10:13:26 -07:00
Anders G. Jørgensen
597196ad3e
Fix server-description colspan 2020-04-11 00:29:39 +02:00
8 changed files with 68 additions and 28 deletions

View file

@ -3,6 +3,15 @@ This file is a running track of new features and fixes to each version of the pa
This project follows [Semantic Versioning](http://semver.org) guidelines. This project follows [Semantic Versioning](http://semver.org) guidelines.
## v0.7.19 (Derelict Dermodactylus)
### Fixed
* **[Security]** Fixes XSS in the admin area's server owner selection.
## v0.7.18 (Derelict Dermodactylus)
### Fixed
* **[Security]** Re-addressed missed endpoint that would not properly limit a user account to 5 API keys.
* **[Security]** Addresses a Client API vulnerability that would allow a user to list all servers on the system ([`GHSA-6888-7f3w-92jx`](https://github.com/pterodactyl/panel/security/advisories/GHSA-6888-7f3w-92jx))
## v0.7.17 (Derelict Dermodactylus) ## v0.7.17 (Derelict Dermodactylus)
### Fixed ### Fixed
* Limited accounts to 5 API keys at a time. * Limited accounts to 5 API keys at a time.

View file

@ -82,10 +82,13 @@ class AccountKeyController extends Controller
*/ */
public function store(StoreAccountKeyRequest $request) public function store(StoreAccountKeyRequest $request)
{ {
if ($this->repository->findCountWhere(['user_id' => $request->user()->id]) >= 5) { $count = $this->repository->findCountWhere([
throw new DisplayException( ['user_id', '=', $request->user()->id],
'Cannot assign more than 5 API keys to an account.' ['key_type', '=', ApiKey::TYPE_ACCOUNT],
); ]);
if ($count >= 5) {
throw new DisplayException('Cannot assign more than 5 API keys to an account.');
} }
$this->keyService->setKeyType(ApiKey::TYPE_ACCOUNT)->handle([ $this->keyService->setKeyType(ApiKey::TYPE_ACCOUNT)->handle([

View file

@ -8,6 +8,7 @@ use Illuminate\Http\Response;
use Pterodactyl\Models\ApiKey; use Pterodactyl\Models\ApiKey;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Prologue\Alerts\AlertsMessageBag; use Prologue\Alerts\AlertsMessageBag;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Services\Api\KeyCreationService; use Pterodactyl\Services\Api\KeyCreationService;
use Pterodactyl\Http\Requests\Base\CreateClientApiKeyRequest; use Pterodactyl\Http\Requests\Base\CreateClientApiKeyRequest;
@ -73,10 +74,20 @@ class ClientApiController extends Controller
* @param \Pterodactyl\Http\Requests\Base\CreateClientApiKeyRequest $request * @param \Pterodactyl\Http\Requests\Base\CreateClientApiKeyRequest $request
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\RedirectResponse
* *
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Model\DataValidationException
*/ */
public function store(CreateClientApiKeyRequest $request): RedirectResponse public function store(CreateClientApiKeyRequest $request): RedirectResponse
{ {
$count = $this->repository->findCountWhere([
['user_id', '=', $request->user()->id],
['key_type', '=', ApiKey::TYPE_ACCOUNT],
]);
if ($count >= 5) {
throw new DisplayException('Cannot assign more than 5 API keys to an account.');
}
$allowedIps = null; $allowedIps = null;
if (! is_null($request->input('allowed_ips'))) { if (! is_null($request->input('allowed_ips'))) {
$allowedIps = json_encode(explode(PHP_EOL, $request->input('allowed_ips'))); $allowedIps = json_encode(explode(PHP_EOL, $request->input('allowed_ips')));

View file

@ -225,18 +225,23 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt
$instance->where('owner_id', $user->id); $instance->where('owner_id', $user->id);
} }
// If set to all, display all servers they can access, including // Only allow these two filters if the user is an administrator.
// those they access as an admin. If set to subuser, only return elseif ($user->root_admin && in_array($level, [ User::FILTER_LEVEL_ALL, User::FILTER_LEVEL_ADMIN ])) {
// the servers they can access because they are owner, or marked // We specifically only match admin in here. If they request all servers and are a root admin
// as a subuser of the server. // we just won't append any filters to the builder and thus they'll be able to see everything
elseif (($level === User::FILTER_LEVEL_ALL && ! $user->root_admin) || $level === User::FILTER_LEVEL_SUBUSER) { // since this will skip over that final else block.
$instance->whereIn('id', $this->getUserAccessServers($user->id)); if ($level === User::FILTER_LEVEL_ADMIN) {
$instance->whereNotIn('id', $this->getUserAccessServers($user->id));
}
} }
// If set to admin, only display the servers a user can access // If we did not match on the user being an administrator and requesting all/admin only or the user
// as an administrator (leaves out owned and subuser of). // is not an admin and requested those locked endpoints, just return all of the servers the user actually
elseif ($level === User::FILTER_LEVEL_ADMIN && $user->root_admin) { // has access to.
$instance->whereNotIn('id', $this->getUserAccessServers($user->id)); //
// @see https://github.com/pterodactyl/panel/security/advisories/GHSA-6888-7f3w-92jx
else {
$instance->whereIn('id', $this->getUserAccessServers($user->id));
} }
$instance->search($this->getSearchTerm()); $instance->search($this->getSearchTerm());

View file

@ -37,6 +37,12 @@ $(document).ready(function() {
placeholder: 'Select Additional Allocations', placeholder: 'Select Additional Allocations',
}); });
function escapeHtml(str) {
var div = document.createElement('div');
div.appendChild(document.createTextNode(str));
return div.innerHTML;
}
$('#pUserId').select2({ $('#pUserId').select2({
ajax: { ajax: {
url: Router.route('admin.users.json'), url: Router.route('admin.users.json'),
@ -56,23 +62,23 @@ $(document).ready(function() {
escapeMarkup: function (markup) { return markup; }, escapeMarkup: function (markup) { return markup; },
minimumInputLength: 2, minimumInputLength: 2,
templateResult: function (data) { templateResult: function (data) {
if (data.loading) return data.text; if (data.loading) return escapeHtml(data.text);
return '<div class="user-block"> \ return '<div class="user-block"> \
<img class="img-circle img-bordered-xs" src="https://www.gravatar.com/avatar/' + data.md5 + '?s=120" alt="User Image"> \ <img class="img-circle img-bordered-xs" src="https://www.gravatar.com/avatar/' + escapeHtml(data.md5) + '?s=120" alt="User Image"> \
<span class="username"> \ <span class="username"> \
<a href="#">' + data.name_first + ' ' + data.name_last +'</a> \ <a href="#">' + escapeHtml(data.name_first) + ' ' + escapeHtml(data.name_last) +'</a> \
</span> \ </span> \
<span class="description"><strong>' + data.email + '</strong> - ' + data.username + '</span> \ <span class="description"><strong>' + escapeHtml(data.email) + '</strong> - ' + escapeHtml(data.username) + '</span> \
</div>'; </div>';
}, },
templateSelection: function (data) { templateSelection: function (data) {
return '<div> \ return '<div> \
<span> \ <span> \
<img class="img-rounded img-bordered-xs" src="https://www.gravatar.com/avatar/' + data.md5 + '?s=120" style="height:28px;margin-top:-4px;" alt="User Image"> \ <img class="img-rounded img-bordered-xs" src="https://www.gravatar.com/avatar/' + escapeHtml(data.md5) + '?s=120" style="height:28px;margin-top:-4px;" alt="User Image"> \
</span> \ </span> \
<span style="padding-left:5px;"> \ <span style="padding-left:5px;"> \
' + data.name_first + ' ' + data.name_last + ' (<strong>' + data.email + '</strong>) \ ' + escapeHtml(data.name_first) + ' ' + escapeHtml(data.name_last) + ' (<strong>' + escapeHtml(data.email) + '</strong>) \
</span> \ </span> \
</div>'; </div>';
} }

View file

@ -259,5 +259,5 @@
@section('footer-scripts') @section('footer-scripts')
@parent @parent
{!! Theme::js('vendor/lodash/lodash.js') !!} {!! Theme::js('vendor/lodash/lodash.js') !!}
{!! Theme::js('js/admin/new-server.js') !!} {!! Theme::js('js/admin/new-server.js?v=20201003.100600') !!}
@endsection @endsection

View file

@ -83,6 +83,12 @@
@section('footer-scripts') @section('footer-scripts')
@parent @parent
<script> <script>
function escapeHtml(str) {
var div = document.createElement('div');
div.appendChild(document.createTextNode(str));
return div.innerHTML;
}
$('#pUserId').select2({ $('#pUserId').select2({
ajax: { ajax: {
url: Router.route('admin.users.json'), url: Router.route('admin.users.json'),
@ -102,14 +108,14 @@
escapeMarkup: function (markup) { return markup; }, escapeMarkup: function (markup) { return markup; },
minimumInputLength: 2, minimumInputLength: 2,
templateResult: function (data) { templateResult: function (data) {
if (data.loading) return data.text; if (data.loading) return escapeHtml(data.text);
return '<div class="user-block"> \ return '<div class="user-block"> \
<img class="img-circle img-bordered-xs" src="https://www.gravatar.com/avatar/' + data.md5 + '?s=120" alt="User Image"> \ <img class="img-circle img-bordered-xs" src="https://www.gravatar.com/avatar/' + escapeHtml(data.md5) + '?s=120" alt="User Image"> \
<span class="username"> \ <span class="username"> \
<a href="#">' + data.name_first + ' ' + data.name_last +'</a> \ <a href="#">' + escapeHtml(data.name_first) + ' ' + escapeHtml(data.name_last) +'</a> \
</span> \ </span> \
<span class="description"><strong>' + data.email + '</strong> - ' + data.username + '</span> \ <span class="description"><strong>' + escapeHtml(data.email) + '</strong> - ' + escapeHtml(data.username) + '</span> \
</div>'; </div>';
}, },
templateSelection: function (data) { templateSelection: function (data) {
@ -125,10 +131,10 @@
return '<div> \ return '<div> \
<span> \ <span> \
<img class="img-rounded img-bordered-xs" src="https://www.gravatar.com/avatar/' + data.md5 + '?s=120" style="height:28px;margin-top:-4px;" alt="User Image"> \ <img class="img-rounded img-bordered-xs" src="https://www.gravatar.com/avatar/' + escapeHtml(data.md5) + '?s=120" style="height:28px;margin-top:-4px;" alt="User Image"> \
</span> \ </span> \
<span style="padding-left:5px;"> \ <span style="padding-left:5px;"> \
' + data.name_first + ' ' + data.name_last + ' (<strong>' + data.email + '</strong>) \ ' + escapeHtml(data.name_first) + ' ' + escapeHtml(data.name_last) + ' (<strong>' + escapeHtml(data.email) + '</strong>) \
</span> \ </span> \
</div>'; </div>';
} }

View file

@ -78,7 +78,7 @@
</tr> </tr>
@if (! empty($server->description)) @if (! empty($server->description))
<tr class="server-description"> <tr class="server-description">
<td colspan="7"><p class="text-muted small no-margin">{{ str_limit($server->description, 400) }}</p></td> <td colspan="8"><p class="text-muted small no-margin">{{ str_limit($server->description, 400) }}</p></td>
</tr> </tr>
@endif @endif
@endforeach @endforeach