Skip to content

Commit

Permalink
v0.9.2
Browse files Browse the repository at this point in the history
* XSS vulnerability patch
* optimize getLastEvent()
  • Loading branch information
arina-tirreno committed Jan 28, 2025
1 parent 18d273f commit b9bfa22
Show file tree
Hide file tree
Showing 8 changed files with 72 additions and 45 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Tirreno Changelog

## Tirreno v0.9.2

* XSS vulnerability patch
* optimize getLastEvent()

## Tirreno v0.9.1

* data plotting with hour and minute resolution
Expand Down
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,14 @@
</a>
</p>

Tirreno is an open-source user security analytics for mission-critical web applications.
Tirreno is an open-source fraud prevention platform.

Tirreno is a universal analytic tool for monitoring online platforms, web applications, SaaS, communities, IoT, mobile applications, intranets, and e-commerce websites. It is effective against external threats associated with partners or customers, as well as internal risks posed by employees or suppliers.
Tirreno is a universal analytic tool for monitoring online platforms, web applications, SaaS, communities, mobile applications, intranets, and e-commerce websites.

* **For Website Owners**: Protect your user areas from account takeovers, malicious bots, and common web vulnerabilities caused by user behavior.
* **For Online Communities**: Combat spam, prevent fake registrations, and stop re-registration from the same IP addresses.
* **For Startups, SaaS, and E-commerce**: Get a ready-made boilerplate for client security, including monitoring customer activity for suspicious behavior and preventing fraud using advanced email, IP address, and phone reputation checks.
* **For Platforms**: Conduct thorough merchant risk assessments to identify and mitigate potential threats from high-risk merchants, ensuring the integrity of your platform.
* **For IoT Security**: Enhance the security of your IoT ecosystem by tracking user activity and identifying potential vulnerabilities.
* **For CISOs and Security Teams**: Monitor employee accounts and secure critical enterprise applications like Mattermost™, GitLab™, CRMs, or intranets. Identify unusual activity patterns or unauthorized access attempts.

Tirreno is a "low-tech" PHP and PostgreSQL software application that can be downloaded and installed on your own web server. After a straightforward five-minute installation process, you can immediately access real-time analytics.

Expand Down
17 changes: 13 additions & 4 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
Release song: https://youtu.be/5TySnF_Pb3M
Release song: https://youtu.be/8hYgxwmHbxA

Tirreno is pleased to announce version v0.9.1.
Important security update: tirreno v0.9.2.

This update enhances data plotting with hour and minute resolutions,
improves JS linting and dependency security, and fixes minor bugs including logbook search issues.
Today, we received a report from IT security expert Sandro Bauer regarding
an XSS vulnerability in Tirreno. After receiving the report, we confirmed
receipt and immediately reproduced the problem, developing a patch the same
day. Briefly, the XSS vulnerability potentially allows attackers to post
malicious scripts by sending them through a payload. However, it's important
to clarify that the Tirreno platform does not directly receive user event data,
as it must come from the main web application, which we expect to be trustworthy.
Another aspect that makes it difficult to exploit this vulnerability is the
truncation of all data displayed in the dashboard.

The Tirreno team highly appreciates Sandro's report and help in maintaining Tirreno's application security.
3 changes: 2 additions & 1 deletion app/Controllers/Pages/Base.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ public function getPageTitle(): string {
}

public function getInternalPageTitleWithPostfix(string $title): string {
$title = sprintf('%s %s', $title, \Utils\Constants::PAGE_TITLE_POSTFIX);
$safeTitle = htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
$title = sprintf('%s %s', $safeTitle, \Utils\Constants::PAGE_TITLE_POSTFIX);

return $title;
}
Expand Down
2 changes: 1 addition & 1 deletion app/Models/Event.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public function getLastEvent(int $apiKey): array {
WHERE
event.key = :api_key
ORDER BY time DESC
ORDER BY id DESC
LIMIT 1 OFFSET 0'
);

