Exposure of WSDL File Containing Sensitive Information

Description

Exposure of WSDL File Containing Sensitive Information occurs when Web Services Description Language (WSDL) files are publicly accessible and contain information that aids attackers. WSDL files describe web service interfaces, methods, parameters, and endpoints. When exposed, they reveal the attack surface, internal method names, data structures, hidden administrative functions, and sometimes credentials or internal URLs.

Risk

Attackers discover all available web service methods. Internal administrative functions revealed. Data structures expose database schema information. Hidden debug or test methods become accessible. Internal URLs and server topology disclosed. Parameter types help craft injection attacks. Credentials or API keys may be embedded in WSDLs.

Solution

Disable WSDL auto-generation in production. Restrict access to WSDL endpoints. Remove sensitive methods from public WSDLs. Create separate WSDLs for internal/external use. Implement authentication for WSDL access. Review WSDLs before deployment. Remove comments containing sensitive information.

Common Consequences

ImpactDetails
ConfidentialityScope: Information Disclosure

Service structure and methods revealed.
Access ControlScope: Attack Surface

Hidden methods become accessible to attackers.
SecurityScope: Reconnaissance

Detailed information aids further attacks.

Example Code + Solution Code

Vulnerable Code

<!-- VULNERABLE: WSDL exposing sensitive information -->
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
                  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
                  xmlns:tns="http://internal.company.com/services"
                  name="UserManagementService"
                  targetNamespace="http://internal.company.com/services">

    <!-- VULNERABLE: Exposes internal endpoint -->
    <wsdl:service name="UserManagementService">
        <wsdl:port name="UserManagementPort" binding="tns:UserManagementBinding">
            <!-- Internal URL exposed! -->
            <soap:address location="http://internal-server.company.local:8080/ws/users"/>
        </wsdl:port>
    </wsdl:service>

    <!-- VULNERABLE: Exposes admin methods -->
    <wsdl:portType name="UserManagementPortType">
        <wsdl:operation name="getUser">
            <wsdl:input message="tns:getUserRequest"/>
            <wsdl:output message="tns:getUserResponse"/>
        </wsdl:operation>

        <!-- Admin functions exposed! -->
        <wsdl:operation name="deleteAllUsers">
            <wsdl:input message="tns:deleteAllUsersRequest"/>
            <wsdl:output message="tns:deleteAllUsersResponse"/>
        </wsdl:operation>

        <wsdl:operation name="createAdminUser">
            <wsdl:input message="tns:createAdminUserRequest"/>
            <wsdl:output message="tns:createAdminUserResponse"/>
        </wsdl:operation>

        <!-- Debug method exposed! -->
        <wsdl:operation name="debugDumpDatabase">
            <wsdl:input message="tns:debugRequest"/>
            <wsdl:output message="tns:debugResponse"/>
        </wsdl:operation>
    </wsdl:portType>

    <!-- VULNERABLE: Exposes data structure details -->
    <wsdl:types>
        <xs:schema>
            <xs:complexType name="User">
                <!-- Reveals database column names -->
                <xs:element name="user_id" type="xs:int"/>
                <xs:element name="password_hash" type="xs:string"/>  <!-- Exposes this field exists -->
                <xs:element name="social_security_number" type="xs:string"/>  <!-- PII field exposed -->
                <xs:element name="credit_card_number" type="xs:string"/>  <!-- Payment data exposed -->
                <xs:element name="internal_role_code" type="xs:int"/>  <!-- Internal enum exposed -->
            </xs:complexType>
        </xs:schema>
    </wsdl:types>

    <!-- VULNERABLE: Comments with sensitive info -->
    <!-- Default admin password: admin123 -->
    <!-- Database: mysql://root:[email protected]/users -->
    <!-- API Key for testing: sk_test_1234567890abcdef -->

</wsdl:definitions>
// VULNERABLE: Auto-generated WSDL exposure
@WebService(
    name = "UserService",
    serviceName = "UserManagementService",
    targetNamespace = "http://internal.company.com/services"
)
public class VulnerableUserService {

    // Public method - OK
    @WebMethod
    public User getUser(int userId) {
        return userRepository.findById(userId);
    }

    // VULNERABLE: Admin method exposed in WSDL
    @WebMethod
    public void deleteAllUsers(String adminPassword) {
        // This method shows up in public WSDL!
        if (adminPassword.equals("secret123")) {
            userRepository.deleteAll();
        }
    }

    // VULNERABLE: Debug method exposed
    @WebMethod
    public String debugDatabaseDump() {
        // Visible in WSDL!
        return databaseService.dumpAllTables();
    }

    // VULNERABLE: Internal method exposed
    @WebMethod
    public void internalProcessPayroll() {
        // Should be internal only!
        payrollService.process();
    }
}

