Skip to content

Instantly share code, notes, and snippets.

@JLFN
Created June 24, 2026 07:05
Show Gist options
  • Select an option

  • Save JLFN/e97645746e4563548f1ecfc71bcd5a4c to your computer and use it in GitHub Desktop.

Select an option

Save JLFN/e97645746e4563548f1ecfc71bcd5a4c to your computer and use it in GitHub Desktop.
use anyhow::anyhow;
use serde_json::json;
use tracing;
use winnow::{
ascii::digit1,
token::take_until,
Parser,
};
/// Parses the initial numeric seed (`i`) from the Akamai JavaScript payload.
/// Looks for `var i = <number>` and extracts the numeric value.
fn parse_i(i: &mut &str) -> winnow::ModalResult<u64> {
let _ = take_until(0.., "var i = ").parse_next(i)?;
let _ = "var i = ".parse_next(i)?;
let num = digit1.parse_next(i)?;
Ok(num.parse::<u64>().unwrap())
}
/// Parses the secondary numeric seed string parts from the Akamai JavaScript payload.
/// Looks for `var j = i + Number("str1" + "str2");` and extracts `"str1"` and `"str2"`.
fn parse_j_strings<'a>(i: &mut &'a str) -> winnow::ModalResult<(&'a str, &'a str)> {
let _ = take_until(0.., "var j = i + Number(\"").parse_next(i)?;
let _ = "var j = i + Number(\"".parse_next(i)?;
let str1 = digit1.parse_next(i)?;
let _ = "\" + \"".parse_next(i)?;
let str2 = digit1.parse_next(i)?;
let _ = "\");".parse_next(i)?;
Ok((str1, str2))
}
/// Extracts the `bm-verify` cryptographic token from the JSON payload inside the
/// XMLHttpRequest send call. Looks for `"bm-verify": "<token>"`.
fn parse_bm_verify<'a>(i: &mut &'a str) -> winnow::ModalResult<&'a str> {
let _ = take_until(0.., "\"bm-verify\": \"").parse_next(i)?;
let _ = "\"bm-verify\": \"".parse_next(i)?;
take_until(0.., "\"").parse_next(i)
}
/// Scans the raw HTML string of an Akamai Interstitial challenge response and extracts
/// the mathematical Proof-of-Work (PoW) solution and the validation token (`bm-verify`).
///
/// The payload expects a calculated `pow` value, which is derived from a base seed `i`
/// and a string-concatenated offset parsed as a number.
///
/// # Returns
/// Returns a tuple containing:
/// 1. The solved PoW integer (`pow`).
/// 2. The extracted `bm-verify` string token.
pub fn extract_challenge_data(html: &str) -> anyhow::Result<(u64, String)> {
let mut input1 = html;
let i_val = parse_i(&mut input1).map_err(|e| anyhow!("Failed to parse i: {}", e))?;
let mut input2 = html;
let (s1, s2) = parse_j_strings(&mut input2).map_err(|e| anyhow!("Failed to parse j strings: {}", e))?;
let pow_str = format!("{}{}", s1, s2);
let pow_add = pow_str.parse::<u64>().unwrap_or(0);
let pow = i_val + pow_add;
let mut input3 = html;
let bm_verify = parse_bm_verify(&mut input3).map_err(|e| anyhow!("Failed to parse bm-verify: {}", e))?;
Ok((pow, bm_verify.to_string()))
}
/// Autonomously solves an Akamai Interstitial WAF challenge.
///
/// This function parses the HTML response containing the `triggerInterstitialChallenge`
/// JavaScript block, computes the mathematical Proof-of-Work, extracts the verification
/// token, and automatically submits the valid solution payload to the `/_sec/verify` endpoint.
///
/// Upon success, the validation cookie (`ak_bmsc` or similar) will be automatically persisted
/// within the provided `primp::Client`'s cookie store, allowing subsequent requests to bypass the WAF.
///
/// # Arguments
/// * `client` - The `primp::Client` instance (must have `cookie_store(true)` enabled).
/// * `url` - The original requested URL that triggered the challenge.
/// * `response_text` - The raw HTML response body containing the challenge JavaScript.
pub async fn solve_akamai_waf_async(
client: &primp::Client,
url: &str,
response_text: &str,
) -> anyhow::Result<Vec<(String, String)>> {
tracing::info!("Starting Akamai WAF challenge solving flow for {}", url);
let (pow, bm_verify) = extract_challenge_data(response_text)?;
let parsed_url = primp::Url::parse(url)?;
let domain = parsed_url.host_str().unwrap_or_default();
let verify_url = format!("https://{}/_sec/verify?provider=interstitial", domain);
let payload = json!({
"bm-verify": bm_verify,
"pow": pow
});
tracing::debug!("Sending Akamai challenge solution to {} with payload {:?}", verify_url, payload);
let resp = client
.request(primp::Method::POST, &verify_url)
.json(&payload)
.send()
.await?;
let status = resp.status().as_u16();
// Extract headers BEFORE calling .text() which moves the response
let mut cookies = Vec::new();
for cookie_val in resp.headers().get_all("set-cookie") {
if let Ok(cookie_str) = cookie_val.to_str() {
let first_part = cookie_str.split(';').next().unwrap_or("");
let parts: Vec<&str> = first_part.splitn(2, '=').collect();
if parts.len() == 2 {
cookies.push((parts[0].to_string(), parts[1].to_string()));
}
}
}
let resp_text = resp.text().await.unwrap_or_default();
tracing::debug!("Akamai WAF Verify response (status {}): {}", status, resp_text);
if status != 200 {
return Err(anyhow::anyhow!(
"Akamai WAF Verify failed: {}",
status
));
}
Ok(cookies)
}
<!DOCTYPE html><html><head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta http-equiv="refresh" content="5; URL='/s?k=B0BT221JSQ%7CB0H1TJLVC5%7CB0FYDGJ539%7CB0FN79C13N&amp;ref=nb_sb_noss&bm-verify=AAQAAAAN_____8nifE3l59lqy8juZoaPuJwLbvTVf0fzMq_cnlPcVAzeAs-B1xtQ40FTPAt9OfRjdNFyRmY2f7khUdwK9lOQB3xjN67Jrbk2PUjMlhDhM3MLlE-rc_oP_JRxW0ZlyGt4RWf6kbHGRn_wy83eQ-gDEJeSGGVmwYRyQ86XGMd25_SYb0cyMEyyJy5eF6yLqADHLjR5HIO3hgUCsklVs2H_yWJxy4nlZ96Phagv_0T_HE5Y3p6zD-BiehuilNjpBE3EFX3uNPE1-fmr_dzINP5OypSMbFDB-ZNkG57U_Xqcf9QoqFkhtgOmRF3sXR6rV5atSR2uDlBxkT0LBa75wcmpY0ST9w'" /><title>&nbsp;</title><script> var i = 1781368961; var j = i + Number("8795" + "81974"); </script> </head> <noscript> <iframe style="border: none; height: 100%; width: 100%;" src=""></iframe></noscript><body> <iframe style="border: none; width: 100vw; height: 100vh;" src="https://m.media-amazon.com/images/S/sash/6Uh4bsAwUkB3vJb.gif"> </iframe> <script> function triggerInterstitialChallenge() {var xhr = new XMLHttpRequest(); xhr.withCredentials = true; xhr.addEventListener("loadend", function() { try { var data = JSON.parse(xhr.responseText); if (data.hasOwnProperty('reload')) { if (data["reload"] == true) { window.location.replace(window.location.href.replace(/[&?]bm-verify=[^#]*/, "")); if(window.location.hash){ window.location.reload(); } } } else if (data.hasOwnProperty('location')) { window.location.replace(data["location"]); } else { window.location.reload(); } } catch (e) { window.location.reload(); }}); xhr.open("POST", "/_sec/verify?provider=interstitial", false); xhr.setRequestHeader("Content-Type", "application/json"); xhr.send(JSON.stringify({"bm-verify": "AAQAAAAN/////+bJf0D6I/nIQzep+rXn62gdXY6KZuD9JEWpXfyNh0Oh8/iJ7dJO39Jz8bDriKLZ9lSB0gXp26OMtQLK4wC2wPTGwznsOwY35flxWj4rIIXNzJnBx7vhUcrhKI5d3pVSwkhEhuC0KlNDHur9uiahCFwvTEyYWAmR5sZZ0KnXVV0x88SXQaYKoEIrhf9NUnETcdxIllqMMn+VOyIq1psCFJ/r/l7RF1j1zsIge224tywbyMetMQRXeWA3LJoxZACZwBkkOBiCvoC99sAOT6UFcF2PNSf363xdJCxfN8ELzTHGwrZad96kz2YAfQ8PAo9FNqBx+kLAkQ3Fq+EdXnto9mCz6s7QyDoep/R+LR9bhstynrY=", "pow": j}));} try {if (document.getElementById("akam-logo")) {document.getElementById("akam-logo").onload = function () {requestAnimationFrame(triggerInterstitialChallenge);}; document.getElementById("akam-logo").onerror = triggerInterstitialChallenge;} else {triggerInterstitialChallenge()}} catch(e) {triggerInterstitialChallenge();}</script> </body> </html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment