Compare commits

..

6 Commits

Author SHA1 Message Date
Jacek
35aa00a457 docs: changelog ver_0.336 2026-03-12 09:30:56 +01:00
Jacek
31426d763e security: faza 3 - error handling w krytycznych sciezkach
- cron.php: przywrocono E_WARNING i E_DEPRECATED (wyciszono tylko E_NOTICE i E_STRICT)
- IntegrationsRepository: try-catch po zapisie tokenow Apilo - blad DB nie sklada false po cichu
- ProductRepository/ArticleRepository: error_log gdy safeUnlink wykryje sciezke poza upload/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 09:30:23 +01:00
Jacek
c4ce330d01 build: ver_0.335 - safeUnlink path traversal, XSS escaping szablony artykulow 2026-03-12 09:23:29 +01:00
Jacek
e92c14c9ab docs: changelog ver_0.335 2026-03-12 09:23:02 +01:00
Jacek
394d09d3e1 security: faza 2 - safeUnlink() i escaping XSS w szablonach artykulow
- ProductRepository: dodano safeUnlink() z walidacja realpath() - zapobiega path traversal
- ArticleRepository: to samo, 4 metody usuwania plikow zaktualizowane
- templates/articles/article-full.php: htmlspecialchars() na tytule, SERVER_NAME i $url
- templates/articles/article-entry.php: htmlspecialchars() na tytule i $url (3 miejsca)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 09:22:32 +01:00
Jacek
564b4eab40 build: ver_0.334 - poprawki bezpieczenstwa, usunieto RedBeanPHP 2026-03-12 09:19:33 +01:00
14 changed files with 258 additions and 150 deletions

View File

@@ -318,9 +318,7 @@ class ArticleRepository
if (is_array($results)) {
foreach ($results as $row) {
if (file_exists('../' . $row['src'])) {
unlink('../' . $row['src']);
}
$this->safeUnlink($row['src']);
}
}
@@ -337,9 +335,7 @@ class ArticleRepository
if (is_array($results)) {
foreach ($results as $row) {
if (file_exists('../' . $row['src'])) {
unlink('../' . $row['src']);
}
$this->safeUnlink($row['src']);
}
}
@@ -819,9 +815,7 @@ class ArticleRepository
$results = $this->db->select('pp_articles_files', '*', ['article_id' => null]);
if (is_array($results)) {
foreach ($results as $row) {
if (file_exists('../' . $row['src'])) {
unlink('../' . $row['src']);
}
$this->safeUnlink($row['src']);
}
}
@@ -836,15 +830,31 @@ class ArticleRepository
$results = $this->db->select('pp_articles_images', '*', ['article_id' => null]);
if (is_array($results)) {
foreach ($results as $row) {
if (file_exists('../' . $row['src'])) {
unlink('../' . $row['src']);
}
$this->safeUnlink($row['src']);
}
}
$this->db->delete('pp_articles_images', ['article_id' => null]);
}
/**
* Usuwa plik z dysku tylko jeśli ścieżka pozostaje wewnątrz katalogu upload/.
* Zapobiega path traversal przy danych z bazy.
*/
private function safeUnlink(string $src): void
{
$base = realpath('../upload');
if (!$base) {
return;
}
$full = realpath('../' . ltrim($src, '/'));
if ($full && strpos($full, $base . DIRECTORY_SEPARATOR) === 0 && is_file($full)) {
unlink($full);
} elseif ($full) {
error_log( '[shopPRO] safeUnlink: ścieżka poza upload/: ' . $src );
}
}
/**
* Pobiera artykuly opublikowane w podanym zakresie dat.
*/

View File

@@ -159,10 +159,15 @@ class IntegrationsRepository
if ( empty( $response['accessToken'] ) )
return false;
$this->saveSetting( 'apilo', 'access-token', $response['accessToken'] );
$this->saveSetting( 'apilo', 'refresh-token', $response['refreshToken'] );
$this->saveSetting( 'apilo', 'access-token-expire-at', $response['accessTokenExpireAt'] );
$this->saveSetting( 'apilo', 'refresh-token-expire-at', $response['refreshTokenExpireAt'] );
try {
$this->saveSetting( 'apilo', 'access-token', $response['accessToken'] );
$this->saveSetting( 'apilo', 'refresh-token', $response['refreshToken'] );
$this->saveSetting( 'apilo', 'access-token-expire-at', $response['accessTokenExpireAt'] );
$this->saveSetting( 'apilo', 'refresh-token-expire-at', $response['refreshTokenExpireAt'] );
} catch ( \Exception $e ) {
error_log( '[shopPRO] Apilo: błąd zapisu tokenów: ' . $e->getMessage() );
return false;
}
return true;
}

