diff --git a/scrapy/http/request/form.py b/scrapy/http/request/form.py
index c2413b431..3ce8fc48e 100644
--- a/scrapy/http/request/form.py
+++ b/scrapy/http/request/form.py
@@ -18,6 +18,7 @@ from scrapy.utils.response import get_base_url
 
 
 class FormRequest(Request):
+    valid_form_methods = ['GET', 'POST']
 
     def __init__(self, *args, **kwargs):
         formdata = kwargs.pop('formdata', None)
@@ -48,7 +49,13 @@ class FormRequest(Request):
         form = _get_form(response, formname, formid, formnumber, formxpath)
         formdata = _get_inputs(form, formdata, dont_click, clickdata, response)
         url = _get_form_url(form, kwargs.pop('url', None))
+
         method = kwargs.pop('method', form.method)
+        if method is not None:
+            method = method.upper()
+            if method not in cls.valid_form_methods:
+                method = 'GET'
+
         return cls(url=url, method=method, formdata=formdata, **kwargs)
 
 

Whitelist form methods in FormRequest.from_response
Hi team, according to this [article](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-method), there are 3 methods that are accepted in form's `method` attribute. I can't remember about other methods, but I'd agree to consider the rest of [REST verbs](https://restfulapi.net/http-methods/) if that's common. Anyway, I think it should be a good idea to whitelist the accepted methods to avoid scenarios like this [vulnerability exploitation](https://medium.com/alertot/web-scraping-considered-dangerous-exploiting-the-telnet-service-in-scrapy-1-5-2-ad5260fea0db) that took advantage of form's method.

This issue affects `FormRequest.from_response` and the affected line is: https://github.com/scrapy/scrapy/blob/a3d38041e230f886d55569f861a11a96ab8a1913/scrapy/http/request/form.py#L51

I want to know if there some reason behind this behavior, otherwise I could send a pull request.
diff --git a/tests/test_http_request.py b/tests/test_http_request.py
index 49f148016..258b48dce 100644
--- a/tests/test_http_request.py
+++ b/tests/test_http_request.py
@@ -1100,6 +1100,20 @@ class FormRequestTest(RequestTest):
         self.assertRaises(ValueError, self.request_class.from_response,
                           response, formcss="input[name='abc']")
 
+    def test_from_response_valid_form_methods(self):
+        body = """<form action="post.php" method="%s">
+            <input type="hidden" name="one" value="1">
+            </form>"""
+
+        for method in self.request_class.valid_form_methods:
+            response = _buildresponse(body % method)
+            r = self.request_class.from_response(response)
+            self.assertEqual(r.method, method)
+
+        response = _buildresponse(body % 'UNKNOWN')
+        r = self.request_class.from_response(response)
+        self.assertEqual(r.method, 'GET')
+
 
 def _buildresponse(body, **kwargs):
     kwargs.setdefault('body', body)

pip install -e .
parse_log_pytest
requirements.txt
pytest
apt-get update apt-get install -y python3-dev libxml2-dev libxslt1-dev zlib1g-dev libffi-dev libssl-dev
3.9
requirements-py3.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==25.3.0
Automat==24.8.1
cffi==1.17.1
constantly==23.10.4
cryptography==44.0.2
cssselect==1.3.0
exceptiongroup==1.2.2
hyperlink==21.0.0
idna==3.10
incremental==24.7.2
iniconfig==2.1.0
jmespath==1.0.1
lxml==5.3.1
packaging==24.2
parsel==1.10.0
pluggy==1.5.0
pyasn1==0.6.1
pyasn1_modules==0.4.2
pycparser==2.22
PyDispatcher==2.0.7
pyOpenSSL==25.0.0
pytest==8.3.5
queuelib==1.7.0
-e git+https://github.com/scrapy/scrapy.git@a3d38041e230f886d55569f861a11a96ab8a1913#egg=Scrapy
service-identity==24.2.0
six==1.17.0
tomli==2.2.1
Twisted==24.11.0
typing_extensions==4.13.0
w3lib==2.3.1
zope.interface==7.2

name: scrapy
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==25.3.0
      - automat==24.8.1
      - cffi==1.17.1
      - constantly==23.10.4
      - cryptography==44.0.2
      - cssselect==1.3.0
      - exceptiongroup==1.2.2
      - hyperlink==21.0.0
      - idna==3.10
      - incremental==24.7.2
      - iniconfig==2.1.0
      - jmespath==1.0.1
      - lxml==5.3.1
      - packaging==24.2
      - parsel==1.10.0
      - pluggy==1.5.0
      - pyasn1==0.6.1
      - pyasn1-modules==0.4.2
      - pycparser==2.22
      - pydispatcher==2.0.7
      - pyopenssl==25.0.0
      - pytest==8.3.5
      - queuelib==1.7.0
      - service-identity==24.2.0
      - six==1.17.0
      - tomli==2.2.1
      - twisted==24.11.0
      - typing-extensions==4.13.0
      - w3lib==2.3.1
      - zope-interface==7.2
prefix: /opt/conda/envs/scrapy

tests/test_http_request.py::FormRequestTest::test_from_response_valid_form_methods
tests/test_http_request.py::RequestTest::test_ajax_url tests/test_http_request.py::RequestTest::test_body tests/test_http_request.py::RequestTest::test_callback_is_callable tests/test_http_request.py::RequestTest::test_copy tests/test_http_request.py::RequestTest::test_copy_inherited_classes tests/test_http_request.py::RequestTest::test_eq tests/test_http_request.py::RequestTest::test_errback_is_callable tests/test_http_request.py::RequestTest::test_headers tests/test_http_request.py::RequestTest::test_immutable_attributes tests/test_http_request.py::RequestTest::test_init tests/test_http_request.py::RequestTest::test_method_always_str tests/test_http_request.py::RequestTest::test_replace tests/test_http_request.py::RequestTest::test_url tests/test_http_request.py::RequestTest::test_url_encoding tests/test_http_request.py::RequestTest::test_url_encoding_nonutf8_untouched tests/test_http_request.py::RequestTest::test_url_encoding_other tests/test_http_request.py::RequestTest::test_url_encoding_query tests/test_http_request.py::RequestTest::test_url_encoding_query_latin1 tests/test_http_request.py::RequestTest::test_url_no_scheme tests/test_http_request.py::RequestTest::test_url_quoting tests/test_http_request.py::FormRequestTest::test_ajax_url tests/test_http_request.py::FormRequestTest::test_body tests/test_http_request.py::FormRequestTest::test_callback_is_callable tests/test_http_request.py::FormRequestTest::test_copy tests/test_http_request.py::FormRequestTest::test_copy_inherited_classes tests/test_http_request.py::FormRequestTest::test_custom_encoding_bytes tests/test_http_request.py::FormRequestTest::test_custom_encoding_textual_data tests/test_http_request.py::FormRequestTest::test_default_encoding_bytes tests/test_http_request.py::FormRequestTest::test_default_encoding_mixed_data tests/test_http_request.py::FormRequestTest::test_default_encoding_textual_data tests/test_http_request.py::FormRequestTest::test_empty_formdata tests/test_http_request.py::FormRequestTest::test_eq tests/test_http_request.py::FormRequestTest::test_errback_is_callable tests/test_http_request.py::FormRequestTest::test_from_response_ambiguous_clickdata tests/test_http_request.py::FormRequestTest::test_from_response_button_notype tests/test_http_request.py::FormRequestTest::test_from_response_button_novalue tests/test_http_request.py::FormRequestTest::test_from_response_button_submit tests/test_http_request.py::FormRequestTest::test_from_response_case_insensitive tests/test_http_request.py::FormRequestTest::test_from_response_checkbox tests/test_http_request.py::FormRequestTest::test_from_response_clickdata_does_not_ignore_image tests/test_http_request.py::FormRequestTest::test_from_response_css tests/test_http_request.py::FormRequestTest::test_from_response_descendants tests/test_http_request.py::FormRequestTest::test_from_response_dont_click tests/test_http_request.py::FormRequestTest::test_from_response_dont_submit_image_as_input tests/test_http_request.py::FormRequestTest::test_from_response_dont_submit_reset_as_input tests/test_http_request.py::FormRequestTest::test_from_response_drop_params tests/test_http_request.py::FormRequestTest::test_from_response_duplicate_form_key tests/test_http_request.py::FormRequestTest::test_from_response_errors_formnumber tests/test_http_request.py::FormRequestTest::test_from_response_errors_noform tests/test_http_request.py::FormRequestTest::test_from_response_extra_headers tests/test_http_request.py::FormRequestTest::test_from_response_formid_errors_formnumber tests/test_http_request.py::FormRequestTest::test_from_response_formid_exists tests/test_http_request.py::FormRequestTest::test_from_response_formid_notexist tests/test_http_request.py::FormRequestTest::test_from_response_formname_errors_formnumber tests/test_http_request.py::FormRequestTest::test_from_response_formname_exists tests/test_http_request.py::FormRequestTest::test_from_response_formname_notexist tests/test_http_request.py::FormRequestTest::test_from_response_formname_notexists_fallback_formid tests/test_http_request.py::FormRequestTest::test_from_response_get tests/test_http_request.py::FormRequestTest::test_from_response_input_hidden tests/test_http_request.py::FormRequestTest::test_from_response_input_text tests/test_http_request.py::FormRequestTest::test_from_response_input_textarea tests/test_http_request.py::FormRequestTest::test_from_response_invalid_html5 tests/test_http_request.py::FormRequestTest::test_from_response_invalid_nr_index_clickdata tests/test_http_request.py::FormRequestTest::test_from_response_multiple_clickdata tests/test_http_request.py::FormRequestTest::test_from_response_multiple_forms_clickdata tests/test_http_request.py::FormRequestTest::test_from_response_noformname tests/test_http_request.py::FormRequestTest::test_from_response_non_matching_clickdata tests/test_http_request.py::FormRequestTest::test_from_response_nr_index_clickdata tests/test_http_request.py::FormRequestTest::test_from_response_override_clickable tests/test_http_request.py::FormRequestTest::test_from_response_override_duplicate_form_key tests/test_http_request.py::FormRequestTest::test_from_response_override_method tests/test_http_request.py::FormRequestTest::test_from_response_override_params tests/test_http_request.py::FormRequestTest::test_from_response_override_url tests/test_http_request.py::FormRequestTest::test_from_response_post tests/test_http_request.py::FormRequestTest::test_from_response_post_nonascii_bytes_latin1 tests/test_http_request.py::FormRequestTest::test_from_response_post_nonascii_bytes_utf8 tests/test_http_request.py::FormRequestTest::test_from_response_post_nonascii_unicode tests/test_http_request.py::FormRequestTest::test_from_response_radio tests/test_http_request.py::FormRequestTest::test_from_response_select tests/test_http_request.py::FormRequestTest::test_from_response_submit_first_clickable tests/test_http_request.py::FormRequestTest::test_from_response_submit_not_first_clickable tests/test_http_request.py::FormRequestTest::test_from_response_submit_novalue tests/test_http_request.py::FormRequestTest::test_from_response_unicode_clickdata tests/test_http_request.py::FormRequestTest::test_from_response_unicode_clickdata_latin1 tests/test_http_request.py::FormRequestTest::test_from_response_unicode_xpath tests/test_http_request.py::FormRequestTest::test_from_response_xpath tests/test_http_request.py::FormRequestTest::test_headers tests/test_http_request.py::FormRequestTest::test_html_base_form_action tests/test_http_request.py::FormRequestTest::test_immutable_attributes tests/test_http_request.py::FormRequestTest::test_init tests/test_http_request.py::FormRequestTest::test_method_always_str tests/test_http_request.py::FormRequestTest::test_multi_key_values tests/test_http_request.py::FormRequestTest::test_replace tests/test_http_request.py::FormRequestTest::test_spaces_in_action tests/test_http_request.py::FormRequestTest::test_url tests/test_http_request.py::FormRequestTest::test_url_encoding tests/test_http_request.py::FormRequestTest::test_url_encoding_nonutf8_untouched tests/test_http_request.py::FormRequestTest::test_url_encoding_other tests/test_http_request.py::FormRequestTest::test_url_encoding_query tests/test_http_request.py::FormRequestTest::test_url_encoding_query_latin1 tests/test_http_request.py::FormRequestTest::test_url_no_scheme tests/test_http_request.py::FormRequestTest::test_url_quoting tests/test_http_request.py::XmlRpcRequestTest::test_ajax_url tests/test_http_request.py::XmlRpcRequestTest::test_body tests/test_http_request.py::XmlRpcRequestTest::test_callback_is_callable tests/test_http_request.py::XmlRpcRequestTest::test_copy tests/test_http_request.py::XmlRpcRequestTest::test_copy_inherited_classes tests/test_http_request.py::XmlRpcRequestTest::test_eq tests/test_http_request.py::XmlRpcRequestTest::test_errback_is_callable tests/test_http_request.py::XmlRpcRequestTest::test_headers tests/test_http_request.py::XmlRpcRequestTest::test_immutable_attributes tests/test_http_request.py::XmlRpcRequestTest::test_init tests/test_http_request.py::XmlRpcRequestTest::test_latin1 tests/test_http_request.py::XmlRpcRequestTest::test_method_always_str tests/test_http_request.py::XmlRpcRequestTest::test_replace tests/test_http_request.py::XmlRpcRequestTest::test_url tests/test_http_request.py::XmlRpcRequestTest::test_url_encoding tests/test_http_request.py::XmlRpcRequestTest::test_url_encoding_nonutf8_untouched tests/test_http_request.py::XmlRpcRequestTest::test_url_encoding_other tests/test_http_request.py::XmlRpcRequestTest::test_url_encoding_query tests/test_http_request.py::XmlRpcRequestTest::test_url_encoding_query_latin1 tests/test_http_request.py::XmlRpcRequestTest::test_url_no_scheme tests/test_http_request.py::XmlRpcRequestTest::test_url_quoting tests/test_http_request.py::XmlRpcRequestTest::test_xmlrpc_dumps tests/test_http_request.py::JSONRequestTest::test_ajax_url tests/test_http_request.py::JSONRequestTest::test_body tests/test_http_request.py::JSONRequestTest::test_body_data tests/test_http_request.py::JSONRequestTest::test_body_data_none tests/test_http_request.py::JSONRequestTest::test_body_none_data tests/test_http_request.py::JSONRequestTest::test_callback_is_callable tests/test_http_request.py::JSONRequestTest::test_copy tests/test_http_request.py::JSONRequestTest::test_copy_inherited_classes tests/test_http_request.py::JSONRequestTest::test_data tests/test_http_request.py::JSONRequestTest::test_data_method tests/test_http_request.py::JSONRequestTest::test_dumps_kwargs tests/test_http_request.py::JSONRequestTest::test_dumps_sort_keys tests/test_http_request.py::JSONRequestTest::test_empty_body_data tests/test_http_request.py::JSONRequestTest::test_eq tests/test_http_request.py::JSONRequestTest::test_errback_is_callable tests/test_http_request.py::JSONRequestTest::test_headers tests/test_http_request.py::JSONRequestTest::test_immutable_attributes tests/test_http_request.py::JSONRequestTest::test_init tests/test_http_request.py::JSONRequestTest::test_method_always_str tests/test_http_request.py::JSONRequestTest::test_replace tests/test_http_request.py::JSONRequestTest::test_replace_data tests/test_http_request.py::JSONRequestTest::test_replace_dumps_kwargs tests/test_http_request.py::JSONRequestTest::test_replace_sort_keys tests/test_http_request.py::JSONRequestTest::test_url tests/test_http_request.py::JSONRequestTest::test_url_encoding tests/test_http_request.py::JSONRequestTest::test_url_encoding_nonutf8_untouched tests/test_http_request.py::JSONRequestTest::test_url_encoding_other tests/test_http_request.py::JSONRequestTest::test_url_encoding_query tests/test_http_request.py::JSONRequestTest::test_url_encoding_query_latin1 tests/test_http_request.py::JSONRequestTest::test_url_no_scheme tests/test_http_request.py::JSONRequestTest::test_url_quoting
scrapy/http/request/form.py
scrapy/http/request/form.py