Compare commits

..

8 Commits

Author SHA1 Message Date
Dan Brown
2558ea8931 Updated version for release v0.26.4 2019-08-06 21:42:09 +01:00
Dan Brown
ac0f47a4b2 Merge branch 'v0.26' into release 2019-08-06 21:41:06 +01:00
Dan Brown
f417675b1d Prevented normal users from changing own email
To address #1542

Updates to only allow email changes by users with the users-manage role
permission.
2019-08-06 21:29:42 +01:00
Dan Brown
2955f414dd Added iframe JS and data url escaping
Related to #1531
2019-08-06 21:08:24 +01:00
Dan Brown
4de719b325 Prevented potential apache image dir listing
Closes #1545
2019-08-06 20:35:27 +01:00
Dan Brown
4f16129869 Updated version for release v0.26.3 2019-07-10 20:21:22 +01:00
Dan Brown
64a8037fdd Merge branch 'v0.26' into release 2019-07-10 20:19:54 +01:00
Dan Brown
c732970f6e Hardened page content script escaping
Increased range of tests to cover.

Fixes #1531
2019-07-10 20:17:22 +01:00
10 changed files with 140 additions and 7 deletions

View File

@@ -760,13 +760,19 @@ class EntityRepo
$xPath = new DOMXPath($doc);
// Remove standard script tags
$scriptElems = $xPath->query('//body//*//script');
$scriptElems = $xPath->query('//script');
foreach ($scriptElems as $scriptElem) {
$scriptElem->parentNode->removeChild($scriptElem);
}
// Remove data or JavaScript iFrames
$badIframes = $xPath->query('//*[contains(@src, \'data:\')] | //*[contains(@src, \'javascript:\')]');
foreach ($badIframes as $badIframe) {
$badIframe->parentNode->removeChild($badIframe);
}
// Remove 'on*' attributes
$onAttributes = $xPath->query('//body//*/@*[starts-with(name(), \'on\')]');
$onAttributes = $xPath->query('//@*[starts-with(name(), \'on\')]');
foreach ($onAttributes as $attr) {
/** @var \DOMAttr $attr*/
$attrName = $attr->nodeName;

View File

@@ -146,7 +146,12 @@ class UserController extends Controller
]);
$user = $this->userRepo->getById($id);
$user->fill($request->all());
$user->fill($request->except(['email']));
// Email updates
if (userCan('users-manage') && $request->filled('email')) {
$user->email = $request->get('email');
}
// Role updates
if (userCan('users-manage') && $request->filled('roles')) {

View File

@@ -1,6 +1,6 @@
<IfModule mod_rewrite.c>
<IfModule mod_negotiation.c>
Options -MultiViews
Options -MultiViews -Indexes
</IfModule>
RewriteEngine On

View File

@@ -1,2 +1,3 @@
*
!.gitignore
!.gitignore
!.htaccess

1
public/uploads/.htaccess Executable file
View File

@@ -0,0 +1 @@
Options -Indexes

View File

@@ -1,6 +1,7 @@
<input type="text" id="{{ $name }}" name="{{ $name }}"
@if($errors->has($name)) class="text-neg" @endif
@if(isset($placeholder)) placeholder="{{$placeholder}}" @endif
@if(isset($disabled) && $disabled) disabled="disabled" @endif
@if(isset($tabindex)) tabindex="{{$tabindex}}" @endif
@if(isset($model) || old($name)) value="{{ old($name) ? old($name) : $model->$name}}" @endif>
@if($errors->has($name))

View File

@@ -19,7 +19,7 @@
<div>
@if($authMethod !== 'ldap' || userCan('users-manage'))
<label for="email">{{ trans('auth.email') }}</label>
@include('form.text', ['name' => 'email'])
@include('form.text', ['name' => 'email', 'disabled' => !userCan('users-manage')])
@endif
</div>
</div>

View File

@@ -80,10 +80,66 @@ class PageContentTest extends TestCase
$page->save();
$pageView = $this->get($page->getUrl());
$pageView->assertStatus(200);
$pageView->assertDontSee($script);
$pageView->assertSee('abc123abc123');
}
public function test_more_complex_content_script_escaping_scenarios()
{
$checks = [
"<p>Some script</p><script>alert('cat')</script>",
"<div><div><div><div><p>Some script</p><script>alert('cat')</script></div></div></div></div>",
"<p>Some script<script>alert('cat')</script></p>",
"<p>Some script <div><script>alert('cat')</script></div></p>",
"<p>Some script <script><div>alert('cat')</script></div></p>",
"<p>Some script <script><div>alert('cat')</script><script><div>alert('cat')</script></p><script><div>alert('cat')</script>",
];
$this->asEditor();
$page = Page::first();
foreach ($checks as $check) {
$page->html = $check;
$page->save();
$pageView = $this->get($page->getUrl());
$pageView->assertStatus(200);
$pageView->assertElementNotContains('.page-content', '<script>');
$pageView->assertElementNotContains('.page-content', '</script>');
}
}
public function test_iframe_js_and_base64_urls_are_removed()
{
$checks = [
'<iframe src="javascript:alert(document.cookie)"></iframe>',
'<iframe SRC=" javascript: alert(document.cookie)"></iframe>',
'<iframe src="data:text/html;base64,PHNjcmlwdD5hbGVydCgnaGVsbG8nKTwvc2NyaXB0Pg==" frameborder="0"></iframe>',
'<iframe src=" data:text/html;base64,PHNjcmlwdD5hbGVydCgnaGVsbG8nKTwvc2NyaXB0Pg==" frameborder="0"></iframe>',
];
$this->asEditor();
$page = Page::first();
foreach ($checks as $check) {
$page->html = $check;
$page->save();
$pageView = $this->get($page->getUrl());
$pageView->assertStatus(200);
$pageView->assertElementNotContains('.page-content', '<iframe>');
$pageView->assertElementNotContains('.page-content', '</iframe>');
$pageView->assertElementNotContains('.page-content', 'src=');
$pageView->assertElementNotContains('.page-content', 'javascript:');
$pageView->assertElementNotContains('.page-content', 'data:');
$pageView->assertElementNotContains('.page-content', 'base64');
}
}
public function test_page_inline_on_attributes_removed_by_default()
{
$this->asEditor();
@@ -93,10 +149,36 @@ class PageContentTest extends TestCase
$page->save();
$pageView = $this->get($page->getUrl());
$pageView->assertStatus(200);
$pageView->assertDontSee($script);
$pageView->assertSee('<p>Hello</p>');
}
public function test_more_complex_inline_on_attributes_escaping_scenarios()
{
$checks = [
'<p onclick="console.log(\'test\')">Hello</p>',
'<div>Lorem ipsum dolor sit amet.</div><p onclick="console.log(\'test\')">Hello</p>',
'<div>Lorem ipsum dolor sit amet.<p onclick="console.log(\'test\')">Hello</p></div>',
'<div><div><div><div>Lorem ipsum dolor sit amet.<p onclick="console.log(\'test\')">Hello</p></div></div></div></div>',
'<div onclick="console.log(\'test\')">Lorem ipsum dolor sit amet.</div><p onclick="console.log(\'test\')">Hello</p><div></div>',
'<a a="<img src=1 onerror=\'alert(1)\'> ',
];
$this->asEditor();
$page = Page::first();
foreach ($checks as $check) {
$page->html = $check;
$page->save();
$pageView = $this->get($page->getUrl());
$pageView->assertStatus(200);
$pageView->assertElementNotContains('.page-content', 'onclick');
}
}
public function test_page_content_scripts_show_when_configured()
{
$this->asEditor();

View File

@@ -119,6 +119,43 @@ class RolesTest extends BrowserKitTest
$this->actingAs($this->user)->visit('/')->dontSee($usersLink);
}
public function test_user_cannot_change_email_unless_they_have_manage_users_permission()
{
$userProfileUrl = '/settings/users/' . $this->user->id;
$originalEmail = $this->user->email;
$this->actingAs($this->user);
$this->visit($userProfileUrl)
->assertResponseOk()
->seeElement('input[name=email][disabled]');
$this->put($userProfileUrl, [
'name' => 'my_new_name',
'email' => 'new_email@example.com',
]);
$this->seeInDatabase('users', [
'id' => $this->user->id,
'email' => $originalEmail,
'name' => 'my_new_name',
]);
$this->giveUserPermissions($this->user, ['users-manage']);
$this->visit($userProfileUrl)
->assertResponseOk()
->dontSeeElement('input[name=email][disabled]')
->seeElement('input[name=email]');
$this->put($userProfileUrl, [
'name' => 'my_new_name_2',
'email' => 'new_email@example.com',
]);
$this->seeInDatabase('users', [
'id' => $this->user->id,
'email' => 'new_email@example.com',
'name' => 'my_new_name_2',
]);
}
public function test_user_roles_manage_permission()
{
$this->actingAs($this->user)->visit('/settings/roles')

View File

@@ -1 +1 @@
v0.26.2
v0.26.4