Severity
P0 — single-header omission grants full inbound webhook injection.
Files
crates/librefang-channels/src/line.rs:380-392
Finding
The handler reads x-line-signature with .unwrap_or(""), then guards the verify
call with if !signature.is_empty() && !verify_line_signature(...). An attacker
who simply omits the header passes both clauses and reaches the parse/dispatch
path with full webhook injection. Additionally the verifier is fed
serde_json::to_vec(&body.0) (re-serialized JSON), but LINE's HMAC is over the
raw bytes — implying signature verification is effectively unreachable for
legitimate LINE servers and only blocks forgers who bother to set the header.
Evidence
// crates/librefang-channels/src/line.rs:380-392
let signature = headers.get("x-line-signature").and_then(|v| v.to_str().ok()).unwrap_or("");
let body_bytes = serde_json::to_vec(&body.0).unwrap_or_default();
if !signature.is_empty() && !verify_line_signature(secret.as_bytes(), &body_bytes, signature) {
warn!("LINE: invalid webhook signature");
return axum::http::StatusCode::UNAUTHORIZED;
}
Suggested Fix
Switch to a Bytes body extractor (preserves raw bytes), require the header
(return 401 if absent), and verify HMAC-SHA256 over raw bytes — never
to_vec(parsed_json). Constant-time compare with subtle::ConstantTimeEq.
Severity
P0 — single-header omission grants full inbound webhook injection.
Files
crates/librefang-channels/src/line.rs:380-392Finding
The handler reads
x-line-signaturewith.unwrap_or(""), then guards the verifycall with
if !signature.is_empty() && !verify_line_signature(...). An attackerwho simply omits the header passes both clauses and reaches the parse/dispatch
path with full webhook injection. Additionally the verifier is fed
serde_json::to_vec(&body.0)(re-serialized JSON), but LINE's HMAC is over theraw bytes — implying signature verification is effectively unreachable for
legitimate LINE servers and only blocks forgers who bother to set the header.
Evidence
Suggested Fix
Switch to a
Bytesbody extractor (preserves raw bytes), require the header(return
401if absent), and verify HMAC-SHA256 over raw bytes — neverto_vec(parsed_json). Constant-time compare withsubtle::ConstantTimeEq.