Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggest anti-spoof logic for CustomURLs #27669

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Telegram/Resources/langs/lang.strings
Original file line number Diff line number Diff line change
Expand Up @@ -3070,6 +3070,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_url_auth_login_option" = "Log in to {domain} as {user}";
"lng_url_auth_allow_messages" = "Allow {bot} to send me messages";

"lng_open_spoof_title" = "⚠️ URL Spoofing";
"lng_open_spoof_link" = "Do you want to open link that is masked to look like different link?";
"lng_open_spoof_link_confirm" = "It is Safe";
"lng_open_spoof_link_label" = "Link is shown as";
"lng_open_spoof_link_url" = "Actual link is";

"lng_bot_start" = "Start";
"lng_bot_choose_group" = "Select a Group";
"lng_bot_no_groups" = "You have no groups";
Expand Down
120 changes: 119 additions & 1 deletion Telegram/SourceFiles/core/click_handler_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ For license and copyright information please follow this link:
#include "window/window_session_controller_link_info.h"
#include "styles/style_layers.h"

#include <QUrlQuery>

namespace {

// Possible context owners: media viewer, profile, history widget.
Expand Down Expand Up @@ -81,6 +83,83 @@ bool UrlRequiresConfirmation(const QUrl &url) {
RegExOption::CaseInsensitive);
}

bool IsLabelImpersonateUrl(QString url, QString label) {
using namespace qthelp;
if (url == label) {
return false;
}

// avoid ' @username' workaround
label = label.trimmed();
QUrl urlObj(Core::TryConvertUrlToLocal(url));
QUrl labelObj(Core::TryConvertUrlToLocal(label));
if (labelObj.host().isEmpty()) {
static const auto IsHost = QRegularExpression(
"^\\w[\\w\\d_]+\\.\\w[^\\s]+$",
QRegularExpression::CaseInsensitiveOption);
const auto match1 = IsHost.match(labelObj.path());
if (match1.hasMatch()) {
labelObj = QUrl("https://" + label);
}
}

// check if tg username
if (label.startsWith("@")) {
// username?
if (urlObj.scheme() != "tg") {
// external website? ok
return false;
}
}

if (urlObj.scheme() == "tg")
{
if (label.startsWith("@")) {
// check tg name
QUrlQuery args(urlObj);
if (args.queryItemValue("domain").compare(label.mid(1), Qt::CaseInsensitive) != 0) {
return true;
}
}
else if (labelObj.scheme() == "tg") {
// tg:// urls should be exactly the same
return (labelObj != urlObj);
}
}

if (label.startsWith("#")) {
// hash tags should be EntityType::Hashtag, not CustomUrl
return true;
}

if (!labelObj.host().isEmpty()) {
// check matching for schema and domain
if (!labelObj.scheme().isEmpty()
&& (urlObj.scheme() != labelObj.scheme())) {
if ((urlObj.scheme() == "https") && (labelObj.scheme() == "http")) {
// accept this case, but not vice versa
}
else {
// schema is different (tg!=http, or https!=http)
return true;
}
}
if (urlObj.host() != labelObj.host()) {
return true;
}
// urls are different somewhere in path?query - fine
// potentially - accept only shorten version of url
return false;
}
return false;
}

HiddenUrlClickHandler::HiddenUrlClickHandler(QString url, QString label)
: UrlClickHandler(url, false)
, _label(label)
, _isSpoof(IsLabelImpersonateUrl(url, label)) {
}

