diff --git a/CHANGELOG.md b/CHANGELOG.md index df2786e9b..1befccbb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # CHANGELOG.md ## v0.47.0 (unreleased) +- The CRUD authentication example now accepts only local login and logout return paths, preventing crafted links from redirecting users to another site. - **Mac users:** the downloadable `sqlpage-macos.tgz` now runs natively on Apple silicon (M-series Macs) and no longer runs on Intel Macs. Homebrew remains the recommended and easiest installation method. On an Intel Mac, [install Homebrew](https://brew.sh/) if needed, then run `brew install sqlpage` (or `brew update` followed by `brew upgrade sqlpage` if you already installed it with Homebrew). Open Terminal in your existing website folder and run `sqlpage` instead of `./sqlpage.bin`; keep your SQL files, database, and `sqlpage` configuration folder in place. Intel installations may build from source and take longer; see the [macOS installation guide](https://sql-page.com/your-first-sql-website/?os=macos#download) for setup and older macOS requirements. - Updated sqlx-oldapi to v0.6.57 to fix SQL Server fallback expressions such as `ISNULL($missing, 'default')` truncating defaults or failing for date values when the bound variable is `NULL`. - Fixed MSSQL `JSON_OBJECT('key': value)` expressions being rejected by SQLPage's parser, including when used in `SET` statements or nested in `sqlpage.*` function calls. diff --git a/examples/CRUD - Authentication/README.md b/examples/CRUD - Authentication/README.md index a5f7d02e7..b0534ce2a 100644 --- a/examples/CRUD - Authentication/README.md +++ b/examples/CRUD - Authentication/README.md @@ -77,13 +77,9 @@ SELECT WHERE username = :username) AS password_hash; ``` -If authentication succeeds, create_session.sql redirects back to starting page using the $path value as the final redirect target: +If authentication succeeds, create_session.sql redirects back to the starting page only when `$path` is a local URL path. It accepts paths beginning with one `/`, relative page names such as `currencies_list.sql`, and relative `./` or `../` paths. It rejects external URLs, authority-relative URLs (`//example.com`), backslashes, percent escapes, and control characters. An unsafe value falls back to `/` after login or `/login.sql` after logout. Keep this check when adapting the example to your own pages; add any extra return routes to your application's allowlist if you use one. -```sql -SELECT - 'redirect' AS component, - ifnull($path, '/') AS link; -``` +See [create_session.sql](www/create_session.sql) for the full local-path check. [logout.sql](www/logout.sql) applies the same check before redirecting. ### Adding User/Login/Logout buttons to the page menu diff --git a/examples/CRUD - Authentication/test.hurl b/examples/CRUD - Authentication/test.hurl index dd496aafc..a31eaf3d0 100644 --- a/examples/CRUD - Authentication/test.hurl +++ b/examples/CRUD - Authentication/test.hurl @@ -129,7 +129,48 @@ HTTP 302 [Asserts] header "Location" == "currencies_list.sql" +GET http://localhost:8080/logout.sql?path=.%2Fcurrencies_list.sql +HTTP 302 +[Asserts] +header "Location" == "./currencies_list.sql" + +GET http://localhost:8080/logout.sql?path=https%3A%2F%2Fevil.example +HTTP 302 +[Asserts] +header "Location" == "/login.sql" + +GET http://localhost:8080/logout.sql?path=%2F%2Fevil.example +HTTP 302 +[Asserts] +header "Location" == "/login.sql" + +GET http://localhost:8080/logout.sql?path=%2F%5Cevil.example +HTTP 302 +[Asserts] +header "Location" == "/login.sql" + +GET http://localhost:8080/logout.sql?path=%2F%252Fevil.example +HTTP 302 +[Asserts] +header "Location" == "/login.sql" + GET http://localhost:8080/currencies_list.sql HTTP 302 [Asserts] header "Location" == "/login.sql?path=/currencies_list.sql" + +POST http://localhost:8080/create_session.sql?path=https%3A%2F%2Fevil.example +[FormParams] +username: admin +password: admin +HTTP 302 +[Asserts] +header "Location" == "/" + +POST http://localhost:8080/create_session.sql?path=.%2Fcurrencies_list.sql +[FormParams] +username: admin +password: admin +HTTP 302 +[Asserts] +header "Location" == "./currencies_list.sql" diff --git a/examples/CRUD - Authentication/www/README.md b/examples/CRUD - Authentication/www/README.md index 81a59bb6d..98abb356c 100644 --- a/examples/CRUD - Authentication/www/README.md +++ b/examples/CRUD - Authentication/www/README.md @@ -77,13 +77,9 @@ SELECT WHERE username = :username) AS password_hash; ``` -If authentication succeeds, create_session.sql redirects back to starting page using the $path value as the final redirect target: +If authentication succeeds, create_session.sql redirects back to the starting page only when `$path` is a local URL path. It accepts paths beginning with one `/`, relative page names such as `currencies_list.sql`, and relative `./` or `../` paths. It rejects external URLs, authority-relative URLs (`//example.com`), backslashes, percent escapes, and control characters. An unsafe value falls back to `/` after login or `/login.sql` after logout. Keep this check when adapting the example to your own pages; add any extra return routes to your application's allowlist if you use one. -```sql -SELECT - 'redirect' AS component, - ifnull($path, '/') AS link; -``` +See [create_session.sql](create_session.sql) for the full local-path check. [logout.sql](logout.sql) applies the same check before redirecting. ### Adding User/Login/Logout buttons to the page menu diff --git a/examples/CRUD - Authentication/www/create_session.sql b/examples/CRUD - Authentication/www/create_session.sql index 87f828bf3..839fdc2e1 100644 --- a/examples/CRUD - Authentication/www/create_session.sql +++ b/examples/CRUD - Authentication/www/create_session.sql @@ -22,6 +22,26 @@ RETURNING -- The user browser will now have a cookie named `session_token` that we can check later -- to see if the user is logged in. +-- Only redirect to a local page. Relative links are accepted for existing +-- example pages such as currencies_list.sql. SELECT 'redirect' AS component, - ifnull($path, '/') AS link; + iif( + $path IS NOT NULL + AND length($path) > 0 + AND ( + (substr($path, 1, 1) = '/' AND substr($path, 2, 1) <> '/') + OR ( + (substr($path, 1, 1) GLOB '[A-Za-z0-9_]' + OR substr($path, 1, 2) = './' + OR substr($path, 1, 3) = '../') + AND instr($path, ':') = 0 + ) + ) + AND instr($path, char(92)) = 0 + AND instr($path, '%') = 0 + AND instr($path, char(0)) = 0 + AND ($path GLOB '*[' || char(1) || '-' || char(31) || char(127) || ']*') = 0, + $path, + '/' + ) AS link; diff --git a/examples/CRUD - Authentication/www/logout.sql b/examples/CRUD - Authentication/www/logout.sql index ceaf15710..eeb5f7b7e 100644 --- a/examples/CRUD - Authentication/www/logout.sql +++ b/examples/CRUD - Authentication/www/logout.sql @@ -5,4 +5,22 @@ SELECT SELECT 'redirect' AS component, - ifnull($path, '/login.sql') AS link -- redirect to the login page after the user logs out + iif( + $path IS NOT NULL + AND length($path) > 0 + AND ( + (substr($path, 1, 1) = '/' AND substr($path, 2, 1) <> '/') + OR ( + (substr($path, 1, 1) GLOB '[A-Za-z0-9_]' + OR substr($path, 1, 2) = './' + OR substr($path, 1, 3) = '../') + AND instr($path, ':') = 0 + ) + ) + AND instr($path, char(92)) = 0 + AND instr($path, '%') = 0 + AND instr($path, char(0)) = 0 + AND ($path GLOB '*[' || char(1) || '-' || char(31) || char(127) || ']*') = 0, + $path, + '/login.sql' + ) AS link; -- redirect to the login page after logout when the path is unsafe