Skip to content

Commit b1e1460

Browse files
security(tabapay): validate postMessage origin in WebView listener
The TabaPay widget forwarded every window.postMessage payload to the native success handler without checking event.origin. A cross-origin frame or opener could spoof a pipe-delimited success payload and trigger onSuccess with attacker-controlled card token data. Install the listener only for valid http(s) page URLs and require event.origin to match the loaded page origin. Skip listener injection when the URI is invalid. Co-authored-by: Sharjeel Yunus <sharjeelyunus@users.noreply.github.com>
1 parent f62fb1e commit b1e1460

3 files changed

Lines changed: 70 additions & 2 deletions

File tree

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import 'dart:convert';
2+
3+
/// Builds JavaScript that forwards `postMessage` payloads to the WebView
4+
/// [messageHandler] channel only when `event.origin` matches the origin of
5+
/// [pageUrl].
6+
///
7+
/// Returns `null` when [pageUrl] is not a valid absolute `http`/`https` URI so
8+
/// callers can fail closed instead of installing an unrestricted listener.
9+
String? buildTabaPayPostMessageListenerScript(String pageUrl) {
10+
final uri = Uri.tryParse(pageUrl);
11+
if (uri == null || !uri.hasScheme || !uri.hasAuthority) {
12+
return null;
13+
}
14+
final scheme = uri.scheme.toLowerCase();
15+
if (scheme != 'http' && scheme != 'https') {
16+
return null;
17+
}
18+
final origin = uri.origin;
19+
if (origin.isEmpty) {
20+
return null;
21+
}
22+
final originLiteral = jsonEncode(origin);
23+
return '''
24+
window.addEventListener("message", function(event) {
25+
if (event.origin !== $originLiteral) {
26+
return;
27+
}
28+
messageHandler.postMessage(event.data);
29+
});
30+
''';
31+
}

modules/ensemble/lib/widget/fintech/tabapayconnect.dart

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import 'package:webview_flutter/webview_flutter.dart';
1010
import '../../framework/action.dart';
1111
import '../../screen_controller.dart';
1212
import '../../util/utils.dart';
13+
import 'tabapay_post_message.dart';
1314

1415
class TabaPayConnectController extends WidgetController {
1516
String uri =
@@ -86,8 +87,11 @@ class TabaPayConnectState extends EWidgetState<TabaPayConnect> {
8687
onMessageReceived: _handleTabaPayMessage)
8788
..setNavigationDelegate(
8889
NavigationDelegate(onPageFinished: (String url) {
89-
_webViewController?.runJavaScript(
90-
'window.addEventListener("message", (event) => messageHandler.postMessage(event.data))');
90+
final listenerScript =
91+
buildTabaPayPostMessageListenerScript(widget.controller.uri);
92+
if (listenerScript != null) {
93+
_webViewController?.runJavaScript(listenerScript);
94+
}
9195
}));
9296
_webViewController?.loadRequest(Uri.parse(widget.controller.uri));
9397
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import 'package:ensemble/widget/fintech/tabapay_post_message.dart';
2+
import 'package:flutter_test/flutter_test.dart';
3+
4+
void main() {
5+
group('buildTabaPayPostMessageListenerScript', () {
6+
test('includes origin check for valid https iframe URL', () {
7+
final script = buildTabaPayPostMessageListenerScript(
8+
'https://iframe.tabapay.com/frame',
9+
);
10+
expect(script, isNotNull);
11+
expect(script, contains('"https://iframe.tabapay.com"'));
12+
expect(script, contains('event.origin !=='));
13+
expect(script, contains('messageHandler.postMessage(event.data)'));
14+
});
15+
16+
test('returns null for invalid or non-http(s) URLs', () {
17+
expect(buildTabaPayPostMessageListenerScript(''), isNull);
18+
expect(buildTabaPayPostMessageListenerScript('not-a-url'), isNull);
19+
expect(
20+
buildTabaPayPostMessageListenerScript('javascript:alert(1)'),
21+
isNull,
22+
);
23+
expect(buildTabaPayPostMessageListenerScript('file:///etc/passwd'), isNull);
24+
});
25+
26+
test('JSON-encodes origin literal for safe embedding in JavaScript', () {
27+
final script = buildTabaPayPostMessageListenerScript(
28+
'https://pay.example.com/frame',
29+
);
30+
expect(script, contains('"https://pay.example.com"'));
31+
});
32+
});
33+
}

0 commit comments

Comments
 (0)