View File

@@ -1601,9 +1601,7 @@ class ProductRepository
$results = $this->db->select( 'pp_shop_products_files', '*', [ 'AND' => [ 'product_id' => $productId, 'to_delete' => 1 ] ] );
if ( is_array( $results ) ) {
foreach ( $results as $row ) {
if ( file_exists( '../' . $row['src'] ) ) {
unlink( '../' . $row['src'] );
}
$this->safeUnlink( $row['src'] );
}
}
$this->db->delete( 'pp_shop_products_files', [ 'AND' => [ 'product_id' => $productId, 'to_delete' => 1 ] ] );
@@ -1614,9 +1612,7 @@ class ProductRepository
$results = $this->db->select( 'pp_shop_products_images', '*', [ 'AND' => [ 'product_id' => $productId, 'to_delete' => 1 ] ] );
if ( is_array( $results ) ) {
foreach ( $results as $row ) {
if ( file_exists( '../' . $row['src'] ) ) {
unlink( '../' . $row['src'] );
}
$this->safeUnlink( $row['src'] );
}
}
$this->db->delete( 'pp_shop_products_images', [ 'AND' => [ 'product_id' => $productId, 'to_delete' => 1 ] ] );
@@ -2125,14 +2121,30 @@ class ProductRepository
$results = $this->db->select( 'pp_shop_products_images', '*', [ 'product_id' => null ] );
if ( is_array( $results ) ) {
foreach ( $results as $row ) {
if ( file_exists( '../' . $row['src'] ) ) {
unlink( '../' . $row['src'] );
}
$this->safeUnlink( $row['src'] );
}
}
$this->db->delete( 'pp_shop_products_images', [ 'product_id' => null ] );
}
/**
* Usuwa plik z dysku tylko jeśli ścieżka pozostaje wewnątrz katalogu upload/.
* Zapobiega path traversal przy danych z bazy.
*/
private function safeUnlink(string $src): void
{
$base = realpath('../upload');
if (!$base) {
return;
}
$full = realpath('../' . ltrim($src, '/'));
if ($full && strpos($full, $base . DIRECTORY_SEPARATOR) === 0 && is_file($full)) {
unlink($full);
} elseif ($full) {
error_log( '[shopPRO] safeUnlink: ścieżka poza upload/: ' . $src );
}
}
/**
* Oznacza plik do usunięcia.
*/

View File

