WS-Security
SOAP’s security framework. Adds message-level signing and encryption on top of (or instead of) transport-level TLS. Used heavily in banking, government, healthcare — anywhere “TLS is not enough” or “the message must be auditable end-to-end.”
Most Python developers consuming SOAP services only need to know: username token (basic), signed timestamps, occasionally signed bodies.
What WS-Security adds beyond TLS
| TLS | WS-Security | |
|---|---|---|
| Where | transport layer | message body (XML signature/encryption) |
| Scope | point-to-point | end-to-end through intermediaries |
| Protects | wire transmission | the XML message itself |
| Headers vs body | covers both | configurable per element |
| Persistence | gone once decrypted | embedded in the message; can be audit-logged |
| Required for | most internet traffic | regulated industries / multi-hop systems |
The use case: messages traverse multiple intermediaries (gateways, message brokers, internal proxies). TLS terminates at each hop; WS-Security stays attached to the message body itself.
The Security header
<soap:Envelope ...>
<soap:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
soap:mustUnderstand="1">
<!-- Username token, signature, encryption, timestamp go here -->
</wsse:Security>
</soap:Header>
<soap:Body>...</soap:Body>
</soap:Envelope>
soap:mustUnderstand="1" tells the receiver: “you must handle this header; if you can’t, fail.” Standard for security headers.
Username Token — the simplest
For basic-auth-like credentials inside the message:
<wsse:Security>
<wsse:UsernameToken>
<wsse:Username>alice</wsse:Username>
<wsse:Password Type="...PasswordText">secret123</wsse:Password>
<wsse:Nonce>random-bytes</wsse:Nonce>
<wsu:Created>2024-01-15T10:30:00Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
Two password types:
| Type | Meaning |
|---|---|
PasswordText |
plaintext (under TLS or you’re in trouble) |
PasswordDigest |
SHA1(nonce + created + password) |
PasswordDigest is the “hashed” form but uses SHA1 — broken cryptographically. Modern services use TLS + PasswordText, or stronger tokens (X.509, SAML).
Server validates: username/password match, nonce hasn’t been seen recently (replay prevention), Created timestamp is fresh (clock skew window).
Timestamps
<wsse:Security>
<wsu:Timestamp xmlns:wsu="...wsutility">
<wsu:Created>2024-01-15T10:30:00Z</wsu:Created>
<wsu:Expires>2024-01-15T10:35:00Z</wsu:Expires>
</wsu:Timestamp>
</wsse:Security>
5-minute window. Receiver rejects messages outside it (anti-replay). Combined with nonce tracking for stronger replay protection.
XML Signature
For integrity + non-repudiation, sign specific elements:
<wsse:Security>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<ds:Reference URI="#body-id">
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>...</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>...</ds:SignatureValue>
<ds:KeyInfo>
<wsse:SecurityTokenReference>
<wsse:Reference URI="#cert-id"/>
</wsse:SecurityTokenReference>
</ds:KeyInfo>
</ds:Signature>
<wsse:BinarySecurityToken wsu:Id="cert-id" EncodingType="...Base64Binary"
ValueType="...X509v3">MIIB...</wsse:BinarySecurityToken>
</wsse:Security>
<soap:Body wsu:Id="body-id">...</soap:Body>
The signature is over the canonicalized form of the element with wsu:Id="body-id". Steps to verify:
- Canonicalize the referenced element (apply exclusive canonicalization).
- Hash with the digest method (SHA256 here).
- Compare to
DigestValue. - Canonicalize SignedInfo.
- Verify signature with the public key from
BinarySecurityToken.
XML Signature is famous for signature wrapping (XSW) vulnerabilities — same class as SAML’s XSW. See ../../11_authentication/sso/08_attack_vectors.md.
XML Encryption
For confidentiality of specific elements:
<soap:Body>
<xenc:EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Content">
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2009/xmlenc11#aes128-gcm"/>
<ds:KeyInfo>
<xenc:EncryptedKey>...</xenc:EncryptedKey>
</ds:KeyInfo>
<xenc:CipherData>
<xenc:CipherValue>...</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedData>
</soap:Body>
The plaintext body element is replaced by <EncryptedData>. Sender encrypts with a fresh symmetric key; encrypts that key with the recipient’s RSA public key; bundles both.
Recipient: decrypts the key with their private key, then decrypts the body.
X.509 Token authentication
Instead of username/password, present a certificate:
<wsse:Security>
<wsse:BinarySecurityToken wsu:Id="my-cert"
EncodingType="...Base64Binary"
ValueType="...X509v3">MIIB...base64 cert...</wsse:BinarySecurityToken>
<ds:Signature>...signed by the cert's private key...</ds:Signature>
</wsse:Security>
The signature proves possession of the cert’s private key — authenticates the sender.
Trust model: the receiver has a CA that signed acceptable client certs (or pinned client cert thumbprints).
This is mTLS-equivalent at the message layer.
SAML token in SOAP
For federated identity in SOAP services:
<wsse:Security>
<saml2:Assertion xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion"
Version="2.0" ID="..." IssueInstant="...">
<saml2:Issuer>https://idp.example.com</saml2:Issuer>
<ds:Signature>...</ds:Signature>
<saml2:Subject>
<saml2:NameID>alice@example.com</saml2:NameID>
</saml2:Subject>
<saml2:AttributeStatement>...</saml2:AttributeStatement>
</saml2:Assertion>
</wsse:Security>
The SAML assertion is the same as in SSO — see ../../11_authentication/sso/01_saml_deep_dive.md. It carries identity claims from an IdP into the SOAP service.
Common WS-Security patterns
Different industries use different combinations:
| Pattern | Use case |
|---|---|
| Username token over TLS | basic enterprise B2B |
| Signed timestamp + body | financial APIs (audit trail) |
| Encrypted body + signature | end-to-end through intermediaries |
| X.509 token + signature | machine-to-machine PKI |
| SAML token | federated identity (between organizations) |
The combination matters — “signed but not encrypted” lets intermediaries read but not modify.
Attacks specific to WS-Security
XML Signature Wrapping (XSW)
Same idea as SAML XSW. The attacker rearranges the XML so the signature validates against one element but the application processes a different one.
<soap:Body>
<usr:GetUser wsu:Id="signed">
<usr:Id>42</usr:Id> <!-- signed; parser ignores -->
</usr:GetUser>
<usr:GetUser>
<usr:Id>1</usr:Id> <!-- NOT signed; parser uses -->
</usr:GetUser>
</soap:Body>
Defense: same-element validation (the parser must use exactly what was signed), schema-strict parsing, careful library choice.
XXE — XML External Entity
Not WS-Security-specific, but every SOAP service is vulnerable if not configured carefully:
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<soap:Envelope>
<soap:Body>
<usr:GetUser><usr:Id>&xxe;</usr:Id></usr:GetUser>
</soap:Body>
</soap:Envelope>
The XML parser fetches file:///etc/passwd and substitutes its contents. Defense: disable DTDs and external entities in your XML parser. Modern libraries do this by default; older ones don’t.
In Python:
import lxml.etree
parser = lxml.etree.XMLParser(resolve_entities=False, no_network=True)
Replay attacks
Capture a signed/encrypted message; resend it. Defense: timestamps + nonce tracking (server remembers recently-seen nonces for the timestamp window).
Signature stripping
Attacker removes the signature header; if the server doesn’t require it, the message is processed unsigned. Defense: mustUnderstand="1" on the Security header; server policy rejects messages without signatures.
When WS-Security matters in practice
Most Python consumers of SOAP services use:
- TLS + UsernameToken: 80% of cases. The Python library handles it.
- TLS + signed timestamp + body: regulated industries. Library config + a cert.
- X.509 token: rare for consumers; common for service-to-service in banks.
- Full WS-Security (sign + encrypt): rare; complex; usually with vendor SDK.
If you have to implement #3 or #4 from scratch, allocate significant time. The libraries help but the WS-Security stack has many footguns.
Common pitfalls
- Trusting
mustUnderstandbut not validating signatures in the application — header present ≠ valid. - XXE not disabled in the XML parser — file read or SSRF via SOAP request.
- Accepting any cert in WS-Security X.509 — must validate against a known CA.
- Clock skew — strict 5-min windows fail when client/server clocks drift. Sync via NTP; allow leeway.
- Reusing nonces — anti-replay broken.
- Outdated SHA1 in PasswordDigest — broken cryptographically.
Common interview confusions
- “WS-Security replaces TLS.” — they’re complementary. WS-Security is end-to-end through intermediaries; TLS is point-to-point. Use both.
- “WS-Security is just username/password.” — it’s a framework: tokens (username, X.509, SAML), signatures, encryption, timestamps. Pick what you need.
- “XML Signature is the same as JWT signature.” — both sign data; XML Signature is much more complex (canonicalization, references, transforms), historically with more vulnerabilities (XSW).
Interview angle
- “What is WS-Security?” — SOAP’s security framework for message-level signing and encryption. Lives in the
<wsse:Security>header. Includes tokens (username, X.509, SAML), XML Signature, XML Encryption, timestamps. - “Why WS-Security on top of TLS?” — TLS is point-to-point; terminates at each hop. WS-Security stays with the message through intermediaries (gateways, message brokers, audit logs). Required for multi-hop systems and regulated audit trails.
- “What’s UsernameToken?” — basic auth equivalent for SOAP: username + password (plaintext or SHA1 digest) + nonce + timestamp in the Security header. Plaintext is fine over TLS; SHA1 digest is cryptographically broken.
- “What’s XML Signature Wrapping?” — class of attacks where the signature validates one element but the parser processes a different one. Mitigation: same-element validation, schema-strict parsing, current libraries.
- “What’s XXE and why does it matter for SOAP?” — XML External Entity: attacker injects an entity that the XML parser resolves, fetching local files or causing SSRF. Disable DTD processing and external entity resolution in any XML parser.
- “Why might you sign without encrypting?” — sign for integrity + non-repudiation (audit trail showing who sent what). Encrypt for confidentiality. Many use cases want intermediaries to read messages (for routing, logging) but not modify them — signing-only matches.