@@ -402,6 +402,37 @@ public function testRecipientSubjectMergeFields()
402402 $ this ->assertEmailSent ('test@example.com ' , 'no-reply@example.com ' , 'Email Subject: Basic Value ' );
403403 }
404404
405+ /**
406+ * The EmailSubject is rendered as a template. An author controlled subject must never have PHP
407+ * within it evaluated (which previously allowed remote code execution via the <%t %> default
408+ * string handling). The payload below must be treated as inert text.
409+ */
410+ public function testRecipientSubjectDoesNotEvaluatePhp ()
411+ {
412+ $ form = $ this ->setupFormFrontend ();
413+
414+ $ rceFile = ASSETS_PATH . '/userforms_rce_test.txt ' ;
415+ if (file_exists ($ rceFile ?? '' )) {
416+ unlink ($ rceFile ?? '' );
417+ }
418+
419+ $ recipient = $ this ->objFromFixture (EmailRecipient::class, 'recipient-1 ' );
420+ $ recipient ->EmailSubject = '<%t Foo "{${ \'file_put_contents \'( \''
421+ . $ rceFile . '\', \'pwned \')}}" %> ' ;
422+ $ recipient ->write ();
423+
424+ $ this ->autoFollowRedirection = false ;
425+ $ this ->clearEmails ();
426+
427+ $ this ->get ($ form ->URLSegment );
428+
429+ $ field = $ this ->objFromFixture (EditableTextField::class, 'basic-text ' );
430+ $ this ->submitForm ('UserForm_Form_ ' . $ form ->ID , null , [$ field ->Name => 'Basic Value ' ]);
431+
432+ // The payload must not have been executed, so no file should have been written.
433+ $ this ->assertFileDoesNotExist ($ rceFile );
434+ }
435+
405436 public function testImageThumbnailCreated ()
406437 {
407438 Config::modify ()->set (Upload_Validator::class, 'use_is_uploaded_file ' , false );
0 commit comments