@@ -1,5 +1,5 @@
<?php
error_reporting( E_ALL ^ E_NOTICE ^ E_STRICT ^ E_WARNING ^ E_DEPRECATED );
error_reporting( E_ALL ^ E_NOTICE ^ E_STRICT );
function __autoload_my_classes( $classname )
{

View File

@@ -4,6 +4,24 @@ Logi zmian z migracji na Domain-Driven Architecture. Najnowsze na gorze.
---
## ver. 0.336 (2026-03-12) - Poprawki bezpieczeństwa: error handling w krytycznych ścieżkach
- **FIX**: `cron.php` — przywrócono `E_WARNING` i `E_DEPRECATED` (wyciszano je od zawsze, ukrywając potencjalne błędy)
- **FIX**: `IntegrationsRepository::apiloAuthorize()` — try-catch po zapisie tokenów Apilo; błąd DB logowany i zwraca `false` zamiast cicho kontynuować
- **FIX**: `ProductRepository::safeUnlink()``error_log()` gdy ścieżka istnieje ale jest poza `upload/`
- **FIX**: `ArticleRepository::safeUnlink()` — to samo
---
## ver. 0.335 (2026-03-12) - Poprawki bezpieczeństwa: path traversal i XSS w szablonach
- **SECURITY**: `ProductRepository` — dodano `safeUnlink()` z walidacją `realpath()` zapobiegającą path traversal; użyta w `cleanupDeletedFiles()`, `cleanupDeletedImages()`, `deleteNonassignedImages()`
- **SECURITY**: `ArticleRepository` — to samo; użyta w `deleteMarkedImages()`, `deleteMarkedFiles()`, `deleteNonassignedFiles()`, `deleteNonassignedImages()`
- **SECURITY**: `templates/articles/article-full.php``htmlspecialchars()` na tytule artykułu, `$_SERVER['SERVER_NAME']` i `$url` w linkach social media
- **SECURITY**: `templates/articles/article-entry.php``htmlspecialchars()` na tytule i `$url` (3 miejsca: href, title, alt)
---
## ver. 0.334 (2026-03-12) - Poprawki bezpieczeństwa: debug log, SQL, RedBeanPHP
- **SECURITY**: `ShopOrderController::paymentStatusTpay()` — usunięto `file_put_contents('tpay.txt', ...)` który logował pełne dane POST/GET płatności do publicznego pliku

View File

@@ -2,11 +2,12 @@
<div class="col-12 col-md-6 ">
<div class="article-entry">
<? $this -> article['language']['seo_link'] ? $url = $this -> article['language']['seo_link'] : $url = 'a-' . $this -> article['id'] . '-' . \Shared\Helpers\Helpers::seo( $this -> article['language']['title'] );?>
<? $safeTitle = htmlspecialchars( $this -> article['language']['title'], ENT_QUOTES, 'UTF-8' ); $safeUrl = htmlspecialchars( $url, ENT_QUOTES, 'UTF-8' );?>
<div class="blog-image">
<a href="/<?= $url;?>" title="<?= $this -> article['language']['title'];?>" <? if ( $this -> article['language']['noindex'] ):?>rel="nofollow"<? endif;?>> <img src="<?= \front\Views\Articles::getImage( $this -> article );?>" alt="<?= $this -> article['language']['title'];?>"></a>
<a href="/<?= $safeUrl;?>" title="<?= $safeTitle;?>" <? if ( $this -> article['language']['noindex'] ):?>rel="nofollow"<? endif;?>> <img src="<?= \front\Views\Articles::getImage( $this -> article );?>" alt="<?= $safeTitle;?>"></a>
</div>
<h3 class="article-title">
<a href="/<? if ( \Shared\Helpers\Helpers::get_session( 'current-lang' ) != ( new \Domain\Languages\LanguagesRepository( $GLOBALS['mdb'] ) )->defaultLanguage() ) echo \Shared\Helpers\Helpers::get_session( 'current-lang' ) . '/';?><?= $url;?>" title="<?= $this -> article['language']['title'];?>" <? if ( $this -> article['language']['noindex'] ):?>rel="nofollow"<? endif;?>><?= $this -> article['language']['title'];?></a>
<a href="/<? if ( \Shared\Helpers\Helpers::get_session( 'current-lang' ) != ( new \Domain\Languages\LanguagesRepository( $GLOBALS['mdb'] ) )->defaultLanguage() ) echo \Shared\Helpers\Helpers::get_session( 'current-lang' ) . '/';?><?= $safeUrl;?>" title="<?= $safeTitle;?>" <? if ( $this -> article['language']['noindex'] ):?>rel="nofollow"<? endif;?>><?= $safeTitle;?></a>
</h3>
<div class="date-add"><?= date( 'd.m.Y', strtotime( $this -> article['date_add'] ) );?></div>
<div class="entry">
@@ -32,6 +33,6 @@
}
?>
</div>
<a href="/<?= $url;?>" class="btn btn-success" title="<?= $this -> article['language']['title'];?>" <? if ( $this -> article['language']['noindex'] ):?>rel="nofollow"<? endif;?>><span class="text"><?= $lang['wiecej'];?></span></a>
<a href="/<?= $safeUrl;?>" class="btn btn-success" title="<?= $safeTitle;?>" <? if ( $this -> article['language']['noindex'] ):?>rel="nofollow"<? endif;?>><span class="text"><?= $lang['wiecej'];?></span></a>
</div>
</div>

View File