// VULNERABLE: JAX-WS configuration exposing WSDL
// web.xml allows unauthenticated access to WSDL
/*
<servlet>
    <servlet-name>UserService</servlet-name>
    <servlet-class>com.sun.xml.ws.transport.http.servlet.WSServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>UserService</servlet-name>
    <url-pattern>/services/users</url-pattern>
</servlet-mapping>
<!-- No security constraint - WSDL accessible at /services/users?wsdl -->
*/
// VULNERABLE: .NET WCF service with exposed WSDL
[ServiceContract]
public interface IUserService
{
    [OperationContract]
    User GetUser(int userId);

    // VULNERABLE: Admin operations in public contract
    [OperationContract]
    void DeleteAllUsers(string adminKey);

    [OperationContract]
    string GetDatabaseConnectionString();

    [OperationContract]
    void ExecuteArbitrarySQL(string sql);  // Very dangerous!
}

// VULNERABLE: WCF configuration enabling metadata
/*
<system.serviceModel>
  <behaviors>
    <serviceBehaviors>
      <behavior>
        <!-- VULNERABLE: Exposes WSDL at ?wsdl -->
        <serviceMetadata httpGetEnabled="true"/>
      </behavior>
    </serviceBehaviors>
  </behaviors>
</system.serviceModel>
*/
# VULNERABLE: Python SOAP service with exposed methods
from spyne import Application, rpc, ServiceBase, Unicode, Integer
from spyne.protocol.soap import Soap11
from spyne.server.wsgi import WsgiApplication

class VulnerableUserService(ServiceBase):

    @rpc(Integer, _returns=Unicode)
    def get_user(ctx, user_id):
        return get_user_from_db(user_id)

    # VULNERABLE: Admin method in public service
    @rpc(Unicode, _returns=Unicode)
    def delete_all_data(ctx, admin_password):
        if admin_password == "secret":
            db.delete_all()
            return "All data deleted"

    # VULNERABLE: Debug method exposed
    @rpc(_returns=Unicode)
    def debug_dump_config(ctx):
        # Returns sensitive configuration
        return str(app.config)

    # VULNERABLE: Internal method exposed
    @rpc(Unicode, Unicode, _returns=Unicode)
    def internal_execute_sql(ctx, query, password):
        # Arbitrary SQL execution!
        return db.execute(query)

# WSDL automatically generated and exposed
app = Application([VulnerableUserService],
    tns='http://internal.company.com/services',
    in_protocol=Soap11(),
    out_protocol=Soap11())

# VULNERABLE: WSDL available at /services?wsdl

Fixed Code

<!-- SAFE: Restricted WSDL for external use -->
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
                  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
                  xmlns:tns="http://api.company.com/services"
                  name="UserService"
                  targetNamespace="http://api.company.com/services">

    <!-- SAFE: Only public endpoint exposed -->
    <wsdl:service name="UserService">
        <wsdl:port name="UserPort" binding="tns:UserBinding">
            <soap:address location="https://api.company.com/v1/users"/>
        </wsdl:port>
    </wsdl:service>

    <!-- SAFE: Only public methods -->
    <wsdl:portType name="UserPortType">
        <wsdl:operation name="getPublicProfile">
            <wsdl:input message="tns:getProfileRequest"/>
            <wsdl:output message="tns:getProfileResponse"/>
        </wsdl:operation>

        <wsdl:operation name="updateProfile">
            <wsdl:input message="tns:updateProfileRequest"/>
            <wsdl:output message="tns:updateProfileResponse"/>
        </wsdl:operation>

        <!-- No admin/debug methods -->
    </wsdl:portType>

    <!-- SAFE: Limited data exposure -->
    <wsdl:types>
        <xs:schema>
            <xs:complexType name="PublicProfile">
                <!-- Only public fields -->
                <xs:element name="displayName" type="xs:string"/>
                <xs:element name="bio" type="xs:string"/>
                <xs:element name="avatarUrl" type="xs:string"/>
                <!-- No internal fields, no PII -->
            </xs:complexType>
        </xs:schema>
    </wsdl:types>

    <!-- SAFE: No sensitive comments -->

</wsdl:definitions>
// SAFE: Separate public and internal services
@WebService(name = "PublicUserService")
public class PublicUserService {

    // Only public methods exposed
    @WebMethod
    public PublicProfile getProfile(String userId) {
        User user = userRepository.findById(userId);
        return user.toPublicProfile();  // Filter sensitive data
    }

    @WebMethod
    public boolean updateProfile(String userId, PublicProfile profile) {
        // Validate and update only allowed fields
        return userService.updatePublicProfile(userId, profile);
    }
}

// SAFE: Internal service on separate endpoint
@WebService(name = "InternalAdminService")
@Authenticated  // Requires authentication
@RolesAllowed("ADMIN")
public class InternalAdminService {

