• Rust基础:使用 trait objects添加模块与基础的查找漏洞方法
  • 发布于 1个月前
  • 82 热度
    0 评论
泛型的概念类比
想象你想给电脑添加一个摄像头,但电脑缺少摄像头接口。你买了一个网络摄像头,通过 USB 接口连接。现在你又想添加存储设备,买了一个外置硬盘,同样可以通过 USB 接口连接。

这就是泛型应用到物理设备上的例子。USB 接口是一个通用接口,连接到它上面的配件就是模块。我们不需要为每种设备都准备特定的接口,比如摄像头专用接口、硬盘专用接口等。几乎任何 USB 设备都可以连接到任何 USB 接口并正常工作。

Rust 中的泛型
泛型的目标是提高代码复用性,并通过允许函数、结构体和 trait 在后期定义类型来减少错误。在 Rust 中,函数、结构体和枚举都可以是泛型的。 示例:
// 泛型函数
fn generic_display<T: Display>(item: T) {
    println!("{}", item);
}
// 堆代码 duidaima.com
// 泛型结构体
struct Point<T> {
    x: T,
    y: T,
}
// 堆代码 duidaima.com
// 泛型枚举
enum Option<T> {
    Some(T),
    None,
}
通过泛型,我们可以编写一个逻辑适用于多种类型的算法,而无需为每种类型重复实现。例如,Vec 和 HashMap 等标准库集合类型就是通过泛型实现的。

Traits
Traits 定义了类型可以共享的行为,类似于其他语言中的接口。它们描述了类型的能力,并允许我们以通用的方式操作不同类型。

默认实现
Traits 可以为方法提供默认实现:
pub trait Hello {
    fn hello(&self) -> String {
        String::from("World")
    }
}
pub struct Sylvain;
impl Hello for Sylvain {
    fn hello(&self) -> String {
        String::from("Sylvain")
    }
}
pub struct Anonymous;
impl Hello for Anonymous {} // 使用默认实现
组合 Traits
Rust 不支持传统的类继承,而是通过组合多个 traits 来实现共享行为。例如:
pub trait Module {
    fn name(&self) -> String;
    fn description(&self) -> String;
}

#[async_trait]
pub trait SubdomainModule: Module {
    async fn enumerate(&self, domain: &str) -> Result<Vec<String>, Error>;
}

#[async_trait]
pub trait HttpModule: Module {
    async fn scan(
        &self,
        http_client: &Client,
        endpoint: &str,
    ) -> Result<Option<HttpFinding>, Error>;
}
Trait 对象
Trait 对象允许我们在运行时使用不同类型的值(即便它们的大小不同),只要它们实现了同一个 trait。这使得我们可以创建一个集合,存储不同类型的值,例如:
let modules: Vec<Box<dyn Module>> = vec![
    Box::new(SubdomainModuleImpl::new()),
    Box::new(HttpModuleImpl::new()),
];
静态分发 vs 动态分发
大部分代码可以查看:
• ch_04/snippets/dispatch/src/statik.rs
• ch_04/snippets/dispatch/src/dynamic.rs
1. 静态分发:在编译时确定类型。Rust 会为每种类型生成特定的代码(单态化),性能更高。
2. 动态分发:在运行时确定类型。通过虚表(vtable)实现,灵活性更高,但性能略有下降。
如果你想动态分配添加dyn关键词即可。
静态分发适合需要绝对性能的场景,而动态分发适合需要更大灵活性的场景。

实战:为扫描器添加模块
我们为扫描器添加两类模块:
1. 子域名模块:用于枚举子域名。
2. HTTP 模块:用于扫描端口和查找漏洞。
子域名模块
#[async_trait]
pub trait SubdomainModule: Module {
    async fn enumerate(&self, domain: &str) -> Result<Vec<String>, Error>;
}
HTTP 模块
#[async_trait]
pub trait HttpModule: Module {
    async fn scan(
        &self,
        http_client: &Client,
        endpoint: &str,
    ) -> Result<Option<HttpFinding>, Error>;
}
以下是一些具体的 HTTP 模块示例:
GitLab 开放注册检测 开放注册GitLab实例还记得向世界开放GitLab实例的故事吗?
// ch_04/tricoder/src/modules/http/gitlab_open_registrations.rs
#[async_trait]
impl HttpModule for GitlabOpenRegistrations {
    async fn scan(
        &self,
        http_client: &Client,
        endpoint: &str,
    ) -> Result<Option<HttpFinding>, Error> {
        let url = format!("{}/users/sign_up", &endpoint);
        let res = http_client.get(&url).send().await?;
        if res.status().is_success() && res.text().await?.contains("Register") {
            return Ok(Some(HttpFinding::GitlabOpenRegistrations(url)));
        }
        Ok(None)
    }
}
.git/HEAD 泄露检测
Git 文件泄露 另一个致命缺陷是 git 文件和目录泄露。当 nginx 或 Apache HTTPServer 服务的 PHP 应用程序配置错误时,通常会发生这种情况。该漏洞是为了让 git 文件可公开访问,例如 .git/configor.git/HEAD 。对于某些脚本,通常可以下载该项目的所有源代码。
https://github.com/liamg/gitjacker 有一天,我审核了一个朋友实习的公司的网站。

