Offenlegung einer WSDL-Datei mit sensiblen Informationen

Beschreibung

Die Offenlegung einer WSDL-Datei mit sensiblen Informationen tritt auf, wenn Web Services Description Language (WSDL)-Dateien öffentlich zugänglich sind und Informationen enthalten, die Angreifern helfen. WSDL-Dateien beschreiben Webservice-Schnittstellen, Methoden, Parameter und Endpunkte. Wenn sie exponiert werden, offenbaren sie die Angriffsfläche, interne Methodennamen, Datenstrukturen, versteckte administrative Funktionen und manchmal Anmeldedaten oder interne URLs.

Risiko

Angreifer entdecken alle verfügbaren Webservice-Methoden. Interne administrative Funktionen werden offenbart. Datenstrukturen legen Datenbank-Schema-Informationen offen. Versteckte Debug- oder Testmethoden werden zugänglich. Interne URLs und Server-Topologie werden offengelegt. Parametertypen helfen bei der Erstellung von Injection-Angriffen. Anmeldedaten oder API-Schlüssel können in WSDLs eingebettet sein.

Lösung

Deaktivieren Sie WSDL-Auto-Generierung in der Produktion. Beschränken Sie den Zugriff auf WSDL-Endpunkte. Entfernen Sie sensible Methoden aus öffentlichen WSDLs. Erstellen Sie separate WSDLs für internen/externen Gebrauch. Implementieren Sie Authentifizierung für WSDL-Zugriff. Überprüfen Sie WSDLs vor der Bereitstellung. Entfernen Sie Kommentare mit sensiblen Informationen.

Häufige Konsequenzen

AuswirkungDetails
VertraulichkeitUmfang: Informationsoffenlegung

Service-Struktur und Methoden werden offenbart.
ZugriffskontrolleUmfang: Angriffsfläche

Versteckte Methoden werden für Angreifer zugänglich.
SicherheitUmfang: Aufklärung

Detaillierte Informationen unterstützen weitere Angriffe.

Beispielcode + Lösungscode

Anfälliger Code

<!-- ANFÄLLIG: WSDL legt sensible Informationen offen -->
<?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">

    <!-- ANFÄLLIG: Legt internen Endpunkt offen -->
    <wsdl:service name="UserManagementService">
        <wsdl:port name="UserManagementPort" binding="tns:UserManagementBinding">
            <!-- Interne URL exponiert! -->
            <soap:address location="http://internal-server.company.local:8080/ws/users"/>
        </wsdl:port>
    </wsdl:service>

    <!-- ANFÄLLIG: Legt Admin-Methoden offen -->
    <wsdl:portType name="UserManagementPortType">
        <wsdl:operation name="getUser">
            <wsdl:input message="tns:getUserRequest"/>
            <wsdl:output message="tns:getUserResponse"/>
        </wsdl:operation>

        <!-- Admin-Funktionen exponiert! -->
        <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-Methode exponiert! -->
        <wsdl:operation name="debugDumpDatabase">
            <wsdl:input message="tns:debugRequest"/>
            <wsdl:output message="tns:debugResponse"/>
        </wsdl:operation>
    </wsdl:portType>

    <!-- ANFÄLLIG: Legt Datenstruktur-Details offen -->
    <wsdl:types>
        <xs:schema>
            <xs:complexType name="User">
                <!-- Offenbart Datenbank-Spaltennamen -->
                <xs:element name="user_id" type="xs:int"/>
                <xs:element name="password_hash" type="xs:string"/>  <!-- Offenbart dass dieses Feld existiert -->
                <xs:element name="social_security_number" type="xs:string"/>  <!-- PII-Feld exponiert -->
                <xs:element name="credit_card_number" type="xs:string"/>  <!-- Zahlungsdaten exponiert -->
                <xs:element name="internal_role_code" type="xs:int"/>  <!-- Internes Enum exponiert -->
            </xs:complexType>
        </xs:schema>
    </wsdl:types>

    <!-- ANFÄLLIG: Kommentare mit sensiblen Infos -->
    <!-- Standard-Admin-Passwort: admin123 -->
    <!-- Datenbank: mysql://root:[email protected]/users -->
    <!-- API-Schlüssel für Tests: sk_test_1234567890abcdef -->

</wsdl:definitions>
// ANFÄLLIG: Auto-generierte WSDL-Exponierung
@WebService(
    name = "UserService",
    serviceName = "UserManagementService",
    targetNamespace = "http://internal.company.com/services"
)
public class VulnerableUserService {