    @WebMethod
    public void deleteUser(String adminToken, String userId) {
        if (!authService.validateAdminToken(adminToken)) {
            throw new SecurityException("Invalid admin token");
        }
        userRepository.delete(userId);
    }
}

// SAFE: Disable auto-WSDL in production
/*
<!-- web.xml for production -->
<context-param>
    <param-name>com.sun.xml.ws.transport.http.HttpAdapter.publishWsdl</param-name>
    <param-value>false</param-value>  <!-- Disable WSDL generation -->
</context-param>

<!-- Or require authentication for WSDL -->
<security-constraint>
    <web-resource-collection>
        <web-resource-name>WSDL Access</web-resource-name>
        <url-pattern>/services/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>developer</role-name>
    </auth-constraint>
</security-constraint>
*/
// SAFE: .NET WCF with restricted metadata
[ServiceContract]
public interface IPublicUserService
{
    // Only public operations
    [OperationContract]
    PublicProfile GetProfile(string userId);

    [OperationContract]
    bool UpdateProfile(string userId, PublicProfile profile);
}

// SAFE: Separate internal service
[ServiceContract]
internal interface IInternalAdminService
{
    [OperationContract]
    void DeleteUser(string adminToken, string userId);
}

// SAFE: WCF configuration - disable metadata in production
/*
<system.serviceModel>
  <behaviors>
    <serviceBehaviors>
      <behavior>
        <!-- SAFE: Disable public metadata in production -->
        <serviceMetadata httpGetEnabled="false" httpsGetEnabled="false"/>

        <!-- Or restrict to specific endpoint with auth -->
        <serviceMetadata>
          <serviceMetadataEndpoint
            binding="wsHttpBinding"
            bindingConfiguration="secureMetadataBinding"/>
        </serviceMetadata>
      </behavior>
    </serviceBehaviors>
  </behaviors>

  <bindings>
    <wsHttpBinding>
      <binding name="secureMetadataBinding">
        <security mode="Transport">
          <transport clientCredentialType="Certificate"/>
        </security>
      </binding>
    </wsHttpBinding>
  </bindings>
</system.serviceModel>
*/
# SAFE: Python with restricted WSDL
from spyne import Application, rpc, ServiceBase, Unicode
from spyne.protocol.soap import Soap11
from spyne.server.wsgi import WsgiApplication

# SAFE: Public service with only allowed methods
class PublicUserService(ServiceBase):

    @rpc(Unicode, _returns=Unicode)
    def get_public_profile(ctx, user_id):
        user = get_user_from_db(user_id)
        return user.to_public_json()  # Only public fields

    @rpc(Unicode, Unicode, _returns=Unicode)
    def update_profile(ctx, user_id, profile_json):
        return update_user_profile(user_id, profile_json)

    # No admin/debug methods in public service

# SAFE: Separate internal admin service
class InternalAdminService(ServiceBase):

    @rpc(Unicode, Unicode, _returns=Unicode)
    def delete_user(ctx, admin_token, user_id):
        if not validate_admin_token(admin_token):
            raise PermissionError("Invalid admin token")
        return delete_user(user_id)

# SAFE: Only expose public service externally
public_app = Application(
    [PublicUserService],
    tns='https://api.company.com/public',
    in_protocol=Soap11(),
    out_protocol=Soap11()
)

# SAFE: Custom WSDL handler with authentication
class SecureWSDLMiddleware:
    def __init__(self, app):
        self.app = app

    def __call__(self, environ, start_response):
        # Check if requesting WSDL
        if environ.get('QUERY_STRING') == 'wsdl':
            # Require authentication for WSDL access
            if not self.is_authenticated(environ):
                start_response('401 Unauthorized', [])
                return [b'Authentication required for WSDL access']

        return self.app(environ, start_response)

    def is_authenticated(self, environ):
        # Implement authentication check
        auth_header = environ.get('HTTP_AUTHORIZATION')
        return validate_auth(auth_header)

wsgi_app = SecureWSDLMiddleware(WsgiApplication(public_app))

Exploited in the Wild

API Discovery

WSDLs used to discover hidden administrative APIs.

SQL Injection

Method signatures from WSDLs aided injection attacks.

Data Breach

Internal data structures revealed through WSDLs.


Tools to test/exploit

  • SoapUI — SOAP testing and discovery.

  • WS-Attacker — web service attacks.

  • Nmap NSE scripts for WSDL discovery.


CVE Examples

  • CVEs from exposed internal web services.

  • Information disclosure via WSDL.


References

  1. MITRE. "CWE-651: Exposure of WSDL File Containing Sensitive Information." https://cwe.mitre.org/data/definitions/651.html

  2. OWASP. "Web Services Security Cheat Sheet."