QString HiddenUrlClickHandler::copyToClipboardText() const {
return url().startsWith(u"internal:url:"_q)
? url().mid(u"internal:url:"_q.size())
Expand All @@ -102,7 +181,7 @@ QString HiddenUrlClickHandler::dragText() const {
return result.startsWith(u"internal:"_q) ? QString() : result;
}

void HiddenUrlClickHandler::Open(QString url, QVariant context) {
void HiddenUrlClickHandler::Open(QString url, QVariant context, bool IsSpoof, QString label) {
url = Core::TryConvertUrlToLocal(url);
if (Core::InternalPassportLink(url)) {
return;
Expand All @@ -111,6 +190,45 @@ void HiddenUrlClickHandler::Open(QString url, QVariant context) {
const auto open = [=] {
UrlClickHandler::Open(url, context);
};

if (IsSpoof) {
Core::App().hideMediaView();
const auto displayed = url;
const auto displayUrl = ShowEncoded(displayed);
const auto my = context.value<ClickHandlerContext>();
const auto controller = my.sessionWindow.get();
const auto use = controller
? &controller->window()
: Core::App().activeWindow();
auto box = Box([=](not_null<Ui::GenericBox*> box) {
Ui::ConfirmBox(box, {
.text = (tr::lng_open_spoof_link(tr::now)),
.confirmed = [=](Fn<void()> hide) { hide(); open(); },
.confirmText = tr::lng_open_spoof_link_confirm(),
.title = (tr::lng_open_spoof_title(tr::now))
});
const auto& st = st::boxLabel;
const auto& stdiv = st::boxDividerLabel;
box->addSkip(st.style.lineHeight - st::boxPadding.bottom());
box->addRow(object_ptr<Ui::FlatLabel>(box, tr::lng_open_spoof_link_label(), stdiv));
const auto url = box->addRow(object_ptr<Ui::FlatLabel>(box, label, st));
box->addRow(object_ptr<Ui::FlatLabel>(box, tr::lng_open_spoof_link_url(), stdiv));
const auto actual_url = box->addRow(object_ptr<Ui::FlatLabel>(box, displayUrl, st));
url->setSelectable(true);
url->setContextCopyText(tr::lng_context_copy_link(tr::now));
actual_url->setSelectable(true);
actual_url->setContextCopyText(tr::lng_context_copy_link(tr::now));
});
if (my.show) {
my.show->showBox(std::move(box));
}
else if (use) {
use->show(std::move(box));
use->activate();
}
return;
}

if (url.startsWith(u"tg://"_q, Qt::CaseInsensitive)
|| url.startsWith(u"internal:"_q, Qt::CaseInsensitive)) {
UrlClickHandler::Open(url, QVariant::fromValue([&] {
Expand Down
10 changes: 6 additions & 4 deletions Telegram/SourceFiles/core/click_handler_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,22 +56,24 @@ class PhoneClickHandler;

class HiddenUrlClickHandler : public UrlClickHandler {
public:
HiddenUrlClickHandler(QString url) : UrlClickHandler(url, false) {
}
HiddenUrlClickHandler(QString url, QString label);
QString copyToClipboardText() const override;
QString copyToClipboardContextItemText() const override;
QString dragText() const override;

static void Open(QString url, QVariant context = {});
static void Open(QString url, QVariant context = {}, bool IsSpoof = false, QString label = QString());
void onClick(ClickContext context) const override {
const auto button = context.button;
if (button == Qt::LeftButton || button == Qt::MiddleButton) {
Open(url(), context.other);
Open(url(), context.other, _isSpoof, _label);
}
}

TextEntity getTextEntity() const override;

private:
QString _label;
bool _isSpoof;
};

class UserData;
Expand Down
4 changes: 2 additions & 2 deletions Telegram/SourceFiles/core/ui_integration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,12 @@ std::shared_ptr<ClickHandler> UiIntegration::createLinkHandler(
case EntityType::Url:
return (!data.data.isEmpty()
&& UrlClickHandler::IsSuspicious(data.data))
? std::make_shared<HiddenUrlClickHandler>(data.data)
? std::make_shared<HiddenUrlClickHandler>(data.data, data.data)
: Integration::createLinkHandler(data, context);

case EntityType::CustomUrl:
return !data.data.isEmpty()
? std::make_shared<HiddenUrlClickHandler>(data.data)
? std::make_shared<HiddenUrlClickHandler>(data.data, data.text)
: Integration::createLinkHandler(data, context);

case EntityType::BotCommand:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ QSize WebPage::countOptimalSize() {
? IvClickHandler(_data, original)
: (previewOfHiddenUrl || UrlClickHandler::IsSuspicious(
_data->url))
? std::make_shared<HiddenUrlClickHandler>(_data->url)
? std::make_shared<HiddenUrlClickHandler>(_data->url, _data->url)
: std::make_shared<UrlClickHandler>(_data->url, true);
if (_data->document
&& (_data->document->isWallPaper()
Expand Down
4 changes: 2 additions & 2 deletions Telegram/SourceFiles/overview/overview_layout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1663,7 +1663,7 @@ Link::Link(

const auto createHandler = [](const QString &url) {
return UrlClickHandler::IsSuspicious(url)
? std::make_shared<HiddenUrlClickHandler>(url)
? std::make_shared<HiddenUrlClickHandler>(url, url)
: std::make_shared<UrlClickHandler>(url, false);
};
_page = media ? media->webpage() : nullptr;
Expand Down Expand Up @@ -1993,7 +1993,7 @@ Link::LinkEntry::LinkEntry(const QString &url, const QString &text)
: text(text)
, width(st::normalFont->width(text))
, lnk(UrlClickHandler::IsSuspicious(url)
? std::make_shared<HiddenUrlClickHandler>(url)
? std::make_shared<HiddenUrlClickHandler>(url, text)
: std::make_shared<UrlClickHandler>(url)) {
}

Expand Down
Loading