Expand Down
2 changes: 1 addition & 1 deletion app/Utils/VersionControl.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
class VersionControl {
public const versionMajor = 0;
public const versionMinor = 9;
public const versionRevision = 1;
public const versionRevision = 2;

public static function versionString(): string {
return sprintf('%d.%d.%d', self::versionMajor, self::versionMinor, self::versionRevision);
Expand Down
60 changes: 32 additions & 28 deletions ui/js/parts/DataRenderers.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ const renderTime = (data) => {
data = `${day}/${month}/${year} ${hours}:${minutes}:${seconds}`;
}

return data;
return escapeForHTMLAttribute(data);
};

const renderTimeMs = (data) => {
Expand Down Expand Up @@ -227,7 +227,7 @@ const renderDate = (data) => {
data = `${day}/${month}/${year}`;
}

return data;
return escapeForHTMLAttribute(data);
};

const renderRuleSelectorItem = (classNames, data) => {
Expand Down Expand Up @@ -309,7 +309,7 @@ const renderHttpMethod = record => {
const type = record.http_method;
if (type) {
let style = (type === 'POST' || type === 'GET') ? 'nolight' : 'highlight';
html = `<span class="${style}">${type}</span>`;
html = `<span class="${style}">${escapeForHTMLAttribute(type)}</span>`;
}

html = renderDefaultIfEmpty(html);
Expand Down Expand Up @@ -700,8 +700,8 @@ const renderPhone = (record) => {
const phone = record.phonenumber;

if (phone) {
const code = !COUNTRIES_EXCEPTIONS.includes(record.country) ? record.country : 'lh';
const tooltip = (record.full_country !== null && record.full_country !== undefined) ? record.full_country : '';
const code = !COUNTRIES_EXCEPTIONS.includes(record.country) ? escapeForHTMLAttribute(record.country) : 'lh';
const tooltip = (record.full_country !== null && record.full_country !== undefined) ? escapeForHTMLAttribute(record.full_country) : '';

const n = MAX_STRING_LENGTH_FOR_PHONE;
const number = truncateWithHellip(phone, n);
Expand Down Expand Up @@ -752,13 +752,12 @@ const renderPhoneType = record => {

if (type) {
const n = getNumberOfSymbols();
html = truncateWithHellip(type, n);

let src = 'smartphone.svg';
if (['landline', 'FIXED_LINE', 'FIXED_LINE_OR_MOBILE', 'TOLL_FREE', 'SHARED_COST'].includes(type)) src = 'landline.svg';
if (['nonFixedVoip', 'VOIP'].includes(type)) src = 'voip.svg';

const tooltip = type.toLowerCase().replace(/_/g, ' ');
const tooltip = escapeForHTMLAttribute(type.toLowerCase().replace(/_/g, ' '));

html = `<span class="tooltip" title="${tooltip}"><img src="/ui/images/icons/${src}"/></span>`;
}
Expand Down Expand Up @@ -794,7 +793,7 @@ const renderUsersList = (data) => {
const renderResource = (value, tooltip) => {
const n = getNumberOfSymbols();
value = value ? value : '/';
tooltip = tooltip ? tooltip : '/';
tooltip = tooltip ? escapeForHTMLAttribute(tooltip) : '/';
value = truncateWithHellip(value, n);

//Create a tooltip data with full url
Expand All @@ -817,19 +816,21 @@ const renderResourceWithoutQuery = record => {

const renderResourceWithQueryAndEventType = record => {
let url = record.url;

if (record.query) {
url = `${record.url}${record.query}`;
url += record.query;
}

if (url && url.length > MAX_TOOLTIP_URL_LENGTH) {
url = `${url.slice(0, MAX_TOOLTIP_URL_LENGTH)}&hellip;`;
let tooltip = '';
if (url.length > MAX_TOOLTIP_URL_LENGTH) {
tooltip = `${escapeForHTMLAttribute(url.slice(0, MAX_TOOLTIP_URL_LENGTH))}&hellip;`
} else {
tooltip = escapeForHTMLAttribute(url);
}

const event_type = checkErrorEventType(record);

const html = `<p class="bullet ${event_type.event_type} tooltip" title="${record.url}"
></p><span class="tooltip" title="${url}">${event_type.event_type_name}</span>`;
const html = `<p class="bullet ${event_type.event_type} tooltip" title="${tooltip}"
></p><span class="tooltip" title="${tooltip}">${event_type.event_type_name}</span>`;

return html;
};
Expand All @@ -846,7 +847,7 @@ const renderIp = record => {
const n = getNumberOfSymbols();

let html = truncateWithHellip(record.ip, n);
let name = record.isp_name;
let name = escapeForHTMLAttribute(record.isp_name);
if (name) {
html = html.replace(/title=".*?"/, `title="${name}"`);
}
Expand Down Expand Up @@ -925,15 +926,15 @@ const renderNetName = (record, length = 'default') => {
let html = '';

if (record.netname) {
html = record.netname;
html = escapeForHTMLAttribute(record.netname);
}

if (!html && record.description) {
html = record.description;
html = escapeForHTMLAttribute(record.description);
}

if (!html && record.asn) {
html = record.asn;
html = escapeForHTMLAttribute(record.asn);
}

if (html) {
Expand Down Expand Up @@ -1050,14 +1051,16 @@ const renderDevice = record => {
const deviceTypeTooltip = record.device_name ? record.device_name : 'unknown';
const deviceTypeImg = deviceIsNormal ? record.device_name : 'unknown';

const ua = escapeForHTMLAttribute(record.ua);

let deviceTypeName = 'unknown';

if (record.device_name && record.device_name !== 'unknown') {
deviceTypeName = deviceIsNormal ? record.device_name : 'other device';
}

const html = `<span class="tooltip" title="${deviceTypeTooltip}"><img src="/ui/images/icons/${deviceTypeImg}.svg"
/></span><span class="tooltip" title="${record.ua}">${deviceTypeName}</span>`;
/></span><span class="tooltip" title="${ua}">${deviceTypeName}</span>`;

return html;
};
Expand All @@ -1067,8 +1070,10 @@ const renderDeviceWithOs = record => {
const deviceTypeTooltip = record.device_name ? record.device_name : 'unknown';
const deviceTypeImg = NORMAL_DEVICES.includes(record.device_name) ? record.device_name : 'unknown';

const ua = escapeForHTMLAttribute(record.ua);

const html = `<span class="tooltip" title="${deviceTypeTooltip}"><img src="/ui/images/icons/${deviceTypeImg}.svg"
/></span><span class="tooltip" title="${record.ua}">${os}</span>`;
/></span><span class="tooltip" title="${ua}">${os}</span>`;

return html;
};
Expand All @@ -1092,7 +1097,7 @@ const renderLanguage = record => {

codeAndRegion = codeAndRegion.join('-');
if (codeAndRegion) {
codeAndRegion = `<span class="nolight">${codeAndRegion}</span>`;
codeAndRegion = `<span class="nolight">${escapeForHTMLAttribute(codeAndRegion)}</span>`;
}

const html = renderDefaultIfEmpty(codeAndRegion);
Expand Down Expand Up @@ -1162,15 +1167,15 @@ const renderBrowser = record => {
};

const renderQuery = record => {
return `<textarea readonly rows="4" cols="37">${renderDefaultIfEmpty(record.query)}</textarea>`;
return `<textarea readonly rows="4" cols="37">${escapeForHTMLAttribute(renderDefaultIfEmpty(record.query))}</textarea>`;
};

const renderReferer = record => {
return `<textarea readonly rows="4" cols="37">${renderDefaultIfEmpty(record.referer)}</textarea>`;
return `<textarea readonly rows="4" cols="37">${escapeForHTMLAttribute(renderDefaultIfEmpty(record.referer))}</textarea>`;
};

const renderUserAgent = record => {
return `<textarea readonly rows="5" cols="37">${renderDefaultIfEmpty(record.ua)}</textarea>`;
return `<textarea readonly rows="5" cols="37">${escapeForHTMLAttribute(renderDefaultIfEmpty(record.ua))}</textarea>`;
};

const renderDefaultIfEmpty = (value) => {
Expand Down Expand Up @@ -1241,17 +1246,17 @@ const renderSensorErrorColumn = record => {
const renderSensorError = record => {
const obj = openJson(record.error_text);
const s = (obj !== null) ? obj.join(';\n') : null;
return `<textarea readonly rows="4" cols="37">${renderDefaultIfEmpty(s)}</textarea>`;
return `<textarea readonly rows="4" cols="37">${escapeForHTMLAttribute(renderDefaultIfEmpty(s))}</textarea>`;
};

const renderRawRequest = record => {
const obj = openJson(record.raw);
const s = (obj !== null) ? JSON.stringify(obj, null, 2) : null;
return `<textarea readonly rows="24" cols="37">${renderDefaultIfEmpty(s)}</textarea>`;
return `<textarea readonly rows="24" cols="37">${escapeForHTMLAttribute(renderDefaultIfEmpty(s))}</textarea>`;
};

const renderErrorType = record => {
return `<p class="bullet ${record.error_value}"></p><span>${record.error_name}</span>`;
return `<p class="bullet ${escapeForHTMLAttribute(record.error_value)}"></p><span>${escapeForHTMLAttribute(record.error_name)}</span>`;
};

const renderMailto = record => {
Expand Down Expand Up @@ -1341,7 +1346,6 @@ export {

//Time
renderTime,
renderTimeMs,
renderDate,

//Rule selector
Expand Down
22 changes: 16 additions & 6 deletions ui/js/parts/utils/String.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import {MAX_TOOLTIP_LENGTH} from './Constants.js?v=2';

const truncateWithHellip = (value, n) => {
let fullValue = value;
let tooltip = value;

if (value && value.length > (n + 2)) {
value = `${value.slice(0, n)}&hellip;`;
if (value) {
if (value.length > (n + 2)) {
value = `${escapeForHTMLAttribute(value.slice(0, n))}&hellip;`;
} else {
value = escapeForHTMLAttribute(value);
}
}

if (fullValue && fullValue.length > MAX_TOOLTIP_LENGTH) {
fullValue = `${fullValue.slice(0, MAX_TOOLTIP_LENGTH)}&hellip;`;
if (tooltip.length > MAX_TOOLTIP_LENGTH) {
tooltip = `${escapeForHTMLAttribute(tooltip.slice(0, MAX_TOOLTIP_LENGTH))}&hellip;`;
} else {
tooltip = escapeForHTMLAttribute(tooltip);
}

value = `<span class="tooltip" title="${fullValue}">${value}</span>`;
value = `<span class="tooltip" title="${tooltip}">${value}</span>`;

return value;
};
Expand All @@ -21,6 +27,10 @@ const replaceAll = (str, search, replacement) => {
};

const escapeForHTMLAttribute = (str) => {
if (str === '&#65293;') {
return str;
}

return (str === null || str === undefined) ? '' :
str.replace(/["'&<>]/g, function(match) {
switch (match) {
Expand Down

0 comments on commit b9bfa22

Please sign in to comment.