    // Öffentliche Methode - OK
    @WebMethod
    public User getUser(int userId) {
        return userRepository.findById(userId);
    }

    // ANFÄLLIG: Admin-Methode in WSDL exponiert
    @WebMethod
    public void deleteAllUsers(String adminPassword) {
        // Diese Methode erscheint in öffentlicher WSDL!
        if (adminPassword.equals("secret123")) {
            userRepository.deleteAll();
        }
    }

    // ANFÄLLIG: Debug-Methode exponiert
    @WebMethod
    public String debugDatabaseDump() {
        // Sichtbar in WSDL!
        return databaseService.dumpAllTables();
    }

    // ANFÄLLIG: Interne Methode exponiert
    @WebMethod
    public void internalProcessPayroll() {
        // Sollte nur intern sein!
        payrollService.process();
    }
}

// ANFÄLLIG: JAX-WS Konfiguration exponiert WSDL
// web.xml erlaubt nicht-authentifizierten Zugriff auf 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>
<!-- Keine Sicherheitsbeschränkung - WSDL zugänglich bei /services/users?wsdl -->
*/
// ANFÄLLIG: .NET WCF-Service mit exponierter WSDL
[ServiceContract]
public interface IUserService
{
    [OperationContract]
    User GetUser(int userId);

    // ANFÄLLIG: Admin-Operationen im öffentlichen Vertrag
    [OperationContract]
    void DeleteAllUsers(string adminKey);

    [OperationContract]
    string GetDatabaseConnectionString();

    [OperationContract]
    void ExecuteArbitrarySQL(string sql);  // Sehr gefährlich!
}

// ANFÄLLIG: WCF-Konfiguration aktiviert Metadaten
/*
<system.serviceModel>
  <behaviors>
    <serviceBehaviors>
      <behavior>
        <!-- ANFÄLLIG: Exponiert WSDL bei ?wsdl -->
        <serviceMetadata httpGetEnabled="true"/>
      </behavior>
    </serviceBehaviors>
  </behaviors>
</system.serviceModel>
*/
# ANFÄLLIG: Python SOAP-Service mit exponierten Methoden
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)

    # ANFÄLLIG: Admin-Methode im öffentlichen Service
    @rpc(Unicode, _returns=Unicode)
    def delete_all_data(ctx, admin_password):
        if admin_password == "secret":
            db.delete_all()
            return "Alle Daten gelöscht"

    # ANFÄLLIG: Debug-Methode exponiert
    @rpc(_returns=Unicode)
    def debug_dump_config(ctx):
        # Gibt sensible Konfiguration zurück
        return str(app.config)

    # ANFÄLLIG: Interne Methode exponiert
    @rpc(Unicode, Unicode, _returns=Unicode)
    def internal_execute_sql(ctx, query, password):
        # Beliebige SQL-Ausführung!
        return db.execute(query)

# WSDL automatisch generiert und exponiert
app = Application([VulnerableUserService],
    tns='http://internal.company.com/services',
    in_protocol=Soap11(),
    out_protocol=Soap11())

# ANFÄLLIG: WSDL verfügbar unter /services?wsdl

Korrigierter Code

<!-- SICHER: Eingeschränkte WSDL für externe Nutzung -->
<?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">

    <!-- SICHER: Nur öffentlicher Endpunkt exponiert -->
    <wsdl:service name="UserService">
        <wsdl:port name="UserPort" binding="tns:UserBinding">
            <soap:address location="https://api.company.com/v1/users"/>
        </wsdl:port>
    </wsdl:service>

    <!-- SICHER: Nur öffentliche Methoden -->
    <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>

        <!-- Keine Admin/Debug-Methoden -->
    </wsdl:portType>

    <!-- SICHER: Begrenzte Datenexponierung -->
    <wsdl:types>
        <xs:schema>
            <xs:complexType name="PublicProfile">
                <!-- Nur öffentliche Felder -->
                <xs:element name="displayName" type="xs:string"/>
                <xs:element name="bio" type="xs:string"/>
                <xs:element name="avatarUrl" type="xs:string"/>
                <!-- Keine internen Felder, keine PII -->
            </xs:complexType>
        </xs:schema>
    </wsdl:types>

    <!-- SICHER: Keine sensiblen Kommentare -->

</wsdl:definitions>
// SICHER: Separate öffentliche und interne Services
@WebService(name = "PublicUserService")
public class PublicUserService {

