A trackside router's TACACS+ traffic crosses a cable an attacker can tap and modify; without the shared secret, what can they still change in the body?
answer
- XOR is malleable
- no integrity check anywhere
- known plaintext names the pad octet
- first body octet is authen_method
- downgrade to line authentication
basics
~20 sAny octet whose plaintext they can predict. The body carries no integrity check, so flipping bits in a masked octet flips the plaintext under it. RFC 8907's worked case rewrites authen_method from TAC_PLUS_AUTHEN_METH_TACACSPLUS to TAC_PLUS_AUTHEN_METH_LINE.
solid answer
~40 sXOR masking is **malleable**: a ciphertext octet is the plaintext octet combined with one pad octet, so changing the ciphertext changes the plaintext by exactly the same amount. Where the attacker knows what the plaintext is, the change is not a guess — recovering that single pad octet is `ciphertext XOR known plaintext`, and re-masking any chosen value is one more XOR. RFC 8907 Section 10.3 works the case out: `authen_method` is the first octet of an authorization REQUEST body, and because deployments commonly authorize everything typed on a locally attached line without restriction, flipping `TAC_PLUS_AUTHEN_METH_TACACSPLUS := 0x06` to `TAC_PLUS_AUTHEN_METH_LINE := 0x03` can get every command approved. The shared secret is never recovered and no hash is broken.
code
pseudocode · 13 lines# attacker sits on the path; the body is masked, the header is readable
known = TAC_PLUS_AUTHEN_METH_TACACSPLUS # 0x06, the assumed plaintext
desired = TAC_PLUS_AUTHEN_METH_LINE # 0x03, what they want instead
observed = captured_body[0] # plaintext XOR pad octet
pad_octet = observed XOR known # recovered for this octet only
forged = desired XOR pad_octet # re-masked under the same pad
captured_body[0] = forged
forward(packet)
# header length unchanged, declared component lengths unchanged,
# so the receiver's reconciliation check still passesgo deeper
Recall that masking and protecting are different things: a body nobody can read can still be changed, because nothing in the packet vouches for its contents.
Explain the malleability arithmetic — ciphertext XOR known plaintext gives the pad octet, and one more XOR substitutes any chosen value at that offset.
Walk the worked case end to end, including why the length reconciliation check stays silent, and say what it means for a device on a cable run nobody inspects.
Weigh whether any part of the estate should keep a device-administration plane with no integrity, against the cost of moving it onto an authenticated transport.
## Why a keystream is malleable The classic TACACS+ transport masks the body by XOR against a `pseudo_pad`. That construction has a property people consistently underestimate: **an attacker who changes the ciphertext changes the plaintext in a controlled way, without ever learning either.** If `c = p XOR k`, then `c XOR d = (p XOR d) XOR k` for any `d` the attacker chooses. The receiver unmasks with the same `k` and gets `p XOR d`, and nothing in the protocol notices. The only thing standing between that property and a chosen substitution is knowing `p` at the offset you want to change. That is the role of **known plaintext**, and this protocol supplies it generously: the body layouts are published, several leading fields are one-octet enumerations with a handful of plausible values, and the common values are common precisely because they are the deployed ones. ## The lever, stated as arithmetic For one octet at a known offset: 1. The attacker observes the masked octet, `observed`. 2. They assume the plaintext, `known` — for `authen_method` in an authorization REQUEST from a device doing device-administration AAA, that is `TAC_PLUS_AUTHEN_METH_TACACSPLUS := 0x06`. 3. `pad_octet = observed XOR known` recovers the pad at that offset **exactly**, for that packet. 4. `forged = desired XOR pad_octet` re-masks whatever value they want, here `TAC_PLUS_AUTHEN_METH_LINE := 0x03`. 5. They write `forged` back and forward the packet. No other octet is touched, the shared secret is never recovered, and MD5 is not attacked at all. The attacker has borrowed one octet of the sender's own pad. ## Why the receiver does not notice The one consistency check the classic transport performs is a length reconciliation: after the body is unmasked, the component lengths declared inside it are summed and compared with the `length` field in the cleartext header, and a mismatch means the packet **MUST** be discarded and an ERROR signalled. That check is the reason a wrong shared secret is detected at all. It does nothing here. Replacing one one-octet enumeration with another one-octet enumeration changes no declared length, so the sums still reconcile and the packet is processed as valid. This is worth saying out loud, because a common wrong answer is that tampering corrupts the packet and the receiver drops it. | What the obfuscation stops | What it does not stop | |---|---| | A passive observer reading the body without the secret | A modification whose plaintext the attacker can predict | | Nothing else on the integrity axis | Replay of a recorded packet | | — | Offline guessing of the secret from a capture | | — | Chosen-plaintext sharpening of both of the above | ## Why this particular field is the specification's example `authen_method` tells the device-administration server how the user was authenticated. The specification is explicit that it is informational and not to be used in policy evaluation, but the reason Section 10.3 singles it out is a deployment habit rather than a protocol rule: **authorizing everything typed on a locally attached line is common**, so a policy that would refuse a command arriving with one method value approves it under another. The attacker does not need to guess which octet or what masks it, because known plaintext tells them both with certainty. One octet, one XOR, and every command goes through. ## What this does and does not require of the attacker - **Required:** a position on the path where packets can be modified in flight, not merely observed. On a cable run between unattended trackside sites that is a physical-access problem, and the specification's own advice is to assume an attacker with access to the data stream can read and modify everything. - **Required:** knowledge of the body layout and a confident guess at one plaintext octet. - **Not required:** the shared secret, any weakness in MD5's collision or preimage resistance, or the ability to read the rest of the body. ## What actually removes it Nothing inside the obfuscation does — the specification states the protocol offers no meaningful integrity, and integrity cannot be retrofitted into an XOR pad. The remedy is a transport that authenticates every byte: RFC 9887 carries TACACS+ inside TLS 1.3 on a separate well-known port, where record protection is authenticated and an in-flight modification breaks the record rather than being delivered. Short of that, the honest mitigation is to treat the path as hostile — keep it physically and topologically narrow, use a dedicated key per client so one break does not travel, and accept that an attacker on the wire can rewrite what the server is asked.
- Why does the length reconciliation check not catch this modification?Because it only compares the component lengths declared inside the unmasked body with the `length` field in the cleartext header. Substituting one one-octet enumeration for another changes neither number, so the sums reconcile and the packet is accepted. The check exists to catch a wrong shared secret, not tampering.
- Why is the substitution certain rather than probabilistic?Because XOR is exact and reversible per octet. Given the true plaintext at that offset, the pad octet is recovered with no residual uncertainty, and re-masking a chosen value is a second XOR against the same octet. The only guess is the assumed plaintext, and for a field with a handful of deployed values that is a short list.
- What does RFC 8907 offer to stop this rather than reduce it?Nothing within the obfuscation — the specification states the protocol provides no meaningful integrity. The remedy it points at is a protected transport. RFC 9887 defines that as TLS 1.3 on TCP port 300 with mandatory support for certificate-based mutual authentication, where a modified record fails authentication instead of being delivered.
saying these in an interview costs you the question
- Says the attacker must recover the shared secret first.
- Assumes tampering corrupts the packet so the receiver drops it.
- Thinks MD5 collision resistance is what is being broken.
- Believes an attacker must read the body to alter it usefully.
- Calls this a replay attack rather than in-flight modification.
- Says a longer shared secret would prevent the substitution.