Skip to content

SoapClient only strips Authorization: Basic when a WSDL imports from another host #23686

Closed

Description

@alexandre-daubois

Description

Originally reported by @christos-cantina-security.

When a WSDL imports a schema or another WSDL from a different host, SoapClient clears the credentials from the stream context before fetching it in sdl_set_uri_credentials() @ ext/soap/php_sdl.c. The strip only matches the exact string Authorization: Basic in http.header, and only when that option is a string. The TODO right above it already notes that the array form of http.header is not handled.

Any other credential passes through to the third-party host as-is: a Bearer or Digest Authorization header, a lowercase authorization: header, a Cookie header, a Proxy-Authorization header, and every header given as an array.

The following code, with https://a.example serving a WSDL that contains <xsd:import schemaLocation="https://b.example/schema.xsd"/>:

<?php
$client = new SoapClient('https://a.example/service.wsdl', [
    'cache_wsdl' => WSDL_CACHE_NONE,
    'stream_context' => stream_context_create([
        'http' => ['header' => "Authorization: Bearer secret-token\r\n"],
    ]),
]);

Resulted in this request on b.example:

GET /schema.xsd HTTP/1.1
Host: b.example
Authorization: Bearer secret-token

But I expected the header to be dropped on the host change, like Authorization: Basic already is:

GET /schema.xsd HTTP/1.1
Host: b.example

This should be hardened on master, potentially introducing a BC break:

  • strip every Authorization, Proxy-Authorization and Cookie header, case-insensitively
  • handle the array form of http.header, resolving the existing TODO
  • add a SoapClient option to keep the headers for setups where the same credentials serve both hosts
  • document the behavior change in UPGRADING

PHP Version

master

Operating System

No response

Activity

  1. alexandre-daubois commented on Sep 14, 2026

    @alexandre-daubois
    MemberAuthor

    Would you like to have a look at this hardening @DanielEScherzer?

  2. bukka commented on Sep 14, 2026

    @bukka
    Member

    I think this should be just for master as there is a real BC break risk and might be worth also considering to have options to allow in case someone really need to propagate (which is quite possible).

  3. alexandre-daubois commented on Sep 14, 2026

    @alexandre-daubois
    MemberAuthor

    I wrote it in the issue but I think it wasn't visible enough. I also put master in the PHP version block

  4. DanielEScherzer commented on Sep 16, 2026

    @DanielEScherzer
    Member

    I've been working on this, and can reproduce the bearer authorization not being stripped, but I'm confused by the note about the non-string http.header option. I am unable to reproduce this locally - can you provide a stream_context_create() call where the header is Authorization: Basic... and it is not stripped?

  5. DanielEScherzer commented on Sep 16, 2026

    @DanielEScherzer
    Member

    I've started work on this at #23698 - it still needs some documentation and UPGRADING, but should address the other string headers

    This report was only about the use of SoapClient - there is another call to sdl_set_uri_credentials() in schema_load_file() - does that also need to be addressed? I'm not familiar with the soap extension and so don't know how schema_load_file() is triggered

  6. mbeccati commented on Sep 16, 2026

    @mbeccati
    Contributor

    Does this also apply to the authentication, login, and password SOAP options, or only if a context is used?

  7. alexandre-daubois commented on Sep 16, 2026

    @alexandre-daubois
    MemberAuthor

    @DanielEScherzer The array form barely happens in practice, get_sdl() flattens the context headers into one string before loading the WSDL, so you can skip it. schema_load_file() is the xsd:import path my repro hits, and it goes through the same sdl_set_uri_credentials(), so your fix already covers it.

    @mbeccati login and password become an Authorization: Basic header in the same string, which the current code already strips, so only headers passed through the stream context leak.

  8. DanielEScherzer commented on Sep 16, 2026

    @DanielEScherzer
    Member

    @DanielEScherzer The array form barely happens in practice, get_sdl() flattens the context headers into one string before loading the WSDL, so you can skip it. schema_load_file() is the xsd:import path my repro hits, and it goes through the same sdl_set_uri_credentials(), so your fix already covers it.

    It should be removing the headers, but my question was about how to keep the headers when specifying that some should be kept

  9. mbeccati commented on Sep 16, 2026

    @mbeccati
    Contributor
  10. alexandre-daubois commented on Sep 16, 2026

    @alexandre-daubois
    MemberAuthor

    @DanielEScherzer Sorry, I misread you. Since schema_load_file() has no this_ptr, I think the simplest way is to store the keep flags in sdlCtx from load_wsdl(), where this_ptr is available, and read them inside sdl_set_uri_credentials() so both callers get the same behaviour without the extra parameter.

    @mbeccati With SOAP_AUTHENTICATION_DIGEST, basic_authentication() adds nothing and the Digest header is only built in php_http.c for SOAP calls, so no Authorization header is sent at all while loading the WSDL.

  11. added 2 commits that reference this issue on Sep 16, 2026
  12. added 5 commits that reference this issue on Sep 28, 2026
  13. added 2 commits that reference this issue on Oct 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions