*

aga2442

  • **
  • 25 posts
[Security] 2 unauthenticated SQL injection + 1 stored XSS in OSClass 8.3.1 core (public profile page / TinyMCE description)

Hello,

We're running OSClass 8.3.1 (your fork) with the Epsilon theme in production and found three security issues while doing an internal security review. All three are reproducible in the stock downloaded source (we did not modify these files), and the affected code path is also live on your own public demo (https://epsilon.mb-themes.com/user/profile/samhaghard) — we have NOT attempted any injection or exploitation against your demo/production site, we only confirmed the URL pattern exists there by normal browsing. All testing was done against our own local copy of the source.

Environment: OSClass 8.3.1 (OsclassPoint fork), Epsilon theme, PHP 8.x.

────────────────────────────────────
1) Unauthenticated SQL injection via `sPattern` on the public user-profile page
────────────────────────────────────
File: oc-includes/osclass/model/Item.php, function findUserItems(), lines 686-692
File: oc-includes/osclass/controller/user-non-secure.php, lines 253-254

`$options['pattern'] = $params['sPattern'];` is passed with no sanitization into:

  sprintf("MATCH(d.s_title, d.s_description) AGAINST('%s' IN BOOLEAN MODE)", $pattern)
  sprintf("lower(concat(d.s_title, d.s_description)) like '%%%s%%'", strtolower(trim((string)$pattern)))

Neither branch calls $this->dao->escapeStr() before interpolating $pattern. For comparison, the main search page (Search.php) DOES escape the same sPattern parameter with escapeStr() — that step is simply missing in this controller.

This endpoint is served by the public "user's other listings" page (osc_user_public_profile_url(), e.g. /user/profile/<username>) and requires NO authentication.

PoC direction: a request like
  /user/profile/<username>?sPattern=test' UNION SELECT ... --
against a rewrite-disabled or query-string-accessible route reaching this controller/action should demonstrate the injection. We stopped short of running this against any live instance; testing on your local dev copy of Item.php::findUserItems() with a crafted sPattern will confirm it (the missing escapeStr() call is visible directly in the source).

Suggested fix: escape $pattern with $this->dao->escapeStr($pattern) before interpolating it into any of the three sprintf() branches, matching what Search.php already does.

────────────────────────────────────
2) Unauthenticated blind SQL injection via `sOrder` (ORDER BY column injection)
────────────────────────────────────
File: oc-includes/osclass/controller/user-non-secure.php, lines 294-295
File: oc-includes/osclass/model/Item.php, lines 892-896
File: oc-includes/osclass/classes/database/DBCommandClass.php, orderBy(), lines 666-676

`$options['order_column'] = $params['sOrder'];` (no whitelist) flows into:

  Item.php:896: $this->dao->orderBy($order_column, $order_direction);
  DBCommandClass.php:670-673: only $direction is validated against ASC/DESC/RAND(); $orderby (the column name) is concatenated as-is:
    $this->aOrderby[] = $orderby . $direction;

This string is later imploded directly into the raw ORDER BY clause. The main search controller (search.php) validates the sort column against Search::getAllowedColumnsForSorting() before use — that whitelist call is missing in user.php / user-non-secure.php.

Same entry point as issue #1 (public user-profile page, no login required). This allows a time-based blind SQL injection via a crafted ORDER BY subquery in the sOrder parameter (e.g. a subquery using SLEEP()).

Suggested fix: validate order_column against the same allowed-columns whitelist used in search.php before passing it to orderBy(), falling back to the default column if it doesn't match.

────────────────────────────────────
3) Stored XSS: TinyMCE item description completely bypasses HTMLPurifier
────────────────────────────────────
File: oc-includes/osclass/ItemActions.php, line 1868
File: oc-includes/osclass/core/Params.php, getParam()/_purify(), lines 46-98

  $aItem['description'] = (osc_tinymce_items_enabled() == '1'
      ? Params::getParam('description', false, false)   // xss_check = false
      : Params::getParam('description'));                // xss_check = true (default)

When $xss_check is false, Params::_purify() returns the raw value immediately (Params.php line 81-83) and never runs it through HTMLPurifier — even though HTMLPurifier is configured with HTML.Allowed = '' (strip all tags) when it IS run. So whenever an admin enables the TinyMCE editor for item descriptions (a supported, documented core setting), every listing description is stored completely unsanitized. The theme layer then prints it unescaped by design (to preserve rich-text formatting), so any authenticated user can publish a listing containing <script> or an onerror= payload that executes for every visitor (including admins) who views the listing — classic stored XSS with a very low bar (any registered user, one listing).

Suggested fix: run HTMLPurifier even when TinyMCE is enabled, but configure a separate, restrictive HTML.Allowed whitelist (matching only tags TinyMCE itself outputs, e.g. b,i,u,strong,em,a[href],br,p,ul,li) instead of skipping purification entirely.

*

MB Themes

Re: 2 unauthenticated SQL injection + 1 stored XSS in OSClass 8.3.1 core
« Reply #1 on: September 28, 2026, 01:58:47 PM »
Thanks for feedback, will be fixed in 8.4
  To get fast support, we need following details: Detail description, URL to reproduce problem, Screenshots