    // Nur öffentliche Methoden exponiert
    @WebMethod
    public PublicProfile getProfile(String userId) {
        User user = userRepository.findById(userId);
        return user.toPublicProfile();  // Filtere sensible Daten
    }

    @WebMethod
    public boolean updateProfile(String userId, PublicProfile profile) {
        // Validiere und aktualisiere nur erlaubte Felder
        return userService.updatePublicProfile(userId, profile);
    }
}

// SICHER: Interner Service auf separatem Endpunkt
@WebService(name = "InternalAdminService")
@Authenticated  // Erfordert Authentifizierung
@RolesAllowed("ADMIN")
public class InternalAdminService {

    @WebMethod
    public void deleteUser(String adminToken, String userId) {
        if (!authService.validateAdminToken(adminToken)) {
            throw new SecurityException("Ungültiges Admin-Token");
        }
        userRepository.delete(userId);
    }
}

// SICHER: Auto-WSDL in Produktion deaktivieren
/*
<!-- web.xml für Produktion -->
<context-param>
    <param-name>com.sun.xml.ws.transport.http.HttpAdapter.publishWsdl</param-name>
    <param-value>false</param-value>  <!-- WSDL-Generierung deaktivieren -->
</context-param>

<!-- Oder Authentifizierung für WSDL erforderlich -->
<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>
*/
// SICHER: .NET WCF mit eingeschränkten Metadaten
[ServiceContract]
public interface IPublicUserService
{
    // Nur öffentliche Operationen
    [OperationContract]
    PublicProfile GetProfile(string userId);

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

// SICHER: Separater interner Service
[ServiceContract]
internal interface IInternalAdminService
{
    [OperationContract]
    void DeleteUser(string adminToken, string userId);
}

// SICHER: WCF-Konfiguration - Metadaten in Produktion deaktivieren
/*
<system.serviceModel>
  <behaviors>
    <serviceBehaviors>
      <behavior>
        <!-- SICHER: Öffentliche Metadaten in Produktion deaktivieren -->
        <serviceMetadata httpGetEnabled="false" httpsGetEnabled="false"/>

        <!-- Oder auf bestimmten Endpunkt mit Auth beschränken -->
        <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>
*/
# SICHER: Python mit eingeschränkter WSDL
from spyne import Application, rpc, ServiceBase, Unicode
from spyne.protocol.soap import Soap11
from spyne.server.wsgi import WsgiApplication

# SICHER: Öffentlicher Service nur mit erlaubten Methoden
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()  # Nur öffentliche Felder

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

    # Keine Admin/Debug-Methoden im öffentlichen Service

# SICHER: Separater interner 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("Ungültiges Admin-Token")
        return delete_user(user_id)

# SICHER: Nur öffentlichen Service extern exponieren
public_app = Application(
    [PublicUserService],
    tns='https://api.company.com/public',
    in_protocol=Soap11(),
    out_protocol=Soap11()
)

# SICHER: Benutzerdefinierter WSDL-Handler mit Authentifizierung
class SecureWSDLMiddleware:
    def __init__(self, app):
        self.app = app

    def __call__(self, environ, start_response):
        # Prüfe ob WSDL angefordert wird
        if environ.get('QUERY_STRING') == 'wsdl':
            # Erfordere Authentifizierung für WSDL-Zugriff
            if not self.is_authenticated(environ):
                start_response('401 Unauthorized', [])
                return [b'Authentifizierung für WSDL-Zugriff erforderlich']

        return self.app(environ, start_response)

    def is_authenticated(self, environ):
        # Implementiere Authentifizierungsprüfung
        auth_header = environ.get('HTTP_AUTHORIZATION')
        return validate_auth(auth_header)

wsgi_app = SecureWSDLMiddleware(WsgiApplication(public_app))

Ausgenutzt in der Praxis

API-Entdeckung

WSDLs werden verwendet um versteckte administrative APIs zu entdecken.

SQL-Injection

Methodensignaturen aus WSDLs unterstützten Injection-Angriffe.

Datenleck

Interne Datenstrukturen durch WSDLs offenbart.


Tools zum Testen/Ausnutzen

  • SoapUI - SOAP-Tests und -Entdeckung.

  • WS-Attacker - Webservice-Angriffe.

  • Nmap NSE-Skripte für WSDL-Entdeckung.


CVE-Beispiele

  • CVEs von exponierten internen Webservices.

  • Informationsoffenlegung über WSDL.


Referenzen

  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."