@@ -8,24 +8,26 @@ $text = \front\Views\Articles::generateHeadersIds( $text );
$this -> article['language']['seo_link'] ? $url = $this -> article['language']['seo_link'] : $url = 'a-' . $this -> article['id'] . '-' . \Shared\Helpers\Helpers::seo( $this -> article['language']['title'] );
if ( $this -> article['show_title'] )
echo '<h3 class="article-title">' . $this -> article['language']['title'] . '</h3>';
echo '<h3 class="article-title">' . htmlspecialchars( $this -> article['language']['title'], ENT_QUOTES, 'UTF-8' ) . '</h3>';
if ( $this -> article['social_icons'] ):
$safeHost = htmlspecialchars( $_SERVER['SERVER_NAME'], ENT_QUOTES, 'UTF-8' );
$safeUrl = htmlspecialchars( $url, ENT_QUOTES, 'UTF-8' );
?>
<div class="social-icons">
<a class="fb" href="http://www.facebook.com/sharer.php?u=http://www.<?= $_SERVER['SERVER_NAME'];?>/<?= $url;?>" onclick="javascript:window.open(this.href, '', 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=600,width=600');return false;" title="facebook" target="_blank" rel="nofollow">
<a class="fb" href="http://www.facebook.com/sharer.php?u=http://www.<?= $safeHost;?>/<?= $safeUrl;?>" onclick="javascript:window.open(this.href, '', 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=600,width=600');return false;" title="facebook" target="_blank" rel="nofollow">
<img src="/images/system/logo-facebook.jpg" alt="facebook">
</a>
<a class="pinterest" href="http://pinterest.com/pin/create/button/?url=http://www.<?= $_SERVER['SERVER_NAME'];?>/<?= $url;?>" onclick="javascript:window.open(this.href, '', 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=600,width=600');return false;" title="pinterest" target="_blank" rel="nofollow">
<a class="pinterest" href="http://pinterest.com/pin/create/button/?url=http://www.<?= $safeHost;?>/<?= $safeUrl;?>" onclick="javascript:window.open(this.href, '', 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=600,width=600');return false;" title="pinterest" target="_blank" rel="nofollow">
<img src="/images/system/logo-pinterest.jpg" alt="pinterest">
</a>
<a class="twitter" href="http://twitter.com/share?url=http://www.<?= $_SERVER['SERVER_NAME'];?>/<?= $url;?>" onclick="javascript:window.open(this.href, '', 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=450,width=600');return false;" title="twitter" target="_blank" rel="nofollow">
<a class="twitter" href="http://twitter.com/share?url=http://www.<?= $safeHost;?>/<?= $safeUrl;?>" onclick="javascript:window.open(this.href, '', 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=450,width=600');return false;" title="twitter" target="_blank" rel="nofollow">
<img src="/images/system/logo-twitter.jpg" alt="twitter">
</a>
<a class="linkedin" href="http://www.linkedin.com/shareArticle?mini=true&amp;url=http://www.<?= $_SERVER['SERVER_NAME'];?>/<?= $url;?>" onclick="javascript:window.open(this.href, '', 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=500,width=850');return false;" title="linked in" target="_blank" rel="nofollow">
<a class="linkedin" href="http://www.linkedin.com/shareArticle?mini=true&amp;url=http://www.<?= $safeHost;?>/<?= $safeUrl;?>" onclick="javascript:window.open(this.href, '', 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=500,width=850');return false;" title="linked in" target="_blank" rel="nofollow">
<img src="/images/system/logo-linkedin.jpg" alt="linkedin">
</a>
<a class="gp" href="https://plus.google.com/share?url=http://www.<?= $_SERVER['SERVER_NAME'];?>/<?= $url;?>" onclick="javascript:window.open(this.href, '', 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=600,width=600');return false;" title="google+" target="_blank" rel="nofollow">
<a class="gp" href="https://plus.google.com/share?url=http://www.<?= $safeHost;?>/<?= $safeUrl;?>" onclick="javascript:window.open(this.href, '', 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=600,width=600');return false;" title="google+" target="_blank" rel="nofollow">
<img src="/images/system/logo-google.jpg" alt="google+">
</a>
</div>

BIN
updates/0.30/ver_0.334.zip Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
F: ../libraries/rb.php

View File

@@ -0,0 +1,27 @@
{
"changelog": "Poprawki bezpieczenstwa: usunieto debug log tpay, naprawa SQL w IntegrationsRepository, usunieto RedBeanPHP",
"version": "0.334",
"files": {
"added": [
],
"deleted": [
"libraries/rb.php"
],
"modified": [
"admin/index.php",
"autoload/Domain/Integrations/IntegrationsRepository.php",
"autoload/front/Controllers/ShopOrderController.php",
"index.php",
"templates/shop-basket/summary-view.php"
]
},
"checksum_zip": "sha256:c009407c5b941d98b2777b6f7335f6587c93900d02df4441801e314b3deb7fb0",
"sql": [
],
"date": "2026-03-12",
"directories_deleted": [
]
}

BIN
updates/0.30/ver_0.335.zip Normal file

Binary file not shown.

View File

@@ -0,0 +1,26 @@
{
"changelog": "Poprawki bezpieczenstwa: safeUnlink() z walidacja realpath(), escaping XSS w szablonach artykulow",
"version": "0.335",
"files": {
"added": [
],
"deleted": [
],
"modified": [
"autoload/Domain/Article/ArticleRepository.php",
"autoload/Domain/Product/ProductRepository.php",
"templates/articles/article-entry.php",
"templates/articles/article-full.php"
]
},
"checksum_zip": "sha256:2347ff654312f34e22b19cd89b229beabb039a3c253b047df07362d5c8393527",
"sql": [
],
"date": "2026-03-12",
"directories_deleted": [
]
}

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
<?
$current_ver = 333;
$current_ver = 335;
for ($i = 1; $i <= $current_ver; $i++)
{