该博客(如果我没记错的话,Wordpress)容易受到此漏洞的影响-ability,我能够下载该项目的所有 git 历史记录。这很有趣,因为我可以访问我朋友实习时所做的所有提交。但更严重的是,数据库凭据是在代码中提交的…
//ch_04/tricoder/src/modules/http/git_head_disclosure.rs
#[async_trait]
impl HttpModule for GitHeadDisclosure {
    async fn scan(
        &self,
        http_client: &Client,
        endpoint: &str,
    ) -> Result<Option<HttpFinding>, Error> {
        let url = format!("{}/.git/HEAD", &endpoint);
        let res = http_client.get(&url).send().await?;
        if res.status().is_success() && res.text().await?.starts_with("ref:") {
            return Ok(Some(HttpFinding::GitHeadDisclosure(url)));
        }
        Ok(None)
    }
}
.env 文件泄露检测 .env 文件泄露 .env 文件泄露也是一种很容易被忽视的漏洞,但可能是致命的:它可能会泄露您的 Web 应用程序的所有秘密,例如数据库凭据、加密密钥…
// ch_04/tricoder/ src/modules/http/dotenv_disclosure.rs
#[async_trait]
impl HttpModule for DotEnvDisclosure {
    async fn scan(
        &self,
        http_client: &Client,
        endpoint: &str,
    ) -> Result<Option<HttpFinding>, Error> {
        let url = format!("{}/.env", &endpoint);
        let res = http_client.get(&url).send().await?;
        if res.status().is_success() {
            return Ok(Some(HttpFinding::DotEnvFileDisclosure(url)));
        }
        Ok(None)
    }
}
目录列表泄露(Directory Listing Disclosure)
目录列表泄露在使用 nginx 和 Apache 部署的 PHP 应用中非常常见。这种配置错误允许任何人访问服务器上的文件和文件夹。令人惊讶的是,通过一些简单的 Google Dorks,你就能获得大量的个人和企业数据。例如:
intitle:"index.of" "parent directory" "size"
通过这样的搜索语句,可以轻松找到暴露的目录和文件。这种问题虽然看起来很严重,但幸运的是,检测这种漏洞相对简单,并且可以轻松实现自动化。

自动化检测示例
以下是一个用于检测目录列表泄露的模块实现:
#[async_trait]
impl HttpModule for DirectoryListingDisclosure {
    async fn scan(
        &self,
        http_client: &Client,
        endpoint: &str,
    ) -> Result<Option<HttpFinding>, Error> {
        let url = format!("{}/", &endpoint);
        let res = http_client.get(&url).send().await?;
        if res.status().is_success() {
            let body = res.text().await?;
            if body.contains("Index of") && body.contains("Parent Directory") {
                return Ok(Some(HttpFinding::DirectoryListingDisclosure(url)));
            }
        }
        Ok(None)
    }
}
在这个实现中,我们通过发送一个简单的 GET 请求来检查目标 URL 是否暴露了目录列表。如果返回的页面中包含了类似 "Index of" 和 "Parent Directory" 的标志性内容,则可以确定存在目录列表泄露。

测试
测试是确保模块正确性的重要手段。Rust 提供了内置的测试框架。
示例
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_is_git_head() {
        let module = GitHeadDisclosure::new();
        assert!(module.is_head_file("ref: refs/heads/main"));
        assert!(!module.is_head_file("random content"));
    }
}
对于异步模块,可以使用 #[tokio::test] 进行测试:
#[tokio::test]
async fn test_directory_listing() {
    let module = DirectoryListingDisclosure::new();
    let body = "<title>Index of /</title>";
    assert!(module.is_directory_listing(body.to_string()).await.unwrap());
}
总结
• 泛型参数:用于绝对性能;trait 对象:用于更大灵活性。
• 在寻找高级漏洞之前,先检查配置错误。
• 测试时不要参考实现代码,而是根据规范编写测试。
• 使用 CI 工具自动化测试,而非手动运行。
通过模块化设计和合理使用泛型与 trait 对象,我们的扫描器可以轻松扩展功能,同时保持代码的清晰性和可维护性。

用户评论